import { Router, Request, Response } from 'express'; import { z } from 'zod'; import { deviceService } from '../services/deviceService'; import { discoveryService } from '../services/discoveryService'; const router = Router(); const createDeviceSchema = z.object({ name: z.string().min(1, 'Name is required'), ipAddress: z.string().min(1, 'IP address is required'), port: z.number().int().positive().optional(), enabled: z.boolean().optional(), }); const updateDeviceSchema = z.object({ name: z.string().min(1).optional(), ipAddress: z.string().min(1).optional(), port: z.number().int().positive().optional(), enabled: z.boolean().optional(), }); // GET /api/devices router.get('/', async (req: Request, res: Response) => { try { const devices = await deviceService.getAllDevices(); res.json(devices); } catch (error) { console.error('Error fetching devices:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to fetch devices' }); } }); // GET /api/devices/:id router.get('/:id', async (req: Request, res: Response) => { try { const device = await deviceService.getDeviceById(req.params.id); if (!device) { return res.status(404).json({ error: 'NotFound', message: 'Device not found' }); } res.json(device); } catch (error) { console.error('Error fetching device:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to fetch device' }); } }); // POST /api/devices router.post('/', async (req: Request, res: Response) => { try { const body = createDeviceSchema.parse(req.body); const device = await deviceService.createDevice(body); res.status(201).json(device); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'ValidationError', details: error.errors.map((e) => ({ field: e.path.join('.'), message: e.message })), }); } console.error('Error creating device:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to create device' }); } }); // PUT /api/devices/:id router.put('/:id', async (req: Request, res: Response) => { try { const body = updateDeviceSchema.parse(req.body); const device = await deviceService.updateDevice(req.params.id, body); res.json(device); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'ValidationError', details: error.errors.map((e) => ({ field: e.path.join('.'), message: e.message })), }); } if (error instanceof Error && error.message.includes('Record to update not found')) { return res.status(404).json({ error: 'NotFound', message: 'Device not found' }); } console.error('Error updating device:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to update device' }); } }); // DELETE /api/devices/:id router.delete('/:id', async (req: Request, res: Response) => { try { await deviceService.deleteDevice(req.params.id); res.status(204).send(); } catch (error) { if (error instanceof Error && error.message.includes('Record to delete does not exist')) { return res.status(404).json({ error: 'NotFound', message: 'Device not found' }); } console.error('Error deleting device:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to delete device' }); } }); // POST /api/devices/:id/ping router.post('/:id/ping', async (req: Request, res: Response) => { try { const result = await deviceService.pingDevice(req.params.id); if (result.status === 'error') { return res.status(502).json(result); } res.json(result); } catch (error) { if (error instanceof Error && error.message === 'Device not found') { return res.status(404).json({ error: 'NotFound', message: 'Device not found' }); } console.error('Error pinging device:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to ping device' }); } }); // GET /api/devices/:id/details router.get('/:id/details', async (req: Request, res: Response) => { try { const details = await deviceService.getDeviceDetails(req.params.id); res.json(details); } catch (error) { if (error instanceof Error && error.message === 'Device not found') { return res.status(404).json({ error: 'NotFound', message: 'Device not found' }); } console.error('Error fetching device details:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to fetch device details' }); } }); // GET /api/devices/:id/presets router.get('/:id/presets', async (req: Request, res: Response) => { try { const presets = await deviceService.getDevicePresets(req.params.id); res.json(presets); } catch (error) { if (error instanceof Error && error.message === 'Device not found') { return res.status(404).json({ error: 'NotFound', message: 'Device not found' }); } console.error('Error fetching device presets:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to fetch presets' }); } }); // GET /api/devices/status/all router.get('/status/all', async (req: Request, res: Response) => { try { const devices = await deviceService.getAllDevicesWithStatus(); res.json(devices); } catch (error) { console.error('Error fetching devices with status:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to fetch devices status' }); } }); // POST /api/devices/:id/turn-on router.post('/:id/turn-on', async (req: Request, res: Response) => { try { await deviceService.turnOnDevice(req.params.id); res.json({ status: 'ok', message: 'Device turned on' }); } catch (error) { if (error instanceof Error && error.message === 'Device not found') { return res.status(404).json({ error: 'NotFound', message: 'Device not found' }); } console.error('Error turning on device:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to turn on device' }); } }); // POST /api/devices/:id/turn-off router.post('/:id/turn-off', async (req: Request, res: Response) => { try { await deviceService.turnOffDevice(req.params.id); res.json({ status: 'ok', message: 'Device turned off' }); } catch (error) { if (error instanceof Error && error.message === 'Device not found') { return res.status(404).json({ error: 'NotFound', message: 'Device not found' }); } console.error('Error turning off device:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to turn off device' }); } }); // POST /api/devices/all/turn-on router.post('/all/turn-on', async (req: Request, res: Response) => { try { const result = await deviceService.turnOnAllDevices(); res.json(result); } catch (error) { console.error('Error turning on all devices:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to turn on all devices' }); } }); // POST /api/devices/all/turn-off router.post('/all/turn-off', async (req: Request, res: Response) => { try { const result = await deviceService.turnOffAllDevices(); res.json(result); } catch (error) { console.error('Error turning off all devices:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to turn off all devices' }); } }); // POST /api/devices/:id/sync router.post('/:id/sync', async (req: Request, res: Response) => { try { const { targetIds } = z.object({ targetIds: z.array(z.string()).min(1, 'At least one target device is required'), }).parse(req.body); const result = await deviceService.syncDeviceState(req.params.id, targetIds); res.json(result); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'ValidationError', details: error.errors.map((e) => ({ field: e.path.join('.'), message: e.message })), }); } if (error instanceof Error && error.message.includes('not found')) { return res.status(404).json({ error: 'NotFound', message: error.message }); } console.error('Error syncing device state:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to sync device state' }); } }); // POST /api/devices/:sourceId/copy-to/:targetId router.post('/:sourceId/copy-to/:targetId', async (req: Request, res: Response) => { try { await deviceService.copyDeviceConfig(req.params.sourceId, req.params.targetId); res.json({ status: 'ok', message: 'Configuration copied successfully' }); } catch (error) { if (error instanceof Error && error.message.includes('not found')) { return res.status(404).json({ error: 'NotFound', message: error.message }); } console.error('Error copying device config:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to copy configuration' }); } }); // POST /api/devices/discover/mdns router.post('/discover/mdns', async (req: Request, res: Response) => { try { const { timeout } = z.object({ timeout: z.number().int().positive().optional(), }).parse(req.body); const discoveredDevices = await discoveryService.discoverDevices(timeout); res.json({ devices: discoveredDevices, count: discoveredDevices.length }); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'ValidationError', details: error.errors.map((e) => ({ field: e.path.join('.'), message: e.message })), }); } console.error('Error discovering devices:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to discover devices' }); } }); // POST /api/devices/discover/scan router.post('/discover/scan', async (req: Request, res: Response) => { try { const { baseIp, startRange, endRange } = z.object({ baseIp: z.string().regex(/^\d{1,3}\.\d{1,3}\.\d{1,3}$/, 'Invalid base IP format (e.g., 192.168.1)'), startRange: z.number().int().min(1).max(254).optional(), endRange: z.number().int().min(1).max(254).optional(), }).parse(req.body); const discoveredDevices = await discoveryService.scanIpRange(baseIp, startRange, endRange); res.json({ devices: discoveredDevices, count: discoveredDevices.length }); } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'ValidationError', details: error.errors.map((e) => ({ field: e.path.join('.'), message: e.message })), }); } console.error('Error scanning IP range:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to scan IP range' }); } }); // POST /api/devices/discover/verify router.post('/discover/verify', async (req: Request, res: Response) => { try { const { ipAddress, port } = z.object({ ipAddress: z.string().min(1, 'IP address is required'), port: z.number().int().positive().optional(), }).parse(req.body); const device = await discoveryService.verifyDevice(ipAddress, port); if (device) { res.json({ found: true, device }); } else { res.json({ found: false, message: 'No WLED device found at this address' }); } } catch (error) { if (error instanceof z.ZodError) { return res.status(400).json({ error: 'ValidationError', details: error.errors.map((e) => ({ field: e.path.join('.'), message: e.message })), }); } console.error('Error verifying device:', error); res.status(500).json({ error: 'InternalError', message: 'Failed to verify device' }); } }); export default router;