feat(attachments): added prisma models for blobs and attachments and basic setup for stream and hash
This commit is contained in:
parent
9d3967c5c7
commit
014f58f78a
|
|
@ -266,6 +266,47 @@ model OAuthSessions {
|
|||
@@map("oauth_sessions")
|
||||
}
|
||||
|
||||
model Blobs {
|
||||
sha256 String @id @db.Char(64)
|
||||
size Int
|
||||
mime String
|
||||
storage_path String
|
||||
refcount Int @default(0)
|
||||
created_at DateTime @default(now())
|
||||
|
||||
attachments Attachments[]
|
||||
|
||||
@@map("blobs")
|
||||
}
|
||||
|
||||
model Attachments {
|
||||
id Int @id @default(autoincrement())
|
||||
sha256 String @db.Char(64)
|
||||
blob Blobs @relation(fields: [sha256], references: [sha256], onUpdate: Cascade)
|
||||
owner_type String //EXPENSES ou éventuellement autre chose comme scan ONU ou photos d'employés, etc
|
||||
owner_id String //expense_id, employee_id, etc
|
||||
orignal_name String
|
||||
status AttachmentStatus @default(ACTIVE)
|
||||
relation_policy RetentionPolicy
|
||||
created_by String
|
||||
created_at DateTime @default(now())
|
||||
|
||||
@@index([owner_type, owner_id, created_at])
|
||||
@@index([sha256])
|
||||
@@map("attachments")
|
||||
}
|
||||
|
||||
enum AttachmentStatus {
|
||||
ACTIVE
|
||||
DELETED
|
||||
}
|
||||
|
||||
enum RetentionPolicy {
|
||||
EXPENSE_7Y
|
||||
TICKET_2Y
|
||||
PROFILE_KEEP_LAST3
|
||||
}
|
||||
|
||||
enum Roles {
|
||||
ADMIN
|
||||
SUPERVISOR
|
||||
|
|
|
|||
69
src/attachments/disk-storage.service.ts
Normal file
69
src/attachments/disk-storage.service.ts
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
import { createHash } from 'node:crypto';
|
||||
import { promises as fsp } from 'node:fs';
|
||||
import { createWriteStream, statSync, existsSync } from 'node:fs';
|
||||
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 class DiskStorageService {
|
||||
private root = resolveAttachmentsRoot();
|
||||
|
||||
private casPath(hash: string) {
|
||||
const a = hash.slice(0,2), b = hash.slice(2,4);
|
||||
return `sha256/${a}/${b}/${hash}`; //relatif pour stockage dans la DB
|
||||
}
|
||||
|
||||
//chemin absolue du storage
|
||||
getAbsolutePath(storagePathRel: string) {
|
||||
return join(this.root, storagePathRel);
|
||||
}
|
||||
|
||||
async exists(storagePathRel: string) {
|
||||
try {
|
||||
statSync(this.getAbsolutePath(storagePathRel));
|
||||
return true;
|
||||
}catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//adds file and hash it
|
||||
async saveStreamAndHash(input: NodeJS.ReadableStream): Promise<SaveResult> {
|
||||
// 1- writing in ROOT:/_tmp while streaming and hashing
|
||||
const tmpDir = ATT_TMP_DIR();
|
||||
await fsp.mkdir(tmpDir, { recursive: true });
|
||||
const tmpPath = join(tmpDir, `up_${Date.now()}_${Math.random().toString(36).slice(2)}`);
|
||||
|
||||
const hash = createHash('sha256');
|
||||
const tmpOut = createWriteStream(tmpPath);
|
||||
input.on('date', (chunk) => hash.update(chunk));
|
||||
await pipeline(input, tmpOut); //await end of writing stream
|
||||
|
||||
const sha = hash.digest('hex');
|
||||
const rel = this.casPath(sha);
|
||||
const finalAbs = this.getAbsolutePath(rel);
|
||||
|
||||
// 2- is there is no destination => move (atomic renaming on the same volume)
|
||||
if(!existsSync(finalAbs)) {
|
||||
await fsp.mkdir(dirname(finalAbs), { recursive:true });
|
||||
try {
|
||||
await fsp.rename(tmpPath, finalAbs);
|
||||
}catch (e) {
|
||||
//if someone is faster and used the same hash
|
||||
if(existsSync(finalAbs)) {
|
||||
await fsp.rm(tmpPath, { force:true });
|
||||
} else {
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//remove duplicata if already exists
|
||||
await fsp.rm(tmpPath, { force:true });
|
||||
}
|
||||
|
||||
const size = statSync(finalAbs).size;
|
||||
return { sha256: sha, storagePath: rel, size };
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user