···22 * GENERATED CODE - DO NOT MODIFY
33 */
44import { type ValidationResult, BlobRef } from '@atproto/lexicon'
55-import { CID } from 'multiformats/cid'
55+import { CID } from 'multiformats'
66import { validate as _validate } from '../../../lexicons'
77import { type $Typed, is$typed as _is$typed, type OmitKey } from '../../../util'
88
+8-86
hosting-service/src/lib/db.ts
···11import postgres from 'postgres';
22+import { createHash } from 'crypto';
2334const sql = postgres(
45 process.env.DATABASE_URL || 'postgres://postgres:postgres@localhost:5432/wisp',
···2122 verified: boolean;
2223}
23242424-// In-memory cache with TTL
2525-interface CacheEntry<T> {
2626- data: T;
2727- expiry: number;
2828-}
29253030-const CACHE_TTL_MS = 10 * 60 * 1000; // 10 minutes
3131-3232-class SimpleCache<T> {
3333- private cache = new Map<string, CacheEntry<T>>();
3434-3535- get(key: string): T | null {
3636- const entry = this.cache.get(key);
3737- if (!entry) return null;
3838-3939- if (Date.now() > entry.expiry) {
4040- this.cache.delete(key);
4141- return null;
4242- }
4343-4444- return entry.data;
4545- }
4646-4747- set(key: string, data: T): void {
4848- this.cache.set(key, {
4949- data,
5050- expiry: Date.now() + CACHE_TTL_MS,
5151- });
5252- }
5353-5454- // Periodic cleanup to prevent memory leaks
5555- cleanup(): void {
5656- const now = Date.now();
5757- for (const [key, entry] of this.cache.entries()) {
5858- if (now > entry.expiry) {
5959- this.cache.delete(key);
6060- }
6161- }
6262- }
6363-}
6464-6565-// Create cache instances
6666-const wispDomainCache = new SimpleCache<DomainLookup | null>();
6767-const customDomainCache = new SimpleCache<CustomDomainLookup | null>();
6868-const customDomainHashCache = new SimpleCache<CustomDomainLookup | null>();
6969-7070-// Run cleanup every 5 minutes
7171-setInterval(() => {
7272- wispDomainCache.cleanup();
7373- customDomainCache.cleanup();
7474- customDomainHashCache.cleanup();
7575-}, 5 * 60 * 1000);
76267727export async function getWispDomain(domain: string): Promise<DomainLookup | null> {
7828 const key = domain.toLowerCase();
79298080- // Check cache first
8181- const cached = wispDomainCache.get(key);
8282- if (cached !== null) {
8383- return cached;
8484- }
8585-8630 // Query database
8731 const result = await sql<DomainLookup[]>`
8832 SELECT did, rkey FROM domains WHERE domain = ${key} LIMIT 1
8933 `;
9034 const data = result[0] || null;
91359292- // Store in cache
9393- wispDomainCache.set(key, data);
9494-9536 return data;
9637}
97389839export async function getCustomDomain(domain: string): Promise<CustomDomainLookup | null> {
9940 const key = domain.toLowerCase();
10041101101- // Check cache first
102102- const cached = customDomainCache.get(key);
103103- if (cached !== null) {
104104- return cached;
105105- }
106106-10742 // Query database
10843 const result = await sql<CustomDomainLookup[]>`
10944 SELECT id, domain, did, rkey, verified FROM custom_domains
···11146 `;
11247 const data = result[0] || null;
11348114114- // Store in cache
115115- customDomainCache.set(key, data);
116116-11749 return data;
11850}
1195112052export async function getCustomDomainByHash(hash: string): Promise<CustomDomainLookup | null> {
121121- // Check cache first
122122- const cached = customDomainHashCache.get(hash);
123123- if (cached !== null) {
124124- return cached;
125125- }
126126-12753 // Query database
12854 const result = await sql<CustomDomainLookup[]>`
12955 SELECT id, domain, did, rkey, verified FROM custom_domains
13056 WHERE id = ${hash} AND verified = true LIMIT 1
13157 `;
13258 const data = result[0] || null;
133133-134134- // Store in cache
135135- customDomainHashCache.set(hash, data);
1365913760 return data;
13861}
···16386 * PostgreSQL advisory locks use bigint (64-bit signed integer)
16487 */
16588function stringToLockId(key: string): bigint {
166166- let hash = 0n;
167167- for (let i = 0; i < key.length; i++) {
168168- const char = BigInt(key.charCodeAt(i));
169169- hash = ((hash << 5n) - hash + char) & 0x7FFFFFFFFFFFFFFFn; // Keep within signed int64 range
170170- }
171171- return hash;
8989+ const hash = createHash('sha256').update(key).digest('hex');
9090+ // Take first 16 hex characters (64 bits) and convert to bigint
9191+ const hashNum = BigInt('0x' + hash.substring(0, 16));
9292+ // Keep within signed int64 range
9393+ return hashNum & 0x7FFFFFFFFFFFFFFFn;
17294}
1739517496/**
···180102 const lockId = stringToLockId(key);
181103182104 try {
183183- const result = await sql`SELECT pg_try_advisory_lock(${lockId}) as acquired`;
105105+ const result = await sql`SELECT pg_try_advisory_lock(${Number(lockId)}) as acquired`;
184106 return result[0]?.acquired === true;
185107 } catch (err) {
186108 console.error('Failed to acquire lock', { key, error: err });
···195117 const lockId = stringToLockId(key);
196118197119 try {
198198- await sql`SELECT pg_advisory_unlock(${lockId})`;
120120+ await sql`SELECT pg_advisory_unlock(${Number(lockId)})`;
199121 } catch (err) {
200122 console.error('Failed to release lock', { key, error: err });
201123 }