diff --git a/apps/backend/src/data/roles.ts b/apps/backend/src/data/roles.ts index 2c42327..d1c46bb 100644 --- a/apps/backend/src/data/roles.ts +++ b/apps/backend/src/data/roles.ts @@ -17,10 +17,18 @@ const roleData: RoleData[] = [ name: "Super Admin", permissions: permissionsData.map((permission) => permission.code), }, + { + code: "user", + description: + "User with standard access rights for general usage of the application.", + isActive: true, + name: "User", + permissions: permissionsData.map((permission) => permission.code), + }, ]; // Manually specify the union of role codes -export type RoleCode = "super-admin" | "*"; +export type RoleCode = "super-admin" | "user" | "*"; const exportedRoleData = roleData; diff --git a/apps/backend/src/index.ts b/apps/backend/src/index.ts index dd8c885..14cf329 100644 --- a/apps/backend/src/index.ts +++ b/apps/backend/src/index.ts @@ -4,6 +4,7 @@ import { Hono } from "hono"; import authRoutes from "./routes/auth/route"; import usersRoute from "./routes/users/route"; import managementAspectsRoute from "./routes/managementAspect/route"; +import respondentsRoute from "./routes/register/route"; import { verifyAccessToken } from "./utils/authUtils"; import permissionRoutes from "./routes/permissions/route"; import { cors } from "hono/cors"; @@ -82,6 +83,7 @@ const routes = app .route("/dev", devRoutes) .route("/questions", questionsRoute) .route("/management-aspect", managementAspectsRoute) + .route("/register", respondentsRoute) .onError((err, c) => { if (err instanceof DashboardError) { return c.json( diff --git a/apps/backend/src/routes/register/route.ts b/apps/backend/src/routes/register/route.ts new file mode 100644 index 0000000..4be7398 --- /dev/null +++ b/apps/backend/src/routes/register/route.ts @@ -0,0 +1,131 @@ +import { Hono } from "hono"; +import { HTTPException } from "hono/http-exception"; +import db from "../../drizzle"; +import { respondents } from "../../drizzle/schema/respondents"; +import { users } from "../../drizzle/schema/users"; +import { rolesSchema } from "../../drizzle/schema/roles"; +import { rolesToUsers } from "../../drizzle/schema/rolesToUsers"; +import { hashPassword } from "../../utils/passwordUtils"; +import requestValidator from "../../utils/requestValidator"; +import authInfo from "../../middlewares/authInfo"; +import { or, eq } from "drizzle-orm"; +import { z } from "zod"; +import HonoEnv from "../../types/HonoEnv"; +import { notFound } from "../../errors/DashboardError"; + +const registerFormSchema = z.object({ + name: z.string().min(1).max(255), + username: z.string().min(1).max(255), + email: z.string().email(), + password: z.string().min(6), + companyName: z.string().min(1).max(255), + position: z.string().min(1).max(255), + workExperience: z.string().min(1).max(255), + address: z.string().min(1), + phoneNumber: z.string().min(1).max(13), + isEnabled: z.string().default("false"), +}); + +const respondentsRoute = new Hono() + .use(authInfo) + //post user + .post("/", requestValidator("json", registerFormSchema), async (c) => { + const formData = c.req.valid("json"); + + // Check if the provided email or username is already exists in database + const conditions = []; + if (formData.email) { + conditions.push(eq(users.email, formData.email)); + } + conditions.push(eq(users.username, formData.username)); + + const existingUser = await db + .select() + .from(users) + .where( + or( + eq(users.email, formData.email), + eq(users.username, formData.username) + ) + ); + + const existingRespondent = await db + .select() + .from(respondents) + .where(eq(respondents.phoneNumber, formData.phoneNumber)); + + if (existingUser.length > 0) { + throw new HTTPException(400, { + message: "Email or username has been registered", + }); + } + + if (existingRespondent.length > 0) { + throw new HTTPException(400, { + message: "Phone number has been registered", + }); + } + + // Hash the password + const hashedPassword = await hashPassword(formData.password); + + // Start a transaction + const result = await db.transaction(async (trx) => { + // Create user + const [newUser] = await trx + .insert(users) + .values({ + name: formData.name, + username: formData.username, + email: formData.email, + password: hashedPassword, + isEnabled: formData.isEnabled?.toLowerCase() === "true" || true, + }) + .returning() + .catch(() => { + throw new HTTPException(500, { message: "Error creating user" }); + }); + + // Create respondent + await trx + .insert(respondents) + .values({ + companyName: formData.companyName, + position: formData.position, + workExperience: formData.workExperience, + address: formData.address, + phoneNumber: formData.phoneNumber, + userId: newUser.id, + }) + .catch(() => { + throw new HTTPException(500, { + message: "Error creating respondent", + }); + }); + + // Automatically assign "user" role to the new user + const [role] = await trx + .select() + .from(rolesSchema) + .where(eq(rolesSchema.code, "user")) + .limit(1); + + if (!role) throw notFound(); + + await trx.insert(rolesToUsers).values({ + userId: newUser.id, + roleId: role.id, + }); + + return newUser; + }); + + return c.json( + { + message: "User created successfully", + }, + 201 + ); + }); + +export default respondentsRoute;