fix: close final F4 review gaps
This commit is contained in:
@@ -1,10 +1,11 @@
|
||||
import { createHash, randomUUID } from 'node:crypto';
|
||||
import { mkdir, unlink, writeFile } from 'node:fs/promises';
|
||||
import { mkdir, readFile, stat, unlink, writeFile } from 'node:fs/promises';
|
||||
import { isAbsolute, parse, resolve } from 'node:path';
|
||||
import {
|
||||
BadRequestException,
|
||||
ConflictException,
|
||||
Injectable,
|
||||
InternalServerErrorException,
|
||||
NotFoundException,
|
||||
} from '@nestjs/common';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
@@ -58,7 +59,7 @@ export class InspectionReportWorkflowService {
|
||||
config: ConfigService,
|
||||
) {
|
||||
const configured = config.get<string>('INSPECTION_REPORT_UPLOAD_ROOT')
|
||||
?? '/app/storage/inspection-reports';
|
||||
?? '/app/storage/asset-media/inspection-reports';
|
||||
if (!isAbsolute(configured)) throw new Error('INSPECTION_REPORT_UPLOAD_ROOT must be absolute');
|
||||
this.root = resolve(configured);
|
||||
if (this.root === parse(this.root).root) {
|
||||
@@ -97,6 +98,30 @@ export class InspectionReportWorkflowService {
|
||||
UPDATE inspection_reports
|
||||
SET executive_summary=$2,
|
||||
report_description=$3,
|
||||
word_status=CASE
|
||||
WHEN executive_summary IS DISTINCT FROM $2 OR report_description IS DISTINCT FROM $3
|
||||
THEN 'PENDING' ELSE word_status END,
|
||||
word_original_name=CASE
|
||||
WHEN executive_summary IS DISTINCT FROM $2 OR report_description IS DISTINCT FROM $3
|
||||
THEN NULL ELSE word_original_name END,
|
||||
word_stored_name=CASE
|
||||
WHEN executive_summary IS DISTINCT FROM $2 OR report_description IS DISTINCT FROM $3
|
||||
THEN NULL ELSE word_stored_name END,
|
||||
word_mime_type=CASE
|
||||
WHEN executive_summary IS DISTINCT FROM $2 OR report_description IS DISTINCT FROM $3
|
||||
THEN NULL ELSE word_mime_type END,
|
||||
word_size_bytes=CASE
|
||||
WHEN executive_summary IS DISTINCT FROM $2 OR report_description IS DISTINCT FROM $3
|
||||
THEN NULL ELSE word_size_bytes END,
|
||||
word_sha256=CASE
|
||||
WHEN executive_summary IS DISTINCT FROM $2 OR report_description IS DISTINCT FROM $3
|
||||
THEN NULL ELSE word_sha256 END,
|
||||
word_generated_at=CASE
|
||||
WHEN executive_summary IS DISTINCT FROM $2 OR report_description IS DISTINCT FROM $3
|
||||
THEN NULL ELSE word_generated_at END,
|
||||
word_error=CASE
|
||||
WHEN executive_summary IS DISTINCT FROM $2 OR report_description IS DISTINCT FROM $3
|
||||
THEN NULL ELSE word_error END,
|
||||
updated_at=CURRENT_TIMESTAMP
|
||||
WHERE id=$1
|
||||
`, [reportId, executiveSummary, reportDescription]);
|
||||
@@ -195,6 +220,44 @@ export class InspectionReportWorkflowService {
|
||||
}
|
||||
}
|
||||
|
||||
async officialPdfContent(reportId: string): Promise<{ filePath: string; originalName: string; mimeType: string }> {
|
||||
const [row] = await this.dataSource.query(`
|
||||
SELECT
|
||||
gedo_pdf_original_name AS "originalName",
|
||||
gedo_pdf_stored_name AS "storedName",
|
||||
gedo_pdf_mime_type AS "mimeType",
|
||||
gedo_pdf_size_bytes::integer AS "sizeBytes",
|
||||
gedo_pdf_sha256 AS sha256
|
||||
FROM inspection_reports
|
||||
WHERE id=$1
|
||||
`, [reportId]) as Array<{
|
||||
originalName: string | null;
|
||||
storedName: string | null;
|
||||
mimeType: string | null;
|
||||
sizeBytes: number | null;
|
||||
sha256: string | null;
|
||||
}>;
|
||||
if (!row) throw reportNotFound();
|
||||
if (!row.originalName || !row.storedName || !row.sizeBytes || !row.sha256) {
|
||||
throw new NotFoundException({
|
||||
code: 'INSPECTION_REPORT_GEDO_PDF_NOT_AVAILABLE',
|
||||
message: 'El PDF oficial de GEDO todavía no está disponible',
|
||||
});
|
||||
}
|
||||
const filePath = resolve(this.root, row.storedName);
|
||||
if (filePath === this.root || !filePath.startsWith(`${this.root}/`)) throw this.reportStorageError();
|
||||
const fileStat = await stat(filePath).catch(() => null);
|
||||
if (!fileStat?.isFile() || fileStat.size !== row.sizeBytes) throw this.reportStorageError();
|
||||
const buffer = await readFile(filePath);
|
||||
const sha256 = createHash('sha256').update(buffer).digest('hex');
|
||||
if (sha256 !== row.sha256) throw this.reportStorageError();
|
||||
return {
|
||||
filePath,
|
||||
originalName: row.originalName,
|
||||
mimeType: row.mimeType ?? 'application/pdf',
|
||||
};
|
||||
}
|
||||
|
||||
async addFollowUp(
|
||||
reportId: string,
|
||||
dto: CreateInspectionReportFollowUpDto,
|
||||
@@ -291,6 +354,13 @@ export class InspectionReportWorkflowService {
|
||||
`, [reportId]);
|
||||
}
|
||||
|
||||
private reportStorageError(): InternalServerErrorException {
|
||||
return new InternalServerErrorException({
|
||||
code: 'INSPECTION_REPORT_GEDO_PDF_STORAGE_ERROR',
|
||||
message: 'El PDF oficial de GEDO no está disponible o no supera la validación de integridad',
|
||||
});
|
||||
}
|
||||
|
||||
private assertPdf(file: UploadedInspectionReportFile | undefined): void {
|
||||
if (!file?.buffer?.length) {
|
||||
throw new BadRequestException({
|
||||
|
||||
Reference in New Issue
Block a user