polish: Enhance demo data for Audit Logs and SOPs, fix photo delete bug
- Seed script now generates 100+ realistic audit logs - Added Policy, Training, Form, and Checklist document types to seed data - Fixed bug in photo deletion logic for older photos
This commit is contained in:
parent
337f1e6adb
commit
bfa3cd21cc
2 changed files with 67 additions and 15 deletions
|
|
@ -409,10 +409,12 @@ async function main() {
|
|||
{ title: `${DEMO_PREFIX} SOP - Nutrient Mixing`, type: 'SOP', content: '# Nutrient Mixing (Front Row Ag)\n\n## Stock Preparation\n1. Part A: 24lbs per 16 gal\n2. Part B: 16lbs + PhosZyme per 16 gal' },
|
||||
{ title: `${DEMO_PREFIX} SOP - Clone Processing`, type: 'SOP', content: '# Clone Processing\n\n## Receiving\n1. Inspect for pests\n2. Count and verify\n3. Log in METRC' },
|
||||
{ title: `${DEMO_PREFIX} SOP - Harvest Procedure`, type: 'SOP', content: '# Harvest Procedure\n\n## Steps\n1. Cut main stem\n2. Hang dry in Dry Room\n3. Monitor temp/humidity\n4. Trim when stems snap' },
|
||||
{ title: `${DEMO_PREFIX} SOP - Visitor Check-In`, type: 'POLICY', content: '# Visitor Check-In\n\n## Requirements\n1. Valid ID\n2. Signed NDA\n3. PPE issued' },
|
||||
{ title: `${DEMO_PREFIX} SOP - Emergency Procedures`, type: 'POLICY', content: '# Emergency Procedures\n\n## Fire\n1. Evacuate\n2. Call 911\n3. Meet at designated point' },
|
||||
{ title: `${DEMO_PREFIX} HVAC Maintenance`, type: 'TRAINING', content: '# HVAC Maintenance\n\n## Quarterly Tasks\n- Replace filters\n- Check refrigerant\n- Clean coils' },
|
||||
{ title: `${DEMO_PREFIX} METRC Training Guide`, type: 'TRAINING', content: '# METRC Training\n\n## Logging Plants\n1. Scan package tag\n2. Enter plant count\n3. Verify location' },
|
||||
{ title: `${DEMO_PREFIX} Policy - Visitor Check-In`, type: 'POLICY', content: '# Visitor Check-In\n\n## Requirements\n1. Valid ID\n2. Signed NDA\n3. PPE issued' },
|
||||
{ title: `${DEMO_PREFIX} Policy - Emergency Procedures`, type: 'POLICY', content: '# Emergency Procedures\n\n## Fire\n1. Evacuate\n2. Call 911\n3. Meet at designated point' },
|
||||
{ title: `${DEMO_PREFIX} Training - HVAC Maintenance`, type: 'TRAINING', content: '# HVAC Maintenance\n\n## Quarterly Tasks\n- Replace filters\n- Check refrigerant\n- Clean coils' },
|
||||
{ title: `${DEMO_PREFIX} Training - METRC Guide`, type: 'TRAINING', content: '# METRC Training\n\n## Logging Plants\n1. Scan package tag\n2. Enter plant count\n3. Verify location' },
|
||||
{ title: `${DEMO_PREFIX} Form - Incident Report`, type: 'FORM', content: '# Incident Report Form\n\n**Date:** ______\n**Location:** ______\n\n## Description of Incident\n(Please describe what happened, who was involved, and any immediate actions taken)\n\n\n## Witness Statements\n\n' },
|
||||
{ title: `${DEMO_PREFIX} Checklist - Harvest Cleaning`, type: 'CHECKLIST', content: '# Harvest Cleaning Checklist\n\n- [ ] Remove all plant debris\n- [ ] Wipe down walls with H2O2\n- [ ] Clean and Sanitize trays\n- [ ] Sweep and Mop floors\n- [ ] Empty dehumidifiers' },
|
||||
];
|
||||
|
||||
for (const doc of documents) {
|
||||
|
|
@ -568,6 +570,10 @@ async function main() {
|
|||
|
||||
// ==================== AUDIT LOGS ====================
|
||||
console.log('\n📋 Creating Audit Log Entries...');
|
||||
// ==================== AUDIT LOGS ====================
|
||||
console.log('\n📋 Creating Audit Log Entries...');
|
||||
|
||||
// Base entries
|
||||
const auditEntries = [
|
||||
{ userId: users.admin?.id, userName: 'Admin User', action: 'LOGIN', entity: 'User', entityName: 'Admin login from office', ipAddress: '192.168.1.50' },
|
||||
{ userId: users.sarah?.id, userName: 'Sarah Chen', action: 'CREATE', entity: 'Batch', entityId: Object.values(batches)[0]?.id, entityName: 'Created batch: Gorilla Glue #4 - B001' },
|
||||
|
|
@ -586,21 +592,58 @@ async function main() {
|
|||
{ userId: users.admin?.id, userName: 'Admin User', action: 'UPDATE', entity: 'Role', entityName: 'Updated permissions for Grower role' },
|
||||
];
|
||||
|
||||
for (let i = 0; i < auditEntries.length; i++) {
|
||||
const entry = auditEntries[i];
|
||||
const timestamp = daysAgo(i % 14);
|
||||
timestamp.setHours(8 + Math.floor(Math.random() * 10), Math.floor(Math.random() * 60));
|
||||
// Generate ~100 random realistic entries
|
||||
const actions = ['LOGIN', 'ACCESS', 'UPDATE', 'CREATE', 'DELETE', 'EXPORT'];
|
||||
const entities = ['Batch', 'Task', 'Document', 'Room', 'Visitor', 'Report'];
|
||||
const demoStaff = Object.values(users);
|
||||
|
||||
for (let i = 0; i < 120; i++) {
|
||||
const user = demoStaff[Math.floor(Math.random() * demoStaff.length)];
|
||||
const action = actions[Math.floor(Math.random() * actions.length)];
|
||||
let entity = entities[Math.floor(Math.random() * entities.length)];
|
||||
|
||||
let entityName = `${entity} #${1000 + i}`;
|
||||
let changes = null;
|
||||
|
||||
if (action === 'LOGIN') {
|
||||
entity = 'User';
|
||||
entityName = 'User Login';
|
||||
} else if (action === 'ACCESS') {
|
||||
entityName = `Accessed ${entity} details`;
|
||||
} else if (action === 'UPDATE') {
|
||||
changes = { status: { from: 'PENDING', to: 'COMPLETED' } };
|
||||
entityName = `Updated ${entity} status`;
|
||||
}
|
||||
|
||||
const daysBack = Math.floor(Math.random() * 30);
|
||||
const timestamp = daysAgo(daysBack);
|
||||
timestamp.setHours(8 + Math.floor(Math.random() * 12), Math.floor(Math.random() * 60)); // Business hours
|
||||
|
||||
auditEntries.push({
|
||||
userId: user.id,
|
||||
userName: user.name.replace(DEMO_PREFIX + ' ', ''),
|
||||
action,
|
||||
entity,
|
||||
entityName,
|
||||
changes,
|
||||
timestamp,
|
||||
ipAddress: `192.168.1.${50 + Math.floor(Math.random() * 100)}`
|
||||
});
|
||||
}
|
||||
|
||||
// Sort by timestamp desc
|
||||
auditEntries.sort((a, b) => b.timestamp - a.timestamp);
|
||||
|
||||
for (const entry of auditEntries) {
|
||||
await prisma.auditLog.create({
|
||||
data: {
|
||||
...entry,
|
||||
changes: entry.changes ? JSON.stringify(entry.changes) : null,
|
||||
metadata: null,
|
||||
timestamp
|
||||
metadata: null
|
||||
}
|
||||
});
|
||||
}
|
||||
console.log(` ✓ ${auditEntries.length} audit log entries`);
|
||||
console.log(` ✓ ${auditEntries.length} audit log entries (15 fixed + 120 generated)`);
|
||||
|
||||
console.log('\n✨ Demo seeding complete!');
|
||||
console.log('\nDemo Logins:');
|
||||
|
|
|
|||
|
|
@ -249,19 +249,28 @@ export async function uploadRoutes(server: FastifyInstance) {
|
|||
// Photos are stored as: /photos/{year}/{month}/{day}/{photoId}/
|
||||
// We need to search for it
|
||||
|
||||
const date = new Date();
|
||||
// Parse date from photoId (format: photo_TIMESTAMP_HASH)
|
||||
const parts = photoId.split('_');
|
||||
if (parts.length < 2) {
|
||||
return reply.status(400).send({ error: 'Invalid photo ID format' });
|
||||
}
|
||||
|
||||
const timestamp = parseInt(parts[1]);
|
||||
const date = new Date(timestamp);
|
||||
|
||||
if (isNaN(date.getTime())) {
|
||||
return reply.status(400).send({ error: 'Invalid photo timestamp' });
|
||||
}
|
||||
|
||||
const year = date.getFullYear();
|
||||
const month = String(date.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(date.getDate()).padStart(2, '0');
|
||||
|
||||
// Try today's date first (most common case)
|
||||
let photoDir = path.join(STORAGE_PATH, `${year}/${month}/${day}`, photoId);
|
||||
|
||||
try {
|
||||
await fs.access(photoDir);
|
||||
} catch {
|
||||
// Photo not found in today's folder - would need to search
|
||||
// For now, return not found
|
||||
return reply.status(404).send({ error: 'Photo not found' });
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue