From 5773b7269ee70deff53a3b7d41de886ab3595ae6 Mon Sep 17 00:00:00 2001 From: Ha Minh Chien Date: Wed, 24 Jan 2024 17:50:48 +0700 Subject: [PATCH] update schema --- .../scripts/database/indexing/index.ts | 1 + .../src/domain/auth/action.ts | 6 +++++- .../src/domain/auth/constants.ts | 2 ++ .../src/domain/auth/index.ts | 1 + .../src/domain/auth/subject.ts | 8 ++++--- .../src/domain/auth/utils.ts | 9 ++++---- .../src/domain/entity/bio-product/entity.ts | 6 +++++- .../src/domain/entity/bio-product/example.ts | 8 ++++++- .../src/domain/entity/branch/entity.ts | 7 ++++--- .../src/domain/entity/branch/example.ts | 9 +++++++- .../src/domain/entity/role/entity.ts | 8 +++---- .../src/domain/entity/role/example.ts | 9 ++++---- .../src/domain/entity/user/auth.ts | 1 - .../src/domain/entity/user/entity.ts | 2 -- .../src/domain/exception/authn/payload.ts | 2 +- .../src/domain/exception/authz/permission.ts | 5 ++++- .../domain/use-case/auth/populate-context.ts | 7 ++----- .../src/domain/use-case/bio-product/create.ts | 3 +++ .../src/domain/use-case/bio-product/update.ts | 3 +++ .../domain/use-case/bio-product/validate.ts | 19 +++++++++++++++++ .../src/domain/use-case/branch/create.ts | 3 +++ .../src/domain/use-case/branch/update.ts | 3 +++ .../src/domain/use-case/branch/validate.ts | 21 +++++++++++++++++++ .../src/domain/use-case/index.ts | 2 ++ .../src/domain/use-case/module.ts | 4 ++++ .../src/domain/use-case/role/validate.ts | 10 ++++----- .../src/domain/use-case/user/update.ts | 1 + .../src/domain/use-case/user/validate.ts | 21 ++++++++++++++----- .../mongo/bio-product/schema.ts | 18 +++++++++++++++- .../src/infrastructure/mongo/branch/schema.ts | 17 ++++++++++++++- .../src/infrastructure/mongo/role/schema.ts | 14 ++++++------- .../http/common/exception-filter/domain.ts | 2 +- .../src/presentation/http/v1/auth/routes.ts | 1 + .../v1/bio-product/dto/create.request-dto.ts | 10 +++++++-- .../http/v1/bio-product/dto/response-dto.ts | 14 +++++++++++-- .../presentation/http/v1/branch/controller.ts | 5 ++++- .../http/v1/branch/dto/create.request-dto.ts | 20 +++++++++++++++--- .../http/v1/branch/dto/response-dto.ts | 20 ++++++++++++++++-- .../presentation/http/v1/role/controller.ts | 2 +- .../http/v1/role/dto/create.request-dto.ts | 11 +++++----- .../http/v1/role/dto/response-dto.ts | 5 ++--- .../http/bootstrap/swagger.bootstrap.ts | 1 - 42 files changed, 245 insertions(+), 76 deletions(-) create mode 100644 apps/hcdc-access-service/scripts/database/indexing/index.ts create mode 100644 apps/hcdc-access-service/src/domain/auth/constants.ts create mode 100644 apps/hcdc-access-service/src/domain/use-case/bio-product/validate.ts create mode 100644 apps/hcdc-access-service/src/domain/use-case/branch/validate.ts diff --git a/apps/hcdc-access-service/scripts/database/indexing/index.ts b/apps/hcdc-access-service/scripts/database/indexing/index.ts new file mode 100644 index 00000000..3418b6c4 --- /dev/null +++ b/apps/hcdc-access-service/scripts/database/indexing/index.ts @@ -0,0 +1 @@ +console.log('indexing collections...') diff --git a/apps/hcdc-access-service/src/domain/auth/action.ts b/apps/hcdc-access-service/src/domain/auth/action.ts index 68f3d3a1..9aaf77f0 100644 --- a/apps/hcdc-access-service/src/domain/auth/action.ts +++ b/apps/hcdc-access-service/src/domain/auth/action.ts @@ -8,6 +8,7 @@ import { TestCategoryAction, UserAction, } from '../entity' +import { AUTH_ACTION_ALL } from './constants' export const AuthAction = { BioProduct: stringEnumValues(BioProductAction), @@ -17,7 +18,10 @@ export const AuthAction = { User: stringEnumValues(UserAction), } satisfies Record -export const AuthActionValues = [...new Set(Object.values(AuthAction).flat())] +export const AuthActionValues = [ + ...new Set(Object.values(AuthAction).flat()), + AUTH_ACTION_ALL, +] export type AuthActionUnionType = (typeof AuthAction)[AuthSubjectUnionType][number] diff --git a/apps/hcdc-access-service/src/domain/auth/constants.ts b/apps/hcdc-access-service/src/domain/auth/constants.ts new file mode 100644 index 00000000..9b058ba0 --- /dev/null +++ b/apps/hcdc-access-service/src/domain/auth/constants.ts @@ -0,0 +1,2 @@ +export const AUTH_SUBJECT_ALL = 'all' +export const AUTH_ACTION_ALL = 'manage' diff --git a/apps/hcdc-access-service/src/domain/auth/index.ts b/apps/hcdc-access-service/src/domain/auth/index.ts index dcbb13b0..bcbeab45 100644 --- a/apps/hcdc-access-service/src/domain/auth/index.ts +++ b/apps/hcdc-access-service/src/domain/auth/index.ts @@ -1,3 +1,4 @@ export * from './action' export * from './subject' export * from './utils' +export * from './constants' diff --git a/apps/hcdc-access-service/src/domain/auth/subject.ts b/apps/hcdc-access-service/src/domain/auth/subject.ts index dead7972..986cc364 100644 --- a/apps/hcdc-access-service/src/domain/auth/subject.ts +++ b/apps/hcdc-access-service/src/domain/auth/subject.ts @@ -1,6 +1,7 @@ import { RecordTypes } from '@casl/mongoose' import { BioProduct, Branch, Role, TestCategory, User } from '../entity' +import { AUTH_SUBJECT_ALL } from './constants' // key-value must be identical for working with '@casl/mongoose'.accessibleBy() export const AuthSubject = { @@ -13,9 +14,10 @@ export const AuthSubject = { export type AuthSubjectUnionType = keyof typeof AuthSubject -export const AuthSubjectValues = Object.keys( - AuthSubject, -) as AuthSubjectUnionType[] +export const AuthSubjectValues = [ + ...Object.keys(AuthSubject), + AUTH_SUBJECT_ALL, +] as AuthSubjectUnionType[] export type SubjectEntityMapping = { BioProduct: BioProduct diff --git a/apps/hcdc-access-service/src/domain/auth/utils.ts b/apps/hcdc-access-service/src/domain/auth/utils.ts index 316b5256..55219893 100644 --- a/apps/hcdc-access-service/src/domain/auth/utils.ts +++ b/apps/hcdc-access-service/src/domain/auth/utils.ts @@ -3,11 +3,12 @@ import { MongoAbility, subject as assignSubject } from '@casl/ability' import { AuthAction } from './action' import { AuthSubject, SubjectEntityMapping } from './subject' import { EAuthzPermissionDenied } from 'src/domain/exception' +import { AUTH_ACTION_ALL, AUTH_SUBJECT_ALL } from './constants' export function checkPermission( ability: MongoAbility, - subject: TSubject, - action: (typeof AuthAction)[TSubject][number], + subject: TSubject | typeof AUTH_SUBJECT_ALL, + action: (typeof AuthAction)[TSubject][number] | typeof AUTH_ACTION_ALL, object?: Partial | null, ) { if (object != undefined) { @@ -19,8 +20,8 @@ export function checkPermission( export function assertPermission( ability: MongoAbility, - subject: TSubject, - action: (typeof AuthAction)[TSubject][number], + subject: TSubject | typeof AUTH_SUBJECT_ALL, + action: (typeof AuthAction)[TSubject][number] | typeof AUTH_ACTION_ALL, object?: Partial | null, ) { if (!checkPermission(ability, subject, action, object)) { diff --git a/apps/hcdc-access-service/src/domain/entity/bio-product/entity.ts b/apps/hcdc-access-service/src/domain/entity/bio-product/entity.ts index 8d5a9617..c5ae4015 100644 --- a/apps/hcdc-access-service/src/domain/entity/bio-product/entity.ts +++ b/apps/hcdc-access-service/src/domain/entity/bio-product/entity.ts @@ -1,6 +1,10 @@ import { BaseEntity } from '../base-entity' +import { Branch } from '../branch' export type BioProduct = BaseEntity & { - index: number + displayIndex: number name: string + + branchId: string + branch?: Branch | null } diff --git a/apps/hcdc-access-service/src/domain/entity/bio-product/example.ts b/apps/hcdc-access-service/src/domain/entity/bio-product/example.ts index 8c78c58b..486c2e56 100644 --- a/apps/hcdc-access-service/src/domain/entity/bio-product/example.ts +++ b/apps/hcdc-access-service/src/domain/entity/bio-product/example.ts @@ -1,11 +1,17 @@ +import { exampleMongoObjectId } from '@diut/nest-core' + import { EntityDataExample } from '../base-entity' import { BioProduct } from './entity' export const exampleBioProduct = { - index: { + displayIndex: { example: 1, }, name: { example: 'CHIV Advia centaur', }, + branchId: exampleMongoObjectId, + branch: { + required: false, + }, } satisfies EntityDataExample diff --git a/apps/hcdc-access-service/src/domain/entity/branch/entity.ts b/apps/hcdc-access-service/src/domain/entity/branch/entity.ts index 8f06da69..e4681dc7 100644 --- a/apps/hcdc-access-service/src/domain/entity/branch/entity.ts +++ b/apps/hcdc-access-service/src/domain/entity/branch/entity.ts @@ -10,10 +10,11 @@ export enum BranchType { export const BranchTypeValues = stringEnumValues(BranchType) export type Branch = BaseEntity & { - index: number + displayIndex: number name: string - address: string - type: BranchType + + sampleOriginIds: string[] + sampleOrigins?: (Omit | null)[] } diff --git a/apps/hcdc-access-service/src/domain/entity/branch/example.ts b/apps/hcdc-access-service/src/domain/entity/branch/example.ts index b923a0e6..896c7faa 100644 --- a/apps/hcdc-access-service/src/domain/entity/branch/example.ts +++ b/apps/hcdc-access-service/src/domain/entity/branch/example.ts @@ -1,8 +1,10 @@ +import { exampleMongoObjectIds } from '@diut/nest-core' + import { EntityDataExample } from '../base-entity' import { Branch, BranchType } from './entity' export const exampleBranch = { - index: { + displayIndex: { example: 1, }, name: { @@ -14,4 +16,9 @@ export const exampleBranch = { type: { enum: BranchType, }, + sampleOriginIds: exampleMongoObjectIds, + sampleOrigins: { + required: false, + isArray: true, + }, } satisfies EntityDataExample diff --git a/apps/hcdc-access-service/src/domain/entity/role/entity.ts b/apps/hcdc-access-service/src/domain/entity/role/entity.ts index fe17b3c7..5e6e874a 100644 --- a/apps/hcdc-access-service/src/domain/entity/role/entity.ts +++ b/apps/hcdc-access-service/src/domain/entity/role/entity.ts @@ -3,13 +3,11 @@ import { Branch } from '../branch' import { PermissionRule } from '../permission-rule' export type Role = BaseEntity & { - index: number + displayIndex: number name: string - description: string - permissions: PermissionRule[] - branchIds: string[] - branches?: (Branch | null)[] + branchId: string + branch?: Branch | null } diff --git a/apps/hcdc-access-service/src/domain/entity/role/example.ts b/apps/hcdc-access-service/src/domain/entity/role/example.ts index 0837885a..aeccced1 100644 --- a/apps/hcdc-access-service/src/domain/entity/role/example.ts +++ b/apps/hcdc-access-service/src/domain/entity/role/example.ts @@ -1,10 +1,10 @@ -import { exampleMongoObjectIds } from '@diut/nest-core' +import { exampleMongoObjectId } from '@diut/nest-core' import { EntityDataExample } from '../base-entity' import { Role } from './entity' export const exampleRole = { - index: { + displayIndex: { example: 1, }, name: { @@ -16,9 +16,8 @@ export const exampleRole = { permissions: { isArray: true, }, - branchIds: exampleMongoObjectIds, - branches: { + branchId: exampleMongoObjectId, + branch: { required: false, - isArray: true, }, } satisfies EntityDataExample diff --git a/apps/hcdc-access-service/src/domain/entity/user/auth.ts b/apps/hcdc-access-service/src/domain/entity/user/auth.ts index cbdf7456..62fd2e68 100644 --- a/apps/hcdc-access-service/src/domain/entity/user/auth.ts +++ b/apps/hcdc-access-service/src/domain/entity/user/auth.ts @@ -1,7 +1,6 @@ import '@casl/mongoose' export enum UserAction { - Manage = 'Manage', Create = 'Create', Read = 'Read', Update = 'Update', diff --git a/apps/hcdc-access-service/src/domain/entity/user/entity.ts b/apps/hcdc-access-service/src/domain/entity/user/entity.ts index 623ac1f9..06c866ae 100644 --- a/apps/hcdc-access-service/src/domain/entity/user/entity.ts +++ b/apps/hcdc-access-service/src/domain/entity/user/entity.ts @@ -6,10 +6,8 @@ import { Role } from '../role' export type User = BaseEntity & { username: string passwordHash: string - name: string phoneNumber: string - inlinePermissions: PermissionRule[] branchIds: string[] diff --git a/apps/hcdc-access-service/src/domain/exception/authn/payload.ts b/apps/hcdc-access-service/src/domain/exception/authn/payload.ts index 5d724711..f98acfed 100644 --- a/apps/hcdc-access-service/src/domain/exception/authn/payload.ts +++ b/apps/hcdc-access-service/src/domain/exception/authn/payload.ts @@ -20,7 +20,7 @@ export class EAuthnPayloadUserNotFound extends EAuthn { DomainErrorCode.AUTHN_PAYLOAD_USER_NOT_FOUND, 'user not found, HttpPublicRoute is applied where it should not be?', undefined, - HttpStatus.BAD_REQUEST, + HttpStatus.UNAUTHORIZED, ) } } diff --git a/apps/hcdc-access-service/src/domain/exception/authz/permission.ts b/apps/hcdc-access-service/src/domain/exception/authz/permission.ts index 61d9e0be..f5a8283f 100644 --- a/apps/hcdc-access-service/src/domain/exception/authz/permission.ts +++ b/apps/hcdc-access-service/src/domain/exception/authz/permission.ts @@ -4,7 +4,10 @@ import { DomainErrorCode } from '@diut/hcdc-common' import { EAuthz } from './base' export class EAuthzPermissionDenied extends EAuthz { - constructor(reason: string) { + constructor( + reason: string, + private readonly debug?: unknown, + ) { super( DomainErrorCode.AUTHZ_PERMISSION_DENIED, `permission denied: ${reason}`, diff --git a/apps/hcdc-access-service/src/domain/use-case/auth/populate-context.ts b/apps/hcdc-access-service/src/domain/use-case/auth/populate-context.ts index 1a494dd2..84b77310 100644 --- a/apps/hcdc-access-service/src/domain/use-case/auth/populate-context.ts +++ b/apps/hcdc-access-service/src/domain/use-case/auth/populate-context.ts @@ -22,22 +22,19 @@ export class AuthPopulateContextUseCase { filter: { _id: input.userId }, populates: [{ path: 'roles', fields: ['permissions'] as (keyof Role)[] }], }) - if (!user) { throw new EAuthnPayloadUserNotFound() } const rolesPermissions: PermissionRule[] = [] user.roles?.forEach((role) => { - if (role != null) { + if (role !== null) { rolesPermissions.push(...role.permissions) } }) - const { inlinePermissions } = user - const permissionTemplate = buildJSONTemplate([ ...rolesPermissions, - ...inlinePermissions, + ...user.inlinePermissions, ]) const permissions = permissionTemplate({ user }) as PermissionRule[] const ability = createMongoAbility(permissions) diff --git a/apps/hcdc-access-service/src/domain/use-case/bio-product/create.ts b/apps/hcdc-access-service/src/domain/use-case/bio-product/create.ts index 57da04c6..4c3d7c34 100644 --- a/apps/hcdc-access-service/src/domain/use-case/bio-product/create.ts +++ b/apps/hcdc-access-service/src/domain/use-case/bio-product/create.ts @@ -8,6 +8,7 @@ import { } from 'src/domain/interface' import { BioProduct, BioProductAction, EntityData } from 'src/domain/entity' import { AuthSubject, assertPermission } from 'src/domain/auth' +import { BioProductValidateUseCase } from './validate' @Injectable() export class BioProductCreateUseCase { @@ -16,6 +17,7 @@ export class BioProductCreateUseCase { private readonly authContext: IAuthContext, @Inject(BioProductRepositoryToken) private readonly bioProductRepository: IBioProductRepository, + private readonly bioProductValidateUseCase: BioProductValidateUseCase, ) {} async execute(input: EntityData) { @@ -26,6 +28,7 @@ export class BioProductCreateUseCase { BioProductAction.Create, input, ) + await this.bioProductValidateUseCase.execute(input) const entity = await this.bioProductRepository.create(input) diff --git a/apps/hcdc-access-service/src/domain/use-case/bio-product/update.ts b/apps/hcdc-access-service/src/domain/use-case/bio-product/update.ts index d79995c0..1e075784 100644 --- a/apps/hcdc-access-service/src/domain/use-case/bio-product/update.ts +++ b/apps/hcdc-access-service/src/domain/use-case/bio-product/update.ts @@ -9,6 +9,7 @@ import { IBioProductRepository, } from 'src/domain/interface' import { BioProductAssertExistsUseCase } from './assert-exists' +import { BioProductValidateUseCase } from './validate' @Injectable() export class BioProductUpdateUseCase { @@ -18,6 +19,7 @@ export class BioProductUpdateUseCase { @Inject(AuthContextToken) private readonly authContext: IAuthContext, private readonly bioProductAssertExistsUseCase: BioProductAssertExistsUseCase, + private readonly bioProductValidateUseCase: BioProductValidateUseCase, ) {} async execute(...input: Parameters) { @@ -29,6 +31,7 @@ export class BioProductUpdateUseCase { BioProductAction.Update, entity, ) + await this.bioProductValidateUseCase.execute(input[1]) return this.bioProductRepository.update(...input) } diff --git a/apps/hcdc-access-service/src/domain/use-case/bio-product/validate.ts b/apps/hcdc-access-service/src/domain/use-case/bio-product/validate.ts new file mode 100644 index 00000000..f51c1166 --- /dev/null +++ b/apps/hcdc-access-service/src/domain/use-case/bio-product/validate.ts @@ -0,0 +1,19 @@ +import { Injectable } from '@nestjs/common' + +import { BioProduct, EntityData } from 'src/domain/entity' +import { BranchAssertExistsUseCase } from '../branch/assert-exists' + +@Injectable() +export class BioProductValidateUseCase { + constructor( + private readonly branchAssertExistsUseCase: BranchAssertExistsUseCase, + ) {} + + async execute(input: Partial, 'branchId'>>) { + const { branchId } = input + + if (branchId !== undefined) { + await this.branchAssertExistsUseCase.execute({ _id: branchId }) + } + } +} diff --git a/apps/hcdc-access-service/src/domain/use-case/branch/create.ts b/apps/hcdc-access-service/src/domain/use-case/branch/create.ts index 67950096..6bca2b3e 100644 --- a/apps/hcdc-access-service/src/domain/use-case/branch/create.ts +++ b/apps/hcdc-access-service/src/domain/use-case/branch/create.ts @@ -8,6 +8,7 @@ import { } from 'src/domain/interface' import { Branch, BranchAction, EntityData } from 'src/domain/entity' import { AuthSubject, assertPermission } from 'src/domain/auth' +import { BranchValidateUseCase } from './validate' @Injectable() export class BranchCreateUseCase { @@ -16,11 +17,13 @@ export class BranchCreateUseCase { private readonly authContext: IAuthContext, @Inject(BranchRepositoryToken) private readonly branchRepository: IBranchRepository, + private readonly branchValidateUseCase: BranchValidateUseCase, ) {} async execute(input: EntityData) { const { ability } = this.authContext.getData() assertPermission(ability, AuthSubject.Branch, BranchAction.Create, input) + await this.branchValidateUseCase.execute(input) const entity = await this.branchRepository.create(input) diff --git a/apps/hcdc-access-service/src/domain/use-case/branch/update.ts b/apps/hcdc-access-service/src/domain/use-case/branch/update.ts index cf896ee0..40cc40fd 100644 --- a/apps/hcdc-access-service/src/domain/use-case/branch/update.ts +++ b/apps/hcdc-access-service/src/domain/use-case/branch/update.ts @@ -9,6 +9,7 @@ import { IBranchRepository, } from 'src/domain/interface' import { BranchAssertExistsUseCase } from './assert-exists' +import { BranchValidateUseCase } from './validate' @Injectable() export class BranchUpdateUseCase { @@ -18,12 +19,14 @@ export class BranchUpdateUseCase { @Inject(AuthContextToken) private readonly authContext: IAuthContext, private readonly branchAssertExistsUseCase: BranchAssertExistsUseCase, + private readonly branchValidateUseCase: BranchValidateUseCase, ) {} async execute(...input: Parameters) { const entity = await this.branchAssertExistsUseCase.execute(input[0]) const { ability } = this.authContext.getData() assertPermission(ability, AuthSubject.Branch, BranchAction.Update, entity) + await this.branchValidateUseCase.execute(input[1]) return this.branchRepository.update(...input) } diff --git a/apps/hcdc-access-service/src/domain/use-case/branch/validate.ts b/apps/hcdc-access-service/src/domain/use-case/branch/validate.ts new file mode 100644 index 00000000..124aaf7f --- /dev/null +++ b/apps/hcdc-access-service/src/domain/use-case/branch/validate.ts @@ -0,0 +1,21 @@ +import { Injectable } from '@nestjs/common' + +import { Branch, EntityData } from 'src/domain/entity' +import { BranchAssertExistsUseCase } from './assert-exists' + +@Injectable() +export class BranchValidateUseCase { + constructor( + private readonly branchAssertExistsUseCase: BranchAssertExistsUseCase, + ) {} + + async execute(input: Partial, 'sampleOriginIds'>>) { + const { sampleOriginIds } = input + + if (sampleOriginIds) { + for (const branchId of sampleOriginIds) { + await this.branchAssertExistsUseCase.execute({ _id: branchId }) + } + } + } +} diff --git a/apps/hcdc-access-service/src/domain/use-case/index.ts b/apps/hcdc-access-service/src/domain/use-case/index.ts index 57ccf8f2..e71725d6 100644 --- a/apps/hcdc-access-service/src/domain/use-case/index.ts +++ b/apps/hcdc-access-service/src/domain/use-case/index.ts @@ -12,6 +12,7 @@ export * from './bio-product/update' export * from './bio-product/delete' export * from './bio-product/search' export * from './bio-product/assert-exists' +export * from './bio-product/validate' export * from './role/create' export * from './role/find-one' @@ -26,6 +27,7 @@ export * from './branch/update' export * from './branch/delete' export * from './branch/search' export * from './branch/assert-exists' +export * from './branch/validate' export * from './user/create' export * from './user/find-one' diff --git a/apps/hcdc-access-service/src/domain/use-case/module.ts b/apps/hcdc-access-service/src/domain/use-case/module.ts index c5595c1f..a02ded60 100644 --- a/apps/hcdc-access-service/src/domain/use-case/module.ts +++ b/apps/hcdc-access-service/src/domain/use-case/module.ts @@ -2,6 +2,7 @@ import { ModuleMetadata } from '@nestjs/common' import { BioProductCreateUseCase } from './bio-product/create' import { BioProductUpdateUseCase } from './bio-product/update' +import { BioProductValidateUseCase } from './bio-product/validate' import { BioProductFindOneUseCase } from './bio-product/find-one' import { BioProductDeleteUseCase } from './bio-product/delete' import { BioProductSearchUseCase } from './bio-product/search' @@ -17,6 +18,7 @@ import { RoleValidateUseCase } from './role/validate' import { BranchCreateUseCase } from './branch/create' import { BranchUpdateUseCase } from './branch/update' +import { BranchValidateUseCase } from './branch/validate' import { BranchFindOneUseCase } from './branch/find-one' import { BranchDeleteUseCase } from './branch/delete' import { BranchSearchUseCase } from './branch/search' @@ -48,6 +50,7 @@ export const useCaseMetadata: ModuleMetadata = { BioProductDeleteUseCase, BioProductSearchUseCase, BioProductAssertExistsUseCase, + BioProductValidateUseCase, BranchCreateUseCase, BranchFindOneUseCase, @@ -55,6 +58,7 @@ export const useCaseMetadata: ModuleMetadata = { BranchDeleteUseCase, BranchSearchUseCase, BranchAssertExistsUseCase, + BranchValidateUseCase, RoleCreateUseCase, RoleFindOneUseCase, diff --git a/apps/hcdc-access-service/src/domain/use-case/role/validate.ts b/apps/hcdc-access-service/src/domain/use-case/role/validate.ts index 31bb892d..e35d860d 100644 --- a/apps/hcdc-access-service/src/domain/use-case/role/validate.ts +++ b/apps/hcdc-access-service/src/domain/use-case/role/validate.ts @@ -9,13 +9,11 @@ export class RoleValidateUseCase { private readonly branchAssertExistsUseCase: BranchAssertExistsUseCase, ) {} - async execute(input: Partial, 'branchIds'>>) { - const { branchIds } = input + async execute(input: Partial, 'branchId'>>) { + const { branchId } = input - if (branchIds) { - for (const branchId of branchIds) { - await this.branchAssertExistsUseCase.execute({ _id: branchId }) - } + if (branchId !== undefined) { + await this.branchAssertExistsUseCase.execute({ _id: branchId }) } } } diff --git a/apps/hcdc-access-service/src/domain/use-case/user/update.ts b/apps/hcdc-access-service/src/domain/use-case/user/update.ts index ec17dd85..2a0e98a9 100644 --- a/apps/hcdc-access-service/src/domain/use-case/user/update.ts +++ b/apps/hcdc-access-service/src/domain/use-case/user/update.ts @@ -37,6 +37,7 @@ export class UserUpdateUseCase { await this.userValidateUseCase.execute({ branchIds: data?.branchIds, roleIds: data?.roleIds, + inlinePermissions: data?.inlinePermissions, }) return this.userRepository.update(filter, data, options) diff --git a/apps/hcdc-access-service/src/domain/use-case/user/validate.ts b/apps/hcdc-access-service/src/domain/use-case/user/validate.ts index 5a571bc4..f0033d0f 100644 --- a/apps/hcdc-access-service/src/domain/use-case/user/validate.ts +++ b/apps/hcdc-access-service/src/domain/use-case/user/validate.ts @@ -1,28 +1,39 @@ -import { Injectable } from '@nestjs/common' +import { Inject, Injectable } from '@nestjs/common' import { User, EntityData } from 'src/domain/entity' import { BranchAssertExistsUseCase } from '../branch/assert-exists' import { RoleAssertExistsUseCase } from '../role/assert-exists' +import { AuthContextToken, IAuthContext } from 'src/domain/interface' +import { AUTH_ACTION_ALL, AuthSubject, assertPermission } from 'src/domain/auth' @Injectable() export class UserValidateUseCase { constructor( + @Inject(AuthContextToken) + private readonly authContext: IAuthContext, private readonly branchAssertExistsUseCase: BranchAssertExistsUseCase, private readonly roleAssertExistsUseCase: RoleAssertExistsUseCase, ) {} async execute( - input: Partial, 'branchIds' | 'roleIds'>>, + input: Partial< + Pick, 'branchIds' | 'roleIds' | 'inlinePermissions'> + >, ) { - const { branchIds, roleIds } = input + const { ability } = this.authContext.getData() + const { branchIds, roleIds, inlinePermissions } = input - if (branchIds) { + if (inlinePermissions !== undefined) { + assertPermission(ability, AuthSubject.Role, AUTH_ACTION_ALL) + } + + if (branchIds !== undefined) { for (const branchId of branchIds) { await this.branchAssertExistsUseCase.execute({ _id: branchId }) } } - if (roleIds) { + if (roleIds !== undefined) { for (const roleId of roleIds) { await this.roleAssertExistsUseCase.execute({ _id: roleId }) } diff --git a/apps/hcdc-access-service/src/infrastructure/mongo/bio-product/schema.ts b/apps/hcdc-access-service/src/infrastructure/mongo/bio-product/schema.ts index 03bf51f8..bce1802c 100644 --- a/apps/hcdc-access-service/src/infrastructure/mongo/bio-product/schema.ts +++ b/apps/hcdc-access-service/src/infrastructure/mongo/bio-product/schema.ts @@ -1,16 +1,32 @@ import { Prop, Schema } from '@nestjs/mongoose' import { BaseSchema, baseSchemaOptions } from '@diut/nest-core' +import { Types } from 'mongoose' import { COLLECTION } from '../collections' +import { BranchSchema } from '../branch' @Schema({ ...baseSchemaOptions, collection: COLLECTION.BIO_PRODUCT, + virtuals: { + branch: { + options: { + ref: BranchSchema.name, + localField: 'branchId', + foreignField: '_id', + justOne: true, + }, + }, + }, }) export class BioProductSchema extends BaseSchema { @Prop({ required: true }) - index: number + displayIndex: number @Prop({ required: true }) name: string + + @Prop({ required: true, type: [Types.ObjectId] }) + branchId: string + branch?: BranchSchema | null } diff --git a/apps/hcdc-access-service/src/infrastructure/mongo/branch/schema.ts b/apps/hcdc-access-service/src/infrastructure/mongo/branch/schema.ts index b3edf5a2..0d0c9bd3 100644 --- a/apps/hcdc-access-service/src/infrastructure/mongo/branch/schema.ts +++ b/apps/hcdc-access-service/src/infrastructure/mongo/branch/schema.ts @@ -1,5 +1,6 @@ import { Prop, Schema } from '@nestjs/mongoose' import { BaseSchema, baseSchemaOptions } from '@diut/nest-core' +import { Types } from 'mongoose' import { COLLECTION } from '../collections' import { BranchType } from 'src/domain' @@ -7,10 +8,20 @@ import { BranchType } from 'src/domain' @Schema({ ...baseSchemaOptions, collection: COLLECTION.BRANCH, + virtuals: { + sampleOrigins: { + options: { + ref: BranchSchema.name, + localField: 'sampleOriginIds', + foreignField: '_id', + justOne: false, + }, + }, + }, }) export class BranchSchema extends BaseSchema { @Prop({ required: true }) - index: number + displayIndex: number @Prop({ required: true }) name: string @@ -20,4 +31,8 @@ export class BranchSchema extends BaseSchema { @Prop({ required: true, enum: BranchType }) type: BranchType + + @Prop({ required: true, type: [Types.ObjectId] }) + sampleOriginIds: string[] + sampleOrigins?: (BranchSchema | null)[] } diff --git a/apps/hcdc-access-service/src/infrastructure/mongo/role/schema.ts b/apps/hcdc-access-service/src/infrastructure/mongo/role/schema.ts index 9d188fa9..2514b7c2 100644 --- a/apps/hcdc-access-service/src/infrastructure/mongo/role/schema.ts +++ b/apps/hcdc-access-service/src/infrastructure/mongo/role/schema.ts @@ -11,19 +11,19 @@ import { PermissionRuleSchema } from '../auth' ...baseSchemaOptions, collection: COLLECTION.ROLE, virtuals: { - branches: { + branch: { options: { ref: BranchSchema.name, - localField: 'branchIds', + localField: 'branchId', foreignField: '_id', - justOne: false, + justOne: true, }, }, }, }) export class RoleSchema extends BaseSchema { @Prop({ required: true }) - index: number + displayIndex: number @Prop({ required: true }) name: string @@ -37,7 +37,7 @@ export class RoleSchema extends BaseSchema { }) permissions: PermissionRule[] - @Prop({ required: true, type: [Types.ObjectId] }) - branchIds: string[] - branches?: (BranchSchema | null)[] + @Prop({ required: true, type: Types.ObjectId }) + branchId: string + branch?: BranchSchema | null } diff --git a/apps/hcdc-access-service/src/presentation/http/common/exception-filter/domain.ts b/apps/hcdc-access-service/src/presentation/http/common/exception-filter/domain.ts index ca2a85e5..4224b017 100644 --- a/apps/hcdc-access-service/src/presentation/http/common/exception-filter/domain.ts +++ b/apps/hcdc-access-service/src/presentation/http/common/exception-filter/domain.ts @@ -16,7 +16,7 @@ export class DomainExceptionFilter implements ExceptionFilter { constructor() {} catch(exception: EDomain, host: ArgumentsHost) { - this.logger.error(exception) + this.logger.warn(exception) const ctx = host.switchToHttp() const response = ctx.getResponse() diff --git a/apps/hcdc-access-service/src/presentation/http/v1/auth/routes.ts b/apps/hcdc-access-service/src/presentation/http/v1/auth/routes.ts index 16ba89be..253e6236 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/auth/routes.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/auth/routes.ts @@ -22,6 +22,7 @@ export const authRoutes = { me: { path: 'me', + serialize: AuthMeResponseDto, openApi: { responses: [ { diff --git a/apps/hcdc-access-service/src/presentation/http/v1/bio-product/dto/create.request-dto.ts b/apps/hcdc-access-service/src/presentation/http/v1/bio-product/dto/create.request-dto.ts index 7bf9ec44..65136520 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/bio-product/dto/create.request-dto.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/bio-product/dto/create.request-dto.ts @@ -1,3 +1,4 @@ +import { IsObjectId } from '@diut/nest-core' import { ApiProperty } from '@nestjs/swagger' import { Expose } from 'class-transformer' import { IsNotEmpty, IsNumber, IsString, Min } from 'class-validator' @@ -6,14 +7,19 @@ import { exampleBioProduct } from 'src/domain' export class BioProductCreateRequestDto { @Expose() - @ApiProperty(exampleBioProduct.index) + @ApiProperty(exampleBioProduct.displayIndex) @IsNumber() @Min(1) - index: number + displayIndex: number @Expose() @ApiProperty(exampleBioProduct.name) @IsString() @IsNotEmpty() name: string + + @Expose() + @ApiProperty(exampleBioProduct.branchId) + @IsObjectId() + branchId: string } diff --git a/apps/hcdc-access-service/src/presentation/http/v1/bio-product/dto/response-dto.ts b/apps/hcdc-access-service/src/presentation/http/v1/bio-product/dto/response-dto.ts index 5f0386b9..9e1ceaba 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/bio-product/dto/response-dto.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/bio-product/dto/response-dto.ts @@ -1,9 +1,19 @@ -import { IntersectionType } from '@nestjs/swagger' +import { ApiProperty, IntersectionType } from '@nestjs/swagger' import { BaseResourceResponseDto } from '@diut/nest-core' +import { Expose, Type } from 'class-transformer' +import { ValidateNested } from 'class-validator' import { BioProductCreateRequestDto } from './create.request-dto' +import { Branch, exampleBioProduct } from 'src/domain' +import { BranchResponseDto } from '../../branch/dto/response-dto' export class BioProductResponseDto extends IntersectionType( BaseResourceResponseDto, BioProductCreateRequestDto, -) {} +) { + @Expose() + @ApiProperty({ ...exampleBioProduct.branch, type: () => BranchResponseDto }) + @ValidateNested({ each: true }) + @Type(() => BranchResponseDto) + branch?: Branch +} diff --git a/apps/hcdc-access-service/src/presentation/http/v1/branch/controller.ts b/apps/hcdc-access-service/src/presentation/http/v1/branch/controller.ts index fc97598f..ec88e38d 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/branch/controller.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/branch/controller.ts @@ -39,7 +39,10 @@ export class BranchController { @HttpRoute(branchRoutes.findById) async findById(@Param('id', ObjectIdPipe) id: string) { - const rv = await this.branchFindOneUseCase.execute({ filter: { _id: id } }) + const rv = await this.branchFindOneUseCase.execute({ + filter: { _id: id }, + populates: [{ path: 'sampleOrigins' }], + }) if (rv == null) { throw new EEntityNotFound(`Branch id=${id}`) diff --git a/apps/hcdc-access-service/src/presentation/http/v1/branch/dto/create.request-dto.ts b/apps/hcdc-access-service/src/presentation/http/v1/branch/dto/create.request-dto.ts index 01b6c758..2a2ae23b 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/branch/dto/create.request-dto.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/branch/dto/create.request-dto.ts @@ -1,15 +1,23 @@ +import { IsObjectId } from '@diut/nest-core' import { ApiProperty } from '@nestjs/swagger' import { Expose } from 'class-transformer' -import { IsEnum, IsNotEmpty, IsNumber, IsString, Min } from 'class-validator' +import { + IsArray, + IsEnum, + IsNotEmpty, + IsNumber, + IsString, + Min, +} from 'class-validator' import { BranchType, exampleBranch } from 'src/domain' export class BranchCreateRequestDto { @Expose() - @ApiProperty(exampleBranch.index) + @ApiProperty(exampleBranch.displayIndex) @IsNumber() @Min(1) - index: number + displayIndex: number @Expose() @ApiProperty(exampleBranch.name) @@ -27,4 +35,10 @@ export class BranchCreateRequestDto { @ApiProperty(exampleBranch.type) @IsEnum(BranchType) type: BranchType + + @Expose() + @ApiProperty(exampleBranch.sampleOriginIds) + @IsArray() + @IsObjectId({ each: true }) + sampleOriginIds: string[] } diff --git a/apps/hcdc-access-service/src/presentation/http/v1/branch/dto/response-dto.ts b/apps/hcdc-access-service/src/presentation/http/v1/branch/dto/response-dto.ts index d20f6a1a..c486336a 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/branch/dto/response-dto.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/branch/dto/response-dto.ts @@ -1,9 +1,25 @@ -import { IntersectionType } from '@nestjs/swagger' +import { ApiProperty, IntersectionType, OmitType } from '@nestjs/swagger' import { BaseResourceResponseDto } from '@diut/nest-core' +import { Expose, Type } from 'class-transformer' +import { IsArray, ValidateNested } from 'class-validator' import { BranchCreateRequestDto } from './create.request-dto' +import { Branch, exampleBranch } from 'src/domain' export class BranchResponseDto extends IntersectionType( BaseResourceResponseDto, BranchCreateRequestDto, -) {} +) { + @Expose() + @ApiProperty({ + ...exampleBranch.sampleOrigins, + type: () => + class OmittedBranchResponseDto extends OmitType(BranchResponseDto, [ + 'sampleOrigins', + ]) {}, + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => BranchResponseDto) + sampleOrigins?: (Branch | null)[] +} diff --git a/apps/hcdc-access-service/src/presentation/http/v1/role/controller.ts b/apps/hcdc-access-service/src/presentation/http/v1/role/controller.ts index 5faf6247..0541ff6e 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/role/controller.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/role/controller.ts @@ -39,7 +39,7 @@ export class RoleController { async findById(@Param('id', ObjectIdPipe) id: string) { const rv = await this.roleFindOneUseCase.execute({ filter: { _id: id }, - populates: [{ path: 'branches' }], + populates: [{ path: 'branch' }], }) if (rv === null) { diff --git a/apps/hcdc-access-service/src/presentation/http/v1/role/dto/create.request-dto.ts b/apps/hcdc-access-service/src/presentation/http/v1/role/dto/create.request-dto.ts index 90876d63..2bc2178e 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/role/dto/create.request-dto.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/role/dto/create.request-dto.ts @@ -15,10 +15,10 @@ import { PermissionRuleRequestDto } from '../../auth/dto/permission-rule.dto' export class RoleCreateRequestDto { @Expose() - @ApiProperty(exampleRole.index) + @ApiProperty(exampleRole.displayIndex) @IsNumber() @Min(1) - index: number + displayIndex: number @Expose() @ApiProperty(exampleRole.name) @@ -43,8 +43,7 @@ export class RoleCreateRequestDto { permissions: PermissionRule[] @Expose() - @ApiProperty(exampleRole.branchIds) - @IsArray() - @IsObjectId({ each: true }) - branchIds: string[] + @ApiProperty(exampleRole.branchId) + @IsObjectId() + branchId: string } diff --git a/apps/hcdc-access-service/src/presentation/http/v1/role/dto/response-dto.ts b/apps/hcdc-access-service/src/presentation/http/v1/role/dto/response-dto.ts index d5e82ad9..53b41f88 100644 --- a/apps/hcdc-access-service/src/presentation/http/v1/role/dto/response-dto.ts +++ b/apps/hcdc-access-service/src/presentation/http/v1/role/dto/response-dto.ts @@ -12,9 +12,8 @@ export class RoleResponseDto extends IntersectionType( RoleCreateRequestDto, ) { @Expose() - @ApiProperty({ ...exampleRole.branches, type: () => BranchResponseDto }) - @IsArray() + @ApiProperty({ ...exampleRole.branch, type: () => BranchResponseDto }) @ValidateNested({ each: true }) @Type(() => BranchResponseDto) - branches?: Branch[] + branch?: Branch } diff --git a/libs/nest-core/src/transport/http/bootstrap/swagger.bootstrap.ts b/libs/nest-core/src/transport/http/bootstrap/swagger.bootstrap.ts index d8bd82ac..3aee044c 100644 --- a/libs/nest-core/src/transport/http/bootstrap/swagger.bootstrap.ts +++ b/libs/nest-core/src/transport/http/bootstrap/swagger.bootstrap.ts @@ -22,7 +22,6 @@ export const SwaggerBootstrap: BootstrapConfig = { SwaggerModule.setup(SWAGGER_ENDPOINT, ctx.app, swaggerDocument, { swaggerOptions: { - docExpansion: 'none', filter: true, persistAuthorization: true, },