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> { 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> { 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> { 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 }; } }