Skip to content

Commit 3ed77ab

Browse files
authored
Merge pull request #2338 from bcgov/feature/2309-QA-1
Allow public docs to load when not authenticated
2 parents 59bc5f1 + 37502b6 commit 3ed77ab

File tree

8 files changed

+109
-22
lines changed

8 files changed

+109
-22
lines changed

portal-frontend/src/app/app-routing.module.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@ import { DocumentFileLoader } from './shared/document-file-loader/document-file-
1010
const routes: Routes = [
1111
{
1212
path: 'document/:uuid',
13-
canActivate: [AuthGuard],
1413
component: DocumentFileLoader,
1514
},
1615
{

portal-frontend/src/app/services/document/document.service.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { UploadDocumentUrlDto } from './document.dto';
1212
})
1313
export class DocumentService {
1414
private serviceUrl = `${environment.apiUrl}/document`;
15+
private publicServiceUrl = `${environment.authUrl}/public/document`;
1516
private httpClientNoAuth: HttpClient | null = null;
1617

1718
constructor(
@@ -78,9 +79,15 @@ export class DocumentService {
7879
}
7980
}
8081

81-
async getDownloadUrlAndFileName(uuid: string, isInline = true): Promise<{ url: string; fileName: string }> {
82+
async getDownloadUrlAndFileName(
83+
uuid: string,
84+
isInline: boolean,
85+
isAuthenticated: boolean,
86+
): Promise<{ url: string; fileName: string }> {
8287
const url =
83-
`${this.serviceUrl}/getDownloadUrlAndFileName/${uuid}` + (isInline ? `?isInline=${isInline.toString()}` : '');
88+
(isAuthenticated ? this.serviceUrl : this.publicServiceUrl) +
89+
`/download-url-and-filename/${uuid}` +
90+
(isInline ? `?isInline=${isInline.toString()}` : '');
8491

8592
return await firstValueFrom(this.httpClient.get<{ url: string; fileName: string }>(url));
8693
}

portal-frontend/src/app/shared/document-file-loader/document-file-loader.component.ts

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,44 @@
1-
import { Component } from '@angular/core';
1+
import { Component, OnDestroy } from '@angular/core';
22
import { ActivatedRoute } from '@angular/router';
33
import { DocumentService } from '../../services/document/document.service';
4+
import { AuthenticationService } from '../../services/authentication/authentication.service';
5+
import { Subject, takeUntil } from 'rxjs';
46

57
@Component({
68
selector: 'app-temp',
79
templateUrl: './document-file-loader.component.html',
810
styleUrl: './document-file-loader.component.scss',
911
})
10-
export class DocumentFileLoader {
12+
export class DocumentFileLoader implements OnDestroy {
13+
$destroy = new Subject<void>();
14+
isAuthenticated = false;
1115
uuid: string | null = null;
1216

1317
constructor(
1418
private route: ActivatedRoute,
1519
private documentService: DocumentService,
16-
) {
20+
private authenticationService: AuthenticationService,
21+
) {}
22+
23+
ngAfterViewInit() {
1724
this.uuid = this.route.snapshot.paramMap.get('uuid');
25+
26+
this.authenticationService.$currentProfile.pipe(takeUntil(this.$destroy)).subscribe((user) => {
27+
const isAuthenticated = !!user;
28+
29+
if (this.uuid !== null) {
30+
this.open(this.uuid, isAuthenticated);
31+
}
32+
});
1833
}
1934

20-
ngAfterViewInit() {
21-
if (this.uuid !== null) {
22-
this.open(this.uuid);
23-
}
35+
ngOnDestroy(): void {
36+
this.$destroy.next();
37+
this.$destroy.complete();
2438
}
2539

26-
async open(uuid: string) {
27-
const { url, fileName } = await this.documentService.getDownloadUrlAndFileName(uuid);
28-
console.log(url, fileName);
40+
async open(uuid: string, isAuthenticated: boolean) {
41+
const { url, fileName } = await this.documentService.getDownloadUrlAndFileName(uuid, true, isAuthenticated);
2942
const object = window.document.createElement('object');
3043

3144
object.data = url;

services/apps/alcs/src/document/document.service.spec.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { Test, TestingModule } from '@nestjs/testing';
1010
import { getRepositoryToken } from '@nestjs/typeorm';
1111
import { mockClient } from 'aws-sdk-client-mock';
1212
import * as config from 'config';
13-
import { Repository } from 'typeorm';
13+
import { DataSource, Repository } from 'typeorm';
1414
import { ClamAVService } from '../clamav/clamav.service';
1515
import { User } from '../user/user.entity';
1616
import {
@@ -31,11 +31,13 @@ describe('DocumentService', () => {
3131
const mockS3Client = mockClient(S3Client);
3232
let mockRepository: DeepMocked<Repository<Document>>;
3333
let mockClamAVService: DeepMocked<ClamAVService>;
34+
let mockDataSource: DeepMocked<DataSource>;
3435

3536
beforeEach(async () => {
3637
mockS3Client.reset();
3738
mockRepository = createMock();
3839
mockClamAVService = createMock();
40+
mockDataSource = createMock();
3941

4042
const module: TestingModule = await Test.createTestingModule({
4143
providers: [
@@ -52,6 +54,10 @@ describe('DocumentService', () => {
5254
provide: ClamAVService,
5355
useValue: mockClamAVService,
5456
},
57+
{
58+
provide: DataSource,
59+
useValue: mockDataSource,
60+
},
5561
],
5662
}).compile();
5763

services/apps/alcs/src/document/document.service.ts

Lines changed: 50 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,21 @@
11
import { CONFIG_TOKEN, IConfig } from '@app/common/config/config.module';
2-
import { BaseServiceException, ServiceValidationException } from '@app/common/exceptions/base.exception';
2+
import {
3+
BaseServiceException,
4+
ServiceNotFoundException,
5+
ServiceValidationException,
6+
} from '@app/common/exceptions/base.exception';
37
import { DeleteObjectCommand, GetObjectCommand, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
48
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
59
import { MultipartFile } from '@fastify/multipart';
610
import { Inject, Injectable, Logger } from '@nestjs/common';
711
import { InjectRepository } from '@nestjs/typeorm';
8-
import { Repository } from 'typeorm';
12+
import { DataSource, Repository } from 'typeorm';
913
import { v4 } from 'uuid';
1014
import { ClamAVService } from '../clamav/clamav.service';
1115
import { User } from '../user/user.entity';
12-
import {
13-
CreateDocumentDto,
14-
DOCUMENT_SOURCE,
15-
DOCUMENT_SYSTEM,
16-
} from './document.dto';
16+
import { CreateDocumentDto, DOCUMENT_SOURCE, DOCUMENT_SYSTEM } from './document.dto';
1717
import { Document } from './document.entity';
18+
import { VISIBILITY_FLAG } from '../alcs/application/application-document/application-document.entity';
1819

1920
const DEFAULT_DB_TAGS = ['ORCS Classification: 85100-20'];
2021
const DEFAULT_S3_TAGS = 'ORCS-Classification=85100-20';
@@ -31,6 +32,7 @@ export class DocumentService {
3132
@InjectRepository(Document)
3233
private documentRepository: Repository<Document>,
3334
private clamAvService: ClamAVService,
35+
private dataSource: DataSource,
3436
) {
3537
this.dataStore = new S3Client({
3638
region: 'us-east-1',
@@ -156,6 +158,47 @@ export class DocumentService {
156158
return { url: await this.getDownloadUrl(document, openInline), fileName: document.fileName };
157159
}
158160

161+
async getPublicDownloadUrlAndFileNameByUuid(
162+
uuid: string,
163+
openInline = false,
164+
): Promise<{ url: string; fileName: string }> {
165+
const document = await this.getDocument(uuid);
166+
167+
const fileDocuments = await this.dataSource.query(
168+
`
169+
select
170+
ad.visibility_flags
171+
from
172+
alcs.application_document ad
173+
where
174+
ad.document_uuid = $1
175+
union
176+
select
177+
noid.visibility_flags
178+
from
179+
alcs.notice_of_intent_document noid
180+
where
181+
noid.document_uuid = $1
182+
union
183+
select
184+
nd2.visibility_flags
185+
from
186+
alcs.notification_document nd2
187+
where
188+
nd2.document_uuid = $1
189+
`,
190+
[uuid],
191+
);
192+
193+
// Unlikely a document is attached to more than one file document, but if it
194+
// is, as long as any of them have made the doc public, show it
195+
if (!fileDocuments.some((doc) => doc.visibility_flags.includes(VISIBILITY_FLAG.PUBLIC))) {
196+
throw new ServiceNotFoundException('Failed to find document');
197+
}
198+
199+
return { url: await this.getDownloadUrl(document, openInline), fileName: document.fileName };
200+
}
201+
159202
async createDocumentRecord(data: CreateDocumentDto) {
160203
const command = new GetObjectCommand({
161204
Bucket: this.config.get('STORAGE.BUCKET'),

services/apps/alcs/src/portal/public/public.controller.spec.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,17 +6,20 @@ import { PublicApplicationService } from './application/public-application.servi
66
import { PublicNoticeOfIntentService } from './notice-of-intent/public-notice-of-intent.service';
77
import { PublicNotificationService } from './notification/public-notification.service';
88
import { PublicController } from './public.controller';
9+
import { DocumentService } from '../../document/document.service';
910

1011
describe('PublicController', () => {
1112
let controller: PublicController;
1213
let mockAppService: DeepMocked<PublicApplicationService>;
1314
let mockNOIService: DeepMocked<PublicNoticeOfIntentService>;
1415
let mockNotificationService: DeepMocked<PublicNotificationService>;
16+
let mockDocumentService: DeepMocked<DocumentService>;
1517

1618
beforeEach(async () => {
1719
mockAppService = createMock();
1820
mockNOIService = createMock();
1921
mockNotificationService = createMock();
22+
mockDocumentService = createMock();
2023

2124
const module: TestingModule = await Test.createTestingModule({
2225
providers: [
@@ -32,6 +35,10 @@ describe('PublicController', () => {
3235
provide: PublicNotificationService,
3336
useValue: mockNotificationService,
3437
},
38+
{
39+
provide: DocumentService,
40+
useValue: mockDocumentService,
41+
},
3542
{
3643
provide: ClsService,
3744
useValue: {},

services/apps/alcs/src/portal/public/public.controller.ts

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1-
import { Controller, Get, Param } from '@nestjs/common';
1+
import { Controller, Get, Param, Query } from '@nestjs/common';
22
import { Public } from 'nest-keycloak-connect';
33
import { PublicApplicationService } from './application/public-application.service';
44
import { PublicNoticeOfIntentService } from './notice-of-intent/public-notice-of-intent.service';
55
import { PublicNotificationService } from './notification/public-notification.service';
6+
import { DocumentService } from '../../document/document.service';
67

78
@Public()
89
@Controller('/public')
@@ -11,6 +12,7 @@ export class PublicController {
1112
private publicAppService: PublicApplicationService,
1213
private publicNoticeOfIntentService: PublicNoticeOfIntentService,
1314
private publicNotificationService: PublicNotificationService,
15+
private documentService: DocumentService,
1416
) {}
1517

1618
@Get('/application/:fileId')
@@ -75,4 +77,12 @@ export class PublicController {
7577
) {
7678
return await this.publicNotificationService.getInlineUrl(documentUuid);
7779
}
80+
81+
@Get('/document/download-url-and-filename/:uuid')
82+
async getDownloadUrlAndFileName(
83+
@Param('uuid') uuid: string,
84+
@Query('isInline') isInline: boolean = false,
85+
): Promise<{ url: string; fileName: string }> {
86+
return await this.documentService.getPublicDownloadUrlAndFileNameByUuid(uuid, isInline);
87+
}
7888
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import { PublicNotificationService } from './notification/public-notification.se
2020
import { PublicController } from './public.controller';
2121
import { PublicSearchModule } from './search/public-search.module';
2222
import { PublicStatusController } from './status/public-status.controller';
23+
import { DocumentModule } from '../../document/document.module';
2324

2425
@Module({
2526
imports: [
@@ -35,6 +36,7 @@ import { PublicStatusController } from './status/public-status.controller';
3536
NoticeOfIntentDecisionModule,
3637
NotificationModule,
3738
NotificationSubmissionModule,
39+
DocumentModule,
3840
RouterModule.register([{ path: 'public', module: PublicSearchModule }]),
3941
],
4042
controllers: [

0 commit comments

Comments
 (0)