FP
This commit is contained in:
255
packages/server/src/db.ts
Normal file
255
packages/server/src/db.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { DatabaseSync } from 'node:sqlite';
|
||||
import type { LogEntry, LogLevel, ObsSettings, Platform } from '@stream-control/shared';
|
||||
import { DEFAULT_OBS_SETTINGS } from '@stream-control/shared';
|
||||
import { config } from './config.ts';
|
||||
|
||||
fs.mkdirSync(path.dirname(config.dbPath), { recursive: true });
|
||||
|
||||
export const db = new DatabaseSync(config.dbPath);
|
||||
|
||||
db.exec('PRAGMA journal_mode = WAL');
|
||||
db.exec('PRAGMA foreign_keys = ON');
|
||||
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS agents (
|
||||
id TEXT PRIMARY KEY,
|
||||
name TEXT NOT NULL,
|
||||
hostname TEXT,
|
||||
platform TEXT NOT NULL DEFAULT 'unknown',
|
||||
agent_version TEXT,
|
||||
token_hash TEXT NOT NULL,
|
||||
obs_host TEXT NOT NULL DEFAULT '127.0.0.1',
|
||||
obs_port INTEGER NOT NULL DEFAULT 4455,
|
||||
obs_password TEXT NOT NULL DEFAULT '',
|
||||
auto_connect INTEGER NOT NULL DEFAULT 1,
|
||||
notes TEXT,
|
||||
created_at INTEGER NOT NULL,
|
||||
last_seen_at INTEGER
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS logs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
agent_id TEXT,
|
||||
level TEXT NOT NULL,
|
||||
message TEXT NOT NULL,
|
||||
ts INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs (ts DESC);
|
||||
`);
|
||||
|
||||
export interface AgentRow {
|
||||
id: string;
|
||||
name: string;
|
||||
hostname: string | null;
|
||||
platform: string;
|
||||
agent_version: string | null;
|
||||
token_hash: string;
|
||||
obs_host: string;
|
||||
obs_port: number;
|
||||
obs_password: string;
|
||||
auto_connect: number;
|
||||
notes: string | null;
|
||||
created_at: number;
|
||||
last_seen_at: number | null;
|
||||
}
|
||||
|
||||
export interface AgentRecord {
|
||||
id: string;
|
||||
name: string;
|
||||
hostname: string | null;
|
||||
platform: Platform;
|
||||
agentVersion: string | null;
|
||||
tokenHash: string;
|
||||
obs: ObsSettings;
|
||||
autoConnectObs: boolean;
|
||||
notes: string | null;
|
||||
createdAt: number;
|
||||
lastSeenAt: number | null;
|
||||
}
|
||||
|
||||
function toRecord(row: AgentRow): AgentRecord {
|
||||
return {
|
||||
id: row.id,
|
||||
name: row.name,
|
||||
hostname: row.hostname,
|
||||
platform: (row.platform as Platform) ?? 'unknown',
|
||||
agentVersion: row.agent_version,
|
||||
tokenHash: row.token_hash,
|
||||
obs: {
|
||||
host: row.obs_host || DEFAULT_OBS_SETTINGS.host,
|
||||
port: Number(row.obs_port) || DEFAULT_OBS_SETTINGS.port,
|
||||
password: row.obs_password ?? '',
|
||||
},
|
||||
autoConnectObs: Number(row.auto_connect) === 1,
|
||||
notes: row.notes,
|
||||
createdAt: Number(row.created_at),
|
||||
lastSeenAt: row.last_seen_at === null ? null : Number(row.last_seen_at),
|
||||
};
|
||||
}
|
||||
|
||||
const stmts = {
|
||||
listAgents: db.prepare('SELECT * FROM agents ORDER BY name COLLATE NOCASE'),
|
||||
getAgent: db.prepare('SELECT * FROM agents WHERE id = ?'),
|
||||
getAgentByTokenHash: db.prepare('SELECT * FROM agents WHERE token_hash = ?'),
|
||||
insertAgent: db.prepare(`
|
||||
INSERT INTO agents (id, name, hostname, platform, agent_version, token_hash,
|
||||
obs_host, obs_port, obs_password, auto_connect, notes, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
`),
|
||||
updateIdentity: db.prepare(`
|
||||
UPDATE agents SET hostname = ?, platform = ?, agent_version = ?, last_seen_at = ?
|
||||
WHERE id = ?
|
||||
`),
|
||||
updateSettings: db.prepare(`
|
||||
UPDATE agents SET name = ?, obs_host = ?, obs_port = ?, obs_password = ?,
|
||||
auto_connect = ?, notes = ?
|
||||
WHERE id = ?
|
||||
`),
|
||||
touchAgent: db.prepare('UPDATE agents SET last_seen_at = ? WHERE id = ?'),
|
||||
rotateToken: db.prepare('UPDATE agents SET token_hash = ? WHERE id = ?'),
|
||||
deleteAgent: db.prepare('DELETE FROM agents WHERE id = ?'),
|
||||
|
||||
insertLog: db.prepare('INSERT INTO logs (agent_id, level, message, ts) VALUES (?, ?, ?, ?)'),
|
||||
recentLogs: db.prepare(`
|
||||
SELECT l.id, l.agent_id, l.level, l.message, l.ts, a.name AS agent_name
|
||||
FROM logs l LEFT JOIN agents a ON a.id = l.agent_id
|
||||
ORDER BY l.id DESC LIMIT ?
|
||||
`),
|
||||
pruneLogs: db.prepare(`
|
||||
DELETE FROM logs WHERE id NOT IN (SELECT id FROM logs ORDER BY id DESC LIMIT ?)
|
||||
`),
|
||||
};
|
||||
|
||||
export const agentsRepo = {
|
||||
list(): AgentRecord[] {
|
||||
return (stmts.listAgents.all() as unknown as AgentRow[]).map(toRecord);
|
||||
},
|
||||
|
||||
get(id: string): AgentRecord | null {
|
||||
const row = stmts.getAgent.get(id) as unknown as AgentRow | undefined;
|
||||
return row ? toRecord(row) : null;
|
||||
},
|
||||
|
||||
findByTokenHash(tokenHash: string): AgentRecord | null {
|
||||
const row = stmts.getAgentByTokenHash.get(tokenHash) as unknown as AgentRow | undefined;
|
||||
return row ? toRecord(row) : null;
|
||||
},
|
||||
|
||||
create(input: {
|
||||
id: string;
|
||||
name: string;
|
||||
tokenHash: string;
|
||||
hostname?: string | null;
|
||||
platform?: Platform;
|
||||
agentVersion?: string | null;
|
||||
obs?: Partial<ObsSettings>;
|
||||
autoConnectObs?: boolean;
|
||||
notes?: string | null;
|
||||
}): AgentRecord {
|
||||
const obs = { ...DEFAULT_OBS_SETTINGS, ...input.obs };
|
||||
stmts.insertAgent.run(
|
||||
input.id,
|
||||
input.name,
|
||||
input.hostname ?? null,
|
||||
input.platform ?? 'unknown',
|
||||
input.agentVersion ?? null,
|
||||
input.tokenHash,
|
||||
obs.host,
|
||||
obs.port,
|
||||
obs.password,
|
||||
input.autoConnectObs === false ? 0 : 1,
|
||||
input.notes ?? null,
|
||||
Date.now(),
|
||||
);
|
||||
const created = agentsRepo.get(input.id);
|
||||
if (!created) throw new Error(`Échec de création de l'agent ${input.id}`);
|
||||
return created;
|
||||
},
|
||||
|
||||
updateIdentity(
|
||||
id: string,
|
||||
identity: { hostname: string | null; platform: Platform; agentVersion: string | null },
|
||||
): void {
|
||||
stmts.updateIdentity.run(
|
||||
identity.hostname,
|
||||
identity.platform,
|
||||
identity.agentVersion,
|
||||
Date.now(),
|
||||
id,
|
||||
);
|
||||
},
|
||||
|
||||
updateSettings(
|
||||
id: string,
|
||||
settings: {
|
||||
name: string;
|
||||
obs: ObsSettings;
|
||||
autoConnectObs: boolean;
|
||||
notes: string | null;
|
||||
},
|
||||
): void {
|
||||
stmts.updateSettings.run(
|
||||
settings.name,
|
||||
settings.obs.host,
|
||||
settings.obs.port,
|
||||
settings.obs.password,
|
||||
settings.autoConnectObs ? 1 : 0,
|
||||
settings.notes,
|
||||
id,
|
||||
);
|
||||
},
|
||||
|
||||
touch(id: string): void {
|
||||
stmts.touchAgent.run(Date.now(), id);
|
||||
},
|
||||
|
||||
rotateToken(id: string, tokenHash: string): void {
|
||||
stmts.rotateToken.run(tokenHash, id);
|
||||
},
|
||||
|
||||
remove(id: string): void {
|
||||
stmts.deleteAgent.run(id);
|
||||
},
|
||||
};
|
||||
|
||||
interface LogRow {
|
||||
id: number;
|
||||
agent_id: string | null;
|
||||
agent_name: string | null;
|
||||
level: string;
|
||||
message: string;
|
||||
ts: number;
|
||||
}
|
||||
|
||||
export const logsRepo = {
|
||||
append(agentId: string | null, level: LogLevel, message: string, ts = Date.now()): LogEntry {
|
||||
const info = stmts.insertLog.run(agentId, level, message, ts);
|
||||
if (Math.random() < 0.02) stmts.pruneLogs.run(config.logRetention);
|
||||
const agent = agentId ? agentsRepo.get(agentId) : null;
|
||||
return {
|
||||
id: Number(info.lastInsertRowid),
|
||||
agentId,
|
||||
agentName: agent?.name ?? null,
|
||||
level,
|
||||
message,
|
||||
ts,
|
||||
};
|
||||
},
|
||||
|
||||
recent(limit = 200): LogEntry[] {
|
||||
const rows = stmts.recentLogs.all(limit) as unknown as LogRow[];
|
||||
return rows
|
||||
.map((row) => ({
|
||||
id: Number(row.id),
|
||||
agentId: row.agent_id,
|
||||
agentName: row.agent_name,
|
||||
level: row.level as LogLevel,
|
||||
message: row.message,
|
||||
ts: Number(row.ts),
|
||||
}))
|
||||
.reverse();
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user