feat(attachments): depolama soyutlaması, içerikten tür tespiti, sharp/HEIC dönüşümü ve yetkili dosya servisi
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,37 @@
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
||||
import path from "node:path";
|
||||
import { isValidStorageKey, type StorageDriver } from "./types";
|
||||
|
||||
/** Yerel disk sürücüsü. Yazma atomiktir (geçici dosya + rename); yarım dosya servis edilmez. */
|
||||
export function createLocalDriver(baseDir: string): StorageDriver {
|
||||
const root = path.resolve(baseDir);
|
||||
|
||||
function resolve(key: string): string {
|
||||
if (!isValidStorageKey(key)) throw new Error(`Geçersiz depolama anahtarı: ${key}`);
|
||||
const full = path.resolve(root, key);
|
||||
if (!full.startsWith(root + path.sep)) throw new Error("Depolama kökü dışına çıkılamaz.");
|
||||
return full;
|
||||
}
|
||||
|
||||
return {
|
||||
async put(key, data) {
|
||||
const full = resolve(key);
|
||||
await mkdir(path.dirname(full), { recursive: true, mode: 0o750 });
|
||||
const tmp = `${full}.${randomBytes(6).toString("hex")}.tmp`;
|
||||
await writeFile(tmp, data, { mode: 0o640 });
|
||||
await rename(tmp, full);
|
||||
},
|
||||
async get(key) {
|
||||
try {
|
||||
return await readFile(resolve(key));
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === "ENOENT") return null;
|
||||
throw error;
|
||||
}
|
||||
},
|
||||
async delete(key) {
|
||||
await rm(resolve(key), { force: true });
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user