Overhaul cloud save system: fix destructive bugs, add save management UI
CI / build-and-push (push) Successful in 57s
CI / build-and-push (push) Successful in 57s
Stop 401 responses from wiping local saves and force-reloading. Fix logout race condition with final cloud save before token invalidation. Replace hard 3-failure cap with exponential backoff (2min to 30min). Switch cloud save interval from tick-based (30s) to wall-clock (5min). Add cloud save status indicator and Force Save button in TopBar. Show save conflict dialog on login when both local and cloud saves exist. Add cloud save list, download, and delete in Settings. Server now keeps 10 save snapshots per user instead of overwriting a single save. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,10 +1,12 @@
|
||||
import { Hono } from 'hono';
|
||||
import { eq, and, desc } from 'drizzle-orm';
|
||||
import { eq, and, desc, notInArray } from 'drizzle-orm';
|
||||
import { db } from '../db';
|
||||
import { saves } from '../db/schema';
|
||||
import { authMiddleware } from '../middleware/auth';
|
||||
import type { AppEnv } from '../types';
|
||||
|
||||
const MAX_SAVES_PER_USER = 10;
|
||||
|
||||
const savesRouter = new Hono<AppEnv>();
|
||||
|
||||
savesRouter.use('*', authMiddleware);
|
||||
@@ -68,29 +70,6 @@ savesRouter.put('/', async (c) => {
|
||||
era: string;
|
||||
}>();
|
||||
|
||||
const existing = await db
|
||||
.select({ id: saves.id })
|
||||
.from(saves)
|
||||
.where(eq(saves.userId, userId))
|
||||
.orderBy(desc(saves.updatedAt))
|
||||
.limit(1);
|
||||
|
||||
if (existing.length > 0) {
|
||||
await db
|
||||
.update(saves)
|
||||
.set({
|
||||
companyName: body.companyName,
|
||||
saveVersion: body.saveVersion,
|
||||
gameData: body.gameData,
|
||||
tickCount: body.tickCount,
|
||||
era: body.era,
|
||||
updatedAt: new Date(),
|
||||
})
|
||||
.where(eq(saves.id, existing[0].id));
|
||||
|
||||
return c.json({ id: existing[0].id, updated: true });
|
||||
}
|
||||
|
||||
const [newSave] = await db
|
||||
.insert(saves)
|
||||
.values({
|
||||
@@ -103,6 +82,20 @@ savesRouter.put('/', async (c) => {
|
||||
})
|
||||
.returning({ id: saves.id });
|
||||
|
||||
const keepIds = await db
|
||||
.select({ id: saves.id })
|
||||
.from(saves)
|
||||
.where(eq(saves.userId, userId))
|
||||
.orderBy(desc(saves.updatedAt))
|
||||
.limit(MAX_SAVES_PER_USER);
|
||||
|
||||
const keepSet = keepIds.map((r) => r.id);
|
||||
if (keepSet.length === MAX_SAVES_PER_USER) {
|
||||
await db
|
||||
.delete(saves)
|
||||
.where(and(eq(saves.userId, userId), notInArray(saves.id, keepSet)));
|
||||
}
|
||||
|
||||
return c.json({ id: newSave.id, created: true });
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user