feat(attachments): minor fix and try catch for attachments controller
This commit is contained in:
parent
e62b4cff1c
commit
30f7179fe6
|
|
@ -2,17 +2,15 @@ import { FileInterceptor } from "@nestjs/platform-express";
|
|||
import { DiskStorageService } from "../services/disk-storage.service";
|
||||
import {
|
||||
Controller,NotFoundException, UseInterceptors, Post, Get, Param, Res,
|
||||
UploadedFile, BadRequestException, UnsupportedMediaTypeException, Body,
|
||||
Delete, NotImplementedException
|
||||
UploadedFile, BadRequestException, UnsupportedMediaTypeException, Body, Delete
|
||||
} from "@nestjs/common";
|
||||
import { maxUploadBytes, allowedMimes } from "../config/upload.config";
|
||||
import { memoryStorage } from 'multer';
|
||||
import { fileTypeFromBuffer, fileTypeFromFile } from "file-type";
|
||||
import { fileTypeFromBuffer } from "file-type";
|
||||
import { Readable } from "node:stream";
|
||||
import { PrismaService } from "src/prisma/prisma.service";
|
||||
import { UploadMetaAttachmentsDto } from "../dtos/upload-meta-attachments.dto";
|
||||
import { resolveAttachmentsRoot } from "src/config/attachment.config";
|
||||
import { casPathFor } from "../utils/cas.util";
|
||||
import * as path from 'node:path';
|
||||
import { promises as fsp } from 'node:fs';
|
||||
import { createReadStream } from "node:fs";
|
||||
|
|
@ -27,23 +25,32 @@ export class AttachmentsController {
|
|||
|
||||
@Get(':id')
|
||||
async getById(@Param('id') id: string, @Res() res: Response) {
|
||||
const att = await this.prisma.attachments.findUnique({
|
||||
where: { id: Number(id) },
|
||||
include: { blob: true },
|
||||
});
|
||||
if (!att) throw new NotFoundException();
|
||||
const num_id = Number(id);
|
||||
if(!Number.isFinite(num_id)) throw new NotFoundException('Invalid id');
|
||||
|
||||
const abs = path.join(resolveAttachmentsRoot(), att.blob.storage_path);
|
||||
const stat = await fsp.stat(abs);
|
||||
const att = await this.prisma.attachments.findUnique({
|
||||
where: { id: num_id },
|
||||
include: { blob: true },
|
||||
});
|
||||
if (!att) throw new NotFoundException();
|
||||
|
||||
res.setHeader('Content-Type', att.blob.mime);
|
||||
res.setHeader('Content-Length', String(stat.size));
|
||||
res.setHeader('ETag', `"sha256-${att.blob.sha256}"`);
|
||||
res.setHeader('Last-Modified', stat.mtime.toUTCString());
|
||||
res.setHeader('Cache-Control', 'private, max-age=31536000, immutable');
|
||||
res.setHeader('X-Content-Type-Options', 'nosniff');
|
||||
|
||||
createReadStream(abs).pipe(res);
|
||||
const abs = path.join(resolveAttachmentsRoot(), att.blob.storage_path);
|
||||
let stat;
|
||||
try {
|
||||
stat = await fsp.stat(abs);
|
||||
}catch {
|
||||
throw new NotFoundException('File not found');
|
||||
}
|
||||
|
||||
res.set('Content-Type', att.blob.mime);
|
||||
res.set('Content-Length', String(stat.size));
|
||||
res.set('ETag', `"sha256-${att.blob.sha256}"`);
|
||||
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);
|
||||
}
|
||||
|
||||
// DEV version, uncomment once connected to DB and distant server
|
||||
|
|
@ -60,10 +67,10 @@ export class AttachmentsController {
|
|||
});
|
||||
|
||||
// decrement refcount
|
||||
const dec = await tx.$executeRawUnsafe(
|
||||
const dec = await tx.$executeRaw
|
||||
`UPDATE "Blobs" SET refcount = refcount - 1
|
||||
WHERE sha256 = $1 AND refcount > 0`, att.sha256,
|
||||
);
|
||||
WHERE sha256 = ${att.sha256} AND refcount > 0`
|
||||
;
|
||||
|
||||
return { ok: true, decremented: dec > 0 };
|
||||
});
|
||||
|
|
@ -83,16 +90,16 @@ export class AttachmentsController {
|
|||
|
||||
//magic detection using binary signature
|
||||
const kind = await fileTypeFromBuffer(file.buffer).catch(() => null);
|
||||
const detectedMime = kind?.mime || file.mimetype || 'application/octet-stream';
|
||||
const detected_mime = kind?.mime || file.mimetype || 'application/octet-stream';
|
||||
|
||||
//strict whitelist
|
||||
if(!allowedMimes().includes(detectedMime)) {
|
||||
throw new UnsupportedMediaTypeException(`This type is not supported: ${detectedMime}`);
|
||||
if(!allowedMimes().includes(detected_mime)) {
|
||||
throw new UnsupportedMediaTypeException(`This type is not supported: ${detected_mime}`);
|
||||
}
|
||||
|
||||
//Saving FS (hash + CAS + unDupes)
|
||||
const stream = Readable.from(file.buffer);
|
||||
const { sha256, storagePath, size } = await this.disk.saveStreamAndHash(stream);
|
||||
const { sha256, storage_path, size } = await this.disk.saveStreamAndHash(stream);
|
||||
|
||||
const now = new Date();
|
||||
const attachment = await this.prisma.$transaction(async (tx) => {
|
||||
|
|
@ -101,15 +108,15 @@ export class AttachmentsController {
|
|||
where: { sha256 },
|
||||
create: {
|
||||
sha256,
|
||||
storage_path: storagePath,
|
||||
storage_path: storage_path,
|
||||
size,
|
||||
mime: detectedMime,
|
||||
mime: detected_mime,
|
||||
refcount: 1,
|
||||
created_at: now,
|
||||
},
|
||||
update: { //only increment, does not change the storage path
|
||||
refcount: { increment: 1 },
|
||||
mime: detectedMime, //update mime and size to keep last image
|
||||
mime: detected_mime, //update mime and size to keep last image
|
||||
size,
|
||||
},
|
||||
});
|
||||
|
|
@ -133,9 +140,9 @@ export class AttachmentsController {
|
|||
ok: true,
|
||||
id: attachment.id,
|
||||
sha256,
|
||||
storagePath: storagePath,
|
||||
storage_path: storage_path,
|
||||
size,
|
||||
mime: detectedMime,
|
||||
mime: detected_mime,
|
||||
original_name: file.originalname,
|
||||
owner_type: attachment.owner_type,
|
||||
owner_id: attachment.owner_id,
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { join, dirname } from 'node:path';
|
|||
import { pipeline } from 'node:stream/promises';
|
||||
import { ATT_TMP_DIR, resolveAttachmentsRoot } from 'src/config/attachment.config';
|
||||
|
||||
export type SaveResult = { sha256:string, storagePath:string, size:number};
|
||||
export type SaveResult = { sha256:string, storage_path:string, size:number};
|
||||
|
||||
export class DiskStorageService {
|
||||
private root = resolveAttachmentsRoot();
|
||||
|
|
@ -64,6 +64,6 @@ export class DiskStorageService {
|
|||
}
|
||||
|
||||
const size = statSync(finalAbs).size;
|
||||
return { sha256: sha, storagePath: rel, size };
|
||||
return { sha256: sha, storage_path: rel, size };
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user