targo-backend/src/time-and-attendance/attachments/services/attachment-get.service.ts

101 lines
3.9 KiB
TypeScript

import { Injectable } from "@nestjs/common";
import { Response } from "express";
import { AdminSearchDto } from "src/time-and-attendance/attachments/dtos/search-filters.dto";
import { PrismaService } from "prisma/postgres/prisma-postgres.service";
import { resolveAttachmentsRoot } from "src/time-and-attendance/attachments/config/attachment.config";
import * as path from 'node:path';
import { promises as fsp } from 'node:fs';
import { createReadStream } from "node:fs";
import { fileTypeFromFile } from "file-type";
import { Result } from "src/common/errors/result-error.factory";
@Injectable()
export class AttachmentGetService {
constructor(
private readonly prisma: PrismaService,
) { }
async getListVariants(id: string): Promise<Result<any, string>> {
const num_id = Number(id);
if (!Number.isFinite(num_id)) return { success: false, error: 'INVALID_ATTACHMENTS' };
const variants = await this.prisma.attachmentVariants.findMany({
where: { attachment_id: num_id },
orderBy: { variant: 'asc' },
select: { variant: true, bytes: true, width: true, height: true, path: true, created_at: true },
});
return { success: true, data: variants };
}
async searchAttachmentWithFilters(dto: AdminSearchDto): Promise<Result<any, string>> {
const where: any = {};
if (dto.owner_type) where.owner_type = dto.owner_type;
if (dto.owner_id) where.owner_id = dto.owner_id;
if (dto.date_from || dto.date_to) {
where.created_at = {};
if (dto.date_from) where.created_at.gte = new Date(dto.date_from + 'T00:00:00Z');
if (dto.date_to) where.created_at.lte = new Date(dto.date_to + 'T23:59:59Z');
}
const page = dto.page ?? 1;
const page_size = dto.page_size ?? 50;
const skip = (page - 1) * page_size;
const take = page_size;
const [items, total] = await this.prisma.$transaction([
this.prisma.attachments.findMany({
where,
orderBy: { created_at: 'desc' },
skip, take,
include: {
blob: {
select: { mime: true, size: true, storage_path: true, sha256: true },
},
},
}),
this.prisma.attachments.count({ where }),
]);
return { success: true, data: { page, page_size: take, total, items } };
}
async findAttachmentById(id: string, variant: string | undefined, res: Response): Promise<Result<boolean, string>> {
const num_id = Number(id);
if (!Number.isFinite(num_id)) return { success: false, error: 'INVALID_ATTACHMENTS' };
const attachment = await this.prisma.attachments.findUnique({
where: { id: num_id },
include: { blob: true },
});
if (!attachment) return { success: false, error: 'ATTACHMENT_NOT_FOUND' };
const relative = variant ? `${attachment.blob.storage_path}.${variant}` : attachment.blob.storage_path;
const abs = path.join(resolveAttachmentsRoot(), relative);
let stat;
try {
stat = await fsp.stat(abs);
} catch {
return { success: false, error: 'INVALID_FILE_PATH' };
}
let mime = attachment.blob.mime;
try {
const kind = await fileTypeFromFile(abs);
if (kind?.mime) mime = kind.mime;
} catch { }
res.set('Content-Type', mime);
res.set('Content-Length', String(stat.size));
res.set('ETag', `"sha256-${attachment.blob.sha256}${variant ? '.' + variant : ''}"`);
res.set('Last-Modified', stat.mtime.toUTCString());
res.set('Cache-Control', 'private, max-age=31536000, immutable');
res.set('X-Content-Type-Options', 'nosniff');
createReadStream(abs).pipe(res);
return { success: true, data: true };
}
}