From 5800069e6baaf934609b8c685a2847220aa07e6f Mon Sep 17 00:00:00 2001 From: Akhil Meka Date: Sat, 3 May 2025 17:02:37 +0530 Subject: [PATCH] added client role and related code --- src/organization/organization.service.ts | 38 +++---- src/permit/permit.controller.ts | 4 +- src/permit/permit.service.ts | 135 +++++++++++++---------- src/processed/processed.route.ts | 58 +++++----- src/processed/processed.service.ts | 67 +++++------ src/user/user.controller.ts | 25 +++-- src/user/user.schema.ts | 33 +++--- src/user/user.service.ts | 37 ++++--- src/utils/roles.ts | 17 +++ 9 files changed, 229 insertions(+), 185 deletions(-) diff --git a/src/organization/organization.service.ts b/src/organization/organization.service.ts index 0d85395..9f2df38 100644 --- a/src/organization/organization.service.ts +++ b/src/organization/organization.service.ts @@ -1,12 +1,12 @@ -import { getFilterObject, getSortObject, PageQueryParams } from '../pagination'; -import { ChangeEvent, dbEvents } from '../realtime'; -import { generateId } from '../utils/id'; +import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; +import { ChangeEvent, dbEvents } from "../realtime"; +import { generateId } from "../utils/id"; import { CreateOrgInput, orgFields, orgModel, UpdateOrgInput, -} from './organization.schema'; +} from "./organization.schema"; export async function createOrg(input: CreateOrgInput, tenantId: string) { const org = await orgModel.create({ @@ -17,13 +17,13 @@ export async function createOrg(input: CreateOrgInput, tenantId: string) { }); dbEvents.emit( - 'change', + "change", { - type: 'insert', - collection: 'orgs', + type: "insert", + collection: "orgs", document: org, } as ChangeEvent, - ['org:read'] + ["org:read"] ); return org; @@ -45,7 +45,7 @@ export async function listOrgs(params: PageQueryParams, tenantId: string) { { $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] } }, { $facet: { - metadata: [{ $count: 'count' }], + metadata: [{ $count: "count" }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, @@ -83,13 +83,13 @@ export async function updateOrg( if (updateOrgResult) { dbEvents.emit( - 'change', + "change", { - type: 'update', - collection: 'orgs', + type: "update", + collection: "orgs", document: updateOrgResult, } as ChangeEvent, - ['org:read'] + ["org:read"] ); } @@ -103,15 +103,15 @@ export async function deleteOrg(orgId: string, tenantId: string) { if (res.deletedCount > 0) { dbEvents.emit( - 'change', + "change", { - type: 'delete', - collection: 'orgs', + type: "delete", + collection: "orgs", document: { pid: orgId, }, } as ChangeEvent, - ['org:read'] + ["org:read"] ); } @@ -127,7 +127,7 @@ export async function searchOrgs(params: PageQueryParams, tenantId: string) { if (!params.searchToken) return { orgs: [], metadata: { count: 0, page, pageSize } }; - const regex = new RegExp(params.searchToken, 'i'); + const regex = new RegExp(params.searchToken, "i"); const orgs = await orgModel.aggregate([ { $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] } }, @@ -138,7 +138,7 @@ export async function searchOrgs(params: PageQueryParams, tenantId: string) { }, { $facet: { - metadata: [{ $count: 'count' }], + metadata: [{ $count: "count" }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, diff --git a/src/permit/permit.controller.ts b/src/permit/permit.controller.ts index 9e91e42..a21ea63 100644 --- a/src/permit/permit.controller.ts +++ b/src/permit/permit.controller.ts @@ -47,7 +47,7 @@ export async function listPermitsHandler( try { const authUser = req.user; - const orgList = await listPermits(queryParams, authUser.tenantId); + const orgList = await listPermits(queryParams, authUser); return res.code(200).send(orgList); } catch (err) { return err; @@ -97,7 +97,7 @@ export async function searchPermitHandler( try { const authUser = req.user; - const permitList = await searchPermit(queryParams, authUser.tenantId); + const permitList = await searchPermit(queryParams, authUser); return res.code(200).send(permitList); } catch (err) { return err; diff --git a/src/permit/permit.service.ts b/src/permit/permit.service.ts index 18a3469..aaedfbb 100644 --- a/src/permit/permit.service.ts +++ b/src/permit/permit.service.ts @@ -1,16 +1,17 @@ -import { orgModel } from '../organization/organization.schema'; -import { getFilterObject, getSortObject, PageQueryParams } from '../pagination'; -import { userModel } from '../user/user.schema'; -import { generateId } from '../utils/id'; +import { orgModel } from "../organization/organization.schema"; +import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; +import { userModel } from "../user/user.schema"; +import { generateId } from "../utils/id"; import { CreatePermitInput, permitFields, permitModel, UpdatePermitInput, -} from './permit.schema'; -import { ChangeEvent, dbEvents } from '../realtime'; -import { permitPipeline } from '../utils/pipeline'; -import { AuthenticatedUser } from '../auth'; +} from "./permit.schema"; +import { ChangeEvent, dbEvents } from "../realtime"; +import { permitPipeline } from "../utils/pipeline"; +import { AuthenticatedUser } from "../auth"; +import mongoose from "mongoose"; export async function createPermit( input: CreatePermitInput, @@ -32,13 +33,13 @@ export async function createPermit( }); dbEvents.emit( - 'change', + "change", { - type: 'insert', - collection: 'permits', + type: "insert", + collection: "permits", document: permit, } as ChangeEvent, - ['permit:read'] + ["permit:read"] ); return permit; @@ -51,26 +52,33 @@ export async function getPermit(permitId: string, tenantId: string) { }) //.populate({ path: "county", select: "pid name avatar" }) //.populate({ path: "client", select: "pid name avatar" }) - .populate({ path: 'assignedTo', select: 'pid name avatar' }) - .populate({ path: 'createdBy', select: 'pid name avatar' }); + .populate({ path: "assignedTo", select: "pid name avatar" }) + .populate({ path: "createdBy", select: "pid name avatar" }); } -export async function listPermits(params: PageQueryParams, tenantId: string) { +export async function listPermits( + params: PageQueryParams, + user: AuthenticatedUser +) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, permitFields); - const filterObj = getFilterObject(params); + const filterObj = getFilterObject(params) || {}; + + if (user.role == "client") { + filterObj["client"] = new mongoose.Types.ObjectId(user.orgId); + } const permitsList = await permitModel.aggregate([ { - $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, + $match: { $and: [{ tenantId: user.tenantId }, { ...filterObj }] }, }, { $lookup: { - from: 'users', - localField: 'assignedTo', - foreignField: '_id', - as: 'assignedRec', + from: "users", + localField: "assignedTo", + foreignField: "_id", + as: "assignedRec", }, }, { @@ -112,12 +120,12 @@ export async function listPermits(params: PageQueryParams, tenantId: string) { statusUpdated: 1, assignedTo: { $let: { - vars: { assigned: { $arrayElemAt: ['$assignedRec', 0] } }, + vars: { assigned: { $arrayElemAt: ["$assignedRec", 0] } }, in: { - _id: '$$assigned._id', - pid: '$$assigned.pid', - name: '$$assigned.name', - avatar: '$$assigned.avatar', + _id: "$$assigned._id", + pid: "$$assigned.pid", + name: "$$assigned.name", + avatar: "$$assigned.avatar", }, }, }, @@ -125,7 +133,7 @@ export async function listPermits(params: PageQueryParams, tenantId: string) { }, { $facet: { - metadata: [{ $count: 'count' }], + metadata: [{ $count: "count" }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, @@ -161,20 +169,20 @@ export async function updatePermit( { ...input, lastUpdateDate: new Date() }, { new: true } ) - .populate({ path: 'county', select: 'pid name avatar' }) - .populate({ path: 'client', select: 'pid name avatar' }) - .populate({ path: 'assignedTo', select: 'pid name avatar' }) - .populate({ path: 'createdBy', select: 'pid name avatar' }); + .populate({ path: "county", select: "pid name avatar" }) + .populate({ path: "client", select: "pid name avatar" }) + .populate({ path: "assignedTo", select: "pid name avatar" }) + .populate({ path: "createdBy", select: "pid name avatar" }); if (updatePermitResult) { dbEvents.emit( - 'change', + "change", { - type: 'update', - collection: 'permits', + type: "update", + collection: "permits", document: updatePermitResult, } as ChangeEvent, - ['permit:read'] + ["permit:read"] ); } @@ -187,50 +195,57 @@ export async function deletePermit(permitId: string, tenantId: string) { }); dbEvents.emit( - 'change', + "change", { - type: 'delete', - collection: 'permits', + type: "delete", + collection: "permits", document: { pid: permitId, }, } as ChangeEvent, - ['permit:read'] + ["permit:read"] ); return res; } -export async function searchPermit(params: PageQueryParams, tenantId: string) { +export async function searchPermit( + params: PageQueryParams, + user: AuthenticatedUser +) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, permitFields); - const filterObj = getFilterObject(params); + const filterObj = getFilterObject(params) || {}; + + if (user.role == "client") { + filterObj["client"] = new mongoose.Types.ObjectId(user.orgId); + } if (!params.searchToken) return { permits: [], metadata: { count: 0, page, pageSize } }; - const regex = new RegExp(params.searchToken, 'i'); + const regex = new RegExp(params.searchToken, "i"); const permitsList = await permitModel.aggregate([ { - $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, + $match: { $and: [{ tenantId: user.tenantId }, { ...filterObj }] }, }, { $match: { $or: [ { permitNumber: { $regex: regex } }, { link: { $regex: regex } }, - { 'address.full_address': { $regex: regex } }, + { "address.full_address": { $regex: regex } }, ], }, }, { $lookup: { - from: 'users', - localField: 'assignedTo', - foreignField: '_id', - as: 'assignedRec', + from: "users", + localField: "assignedTo", + foreignField: "_id", + as: "assignedRec", }, }, { @@ -272,12 +287,12 @@ export async function searchPermit(params: PageQueryParams, tenantId: string) { statusUpdated: 1, assignedTo: { $let: { - vars: { assigned: { $arrayElemAt: ['$assignedRec', 0] } }, + vars: { assigned: { $arrayElemAt: ["$assignedRec", 0] } }, in: { - _id: '$$assigned._id', - pid: '$$assigned.pid', - name: '$$assigned.name', - avatar: '$$assigned.avatar', + _id: "$$assigned._id", + pid: "$$assigned.pid", + name: "$$assigned.name", + avatar: "$$assigned.avatar", }, }, }, @@ -285,7 +300,7 @@ export async function searchPermit(params: PageQueryParams, tenantId: string) { }, { $facet: { - metadata: [{ $count: 'count' }], + metadata: [{ $count: "count" }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, @@ -312,12 +327,12 @@ export async function getUniqueValuesPermit(field: string, tenenatId: string) { let values = await permitModel.distinct(field, { tenantId: tenenatId }); let matchedValues = []; - if (field === 'county.name') { - matchedValues = await orgModel.find().where('name').in(values).exec(); - } else if (field === 'client') { - matchedValues = await orgModel.find().where('_id').in(values).exec(); - } else if (field === 'assignedTo') { - matchedValues = await userModel.find().where('name').in(values).exec(); + if (field === "county.name") { + matchedValues = await orgModel.find().where("name").in(values).exec(); + } else if (field === "client") { + matchedValues = await orgModel.find().where("_id").in(values).exec(); + } else if (field === "assignedTo") { + matchedValues = await userModel.find().where("name").in(values).exec(); } if (matchedValues.length > 0) { diff --git a/src/processed/processed.route.ts b/src/processed/processed.route.ts index b8c4768..7bdc2bd 100644 --- a/src/processed/processed.route.ts +++ b/src/processed/processed.route.ts @@ -1,29 +1,29 @@ -import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; -import { PageQueryParams } from '../pagination'; +import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify"; +import { PageQueryParams } from "../pagination"; import { getProcessedPermit, getUniqueValuesProcessed, listProcessedPermits, updateProcessed, -} from './processed.service'; -import { $processed, UpdateProcessedInput } from './processed.schema'; -import { noteRoutes } from '../note/note.route'; +} from "./processed.service"; +import { $processed, UpdateProcessedInput } from "./processed.schema"; +import { noteRoutes } from "../note/note.route"; export async function processedRoutes(fastify: FastifyInstance) { fastify.get( - '/', + "/", { schema: { - querystring: $processed('pageQueryParams'), + querystring: $processed("pageQueryParams"), }, - config: { requiredClaims: ['permit:read'] }, + config: { requiredClaims: ["permit:read"] }, preHandler: [fastify.authorize], }, async (req: FastifyRequest, res: FastifyReply) => { const params = req.query as PageQueryParams; try { - const permits = await listProcessedPermits(params, req.user.tenantId); + const permits = await listProcessedPermits(params, req.user); return res.code(200).send(permits); } catch (err) { return err; @@ -32,19 +32,19 @@ export async function processedRoutes(fastify: FastifyInstance) { ); fastify.get( - '/search', + "/search", { schema: { - querystring: $processed('pageQueryParams'), + querystring: $processed("pageQueryParams"), }, - config: { requiredClaims: ['permit:read'] }, + config: { requiredClaims: ["permit:read"] }, preHandler: [fastify.authorize], }, async (req: FastifyRequest, res: FastifyReply) => { const params = req.query as PageQueryParams; try { - const permits = await listProcessedPermits(params, req.user.tenantId); + const permits = await listProcessedPermits(params, req.user); return res.code(200).send(permits); } catch (err) { return err; @@ -53,15 +53,15 @@ export async function processedRoutes(fastify: FastifyInstance) { ); fastify.get( - '/:permitId', + "/:permitId", { schema: { params: { - type: 'object', - properties: { permitId: { type: 'string' } }, + type: "object", + properties: { permitId: { type: "string" } }, }, }, - config: { requiredClaims: ['permit:read'] }, + config: { requiredClaims: ["permit:read"] }, preHandler: [fastify.authorize], }, async (req: FastifyRequest, res: FastifyReply) => { @@ -77,16 +77,16 @@ export async function processedRoutes(fastify: FastifyInstance) { ); fastify.patch( - '/:permitId', + "/:permitId", { schema: { params: { - type: 'object', - properties: { permitId: { type: 'string' } }, + type: "object", + properties: { permitId: { type: "string" } }, }, - body: $processed('updateProcessedInput'), + body: $processed("updateProcessedInput"), }, - config: { requiredClaims: ['permit:write'] }, + config: { requiredClaims: ["permit:write"] }, preHandler: [fastify.authorize], }, async (req: FastifyRequest, res: FastifyReply) => { @@ -103,17 +103,17 @@ export async function processedRoutes(fastify: FastifyInstance) { ); fastify.get( - '/fields/:field', + "/fields/:field", { schema: { params: { - type: 'object', + type: "object", properties: { - field: { type: 'string' }, + field: { type: "string" }, }, }, }, - config: { requiredClaims: ['permit:read'] }, + config: { requiredClaims: ["permit:read"] }, preHandler: [fastify.authorize], }, async (req: FastifyRequest, res: FastifyReply) => { @@ -132,8 +132,8 @@ export async function processedRoutes(fastify: FastifyInstance) { ); await noteRoutes(fastify, { - read: 'permit:read', - write: 'permit:write', - delete: 'permit:delete', + read: "permit:read", + write: "permit:write", + delete: "permit:delete", }); } diff --git a/src/processed/processed.service.ts b/src/processed/processed.service.ts index 2a99019..a11502d 100644 --- a/src/processed/processed.service.ts +++ b/src/processed/processed.service.ts @@ -1,12 +1,13 @@ -import { AuthenticatedUser } from '../auth'; -import { orgModel } from '../organization/organization.schema'; -import { getFilterObject, getSortObject, PageQueryParams } from '../pagination'; -import { userModel } from '../user/user.schema'; +import mongoose from "mongoose"; +import { AuthenticatedUser } from "../auth"; +import { orgModel } from "../organization/organization.schema"; +import { getFilterObject, getSortObject, PageQueryParams } from "../pagination"; +import { userModel } from "../user/user.schema"; import { processedFields, processedModel, UpdateProcessedInput, -} from './processed.schema'; +} from "./processed.schema"; export async function getProcessedPermit(permitId: String, tenantId: String) { return await processedModel.findOne({ @@ -27,35 +28,39 @@ export async function updateProcessed( { ...input, lastUpdateDate: new Date() }, { new: true } ) - .populate({ path: 'county', select: 'pid name avatar' }) - .populate({ path: 'client', select: 'pid name avatar' }) - .populate({ path: 'assignedTo', select: 'pid name avatar' }) - .populate({ path: 'createdBy', select: 'pid name avatar' }); + .populate({ path: "county", select: "pid name avatar" }) + .populate({ path: "client", select: "pid name avatar" }) + .populate({ path: "assignedTo", select: "pid name avatar" }) + .populate({ path: "createdBy", select: "pid name avatar" }); } export async function listProcessedPermits( params: PageQueryParams, - tenantId: string + user: AuthenticatedUser ) { const page = params.page || 1; const pageSize = params.pageSize || 10; const sortObj = getSortObject(params, processedFields); - const filterObj = getFilterObject(params); + const filterObj = getFilterObject(params) || {}; + + if (user.role == "client") { + filterObj["client"] = new mongoose.Types.ObjectId(user.orgId); + } const pipeline: any = [ { - $match: { $and: [{ tenantId: tenantId }, { ...filterObj }] }, + $match: { $and: [{ tenantId: user.tenantId }, { ...filterObj }] }, }, ]; - if (params.searchToken && params.searchToken != '') { - const regex = new RegExp(params.searchToken, 'i'); + if (params.searchToken && params.searchToken != "") { + const regex = new RegExp(params.searchToken, "i"); pipeline.push({ $match: { $or: [ { permitNumber: { $regex: regex } }, { link: { $regex: regex } }, - { 'address.full_address': { $regex: regex } }, + { "address.full_address": { $regex: regex } }, ], }, }); @@ -65,10 +70,10 @@ export async function listProcessedPermits( ...[ { $lookup: { - from: 'users', - localField: 'assignedTo', - foreignField: '_id', - as: 'assignedRec', + from: "users", + localField: "assignedTo", + foreignField: "_id", + as: "assignedRec", }, }, { @@ -111,12 +116,12 @@ export async function listProcessedPermits( transferDate: 1, assignedTo: { $let: { - vars: { assigned: { $arrayElemAt: ['$assignedRec', 0] } }, + vars: { assigned: { $arrayElemAt: ["$assignedRec", 0] } }, in: { - _id: '$$assigned._id', - pid: '$$assigned.pid', - name: '$$assigned.name', - avatar: '$$assigned.avatar', + _id: "$$assigned._id", + pid: "$$assigned.pid", + name: "$$assigned.name", + avatar: "$$assigned.avatar", }, }, }, @@ -124,7 +129,7 @@ export async function listProcessedPermits( }, { $facet: { - metadata: [{ $count: 'count' }], + metadata: [{ $count: "count" }], data: [ { $sort: sortObj }, { $skip: (page - 1) * pageSize }, @@ -157,12 +162,12 @@ export async function getUniqueValuesProcessed( let values = await processedModel.distinct(field, { tenantId: tenenatId }); let matchedValues = []; - if (field === 'county.name') { - matchedValues = await orgModel.find().where('name').in(values).exec(); - } else if (field === 'client') { - matchedValues = await orgModel.find().where('_id').in(values).exec(); - } else if (field === 'assignedTo') { - matchedValues = await userModel.find().where('name').in(values).exec(); + if (field === "county.name") { + matchedValues = await orgModel.find().where("name").in(values).exec(); + } else if (field === "client") { + matchedValues = await orgModel.find().where("_id").in(values).exec(); + } else if (field === "assignedTo") { + matchedValues = await userModel.find().where("name").in(values).exec(); } if (matchedValues.length > 0) { diff --git a/src/user/user.controller.ts b/src/user/user.controller.ts index a05c352..0268501 100644 --- a/src/user/user.controller.ts +++ b/src/user/user.controller.ts @@ -1,13 +1,14 @@ -import { FastifyReply, FastifyRequest } from 'fastify'; +import { FastifyReply, FastifyRequest } from "fastify"; import { createUser, deleteUser, + ErrMissingOrdId, ErrOpNotValid, getUser, listUsers, updateUser, -} from './user.service'; -import { CreateUserInput, UpdateUserInput } from './user.schema'; +} from "./user.service"; +import { CreateUserInput, UpdateUserInput } from "./user.schema"; export async function createUserHandler( req: FastifyRequest, @@ -19,8 +20,12 @@ export async function createUserHandler( const user = await createUser(body, req.user); return res.code(201).send(user); } catch (err) { - if (err instanceof Error && err.message == ErrOpNotValid.message) - return res.code(400).send(err.message); + if ( + err instanceof Error && + (err.message == ErrOpNotValid.message || + err.message == ErrMissingOrdId.message) + ) + return res.code(400).send({ error: err.message }); return err; } } @@ -29,14 +34,14 @@ export async function getCurrentUserHandler( req: FastifyRequest, res: FastifyReply ) { - if (req.user.type !== 'user') { + if (req.user.type !== "user") { return res.code(400).send(); } try { const user = await getUser(req.user.userId); if (user == null) - return res.code(404).send({ error: 'resource not found' }); + return res.code(404).send({ error: "resource not found" }); return res.code(200).send(user); } catch (err) { @@ -50,7 +55,7 @@ export async function getUserHandler(req: FastifyRequest, res: FastifyReply) { try { const user = await getUser(userId); if (user == null) - return res.code(404).send({ error: 'resource not found' }); + return res.code(404).send({ error: "resource not found" }); return res.code(200).send(user); } catch (err) { @@ -76,7 +81,7 @@ export async function updateUserHandler( try { const updatedUser = await updateUser(userId, input); - if (!updateUser) return res.code(404).send({ error: 'resource not found' }); + if (!updateUser) return res.code(404).send({ error: "resource not found" }); return res.code(200).send(updatedUser); } catch (err) { @@ -93,7 +98,7 @@ export async function deleteUserHandler( 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(404).send({ error: "resource not found" }); return res.code(204).send(); } catch (err) { diff --git a/src/user/user.schema.ts b/src/user/user.schema.ts index 920dc99..963967b 100644 --- a/src/user/user.schema.ts +++ b/src/user/user.schema.ts @@ -1,7 +1,7 @@ -import { buildJsonSchemas } from 'fastify-zod'; -import mongoose, { InferSchemaType } from 'mongoose'; -import { z } from 'zod'; -import { roles } from '../utils/roles'; +import { buildJsonSchemas } from "fastify-zod"; +import mongoose, { InferSchemaType } from "mongoose"; +import { z } from "zod"; +import { roles } from "../utils/roles"; const userSchema = new mongoose.Schema({ tenantId: { @@ -30,7 +30,7 @@ const userSchema = new mongoose.Schema({ }, defaultClient: { type: mongoose.Types.ObjectId, - ref: 'organization', + ref: "organization", }, passKeys: [new mongoose.Schema({}, { _id: false, strict: false })], challenge: new mongoose.Schema( @@ -53,7 +53,7 @@ const userSchema = new mongoose.Schema({ dev: Boolean, }); -export const userModel = mongoose.model('user', userSchema); +export const userModel = mongoose.model("user", userSchema); export type User = InferSchemaType; @@ -62,8 +62,8 @@ const userCore = { lastName: z.string().max(30), email: z .string({ - required_error: 'Email is required', - invalid_type_error: 'Email must be a valid string', + required_error: "Email is required", + invalid_type_error: "Email must be a valid string", }) .email(), avatar: z.string().optional(), @@ -75,14 +75,9 @@ const createUserInput = z .object({ ...userCore, }) - .superRefine((data, ctx) => { - if (data.role == 'builder' && !data.orgId) { - ctx.addIssue({ - path: ['orgId'], - message: 'orgId is required when role is "builder"', - code: z.ZodIssueCode.custom, - }); - } + .refine((data) => data.role !== "client" || data.orgId, { + message: 'orgId is required when role is "client"', + path: ["orgId"], }); const updateUserInput = z.object({ @@ -90,8 +85,8 @@ const updateUserInput = z.object({ lastName: z.string().max(30).optional(), email: z .string({ - required_error: 'Email is required', - invalid_type_error: 'Email must be a valid string', + required_error: "Email is required", + invalid_type_error: "Email must be a valid string", }) .email() .optional(), @@ -125,5 +120,5 @@ export const { schemas: userSchemas, $ref: $user } = buildJsonSchemas( updateUserInput, userResponse, }, - { $id: 'user' } + { $id: "user" } ); diff --git a/src/user/user.service.ts b/src/user/user.service.ts index b9acc41..d8d4fb7 100644 --- a/src/user/user.service.ts +++ b/src/user/user.service.ts @@ -1,43 +1,50 @@ -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'; +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 const ErrOpNotValid = new Error('operation is not valid'); +export const ErrOpNotValid = new Error("operation is not valid"); +export const ErrMissingOrdId = new Error( + "orgId is required when role is client" +); export async function createUser( input: CreateUserInput, user: AuthenticatedUser ) { - if (input.role == 'admin' && user.role != 'superAdmin') { + if (input.role == "admin" && user.role != "superAdmin") { throw ErrOpNotValid; } + if (input.role == "client" && !input.orgId) { + throw ErrMissingOrdId; + } + const token = await generateToken(); const newUser = await userModel.create({ tenantId: user.tenantId, pid: generateId(), - name: input.firstName + ' ' + input.lastName, + name: input.firstName + " " + input.lastName, createdAt: new Date(), createdBy: user.userId, token: { value: token, expiry: new Date(Date.now() + 3600 * 48 * 1000), }, - status: 'invited', + status: "invited", ...input, }); const sent = await sendMail( input.email, - 'You have been invited to Quicker Permtis.', + "You have been invited to Quicker Permtis.", `Click here to register.` ); @@ -56,7 +63,7 @@ export async function getUser(userId: string) { } export async function getUserByToken(token: string) { - return await userModel.findOne({ 'token.value': token }); + return await userModel.findOne({ "token.value": token }); } export async function getUserByEmail(email: string) { @@ -67,7 +74,7 @@ export async function listUsers(tenantId: string) { return await userModel .find({ $and: [{ tenantId: tenantId }, { dev: { $ne: true } }] }) .select( - '_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin' + "_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin" ); } @@ -77,7 +84,7 @@ export async function updateUser(userId: string, input: UpdateUserInput) { new: true, }) .select( - '_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin' + "_id pid orgId firstName lastName name email role avatar status createdAt createdBy lastLogin" ); } diff --git a/src/utils/roles.ts b/src/utils/roles.ts index e7165ba..80d7448 100644 --- a/src/utils/roles.ts +++ b/src/utils/roles.ts @@ -102,6 +102,23 @@ export const rules: Record< users: ["__v"], }, }, + client: { + claims: [ + "permit:read", + "file:upload", + "file:download", + "view:read", + "view:write", + "view:delete", + ], + hiddenFields: { + orgs: ["__v"], + permits: ["__v"], + rts: ["__v"], + tasks: ["__v"], + users: ["__v"], + }, + }, }; export const roles = Object.keys(rules) as [