Initial commit
Next.js + Express event management app for Hope Family Church.
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Checks whether the Prisma client has the EventAttachment model and the table is queryable.
|
||||
* @param {import('@prisma/client').PrismaClient} prisma
|
||||
*/
|
||||
async function canUseEventAttachment(prisma) {
|
||||
const hasModel = !!(prisma && prisma.eventAttachment && typeof prisma.eventAttachment.findMany === 'function');
|
||||
if (!hasModel) return { ok: false, reason: 'NO_MODEL' };
|
||||
try {
|
||||
await prisma.eventAttachment.findFirst({});
|
||||
return { ok: true };
|
||||
} catch (e) {
|
||||
return { ok: false, reason: 'QUERY_ERROR', error: String(e?.message || e) };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads all manifest files for event attachments and inserts missing rows into DB.
|
||||
* It is idempotent: it will skip entries that already exist by id. If id lookup fails,
|
||||
* it tries to avoid duplicates using a composite check (eventId + filename + size).
|
||||
*
|
||||
* @param {import('@prisma/client').PrismaClient} prisma
|
||||
* @param {{ dryRun?: boolean, removeManifestAfterImport?: boolean }} [options]
|
||||
* @returns {Promise<{processedFiles:number, imported:number, skipped:number, errors:Array<{file:string,error:string}>, details:Array<{file:string, imported:number, skipped:number}>}>}
|
||||
*/
|
||||
async function syncManifestsToDb(prisma, options = {}) {
|
||||
const { dryRun = false, removeManifestAfterImport = false } = options;
|
||||
const baseDir = path.join(__dirname, '..', '..', 'public', 'uploads', 'event-files');
|
||||
const result = { processedFiles: 0, imported: 0, skipped: 0, errors: [], details: [] };
|
||||
|
||||
const check = await canUseEventAttachment(prisma);
|
||||
if (!check.ok) {
|
||||
return { ...result, errors: [{ file: '*', error: `Attachments model not usable (${check.reason})${check.error ? ': ' + check.error : ''}` }] };
|
||||
}
|
||||
|
||||
if (!fs.existsSync(baseDir)) return result;
|
||||
const files = fs.readdirSync(baseDir).filter(f => f.endsWith('.attachments.json'));
|
||||
|
||||
for (const file of files) {
|
||||
const manifestPath = path.join(baseDir, file);
|
||||
result.processedFiles += 1;
|
||||
let list = [];
|
||||
try {
|
||||
const raw = fs.readFileSync(manifestPath, 'utf-8');
|
||||
list = JSON.parse(raw) || [];
|
||||
} catch (e) {
|
||||
result.errors.push({ file, error: 'Failed to parse JSON: ' + String(e?.message || e) });
|
||||
continue;
|
||||
}
|
||||
|
||||
let imported = 0;
|
||||
let skipped = 0;
|
||||
|
||||
for (const entry of list) {
|
||||
try {
|
||||
// Check existing by id first
|
||||
const existsById = entry?.id ? await prisma.eventAttachment.findUnique({ where: { id: entry.id } }) : null;
|
||||
if (existsById) { skipped++; continue; }
|
||||
|
||||
// Check using composite heuristic to avoid duplicates
|
||||
const maybeExisting = await prisma.eventAttachment.findFirst({
|
||||
where: {
|
||||
eventId: entry.eventId,
|
||||
filename: entry.filename,
|
||||
size: typeof entry.size === 'number' ? entry.size : undefined,
|
||||
}
|
||||
});
|
||||
if (maybeExisting) { skipped++; continue; }
|
||||
|
||||
if (!dryRun) {
|
||||
await prisma.eventAttachment.create({
|
||||
data: {
|
||||
id: entry.id || undefined,
|
||||
eventId: entry.eventId,
|
||||
originalName: entry.originalName || entry.filename || 'file',
|
||||
filename: entry.filename,
|
||||
mimeType: entry.mimeType || 'application/octet-stream',
|
||||
size: typeof entry.size === 'number' ? entry.size : 0,
|
||||
url: entry.url,
|
||||
createdAt: entry.createdAt ? new Date(entry.createdAt) : undefined,
|
||||
}
|
||||
});
|
||||
}
|
||||
imported++;
|
||||
} catch (e) {
|
||||
result.errors.push({ file, error: 'Insert failed: ' + String(e?.message || e) });
|
||||
}
|
||||
}
|
||||
|
||||
result.imported += imported;
|
||||
result.skipped += skipped;
|
||||
result.details.push({ file, imported, skipped });
|
||||
|
||||
// Optionally remove manifest if all entries are in DB now
|
||||
if (!dryRun && removeManifestAfterImport && imported > 0) {
|
||||
try {
|
||||
fs.unlinkSync(manifestPath);
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
module.exports = { syncManifestsToDb, canUseEventAttachment };
|
||||
Reference in New Issue
Block a user