Multi-tenant scheduling SaaS (AgendaPro-equivalent): - Backend: Node + Express + node:sqlite, multi-tenant (admin/owner/employee), versioned migrations, 4 templates (estetica-spa, barberia, clinica, blank). - Frontend: React 18 + TS + Vite + Tailwind, FullCalendar drag-and-drop, Recharts dashboard, mobile-first (day/list views on mobile). - Features: calendar+services+employees+clients+tickets, dashboard with best employee/service, top tickets, top/frequent clients, commissions, cancellation policy + no-show tracking, public online booking (/b/:slug), cash register (cierre de caja), reminders/notifications center. - Verification: 65/65 e2e API, 58/58 visual (0 overflow, 0 console errors), typecheck clean, vite build OK.
51 lines
1.5 KiB
TypeScript
51 lines
1.5 KiB
TypeScript
import type { Request, Response, NextFunction } from "express";
|
|
import { db } from "../db.ts";
|
|
import type { User } from "../../shared/types.ts";
|
|
|
|
export interface AuthedRequest extends Request {
|
|
user?: User;
|
|
}
|
|
|
|
export function authRequired(req: AuthedRequest, res: Response, next: NextFunction) {
|
|
const header = req.header("authorization") || "";
|
|
const token = header.startsWith("Bearer ") ? header.slice(7) : req.header("x-user-id");
|
|
if (!token) {
|
|
res.status(401).json({ error: "No autorizado" });
|
|
return;
|
|
}
|
|
const userId = Number(token);
|
|
if (!Number.isFinite(userId)) {
|
|
res.status(401).json({ error: "Token inválido" });
|
|
return;
|
|
}
|
|
const user = db
|
|
.prepare(`SELECT id, business_id, email, name, role, employee_id, avatar_color FROM users WHERE id = ?`)
|
|
.get(userId) as User | undefined;
|
|
if (!user) {
|
|
res.status(401).json({ error: "Usuario no encontrado" });
|
|
return;
|
|
}
|
|
req.user = user;
|
|
next();
|
|
}
|
|
|
|
export function ownerOnly(req: AuthedRequest, res: Response, next: NextFunction) {
|
|
if (req.user?.role !== "owner") {
|
|
res.status(403).json({ error: "Solo el dueño puede realizar esta acción" });
|
|
return;
|
|
}
|
|
next();
|
|
}
|
|
|
|
export function adminOnly(req: AuthedRequest, res: Response, next: NextFunction) {
|
|
if (req.user?.role !== "admin") {
|
|
res.status(403).json({ error: "Acceso restringido al administrador de la plataforma" });
|
|
return;
|
|
}
|
|
next();
|
|
}
|
|
|
|
export function err(res: Response, status: number, message: string) {
|
|
return res.status(status).json({ error: message });
|
|
}
|