import { FastifyInstance, FastifyRequest, FastifyReply } from 'fastify'; const DEMO_PREFIX = '[DEMO]'; /** * Admin routes for demo/testing operations * These should only be available in development or testing environments */ export async function adminRoutes(fastify: FastifyInstance) { // Auth check - require OWNER or ADMIN role fastify.addHook('onRequest', async (request, reply) => { try { await request.jwtVerify(); const user = request.user as { role?: string }; if (!['OWNER', 'ADMIN'].includes(user?.role || '')) { return reply.code(403).send({ error: 'Admin access required' }); } } catch (err) { return reply.code(401).send({ error: 'Unauthorized' }); } }); /** * Reseed demo plants in a realistic layout * - Each batch stays in appropriate room based on stage * - Veg batches → Veg rooms, Flower batches → Flower rooms, etc. */ fastify.post('/reseed-plants', async (request: FastifyRequest, reply: FastifyReply) => { const prisma = fastify.prisma; try { fastify.log.info('🌿 Reseeding plants in realistic layout...'); // Clear existing plants await prisma.facilityPlant.deleteMany({}); await prisma.facilityPosition.updateMany({ data: { status: 'EMPTY' } }); // Get all demo batches const batches = await prisma.batch.findMany({ where: { name: { startsWith: DEMO_PREFIX } }, orderBy: { startDate: 'asc' } }); if (batches.length === 0) { return reply.code(400).send({ error: 'No demo batches found. Run seed-demo.js first.' }); } // Section mapping by room type const sectionMapping: Record = { VEG_PRIMARY: ['T1', 'T2', 'T3', 'T4'], MOTHER: ['M1', 'M2'], FLOWER_A: ['A1', 'A2', 'A3', 'A4', 'A5', 'A6'], FLOWER_B: ['B1', 'B2', 'B3', 'B4', 'B5', 'B6'], DRY: ['D1', 'D2'], CURE: ['C1', 'C2'], }; const results: { batch: string; planted: number }[] = []; for (const batch of batches) { let targetSections: string[] = []; let fillPercentage = 0.85; switch (batch.stage) { case 'CLONE_IN': case 'VEGETATIVE': targetSections = sectionMapping.VEG_PRIMARY; fillPercentage = 0.9; break; case 'FLOWERING': if (batch.strain === 'Gorilla Glue #4') { targetSections = sectionMapping.FLOWER_A; } else if (batch.strain === 'Wedding Cake') { targetSections = sectionMapping.FLOWER_B; } else { targetSections = ['A4', 'A5', 'A6']; } fillPercentage = 0.95; break; case 'DRYING': targetSections = sectionMapping.DRY; fillPercentage = 0.6; break; case 'CURING': targetSections = sectionMapping.CURE; fillPercentage = 0.5; break; default: continue; } // Get sections const sections = await prisma.facilitySection.findMany({ where: { code: { in: targetSections } }, include: { positions: true } }); // Collect available positions let allPositions: any[] = []; for (const section of sections) { const available = section.positions.filter((p: any) => p.status === 'EMPTY'); allPositions = allPositions.concat( available.map((p: any) => ({ ...p, sectionCode: section.code })) ); } const targetCount = Math.min( Math.floor(allPositions.length * fillPercentage), batch.plantCount || allPositions.length ); const plantsToCreate = []; const positionIds = []; for (let i = 0; i < targetCount && i < allPositions.length; i++) { const pos = allPositions[i]; const tagPrefix = batch.strain?.substring(0, 2).toUpperCase() || 'XX'; const tagNumber = `1A4-${tagPrefix}-${batch.id.substring(0, 4).toUpperCase()}-${String(i + 1).padStart(4, '0')}`; plantsToCreate.push({ tagNumber, batchId: batch.id, positionId: pos.id, address: `${pos.sectionCode}-R${pos.row}-C${pos.column}`, status: 'ACTIVE' }); positionIds.push(pos.id); } if (plantsToCreate.length > 0) { await prisma.facilityPlant.createMany({ data: plantsToCreate, skipDuplicates: true }); await prisma.facilityPosition.updateMany({ where: { id: { in: positionIds } }, data: { status: 'OCCUPIED' } }); } results.push({ batch: batch.name || batch.id, planted: plantsToCreate.length }); } const totalPlants = await prisma.facilityPlant.count(); return { success: true, message: 'Plants reseeded in realistic layout', totalPlants, batches: results }; } catch (error) { fastify.log.error(error); return reply.code(500).send({ error: 'Failed to reseed plants', details: error instanceof Error ? error.message : 'Unknown error' }); } }); }