add user routes

This commit is contained in:
2025-02-28 15:32:15 +05:30
parent 425d1f8c26
commit d2f3ebdb46
5 changed files with 148 additions and 20 deletions

View File

@@ -1,6 +1,12 @@
import { FastifyReply, FastifyRequest } from "fastify";
import { createUser, getUser } from "./user.service";
import { CreateUserInput } from "./user.schema";
import {
createUser,
deleteUser,
getUser,
listUsers,
updateUser,
} from "./user.service";
import { CreateUserInput, UpdateUserInput } from "./user.schema";
export async function createUserHandler(
req: FastifyRequest,
@@ -9,8 +15,7 @@ export async function createUserHandler(
const body = req.body as CreateUserInput;
try {
const authUser = req.user;
const user = await createUser(body, authUser.tenantId);
const user = await createUser(body, req.user);
return res.code(201).send(user);
} catch (err) {
return err;
@@ -49,3 +54,46 @@ export async function getUserHandler(req: FastifyRequest, res: FastifyReply) {
return err;
}
}
export async function listUserHandler(req: FastifyRequest, res: FastifyReply) {
try {
const users = await listUsers(req.user.tenantId);
return res.code(200).send({ users: users });
} catch (err) {
return err;
}
}
export async function updateUserHandler(
req: FastifyRequest,
res: FastifyReply
) {
const input = req.body as UpdateUserInput;
const { userId } = req.params as { userId: string };
try {
const updatedUser = await updateUser(userId, input);
if (!updateUser) return res.code(404).send({ error: "resource not found" });
return res.code(200).send(updatedUser);
} catch (err) {
return err;
}
}
export async function deleteUserHandler(
req: FastifyRequest,
res: FastifyReply
) {
const { userId } = req.params as { userId: string };
try {
const deleteResult = await deleteUser(userId, req.user.tenantId);
if (deleteResult.deletedCount == 0)
return res.code(404).send({ error: "resource not found" });
return res.code(204).send();
} catch (err) {
return err;
}
}

View File

@@ -1,8 +1,11 @@
import { FastifyInstance } from "fastify";
import {
createUserHandler,
deleteUserHandler,
getCurrentUserHandler,
getUserHandler,
listUserHandler,
updateUserHandler,
} from "./user.controller";
import { $user } from "./user.schema";
@@ -13,7 +16,7 @@ export default async function userRoutes(fastify: FastifyInstance) {
schema: {
body: $user("createUserInput"),
response: {
201: $user("createUserResponse"),
201: $user("userResponse"),
},
},
config: { requiredClaims: ["user:write"] },
@@ -27,7 +30,7 @@ export default async function userRoutes(fastify: FastifyInstance) {
{
schema: {
response: {
200: $user("createUserResponse"),
200: $user("userResponse"),
},
},
},
@@ -39,7 +42,7 @@ export default async function userRoutes(fastify: FastifyInstance) {
{
schema: {
response: {
200: $user("createUserResponse"),
200: $user("userResponse"),
},
},
config: { requiredClaims: ["user:read"] },
@@ -47,4 +50,34 @@ export default async function userRoutes(fastify: FastifyInstance) {
},
getUserHandler
);
fastify.get(
"/",
{
config: { requiredClaims: ["user:read"] },
preHandler: [fastify.authorize],
},
listUserHandler
);
fastify.patch(
"/:userId",
{
schema: {
body: $user("updateUserInput"),
},
config: { requiredClaims: ["user:write"] },
preHandler: [fastify.authorize],
},
updateUserHandler
);
fastify.delete(
"/:userId",
{
config: { requiredClaims: ["user:delete"] },
preHandler: [fastify.authorize],
},
deleteUserHandler
);
}

View File

@@ -80,11 +80,6 @@ const createUserInput = z
}
});
const createUserResponse = z.object({
pid: z.string().cuid2(),
...userCore,
});
const updateUserInput = z.object({
firstName: z.string().max(30).optional(),
lastName: z.string().max(30).optional(),
@@ -96,6 +91,23 @@ const updateUserInput = z.object({
.email()
.optional(),
avatar: z.string().url().optional(),
role: z.enum(roles).optional(),
});
const userResponse = z.object({
_id: z.string(),
pid: z.string(),
orgId: z.string().optional(),
firstName: z.string().optional(),
lastName: z.string().optional(),
name: z.string().optional(),
email: z.string().optional(),
role: z.string().optional(),
avatar: z.string().optional(),
status: z.string().optional(),
createdAt: z.string().optional(),
createdBy: z.string().optional(),
lastLogin: z.string().optional(),
});
export type CreateUserInput = z.infer<typeof createUserInput>;
@@ -104,7 +116,8 @@ export type UpdateUserInput = z.infer<typeof updateUserInput>;
export const { schemas: userSchemas, $ref: $user } = buildJsonSchemas(
{
createUserInput,
createUserResponse,
updateUserInput,
userResponse,
},
{ $id: "user" }
);

View File

@@ -2,19 +2,25 @@ import mongoose from "mongoose";
import { generateId, generateToken } from "../utils/id";
import { CreateUserInput, UpdateUserInput, userModel } from "./user.schema";
import { sendMail } from "../utils/mail";
import { AuthenticatedUser } from "../auth";
export async function createUser(input: CreateUserInput, tenantId: string) {
export async function createUser(
input: CreateUserInput,
user: AuthenticatedUser
) {
const token = await generateToken();
const user = await userModel.create({
tenantId: tenantId,
const newUser = await userModel.create({
tenantId: user.tenantId,
pid: generateId(),
name: input.firstName + " " + input.lastName,
createdAt: new Date(),
createdBy: user.userId,
token: {
value: token,
expiry: new Date(Date.now() + 3600 * 6 * 1000),
},
status: "invited",
...input,
});
@@ -22,11 +28,15 @@ export async function createUser(input: CreateUserInput, tenantId: string) {
input.email,
"You have been invited to Quicker Permtis.",
`Click <a href="${
process.env.SERVER_DOMAIN + "/auth/webauthn/register?token=" + token
process.env.SERVER_DOMAIN +
"/auth/webauthn/register?token=" +
token +
"&email=" +
newUser.email
}">here</a> to register.`
);
return user;
return newUser;
}
export async function getUser(userId: string) {
@@ -47,6 +57,26 @@ export async function getUserByEmail(email: string) {
return await userModel.findOne({ email: email });
}
export async function updateUser(userId: string, input: UpdateUserInput) {
return await userModel.findOneAndUpdate({ pid: userId }, input);
export async function listUsers(tenantId: string) {
return await userModel
.find({ tenantId: tenantId })
.select(
"_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin"
);
}
export async function updateUser(userId: string, input: UpdateUserInput) {
return await userModel
.findOneAndUpdate({ pid: userId }, input, {
new: true,
})
.select(
"_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin"
);
}
export async function deleteUser(userId: string, tenantId: string) {
return await userModel.deleteOne({
$and: [{ pid: userId }, { tenantId: tenantId }],
});
}

View File

@@ -112,6 +112,7 @@ export async function webAuthnRoutes(fastify: FastifyInstance) {
return res.code(400).send({ error: "registration failed" });
}
userInDB.status = "active";
userInDB.passKeys.push({
credentialID: verification.registrationInfo.credential.id,
credentialPublicKey:
@@ -238,6 +239,9 @@ export async function webAuthnRoutes(fastify: FastifyInstance) {
req.headers["user-agent"]
);
userInDB.lastLogin = new Date();
await userInDB.save();
res.send({ session_token: newSession.sid });
} catch (error) {
res.code(400).send({ error: (error as Error).message });