feat: add server modules (audit, upload, email, content, tracking)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-26 11:59:00 -05:00
co-authored by Claude Sonnet 4.6
parent 649c324f37
commit 3669c0d4f8
5 changed files with 371 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
import fs from 'fs/promises';
import path from 'path';
import crypto from 'crypto';
const UPLOAD_DIR = process.env.UPLOAD_DIR || './uploads';
const MAX_SIZE = 10 * 1024 * 1024; // 10MB
const ALLOWED_TYPES = ['image/jpeg', 'image/png', 'image/webp', 'image/gif'];
const MAGIC_BYTES: Record<string, number[]> = {
'image/jpeg': [0xff, 0xd8, 0xff],
'image/png': [0x89, 0x50, 0x4e, 0x47],
'image/gif': [0x47, 0x49, 0x46],
'image/webp': [0x52, 0x49, 0x46, 0x46]
};
function validateMagicBytes(buffer: Buffer, mimeType: string): boolean {
const expected = MAGIC_BYTES[mimeType];
if (!expected) return false;
for (let i = 0; i < expected.length; i++) {
if (buffer[i] !== expected[i]) return false;
}
return true;
}
export async function saveUpload(
file: File,
subdir = 'general'
): Promise<{ url: string; path: string }> {
if (file.size > MAX_SIZE) {
throw new Error('File too large (max 10MB)');
}
if (!ALLOWED_TYPES.includes(file.type)) {
throw new Error('File type not allowed');
}
const buffer = Buffer.from(await file.arrayBuffer());
if (!validateMagicBytes(buffer, file.type)) {
throw new Error('File content does not match declared type');
}
const ext = file.name.split('.').pop()?.toLowerCase() || 'jpg';
const filename = `${crypto.randomUUID()}.${ext}`;
const dir = path.join(UPLOAD_DIR, subdir);
await fs.mkdir(dir, { recursive: true });
const filePath = path.join(dir, filename);
await fs.writeFile(filePath, buffer);
return {
url: `/uploads/${subdir}/${filename}`,
path: filePath
};
}
export async function deleteUpload(url: string): Promise<void> {
if (!url.startsWith('/uploads/')) return;
const filePath = path.join(UPLOAD_DIR, url.replace('/uploads/', ''));
try {
await fs.unlink(filePath);
} catch {
// File already gone
}
}