From 8c0932f6d820aecc896f7f1076843c2c30270e13 Mon Sep 17 00:00:00 2001 From: Methapon Metanipat Date: Tue, 10 Sep 2024 14:36:26 +0700 Subject: [PATCH] feat: refactor permission product service --- .../migration.sql | 46 ++++ prisma/schema.prisma | 20 +- src/controllers/04-product-controller.ts | 210 ++++++------------ src/controllers/04-service-controller.ts | 205 ++++++----------- 4 files changed, 189 insertions(+), 292 deletions(-) create mode 100644 prisma/migrations/20240910073154_update_product_service_fields/migration.sql diff --git a/prisma/migrations/20240910073154_update_product_service_fields/migration.sql b/prisma/migrations/20240910073154_update_product_service_fields/migration.sql new file mode 100644 index 0000000..4705ba8 --- /dev/null +++ b/prisma/migrations/20240910073154_update_product_service_fields/migration.sql @@ -0,0 +1,46 @@ +/* + Warnings: + + - You are about to drop the column `registeredBranchId` on the `Product` table. All the data in the column will be lost. + - You are about to drop the column `registeredBranchId` on the `Service` table. All the data in the column will be lost. + - Made the column `productGroupId` on table `Product` required. This step will fail if there are existing NULL values in that column. + - Made the column `registeredBranchId` on table `ProductGroup` required. This step will fail if there are existing NULL values in that column. + - Made the column `productGroupId` on table `Service` required. This step will fail if there are existing NULL values in that column. + +*/ +-- DropForeignKey +ALTER TABLE "Product" DROP CONSTRAINT "Product_productGroupId_fkey"; + +-- DropForeignKey +ALTER TABLE "Product" DROP CONSTRAINT "Product_registeredBranchId_fkey"; + +-- DropForeignKey +ALTER TABLE "ProductGroup" DROP CONSTRAINT "ProductGroup_registeredBranchId_fkey"; + +-- DropForeignKey +ALTER TABLE "Service" DROP CONSTRAINT "Service_productGroupId_fkey"; + +-- DropForeignKey +ALTER TABLE "Service" DROP CONSTRAINT "Service_registeredBranchId_fkey"; + +-- AlterTable +ALTER TABLE "Product" DROP COLUMN "registeredBranchId", +ADD COLUMN "shared" BOOLEAN NOT NULL DEFAULT false, +ALTER COLUMN "productGroupId" SET NOT NULL; + +-- AlterTable +ALTER TABLE "ProductGroup" ALTER COLUMN "registeredBranchId" SET NOT NULL; + +-- AlterTable +ALTER TABLE "Service" DROP COLUMN "registeredBranchId", +ADD COLUMN "shared" BOOLEAN NOT NULL DEFAULT false, +ALTER COLUMN "productGroupId" SET NOT NULL; + +-- AddForeignKey +ALTER TABLE "ProductGroup" ADD CONSTRAINT "ProductGroup_registeredBranchId_fkey" FOREIGN KEY ("registeredBranchId") REFERENCES "Branch"("id") ON DELETE RESTRICT ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Product" ADD CONSTRAINT "Product_productGroupId_fkey" FOREIGN KEY ("productGroupId") REFERENCES "ProductGroup"("id") ON DELETE CASCADE ON UPDATE CASCADE; + +-- AddForeignKey +ALTER TABLE "Service" ADD CONSTRAINT "Service_productGroupId_fkey" FOREIGN KEY ("productGroupId") REFERENCES "ProductGroup"("id") ON DELETE CASCADE ON UPDATE CASCADE; diff --git a/prisma/schema.prisma b/prisma/schema.prisma index fa5f26e..8dea411 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -238,8 +238,6 @@ model Branch { contact BranchContact[] user BranchUser[] - productRegistration Product[] - serviceRegistration Service[] customerRegistration Customer[] productGroup ProductGroup[] } @@ -662,8 +660,8 @@ model ProductGroup { status Status @default(CREATED) statusOrder Int @default(0) - registeredBranchId String? - registeredBranch Branch? @relation(fields: [registeredBranchId], references: [id]) + registeredBranchId String + registeredBranch Branch @relation(fields: [registeredBranchId], references: [id]) createdAt DateTime @default(now()) createdBy User? @relation(name: "ProductGroupCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) @@ -696,11 +694,8 @@ model Product { remark String? - productGroup ProductGroup? @relation(fields: [productGroupId], references: [id], onDelete: SetNull) - productGroupId String? - - registeredBranchId String? - registeredBranch Branch? @relation(fields: [registeredBranchId], references: [id]) + productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade) + productGroupId String workProduct WorkProduct[] quotationServiceWorkProduct QuotationServiceWorkProduct[] @@ -729,11 +724,8 @@ model Service { work Work[] quotationService QuotationService[] - productGroup ProductGroup? @relation(fields: [productGroupId], references: [id], onDelete: SetNull) - productGroupId String? - - registeredBranchId String? - registeredBranch Branch? @relation(fields: [registeredBranchId], references: [id]) + productGroup ProductGroup @relation(fields: [productGroupId], references: [id], onDelete: Cascade) + productGroupId String createdAt DateTime @default(now()) createdBy User? @relation(name: "ServiceCreatedByUser", fields: [createdByUserId], references: [id], onDelete: SetNull) diff --git a/src/controllers/04-product-controller.ts b/src/controllers/04-product-controller.ts index bc62443..ebe445d 100644 --- a/src/controllers/04-product-controller.ts +++ b/src/controllers/04-product-controller.ts @@ -15,25 +15,34 @@ import { import { Prisma, Status } from "@prisma/client"; import prisma from "../db"; -import minio, { presignedGetObjectIfExist } from "../services/minio"; import { RequestWithUser } from "../interfaces/user"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; +import { + branchRelationPermInclude, + createPermCheck, + createPermCondition, +} from "../services/permission"; +import { isSystem } from "../utils/keycloak"; +import { filterStatus } from "../services/prisma"; -if (!process.env.MINIO_BUCKET) { - throw Error("Require MinIO bucket."); -} - -const MINIO_BUCKET = process.env.MINIO_BUCKET; const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "branch_manager", "head_of_account", "account", + "head_of_sale", ]; +function globalAllow(user: RequestWithUser["user"]) { + const allowList = ["system", "head_of_admin", "admin", "head_of_account", "head_of_sale"]; + return allowList.some((v) => user.roles?.includes(v)); +} + +const permissionCond = createPermCondition(globalAllow); +const permissionCheck = createPermCheck(globalAllow); + type ProductCreate = { status?: Status; code: @@ -61,8 +70,6 @@ type ProductCreate = { expenseType?: string; productGroupId: string; remark?: string; - - registeredBranchId?: string; }; type ProductUpdate = { @@ -77,56 +84,43 @@ type ProductUpdate = { vatIncluded?: boolean; expenseType?: string; productGroupId?: string; - - registeredBranchId?: string; }; -function imageLocation(id: string) { - return `product/${id}/image`; -} - -function globalAllow(roles?: string[]) { - return ["system", "head_of_admin", "admin", "branch_manager", "head_of_account"].some((v) => - roles?.includes(v), - ); -} - @Route("api/v1/product") @Tags("Product") export class ProductController extends Controller { @Get("stats") - async getProductStats(@Query() productGroupId?: string) { - return await prisma.product.count({ where: { productGroupId } }); + @Security("keycloak") + async getProductStats(@Request() req: RequestWithUser, @Query() productGroupId?: string) { + return await prisma.product.count({ + where: { + productGroupId, + productGroup: isSystem(req.user) + ? undefined + : { registeredBranch: { OR: permissionCond(req.user) } }, + }, + }); } @Get() @Security("keycloak") async getProduct( + @Request() req: RequestWithUser, @Query() status?: Status, @Query() productGroupId?: string, @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, - @Query() registeredBranchId?: string, ) { - const filterStatus = (val?: Status) => { - if (!val) return {}; - - return val !== Status.CREATED && val !== Status.ACTIVE - ? { status: val } - : { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] }; - }; - const where = { - OR: [ - { name: { contains: query }, productGroupId, ...filterStatus(status) }, - { detail: { contains: query }, productGroupId, ...filterStatus(status) }, - ], - AND: registeredBranchId - ? { - OR: [{ registeredBranchId: registeredBranchId }, { registeredBranchId: null }], - } - : undefined, + OR: query ? [{ name: { contains: query } }, { detail: { contains: query } }] : undefined, + AND: { + ...filterStatus(status), + productGroupId, + productGroup: isSystem(req.user) + ? undefined + : { registeredBranch: { OR: permissionCond(req.user) } }, + }, } satisfies Prisma.ProductWhereInput; const [result, total] = await prisma.$transaction([ @@ -144,16 +138,7 @@ export class ProductController extends Controller { ]); return { - result: await Promise.all( - result.map(async (v) => ({ - ...v, - imageUrl: await presignedGetObjectIfExist( - MINIO_BUCKET, - imageLocation(v.id), - 12 * 60 * 60, - ), - })), - ), + result, page, pageSize, total, @@ -175,47 +160,25 @@ export class ProductController extends Controller { throw new HttpError(HttpStatus.NOT_FOUND, "Product cannot be found.", "productNotFound"); } - return Object.assign(record, { - imageUrl: await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(record.id), 60 * 60), - }); - } - - @Get("{productId}/image") - async getProductImageById(@Request() req: RequestWithUser, @Path() productId: string) { - const url = await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(productId), 60 * 60); - - if (!url) { - throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound"); - } - - return req.res?.redirect(url); + return record; } @Post() @Security("keycloak", MANAGE_ROLES) async createProduct(@Request() req: RequestWithUser, @Body() body: ProductCreate) { - const [productGroup, branch] = await prisma.$transaction([ + const [productGroup] = await prisma.$transaction([ prisma.productGroup.findFirst({ include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, createdBy: true, updatedBy: true, }, where: { id: body.productGroupId }, }), - prisma.branch.findFirst({ - include: { user: { where: { userId: req.user.sub } } }, - where: { id: body.registeredBranchId }, - }), ]); - if (!globalAllow(req.user.roles) && !branch?.user.find((v) => v.userId === req.user.sub)) { - throw new HttpError( - HttpStatus.FORBIDDEN, - "You do not have permission to perform this action.", - "noPermission", - ); - } - if (!productGroup) { throw new HttpError( HttpStatus.BAD_REQUEST, @@ -224,22 +187,18 @@ export class ProductController extends Controller { ); } - if (body.registeredBranchId && !branch) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Branch cannot be found.", - "relationBranchNotFound", - ); - } + await permissionCheck(req.user, productGroup.registeredBranch); const record = await prisma.$transaction( async (tx) => { + const branch = productGroup.registeredBranch; + const company = (branch.headOffice || branch).code; const last = await tx.runningNo.upsert({ where: { - key: `PRODUCT_${body.code.toLocaleUpperCase()}`, + key: `PRODUCT_${company}_${body.code.toLocaleUpperCase()}`, }, create: { - key: `PRODUCT_${body.code.toLocaleUpperCase()}`, + key: `PRODUCT_${company}_${body.code.toLocaleUpperCase()}`, value: 1, }, update: { value: { increment: 1 } }, @@ -276,18 +235,7 @@ export class ProductController extends Controller { this.setStatus(HttpStatus.CREATED); - return Object.assign(record, { - imageUrl: await presignedGetObjectIfExist( - MINIO_BUCKET, - imageLocation(record.id), - 12 * 60 * 60, - ), - imageUploadUrl: await minio.presignedPutObject( - MINIO_BUCKET, - imageLocation(record.id), - 12 * 60 * 60, - ), - }); + return record; } @Put("{productId}") @@ -297,12 +245,14 @@ export class ProductController extends Controller { @Body() body: ProductUpdate, @Path() productId: string, ) { - const [product, productGroup, branch] = await prisma.$transaction([ + const [product, productGroup] = await prisma.$transaction([ prisma.product.findUnique({ include: { - registeredBranch: { - where: { - user: { some: { userId: req.user.sub } }, + productGroup: { + include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, }, }, }, @@ -310,27 +260,21 @@ export class ProductController extends Controller { }), prisma.productGroup.findFirst({ include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, createdBy: true, updatedBy: true, }, where: { id: body.productGroupId }, }), - prisma.branch.findFirst({ where: { id: body.registeredBranchId } }), ]); if (!product) { throw new HttpError(HttpStatus.NOT_FOUND, "Product cannot be found.", "productNotFound"); } - if (!globalAllow(req.user.roles) && !product.registeredBranch) { - throw new HttpError( - HttpStatus.FORBIDDEN, - "You do not have permission to perform this action.", - "noPermission", - ); - } - - if (!productGroup) { + if (!!body.productGroupId && !productGroup) { throw new HttpError( HttpStatus.BAD_REQUEST, "Product Group cannot be found.", @@ -338,12 +282,9 @@ export class ProductController extends Controller { ); } - if (body.registeredBranchId && !branch) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Branch cannot be found.", - "relationBranchNotFound", - ); + await permissionCheck(req.user, product.productGroup.registeredBranch); + if (body.productGroupId && productGroup) { + await permissionCheck(req.user, productGroup.registeredBranch); } const record = await prisma.product.update({ @@ -355,25 +296,14 @@ export class ProductController extends Controller { where: { id: productId }, }); - if (productGroup.status === "CREATED") { + if (productGroup?.status === "CREATED") { await prisma.productGroup.updateMany({ where: { id: body.productGroupId, status: Status.CREATED }, data: { status: Status.ACTIVE }, }); } - return Object.assign(record, { - imageUrl: await presignedGetObjectIfExist( - MINIO_BUCKET, - imageLocation(record.id), - 12 * 60 * 60, - ), - imageUploadUrl: await minio.presignedPutObject( - MINIO_BUCKET, - imageLocation(record.id), - 12 * 60 * 60, - ), - }); + return record; } @Delete("{productId}") @@ -381,9 +311,11 @@ export class ProductController extends Controller { async deleteProduct(@Request() req: RequestWithUser, @Path() productId: string) { const record = await prisma.product.findFirst({ include: { - registeredBranch: { - where: { - user: { some: { userId: req.user.sub } }, + productGroup: { + include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, }, }, }, @@ -395,14 +327,6 @@ export class ProductController extends Controller { throw new HttpError(HttpStatus.NOT_FOUND, "Product cannot be found.", "productNotFound"); } - if (!globalAllow(req.user.roles) && !record.registeredBranch) { - throw new HttpError( - HttpStatus.FORBIDDEN, - "You do not have permission to perform this action.", - "noPermission", - ); - } - if (record.status !== Status.CREATED) { throw new HttpError(HttpStatus.FORBIDDEN, "Product is in used.", "productInUsed"); } diff --git a/src/controllers/04-service-controller.ts b/src/controllers/04-service-controller.ts index 4ff184a..aa32e97 100644 --- a/src/controllers/04-service-controller.ts +++ b/src/controllers/04-service-controller.ts @@ -15,25 +15,34 @@ import { import { Prisma, Status } from "@prisma/client"; import prisma from "../db"; -import minio, { presignedGetObjectIfExist } from "../services/minio"; import { RequestWithUser } from "../interfaces/user"; import HttpError from "../interfaces/http-error"; import HttpStatus from "../interfaces/http-status"; +import { isSystem } from "../utils/keycloak"; +import { + branchRelationPermInclude, + createPermCheck, + createPermCondition, +} from "../services/permission"; +import { filterStatus } from "../services/prisma"; -if (!process.env.MINIO_BUCKET) { - throw Error("Require MinIO bucket."); -} - -const MINIO_BUCKET = process.env.MINIO_BUCKET; const MANAGE_ROLES = [ "system", "head_of_admin", "admin", - "branch_manager", "head_of_account", "account", + "head_of_sale", ]; +function globalAllow(user: RequestWithUser["user"]) { + const allowList = ["system", "head_of_admin", "admin", "head_of_account", "head_of_sale"]; + return allowList.some((v) => user.roles?.includes(v)); +} + +const permissionCond = createPermCondition(globalAllow); +const permissionCheck = createPermCheck(globalAllow); + type ServiceCreate = { code: string; name: string; @@ -48,7 +57,6 @@ type ServiceCreate = { attributes?: { [key: string]: any }; }[]; productGroupId: string; - registeredBranchId?: string; }; type ServiceUpdate = { @@ -64,55 +72,52 @@ type ServiceUpdate = { attributes?: { [key: string]: any }; }[]; productGroupId?: string; - registeredBranchId?: string; }; -function imageLocation(id: string) { - return `service/${id}/service-image`; -} - -function globalAllow(roles?: string[]) { - return ["system", "head_of_admin", "admin", "head_of_account"].some((v) => roles?.includes(v)); -} - @Route("api/v1/service") @Tags("Service") export class ServiceController extends Controller { @Get("stats") @Security("keycloak") - async getServiceStats(@Query() productGroupId?: string) { - return await prisma.service.count({ where: { productGroupId } }); + async getServiceStats(@Request() req: RequestWithUser, @Query() productGroupId?: string) { + return await prisma.service.count({ + where: { + productGroupId, + productGroup: isSystem(req.user) + ? undefined + : { + registeredBranch: { + OR: permissionCond(req.user), + }, + }, + }, + }); } @Get() @Security("keycloak") async getService( + @Request() req: RequestWithUser, @Query() query: string = "", @Query() page: number = 1, @Query() pageSize: number = 30, @Query() status?: Status, @Query() productGroupId?: string, - @Query() registeredBranchId?: string, @Query() fullDetail?: boolean, ) { - const filterStatus = (val?: Status) => { - if (!val) return {}; - - return val !== Status.CREATED && val !== Status.ACTIVE - ? { status: val } - : { OR: [{ status: Status.CREATED }, { status: Status.ACTIVE }] }; - }; - const where = { - OR: [ - { name: { contains: query }, productGroupId, ...filterStatus(status) }, - { detail: { contains: query }, productGroupId, ...filterStatus(status) }, - ], - AND: registeredBranchId - ? { - OR: [{ registeredBranchId: registeredBranchId }, { registeredBranchId: null }], - } - : undefined, + OR: query ? [{ name: { contains: query } }, { detail: { contains: query } }] : undefined, + AND: { + ...filterStatus(status), + productGroupId, + productGroup: isSystem(req.user) + ? undefined + : { + registeredBranch: { + OR: permissionCond(req.user), + }, + }, + }, } satisfies Prisma.ServiceWhereInput; const [result, total] = await prisma.$transaction([ @@ -141,16 +146,7 @@ export class ServiceController extends Controller { ]); return { - result: await Promise.all( - result.map(async (v) => ({ - ...v, - imageUrl: await presignedGetObjectIfExist( - MINIO_BUCKET, - imageLocation(v.id), - 12 * 60 * 60, - ), - })), - ), + result, page, pageSize, total, @@ -181,9 +177,7 @@ export class ServiceController extends Controller { throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound"); } - return Object.assign(record, { - imageUrl: await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(record.id), 60 * 60), - }); + return record; } @Get("{serviceId}/work") @@ -218,44 +212,24 @@ export class ServiceController extends Controller { return { result, page, pageSize, total }; } - @Get("{serviceId}/image") - async getServiceImageById(@Request() req: RequestWithUser, @Path() serviceId: string) { - const url = await presignedGetObjectIfExist(MINIO_BUCKET, imageLocation(serviceId), 60 * 60); - - if (!url) { - throw new HttpError(HttpStatus.NOT_FOUND, "Image cannot be found", "imageNotFound"); - } - - return req.res?.redirect(url); - } - @Post() @Security("keycloak", MANAGE_ROLES) async createService(@Request() req: RequestWithUser, @Body() body: ServiceCreate) { const { work, productGroupId, ...payload } = body; - const [productGroup, branch] = await prisma.$transaction([ + const [productGroup] = await prisma.$transaction([ prisma.productGroup.findFirst({ include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, createdBy: true, updatedBy: true, }, where: { id: body.productGroupId }, }), - prisma.branch.findFirst({ - include: { user: { where: { userId: req.user.sub } } }, - where: { id: body.registeredBranchId }, - }), ]); - if (!globalAllow(req.user.roles) && !branch?.user.find((v) => v.userId === req.user.sub)) { - throw new HttpError( - HttpStatus.FORBIDDEN, - "You do not have permission to perform this action.", - "noPermission", - ); - } - if (!productGroup) { throw new HttpError( HttpStatus.BAD_REQUEST, @@ -264,13 +238,7 @@ export class ServiceController extends Controller { ); } - if (body.registeredBranchId && !branch) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Branch cannot be found.", - "relationBranchNotFound", - ); - } + await permissionCheck(req.user, productGroup.registeredBranch); const record = await prisma.$transaction( async (tx) => { @@ -325,18 +293,7 @@ export class ServiceController extends Controller { ); this.setStatus(HttpStatus.CREATED); - return Object.assign(record, { - imageUrl: await presignedGetObjectIfExist( - MINIO_BUCKET, - imageLocation(record.id), - 12 * 60 * 60, - ), - imageUploadUrl: await minio.presignedPutObject( - MINIO_BUCKET, - imageLocation(record.id), - 12 * 60 * 60, - ), - }); + return record; } @Put("{serviceId}") @@ -351,12 +308,14 @@ export class ServiceController extends Controller { } const { work, productGroupId, ...payload } = body; - const [service, productGroup, branch] = await prisma.$transaction([ + const [service, productGroup] = await prisma.$transaction([ prisma.service.findUnique({ include: { - registeredBranch: { - where: { - user: { some: { userId: req.user.sub } }, + productGroup: { + include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, }, }, }, @@ -364,27 +323,21 @@ export class ServiceController extends Controller { }), prisma.productGroup.findFirst({ include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, createdBy: true, updatedBy: true, }, where: { id: body.productGroupId }, }), - prisma.branch.findFirst({ where: { id: body.registeredBranchId } }), ]); if (!service) { throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound"); } - if (!globalAllow(req.user.roles) && !service.registeredBranch) { - throw new HttpError( - HttpStatus.FORBIDDEN, - "You do not have permission to perform this action.", - "noPermission", - ); - } - - if (!productGroup) { + if (!!body.productGroupId && !productGroup) { throw new HttpError( HttpStatus.BAD_REQUEST, "Product Type cannot be found.", @@ -392,12 +345,9 @@ export class ServiceController extends Controller { ); } - if (body.registeredBranchId && !branch) { - throw new HttpError( - HttpStatus.BAD_REQUEST, - "Branch cannot be found.", - "relationBranchNotFound", - ); + await permissionCheck(req.user, service.productGroup.registeredBranch); + if (!!body.productGroupId && productGroup) { + await permissionCheck(req.user, productGroup.registeredBranch); } const record = await prisma.$transaction(async (tx) => { @@ -429,18 +379,7 @@ export class ServiceController extends Controller { }); }); - return Object.assign(record, { - imageUrl: await presignedGetObjectIfExist( - MINIO_BUCKET, - imageLocation(record.id), - 12 * 60 * 60, - ), - imageUploadUrl: await minio.presignedPutObject( - MINIO_BUCKET, - imageLocation(record.id), - 12 * 60 * 60, - ), - }); + return record; } @Delete("{serviceId}") @@ -448,9 +387,11 @@ export class ServiceController extends Controller { async deleteService(@Request() req: RequestWithUser, @Path() serviceId: string) { const record = await prisma.service.findFirst({ include: { - registeredBranch: { - where: { - user: { some: { userId: req.user.sub } }, + productGroup: { + include: { + registeredBranch: { + include: branchRelationPermInclude(req.user), + }, }, }, }, @@ -461,13 +402,7 @@ export class ServiceController extends Controller { throw new HttpError(HttpStatus.NOT_FOUND, "Service cannot be found.", "serviceNotFound"); } - if (!globalAllow(req.user.roles) && !record.registeredBranch) { - throw new HttpError( - HttpStatus.FORBIDDEN, - "You do not have permission to perform this action.", - "noPermission", - ); - } + await permissionCheck(req.user, record.productGroup.registeredBranch); if (record.status !== Status.CREATED) { throw new HttpError(HttpStatus.FORBIDDEN, "Service is in used.", "serviceInUsed");