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:
227
water-api/src/controllers/project.controller.ts
Normal file
227
water-api/src/controllers/project.controller.ts
Normal file
@@ -0,0 +1,227 @@
|
||||
import { Request, Response } from 'express';
|
||||
import { AuthenticatedRequest } from '../middleware/auth.middleware';
|
||||
import * as projectService from '../services/project.service';
|
||||
import { CreateProjectInput, UpdateProjectInput, ProjectStatusType } from '../validators/project.validator';
|
||||
|
||||
/**
|
||||
* GET /projects
|
||||
* List all projects with pagination and optional filtering
|
||||
* Query params: page, pageSize, status, area_name, search
|
||||
*/
|
||||
export async function getAll(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const page = parseInt(req.query.page as string, 10) || 1;
|
||||
const pageSize = Math.min(parseInt(req.query.pageSize as string, 10) || 10, 100);
|
||||
|
||||
const filters: projectService.ProjectFilters = {};
|
||||
|
||||
if (req.query.status) {
|
||||
filters.status = req.query.status as ProjectStatusType;
|
||||
}
|
||||
|
||||
if (req.query.area_name) {
|
||||
filters.area_name = req.query.area_name as string;
|
||||
}
|
||||
|
||||
if (req.query.search) {
|
||||
filters.search = req.query.search as string;
|
||||
}
|
||||
|
||||
const result = await projectService.getAll(filters, { page, pageSize });
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: result.data,
|
||||
pagination: result.pagination,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching projects:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch projects',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /projects/:id
|
||||
* Get a single project by ID
|
||||
*/
|
||||
export async function getById(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const project = await projectService.getById(id);
|
||||
|
||||
if (!project) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Project not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: project,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching project:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch project',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* POST /projects
|
||||
* Create a new project
|
||||
* Requires authentication
|
||||
*/
|
||||
export async function create(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const userId = req.user?.id;
|
||||
|
||||
if (!userId) {
|
||||
res.status(401).json({
|
||||
success: false,
|
||||
error: 'Authentication required',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const data = req.body as CreateProjectInput;
|
||||
|
||||
const project = await projectService.create(data, userId);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: project,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error creating project:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to create project',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT /projects/:id
|
||||
* Update an existing project
|
||||
* Requires authentication
|
||||
*/
|
||||
export async function update(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
const data = req.body as UpdateProjectInput;
|
||||
|
||||
const project = await projectService.update(id, data);
|
||||
|
||||
if (!project) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Project not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: project,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error updating project:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to update project',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE /projects/:id
|
||||
* Delete a project
|
||||
* Requires admin role
|
||||
*/
|
||||
export async function deleteProject(req: AuthenticatedRequest, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
// First check if project exists
|
||||
const project = await projectService.getById(id);
|
||||
|
||||
if (!project) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Project not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
const deleted = await projectService.deleteProject(id);
|
||||
|
||||
if (!deleted) {
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to delete project',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: { message: 'Project deleted successfully' },
|
||||
});
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : 'Failed to delete project';
|
||||
|
||||
// Handle dependency error
|
||||
if (message.includes('Cannot delete project')) {
|
||||
res.status(409).json({
|
||||
success: false,
|
||||
error: message,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
console.error('Error deleting project:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to delete project',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /projects/:id/stats
|
||||
* Get project statistics
|
||||
*/
|
||||
export async function getStats(req: Request, res: Response): Promise<void> {
|
||||
try {
|
||||
const { id } = req.params;
|
||||
|
||||
const stats = await projectService.getStats(id);
|
||||
|
||||
if (!stats) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: 'Project not found',
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
res.status(200).json({
|
||||
success: true,
|
||||
data: stats,
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Error fetching project stats:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Failed to fetch project statistics',
|
||||
});
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user