Files
pn-new-crm/src/lib/db/schema/email.ts
Matt 0e8feb1073 chore: prettier format pass on branch files
Auto-format all files modified during the documents-hub-split feature
branch that were not yet aligned with the project's Prettier config
(single quotes, semicolons, trailing commas).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-11 13:01:47 +02:00

96 lines
3.6 KiB
TypeScript

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;