Added Health Ednpoint for Devices
Added Offline Sync for times
This commit is contained in:
@@ -3,7 +3,7 @@ import {
|
|||||||
uuid,
|
uuid,
|
||||||
timestamp,
|
timestamp,
|
||||||
text,
|
text,
|
||||||
bigint,
|
bigint, jsonb,
|
||||||
} from "drizzle-orm/pg-core"
|
} from "drizzle-orm/pg-core"
|
||||||
|
|
||||||
import { tenants } from "./tenants"
|
import { tenants } from "./tenants"
|
||||||
@@ -23,6 +23,11 @@ export const devices = pgTable("devices", {
|
|||||||
password: text("password"),
|
password: text("password"),
|
||||||
|
|
||||||
externalId: text("externalId"),
|
externalId: text("externalId"),
|
||||||
|
|
||||||
|
lastSeen: timestamp("last_seen", { withTimezone: true }),
|
||||||
|
|
||||||
|
// Hier speichern wir den ganzen Payload (RSSI, Heap, IP, etc.)
|
||||||
|
lastDebugInfo: jsonb("last_debug_info"),
|
||||||
})
|
})
|
||||||
|
|
||||||
export type Device = typeof devices.$inferSelect
|
export type Device = typeof devices.$inferSelect
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import staffTimeRoutesInternal from "./routes/internal/time";
|
|||||||
|
|
||||||
//Devices
|
//Devices
|
||||||
import devicesRFIDRoutes from "./routes/devices/rfid";
|
import devicesRFIDRoutes from "./routes/devices/rfid";
|
||||||
|
import devicesManagementRoutes from "./routes/devices/management";
|
||||||
|
|
||||||
|
|
||||||
import {sendMail} from "./utils/mailer";
|
import {sendMail} from "./utils/mailer";
|
||||||
@@ -71,7 +72,6 @@ async function main() {
|
|||||||
|
|
||||||
// Plugins Global verfügbar
|
// Plugins Global verfügbar
|
||||||
await app.register(swaggerPlugin);
|
await app.register(swaggerPlugin);
|
||||||
await app.register(corsPlugin);
|
|
||||||
await app.register(supabasePlugin);
|
await app.register(supabasePlugin);
|
||||||
await app.register(tenantPlugin);
|
await app.register(tenantPlugin);
|
||||||
await app.register(dayjsPlugin);
|
await app.register(dayjsPlugin);
|
||||||
@@ -116,8 +116,10 @@ async function main() {
|
|||||||
|
|
||||||
await app.register(async (devicesApp) => {
|
await app.register(async (devicesApp) => {
|
||||||
await devicesApp.register(devicesRFIDRoutes)
|
await devicesApp.register(devicesRFIDRoutes)
|
||||||
|
await devicesApp.register(devicesManagementRoutes)
|
||||||
},{prefix: "/devices"})
|
},{prefix: "/devices"})
|
||||||
|
|
||||||
|
await app.register(corsPlugin);
|
||||||
|
|
||||||
//Geschützte Routes
|
//Geschützte Routes
|
||||||
|
|
||||||
|
|||||||
58
backend/src/routes/devices/management.ts
Normal file
58
backend/src/routes/devices/management.ts
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
import { FastifyInstance } from "fastify";
|
||||||
|
import { eq } from "drizzle-orm";
|
||||||
|
import { db } from "../../../db"; // <--- PFAD ZUR DB INSTANZ ANPASSEN
|
||||||
|
import { devices } from "../../../db/schema";
|
||||||
|
|
||||||
|
// Definition, was wir vom ESP32 erwarten
|
||||||
|
interface HealthBody {
|
||||||
|
terminal_id: string;
|
||||||
|
ip_address?: string;
|
||||||
|
wifi_rssi?: number;
|
||||||
|
uptime_seconds?: number;
|
||||||
|
heap_free?: number;
|
||||||
|
[key: string]: any; // Erlaubt weitere Felder
|
||||||
|
}
|
||||||
|
|
||||||
|
export default async function devicesManagementRoutes(server: FastifyInstance) {
|
||||||
|
server.post<{ Body: HealthBody }>(
|
||||||
|
"/health",
|
||||||
|
async (req, reply) => {
|
||||||
|
try {
|
||||||
|
const data = req.body;
|
||||||
|
|
||||||
|
// 1. Validierung: Haben wir eine ID?
|
||||||
|
if (!data.terminal_id) {
|
||||||
|
console.warn("Health Check ohne terminal_id empfangen:", data);
|
||||||
|
return reply.code(400).send({ error: "terminal_id missing" });
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`Health Ping von Device ${data.terminal_id}`, data);
|
||||||
|
|
||||||
|
// 2. Datenbank Update
|
||||||
|
// Wir suchen das Gerät mit der passenden externalId
|
||||||
|
const result = await db
|
||||||
|
.update(devices)
|
||||||
|
.set({
|
||||||
|
lastSeen: new Date(), // Setzt Zeit auf JETZT
|
||||||
|
lastDebugInfo: data // Speichert das ganze JSON
|
||||||
|
})
|
||||||
|
.where(eq(devices.externalId, data.terminal_id))
|
||||||
|
.returning({ id: devices.id }); // Gibt ID zurück, falls gefunden
|
||||||
|
|
||||||
|
// 3. Checken ob Gerät gefunden wurde
|
||||||
|
if (result.length === 0) {
|
||||||
|
console.warn(`Unbekanntes Terminal versucht Health Check: ${data.terminal_id}`);
|
||||||
|
// Optional: 404 senden oder ignorieren (Sicherheit)
|
||||||
|
return reply.code(404).send({ error: "Device not found" });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alles OK
|
||||||
|
return reply.code(200).send({ status: "ok" });
|
||||||
|
|
||||||
|
} catch (err: any) {
|
||||||
|
console.error("Health Check Error:", err);
|
||||||
|
return reply.code(500).send({ error: err.message });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -1,37 +1,39 @@
|
|||||||
import { FastifyInstance } from "fastify";
|
import { FastifyInstance } from "fastify";
|
||||||
import {and, desc, eq} from "drizzle-orm";
|
import { and, desc, eq } from "drizzle-orm";
|
||||||
import {authProfiles, devices, stafftimeevents} from "../../../db/schema";
|
import { authProfiles, devices, stafftimeevents } from "../../../db/schema";
|
||||||
|
|
||||||
export default async function devicesRFIDRoutes(server: FastifyInstance) {
|
export default async function devicesRFIDRoutes(server: FastifyInstance) {
|
||||||
server.post(
|
server.post(
|
||||||
"/rfid/createevent/:terminal_id",
|
"/rfid/createevent/:terminal_id",
|
||||||
async (req, reply) => {
|
async (req, reply) => {
|
||||||
try {
|
try {
|
||||||
|
// 1. Timestamp aus dem Body holen (optional)
|
||||||
|
const { rfid_id, timestamp } = req.body as {
|
||||||
|
rfid_id: string,
|
||||||
|
timestamp?: number // Kann undefined sein (Live) oder Zahl (Offline)
|
||||||
|
};
|
||||||
|
|
||||||
const {rfid_id} = req.body as {rfid_id: string};
|
const { terminal_id } = req.params as { terminal_id: string };
|
||||||
const {terminal_id} = req.params as {terminal_id: string};
|
|
||||||
|
|
||||||
if(!rfid_id ||!terminal_id) {
|
if (!rfid_id || !terminal_id) {
|
||||||
console.log(`Missing Params`);
|
console.log(`Missing Params`);
|
||||||
return reply.code(400).send(`Missing Params`)
|
return reply.code(400).send(`Missing Params`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Gerät suchen
|
||||||
const device = await server.db
|
const device = await server.db
|
||||||
.select()
|
.select()
|
||||||
.from(devices)
|
.from(devices)
|
||||||
.where(
|
.where(eq(devices.externalId, terminal_id))
|
||||||
eq(devices.externalId, terminal_id)
|
|
||||||
|
|
||||||
)
|
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.then(rows => rows[0]);
|
.then(rows => rows[0]);
|
||||||
|
|
||||||
if(!device) {
|
if (!device) {
|
||||||
console.log(`Device ${terminal_id} not found`);
|
console.log(`Device ${terminal_id} not found`);
|
||||||
return reply.code(400).send(`Device ${terminal_id} not found`)
|
return reply.code(400).send(`Device ${terminal_id} not found`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 3. User-Profil suchen
|
||||||
const profile = await server.db
|
const profile = await server.db
|
||||||
.select()
|
.select()
|
||||||
.from(authProfiles)
|
.from(authProfiles)
|
||||||
@@ -44,55 +46,56 @@ export default async function devicesRFIDRoutes(server: FastifyInstance) {
|
|||||||
.limit(1)
|
.limit(1)
|
||||||
.then(rows => rows[0]);
|
.then(rows => rows[0]);
|
||||||
|
|
||||||
if(!profile) {
|
if (!profile) {
|
||||||
console.log(`Profile for Token ${rfid_id} not found`);
|
console.log(`Profile for Token ${rfid_id} not found`);
|
||||||
return reply.code(400).send(`Profile for Token ${rfid_id} not found`)
|
return reply.code(400).send(`Profile for Token ${rfid_id} not found`);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 4. Letztes Event suchen (für Status-Toggle Work Start/End)
|
||||||
const lastEvent = await server.db
|
const lastEvent = await server.db
|
||||||
.select()
|
.select()
|
||||||
.from(stafftimeevents)
|
.from(stafftimeevents)
|
||||||
.where(
|
.where(eq(stafftimeevents.user_id, profile.user_id))
|
||||||
eq(stafftimeevents.user_id, profile.user_id)
|
.orderBy(desc(stafftimeevents.eventtime))
|
||||||
)
|
|
||||||
.orderBy(desc(stafftimeevents.eventtime)) // <-- Sortierung: Neuestes zuerst
|
|
||||||
.limit(1)
|
.limit(1)
|
||||||
.then(rows => rows[0]);
|
.then(rows => rows[0]);
|
||||||
|
|
||||||
console.log(lastEvent)
|
// 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 = {
|
const dataToInsert = {
|
||||||
tenant_id: device.tenant,
|
tenant_id: device.tenant,
|
||||||
user_id: profile.user_id,
|
user_id: profile.user_id,
|
||||||
actortype: "system",
|
actortype: "system",
|
||||||
eventtime: new Date(),
|
eventtime: actualEventTime, // Hier nutzen wir die berechnete Zeit
|
||||||
eventtype: lastEvent.eventtype === "work_start" ? "work_end" : "work_start",
|
eventtype: nextEventType,
|
||||||
source: "WEB"
|
source: "TERMINAL" // Habe ich von WEB auf TERMINAL geändert (optional)
|
||||||
}
|
};
|
||||||
|
|
||||||
console.log(dataToInsert)
|
console.log(`New Event for ${profile.user_id}: ${nextEventType} @ ${actualEventTime.toISOString()}`);
|
||||||
|
|
||||||
const [created] = await server.db
|
const [created] = await server.db
|
||||||
.insert(stafftimeevents)
|
.insert(stafftimeevents)
|
||||||
//@ts-ignore
|
//@ts-ignore
|
||||||
.values(dataToInsert)
|
.values(dataToInsert)
|
||||||
.returning()
|
.returning();
|
||||||
|
|
||||||
|
return created;
|
||||||
|
|
||||||
return created
|
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error(err)
|
console.error(err);
|
||||||
return reply.code(400).send({ error: err.message })
|
return reply.code(400).send({ error: err.message });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
console.log(req.body)
|
|
||||||
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user