diff --git a/src/routes/exports.js b/src/routes/exports.js index 5c3e967e..2abee3ed 100644 --- a/src/routes/exports.js +++ b/src/routes/exports.js @@ -2,6 +2,8 @@ const express = require('express'); const router = express.Router(); const { handleExport } = require('../services/exporter'); const { childLogger } = require('../config/logger'); +const { validate } = require('../middleware/validate'); +const { readingsQuerySchema, analyticsExportSchema, createReadingSchema, bulkReadingsSchema } = require('../schemas/metering.schema'); const log = childLogger('routes:exports'); diff --git a/src/routes/index.js b/src/routes/index.js index 20ac8c46..b91056e1 100644 --- a/src/routes/index.js +++ b/src/routes/index.js @@ -4,14 +4,14 @@ const { services } = require('../services'); // Import route modules here as they are created // const authRoutes = require('./auth'); -// const adminRoutes = require('./admin'); -// const analyticsRoutes = require('./analytics'); +const adminRoutes = require('./admin'); +const analyticsRoutes = require('./analytics'); const exportRoutes = require('./exports'); // Mount routes under their respective prefixes // router.use('/api/auth', authRoutes); -// router.use('/api/admin', adminRoutes); -// router.use('/api/analytics', analyticsRoutes); +router.use('/api/admin', adminRoutes); +router.use('/api/analytics', analyticsRoutes); router.use('/api/exports', exportRoutes); // Health check route diff --git a/src/schemas/metering.schema.js b/src/schemas/metering.schema.js new file mode 100644 index 00000000..654c32f2 --- /dev/null +++ b/src/schemas/metering.schema.js @@ -0,0 +1,53 @@ +// src/schemas/metering.schema.js +// +// Zod validation schemas for metering/export endpoints (issue #8). +// Covers the export routes that were missing validation in src/routes/exports.js. + +const { z } = require('zod'); +const { isoDateString, paginationQuerySchema } = require('./common.schema'); + +/** + * Query params for listing meter readings via export endpoints. + */ +const readingsQuerySchema = paginationQuerySchema.extend({ + meterId: z.string().min(1).optional(), + status: z.enum(['verified', 'pending', 'rejected']).optional(), + unit: z.enum(['kWh', 'kW', 'V', 'A', 'VAR', 'VA']).optional(), + startDate: isoDateString.optional(), + endDate: isoDateString.optional(), +}); + +/** + * Query params for analytics export endpoints. + */ +const analyticsExportSchema = z.object({ + startDate: isoDateString.optional(), + endDate: isoDateString.optional(), + granularity: z.enum(['hour', 'day', 'week', 'month']).default('day'), + format: z.enum(['json', 'csv']).default('json'), +}); + +/** + * Body schema for creating a meter reading submission. + */ +const createReadingSchema = z.object({ + meterId: z.string().min(1), + timestamp: isoDateString, + value: z.number().finite(), + unit: z.enum(['kWh', 'kW', 'V', 'A', 'VAR', 'VA']), + status: z.enum(['verified', 'pending', 'rejected']).default('pending'), +}).strict(); + +/** + * Body schema for bulk reading import. + */ +const bulkReadingsSchema = z.object({ + readings: z.array(createReadingSchema).min(1).max(1000), +}).strict(); + +module.exports = { + readingsQuerySchema, + analyticsExportSchema, + createReadingSchema, + bulkReadingsSchema, +};