1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
|
import Database from 'better-sqlite3';
import { resolve } from 'node:path';
// TEMPORARY dev wiring. The API reads directly from the SvelteKit UI's
// SQLite file so "create a key in the UI → use it in the API" works with
// zero infra. This whole module gets replaced when the API moves to
// @tidyindex/core + Drizzle + Neon (or self-hosted Postgres via Hyperdrive).
const DB_PATH = resolve(
process.env.DATABASE_PATH ?? '../ui/data/dashboard.db'
);
export const db = new Database(DB_PATH);
db.pragma('foreign_keys = ON');
export interface ApiKeyLookup {
keyId: string;
name: string;
scopes: string[];
account: {
id: string;
email: string | null;
plan: string;
};
}
const lookupStmt = db.prepare<
[string],
{
key_id: string;
key_name: string;
key_scopes: string;
account_id: string;
account_email: string | null;
account_plan: string;
}
>(`
SELECT
k.id AS key_id,
k.name AS key_name,
k.scopes AS key_scopes,
a.id AS account_id,
a.email AS account_email,
a.plan AS account_plan
FROM api_keys k
JOIN accounts a ON a.id = k.account_id
WHERE k.key_hash = ? AND k.active = 1
`);
const touchStmt = db.prepare<[number, string]>(
`UPDATE api_keys SET last_used_at = ? WHERE id = ?`
);
export function lookupApiKey(hash: string): ApiKeyLookup | null {
const row = lookupStmt.get(hash);
if (!row) return null;
// Update last_used_at so the UI shows the key as active. Cheap,
// synchronous, runs on the hot path — fine for dev; in prod this
// becomes a buffered write on the AccountMeter Durable Object.
touchStmt.run(Date.now(), row.key_id);
return {
keyId: row.key_id,
name: row.key_name,
scopes: JSON.parse(row.key_scopes) as string[],
account: {
id: row.account_id,
email: row.account_email,
plan: row.account_plan
}
};
}
|