export default (router) => { router.get('/', (req, res) => res.send('Hello, World!')); }; export default { id: 'mayo-api', handler: (router, { database }) => { router.get('/products/by-order/:orderNumber/:orderYear/:productIndex/timeline', async (req, res) => { if (!req.accountability?.user) { return res.status(401).json({ error: 'Unauthorized' }) } const { orderNumber, orderYear, productIndex } = req.params const normalizedOrderNumber = Number(orderNumber) const normalizedOrderYear = Number(orderYear) const normalizedProductIndex = Number(productIndex) if ( Number.isNaN(normalizedOrderNumber) || Number.isNaN(normalizedOrderYear) || Number.isNaN(normalizedProductIndex) ) { return res.status(400).json({ error: 'Invalid order code' }) } const orderProduct = await database('mayo_order_products as op') .select( 'op.id as order_product_id', 'op.product_id', 'op.produc_order_idx', 'o.id as order_id', 'o.order_number', 'o.order_year', 'p.note', 'm.id as model_id', 'm.name as model_name' ) .join('mayo_orders as o', 'op.order_id', 'o.id') .join('mayo_products as p', 'op.product_id', 'p.id') .leftJoin('mayo_models as m', 'p.model_id', 'm.id') .where('o.order_number', normalizedOrderNumber) .where('o.order_year', normalizedOrderYear) .where('op.produc_order_idx', normalizedProductIndex) .first() if (!orderProduct) { return res.status(404).json({ error: 'Product not found' }) } const events = await database('mayo_events as e') .select( 'e.id', 'e.ordinal', 'e.event_kind', 'e.event_date', 'p.id as part_id', 'p.part_type' ) .join('mayo_parts as p', 'e.part_id', 'p.id') .where('p.product_id', orderProduct.product_id) .orderBy('e.ordinal', 'asc') const eventIds = events.map((event) => event.id) const [operations, notes, photos] = await Promise.all([ database('mayo_event_operations as eo') .select( 'eo.event_id', 'o.id as operation_id', 'o.name', 'o.description' ) .leftJoin('mayo_operations as o', 'eo.operation_id', 'o.id') .whereIn('eo.event_id', eventIds), database('mayo_event_notes') .select('event_id', 'note_type', 'note') .whereIn('event_id', eventIds), database('mayo_event_photos') .select('event_id', 'photo_url') .whereIn('event_id', eventIds), ]) const operationsByEventId = new Map(operations.map((operation) => [operation.event_id, operation])) const notesByEventId = new Map(notes.map((note) => [note.event_id, note])) const photosByEventId = photos.reduce((acc, photo) => { if (!acc.has(photo.event_id)) acc.set(photo.event_id, []) acc.get(photo.event_id).push({ file_id: photo.photo_url, url: photo.photo_url ? `/assets/${photo.photo_url}` : null, }) return acc }, new Map()) const timeline = events.map((event) => ({ id: event.id, ordinal: event.ordinal, kind: event.event_kind, date: event.event_date, part: { id: event.part_id, type: event.part_type, }, operation: operationsByEventId.get(event.id) ?? null, note: notesByEventId.get(event.id) ?? null, photos: photosByEventId.get(event.id) ?? [], })) return res.json({ product: { id: orderProduct.product_id, order_code: `${String(orderProduct.order_number).padStart(4, '0')}/${orderProduct.order_year}/${orderProduct.produc_order_idx}`, model: { id: orderProduct.model_id, name: orderProduct.model_name, }, note: orderProduct.note, }, timeline, }) }) }, }