Skip to content

Commit

Permalink
Merge pull request #20 from bcgov/feat/get-in-person-visits
Browse files Browse the repository at this point in the history
WIP: GET In Person Child / Youth Visits
  • Loading branch information
hannah-macdonald1 authored Nov 6, 2024
2 parents a0c3023 + be016b3 commit 6486d26
Show file tree
Hide file tree
Showing 40 changed files with 954 additions and 187 deletions.
1 change: 1 addition & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ CLIENT_ID='id here'
CLIENT_SECRET='secret here'
UPSTREAM_BASE_URL=http://www.google.com
SUPPORT_NETWORK_ENDPOINT=/endpoint/path/here
IN_PERSON_VISITS_ENDPOINT=/endpoint/path/here
CASE_ENDPOINT=/endpoint/path/here
INCIDENT_ENDPOINT=/endpoint/path/here
SR_ENDPOINT=/endpoint/path/here
Expand Down
8 changes: 8 additions & 0 deletions .github/workflows/build-and-push-ghcr.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ jobs:
value: ${{ secrets.APS_NAMESPACE }}
commitChange: false

- name: 'YAML poke: Set build number'
uses: fjogeleit/[email protected]
with:
valueFile: 'helm/values.yaml'
propertyPath: 'vpiAppBuildLabel.version'
value: "${{ github.ref_name }}-${{ github.run_number }}"
commitChange: false

- name: Authenticate with OpenShift
uses: redhat-actions/oc-login@v1
with:
Expand Down
7 changes: 7 additions & 0 deletions helm/templates/deployment.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -82,4 +82,11 @@ spec:
secretKeyRef:
name: visitz-api
key: SKIP_AUTH_GUARD
- name: IN_PERSON_VISITS_ENDPOINT
valueFrom:
secretKeyRef:
name: visitz-api
key: IN_PERSON_VISITS_ENDPOINT
- name: VPI_APP_LABEL
value: {{ .Values.vpiAppBuildLabel.version }}
restartPolicy: Always
3 changes: 3 additions & 0 deletions helm/values.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ affinity: {}

aps:
namespace: '<set by CI/CD>'

vpiAppBuildLabel:
version: '<set by CI/CD>'
25 changes: 23 additions & 2 deletions src/app.module.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Module } from '@nestjs/common';
import { ConfigModule } from '@nestjs/config';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { AppController } from './app.controller';
import { AppService } from './app.service';
import configuration from './configuration/configuration';
Expand All @@ -8,17 +8,38 @@ import { ControllersModule } from './controllers/controllers.module';
import { HelpersModule } from './helpers/helpers.module';
import { LoggerModule } from 'nestjs-pino';
import { CacheModule } from '@nestjs/cache-manager';
import { ExternalApiModule } from './external-api/external-api.module';

@Module({
imports: [
ConfigModule.forRoot({
load: [configuration],
}),
LoggerModule.forRoot(),
LoggerModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (configService: ConfigService) => ({
pinoHttp: {
customSuccessObject: (req, res, loggableObject) => {
return {
...loggableObject,
buildNumber: configService.get('buildInfo.buildNumber'),
};
},
customErrorObject: (req, res, loggableObject) => {
return {
...loggableObject,
buildNumber: configService.get('buildInfo.buildNumber'),
};
},
},
}),
}),
CacheModule.register({ isGlobal: true }),
CommonModule,
ControllersModule,
HelpersModule,
ExternalApiModule,
],
controllers: [AppController],
providers: [AppService],
Expand Down
2 changes: 1 addition & 1 deletion src/common/common.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { UtilitiesModule } from '../helpers/utilities/utilities.module';
import { UtilitiesService } from '../helpers/utilities/utilities.service';
import { AuthService } from './guards/auth/auth.service';
import { AuthModule } from './guards/auth/auth.module';
import { TokenRefresherModule } from '../helpers/token-refresher/token-refresher.module';
import { TokenRefresherModule } from '../external-api/token-refresher/token-refresher.module';

@Module({
providers: [UtilitiesService, AuthService, UtilitiesService, ConfigService],
Expand Down
3 changes: 2 additions & 1 deletion src/common/constants/parameter-constants.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
const VIEW_MODE = 'Catalog';
const CHILD_LINKS = 'None';
const CONTENT_TYPE = 'application/json';
const PAGINATION = 'N';

const idRegex = /[0-9\-A-Za-z]+/;

export { VIEW_MODE, CHILD_LINKS, CONTENT_TYPE, idRegex };
export { VIEW_MODE, CHILD_LINKS, CONTENT_TYPE, PAGINATION, idRegex };
9 changes: 9 additions & 0 deletions src/common/constants/upstream-constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const baseUrlEnvVarName = 'UPSTREAM_BASE_URL';
const supportNetworkEndpointEnvVarName = 'SUPPORT_NETWORK_ENDPOINT';
const inPersonVisitsEndpointEnvVarName = 'IN_PERSON_VISITS_ENDPOINT';

export {
baseUrlEnvVarName,
supportNetworkEndpointEnvVarName,
inPersonVisitsEndpointEnvVarName,
};
2 changes: 1 addition & 1 deletion src/common/guards/auth/auth.guard.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { UtilitiesService } from '../../../helpers/utilities/utilities.service';
import { AuthGuard } from './auth.guard';
import { AuthService } from './auth.service';
import { getMockReq } from '@jest-mock/express';
import { TokenRefresherService } from '../../../helpers/token-refresher/token-refresher.service';
import { TokenRefresherService } from '../../../external-api/token-refresher/token-refresher.service';

describe('AuthGuard', () => {
let service: AuthService;
Expand Down
4 changes: 2 additions & 2 deletions src/common/guards/auth/auth.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { HttpModule } from '@nestjs/axios';
import { AuthService } from './auth.service';
import { UtilitiesModule } from '../../../helpers/utilities/utilities.module';
import { UtilitiesService } from '../../../helpers/utilities/utilities.service';
import { TokenRefresherService } from '../../../helpers/token-refresher/token-refresher.service';
import { TokenRefresherModule } from '../../../helpers/token-refresher/token-refresher.module';
import { TokenRefresherService } from '../../../external-api/token-refresher/token-refresher.service';
import { TokenRefresherModule } from '../../../external-api/token-refresher/token-refresher.module';

@Module({
providers: [
Expand Down
2 changes: 1 addition & 1 deletion src/common/guards/auth/auth.service.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import { AuthService } from './auth.service';
import { RecordType } from '../../../common/constants/enumerations';
import { EnumTypeError } from '../../../common/errors/errors';
import { UtilitiesService } from '../../../helpers/utilities/utilities.service';
import { TokenRefresherService } from '../../../helpers/token-refresher/token-refresher.service';
import { TokenRefresherService } from '../../../external-api/token-refresher/token-refresher.service';

describe('AuthService', () => {
let service: AuthService;
Expand Down
18 changes: 12 additions & 6 deletions src/common/guards/auth/auth.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,14 @@ import {
} from '../../../common/constants/parameter-constants';
import { firstValueFrom } from 'rxjs';
import { AxiosError } from 'axios';
import { TokenRefresherService } from '../../../helpers/token-refresher/token-refresher.service';
import { TokenRefresherService } from '../../../external-api/token-refresher/token-refresher.service';
import { baseUrlEnvVarName } from '../../../common/constants/upstream-constants';

@Injectable()
export class AuthService {
cacheTime: number;
baseUrl: string;
buildNumber: string;
private readonly logger = new Logger(AuthService.name);

constructor(
Expand All @@ -30,7 +32,8 @@ export class AuthService {
private readonly tokenRefresherService: TokenRefresherService,
) {
this.cacheTime = this.configService.get<number>('recordCache.cacheTtlMs');
this.baseUrl = this.configService.get<string>('UPSTREAM_BASE_URL');
this.baseUrl = this.configService.get<string>(baseUrlEnvVarName);
this.buildNumber = this.configService.get<string>('buildInfo.buildNumber');
}

async getRecordAndValidate(req: Request): Promise<boolean> {
Expand All @@ -45,8 +48,6 @@ export class AuthService {
const key = `${id}|${recordType}`;
let upstreamResult: string | null | undefined =
await this.cacheManager.get(key);
// TODO: Remove this console log once guard is verified working
this.logger.log(`Cache result: ${upstreamResult}`);

if (upstreamResult === undefined) {
upstreamResult = await this.getAssignedIdirUpstream(id, recordType);
Expand Down Expand Up @@ -114,9 +115,14 @@ export class AuthService {
return idir;
} catch (error) {
if (error instanceof AxiosError) {
this.logger.error(error.message, error.stack, error.cause);
this.logger.error({
msg: error.message,
stack: error.stack,
cause: error.cause,
buildNumber: this.buildNumber,
});
} else {
this.logger.error(error);
this.logger.error({ error, buildNumber: this.buildNumber });
}
}
return null;
Expand Down
14 changes: 14 additions & 0 deletions src/configuration/configuration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,5 +27,19 @@ export default () => ({
clientId: process.env.CLIENT_ID,
clientSecret: process.env.CLIENT_SECRET,
},
workspaces: {
supportNetwork: undefined,
inPersonVisits: undefined,
},
sinceFieldName: {
supportNetwork: 'Updated',
inPersonVisits: undefined,
},
skipAuthGuard: process.env.SKIP_AUTH_GUARD === 'true',
buildInfo: {
buildNumber:
process.env.VPI_APP_LABEL === undefined
? 'localBuild'
: process.env.VPI_APP_LABEL,
},
});
37 changes: 36 additions & 1 deletion src/controllers/cases/cases.controller.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,15 @@ import {
import { SinceQueryParams } from '../../dto/since-query-params.dto';
import { IdPathParams } from '../../dto/id-path-params.dto';
import { AuthService } from '../../common/guards/auth/auth.service';
import { TokenRefresherService } from '../../helpers/token-refresher/token-refresher.service';
import { TokenRefresherService } from '../../external-api/token-refresher/token-refresher.service';
import { SupportNetworkService } from '../../helpers/support-network/support-network.service';
import { UtilitiesService } from '../../helpers/utilities/utilities.service';
import { InPersonVisitsService } from '../../helpers/in-person-visits/in-person-visits.service';
import { RequestPreparerService } from '../../external-api/request-preparer/request-preparer.service';
import {
InPersonVisitsEntity,
InPersonVisitsSingleResponseCaseExample,
} from '../../entities/in-person-visits.entity';

describe('CasesController', () => {
let controller: CasesController;
Expand All @@ -27,6 +33,8 @@ describe('CasesController', () => {
AuthService,
SupportNetworkService,
TokenRefresherService,
InPersonVisitsService,
RequestPreparerService,
{ provide: CACHE_MANAGER, useValue: {} },
ConfigService,
UtilitiesService,
Expand Down Expand Up @@ -70,4 +78,31 @@ describe('CasesController', () => {
},
);
});

describe('getSingleCaseInPersonVisitRecord tests', () => {
it.each([
[
InPersonVisitsSingleResponseCaseExample,
{ id: 'test' } as IdPathParams,
{ since: '2020-02-02' } as SinceQueryParams,
],
])(
'should return single values given good input',
async (data, idPathParams, sinceQueryParams) => {
const casesServiceSpy = jest
.spyOn(casesService, 'getSingleCaseInPersonVisitRecord')
.mockReturnValueOnce(Promise.resolve(new InPersonVisitsEntity(data)));

const result = await controller.getSingleCaseInPersonVisitRecord(
idPathParams,
sinceQueryParams,
);
expect(casesServiceSpy).toHaveBeenCalledWith(
idPathParams,
sinceQueryParams,
);
expect(result).toEqual(new InPersonVisitsEntity(data));
},
);
});
});
56 changes: 56 additions & 0 deletions src/controllers/cases/cases.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,12 @@ import { ApiNotFoundEntity } from '../../entities/api-not-found.entity';
import { CONTENT_TYPE } from '../../common/constants/parameter-constants';
import { ApiInternalServerErrorEntity } from '../../entities/api-internal-server-error.entity';
import { AuthGuard } from '../../common/guards/auth/auth.guard';
import {
InPersonVisitsEntity,
InPersonVisitsListResponseCaseExample,
InPersonVisitsSingleResponseCaseExample,
NestedInPersonVisitsEntity,
} from '../../entities/in-person-visits.entity';

@Controller('case')
@UseGuards(AuthGuard)
Expand Down Expand Up @@ -90,4 +96,54 @@ export class CasesController {
since,
);
}

@UseInterceptors(ClassSerializerInterceptor)
@Get(':id/visits')
@ApiOperation({
description:
'Find all In Person Child / Youth Visits related to a given Case entity by Case id.',
})
@ApiQuery({ name: 'since', required: false })
@ApiExtraModels(InPersonVisitsEntity, NestedInPersonVisitsEntity)
@ApiOkResponse({
content: {
[CONTENT_TYPE]: {
schema: {
oneOf: [
{ $ref: getSchemaPath(InPersonVisitsEntity) },
{ $ref: getSchemaPath(NestedInPersonVisitsEntity) },
],
},
examples: {
InPersonVisitsSingleResponse: {
value: InPersonVisitsSingleResponseCaseExample,
},
InPersonVisitsListResponse: {
value: InPersonVisitsListResponseCaseExample,
},
},
},
},
})
async getSingleCaseInPersonVisitRecord(
@Param(
new ValidationPipe({
transform: true,
transformOptions: { enableImplicitConversion: true },
forbidNonWhitelisted: true,
}),
)
id: IdPathParams,
@Query(
new ValidationPipe({
transform: true,
transformOptions: { enableImplicitConversion: true },
forbidNonWhitelisted: true,
skipMissingProperties: true,
}),
)
since?: SinceQueryParams,
): Promise<InPersonVisitsEntity | NestedInPersonVisitsEntity> {
return await this.casesService.getSingleCaseInPersonVisitRecord(id, since);
}
}
12 changes: 10 additions & 2 deletions src/controllers/cases/cases.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,18 @@ import { AuthService } from '../../common/guards/auth/auth.service';
import { ConfigService } from '@nestjs/config';
import { UtilitiesService } from '../../helpers/utilities/utilities.service';
import { HttpModule } from '@nestjs/axios';
import { TokenRefresherService } from '../../external-api/token-refresher/token-refresher.service';
import { ExternalApiModule } from '../../external-api/external-api.module';

@Module({
providers: [CasesService, AuthService, ConfigService, UtilitiesService],
providers: [
CasesService,
AuthService,
ConfigService,
UtilitiesService,
TokenRefresherService,
],
controllers: [CasesController],
imports: [HelpersModule, AuthModule, HttpModule],
imports: [HelpersModule, AuthModule, HttpModule, ExternalApiModule],
})
export class CasesModule {}
Loading

0 comments on commit 6486d26

Please sign in to comment.