File
Blob: src/worker/db/schema.ts
| 1 | import { sql } from "drizzle-orm"; |
| 2 | import { index, integer, sqliteTable, text, uniqueIndex } from "drizzle-orm/sqlite-core"; |
| 3 | |
| 4 | export const domains = sqliteTable( |
| 5 | "domains", |
| 6 | { |
| 7 | id: text("id").primaryKey(), |
| 8 | domain: text("domain").notNull().unique(), |
| 9 | isActive: integer("is_active", { mode: "boolean" }).notNull().default(true), |
| 10 | createdAt: integer("created_at", { mode: "timestamp_ms" }) |
| 11 | .notNull() |
| 12 | .default(sql`(unixepoch() * 1000)`), |
| 13 | }, |
| 14 | (table) => [index("idx_domains_active").on(table.isActive)], |
| 15 | ); |
| 16 | |
| 17 | export const inboxes = sqliteTable( |
| 18 | "inboxes", |
| 19 | { |
| 20 | id: text("id").primaryKey(), |
| 21 | localPart: text("local_part").notNull(), |
| 22 | domain: text("domain").notNull(), |
| 23 | fullAddress: text("full_address").notNull().unique(), |
| 24 | isPermanent: integer("is_permanent", { mode: "boolean" }).notNull().default(false), |
| 25 | createdAt: integer("created_at", { mode: "timestamp_ms" }) |
| 26 | .notNull() |
| 27 | .default(sql`(unixepoch() * 1000)`), |
| 28 | expiresAt: integer("expires_at", { mode: "timestamp_ms" }), |
| 29 | }, |
| 30 | (table) => [ |
| 31 | uniqueIndex("idx_inboxes_local_domain").on(table.localPart, table.domain), |
| 32 | index("idx_inboxes_domain").on(table.domain), |
| 33 | index("idx_inboxes_permanent_expires").on(table.isPermanent, table.expiresAt), |
| 34 | index("idx_inboxes_permanent_created").on(table.isPermanent, table.createdAt), |
| 35 | index("idx_inboxes_permanent_domain_local").on(table.isPermanent, table.domain, table.localPart), |
| 36 | ], |
| 37 | ); |
| 38 | |
| 39 | export const emails = sqliteTable( |
| 40 | "emails", |
| 41 | { |
| 42 | id: text("id").primaryKey(), |
| 43 | inboxId: text("inbox_id") |
| 44 | .notNull() |
| 45 | .references(() => inboxes.id, { onDelete: "cascade" }), |
| 46 | recipientAddress: text("recipient_address").notNull().default(""), |
| 47 | fromAddress: text("from_address").notNull(), |
| 48 | fromName: text("from_name"), |
| 49 | subject: text("subject").default("(no subject)"), |
| 50 | receivedAt: integer("received_at", { mode: "timestamp_ms" }) |
| 51 | .notNull() |
| 52 | .default(sql`(unixepoch() * 1000)`), |
| 53 | isRead: integer("is_read", { mode: "boolean" }).notNull().default(false), |
| 54 | sizeBytes: integer("size_bytes").default(0), |
| 55 | hasAttachments: integer("has_attachments", { mode: "boolean" }).notNull().default(false), |
| 56 | bodyKey: text("body_key"), |
| 57 | }, |
| 58 | (table) => [index("idx_emails_inbox_received_id").on(table.inboxId, table.receivedAt, table.id)], |
| 59 | ); |
| 60 | |
| 61 | export const attachments = sqliteTable( |
| 62 | "attachments", |
| 63 | { |
| 64 | id: text("id").primaryKey(), |
| 65 | emailId: text("email_id") |
| 66 | .notNull() |
| 67 | .references(() => emails.id, { onDelete: "cascade" }), |
| 68 | filename: text("filename"), |
| 69 | contentType: text("content_type"), |
| 70 | sizeBytes: integer("size_bytes").default(0), |
| 71 | storageKey: text("storage_key").notNull(), |
| 72 | }, |
| 73 | (table) => [index("idx_attachments_email").on(table.emailId)], |
| 74 | ); |