rework pwa cc
Build and Publish Docker Image / build-and-push-image (push) Successful in 1m48s

This commit is contained in:
Zed
2026-07-11 02:01:08 +02:00
parent 9ae37f98ed
commit ce1db937f2
52 changed files with 5210 additions and 48 deletions
+21
View File
@@ -0,0 +1,21 @@
import type { Request, Response } from "express";
import { z } from "zod";
import * as syncService from "./service.js";
const pushSchema = z.object({
locations: z.array(z.any()).optional(),
players: z.array(z.any()).optional(),
sessions: z.array(z.any()).optional(),
});
export async function pull(req: Request, res: Response) {
const since = Number(req.query.since) || 0;
const result = await syncService.pull(req.userId!, since);
res.json(result);
}
export async function push(req: Request, res: Response) {
const payload = pushSchema.parse(req.body);
const result = await syncService.push(req.userId!, payload);
res.json(result);
}
+9
View File
@@ -0,0 +1,9 @@
import { Router } from "express";
import { requireAuth } from "../../middleware/auth.js";
import { asyncHandler } from "../../middleware/errorHandler.js";
import * as controller from "./controller.js";
export const syncRouter = Router();
syncRouter.get("/", requireAuth, asyncHandler(controller.pull));
syncRouter.post("/", requireAuth, asyncHandler(controller.push));
+143
View File
@@ -0,0 +1,143 @@
import { and, eq, gt } from "drizzle-orm";
import { getDb } from "../../db/client.js";
import { locations, players, sessions } from "../../db/schema.js";
// ---- Pull: everything changed since a cursor (tombstones included) ----
export async function pull(ownerId: string, since: number) {
const db = getDb();
const serverTime = Date.now();
const [loc, pl, se] = await Promise.all([
db
.select()
.from(locations)
.where(and(eq(locations.ownerId, ownerId), gt(locations.updatedAt, since))),
db
.select()
.from(players)
.where(and(eq(players.ownerId, ownerId), gt(players.updatedAt, since))),
db
.select()
.from(sessions)
.where(and(eq(sessions.ownerId, ownerId), gt(sessions.updatedAt, since))),
]);
return { serverTime, locations: loc, players: pl, sessions: se };
}
// ---- Push: upsert client records; server clock is authoritative ----
export interface PushPayload {
locations?: any[];
players?: any[];
sessions?: any[];
}
interface Applied {
id: string;
updatedAt: number;
}
export async function push(ownerId: string, payload: PushPayload) {
const db = getDb();
const now = Date.now();
const applied = {
locations: [] as Applied[],
players: [] as Applied[],
sessions: [] as Applied[],
};
await db.transaction(async (tx) => {
for (const r of payload.locations ?? []) {
if (!r?.id) continue;
await tx
.insert(locations)
.values({
id: r.id,
ownerId,
name: r.name ?? "",
address: r.address ?? null,
createdAt: r.createdAt ?? now,
updatedAt: now,
deletedAt: r.deletedAt ?? null,
})
.onConflictDoUpdate({
target: locations.id,
set: {
name: r.name ?? "",
address: r.address ?? null,
updatedAt: now,
deletedAt: r.deletedAt ?? null,
},
});
applied.locations.push({ id: r.id, updatedAt: now });
}
for (const r of payload.players ?? []) {
if (!r?.id) continue;
await tx
.insert(players)
.values({
id: r.id,
ownerId,
name: r.name ?? "",
avatar: r.avatar ?? null,
createdAt: r.createdAt ?? now,
updatedAt: now,
deletedAt: r.deletedAt ?? null,
})
.onConflictDoUpdate({
target: players.id,
set: {
name: r.name ?? "",
avatar: r.avatar ?? null,
updatedAt: now,
deletedAt: r.deletedAt ?? null,
},
});
applied.players.push({ id: r.id, updatedAt: now });
}
for (const r of payload.sessions ?? []) {
if (!r?.id) continue;
await tx
.insert(sessions)
.values({
id: r.id,
ownerId,
gameId: r.gameId ?? "",
dateStart: r.dateStart ?? now,
dateEnd: r.dateEnd ?? null,
players: r.players ?? [],
rounds: r.rounds ?? [],
status: r.status ?? "finished",
options: r.options ?? {},
winnerIds: r.winnerIds ?? null,
locationId: r.locationId ?? null,
createdAt: r.dateStart ?? now,
updatedAt: now,
deletedAt: r.deletedAt ?? null,
})
.onConflictDoUpdate({
target: sessions.id,
set: {
gameId: r.gameId ?? "",
dateStart: r.dateStart ?? now,
dateEnd: r.dateEnd ?? null,
players: r.players ?? [],
rounds: r.rounds ?? [],
status: r.status ?? "finished",
options: r.options ?? {},
winnerIds: r.winnerIds ?? null,
locationId: r.locationId ?? null,
updatedAt: now,
deletedAt: r.deletedAt ?? null,
},
});
applied.sessions.push({ id: r.id, updatedAt: now });
}
});
return { serverTime: now, applied };
}