Initial commit: Port Nimara CRM (Layers 0-4)
Full CRM rebuild with Next.js 15, TypeScript, Tailwind, Drizzle ORM, PostgreSQL, Redis, BullMQ, MinIO, and Socket.io. Includes 461 source files covering clients, berths, interests/pipeline, documents/EOI, expenses/invoices, email, notifications, dashboard, admin, and client portal. CI/CD via Gitea Actions with Docker builds. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
95
src/lib/db/schema/email.ts
Normal file
95
src/lib/db/schema/email.ts
Normal file
@@ -0,0 +1,95 @@
|
||||
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;
|
||||
Reference in New Issue
Block a user