db: add migrations; util functions
vi did:web:vt3e.cat
Mon, 25 May 2026 23:10:52 +0100
6 files changed,
345 insertions(+),
34 deletions(-)
M
pkgs/db/drizzle.config.ts
→
pkgs/db/drizzle.config.ts
@@ -3,7 +3,7 @@ import config from "@stealth-developers/config";
export default defineConfig({ out: "./drizzle", - schema: "./src/db/schema.ts", + schema: "./src/schemas/index.ts", dialect: "turso", dbCredentials: { url: config.db.url,
A
pkgs/db/drizzle/20260525174422_youthful_bedlam/migration.sql
@@ -0,0 +1,22 @@
+CREATE TABLE `actors` ( + `id` integer PRIMARY KEY AUTOINCREMENT, + `discord_id` text UNIQUE, + `display_name` text, + `username` text, + `avatar` text, + `created_at` integer NOT NULL, + `updated_at` integer NOT NULL, + `deleted_at` integer +); +--> statement-breakpoint +CREATE TABLE `sessions` ( + `id` text PRIMARY KEY, + `user_id` integer NOT NULL, + `access_token` text NOT NULL, + `refresh_token` text NOT NULL, + `expires_at` integer NOT NULL, + `created_at` integer NOT NULL, + `scope` text, + `token_type` text, + CONSTRAINT `fk_sessions_user_id_actors_id_fk` FOREIGN KEY (`user_id`) REFERENCES `actors`(`id`) +);
A
pkgs/db/drizzle/20260525174422_youthful_bedlam/snapshot.json
@@ -0,0 +1,221 @@
+{ + "version": "7", + "dialect": "sqlite", + "id": "0771a4dc-9b9f-4342-b6cc-7368e6e6ac93", + "prevIds": [ + "00000000-0000-0000-0000-000000000000" + ], + "ddl": [ + { + "name": "actors", + "entityType": "tables" + }, + { + "name": "sessions", + "entityType": "tables" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": true, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "actors" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "discord_id", + "entityType": "columns", + "table": "actors" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "display_name", + "entityType": "columns", + "table": "actors" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "username", + "entityType": "columns", + "table": "actors" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "avatar", + "entityType": "columns", + "table": "actors" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "created_at", + "entityType": "columns", + "table": "actors" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "updated_at", + "entityType": "columns", + "table": "actors" + }, + { + "type": "integer", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "deleted_at", + "entityType": "columns", + "table": "actors" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "id", + "entityType": "columns", + "table": "sessions" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "user_id", + "entityType": "columns", + "table": "sessions" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "access_token", + "entityType": "columns", + "table": "sessions" + }, + { + "type": "text", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "refresh_token", + "entityType": "columns", + "table": "sessions" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "expires_at", + "entityType": "columns", + "table": "sessions" + }, + { + "type": "integer", + "notNull": true, + "autoincrement": false, + "default": null, + "generated": null, + "name": "created_at", + "entityType": "columns", + "table": "sessions" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "scope", + "entityType": "columns", + "table": "sessions" + }, + { + "type": "text", + "notNull": false, + "autoincrement": false, + "default": null, + "generated": null, + "name": "token_type", + "entityType": "columns", + "table": "sessions" + }, + { + "columns": [ + "user_id" + ], + "tableTo": "actors", + "columnsTo": [ + "id" + ], + "onUpdate": "NO ACTION", + "onDelete": "NO ACTION", + "nameExplicit": false, + "name": "fk_sessions_user_id_actors_id_fk", + "entityType": "fks", + "table": "sessions" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "actors_pk", + "table": "actors", + "entityType": "pks" + }, + { + "columns": [ + "id" + ], + "nameExplicit": false, + "name": "sessions_pk", + "table": "sessions", + "entityType": "pks" + }, + { + "columns": [ + "discord_id" + ], + "nameExplicit": false, + "name": "actors_discord_id_unique", + "entityType": "uniques", + "table": "actors" + } + ], + "renames": [] +}
M
pkgs/db/src/index.ts
→
pkgs/db/src/index.ts
@@ -3,11 +3,13 @@ import { drizzle } from "drizzle-orm/libsql";
import config from "@stealth-developers/config"; const client = createClient({ - url: config.db.url, - authToken: config.db.auth, + url: config.db.url, + authToken: config.db.auth, }); const db = drizzle({ client }); export * from "./schemas"; +export * from "./utils"; +export * from "drizzle-orm"; export default db;
M
pkgs/db/src/schemas/index.ts
→
pkgs/db/src/schemas/index.ts
@@ -2,45 +2,45 @@ import { defineRelations } from "drizzle-orm";
import * as s from "drizzle-orm/sqlite-core"; export const actors = s.sqliteTable("actors", { - id: s.integer("id").primaryKey({ autoIncrement: true }), + id: s.integer("id").primaryKey({ autoIncrement: true }), - discordId: s.text("discord_id").unique(), - displayName: s.text("display_name"), - username: s.text("username"), - avatar: s.text("avatar"), + discordId: s.text("discord_id").unique(), + displayName: s.text("display_name"), + username: s.text("username"), + avatar: s.text("avatar"), - createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), - updatedAt: s.integer("updated_at", { mode: "timestamp" }).notNull(), - deletedAt: s.integer("deleted_at", { mode: "timestamp" }), + createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), + updatedAt: s.integer("updated_at", { mode: "timestamp" }).notNull(), + deletedAt: s.integer("deleted_at", { mode: "timestamp" }), }); export const sessions = s.sqliteTable("sessions", { - id: s.text("id").primaryKey(), - userId: s - .integer("user_id") - .notNull() - .references(() => actors.id), + id: s.text("id").primaryKey(), + userId: s + .integer("user_id") + .notNull() + .references(() => actors.id), - accessToken: s.text("access_token").notNull(), - refreshToken: s.text("refresh_token").notNull(), - expiresAt: s.integer("expires_at").notNull(), - createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), + accessToken: s.text("access_token").notNull(), + refreshToken: s.text("refresh_token").notNull(), + expiresAt: s.integer("expires_at", { mode: "timestamp" }).notNull(), + createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), - scope: s.text("scope"), - tokenType: s.text("token_type"), + scope: s.text("scope"), + tokenType: s.text("token_type"), }); export const actorRelationships = defineRelations({ actors, sessions }, (r) => ({ - actors: { - sessions: r.many.sessions({ - from: r.actors.id, - to: r.sessions.userId, - }), - }, - sessions: { - actor: r.one.actors({ - from: r.sessions.userId, - to: r.actors.id, - }), - }, + actors: { + sessions: r.many.sessions({ + from: r.actors.id, + to: r.sessions.userId, + }), + }, + sessions: { + actor: r.one.actors({ + from: r.sessions.userId, + to: r.actors.id, + }), + }, }));
A
pkgs/db/src/utils/index.ts
@@ -0,0 +1,66 @@
+import { eq } from "drizzle-orm"; +import db, { actors, sessions } from ".."; + +export async function getUser(id: number) { + const [result] = await db.select().from(actors).where(eq(actors.id, id)); + return result; +} +export async function getUserByDiscordId(discordId: string) { + const [result] = await db.select().from(actors).where(eq(actors.discordId, discordId)); + return result; +} + +export async function getUserSessions(id: number) { + const result = await db.select().from(sessions).where(eq(sessions.userId, id)); + return result; +} +export async function getSession(id: string) { + const [result] = await db.select().from(sessions).where(eq(sessions.id, id)); + return result; +} + +export async function upsertUser(user: { + username: string; + displayName: string | null; + id: string; + avatar: string | null; +}) { + const [upsertedUser] = await db + .insert(actors) + .values({ + discordId: user.id, + username: user.username, + displayName: user.displayName, + avatar: user.avatar, + + createdAt: new Date(), + updatedAt: new Date(), + }) + .onConflictDoUpdate({ + target: actors.discordId, + set: { + username: user.username, + displayName: user.displayName, + avatar: user.avatar, + updatedAt: new Date(), + }, + }) + .returning(); + + return upsertedUser; +} + +export async function anonymiseUser(id: number) { + const result = await db + .update(actors) + .set({ + username: `Anonymised User`, + displayName: null, + avatar: null, + updatedAt: new Date(), + }) + .where(eq(actors.id, id)) + .returning(); + + return result; +}