mirror of
https://github.com/jeffvli/feishin.git
synced 2026-05-08 13:00:13 +02:00
Add initial users manager
This commit is contained in:
@@ -25,7 +25,7 @@ const createUser = async (
|
||||
req: TypedRequest<typeof validation.users.createUser>,
|
||||
res: Response
|
||||
) => {
|
||||
const user = await service.users.createUser(req.body);
|
||||
const user = await service.users.createUser(req.authUser, req.body);
|
||||
const success = ApiSuccess.ok({ data: toApiModel.users([user])[0] });
|
||||
return res.status(success.statusCode).json(getSuccessResponse(success));
|
||||
};
|
||||
|
||||
@@ -2,25 +2,48 @@ import express, { Router } from 'express';
|
||||
import { controller } from '@controllers/index';
|
||||
import { service } from '@services/index';
|
||||
import { ApiError } from '@utils/index';
|
||||
import { validation } from '@validations/index';
|
||||
import { validateRequest } from '@validations/shared.validation';
|
||||
import { authenticateAdmin } from '../middleware/authenticate-admin';
|
||||
|
||||
export const router: Router = express.Router({ mergeParams: true });
|
||||
|
||||
router
|
||||
.route('/')
|
||||
.get(authenticateAdmin, controller.users.getUserList)
|
||||
.post(authenticateAdmin, controller.users.createUser);
|
||||
.get(
|
||||
authenticateAdmin,
|
||||
validateRequest(validation.users.list),
|
||||
controller.users.getUserList
|
||||
)
|
||||
.post(
|
||||
authenticateAdmin,
|
||||
validateRequest(validation.users.createUser),
|
||||
controller.users.createUser
|
||||
);
|
||||
|
||||
router.param('userId', async (req, _res, next, userId) => {
|
||||
await service.users.findById(req.authUser, { id: userId });
|
||||
const user = await service.users.findById(req.authUser, { id: userId });
|
||||
|
||||
if (req.authUser.isAdmin || req.authUser.id === userId) {
|
||||
if (req.authUser.id === userId) {
|
||||
return next();
|
||||
}
|
||||
|
||||
throw ApiError.forbidden('You are not allowed to access this resource');
|
||||
// Only superadmins can modify other admins
|
||||
if (user.isAdmin && !req.authUser.isSuperAdmin) {
|
||||
throw ApiError.forbidden('You are not authorized to access this resource');
|
||||
}
|
||||
|
||||
return next();
|
||||
});
|
||||
|
||||
router.route('/:userId/update').post(controller.users.updateUser);
|
||||
|
||||
router.route('/:userId/delete').post(controller.users.deleteUser);
|
||||
router
|
||||
.route('/:userId')
|
||||
.get(validateRequest(validation.users.detail), controller.users.getUserDetail)
|
||||
.patch(
|
||||
validateRequest(validation.users.updateUser),
|
||||
controller.users.updateUser
|
||||
)
|
||||
.delete(
|
||||
validateRequest(validation.users.deleteUser),
|
||||
controller.users.deleteUser
|
||||
);
|
||||
|
||||
@@ -27,61 +27,82 @@ const findMany = async () => {
|
||||
return users;
|
||||
};
|
||||
|
||||
const createUser = async (options: {
|
||||
displayName?: string;
|
||||
password: string;
|
||||
username: string;
|
||||
}) => {
|
||||
const { password, username, displayName } = options;
|
||||
const createUser = async (
|
||||
user: AuthUser,
|
||||
options: {
|
||||
displayName?: string;
|
||||
isAdmin?: boolean;
|
||||
password: string;
|
||||
username: string;
|
||||
}
|
||||
) => {
|
||||
const { password, username, displayName, isAdmin } = options;
|
||||
|
||||
const [userExists, displayNameExists] = await prisma.$transaction([
|
||||
prisma.user.findUnique({ where: { username } }),
|
||||
prisma.user.findUnique({ where: { displayName } }),
|
||||
]);
|
||||
if (isAdmin && !user.isSuperAdmin) {
|
||||
throw ApiError.badRequest('You are not authorized to create an admin.');
|
||||
}
|
||||
|
||||
const userExists = await prisma.user.findUnique({ where: { username } });
|
||||
|
||||
if (userExists) {
|
||||
throw ApiError.conflict('The user already exists.');
|
||||
}
|
||||
|
||||
const displayNameExists = await prisma.user.findUnique({
|
||||
where: { displayName },
|
||||
});
|
||||
|
||||
if (displayNameExists) {
|
||||
throw ApiError.conflict('The display name already exists.');
|
||||
}
|
||||
|
||||
const hashedPassword = await bcrypt.hash(password, 12);
|
||||
|
||||
const user = await prisma.user.create({
|
||||
const createdUser = await prisma.user.create({
|
||||
data: {
|
||||
deviceId: `${username}_${randomString(10)}`,
|
||||
enabled: false,
|
||||
isAdmin,
|
||||
password: hashedPassword,
|
||||
username,
|
||||
},
|
||||
});
|
||||
|
||||
return user;
|
||||
return createdUser;
|
||||
};
|
||||
|
||||
const deleteUser = async (options: { userId: string }) => {
|
||||
const { userId } = options;
|
||||
|
||||
const user = await prisma.user.delete({ where: { id: userId } });
|
||||
return user;
|
||||
const user = await prisma.user.findUnique({ where: { id: userId } });
|
||||
|
||||
if (!user) {
|
||||
throw ApiError.notFound('The user does not exist.');
|
||||
}
|
||||
|
||||
if (user?.isSuperAdmin) {
|
||||
throw ApiError.badRequest('You cannot delete a superadmin.');
|
||||
}
|
||||
|
||||
await prisma.user.delete({ where: { id: userId } });
|
||||
};
|
||||
|
||||
const updateUser = async (
|
||||
options: { userId: string },
|
||||
data: {
|
||||
displayName?: string;
|
||||
isAdmin?: boolean;
|
||||
password?: string;
|
||||
username?: string;
|
||||
}
|
||||
) => {
|
||||
const { userId } = options;
|
||||
const { username, password } = data;
|
||||
const { username, password, isAdmin, displayName } = data;
|
||||
|
||||
const hashedPassword = password && (await bcrypt.hash(password, 12));
|
||||
|
||||
const user = await prisma.user.update({
|
||||
data: { password: hashedPassword, username },
|
||||
data: { displayName, isAdmin, password: hashedPassword, username },
|
||||
where: { id: userId },
|
||||
});
|
||||
|
||||
|
||||
@@ -1,6 +1,14 @@
|
||||
import { z } from 'zod';
|
||||
import { idValidation } from './shared.validation';
|
||||
|
||||
const noWhiteSpaces = /^\S*$/;
|
||||
|
||||
const list = {
|
||||
body: z.object({}),
|
||||
params: z.object({}),
|
||||
query: z.object({}),
|
||||
};
|
||||
|
||||
const detail = {
|
||||
body: z.object({}),
|
||||
params: z.object({ ...idValidation('userId') }),
|
||||
@@ -9,9 +17,16 @@ const detail = {
|
||||
|
||||
const createUser = {
|
||||
body: z.object({
|
||||
displayName: z.optional(z.string()),
|
||||
displayName: z.optional(z.string().min(0).max(100)),
|
||||
isAdmin: z.optional(z.boolean()),
|
||||
password: z.string().min(6).max(255),
|
||||
username: z.string().min(2).max(255),
|
||||
username: z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.refine((value) => noWhiteSpaces.test(value), {
|
||||
message: 'No white spaces allowed',
|
||||
}),
|
||||
}),
|
||||
params: z.object({}),
|
||||
query: z.object({}),
|
||||
@@ -25,9 +40,18 @@ const deleteUser = {
|
||||
|
||||
const updateUser = {
|
||||
body: z.object({
|
||||
displayName: z.optional(z.string().min(2).max(255)),
|
||||
displayName: z.optional(z.string().min(0).max(100)),
|
||||
isAdmin: z.optional(z.boolean()),
|
||||
password: z.optional(z.string().min(6).max(255)),
|
||||
username: z.optional(z.string().min(2).max(255)),
|
||||
username: z.optional(
|
||||
z
|
||||
.string()
|
||||
.min(2)
|
||||
.max(30)
|
||||
.refine((value) => noWhiteSpaces.test(value), {
|
||||
message: 'No white spaces allowed',
|
||||
})
|
||||
),
|
||||
}),
|
||||
params: z.object({ ...idValidation('userId') }),
|
||||
query: z.object({}),
|
||||
@@ -37,5 +61,6 @@ export const usersValidation = {
|
||||
createUser,
|
||||
deleteUser,
|
||||
detail,
|
||||
list,
|
||||
updateUser,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user