From 91eac97d5cb351edc6d46979ce99b0bc0b62fda8 Mon Sep 17 00:00:00 2001 From: Alex Gleason Date: Sun, 6 Aug 2023 12:54:00 -0500 Subject: [PATCH] Preliminary sqlite db setup --- src/controllers/activitypub/actor.ts | 4 +- src/controllers/well-known/nostr.ts | 31 ++++----- src/controllers/well-known/webfinger.ts | 39 ++++++------ src/db.ts | 84 +++++++++++++++++++++---- 4 files changed, 107 insertions(+), 51 deletions(-) diff --git a/src/controllers/activitypub/actor.ts b/src/controllers/activitypub/actor.ts index 5d78609..0051c95 100644 --- a/src/controllers/activitypub/actor.ts +++ b/src/controllers/activitypub/actor.ts @@ -7,7 +7,9 @@ import type { AppContext, AppController } from '@/app.ts'; const actorController: AppController = async (c) => { const username = c.req.param('username'); - const user = await db.users.findFirst({ where: { username } }); + + const user = db.getUserByUsername(username); + if (!user) return notFound(c); const event = await getAuthor(user.pubkey); if (!event) return notFound(c); diff --git a/src/controllers/well-known/nostr.ts b/src/controllers/well-known/nostr.ts index db46d55..1e6a6d2 100644 --- a/src/controllers/well-known/nostr.ts +++ b/src/controllers/well-known/nostr.ts @@ -10,25 +10,20 @@ const nameSchema = z.string().min(1).regex(/^\w+$/); * Serves NIP-05's nostr.json. * https://github.com/nostr-protocol/nips/blob/master/05.md */ -const nostrController: AppController = async (c) => { - try { - const name = nameSchema.parse(c.req.query('name')); - const user = await db.users.findFirst({ where: { username: name } }); - const relay = Conf.relay; +const nostrController: AppController = (c) => { + const name = nameSchema.safeParse(c.req.query('name')); + const user = name.success ? db.getUserByUsername(name.data) : null; - return c.json({ - names: { - [user.username]: user.pubkey, - }, - relays: relay - ? { - [user.pubkey]: [relay], - } - : {}, - }); - } catch (_e) { - return c.json({ names: {}, relays: {} }); - } + if (!user) return c.json({ names: {}, relays: {} }); + + return c.json({ + names: { + [user.username]: user.pubkey, + }, + relays: { + [user.pubkey]: [Conf.relay], + }, + }); }; export { nostrController }; diff --git a/src/controllers/well-known/webfinger.ts b/src/controllers/well-known/webfinger.ts index 8ee51d2..0f65ef6 100644 --- a/src/controllers/well-known/webfinger.ts +++ b/src/controllers/well-known/webfinger.ts @@ -36,26 +36,27 @@ const acctSchema = z.custom((value) => value instanceof URL) path: ['resource', 'acct'], }); -async function handleAcct(c: AppContext, resource: URL): Promise { - try { - const [username, host] = acctSchema.parse(resource); - const user = await db.users.findFirst({ where: { username } }); - - const json = renderWebfinger({ - pubkey: user.pubkey, - username: user.username, - subject: `acct:${username}@${host}`, - }); - - c.header('content-type', 'application/jrd+json'); - return c.body(JSON.stringify(json)); - } catch (e) { - if (e instanceof z.ZodError) { - return c.json({ error: 'Invalid acct URI', schema: e }, 400); - } else { - return c.json({ error: 'Not found' }, 404); - } +function handleAcct(c: AppContext, resource: URL): Response { + const result = acctSchema.safeParse(resource); + if (!result.success) { + return c.json({ error: 'Invalid acct URI', schema: result.error }, 400); } + + const [username, host] = result.data; + const user = db.getUserByUsername(username); + + if (!user) { + return c.json({ error: 'Not found' }, 404); + } + + const json = renderWebfinger({ + pubkey: user.pubkey, + username: user.username, + subject: `acct:${username}@${host}`, + }); + + c.header('content-type', 'application/jrd+json'); + return c.body(JSON.stringify(json)); } interface RenderWebfingerOpts { diff --git a/src/db.ts b/src/db.ts index 8a22426..ca19517 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,18 +1,76 @@ -import { createPentagon, z } from '@/deps.ts'; -import { hexIdSchema } from '@/schema.ts'; +import { Sqlite } from '@/deps.ts'; -const kv = await Deno.openKv(); +interface User { + pubkey: string; + username: string; + inserted_at: Date; +} -const userSchema = z.object({ - pubkey: hexIdSchema.describe('primary'), - username: z.string().regex(/^\w{1,30}$/).describe('unique'), - createdAt: z.date(), -}); +class DittoDB { + #db: Sqlite; -const db = createPentagon(kv, { - users: { - schema: userSchema, - }, -}); + constructor(db: Sqlite) { + this.#db = db; + this.#db.execute(` + CREATE TABLE events ( + id TEXT PRIMARY KEY, + kind INTEGER NOT NULL, + pubkey TEXT NOT NULL, + content TEXT NOT NULL, + created_at INTEGER NOT NULL, + tags TEXT NOT NULL, + sig TEXT NOT NULL + ); + + CREATE INDEX idx_events_kind ON events(kind); + CREATE INDEX idx_events_pubkey ON events(pubkey); + + CREATE TABLE tags ( + tag TEXT NOT NULL, + value_1 TEXT, + value_2 TEXT, + value_3 TEXT, + event_id TEXT NOT NULL, + FOREIGN KEY(event_id) REFERENCES events(id) ON DELETE CASCADE + ); + + CREATE INDEX idx_tags_tag ON tags(tag); + CREATE INDEX idx_tags_value_1 ON tags(value_1); + CREATE INDEX idx_tags_event_id ON tags(event_id); + + CREATE TABLE users ( + pubkey TEXT PRIMARY KEY, + username TEXT NOT NULL, + inserted_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL + ); + + CREATE UNIQUE INDEX idx_users_username ON users(username); + `); + } + + insertUser(user: Pick): void { + this.#db.query( + 'INSERT INTO users(pubkey, username) VALUES (?, ?)', + [user.pubkey, user.username], + ); + } + + getUserByUsername(username: string): User | null { + const result = this.#db.query<[string, string, Date]>( + 'SELECT pubkey, username, inserted_at FROM users WHERE username = ?', + [username], + )[0]; + if (!result) return null; + return { + pubkey: result[0], + username: result[1], + inserted_at: result[2], + }; + } +} + +const db = new DittoDB( + new Sqlite('data/db.sqlite3'), +); export { db };