Pull Request branch dev-clone to main #1
|
|
@ -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 })
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user