import { pgTable, text, boolean, integer, timestamp, index, uniqueIndex, } from 'drizzle-orm/pg-core'; import { sql } from 'drizzle-orm'; import { ports } from './ports'; import { clients } from './clients'; import { files } from './documents'; export const emailAccounts = pgTable( 'email_accounts', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), userId: text('user_id').notNull(), // references Better Auth user ID portId: text('port_id') .notNull() .references(() => ports.id), provider: text('provider').notNull(), // google, outlook, custom emailAddress: text('email_address').notNull(), smtpHost: text('smtp_host').notNull(), smtpPort: integer('smtp_port').notNull(), imapHost: text('imap_host').notNull(), imapPort: integer('imap_port').notNull(), // credentials_enc stored as base64-encoded text (encrypted at application layer) credentialsEnc: text('credentials_enc').notNull(), isActive: boolean('is_active').notNull().default(true), lastSyncAt: timestamp('last_sync_at', { withTimezone: true }), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [index('idx_ea_user').on(table.userId), index('idx_ea_port').on(table.portId)], ); export const emailThreads = pgTable( 'email_threads', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), portId: text('port_id') .notNull() .references(() => ports.id), clientId: text('client_id').references(() => clients.id), subject: text('subject'), lastMessageAt: timestamp('last_message_at', { withTimezone: true }), messageCount: integer('message_count').notNull().default(0), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), updatedAt: timestamp('updated_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [index('idx_et_client').on(table.clientId), index('idx_et_port').on(table.portId)], ); export const emailMessages = pgTable( 'email_messages', { id: text('id') .primaryKey() .$defaultFn(() => crypto.randomUUID()), threadId: text('thread_id') .notNull() .references(() => emailThreads.id, { onDelete: 'cascade' }), messageIdHeader: text('message_id_header'), // email Message-ID header fromAddress: text('from_address').notNull(), toAddresses: text('to_addresses').array().notNull(), ccAddresses: text('cc_addresses').array(), subject: text('subject'), bodyText: text('body_text'), bodyHtml: text('body_html'), direction: text('direction').notNull(), // inbound, outbound sentAt: timestamp('sent_at', { withTimezone: true }).notNull(), attachmentFileIds: text('attachment_file_ids').array(), // references to files table rawFileId: text('raw_file_id').references(() => files.id), createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(), }, (table) => [ index('idx_em_thread').on(table.threadId), uniqueIndex('idx_em_message_id') .on(table.messageIdHeader) .where(sql`${table.messageIdHeader} IS NOT NULL`), ], ); export type EmailAccount = typeof emailAccounts.$inferSelect; export type NewEmailAccount = typeof emailAccounts.$inferInsert; export type EmailThread = typeof emailThreads.$inferSelect; export type NewEmailThread = typeof emailThreads.$inferInsert; export type EmailMessage = typeof emailMessages.$inferSelect; export type NewEmailMessage = typeof emailMessages.$inferInsert;