Skip to content

Commit 2beb0b6

Browse files
authored
Merge pull request #2374 from bcgov/feature/2358
Set up C&E backend: DB table, service, controller
2 parents 41a0b90 + 0a217c6 commit 2beb0b6

11 files changed

+671
-0
lines changed

services/apps/alcs/src/alcs/alcs.module.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import { StaffJournalModule } from './staff-journal/staff-journal.module';
3131
import { IncomingFileModule } from './incoming-files/incoming-file.module';
3232
import { TagModule } from './tag/tag.module';
3333
import { AlcsDocumentModule } from '../alcs/document/document.module';
34+
import { ComplianceAndEnforcementModule } from './compliance-and-enforcement/compliance-and-enforcement.module';
3435

3536
@Module({
3637
imports: [
@@ -56,6 +57,7 @@ import { AlcsDocumentModule } from '../alcs/document/document.module';
5657
NotificationModule,
5758
NotificationTimelineModule,
5859
InquiryModule,
60+
ComplianceAndEnforcementModule,
5961
MeetingModule,
6062
PlanningReviewTimelineModule,
6163
MaintenanceModule,
@@ -88,6 +90,7 @@ import { AlcsDocumentModule } from '../alcs/document/document.module';
8890
{ path: 'alcs', module: NotificationSubmissionStatusModule },
8991
{ path: 'alcs', module: NotificationTimelineModule },
9092
{ path: 'alcs', module: InquiryModule },
93+
{ path: 'alcs', module: ComplianceAndEnforcementModule },
9194
{ path: 'alcs', module: MeetingModule },
9295
{ path: 'alcs', module: PlanningReviewTimelineModule },
9396
{ path: 'alcs', module: TagModule },
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { createMap, forMember, mapFrom, Mapper } from 'automapper-core';
3+
import { AutomapperProfile, InjectMapper } from 'automapper-nestjs';
4+
import { AllegedActivity, ComplianceAndEnforcement, InitialSubmissionType } from './compliance-and-enforcement.entity';
5+
import { ComplianceAndEnforcementDto, UpdateComplianceAndEnforcementDto } from './compliance-and-enforcement.dto';
6+
import { In } from 'typeorm';
7+
8+
@Injectable()
9+
export class ComplianceAndEnforcementProfile extends AutomapperProfile {
10+
constructor(@InjectMapper() mapper: Mapper) {
11+
super(mapper);
12+
}
13+
14+
override get profile() {
15+
return (mapper) => {
16+
createMap(
17+
mapper,
18+
ComplianceAndEnforcement,
19+
ComplianceAndEnforcementDto,
20+
forMember(
21+
(dto) => dto.dateSubmitted,
22+
mapFrom((entity) => entity.dateSubmitted?.getTime()),
23+
),
24+
forMember(
25+
(dto) => dto.dateOpened,
26+
mapFrom((entity) => entity.dateOpened?.getTime()),
27+
),
28+
forMember(
29+
(dto) => dto.dateClosed,
30+
mapFrom((entity) => entity.dateClosed?.getTime()),
31+
),
32+
forMember(
33+
(dto) => dto.initialSubmissionType,
34+
mapFrom((entity) =>
35+
entity.initialSubmissionType !== null ? (entity.initialSubmissionType as string) : null,
36+
),
37+
),
38+
forMember(
39+
(dto) => dto.allegedActivity,
40+
mapFrom((entity) => entity.allegedActivity.map((activity) => activity as string)),
41+
),
42+
);
43+
44+
createMap(
45+
mapper,
46+
UpdateComplianceAndEnforcementDto,
47+
ComplianceAndEnforcement,
48+
forMember(
49+
(entity) => entity.dateSubmitted,
50+
mapFrom((dto) =>
51+
dto.dateSubmitted !== undefined && dto.dateSubmitted !== null
52+
? new Date(dto.dateSubmitted)
53+
: dto.dateSubmitted,
54+
),
55+
),
56+
forMember(
57+
(entity) => entity.dateOpened,
58+
mapFrom((dto) =>
59+
dto.dateOpened !== undefined && dto.dateOpened !== null ? new Date(dto.dateOpened) : dto.dateOpened,
60+
),
61+
),
62+
forMember(
63+
(entity) => entity.dateClosed,
64+
mapFrom((dto) =>
65+
dto.dateClosed !== undefined && dto.dateClosed !== null ? new Date(dto.dateClosed) : dto.dateClosed,
66+
),
67+
),
68+
forMember(
69+
(entity) => entity.initialSubmissionType,
70+
mapFrom((dto) =>
71+
dto.initialSubmissionType !== null && dto.initialSubmissionType !== undefined
72+
? (dto.initialSubmissionType as InitialSubmissionType)
73+
: dto.initialSubmissionType,
74+
),
75+
),
76+
forMember(
77+
(entity) => entity.allegedActivity,
78+
mapFrom((dto) =>
79+
dto.allegedActivity !== undefined
80+
? dto.allegedActivity.map((activity) => activity as AllegedActivity)
81+
: dto.allegedActivity,
82+
),
83+
),
84+
);
85+
};
86+
}
87+
}
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import { Test, TestingModule } from '@nestjs/testing';
2+
import { ComplianceAndEnforcementController } from './compliance-and-enforcement.controller';
3+
import { ComplianceAndEnforcementService } from './compliance-and-enforcement.service';
4+
import { createMock, DeepMocked } from '@golevelup/nestjs-testing';
5+
import { mockKeyCloakProviders } from '../../../test/mocks/mockTypes';
6+
import { ClsService } from 'nestjs-cls';
7+
import { ComplianceAndEnforcementDto } from './compliance-and-enforcement.dto';
8+
import { AutomapperModule } from 'automapper-nestjs';
9+
import { classes } from 'automapper-classes';
10+
11+
describe('ComplianceAndEnforcementController', () => {
12+
let controller: ComplianceAndEnforcementController;
13+
let mockComplianceAndEnforcementService: DeepMocked<ComplianceAndEnforcementService>;
14+
15+
beforeEach(async () => {
16+
mockComplianceAndEnforcementService = createMock();
17+
18+
const module: TestingModule = await Test.createTestingModule({
19+
imports: [
20+
AutomapperModule.forRoot({
21+
strategyInitializer: classes(),
22+
}),
23+
],
24+
controllers: [ComplianceAndEnforcementController],
25+
providers: [
26+
{
27+
provide: ComplianceAndEnforcementService,
28+
useValue: mockComplianceAndEnforcementService,
29+
},
30+
{
31+
provide: ClsService,
32+
useValue: {},
33+
},
34+
...mockKeyCloakProviders,
35+
],
36+
}).compile();
37+
38+
controller = module.get<ComplianceAndEnforcementController>(ComplianceAndEnforcementController);
39+
});
40+
41+
it('should be defined', () => {
42+
expect(controller).toBeDefined();
43+
});
44+
45+
describe('fetch', () => {
46+
it('should return all items', async () => {
47+
const result: ComplianceAndEnforcementDto[] = [
48+
{
49+
uuid: '1',
50+
fileNumber: '1',
51+
dateSubmitted: 0,
52+
dateOpened: 0,
53+
dateClosed: 0,
54+
initialSubmissionType: null,
55+
allegedContraventionNarrative: '',
56+
allegedActivity: [],
57+
intakeNotes: '',
58+
},
59+
{
60+
uuid: '2',
61+
fileNumber: '2',
62+
dateSubmitted: 0,
63+
dateOpened: 0,
64+
dateClosed: 0,
65+
initialSubmissionType: null,
66+
allegedContraventionNarrative: '',
67+
allegedActivity: [],
68+
intakeNotes: '',
69+
},
70+
];
71+
mockComplianceAndEnforcementService.fetchAll.mockResolvedValue(result);
72+
expect(await controller.fetchAll()).toEqual(result);
73+
expect(mockComplianceAndEnforcementService.fetchAll).toHaveBeenCalled();
74+
});
75+
});
76+
77+
describe('fetchByFileNumber', () => {
78+
it('should return a single item by ID', async () => {
79+
const result: ComplianceAndEnforcementDto = {
80+
uuid: '1',
81+
fileNumber: '1',
82+
dateSubmitted: 0,
83+
dateOpened: 0,
84+
dateClosed: 0,
85+
initialSubmissionType: null,
86+
allegedContraventionNarrative: '',
87+
allegedActivity: [],
88+
intakeNotes: '',
89+
};
90+
mockComplianceAndEnforcementService.fetchByFileNumber.mockResolvedValue(result);
91+
expect(await controller.fetchByFileNumber('1')).toEqual(result);
92+
expect(mockComplianceAndEnforcementService.fetchByFileNumber).toHaveBeenCalledWith('1');
93+
});
94+
});
95+
96+
describe('create', () => {
97+
it('should create a new item', async () => {
98+
const createDto = {};
99+
const resultDto: ComplianceAndEnforcementDto = {
100+
uuid: '1',
101+
fileNumber: '1',
102+
dateSubmitted: 0,
103+
dateOpened: 0,
104+
dateClosed: 0,
105+
initialSubmissionType: null,
106+
allegedContraventionNarrative: '',
107+
allegedActivity: [],
108+
intakeNotes: '',
109+
};
110+
mockComplianceAndEnforcementService.create.mockResolvedValue(resultDto);
111+
expect(await controller.create(createDto)).toEqual(resultDto);
112+
expect(mockComplianceAndEnforcementService.create).toHaveBeenCalledWith(createDto);
113+
});
114+
});
115+
116+
describe('update', () => {
117+
it('should update an existing item', async () => {
118+
const updateDto = {
119+
uuid: '1',
120+
fileNumber: '1',
121+
dateSubmitted: 0,
122+
dateOpened: 0,
123+
dateClosed: 0,
124+
initialSubmissionType: null,
125+
allegedContraventionNarrative: '',
126+
allegedActivity: [],
127+
intakeNotes: '',
128+
};
129+
const resultDto = {
130+
uuid: '1',
131+
fileNumber: '1',
132+
dateSubmitted: 0,
133+
dateOpened: 0,
134+
dateClosed: 0,
135+
initialSubmissionType: null,
136+
allegedContraventionNarrative: '',
137+
allegedActivity: [],
138+
intakeNotes: '',
139+
};
140+
mockComplianceAndEnforcementService.update.mockResolvedValue(resultDto);
141+
expect(await controller.update('1', updateDto)).toEqual(resultDto);
142+
expect(mockComplianceAndEnforcementService.update).toHaveBeenCalledWith('1', updateDto);
143+
});
144+
});
145+
146+
describe('delete', () => {
147+
it('should delete an item', async () => {
148+
const result = { affected: 1, raw: '' };
149+
mockComplianceAndEnforcementService.delete.mockResolvedValue(result);
150+
expect(await controller.delete('1')).toEqual(result);
151+
expect(mockComplianceAndEnforcementService.delete).toHaveBeenCalledWith('1');
152+
});
153+
});
154+
});
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
import { Body, Controller, Delete, Get, Param, Patch, Post, UseGuards } from '@nestjs/common';
2+
import { ApiOAuth2 } from '@nestjs/swagger';
3+
import * as config from 'config';
4+
import { RolesGuard } from '../../common/authorization/roles-guard.service';
5+
import { UserRoles } from '../../common/authorization/roles.decorator';
6+
import { AUTH_ROLE, ROLES_ALLOWED_APPLICATIONS } from '../../common/authorization/roles';
7+
import { ComplianceAndEnforcementService } from './compliance-and-enforcement.service';
8+
import { ComplianceAndEnforcementDto, UpdateComplianceAndEnforcementDto } from './compliance-and-enforcement.dto';
9+
10+
@Controller('compliance-and-enforcement')
11+
@ApiOAuth2(config.get<string[]>('KEYCLOAK.SCOPES'))
12+
@UseGuards(RolesGuard)
13+
export class ComplianceAndEnforcementController {
14+
constructor(private service: ComplianceAndEnforcementService) {}
15+
16+
@Get('')
17+
@UserRoles(...ROLES_ALLOWED_APPLICATIONS)
18+
async fetchAll(): Promise<ComplianceAndEnforcementDto[]> {
19+
return await this.service.fetchAll();
20+
}
21+
22+
@Get('/:fileNumber')
23+
@UserRoles(...ROLES_ALLOWED_APPLICATIONS)
24+
async fetchByFileNumber(@Param('fileNumber') fileNumber: string): Promise<ComplianceAndEnforcementDto> {
25+
return await this.service.fetchByFileNumber(fileNumber);
26+
}
27+
28+
@Post('')
29+
@UserRoles(AUTH_ROLE.ADMIN, AUTH_ROLE.C_AND_E)
30+
async create(@Body() createDto: UpdateComplianceAndEnforcementDto) {
31+
return await this.service.create(createDto);
32+
}
33+
34+
@Patch('/:uuid')
35+
@UserRoles(AUTH_ROLE.ADMIN, AUTH_ROLE.C_AND_E)
36+
async update(@Param('uuid') uuid: string, @Body() updateDto: UpdateComplianceAndEnforcementDto) {
37+
return await this.service.update(uuid, updateDto);
38+
}
39+
40+
@Delete('/:uuid')
41+
@UserRoles(AUTH_ROLE.ADMIN, AUTH_ROLE.C_AND_E)
42+
async delete(@Param('uuid') uuid: string) {
43+
return await this.service.delete(uuid);
44+
}
45+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { AutoMap } from 'automapper-classes';
2+
import { IsNumber, IsString, IsEnum, IsOptional } from 'class-validator';
3+
import { AllegedActivity, InitialSubmissionType } from './compliance-and-enforcement.entity';
4+
5+
export class ComplianceAndEnforcementDto {
6+
@AutoMap()
7+
uuid: string;
8+
9+
@AutoMap()
10+
fileNumber: string;
11+
12+
@AutoMap()
13+
dateSubmitted: number | null;
14+
15+
@AutoMap()
16+
dateOpened: number | null;
17+
18+
@AutoMap()
19+
dateClosed: number | null;
20+
21+
@AutoMap()
22+
initialSubmissionType: InitialSubmissionType | null;
23+
24+
@AutoMap()
25+
allegedContraventionNarrative: string;
26+
27+
@AutoMap()
28+
allegedActivity: AllegedActivity[];
29+
30+
@AutoMap()
31+
intakeNotes: string;
32+
}
33+
34+
export class UpdateComplianceAndEnforcementDto {
35+
@IsOptional()
36+
@IsNumber()
37+
dateSubmitted?: number | null;
38+
39+
@IsOptional()
40+
@IsNumber()
41+
dateOpened?: number | null;
42+
43+
@IsOptional()
44+
@IsNumber()
45+
dateClosed?: number | null;
46+
47+
@IsOptional()
48+
@IsEnum(InitialSubmissionType)
49+
initialSubmissionType?: string | null;
50+
51+
@IsOptional()
52+
@IsString()
53+
allegedContraventionNarrative?: string;
54+
55+
@IsOptional()
56+
@IsEnum(AllegedActivity, { each: true })
57+
allegedActivity?: string[];
58+
59+
@IsOptional()
60+
@IsString()
61+
intakeNotes?: string;
62+
}

0 commit comments

Comments
 (0)