feat: add server and frontend initial - wood chopping

This commit is contained in:
2026-09-05 16:37:45 +05:00
parent c79681dc59
commit f6c338e1b0
28 changed files with 1857 additions and 195 deletions
+56
View File
@@ -0,0 +1,56 @@
import { mkdirSync } from 'node:fs';
import path from 'node:path';
import { DatabaseSync } from 'node:sqlite';
import type { ViewerRecord } from '../sim/world';
/**
* Тонкий слой persist на встроенном node:sqlite — без нативных сборок и
* install-скриптов. При необходимости заменяется (better-sqlite3/postgres)
* без изменений в остальном коде.
*/
export class Db {
private readonly db: DatabaseSync;
constructor(dbPath: string) {
if (dbPath !== ':memory:') {
mkdirSync(path.dirname(dbPath), { recursive: true });
}
this.db = new DatabaseSync(dbPath);
this.db.exec('PRAGMA journal_mode = WAL');
this.db.exec(`
CREATE TABLE IF NOT EXISTS viewers (
channel_id TEXT NOT NULL,
user_id TEXT NOT NULL,
name TEXT NOT NULL,
color TEXT NOT NULL,
xp INTEGER NOT NULL DEFAULT 0,
logs INTEGER NOT NULL DEFAULT 0,
updated_at INTEGER NOT NULL,
PRIMARY KEY (channel_id, user_id)
)
`);
}
loadViewers(channelId: string): ViewerRecord[] {
const rows = this.db
.prepare('SELECT user_id, name, color, xp, logs FROM viewers WHERE channel_id = ?')
.all(channelId) as Array<{ user_id: string; name: string; color: string; xp: number; logs: number }>;
return rows.map((r) => ({ userId: r.user_id, name: r.name, color: r.color, xp: r.xp, logs: r.logs }));
}
saveViewer(channelId: string, v: ViewerRecord): void {
this.db
.prepare(
`INSERT INTO viewers (channel_id, user_id, name, color, xp, logs, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?)
ON CONFLICT(channel_id, user_id) DO UPDATE SET
name = excluded.name, color = excluded.color, xp = excluded.xp,
logs = excluded.logs, updated_at = excluded.updated_at`,
)
.run(channelId, v.userId, v.name, v.color, v.xp, v.logs, Date.now());
}
close(): void {
this.db.close();
}
}