diff --git a/src/db.ts b/src/db.ts index 240b871..2536589 100644 --- a/src/db.ts +++ b/src/db.ts @@ -1,4 +1,7 @@ -import { DenoSqliteDialect, Kysely, Sqlite } from '@/deps.ts'; +import fs from 'node:fs/promises'; +import path from 'node:path'; + +import { DenoSqliteDialect, FileMigrationProvider, Kysely, Migrator, Sqlite } from '@/deps.ts'; interface Tables { events: EventRow; @@ -30,49 +33,22 @@ interface UserRow { inserted_at: Date; } -const sqlite = new Sqlite('data/db.sqlite3'); - -// TODO: move this into a proper migration -sqlite.execute(` - CREATE TABLE IF NOT EXISTS 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 IF NOT EXISTS idx_events_kind ON events(kind); - CREATE INDEX IF NOT EXISTS idx_events_pubkey ON events(pubkey); - - CREATE TABLE IF NOT EXISTS 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 IF NOT EXISTS idx_tags_tag ON tags(tag); - CREATE INDEX IF NOT EXISTS idx_tags_value_1 ON tags(value_1); - CREATE INDEX IF NOT EXISTS idx_tags_event_id ON tags(event_id); - - CREATE TABLE IF NOT EXISTS users ( - pubkey TEXT PRIMARY KEY, - username TEXT NOT NULL, - inserted_at DATETIME DEFAULT CURRENT_TIMESTAMP NOT NULL - ); - - CREATE UNIQUE INDEX IF NOT EXISTS idx_users_username ON users(username); -`); - const db = new Kysely({ dialect: new DenoSqliteDialect({ - database: sqlite, + database: new Sqlite('data/db.sqlite3'), }), }); +const migrator = new Migrator({ + db, + provider: new FileMigrationProvider({ + fs, + path, + migrationFolder: new URL(import.meta.resolve('./db/migrations')).pathname, + }), +}); + +console.log('Running migrations...'); +await migrator.migrateToLatest(); + export { db, type EventRow, type TagRow, type UserRow }; diff --git a/src/db/events.ts b/src/db/events.ts index e9fdf02..560df7f 100644 --- a/src/db/events.ts +++ b/src/db/events.ts @@ -26,9 +26,11 @@ function insertEvent(event: SignedEvent): Promise { return results; }, []); - await trx.insertInto('tags') - .values(tags) - .execute(); + await Promise.all(tags.map((tag) => { + return trx.insertInto('tags') + .values(tag) + .execute(); + })); }); } diff --git a/src/db/migrations/000_create_events.ts b/src/db/migrations/000_create_events.ts new file mode 100644 index 0000000..970edcd --- /dev/null +++ b/src/db/migrations/000_create_events.ts @@ -0,0 +1,66 @@ +import { Kysely, sql } from '@/deps.ts'; + +export async function up(db: Kysely): Promise { + await db.schema + .createTable('events') + .addColumn('id', 'text', (col) => col.primaryKey()) + .addColumn('kind', 'integer', (col) => col.notNull()) + .addColumn('pubkey', 'text', (col) => col.notNull()) + .addColumn('content', 'text', (col) => col.notNull()) + .addColumn('created_at', 'integer', (col) => col.notNull()) + .addColumn('tags', 'text', (col) => col.notNull()) + .addColumn('sig', 'text', (col) => col.notNull()) + .execute(); + + await db.schema + .createTable('tags') + .addColumn('tag', 'text', (col) => col.notNull()) + .addColumn('value_1', 'text') + .addColumn('value_2', 'text') + .addColumn('value_3', 'text') + .addColumn('event_id', 'text', (col) => col.notNull().references('events.id')) + .execute(); + + await db.schema + .createTable('users') + .addColumn('pubkey', 'text', (col) => col.primaryKey()) + .addColumn('username', 'text', (col) => col.notNull().unique()) + .addColumn('inserted_at', 'datetime', (col) => col.notNull().defaultTo(sql`CURRENT_TIMESTAMP`)) + .execute(); + + await db.schema + .createIndex('idx_events_kind') + .on('events') + .column('kind') + .execute(); + + await db.schema + .createIndex('idx_events_pubkey') + .on('events') + .column('pubkey') + .execute(); + + await db.schema + .createIndex('idx_tags_tag') + .on('tags') + .column('tag') + .execute(); + + await db.schema + .createIndex('idx_tags_value_1') + .on('tags') + .column('value_1') + .execute(); + + await db.schema + .createIndex('idx_tags_event_id') + .on('tags') + .column('event_id') + .execute(); +} + +export async function down(db: Kysely): Promise { + await db.schema.dropTable('events').execute(); + await db.schema.dropTable('tags').execute(); + await db.schema.dropTable('users').execute(); +} diff --git a/src/deps.ts b/src/deps.ts index d83e674..12e2baf 100644 --- a/src/deps.ts +++ b/src/deps.ts @@ -50,5 +50,12 @@ export * as secp from 'npm:@noble/secp256k1@^2.0.0'; export { LRUCache } from 'npm:lru-cache@^10.0.0'; export { DB as Sqlite } from 'https://deno.land/x/sqlite@v3.7.3/mod.ts'; export * as dotenv from 'https://deno.land/std@0.197.0/dotenv/mod.ts'; -export { type Insertable, Kysely, type NullableInsertKeys } from 'npm:kysely@^0.25.0'; +export { + FileMigrationProvider, + type Insertable, + Kysely, + Migrator, + type NullableInsertKeys, + sql, +} from 'npm:kysely@^0.25.0'; export { DenoSqliteDialect } from 'https://gitlab.com/soapbox-pub/kysely-deno-sqlite/-/raw/76748303a45fac64a889cd2b9265c6c9b8ef2e8b/mod.ts';