Skip to content

Commit

Permalink
feat(external): add ResultExternalPage and related routing
Browse files Browse the repository at this point in the history
  • Loading branch information
wermarter committed Dec 25, 2024
1 parent 7d6dcea commit f91e841
Show file tree
Hide file tree
Showing 49 changed files with 949 additions and 199 deletions.
4 changes: 3 additions & 1 deletion apps/hcdc-access-service/.env.local
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,6 @@ BROWSER_SERVICE_URL=gateway-internal.envoy-gateway-system.svc.cluster.local:9000
REDIS_MASTER_GROUP_NAME=mymaster
REDIS_SENTINEL_HOST=redis.infra.svc.cluster.local
REDIS_SENTINEL_PORT=26379
REDIS_REPLICAS_COUNT=2
REDIS_REPLICAS_COUNT=2

REMINDER_URL_PREFIX='https://lab.hcdc.vn/external/sample-result?jwt='
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<!DOCTYPE html>
<html lang="vi">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<script src="https://cdn.tailwindcss.com"></script>
<style>
* {
font-family: 'Times New Roman', monospace;
}
html {
font-size: 15px;
}
@page {
margin-top: 5mm;
margin-bottom: 5mm;
margin-left: 18mm;
margin-right: 10mm;
}
p {
page-break-inside: avoid;
}
.print-footer {
position: fixed;
bottom: 0;
font-size: 10px;
}
</style>
</head>
<body>
<!-- <div class="print-footer">
BM.TTXN.04/3
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Phiên bản: 1.0
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
Ngày áp dụng: 18/09/2019
</div> -->
<div class="flex flex-row justify-between">
<div class="flex flex-row">
<img
src=<%= `https://raw.githubusercontent.com/wermarter/diut/refs/heads/main/apps/hcdc-access-service/assets/logo.png` %>
style="width: 90px; object-fit: contain"
/>
<div class="text-center ml-1">
<p>SỞ Y TẾ TP.HỒ CHÍ MINH</p>
<p class="font-bold">TRUNG TÂM KIỂM SOÁT BỆNH TẬT TP.HCM</p>
<p><%= meta.branch.address %></p>
<p>Website: www.hcdc.gov.vn</p>
</div>
</div>
<div class="text-center">
<p class="text-xl font-bold my-1">PHIẾU HẸN KẾT QUẢ XÉT NGHIỆM</p>
<div class="ml-10 text-left">
<p class="text-base">
Ngày lấy mẫu:
<span><%= data.sample.sampledAt.toLocaleDateString('vi') %></span>
</p>
<p>Nơi chỉ định: Phòng khám</p>
<p>Nơi lấy mẫu: Phòng xét nghiệm</p>
</div>
</div>
</div>
<div class="m-4">
<div class="flex flex-row justify-between">
<div class="flex flex-col">
<p class="text-lg">
Họ và tên:
<span class="font-bold"
><%= data.sample.patient.name.toLocaleUpperCase() %></span
>
</p>
<p class="text-lg">
ID XN: <span class="font-bold"><%= data.sample.sampleId %></span>
</p>
</div>
<div class="flex flex-col">
<p class="text-lg">Năm sinh: <span><%= data.sample.patient.birthYear %></span></p>
<p class="text-lg">Loại bệnh phẩm: Máu</p>
</div>
<div class="flex flex-col">
<p class="text-lg">
Giới tính:
<span><%= data.sample.patient.gender === "Male" ? "Nam" : "Nữ" %></span>
</p>
<p class="text-lg">
CCCD (nếu có):
<span><%= data.sample.patient.SSN %></span>
</p>
</div>
</div>
<p class="text-lg">Địa chỉ: <span><%= data.sample.patient.address %></span></p>

</div>
<div class="flex flex-row justify-between mt-6">
<div
class="flex flex-col text-center mr-8"
style="page-break-inside: avoid"
>
<p>
Ngày <%= new Date().getDate() %> tháng <%= new Date().getMonth() + 1
%> năm <%= new Date().getFullYear() %>
</p>
<p class="font-bold">Nhân viên xét nghiệm</p>
<p style="margin-top: 20mm" class="font-bold"><%= meta.authorName %></p>
</div>
</div>
<img
src=<%= `https://quickchart.io/qr?size=300&text=${data.url}` %>
/>
</body>
</html>
2 changes: 2 additions & 0 deletions apps/hcdc-access-service/src/app/sample/module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { SampleGeneratePrintUrlUseCase } from './use-case/generate-print-url'
import { SampleInitResultUseCase } from './use-case/init-result'
import { SampleLockUseCase } from './use-case/lock'
import { SamplePrintUseCase } from './use-case/print'
import { SamplePrintReminderUseCase } from './use-case/print-reminder'
import { SampleSearchUseCase } from './use-case/search'
import { SampleUnlockUseCase } from './use-case/unlock'
import { SampleUpdateInfoUseCase } from './use-case/update-info'
Expand All @@ -36,5 +37,6 @@ export const sampleMetadata: ModuleMetadata = {
SampleUploadResultImageUseCase,
SampleDownloadResultImageUseCase,
SampleGeneratePrintUrlUseCase,
SamplePrintReminderUseCase,
],
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,11 @@ import { AuthSubject, ExternalRoutePath, SampleAction } from '@diut/hcdc'
import { Inject, Injectable } from '@nestjs/common'
import { assertPermission } from 'src/app/auth/common'
import { AuthAuthorizeExternalRouteUseCase } from 'src/app/auth/use-case/authorize-external-route'
import { AUTH_CONTEXT_TOKEN, IAuthContext } from 'src/domain'
import { SamplePrintOptions } from '../common'
import {
AUTH_CONTEXT_TOKEN,
ExternalRouteOptions,
IAuthContext,
} from 'src/domain'
import { SampleAssertExistsUseCase } from './assert-exists'

@Injectable()
Expand All @@ -15,7 +18,9 @@ export class SampleGeneratePrintUrlUseCase {
private readonly authContext: IAuthContext,
) {}

async execute(input: { printOptions: SamplePrintOptions[] }) {
async execute(
input: ExternalRouteOptions[ExternalRoutePath.PrintSampleResult],
) {
const { ability } = this.authContext.getDataInternal()
const branchIds = new Set<string>()

Expand Down
134 changes: 134 additions & 0 deletions apps/hcdc-access-service/src/app/sample/use-case/print-reminder.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
import { NodeEnv } from '@diut/common'
import { AuthSubject, ExternalRoutePath, SampleAction } from '@diut/hcdc'
import { PageFormat, PageOrientation } from '@diut/services'
import { Inject, Injectable } from '@nestjs/common'
import { render } from 'ejs'
import { readFile } from 'fs/promises'
import { join } from 'path'
import { of } from 'rxjs'
import { assertPermission } from 'src/app/auth/common'
import { AuthAuthorizeExternalRouteUseCase } from 'src/app/auth/use-case/authorize-external-route'
import { AppConfig, loadAppConfig } from 'src/config'
import {
AUTH_CONTEXT_TOKEN,
BROWSER_SERVICE_TOKEN,
EEntityNotFound,
IAuthContext,
IBrowserService,
IStorageBucket,
IStorageService,
STORAGE_BUCKET_TOKEN,
STORAGE_SERVICE_TOKEN,
StorageBucket,
StorageKeyFactory,
} from 'src/domain'
import { SampleFindOneUseCase } from './find-one'

const TEMPLATE_PATH = 'reminder.ejs'

@Injectable()
export class SamplePrintReminderUseCase {
constructor(
private readonly sampleFindOneUseCase: SampleFindOneUseCase,
private readonly authorizeExternalRouteUseCase: AuthAuthorizeExternalRouteUseCase,
@Inject(AUTH_CONTEXT_TOKEN)
private readonly authContext: IAuthContext,
@Inject(BROWSER_SERVICE_TOKEN)
private readonly browserService: IBrowserService,
@Inject(loadAppConfig.KEY)
private readonly appConfig: AppConfig,
@Inject(STORAGE_SERVICE_TOKEN)
private readonly storageService: IStorageService,
@Inject(STORAGE_BUCKET_TOKEN)
private readonly storageBucket: IStorageBucket,
) {}

async execute(sampleId: string, date: Date) {
const htmlContent = await this.getHtmlContent(sampleId, date)

const { mergedPdf } = await this.browserService.printMultiplePage(
of({
htmlContent,
pageFormat: PageFormat.A5,
pageOrientation: PageOrientation.Landscape,
}),
)

return mergedPdf
}

private async getHtmlContent(sampleId: string, date: Date) {
const { jwt, sample, meta } = await this.getPrintData(sampleId)
const printTemplate = await this.getPrintTemplate()
const url = this.appConfig.REMINDER_URL_PREFIX + jwt

return render(
printTemplate,
{ data: { url, sample, date }, meta },
{ async: true },
)
}

private async getPrintTemplate() {
const isDevelopment = this.appConfig.NODE_ENV === NodeEnv.Development
if (isDevelopment) {
const buffer = await readFile(
join(
__dirname,
'..',
'..',
`print-form/print-template/${TEMPLATE_PATH}`,
),
)

return buffer.toString()
}

const { buffer } = await this.storageService.readToBuffer({
key: StorageKeyFactory[StorageBucket.APP].printFormTemplate({
templatePath: TEMPLATE_PATH,
}),
bucket: this.storageBucket.get(StorageBucket.APP),
})
return buffer.toString()
}

private async getPrintData(sampleId: string) {
const { ability, user } = this.authContext.getDataInternal()

const sample = await this.sampleFindOneUseCase.execute({
filter: { sampleId },
populates: [
{
path: 'patient',
},
{
path: 'branch',
},
],
})
if (!sample) {
throw new EEntityNotFound(`Sample id=${sampleId}`)
}

assertPermission(
ability,
AuthSubject.Sample,
SampleAction.ReadResult,
sample,
)

const { jwt } = await this.authorizeExternalRouteUseCase.execute(
this.constructor.name,
ExternalRoutePath.GetSampleResult,
{ sampleId: sample._id },
[sample.branchId],
)

return {
jwt,
sample,
meta: { branch: sample.branch, authorName: user.name },
}
}
}
5 changes: 5 additions & 0 deletions apps/hcdc-access-service/src/config/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ export class AppConfig {
@IsNotEmpty()
SERVICE_NAME: string

@Expose()
@IsString()
@IsNotEmpty()
REMINDER_URL_PREFIX: string

@Expose()
@IsString()
@IsNotEmpty()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { ApiQuery } from '@nestjs/swagger'

export type ExternalAuthQuery = {
jwt: string
}

export const AuthQuery = () =>
ApiQuery({
name: 'jwt',
required: true,
type: 'string',
})
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
IAuthCacheService,
IAuthContext,
} from 'src/domain'
import { ExternalAuthQuery } from './common'

export class HttpExternalAuthGuard implements CanActivate {
private readonly logger = new Logger(this.constructor.name)
Expand All @@ -30,7 +31,7 @@ export class HttpExternalAuthGuard implements CanActivate {

async canActivate(context: ExecutionContext) {
const request = context.switchToHttp().getRequest<Request>()
const jwt = request.query.jwt as string
const jwt = (request.query as ExternalAuthQuery).jwt
if (!jwt) return false

const payload = await this.verifyToken(jwt)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
export * from './service'
export * from './common'
export * from './guard'
export * from './service'
Loading

0 comments on commit f91e841

Please sign in to comment.