Add device diagnostics and P2P access controls

This commit is contained in:
Jonathan Sykes
2026-10-14 02:45:33 +08:00
parent e9a597c415
commit 5177a4d441
11 changed files with 147 additions and 8 deletions

View File

@@ -37,6 +37,10 @@ export async function initP2pSchema() {
fingerprint TEXT,
profile TEXT,
share INTEGER NOT NULL DEFAULT 1,
user_agent TEXT,
banned_at INTEGER,
bytes_up INTEGER NOT NULL DEFAULT 0,
bytes_down INTEGER NOT NULL DEFAULT 0,
created_at INTEGER NOT NULL,
last_seen_at INTEGER NOT NULL
);
@@ -60,6 +64,10 @@ export async function initP2pSchema() {
PRIMARY KEY (video_id, day)
);
`);
for (const [name, decl] of [['user_agent','TEXT'],['banned_at','INTEGER'],['bytes_up','INTEGER NOT NULL DEFAULT 0'],['bytes_down','INTEGER NOT NULL DEFAULT 0']]) {
try { await db.execute(`ALTER TABLE p2p_devices ADD COLUMN ${name} ${decl}`); }
catch (e) { if (!/duplicate column/i.test(String(e.message))) throw e; }
}
// media_cache.sha256 — the cid of the current <id>.<gen>.mp4 (plan 009).
try { await db.execute('ALTER TABLE media_cache ADD COLUMN sha256 TEXT'); }
catch (e) { if (!/duplicate column/i.test(String(e.message))) throw e; }
@@ -117,11 +125,11 @@ export async function revokeContent(cid) {
// ---- devices ------------------------------------------------------------------
export async function createDevice({ deviceId, secretHash, fingerprint, profile, now }) {
export async function createDevice({ deviceId, secretHash, fingerprint, profile, userAgent, now }) {
await db.execute({
sql: `INSERT INTO p2p_devices (device_id, secret_hash, fingerprint, profile, share, created_at, last_seen_at)
VALUES (?, ?, ?, ?, 1, ?, ?)`,
args: [deviceId, secretHash, fingerprint || null, profile || null, now, now],
sql: `INSERT INTO p2p_devices (device_id, secret_hash, fingerprint, profile, share, user_agent, created_at, last_seen_at)
VALUES (?, ?, ?, ?, 1, ?, ?, ?)`,
args: [deviceId, secretHash, fingerprint || null, profile || null, userAgent || null, now, now],
});
}
@@ -139,6 +147,26 @@ export async function touchDevice(deviceId, { now, share, profile } = {}) {
});
}
export async function listAdminDevices({ limit = 100, offset = 0 } = {}) {
const r = await db.execute({ sql: `SELECT d.device_id,d.fingerprint,d.profile,d.share,d.user_agent,d.banned_at,d.bytes_up,d.bytes_down,d.created_at,d.last_seen_at,
COUNT(CASE WHEN h.status='active' THEN 1 END) AS holdings,
COALESCE(SUM(CASE WHEN h.status='active' THEN c.size ELSE 0 END),0) AS holding_bytes
FROM p2p_devices d LEFT JOIN p2p_holders h ON h.device_id=d.device_id LEFT JOIN p2p_content c ON c.cid=h.cid
GROUP BY d.device_id ORDER BY d.last_seen_at DESC LIMIT ? OFFSET ?`, args: [Math.max(1, Math.min(200, limit)), Math.max(0, offset)] });
return rowsOf(r);
}
export async function setDeviceBanned(deviceId, banned, now = Date.now()) {
const r = await db.execute({ sql: 'UPDATE p2p_devices SET banned_at=? WHERE device_id=?', args: [banned ? now : null, deviceId] });
return !!r.rowsAffected;
}
export async function removeDeviceHolders(deviceId, now = Date.now()) {
const r = await db.execute({ sql: "UPDATE p2p_holders SET status='removed',removed_at=? WHERE device_id=? AND status='active'", args: [now, deviceId] });
return Number(r.rowsAffected || 0);
}
export async function recordDeviceTraffic(deviceId, { up = 0, down = 0 } = {}) {
await db.execute({ sql: 'UPDATE p2p_devices SET bytes_up=bytes_up+?,bytes_down=bytes_down+? WHERE device_id=?', args: [Math.max(0, Number(up) || 0), Math.max(0, Number(down) || 0), deviceId] });
}
// ---- holders (persistent; never expired by time) ------------------------------
export async function upsertHolder({ cid, deviceId, trust = 'reported', now }) {