Files
FEDEO/backend/db/schema/emails.ts
florianfederspiel 21e2bc2755 E-Mail Cache und Konto-Synchronisation vorbereiten
KI-AGENT: Ergänzt Tabellen für lokalen E-Mail-Cache, IMAP-Sync-Service und Inbox-API. Überarbeitet außerdem die E-Mail-Konto-Seiten mit sicherer Passwortbehandlung und manuellem Sync.
2026-05-23 20:00:05 +02:00

209 lines
7.7 KiB
TypeScript

import {
bigint,
boolean,
index,
integer,
jsonb,
pgTable,
text,
timestamp,
uniqueIndex,
uuid,
} from "drizzle-orm/pg-core"
import { tenants } from "./tenants"
import { authUsers } from "./auth_users"
import { userCredentials } from "./user_credentials"
export const emailMailboxes = pgTable(
"email_mailboxes",
{
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade", onUpdate: "cascade" }),
userId: uuid("user_id")
.notNull()
.references(() => authUsers.id, { onDelete: "cascade", onUpdate: "cascade" }),
accountId: uuid("account_id")
.notNull()
.references(() => userCredentials.id, { onDelete: "cascade", onUpdate: "cascade" }),
path: text("path").notNull(),
delimiter: text("delimiter"),
name: text("name").notNull(),
specialUse: text("special_use"),
flags: jsonb("flags").$type<string[]>(),
exists: integer("exists").notNull().default(0),
unseen: integer("unseen").notNull().default(0),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }),
},
(table) => ({
accountPathKey: uniqueIndex("email_mailboxes_account_path_key")
.on(table.accountId, table.path),
tenantAccountIdx: index("email_mailboxes_tenant_account_idx")
.on(table.tenantId, table.accountId),
}),
)
export const emailMessages = pgTable(
"email_messages",
{
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade", onUpdate: "cascade" }),
userId: uuid("user_id")
.notNull()
.references(() => authUsers.id, { onDelete: "cascade", onUpdate: "cascade" }),
accountId: uuid("account_id")
.notNull()
.references(() => userCredentials.id, { onDelete: "cascade", onUpdate: "cascade" }),
mailboxId: uuid("mailbox_id")
.notNull()
.references(() => emailMailboxes.id, { onDelete: "cascade", onUpdate: "cascade" }),
mailboxPath: text("mailbox_path").notNull(),
uid: bigint("uid", { mode: "number" }).notNull(),
emailId: text("email_id"),
messageId: text("message_id"),
inReplyTo: text("in_reply_to"),
threadId: text("thread_id"),
subject: text("subject"),
from: jsonb("from").$type<Array<{ name?: string | null; address?: string | null }>>(),
to: jsonb("to").$type<Array<{ name?: string | null; address?: string | null }>>(),
cc: jsonb("cc").$type<Array<{ name?: string | null; address?: string | null }>>(),
bcc: jsonb("bcc").$type<Array<{ name?: string | null; address?: string | null }>>(),
replyTo: jsonb("reply_to").$type<Array<{ name?: string | null; address?: string | null }>>(),
preview: text("preview"),
flags: jsonb("flags").$type<string[]>(),
seen: boolean("seen").notNull().default(false),
flagged: boolean("flagged").notNull().default(false),
hasAttachments: boolean("has_attachments").notNull().default(false),
size: bigint("size", { mode: "number" }),
sentAt: timestamp("sent_at", { withTimezone: true }),
receivedAt: timestamp("received_at", { withTimezone: true }),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }),
},
(table) => ({
mailboxUidKey: uniqueIndex("email_messages_mailbox_uid_key")
.on(table.mailboxId, table.uid),
accountMailboxIdx: index("email_messages_account_mailbox_idx")
.on(table.accountId, table.mailboxPath),
receivedIdx: index("email_messages_received_idx")
.on(table.receivedAt),
messageIdIdx: index("email_messages_message_id_idx")
.on(table.messageId),
threadIdx: index("email_messages_thread_idx")
.on(table.threadId),
}),
)
export const emailMessageBodies = pgTable("email_message_bodies", {
messageId: uuid("message_id")
.primaryKey()
.references(() => emailMessages.id, { onDelete: "cascade", onUpdate: "cascade" }),
text: text("text"),
html: text("html"),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }),
})
export const emailAttachments = pgTable(
"email_attachments",
{
id: uuid("id").primaryKey().defaultRandom(),
messageId: uuid("message_id")
.notNull()
.references(() => emailMessages.id, { onDelete: "cascade", onUpdate: "cascade" }),
filename: text("filename"),
contentType: text("content_type"),
contentId: text("content_id"),
disposition: text("disposition"),
size: bigint("size", { mode: "number" }),
checksum: text("checksum"),
storageKey: text("storage_key"),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
},
(table) => ({
messageIdx: index("email_attachments_message_idx")
.on(table.messageId),
}),
)
export const emailSyncState = pgTable(
"email_sync_state",
{
id: uuid("id").primaryKey().defaultRandom(),
tenantId: bigint("tenant_id", { mode: "number" })
.notNull()
.references(() => tenants.id, { onDelete: "cascade", onUpdate: "cascade" }),
userId: uuid("user_id")
.notNull()
.references(() => authUsers.id, { onDelete: "cascade", onUpdate: "cascade" }),
accountId: uuid("account_id")
.notNull()
.references(() => userCredentials.id, { onDelete: "cascade", onUpdate: "cascade" }),
mailboxId: uuid("mailbox_id")
.notNull()
.references(() => emailMailboxes.id, { onDelete: "cascade", onUpdate: "cascade" }),
mailboxPath: text("mailbox_path").notNull(),
uidValidity: bigint("uid_validity", { mode: "number" }),
highestUid: bigint("highest_uid", { mode: "number" }).notNull().default(0),
modSeq: text("mod_seq"),
lastSyncedAt: timestamp("last_synced_at", { withTimezone: true }),
syncError: text("sync_error"),
createdAt: timestamp("created_at", { withTimezone: true })
.notNull()
.defaultNow(),
updatedAt: timestamp("updated_at", { withTimezone: true }),
},
(table) => ({
mailboxKey: uniqueIndex("email_sync_state_mailbox_key")
.on(table.accountId, table.mailboxPath),
tenantAccountIdx: index("email_sync_state_tenant_account_idx")
.on(table.tenantId, table.accountId),
}),
)
export type EmailMailbox = typeof emailMailboxes.$inferSelect
export type NewEmailMailbox = typeof emailMailboxes.$inferInsert
export type EmailMessage = typeof emailMessages.$inferSelect
export type NewEmailMessage = typeof emailMessages.$inferInsert
export type EmailMessageBody = typeof emailMessageBodies.$inferSelect
export type NewEmailMessageBody = typeof emailMessageBodies.$inferInsert
export type EmailAttachment = typeof emailAttachments.$inferSelect
export type NewEmailAttachment = typeof emailAttachments.$inferInsert
export type EmailSyncState = typeof emailSyncState.$inferSelect
export type NewEmailSyncState = typeof emailSyncState.$inferInsert