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:
158
water-api/src/controllers/reading.controller.ts
Normal file
158
water-api/src/controllers/reading.controller.ts
Normal file
@@ -0,0 +1,158 @@
|
||||
import { Request, Response } from 'express';
|
||||
import * as readingService from '../services/reading.service';
|
||||
|
||||
/**
|
||||
* GET /readings
|
||||
* List all readings with pagination and filtering
|
||||
*/
|
||||
export async function getAll(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const {
|
||||
page = '1',
|
||||
pageSize = '50',
|
||||
meter_id,
|
||||
concentrator_id,
|
||||
project_id,
|
||||
start_date,
|
||||
end_date,
|
||||
reading_type,
|
||||
} = req.query;
|
||||
|
||||
const filters: readingService.ReadingFilters = {};
|
||||
if (meter_id) filters.meter_id = meter_id as string;
|
||||
if (concentrator_id) filters.concentrator_id = concentrator_id as string;
|
||||
if (project_id) filters.project_id = project_id as string;
|
||||
if (start_date) filters.start_date = start_date as string;
|
||||
if (end_date) filters.end_date = end_date as string;
|
||||
if (reading_type) filters.reading_type = reading_type as string;
|
||||
|
||||
const pagination = {
|
||||
page: parseInt(page as string, 10),
|
||||
pageSize: Math.min(parseInt(pageSize as string, 10), 100), // Max 100 per page
|
||||
};
|
||||
|
||||
const result = await readingService.getAll(filters, pagination);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result.data,
|
||||
pagination: result.pagination,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching readings:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /readings/:id
|
||||
* Get a single reading by ID
|
||||
*/
|
||||
export async function getById(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const reading = await readingService.getById(id);
|
||||
|
||||
if (!reading) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Reading not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: reading,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching reading:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /readings
|
||||
* Create a new reading
|
||||
*/
|
||||
export async function create(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const data = req.body as readingService.CreateReadingInput;
|
||||
|
||||
const reading = await readingService.create(data);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: reading,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating reading:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /readings/:id
|
||||
* Delete a reading
|
||||
*/
|
||||
export async function deleteReading(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const deleted = await readingService.deleteReading(id);
|
||||
|
||||
if (!deleted) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Reading not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: { message: 'Reading deleted successfully' },
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error deleting reading:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /readings/summary
|
||||
* Get consumption summary statistics
|
||||
*/
|
||||
export async function getSummary(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { project_id } = req.query;
|
||||
|
||||
const summary = await readingService.getConsumptionSummary(
|
||||
project_id as string | undefined
|
||||
);
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: summary,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching summary:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user