Files
FEDEO/backend/src/routes/devices/rfid.ts
flfeders 4ffafef3f1
All checks were successful
Build and Release FEDEO / verify (push) Successful in 1m5s
Build and Release FEDEO / build-and-release (push) Successful in 6m53s
feat(admin): archive users and their profiles permanently
2026-10-15 14:22:12 +02:00

123 lines
5.6 KiB
TypeScript

import { randomUUID } from "node:crypto";
import { sql } from "drizzle-orm";
import { TimeService, digest, rows } from "../../modules/time-v2/service";
import { FastifyInstance } from "fastify";
import { and, desc, eq } from "drizzle-orm";
import { authProfiles, devices, stafftimeevents } from "../../../db/schema";
export default async function devicesRFIDRoutes(server: FastifyInstance) {
server.post(
"/rfid/createevent/:terminal_id",
async (req, reply) => {
try {
// 1. Timestamp aus dem Body holen (optional)
const { rfid_id, timestamp, request_id } = req.body as {
request_id?: string,
rfid_id: string,
timestamp?: number // Kann undefined sein (Live) oder Zahl (Offline)
};
const { terminal_id } = req.params as { terminal_id: string };
if (!rfid_id || !terminal_id) {
console.log(`Missing Params`);
return reply.code(400).send(`Missing Params`);
}
// 2. Gerät suchen
const device = await server.db
.select()
.from(devices)
.where(eq(devices.externalId, terminal_id))
.limit(1)
.then(rows => rows[0]);
if (!device) {
console.log(`Device ${terminal_id} not found`);
return reply.code(400).send(`Device ${terminal_id} not found`);
}
// 3. User-Profil suchen
const profile = await server.db
.select()
.from(authProfiles)
.where(
and(
eq(authProfiles.tenant_id, device.tenant),
eq(authProfiles.token_id, rfid_id),
eq(authProfiles.active, true)
)
)
.limit(1)
.then(rows => rows[0]);
if (!profile) {
console.log(`Profile for Token ${rfid_id} not found`);
return reply.code(400).send(`Profile for Token ${rfid_id} not found`);
}
const timeService = new TimeService(server.db);
const actor = { tenant: device.tenant, user: profile.user_id, trusted: true };
if (profile.user_id && await timeService.active(actor, profile.user_id)) {
return await server.db.transaction(async tx => {
await timeService.lock(tx, actor, profile.user_id);
const scoped = new TimeService(tx);
const at = timestamp && timestamp > 0 ? new Date(timestamp * 1000).toISOString() : new Date().toISOString();
const key = `rfid:${digest({ terminal_id, rfid_id, event: request_id || timestamp || randomUUID() })}`;
const [cached] = await rows(tx, sql`SELECT response FROM staff_time_v2_requests WHERE tenant_id=${device.tenant} AND user_id=${profile.user_id}::uuid AND request_id=${key}`);
if (cached) return cached.response;
const current = await scoped.current(actor, profile.user_id);
return scoped.mutate(actor, profile.user_id, key, current
? { action: 'stop', id: current.id, version: current.version, at }
: { action: 'start', at }, 'terminal');
});
}
// 4. Letztes Event suchen (für Status-Toggle Work Start/End)
const lastEvent = await server.db
.select()
.from(stafftimeevents)
.where(and(eq(stafftimeevents.user_id, profile.user_id), eq(stafftimeevents.tenant_id, device.tenant)))
.orderBy(desc(stafftimeevents.eventtime))
.limit(1)
.then(rows => rows[0]);
// 5. Zeitstempel Logik (WICHTIG!)
// Der ESP32 sendet Unix-Timestamp in SEKUNDEN. JS braucht MILLISEKUNDEN.
// Wenn kein Timestamp kommt (0 oder undefined), nehmen wir JETZT.
const actualEventTime = (timestamp && timestamp > 0)
? new Date(timestamp * 1000)
: new Date();
// 6. Event Typ bestimmen (Toggle Logik)
// Falls noch nie gestempelt wurde (lastEvent undefined), fangen wir mit start an.
const nextEventType = (lastEvent?.eventtype === "work_start")
? "work_end"
: "work_start";
const dataToInsert = {
tenant_id: device.tenant,
user_id: profile.user_id,
actortype: "system",
eventtime: actualEventTime, // Hier nutzen wir die berechnete Zeit
eventtype: nextEventType,
source: "TERMINAL" // Habe ich von WEB auf TERMINAL geändert (optional)
};
console.log(`New Event for ${profile.user_id}: ${nextEventType} @ ${actualEventTime.toISOString()}`);
const [created] = await server.db
.insert(stafftimeevents)
//@ts-ignore
.values(dataToInsert)
.returning();
return created;
} catch (err: any) {
console.error(err);
return reply.code(400).send({ error: err.message });
}
}
);
}