Migrar backend a PostgreSQL + Node.js/Express con nuevas funcionalidades
Backend (water-api/): - Crear API REST completa con Express + TypeScript - Implementar autenticación JWT con refresh tokens - CRUD completo para: projects, concentrators, meters, gateways, devices, users, roles - Agregar validación con Zod para todas las entidades - Implementar webhooks para The Things Stack (LoRaWAN) - Agregar endpoint de lecturas con filtros y resumen de consumo - Implementar carga masiva de medidores via Excel (.xlsx) Frontend: - Crear cliente HTTP con manejo automático de JWT y refresh - Actualizar todas las APIs para usar nuevo backend - Agregar sistema de autenticación real (login, logout, me) - Agregar selector de tipo (LORA, LoRaWAN, Grandes) en concentradores y medidores - Agregar campo Meter ID en medidores - Crear modal de carga masiva para medidores - Agregar página de consumo con gráficas y filtros - Corregir carga de proyectos independiente de datos existentes Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
118
water-api/src/validators/project.validator.ts
Normal file
118
water-api/src/validators/project.validator.ts
Normal file
@@ -0,0 +1,118 @@
|
||||
import { z } from 'zod';
|
||||
import { Request, Response, NextFunction } from 'express';
|
||||
|
||||
/**
|
||||
* Project status enum values
|
||||
*/
|
||||
export const ProjectStatus = {
|
||||
ACTIVE: 'ACTIVE',
|
||||
INACTIVE: 'INACTIVE',
|
||||
COMPLETED: 'COMPLETED',
|
||||
} as const;
|
||||
|
||||
export type ProjectStatusType = (typeof ProjectStatus)[keyof typeof ProjectStatus];
|
||||
|
||||
/**
|
||||
* Schema for creating a new project
|
||||
* - name: required, non-empty string
|
||||
* - description: optional string
|
||||
* - area_name: optional string
|
||||
* - location: optional string
|
||||
* - status: optional, defaults to ACTIVE
|
||||
*/
|
||||
export const createProjectSchema = z.object({
|
||||
name: z
|
||||
.string({ required_error: 'Name is required' })
|
||||
.min(1, 'Name cannot be empty')
|
||||
.max(255, 'Name must be at most 255 characters'),
|
||||
description: z
|
||||
.string()
|
||||
.max(1000, 'Description must be at most 1000 characters')
|
||||
.optional()
|
||||
.nullable(),
|
||||
area_name: z
|
||||
.string()
|
||||
.max(255, 'Area name must be at most 255 characters')
|
||||
.optional()
|
||||
.nullable(),
|
||||
location: z
|
||||
.string()
|
||||
.max(500, 'Location must be at most 500 characters')
|
||||
.optional()
|
||||
.nullable(),
|
||||
status: z
|
||||
.enum([ProjectStatus.ACTIVE, ProjectStatus.INACTIVE, ProjectStatus.COMPLETED])
|
||||
.default(ProjectStatus.ACTIVE)
|
||||
.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Schema for updating a project
|
||||
* All fields are optional
|
||||
*/
|
||||
export const updateProjectSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(1, 'Name cannot be empty')
|
||||
.max(255, 'Name must be at most 255 characters')
|
||||
.optional(),
|
||||
description: z
|
||||
.string()
|
||||
.max(1000, 'Description must be at most 1000 characters')
|
||||
.optional()
|
||||
.nullable(),
|
||||
area_name: z
|
||||
.string()
|
||||
.max(255, 'Area name must be at most 255 characters')
|
||||
.optional()
|
||||
.nullable(),
|
||||
location: z
|
||||
.string()
|
||||
.max(500, 'Location must be at most 500 characters')
|
||||
.optional()
|
||||
.nullable(),
|
||||
status: z
|
||||
.enum([ProjectStatus.ACTIVE, ProjectStatus.INACTIVE, ProjectStatus.COMPLETED])
|
||||
.optional(),
|
||||
});
|
||||
|
||||
/**
|
||||
* Type definitions derived from schemas
|
||||
*/
|
||||
export type CreateProjectInput = z.infer<typeof createProjectSchema>;
|
||||
export type UpdateProjectInput = z.infer<typeof updateProjectSchema>;
|
||||
|
||||
/**
|
||||
* Generic validation middleware factory
|
||||
* Creates a middleware that validates request body against a Zod schema
|
||||
* @param schema - Zod schema to validate against
|
||||
*/
|
||||
function validate<T extends z.ZodTypeAny>(schema: T) {
|
||||
return (req: Request, res: Response, next: NextFunction): void => {
|
||||
const result = schema.safeParse(req.body);
|
||||
|
||||
if (!result.success) {
|
||||
const errors = result.error.errors.map((err) => ({
|
||||
field: err.path.join('.'),
|
||||
message: err.message,
|
||||
}));
|
||||
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Validation failed',
|
||||
details: errors,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Replace body with validated and typed data
|
||||
req.body = result.data;
|
||||
next();
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Pre-configured validation middlewares for projects
|
||||
*/
|
||||
export const validateCreateProject = validate(createProjectSchema);
|
||||
export const validateUpdateProject = validate(updateProjectSchema);
|
||||
Reference in New Issue
Block a user