Feat : Streamer watch list
This commit is contained in:
@@ -6,13 +6,16 @@ import type {
|
||||
LogLevel,
|
||||
ObsSettings,
|
||||
Platform,
|
||||
StreamState,
|
||||
WatchSettings,
|
||||
WatchTarget,
|
||||
} from '@stream-control/shared';
|
||||
import {
|
||||
DEFAULT_OBS_SETTINGS,
|
||||
DEFAULT_WATCH_SETTINGS,
|
||||
normalizeWatchSettings,
|
||||
safeJsonParse,
|
||||
stripchatProfileUrl,
|
||||
} from '@stream-control/shared';
|
||||
import { config } from './config.ts';
|
||||
|
||||
@@ -49,6 +52,26 @@ db.exec(`
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_logs_ts ON logs (ts DESC);
|
||||
|
||||
-- Profils surveillés : le serveur sonde leur statut et signale les passages
|
||||
-- en direct. Indépendant des agents : on peut veiller sans rien enregistrer.
|
||||
CREATE TABLE IF NOT EXISTS watch_targets (
|
||||
id TEXT PRIMARY KEY,
|
||||
provider TEXT NOT NULL DEFAULT 'stripchat',
|
||||
username TEXT NOT NULL,
|
||||
label TEXT,
|
||||
agent_id TEXT,
|
||||
notify INTEGER NOT NULL DEFAULT 1,
|
||||
state TEXT NOT NULL DEFAULT 'unknown',
|
||||
raw_status TEXT,
|
||||
state_since INTEGER NOT NULL,
|
||||
last_checked_at INTEGER,
|
||||
last_error TEXT,
|
||||
created_at INTEGER NOT NULL
|
||||
);
|
||||
|
||||
CREATE UNIQUE INDEX IF NOT EXISTS idx_targets_identity
|
||||
ON watch_targets (provider, username);
|
||||
`);
|
||||
|
||||
/** Migrations additives : sûres à rejouer sur une base déjà peuplée. */
|
||||
@@ -249,6 +272,136 @@ export const agentsRepo = {
|
||||
},
|
||||
};
|
||||
|
||||
// --- Profils surveillés ------------------------------------------------------
|
||||
|
||||
interface TargetRow {
|
||||
id: string;
|
||||
provider: string;
|
||||
username: string;
|
||||
label: string | null;
|
||||
agent_id: string | null;
|
||||
notify: number;
|
||||
state: string;
|
||||
raw_status: string | null;
|
||||
state_since: number;
|
||||
last_checked_at: number | null;
|
||||
last_error: string | null;
|
||||
created_at: number;
|
||||
}
|
||||
|
||||
function toTarget(row: TargetRow): WatchTarget {
|
||||
return {
|
||||
id: row.id,
|
||||
provider: (row.provider as WatchTarget['provider']) ?? 'stripchat',
|
||||
username: row.username,
|
||||
label: row.label,
|
||||
url: stripchatProfileUrl(row.username),
|
||||
agentId: row.agent_id,
|
||||
notify: Number(row.notify) === 1,
|
||||
state: (row.state as StreamState) ?? 'unknown',
|
||||
rawStatus: row.raw_status,
|
||||
stateSince: Number(row.state_since),
|
||||
lastCheckedAt: row.last_checked_at === null ? null : Number(row.last_checked_at),
|
||||
lastError: row.last_error,
|
||||
createdAt: Number(row.created_at),
|
||||
};
|
||||
}
|
||||
|
||||
const targetStmts = {
|
||||
list: db.prepare('SELECT * FROM watch_targets ORDER BY username COLLATE NOCASE'),
|
||||
get: db.prepare('SELECT * FROM watch_targets WHERE id = ?'),
|
||||
getByName: db.prepare('SELECT * FROM watch_targets WHERE provider = ? AND username = ?'),
|
||||
insert: db.prepare(`
|
||||
INSERT INTO watch_targets (id, provider, username, label, agent_id, notify,
|
||||
state, state_since, created_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, 'unknown', ?, ?)
|
||||
`),
|
||||
updateSettings: db.prepare(
|
||||
'UPDATE watch_targets SET label = ?, agent_id = ?, notify = ? WHERE id = ?',
|
||||
),
|
||||
updateState: db.prepare(`
|
||||
UPDATE watch_targets
|
||||
SET state = ?, raw_status = ?, state_since = ?, last_checked_at = ?, last_error = ?
|
||||
WHERE id = ?
|
||||
`),
|
||||
remove: db.prepare('DELETE FROM watch_targets WHERE id = ?'),
|
||||
};
|
||||
|
||||
export const targetsRepo = {
|
||||
list(): WatchTarget[] {
|
||||
return (targetStmts.list.all() as unknown as TargetRow[]).map(toTarget);
|
||||
},
|
||||
|
||||
get(id: string): WatchTarget | null {
|
||||
const row = targetStmts.get.get(id) as unknown as TargetRow | undefined;
|
||||
return row ? toTarget(row) : null;
|
||||
},
|
||||
|
||||
findByUsername(provider: string, username: string): WatchTarget | null {
|
||||
const row = targetStmts.getByName.get(provider, username) as unknown as TargetRow | undefined;
|
||||
return row ? toTarget(row) : null;
|
||||
},
|
||||
|
||||
create(input: {
|
||||
id: string;
|
||||
username: string;
|
||||
provider?: string;
|
||||
label?: string | null;
|
||||
agentId?: string | null;
|
||||
notify?: boolean;
|
||||
}): WatchTarget {
|
||||
const now = Date.now();
|
||||
targetStmts.insert.run(
|
||||
input.id,
|
||||
input.provider ?? 'stripchat',
|
||||
input.username,
|
||||
input.label ?? null,
|
||||
input.agentId ?? null,
|
||||
input.notify === false ? 0 : 1,
|
||||
now,
|
||||
now,
|
||||
);
|
||||
const created = targetsRepo.get(input.id);
|
||||
if (!created) throw new Error(`Échec de création du profil surveillé ${input.username}`);
|
||||
return created;
|
||||
},
|
||||
|
||||
updateSettings(
|
||||
id: string,
|
||||
settings: { label: string | null; agentId: string | null; notify: boolean },
|
||||
): void {
|
||||
targetStmts.updateSettings.run(
|
||||
settings.label,
|
||||
settings.agentId,
|
||||
settings.notify ? 1 : 0,
|
||||
id,
|
||||
);
|
||||
},
|
||||
|
||||
updateState(
|
||||
id: string,
|
||||
state: {
|
||||
state: StreamState;
|
||||
rawStatus: string | null;
|
||||
stateSince: number;
|
||||
lastError: string | null;
|
||||
},
|
||||
): void {
|
||||
targetStmts.updateState.run(
|
||||
state.state,
|
||||
state.rawStatus,
|
||||
state.stateSince,
|
||||
Date.now(),
|
||||
state.lastError,
|
||||
id,
|
||||
);
|
||||
},
|
||||
|
||||
remove(id: string): void {
|
||||
targetStmts.remove.run(id);
|
||||
},
|
||||
};
|
||||
|
||||
interface LogRow {
|
||||
id: number;
|
||||
agent_id: string | null;
|
||||
|
||||
Reference in New Issue
Block a user