fix(settings, yjs, ux, ci): default settings, y-leveldb restart resilience, dashboard one-click open, delete error handling, Playwright CI
This commit is contained in:
@@ -25,16 +25,15 @@ async function main() {
|
||||
}
|
||||
|
||||
// Graceful shutdown
|
||||
process.on('SIGTERM', async () => {
|
||||
const shutdown = async (signal: string) => {
|
||||
console.log(`Received ${signal}, shutting down gracefully...`);
|
||||
await fastify.close();
|
||||
await closeYjsPersistence();
|
||||
db.close();
|
||||
process.exit(0);
|
||||
});
|
||||
process.on('SIGINT', async () => {
|
||||
await fastify.close();
|
||||
db.close();
|
||||
process.exit(0);
|
||||
});
|
||||
};
|
||||
process.on('SIGTERM', () => shutdown('SIGTERM'));
|
||||
process.on('SIGINT', () => shutdown('SIGINT'));
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
@@ -7,6 +7,13 @@ import { requireAuth } from '../auth/authMiddleware.js';
|
||||
import type { AuthService } from '../auth/AuthService.js';
|
||||
import { validateSettingsKey, validateSettingsValue } from '../utils/validation.js';
|
||||
|
||||
/** Built-in default settings so the frontend never sees a 404 for known keys. */
|
||||
const DEFAULT_SETTINGS: Record<string, string> = {
|
||||
'cad.unit': 'mm',
|
||||
'cad.gridSize': '20',
|
||||
'cad.scaleFactor': '1',
|
||||
};
|
||||
|
||||
export function registerSettingsRoutes(fastify: FastifyInstance, db: DatabaseInterface, authService: AuthService) {
|
||||
// Get a single setting by key
|
||||
fastify.get('/api/settings/:key', async (request, reply) => {
|
||||
@@ -15,8 +22,12 @@ export function registerSettingsRoutes(fastify: FastifyInstance, db: DatabaseInt
|
||||
const keyErr = validateSettingsKey(key);
|
||||
if (keyErr) return reply.code(400).send({ error: keyErr });
|
||||
const setting = db.getSetting(key);
|
||||
if (!setting) return reply.code(404).send({ error: 'Setting not found' });
|
||||
return setting;
|
||||
if (setting) return setting;
|
||||
// Return a default value for known CAD settings instead of 404
|
||||
if (DEFAULT_SETTINGS[key] !== undefined) {
|
||||
return { key, value: DEFAULT_SETTINGS[key], updated_at: new Date().toISOString() };
|
||||
}
|
||||
return reply.code(404).send({ error: 'Setting not found' });
|
||||
});
|
||||
|
||||
// Create or update a setting (upsert)
|
||||
|
||||
@@ -28,25 +28,54 @@ let persistence: LeveldbPersistence | null = null;
|
||||
let persistenceReady: Promise<void> | null = null;
|
||||
|
||||
export async function initYjsPersistence(): Promise<void> {
|
||||
try {
|
||||
persistence = new LeveldbPersistence(PERSISTENCE_DIR);
|
||||
// Set persistenceReady so getOrCreateDoc can await it
|
||||
persistenceReady = (async () => {
|
||||
// Wait for LevelDB to be ready
|
||||
await (persistence as any).getAllDocNames();
|
||||
})();
|
||||
await persistenceReady;
|
||||
console.log(`Yjs persistence initialized at ${PERSISTENCE_DIR}`);
|
||||
} catch (err) {
|
||||
console.warn('Yjs persistence failed to init (non-fatal):', err);
|
||||
}
|
||||
const tryInit = async (allowReset: boolean): Promise<void> => {
|
||||
try {
|
||||
persistence = new LeveldbPersistence(PERSISTENCE_DIR);
|
||||
persistenceReady = (async () => {
|
||||
// Wait for LevelDB to be ready
|
||||
await (persistence as any).getAllDocNames();
|
||||
})();
|
||||
await persistenceReady;
|
||||
console.log(`Yjs persistence initialized at ${PERSISTENCE_DIR}`);
|
||||
} catch (err) {
|
||||
// In dev mode the persistence dir lives under /tmp. If the previous process
|
||||
// did not shut down cleanly, LevelDB can be left in a locked/corrupted state.
|
||||
// Resetting the directory is acceptable because collaboration history is ephemeral.
|
||||
if (allowReset && PERSISTENCE_DIR.startsWith('/tmp/')) {
|
||||
console.warn(`Yjs persistence init failed, resetting ${PERSISTENCE_DIR} and retrying...`);
|
||||
try {
|
||||
if (persistence) {
|
||||
await persistence.destroy().catch(() => {});
|
||||
persistence = null;
|
||||
persistenceReady = null;
|
||||
}
|
||||
const fs = await import('fs');
|
||||
fs.rmSync(PERSISTENCE_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(PERSISTENCE_DIR, { recursive: true });
|
||||
} catch (cleanupErr) {
|
||||
console.warn('Yjs persistence cleanup failed:', cleanupErr);
|
||||
}
|
||||
return tryInit(false);
|
||||
}
|
||||
console.warn('Yjs persistence failed to init (running without disk persistence):', err);
|
||||
persistence = null;
|
||||
persistenceReady = null;
|
||||
}
|
||||
};
|
||||
|
||||
await tryInit(true);
|
||||
}
|
||||
|
||||
export async function closeYjsPersistence(): Promise<void> {
|
||||
if (persistence) {
|
||||
await persistence.destroy();
|
||||
try {
|
||||
await persistence.destroy();
|
||||
} catch (err) {
|
||||
console.warn('Yjs persistence close failed (non-fatal):', err);
|
||||
}
|
||||
persistence = null;
|
||||
}
|
||||
persistenceReady = null;
|
||||
}
|
||||
|
||||
async function getOrCreateDoc(docName: string): Promise<Y.Doc> {
|
||||
|
||||
Reference in New Issue
Block a user