Update : API for Users

This commit is contained in:
percyfikri 2024-09-11 09:55:44 +07:00
parent 282b4dfae3
commit 7643a7d5d7

View File

@ -1,4 +1,4 @@
import { and, eq, ilike, isNull, or, sql } from "drizzle-orm"; import { and, eq, ilike, isNull, or, sql, not } from "drizzle-orm";
import { Hono } from "hono"; import { Hono } from "hono";
import { z } from "zod"; import { z } from "zod";
@ -12,12 +12,19 @@ import HonoEnv from "../../types/HonoEnv";
import requestValidator from "../../utils/requestValidator"; import requestValidator from "../../utils/requestValidator";
import authInfo from "../../middlewares/authInfo"; import authInfo from "../../middlewares/authInfo";
import checkPermission from "../../middlewares/checkPermission"; import checkPermission from "../../middlewares/checkPermission";
import { respondents } from "../../drizzle/schema/respondents";
import { notFound } from "../../errors/DashboardError";
export const userFormSchema = z.object({ export const userFormSchema = z.object({
name: z.string().min(1).max(255), name: z.string().min(1).max(255),
username: z.string().min(1).max(255), username: z.string().min(1).max(255),
email: z.string().email().optional().or(z.literal("")), email: z.string().email().optional().or(z.literal("")),
password: z.string().min(6), 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"), isEnabled: z.string().default("false"),
roles: z roles: z
.string() .string()
@ -87,9 +94,14 @@ const usersRoute = new Hono<HonoEnv>()
createdAt: users.createdAt, createdAt: users.createdAt,
updatedAt: users.updatedAt, updatedAt: users.updatedAt,
...(includeTrashed ? { deletedAt: users.deletedAt } : {}), ...(includeTrashed ? { deletedAt: users.deletedAt } : {}),
company: respondents.companyName,
roles: rolesSchema.name,
fullCount: totalCountQuery, fullCount: totalCountQuery,
}) })
.from(users) .from(users)
.leftJoin(respondents, eq(users.id, respondents.userId))
.leftJoin(rolesToUsers, eq(users.id, rolesToUsers.userId))
.leftJoin(rolesSchema, eq(rolesToUsers.roleId, rolesSchema.id))
.where( .where(
and( and(
includeTrashed ? undefined : isNull(users.deletedAt), includeTrashed ? undefined : isNull(users.deletedAt),
@ -139,7 +151,12 @@ const usersRoute = new Hono<HonoEnv>()
.select({ .select({
id: users.id, id: users.id,
name: users.name, name: users.name,
position: respondents.position,
workExperience: respondents.workExperience,
email: users.email, email: users.email,
companyName: respondents.companyName,
address: respondents.address,
phoneNumber: respondents.phoneNumber,
username: users.username, username: users.username,
isEnabled: users.isEnabled, isEnabled: users.isEnabled,
createdAt: users.createdAt, createdAt: users.createdAt,
@ -151,6 +168,7 @@ const usersRoute = new Hono<HonoEnv>()
}, },
}) })
.from(users) .from(users)
.leftJoin(respondents, eq(users.id, respondents.userId))
.leftJoin(rolesToUsers, eq(users.id, rolesToUsers.userId)) .leftJoin(rolesToUsers, eq(users.id, rolesToUsers.userId))
.leftJoin(rolesSchema, eq(rolesToUsers.roleId, rolesSchema.id)) .leftJoin(rolesSchema, eq(rolesToUsers.roleId, rolesSchema.id))
.where( .where(
@ -184,34 +202,124 @@ const usersRoute = new Hono<HonoEnv>()
.post( .post(
"/", "/",
checkPermission("users.create"), checkPermission("users.create"),
requestValidator("form", userFormSchema), requestValidator("json", userFormSchema),
async (c) => { async (c) => {
const userData = c.req.valid("form"); const userData = c.req.valid("json");
const user = await db // Check if the provided email or username is already exists in database
const conditions = [];
if (userData.email) {
conditions.push(eq(users.email, userData.email));
}
conditions.push(eq(users.username, userData.username));
const existingUser = await db
.select()
.from(users)
.where(
or(
eq(users.email, userData.email),
eq(users.username, userData.username)
)
);
const existingRespondent = await db
.select()
.from(respondents)
.where(eq(respondents.phoneNumber, userData.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(userData.password);
// Start a transaction
const result = await db.transaction(async (trx) => {
// Create user
const [newUser] = await trx
.insert(users) .insert(users)
.values({ .values({
name: userData.name, name: userData.name,
username: userData.username, username: userData.username,
email: userData.email, email: userData.email,
password: await hashPassword(userData.password), password: hashedPassword,
isEnabled: userData.isEnabled.toLowerCase() === "true", isEnabled: userData.isEnabled?.toLowerCase() === "true" || true,
}) })
.returning(); .returning()
.catch(() => {
throw new HTTPException(500, { message: "Error creating user" });
});
// Create respondent
const [newRespondent] = await trx
.insert(respondents)
.values({
companyName: userData.companyName,
position: userData.position,
workExperience: userData.workExperience,
address: userData.address,
phoneNumber: userData.phoneNumber,
userId: newUser.id,
})
.returning()
.catch((err) => {
throw new HTTPException(500, {
message: "Error creating respondent: " + err.message,
});
});
// 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,
});
// Add other roles if provided
if (userData.roles) { if (userData.roles) {
const roles = JSON.parse(userData.roles) as string[]; const roles = JSON.parse(userData.roles) as string[];
console.log(roles);
if (roles.length) { for (let roleCode of roles) {
await db.insert(rolesToUsers).values( const role = (
roles.map((role) => ({ await trx
userId: user[0].id, .select()
roleId: role, .from(rolesSchema)
})) .where(eq(rolesSchema.code, roleCode))
); .limit(1)
)[0];
if (role) {
await trx.insert(rolesToUsers).values({
userId: newUser.id,
roleId: role.id,
});
} else {
throw new HTTPException(404, {
message: `Role ${roleCode} does not exists`,
});
} }
} }
}
return newUser;
});
return c.json( return c.json(
{ {
@ -226,10 +334,32 @@ const usersRoute = new Hono<HonoEnv>()
.patch( .patch(
"/:id", "/:id",
checkPermission("users.update"), checkPermission("users.update"),
requestValidator("form", userUpdateSchema), requestValidator("json", userUpdateSchema),
async (c) => { async (c) => {
const userId = c.req.param("id"); const userId = c.req.param("id");
const userData = c.req.valid("form"); const userData = c.req.valid("json");
// Check if the provided email or username is already exists in the database (excluding the current user)
if (userData.email || userData.username) {
const existingUser = await db
.select()
.from(users)
.where(
and(
or(
eq(users.email, userData.email),
eq(users.username, userData.username)
),
not(eq(users.id, userId))
)
);
if (existingUser.length > 0) {
throw new HTTPException(400, {
message: "Email or username has been registered by another user",
});
}
}
const user = await db const user = await db
.select() .select()
@ -238,7 +368,10 @@ const usersRoute = new Hono<HonoEnv>()
if (!user[0]) return c.notFound(); if (!user[0]) return c.notFound();
await db // Start transaction to update both user and respondent
await db.transaction(async (trx) => {
// Update user
await trx
.update(users) .update(users)
.set({ .set({
...userData, ...userData,
@ -250,6 +383,52 @@ const usersRoute = new Hono<HonoEnv>()
}) })
.where(eq(users.id, userId)); .where(eq(users.id, userId));
// Update respondent data if provided
if (userData.companyName || userData.position || userData.workExperience || userData.address || userData.phoneNumber) {
await trx
.update(respondents)
.set({
...(userData.companyName ? {companyName: userData.companyName} : {}),
...(userData.position ? {position: userData.position} : {}),
...(userData.workExperience ? {workExperience: userData.workExperience} : {}),
...(userData.address ? {address: userData.address} : {}),
...(userData.phoneNumber ? {phoneNumber: userData.phoneNumber} : {}),
updatedAt: new Date(),
})
.where(eq(respondents.userId, userId));
}
// Update roles if provided
if (userData.roles) {
const roles = JSON.parse(userData.roles) as string[];
// Remove existing roles for the user
await trx.delete(rolesToUsers).where(eq(rolesToUsers.userId, userId));
// Assign new roles
for (let roleCode of roles) {
const role = (
await trx
.select()
.from(rolesSchema)
.where(eq(rolesSchema.code, roleCode))
.limit(1)
)[0];
if (role) {
await trx.insert(rolesToUsers).values({
userId: userId,
roleId: role.id,
});
} else {
throw new HTTPException(404, {
message: `Role ${roleCode} does not exist`,
});
}
}
}
});
return c.json({ return c.json({
message: "User updated successfully", message: "User updated successfully",
}); });
@ -273,6 +452,7 @@ const usersRoute = new Hono<HonoEnv>()
const skipTrash = const skipTrash =
c.req.valid("form").skipTrash.toLowerCase() === "true"; c.req.valid("form").skipTrash.toLowerCase() === "true";
// Check if the user exists
const user = await db const user = await db
.select() .select()
.from(users) .from(users)
@ -283,17 +463,20 @@ const usersRoute = new Hono<HonoEnv>()
) )
); );
// Throw error if the user does not exist
if (!user[0]) if (!user[0])
throw new HTTPException(404, { throw new HTTPException(404, {
message: "The user is not found", message: "The user is not found",
}); });
// Throw error if the user is trying to delete themselves
if (user[0].id === currentUserId) { if (user[0].id === currentUserId) {
throw new HTTPException(400, { throw new HTTPException(400, {
message: "You cannot delete yourself", message: "You cannot delete yourself",
}); });
} }
// Delete or soft delete user
if (skipTrash) { if (skipTrash) {
await db.delete(users).where(eq(users.id, userId)); await db.delete(users).where(eq(users.id, userId));
} else { } else {
@ -311,21 +494,27 @@ const usersRoute = new Hono<HonoEnv>()
) )
//undo delete //undo delete
.patch("/restore/:id", checkPermission("users.restore"), async (c) => { .patch(
"/restore/:id",
checkPermission("users.restore"),
async (c) => {
const userId = c.req.param("id"); const userId = c.req.param("id");
// Check if the user exists
const user = ( const user = (
await db.select().from(users).where(eq(users.id, userId)) await db.select().from(users).where(eq(users.id, userId))
)[0]; )[0];
if (!user) return c.notFound(); if (!user) return c.notFound();
// Throw error if the user is not deleted
if (!user.deletedAt) { if (!user.deletedAt) {
throw new HTTPException(400, { throw new HTTPException(400, {
message: "The user is not deleted", message: "The user is not deleted",
}); });
} }
// Restore user
await db await db
.update(users) .update(users)
.set({ deletedAt: null }) .set({ deletedAt: null })