all repos — stealth-developers @ cebb512f739021bf604c24a233edccfd2862d236

db: shift to support multiple guilds
vi did:web:vt3e.cat
Wed, 03 Jun 2026 22:24:19 +0100
commit

cebb512f739021bf604c24a233edccfd2862d236

parent

029d0f7869ef4560f712596897a81bd0ef5b39c4

M pkgs/db/src/schemas/actors.tspkgs/db/src/schemas/actors.ts

@@ -1,32 +1,31 @@

import * as s from "drizzle-orm/sqlite-core"; +import { guilds } from "./guilds"; +import { users } from "./users"; -export const actors = s.sqliteTable("actors", { - id: s.integer("id").primaryKey({ autoIncrement: true }), +export const actors = s.sqliteTable( + "actors", + { + 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"), + guildId: s + .integer("guild_id") + .notNull() + .references(() => guilds.id, { onDelete: "cascade" }), + userId: s + .integer("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), - moderator: s.integer("moderator", { mode: "boolean" }), + 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" }), -}); + moderator: s.integer("moderator", { mode: "boolean" }), -export const sessions = s.sqliteTable("sessions", { - 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(), - scope: s.text("scope"), - tokenType: s.text("token_type"), - - expiresAt: s.integer("expires_at", { mode: "timestamp" }).notNull(), - createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), -}); + createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), + updatedAt: s.integer("updated_at", { mode: "timestamp" }).notNull(), + deletedAt: s.integer("deleted_at", { mode: "timestamp" }), + }, + (table) => [s.uniqueIndex("actors_guild_user_idx").on(table.guildId, table.userId)], +);
A pkgs/db/src/schemas/guilds.ts

@@ -0,0 +1,19 @@

+import * as s from "drizzle-orm/sqlite-core"; + +export const guilds = s.sqliteTable("guilds", { + id: s.integer("id").primaryKey({ autoIncrement: true }), + discordId: s.text("discord_id").unique().notNull(), + + name: s.text("name").notNull(), + icon: s.text("icon"), + + ticketCategory: s.text("ticket_category"), + ticketLogChannel: s.text("ticket_log_channel"), + ticketGreeting: s.text("ticket_greeting"), + + ticketPromptChannel: s.text("ticket_prompt_channel"), + ticketPrompt: s.text("ticket_prompt"), + + createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), + updatedAt: s.integer("updated_at", { mode: "timestamp" }).notNull(), +});
M pkgs/db/src/schemas/index.tspkgs/db/src/schemas/index.ts

@@ -1,3 +1,7 @@

+export * from "./users"; export * from "./actors"; + +export * from "./guilds"; export * from "./tickets"; + export * from "./relations";
M pkgs/db/src/schemas/relations.tspkgs/db/src/schemas/relations.ts

@@ -1,22 +1,38 @@

import { defineRelations } from "drizzle-orm"; -import { actors, sessions } from "./actors"; +import { guilds } from "./guilds"; +import { users, sessions } from "./users"; +import { actors } from "./actors"; import { tickets, ticketParticipants, ticketMessages, ticketAttachments } from "./tickets"; export const schemaRelations = defineRelations( { - actors, + guilds, + users, sessions, + actors, tickets, ticketParticipants, ticketMessages, ticketAttachments, }, (r) => ({ + guilds: { + actors: r.many.actors({ from: r.guilds.id, to: r.actors.guildId }), + tickets: r.many.tickets({ from: r.guilds.id, to: r.tickets.guildId }), + }, + + users: { + sessions: r.many.sessions({ from: r.users.id, to: r.sessions.userId }), + actors: r.many.actors({ from: r.users.id, to: r.actors.userId }), + }, + + sessions: { + user: r.one.users({ from: r.sessions.userId, to: r.users.id }), + }, + actors: { - sessions: r.many.sessions({ - from: r.actors.id, - to: r.sessions.userId, - }), + guild: r.one.guilds({ from: r.actors.guildId, to: r.guilds.id }), + user: r.one.users({ from: r.actors.userId, to: r.users.id }), openedTickets: r.many.tickets({ from: r.actors.id,

@@ -47,14 +63,9 @@ to: r.ticketAttachments.actorId,

}), }, - sessions: { - actor: r.one.actors({ - from: r.sessions.userId, - to: r.actors.id, - }), - }, + tickets: { + guild: r.one.guilds({ from: r.tickets.guildId, to: r.guilds.id }), - tickets: { opener: r.one.actors({ from: r.tickets.openedBy, to: r.actors.id,

@@ -70,14 +81,12 @@ from: r.tickets.claimedBy,

to: r.actors.id, alias: "ticket_claimed_by", }), + participants: r.many.ticketParticipants({ from: r.tickets.id, to: r.ticketParticipants.ticketId, }), - messages: r.many.ticketMessages({ - from: r.tickets.id, - to: r.ticketMessages.ticketId, - }), + messages: r.many.ticketMessages({ from: r.tickets.id, to: r.ticketMessages.ticketId }), attachments: r.many.ticketAttachments({ from: r.tickets.id, to: r.ticketAttachments.ticketId,

@@ -85,25 +94,13 @@ }),

}, ticketParticipants: { - tickets: r.one.tickets({ - from: r.ticketParticipants.ticketId, - to: r.tickets.id, - }), - actor: r.one.actors({ - from: r.ticketParticipants.actorId, - to: r.actors.id, - }), + tickets: r.one.tickets({ from: r.ticketParticipants.ticketId, to: r.tickets.id }), + actor: r.one.actors({ from: r.ticketParticipants.actorId, to: r.actors.id }), }, ticketMessages: { - tickets: r.one.tickets({ - from: r.ticketMessages.ticketId, - to: r.tickets.id, - }), - actor: r.one.actors({ - from: r.ticketMessages.actorId, - to: r.actors.id, - }), + tickets: r.one.tickets({ from: r.ticketMessages.ticketId, to: r.tickets.id }), + actor: r.one.actors({ from: r.ticketMessages.actorId, to: r.actors.id }), attachments: r.many.ticketAttachments({ from: r.ticketMessages.id, to: r.ticketAttachments.messageId,

@@ -111,14 +108,8 @@ }),

}, ticketAttachments: { - tickets: r.one.tickets({ - from: r.ticketAttachments.ticketId, - to: r.tickets.id, - }), - actor: r.one.actors({ - from: r.ticketAttachments.actorId, - to: r.actors.id, - }), + tickets: r.one.tickets({ from: r.ticketAttachments.ticketId, to: r.tickets.id }), + actor: r.one.actors({ from: r.ticketAttachments.actorId, to: r.actors.id }), message: r.one.ticketMessages({ from: r.ticketAttachments.messageId, to: r.ticketMessages.id,
M pkgs/db/src/schemas/tickets.tspkgs/db/src/schemas/tickets.ts

@@ -1,49 +1,58 @@

import * as s from "drizzle-orm/sqlite-core"; import { actors } from "./actors"; +import { guilds } from "./guilds"; -export const tickets = s.sqliteTable("tickets", { - id: s.integer("id").primaryKey({ autoIncrement: true }), - anonymousId: s.text("anonymous_id").notNull(), +export const tickets = s.sqliteTable( + "tickets", + { + id: s.text("id").primaryKey(), + guildId: s + .integer("guild_id") + .notNull() + .references(() => guilds.id, { onDelete: "cascade" }), + localId: s.integer("local_id").notNull(), - status: s.text("status").notNull(), - channelId: s.text("channel_id"), + status: s.text("status").notNull(), + channelId: s.text("channel_id"), - closeReason: s.text("close_reason"), - privateReason: s.text("private_reason"), + closeReason: s.text("close_reason"), + privateReason: s.text("private_reason"), - openedAt: s.integer("opened_at", { mode: "timestamp" }).notNull(), - closedAt: s.integer("closed_at", { mode: "timestamp" }), + openedAt: s.integer("opened_at", { mode: "timestamp" }).notNull(), + closedAt: s.integer("closed_at", { mode: "timestamp" }), - /** the actor that opened the ticket, may be the same as the subject */ - openedBy: s - .integer("opened_by") - .notNull() - .references(() => actors.id), - openReason: s.text("open_reason"), + /** the actor that opened the ticket, may be the same as the subject */ + openedBy: s + .integer("opened_by") + .notNull() + .references(() => actors.id), + openReason: s.text("open_reason"), - /** the actor who is the subject of the ticket */ - subject: s - .integer("subject") - .notNull() - .references(() => actors.id), + /** the actor who is the subject of the ticket */ + subject: s + .integer("subject") + .notNull() + .references(() => actors.id), - claimedBy: s.integer("claimed_by").references(() => actors.id), - claimedAt: s.integer("claimed_at", { mode: "timestamp" }), + claimedBy: s.integer("claimed_by").references(() => actors.id), + claimedAt: s.integer("claimed_at", { mode: "timestamp" }), - thankedAt: s.integer("thanked_at", { mode: "timestamp" }), - warnedAt: s.integer("warned_at", { mode: "timestamp" }), - lastMessageAt: s.integer("last_message_at", { mode: "timestamp" }), + thankedAt: s.integer("thanked_at", { mode: "timestamp" }), + warnedAt: s.integer("warned_at", { mode: "timestamp" }), + lastMessageAt: s.integer("last_message_at", { mode: "timestamp" }), - hasModeratorMessage: s.integer("has_moderator_message", { mode: "boolean" }), - hasAuthorMessage: s.integer("has_author_message", { mode: "boolean" }), -}); + hasModeratorMessage: s.integer("has_moderator_message", { mode: "boolean" }), + hasAuthorMessage: s.integer("has_author_message", { mode: "boolean" }), + }, + (table) => [s.uniqueIndex("tickets_guild_local_idx").on(table.guildId, table.localId)], +); export const ticketParticipants = s.sqliteTable( "ticket_participants", { id: s.integer("id").primaryKey({ autoIncrement: true }), ticketId: s - .integer("ticket_id") + .text("ticket_id") .notNull() .references(() => tickets.id, { onDelete: "cascade" }), actorId: s

@@ -71,7 +80,7 @@ publicId: s.text("public_id").notNull(),

messageId: s.text("message_id"), ticketId: s - .integer("ticket_id") + .text("ticket_id") .notNull() .references(() => tickets.id, { onDelete: "cascade" }), actorId: s

@@ -100,7 +109,7 @@ id: s.integer("id").primaryKey({ autoIncrement: true }),

publicId: s.text("public_id").notNull(), messageId: s.integer("message_id").references(() => ticketMessages.id, { onDelete: "cascade" }), ticketId: s - .integer("ticket_id") + .text("ticket_id") .notNull() .references(() => tickets.id, { onDelete: "cascade" }), actorId: s
A pkgs/db/src/schemas/users.ts

@@ -0,0 +1,29 @@

+import * as s from "drizzle-orm/sqlite-core"; + +export const users = s.sqliteTable("users", { + 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"), + + createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), + updatedAt: s.integer("updated_at", { mode: "timestamp" }).notNull(), +}); + +export const sessions = s.sqliteTable("sessions", { + id: s.text("id").primaryKey(), + userId: s + .integer("user_id") + .notNull() + .references(() => users.id, { onDelete: "cascade" }), + + accessToken: s.text("access_token").notNull(), + refreshToken: s.text("refresh_token").notNull(), + scope: s.text("scope"), + tokenType: s.text("token_type"), + + expiresAt: s.integer("expires_at", { mode: "timestamp" }).notNull(), + createdAt: s.integer("created_at", { mode: "timestamp" }).notNull(), +});