Pull Request branch dev-clone to main #1
|
|
@ -48,15 +48,20 @@ const permissionsData = [
|
||||||
code: "questions.restore",
|
code: "questions.restore",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code :"assessmentRequestManagement.readAll",
|
code: "managementAspect.readAll",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code: "assessmentRequestManagement.update",
|
code: "managementAspect.create",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
code :"assessmentRequestManagement.read",
|
code: "managementAspect.update",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "managementAspect.delete",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
code: "managementAspect.restore",
|
||||||
},
|
},
|
||||||
|
|
||||||
] as const;
|
] as const;
|
||||||
|
|
||||||
export type SpecificPermissionCode = (typeof permissionsData)[number]["code"];
|
export type SpecificPermissionCode = (typeof permissionsData)[number]["code"];
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,18 @@ const roleData: RoleData[] = [
|
||||||
name: "Super Admin",
|
name: "Super Admin",
|
||||||
permissions: permissionsData.map((permission) => permission.code),
|
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
|
// Manually specify the union of role codes
|
||||||
export type RoleCode = "super-admin" | "*";
|
export type RoleCode = "super-admin" | "user" | "*";
|
||||||
|
|
||||||
const exportedRoleData = roleData;
|
const exportedRoleData = roleData;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,8 @@ import { configDotenv } from "dotenv";
|
||||||
import { Hono } from "hono";
|
import { Hono } from "hono";
|
||||||
import authRoutes from "./routes/auth/route";
|
import authRoutes from "./routes/auth/route";
|
||||||
import usersRoute from "./routes/users/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 { verifyAccessToken } from "./utils/authUtils";
|
||||||
import permissionRoutes from "./routes/permissions/route";
|
import permissionRoutes from "./routes/permissions/route";
|
||||||
import { cors } from "hono/cors";
|
import { cors } from "hono/cors";
|
||||||
|
|
@ -81,7 +83,8 @@ const routes = app
|
||||||
.route("/roles", rolesRoute)
|
.route("/roles", rolesRoute)
|
||||||
.route("/dev", devRoutes)
|
.route("/dev", devRoutes)
|
||||||
.route("/questions", questionsRoute)
|
.route("/questions", questionsRoute)
|
||||||
.route("/assessmentRequestManagement",assessmentsRequestManagementRoutes)
|
.route("/management-aspect", managementAspectsRoute)
|
||||||
|
.route("/register", respondentsRoute)
|
||||||
.onError((err, c) => {
|
.onError((err, c) => {
|
||||||
if (err instanceof DashboardError) {
|
if (err instanceof DashboardError) {
|
||||||
return c.json(
|
return c.json(
|
||||||
|
|
|
||||||
492
apps/backend/src/routes/managementAspect/route.ts
Normal file
492
apps/backend/src/routes/managementAspect/route.ts
Normal file
|
|
@ -0,0 +1,492 @@
|
||||||
|
import { and, eq, ilike, isNull, or, sql } from "drizzle-orm";
|
||||||
|
import { Hono } from "hono";
|
||||||
|
|
||||||
|
import { z } from "zod";
|
||||||
|
import db from "../../drizzle";
|
||||||
|
import { aspects } from "../../drizzle/schema/aspects";
|
||||||
|
import { subAspects } from "../../drizzle/schema/subAspects";
|
||||||
|
import HonoEnv from "../../types/HonoEnv";
|
||||||
|
import requestValidator from "../../utils/requestValidator";
|
||||||
|
import authInfo from "../../middlewares/authInfo";
|
||||||
|
import checkPermission from "../../middlewares/checkPermission";
|
||||||
|
import { forbidden } from "../../errors/DashboardError";
|
||||||
|
import { notFound } from "../../errors/DashboardError";
|
||||||
|
|
||||||
|
// Schema for creating and updating aspects
|
||||||
|
export const aspectFormSchema = z.object({
|
||||||
|
name: z.string().min(1).max(50),
|
||||||
|
subAspects: z
|
||||||
|
.string()
|
||||||
|
.refine(
|
||||||
|
(data) => {
|
||||||
|
try {
|
||||||
|
const parsed = JSON.parse(data);
|
||||||
|
return Array.isArray(parsed);
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
message: "Sub Aspects must be an array",
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const aspectUpdateSchema = aspectFormSchema.extend({
|
||||||
|
subAspects: z.string().optional().or(z.literal("")),
|
||||||
|
});
|
||||||
|
|
||||||
|
// Schema for creating and updating subAspects
|
||||||
|
export const subAspectFormSchema = z.object({
|
||||||
|
name: z.string().min(1).max(50),
|
||||||
|
aspectId: z.string().uuid(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const subAspectUpdateSchema = subAspectFormSchema.extend({});
|
||||||
|
|
||||||
|
const managementAspectRoute = new Hono<HonoEnv>()
|
||||||
|
.use(authInfo)
|
||||||
|
/**
|
||||||
|
* Get All Aspects (With Metadata)
|
||||||
|
*
|
||||||
|
* Query params:
|
||||||
|
* - includeTrashed: boolean (default: false)
|
||||||
|
* - withMetadata: boolean
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Get all aspects
|
||||||
|
.get(
|
||||||
|
"/",
|
||||||
|
checkPermission("managementAspect.readAll"),
|
||||||
|
requestValidator(
|
||||||
|
"query",
|
||||||
|
z.object({
|
||||||
|
includeTrashed: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v?.toLowerCase() === "true"),
|
||||||
|
withMetadata: z
|
||||||
|
.string()
|
||||||
|
.optional()
|
||||||
|
.transform((v) => v?.toLowerCase() === "true"),
|
||||||
|
page: z.coerce.number().int().min(0).default(0),
|
||||||
|
limit: z.coerce.number().int().min(1).max(1000).default(10),
|
||||||
|
q: z.string().default(""),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const { includeTrashed, page, limit, q } = c.req.valid("query");
|
||||||
|
|
||||||
|
const totalCountQuery = includeTrashed
|
||||||
|
? sql<number>`(SELECT count(*) FROM ${aspects})`
|
||||||
|
: sql<number>`(SELECT count(*) FROM ${aspects} WHERE ${aspects.deletedAt} IS NULL)`;
|
||||||
|
|
||||||
|
const result = await db
|
||||||
|
.select({
|
||||||
|
id: aspects.id,
|
||||||
|
name: aspects.name,
|
||||||
|
createdAt: aspects.createdAt,
|
||||||
|
updatedAt: aspects.updatedAt,
|
||||||
|
...(includeTrashed ? { deletedAt: aspects.deletedAt } : {}),
|
||||||
|
fullCount: totalCountQuery,
|
||||||
|
})
|
||||||
|
.from(aspects)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
includeTrashed ? undefined : isNull(aspects.deletedAt),
|
||||||
|
q ? or(ilike(aspects.name, q), eq(aspects.id, q)) : undefined
|
||||||
|
)
|
||||||
|
)
|
||||||
|
.offset(page * limit)
|
||||||
|
.limit(limit);
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
data: result.map((d) => ({ ...d, fullCount: undefined })),
|
||||||
|
_metadata: {
|
||||||
|
currentPage: page,
|
||||||
|
totalPages: Math.ceil((Number(result[0]?.fullCount) ?? 0) / limit),
|
||||||
|
totalItems: Number(result[0]?.fullCount) ?? 0,
|
||||||
|
perPage: limit,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get aspect by id
|
||||||
|
.get(
|
||||||
|
"/:id",
|
||||||
|
checkPermission("managementAspect.readAll"),
|
||||||
|
requestValidator(
|
||||||
|
"query",
|
||||||
|
z.object({
|
||||||
|
includeTrashed: z.string().default("false"),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const aspectId = c.req.param("id");
|
||||||
|
|
||||||
|
if (!aspectId)
|
||||||
|
throw notFound({
|
||||||
|
message: "Missing id",
|
||||||
|
});
|
||||||
|
|
||||||
|
const includeTrashed = c.req.query("includeTrashed")?.toLowerCase() === "true";
|
||||||
|
|
||||||
|
const queryResult = await db
|
||||||
|
.select({
|
||||||
|
id: aspects.id,
|
||||||
|
name: aspects.name,
|
||||||
|
createdAt: aspects.createdAt,
|
||||||
|
updatedAt: aspects.updatedAt,
|
||||||
|
...(includeTrashed ? { deletedAt: aspects.deletedAt } : {}),
|
||||||
|
subAspect: {
|
||||||
|
name: subAspects.name,
|
||||||
|
id: subAspects.id,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
.from(aspects)
|
||||||
|
.leftJoin(subAspects, eq(aspects.id, subAspects.aspectId))
|
||||||
|
.where(and(eq(aspects.id, aspectId), !includeTrashed ? isNull(aspects.deletedAt) : undefined));
|
||||||
|
|
||||||
|
if (!queryResult.length)
|
||||||
|
throw forbidden({
|
||||||
|
message: "The aspect does not exist",
|
||||||
|
});
|
||||||
|
|
||||||
|
const subAspectsList = queryResult.reduce((prev, curr) => {
|
||||||
|
if (!curr.subAspect) return prev;
|
||||||
|
prev.set(curr.subAspect.id, curr.subAspect.name);
|
||||||
|
return prev;
|
||||||
|
}, new Map<string, string>()); // Map<id, name>
|
||||||
|
|
||||||
|
const aspectData = {
|
||||||
|
...queryResult[0],
|
||||||
|
subAspect: undefined,
|
||||||
|
subAspects: Array.from(subAspectsList, ([id, name]) => ({ id, name })),
|
||||||
|
};
|
||||||
|
|
||||||
|
return c.json(aspectData);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create aspect
|
||||||
|
.post("/",
|
||||||
|
checkPermission("managementAspect.create"),
|
||||||
|
requestValidator("json", aspectFormSchema),
|
||||||
|
async (c) => {
|
||||||
|
const aspectData = c.req.valid("json");
|
||||||
|
|
||||||
|
// Validation to check if the aspect name already exists
|
||||||
|
const existingAspect = await db
|
||||||
|
.select()
|
||||||
|
.from(aspects)
|
||||||
|
.where(eq(aspects.name, aspectData.name));
|
||||||
|
|
||||||
|
if (existingAspect.length > 0) {
|
||||||
|
throw forbidden({
|
||||||
|
message: "Aspect name already exists",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const aspect = await db
|
||||||
|
.insert(aspects)
|
||||||
|
.values({
|
||||||
|
name: aspectData.name,
|
||||||
|
})
|
||||||
|
.returning();
|
||||||
|
|
||||||
|
// if sub-aspects are available, parse them into a string array
|
||||||
|
if (aspectData.subAspects) {
|
||||||
|
const subAspectsArray = JSON.parse(aspectData.subAspects) as string[];
|
||||||
|
// if there are sub-aspects, insert them into the database
|
||||||
|
if (subAspectsArray.length) {
|
||||||
|
await db.insert(subAspects).values(
|
||||||
|
subAspectsArray.map((subAspect) => ({
|
||||||
|
aspectId: aspect[0].id,
|
||||||
|
name: subAspect,
|
||||||
|
}))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
message: "Aspect created successfully",
|
||||||
|
},
|
||||||
|
201
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update aspect
|
||||||
|
.patch(
|
||||||
|
"/:id",
|
||||||
|
checkPermission("managementAspect.update"),
|
||||||
|
requestValidator("json", aspectUpdateSchema),
|
||||||
|
async (c) => {
|
||||||
|
const aspectId = c.req.param("id");
|
||||||
|
const aspectData = c.req.valid("json");
|
||||||
|
|
||||||
|
// Validation to check if the new aspect name already exists
|
||||||
|
const existingAspect = await db
|
||||||
|
.select()
|
||||||
|
.from(aspects)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(aspects.name, aspectData.name),
|
||||||
|
isNull(aspects.deletedAt),
|
||||||
|
sql`${aspects.id} <> ${aspectId}`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (existingAspect.length > 0) {
|
||||||
|
throw forbidden({
|
||||||
|
message: "Aspect name already exists",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const aspect = await db
|
||||||
|
.select()
|
||||||
|
.from(aspects)
|
||||||
|
.where(and(eq(aspects.id, aspectId), isNull(aspects.deletedAt)));
|
||||||
|
|
||||||
|
if (!aspect[0]) throw notFound();
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(aspects)
|
||||||
|
.set({
|
||||||
|
...aspectData,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(aspects.id, aspectId));
|
||||||
|
|
||||||
|
//Update for Sub-Aspects
|
||||||
|
// if (aspectData.subAspects) {
|
||||||
|
// const subAspectsArray = JSON.parse(aspectData.subAspects) as string[];
|
||||||
|
|
||||||
|
// await db.delete(subAspects).where(eq(subAspects.aspectId, aspectId));
|
||||||
|
|
||||||
|
// if (subAspectsArray.length) {
|
||||||
|
// await db.insert(subAspects).values(
|
||||||
|
// subAspectsArray.map((subAspect) => ({
|
||||||
|
// aspectId: aspectId,
|
||||||
|
// name: subAspect,
|
||||||
|
// }))
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
message: "Aspect updated successfully",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Delete aspect
|
||||||
|
.delete(
|
||||||
|
"/:id",
|
||||||
|
checkPermission("managementAspect.delete"),
|
||||||
|
requestValidator(
|
||||||
|
"form",
|
||||||
|
z.object({
|
||||||
|
// skipTrash: z.string().default("false"),
|
||||||
|
})
|
||||||
|
),
|
||||||
|
async (c) => {
|
||||||
|
const aspectId = c.req.param("id");
|
||||||
|
|
||||||
|
// const skipTrash = c.req.valid("form").skipTrash.toLowerCase() === "true";
|
||||||
|
|
||||||
|
const aspect = await db
|
||||||
|
.select()
|
||||||
|
.from(aspects)
|
||||||
|
.where(and(eq(aspects.id, aspectId), isNull(aspects.deletedAt)));
|
||||||
|
|
||||||
|
if (!aspect[0])
|
||||||
|
throw notFound({
|
||||||
|
message: "The aspect is not found",
|
||||||
|
});
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(aspects)
|
||||||
|
.set({
|
||||||
|
deletedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(aspects.id, aspectId));
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
message: "Aspect deleted successfully",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Undo delete
|
||||||
|
.patch(
|
||||||
|
"/restore/:id",
|
||||||
|
checkPermission("managementAspect.restore"),
|
||||||
|
async (c) => {
|
||||||
|
const aspectId = c.req.param("id");
|
||||||
|
|
||||||
|
const aspect = (await db.select().from(aspects).where(eq(aspects.id, aspectId)))[0];
|
||||||
|
|
||||||
|
if (!aspect) throw notFound();
|
||||||
|
|
||||||
|
if (!aspect.deletedAt) {
|
||||||
|
throw forbidden({
|
||||||
|
message: "The aspect is not deleted",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
await db.update(aspects).set({ deletedAt: null }).where(eq(aspects.id, aspectId));
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
message: "Aspect restored successfully",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get sub aspects by aspect ID
|
||||||
|
.get(
|
||||||
|
"/subAspects/:aspectId",
|
||||||
|
checkPermission("managementAspect.readAll"),
|
||||||
|
async (c) => {
|
||||||
|
const aspectId = c.req.param("aspectId");
|
||||||
|
|
||||||
|
const aspect = await db
|
||||||
|
.select()
|
||||||
|
.from(aspects)
|
||||||
|
.where(and(
|
||||||
|
eq(aspects.id, aspectId),
|
||||||
|
isNull(aspects.deletedAt)));
|
||||||
|
|
||||||
|
if (!aspect[0])
|
||||||
|
throw notFound({
|
||||||
|
message: "The aspect is not found",
|
||||||
|
});
|
||||||
|
|
||||||
|
const subAspectsData = await db
|
||||||
|
.select()
|
||||||
|
.from(subAspects)
|
||||||
|
.where(eq(subAspects.aspectId, aspectId));
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
subAspects: subAspectsData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Create sub aspect
|
||||||
|
.post(
|
||||||
|
"/subAspect",
|
||||||
|
checkPermission("managementAspect.create"),
|
||||||
|
requestValidator("json", subAspectFormSchema),
|
||||||
|
async (c) => {
|
||||||
|
const subAspectData = c.req.valid("json");
|
||||||
|
|
||||||
|
// Validation to check if the sub aspect name already exists
|
||||||
|
const existingSubAspect = await db
|
||||||
|
.select()
|
||||||
|
.from(subAspects)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(subAspects.name, subAspectData.name),
|
||||||
|
eq(subAspects.aspectId, subAspectData.aspectId)));
|
||||||
|
|
||||||
|
if (existingSubAspect.length > 0) {
|
||||||
|
throw forbidden({ message: "Nama Sub Aspek sudah tersedia!" });
|
||||||
|
}
|
||||||
|
|
||||||
|
const [aspect] = await db
|
||||||
|
.select()
|
||||||
|
.from(aspects)
|
||||||
|
.where(
|
||||||
|
and(
|
||||||
|
eq(aspects.id, subAspectData.aspectId),
|
||||||
|
isNull(aspects.deletedAt)));
|
||||||
|
|
||||||
|
if (!aspect)
|
||||||
|
throw forbidden({
|
||||||
|
message: "The aspect is not found",
|
||||||
|
});
|
||||||
|
|
||||||
|
await db.insert(subAspects).values(subAspectData);
|
||||||
|
|
||||||
|
return c.json(
|
||||||
|
{
|
||||||
|
message: "Sub aspect created successfully",
|
||||||
|
},
|
||||||
|
201
|
||||||
|
);
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update sub aspect
|
||||||
|
.patch(
|
||||||
|
"/subAspect/:id", checkPermission("managementAspect.update"),
|
||||||
|
requestValidator("json", subAspectUpdateSchema),
|
||||||
|
async (c) => {
|
||||||
|
const subAspectId = c.req.param("id");
|
||||||
|
const subAspectData = c.req.valid("json");
|
||||||
|
|
||||||
|
// Validation to check if the new sub aspect name already exists
|
||||||
|
const existingSubAspect = await db
|
||||||
|
.select()
|
||||||
|
.from(subAspects)
|
||||||
|
.where(
|
||||||
|
eq(subAspects.aspectId, subAspectData.aspectId));
|
||||||
|
|
||||||
|
if (existingSubAspect.length > 0) {
|
||||||
|
throw forbidden({ message: "Name Sub Aspect already exists" });
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!existingSubAspect[0])
|
||||||
|
throw notFound({
|
||||||
|
message: "The sub aspect is not found",
|
||||||
|
});
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(subAspects)
|
||||||
|
.set({
|
||||||
|
...subAspectData,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(subAspects.id, subAspectId));
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
message: "Sub aspect updated successfully",
|
||||||
|
});
|
||||||
|
})
|
||||||
|
|
||||||
|
// Delete sub aspect
|
||||||
|
.delete(
|
||||||
|
"/subAspect/:id",
|
||||||
|
checkPermission("managementAspect.delete"),
|
||||||
|
async (c) => {
|
||||||
|
const subAspectId = c.req.param("id");
|
||||||
|
|
||||||
|
const subAspect = await db
|
||||||
|
.select()
|
||||||
|
.from(subAspects)
|
||||||
|
.where(eq(subAspects.id, subAspectId));
|
||||||
|
|
||||||
|
if (!subAspect[0])
|
||||||
|
throw notFound({
|
||||||
|
message: "The sub aspect is not found",
|
||||||
|
});
|
||||||
|
|
||||||
|
await db
|
||||||
|
.update(subAspects)
|
||||||
|
.set({
|
||||||
|
deletedAt: new Date(),
|
||||||
|
})
|
||||||
|
.where(eq(subAspects.id, subAspectId));
|
||||||
|
// await db.delete(subAspects).where(eq(subAspects.id, subAspectId));
|
||||||
|
|
||||||
|
return c.json({
|
||||||
|
message: "Sub aspect deleted successfully",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default managementAspectRoute;
|
||||||
131
apps/backend/src/routes/register/route.ts
Normal file
131
apps/backend/src/routes/register/route.ts
Normal file
|
|
@ -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<HonoEnv>()
|
||||||
|
.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;
|
||||||
Loading…
Reference in New Issue
Block a user