Actualizar server.js
This commit is contained in:
58
server.js
58
server.js
@@ -431,6 +431,44 @@ function signToken(user) { const accountId = user.owner_id || user.id; return jw
|
|||||||
function authMiddleware(req, res, next) { const h = req.headers.authorization || ""; const token = h.startsWith("Bearer ") ? h.slice(7) : ""; if (!token) return res.status(401).json({ ok: false, error: "No token" }); try { req.user = jwt.verify(token, JWT_SECRET); next(); } catch { return res.status(401).json({ ok: false, error: "Token inválido" }); } }
|
function authMiddleware(req, res, next) { const h = req.headers.authorization || ""; const token = h.startsWith("Bearer ") ? h.slice(7) : ""; if (!token) return res.status(401).json({ ok: false, error: "No token" }); try { req.user = jwt.verify(token, JWT_SECRET); next(); } catch { return res.status(401).json({ ok: false, error: "Token inválido" }); } }
|
||||||
function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)); }
|
function genCode6() { return String(Math.floor(100000 + Math.random() * 900000)); }
|
||||||
|
|
||||||
|
|
||||||
|
// ==========================================
|
||||||
|
// 🛡️ ESCUDO DE TITANIO: ANTI-SOLAPAMIENTOS BBDD
|
||||||
|
// ==========================================
|
||||||
|
async function comprobarDisponibilidad(ownerId, workerId, date, time, durationMin, excludeId = null) {
|
||||||
|
if (!workerId || !date || !time) return { choca: false };
|
||||||
|
|
||||||
|
let [newH, newM] = time.split(':').map(Number);
|
||||||
|
let newStart = newH * 60 + newM;
|
||||||
|
let newEnd = newStart + (parseInt(durationMin) || 60);
|
||||||
|
|
||||||
|
let query = `
|
||||||
|
SELECT id, service_ref, raw_data->>'scheduled_time' as time, raw_data->>'duration_minutes' as dur,
|
||||||
|
raw_data->>'requested_time' as req_time, raw_data->>'appointment_status' as app_status
|
||||||
|
FROM scraped_services
|
||||||
|
WHERE owner_id = $1 AND assigned_to = $2 AND status != 'archived'
|
||||||
|
AND (
|
||||||
|
raw_data->>'scheduled_date' = $3 OR
|
||||||
|
(raw_data->>'requested_date' = $3 AND raw_data->>'appointment_status' = 'pending')
|
||||||
|
)
|
||||||
|
`;
|
||||||
|
let params = [ownerId, workerId, date];
|
||||||
|
if (excludeId) { query += ` AND id != $4`; params.push(excludeId); }
|
||||||
|
|
||||||
|
const q = await pool.query(query, params);
|
||||||
|
|
||||||
|
for (const s of q.rows) {
|
||||||
|
let sTime = s.time || (s.app_status === 'pending' ? s.req_time : null);
|
||||||
|
if (sTime && sTime.includes(':')) {
|
||||||
|
let [sH, sM] = sTime.split(':').map(Number);
|
||||||
|
let sStart = sH * 60 + sM;
|
||||||
|
let sEnd = sStart + parseInt(s.dur || 60);
|
||||||
|
if (newStart < sEnd && newEnd > sStart) return { choca: true, ref: s.service_ref, time: sTime };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return { choca: false };
|
||||||
|
}
|
||||||
|
|
||||||
// ==========================================
|
// ==========================================
|
||||||
// 🔐 SISTEMA DE AUTENTICACIÓN (LOGIN Y SESIÓN)
|
// 🔐 SISTEMA DE AUTENTICACIÓN (LOGIN Y SESIÓN)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
@@ -1287,8 +1325,8 @@ app.get("/public/portal/:token/slots", async (req, res) => {
|
|||||||
const targetGuildId = raw["guild_id"];
|
const targetGuildId = raw["guild_id"];
|
||||||
|
|
||||||
const agendaQ = await pool.query(`
|
const agendaQ = await pool.query(`
|
||||||
SELECT raw_data->>'scheduled_date' as date,
|
SELECT COALESCE(NULLIF(raw_data->>'scheduled_date', ''), raw_data->>'requested_date') as date,
|
||||||
raw_data->>'scheduled_time' as time,
|
COALESCE(NULLIF(raw_data->>'scheduled_time', ''), raw_data->>'requested_time') as time,
|
||||||
raw_data->>'duration_minutes' as duration,
|
raw_data->>'duration_minutes' as duration,
|
||||||
raw_data->>'Población' as poblacion,
|
raw_data->>'Población' as poblacion,
|
||||||
raw_data->>'Código Postal' as cp,
|
raw_data->>'Código Postal' as cp,
|
||||||
@@ -1296,8 +1334,12 @@ app.get("/public/portal/:token/slots", async (req, res) => {
|
|||||||
raw_data->>'blocked_guild_id' as blocked_guild_id
|
raw_data->>'blocked_guild_id' as blocked_guild_id
|
||||||
FROM scraped_services
|
FROM scraped_services
|
||||||
WHERE assigned_to = $1
|
WHERE assigned_to = $1
|
||||||
AND raw_data->>'scheduled_date' IS NOT NULL
|
AND status != 'archived'
|
||||||
AND raw_data->>'scheduled_date' >= CURRENT_DATE::text
|
AND (
|
||||||
|
(raw_data->>'scheduled_date' IS NOT NULL AND raw_data->>'scheduled_date' >= CURRENT_DATE::text)
|
||||||
|
OR
|
||||||
|
(raw_data->>'appointment_status' = 'pending' AND raw_data->>'requested_date' >= CURRENT_DATE::text)
|
||||||
|
)
|
||||||
`, [assignedTo]);
|
`, [assignedTo]);
|
||||||
|
|
||||||
const agendaMap = {};
|
const agendaMap = {};
|
||||||
@@ -1432,6 +1474,14 @@ app.post("/public/portal/:token/book", async (req, res) => {
|
|||||||
const srv = serviceQ.rows[0];
|
const srv = serviceQ.rows[0];
|
||||||
const raw = srv.raw_data || {};
|
const raw = srv.raw_data || {};
|
||||||
|
|
||||||
|
// 🛡️ ESCUDO: Verificamos que el hueco siga libre (Por si 2 clientes hacen clic en el mismo milisegundo)
|
||||||
|
if (srv.assigned_to) {
|
||||||
|
const solapamiento = await comprobarDisponibilidad(ownerId, srv.assigned_to, date, time, 60, serviceId);
|
||||||
|
if (solapamiento.choca) {
|
||||||
|
return res.status(400).json({ ok: false, error: "Lo sentimos, alguien acaba de reservar ese mismo hueco hace unos instantes. Por favor, elige otro horario." });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Grabamos la solicitud en el jsonb para que el admin la vea en agenda.html
|
// Grabamos la solicitud en el jsonb para que el admin la vea en agenda.html
|
||||||
raw.requested_date = date;
|
raw.requested_date = date;
|
||||||
raw.requested_time = time;
|
raw.requested_time = time;
|
||||||
|
|||||||
Reference in New Issue
Block a user