option for more emails,fix jmap service,add table email accounts

This commit is contained in:
richardtekula
2025-11-19 13:15:45 +01:00
parent 97f437c1c4
commit 1e7c1eab90
18 changed files with 1991 additions and 1299 deletions

View File

@@ -0,0 +1,17 @@
CREATE TABLE "email_accounts" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"email" text NOT NULL,
"email_password" text NOT NULL,
"jmap_account_id" text NOT NULL,
"is_primary" boolean DEFAULT false NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
);
--> statement-breakpoint
ALTER TABLE "contacts" ADD COLUMN "email_account_id" uuid NOT NULL;--> statement-breakpoint
ALTER TABLE "emails" ADD COLUMN "email_account_id" uuid NOT NULL;--> statement-breakpoint
ALTER TABLE "email_accounts" ADD CONSTRAINT "email_accounts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "contacts" ADD CONSTRAINT "contacts_email_account_id_email_accounts_id_fk" FOREIGN KEY ("email_account_id") REFERENCES "public"."email_accounts"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
ALTER TABLE "emails" ADD CONSTRAINT "emails_email_account_id_email_accounts_id_fk" FOREIGN KEY ("email_account_id") REFERENCES "public"."email_accounts"("id") ON DELETE cascade ON UPDATE no action;

View File

@@ -0,0 +1,600 @@
{
"id": "0a729a36-e7a3-488d-b9c5-26392e1cc67d",
"prevId": "1b8c1e0f-8476-470c-a641-b3c350a2c1a4",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.audit_logs": {
"name": "audit_logs",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"action": {
"name": "action",
"type": "text",
"primaryKey": false,
"notNull": true
},
"resource": {
"name": "resource",
"type": "text",
"primaryKey": false,
"notNull": true
},
"resource_id": {
"name": "resource_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"old_value": {
"name": "old_value",
"type": "text",
"primaryKey": false,
"notNull": false
},
"new_value": {
"name": "new_value",
"type": "text",
"primaryKey": false,
"notNull": false
},
"ip_address": {
"name": "ip_address",
"type": "text",
"primaryKey": false,
"notNull": false
},
"user_agent": {
"name": "user_agent",
"type": "text",
"primaryKey": false,
"notNull": false
},
"success": {
"name": "success",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"error_message": {
"name": "error_message",
"type": "text",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"audit_logs_user_id_users_id_fk": {
"name": "audit_logs_user_id_users_id_fk",
"tableFrom": "audit_logs",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "set null",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.contacts": {
"name": "contacts",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"email_account_id": {
"name": "email_account_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"name": {
"name": "name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"notes": {
"name": "notes",
"type": "text",
"primaryKey": false,
"notNull": false
},
"added_at": {
"name": "added_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"contacts_user_id_users_id_fk": {
"name": "contacts_user_id_users_id_fk",
"tableFrom": "contacts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"contacts_email_account_id_email_accounts_id_fk": {
"name": "contacts_email_account_id_email_accounts_id_fk",
"tableFrom": "contacts",
"tableTo": "email_accounts",
"columnsFrom": [
"email_account_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.email_accounts": {
"name": "email_accounts",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email_password": {
"name": "email_password",
"type": "text",
"primaryKey": false,
"notNull": true
},
"jmap_account_id": {
"name": "jmap_account_id",
"type": "text",
"primaryKey": false,
"notNull": true
},
"is_primary": {
"name": "is_primary",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"is_active": {
"name": "is_active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"email_accounts_user_id_users_id_fk": {
"name": "email_accounts_user_id_users_id_fk",
"tableFrom": "email_accounts",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.emails": {
"name": "emails",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"user_id": {
"name": "user_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"email_account_id": {
"name": "email_account_id",
"type": "uuid",
"primaryKey": false,
"notNull": true
},
"contact_id": {
"name": "contact_id",
"type": "uuid",
"primaryKey": false,
"notNull": false
},
"jmap_id": {
"name": "jmap_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"message_id": {
"name": "message_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"thread_id": {
"name": "thread_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"in_reply_to": {
"name": "in_reply_to",
"type": "text",
"primaryKey": false,
"notNull": false
},
"from": {
"name": "from",
"type": "text",
"primaryKey": false,
"notNull": false
},
"to": {
"name": "to",
"type": "text",
"primaryKey": false,
"notNull": false
},
"subject": {
"name": "subject",
"type": "text",
"primaryKey": false,
"notNull": false
},
"body": {
"name": "body",
"type": "text",
"primaryKey": false,
"notNull": false
},
"is_read": {
"name": "is_read",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": false
},
"date": {
"name": "date",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"emails_user_id_users_id_fk": {
"name": "emails_user_id_users_id_fk",
"tableFrom": "emails",
"tableTo": "users",
"columnsFrom": [
"user_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"emails_email_account_id_email_accounts_id_fk": {
"name": "emails_email_account_id_email_accounts_id_fk",
"tableFrom": "emails",
"tableTo": "email_accounts",
"columnsFrom": [
"email_account_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
},
"emails_contact_id_contacts_id_fk": {
"name": "emails_contact_id_contacts_id_fk",
"tableFrom": "emails",
"tableTo": "contacts",
"columnsFrom": [
"contact_id"
],
"columnsTo": [
"id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"emails_jmap_id_unique": {
"name": "emails_jmap_id_unique",
"nullsNotDistinct": false,
"columns": [
"jmap_id"
]
},
"emails_message_id_unique": {
"name": "emails_message_id_unique",
"nullsNotDistinct": false,
"columns": [
"message_id"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "uuid",
"primaryKey": true,
"notNull": true,
"default": "gen_random_uuid()"
},
"username": {
"name": "username",
"type": "text",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "text",
"primaryKey": false,
"notNull": false
},
"email_password": {
"name": "email_password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"jmap_account_id": {
"name": "jmap_account_id",
"type": "text",
"primaryKey": false,
"notNull": false
},
"first_name": {
"name": "first_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"last_name": {
"name": "last_name",
"type": "text",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"temp_password": {
"name": "temp_password",
"type": "text",
"primaryKey": false,
"notNull": false
},
"changed_password": {
"name": "changed_password",
"type": "boolean",
"primaryKey": false,
"notNull": false,
"default": false
},
"role": {
"name": "role",
"type": "role",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'member'"
},
"last_login": {
"name": "last_login",
"type": "timestamp",
"primaryKey": false,
"notNull": false
},
"created_at": {
"name": "created_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_username_unique": {
"name": "users_username_unique",
"nullsNotDistinct": false,
"columns": [
"username"
]
},
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.role": {
"name": "role",
"schema": "public",
"values": [
"admin",
"member"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}

View File

@@ -15,6 +15,13 @@
"when": 1763457837858,
"tag": "0001_slow_drax",
"breakpoints": true
},
{
"idx": 2,
"version": "7",
"when": 1763547133084,
"tag": "0002_parallel_guardian",
"breakpoints": true
}
]
}

View File

@@ -0,0 +1,138 @@
import 'dotenv/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import pkg from 'pg';
const { Pool } = pkg;
import { sql } from 'drizzle-orm';
/**
* Data-only migration script to move existing user emails to email_accounts
* Assumes tables already exist from Drizzle migrations
*/
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
user: process.env.DB_USER || 'admin',
password: process.env.DB_PASSWORD || 'heslo123',
database: process.env.DB_NAME || 'crm',
});
const db = drizzle(pool);
async function migrateData() {
console.log('🚀 Starting data migration to email accounts...\n');
try {
// Step 1: Check if email_accounts table exists
console.log('Step 1: Checking email_accounts table...');
const tableExists = await db.execute(sql`
SELECT EXISTS (
SELECT FROM information_schema.tables
WHERE table_schema = 'public'
AND table_name = 'email_accounts'
)
`);
if (!tableExists.rows[0].exists) {
throw new Error('email_accounts table does not exist. Run Drizzle migrations first.');
}
console.log('✅ email_accounts table exists\n');
// Step 2: Migrate existing user emails to email_accounts
console.log('Step 2: Migrating existing user emails to email_accounts...');
const usersWithEmail = await db.execute(sql`
SELECT id, email, email_password, jmap_account_id
FROM users
WHERE email IS NOT NULL
AND email_password IS NOT NULL
AND jmap_account_id IS NOT NULL
`);
console.log(`Found ${usersWithEmail.rows.length} users with email accounts`);
for (const user of usersWithEmail.rows) {
// Check if already migrated
const existing = await db.execute(sql`
SELECT id FROM email_accounts
WHERE user_id = ${user.id} AND email = ${user.email}
`);
if (existing.rows.length > 0) {
console.log(` ⏩ Skipping user ${user.id}: ${user.email} (already migrated)`);
continue;
}
await db.execute(sql`
INSERT INTO email_accounts (user_id, email, email_password, jmap_account_id, is_primary, is_active)
VALUES (${user.id}, ${user.email}, ${user.email_password}, ${user.jmap_account_id}, true, true)
`);
console.log(` ✓ Migrated email account for user ${user.id}: ${user.email}`);
}
console.log('✅ User emails migrated\n');
// Step 3: Update existing contacts with email_account_id
console.log('Step 3: Updating existing contacts with email_account_id...');
const contactsNeedUpdate = await db.execute(sql`
SELECT COUNT(*) as count FROM contacts WHERE email_account_id IS NULL
`);
if (parseInt(contactsNeedUpdate.rows[0].count) > 0) {
await db.execute(sql`
UPDATE contacts
SET email_account_id = (
SELECT ea.id
FROM email_accounts ea
WHERE ea.user_id = contacts.user_id
AND ea.is_primary = true
LIMIT 1
)
WHERE email_account_id IS NULL
`);
console.log(`✅ Updated ${contactsNeedUpdate.rows[0].count} contacts\n`);
} else {
console.log('✅ No contacts to update\n');
}
// Step 4: Update existing emails with email_account_id
console.log('Step 4: Updating existing emails with email_account_id...');
const emailsNeedUpdate = await db.execute(sql`
SELECT COUNT(*) as count FROM emails WHERE email_account_id IS NULL
`);
if (parseInt(emailsNeedUpdate.rows[0].count) > 0) {
await db.execute(sql`
UPDATE emails
SET email_account_id = (
SELECT ea.id
FROM email_accounts ea
WHERE ea.user_id = emails.user_id
AND ea.is_primary = true
LIMIT 1
)
WHERE email_account_id IS NULL
`);
console.log(`✅ Updated ${emailsNeedUpdate.rows[0].count} emails\n`);
} else {
console.log('✅ No emails to update\n');
}
// Summary
console.log('🎉 Data migration completed successfully!\n');
console.log('Summary:');
console.log(` - Email accounts migrated: ${usersWithEmail.rows.length}`);
console.log(` - Contacts updated: ${contactsNeedUpdate.rows[0].count}`);
console.log(` - Emails updated: ${emailsNeedUpdate.rows[0].count}`);
} catch (error) {
console.error('❌ Migration failed:', error);
throw error;
} finally {
await pool.end();
}
}
// Run migration
migrateData().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

View File

@@ -0,0 +1,179 @@
import 'dotenv/config';
import { drizzle } from 'drizzle-orm/node-postgres';
import pkg from 'pg';
const { Pool } = pkg;
import { users, emailAccounts, contacts, emails } from '../schema.js';
import { sql } from 'drizzle-orm';
/**
* Migration script to move from single email per user to multiple email accounts
*
* Steps:
* 1. Create email_accounts table
* 2. Migrate existing user emails to email_accounts (as primary)
* 3. Add email_account_id to contacts and emails tables
* 4. Update existing contacts and emails to reference new email accounts
*/
const pool = new Pool({
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT || '5432'),
user: process.env.DB_USER || 'admin',
password: process.env.DB_PASSWORD || 'heslo123',
database: process.env.DB_NAME || 'crm',
});
const db = drizzle(pool);
async function migrateToEmailAccounts() {
console.log('🚀 Starting migration to email accounts...\n');
try {
// Step 1: Create email_accounts table
console.log('Step 1: Creating email_accounts table...');
await db.execute(sql`
CREATE TABLE IF NOT EXISTS "email_accounts" (
"id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL,
"user_id" uuid NOT NULL,
"email" text NOT NULL,
"email_password" text NOT NULL,
"jmap_account_id" text NOT NULL,
"is_primary" boolean DEFAULT false NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp DEFAULT now() NOT NULL,
"updated_at" timestamp DEFAULT now() NOT NULL
)
`);
await db.execute(sql`
ALTER TABLE "email_accounts"
ADD CONSTRAINT "email_accounts_user_id_users_id_fk"
FOREIGN KEY ("user_id") REFERENCES "public"."users"("id")
ON DELETE cascade ON UPDATE no action
`);
console.log('✅ email_accounts table created\n');
// Step 2: Migrate existing user emails to email_accounts
console.log('Step 2: Migrating existing user emails to email_accounts...');
const usersWithEmail = await db.execute(sql`
SELECT id, email, email_password, jmap_account_id
FROM users
WHERE email IS NOT NULL
AND email_password IS NOT NULL
AND jmap_account_id IS NOT NULL
`);
console.log(`Found ${usersWithEmail.rows.length} users with email accounts`);
for (const user of usersWithEmail.rows) {
await db.execute(sql`
INSERT INTO email_accounts (user_id, email, email_password, jmap_account_id, is_primary, is_active)
VALUES (${user.id}, ${user.email}, ${user.email_password}, ${user.jmap_account_id}, true, true)
`);
console.log(` ✓ Migrated email account for user ${user.id}: ${user.email}`);
}
console.log('✅ User emails migrated\n');
// Step 3: Add email_account_id column to contacts (nullable first)
console.log('Step 3: Adding email_account_id to contacts table...');
await db.execute(sql`
ALTER TABLE contacts
ADD COLUMN IF NOT EXISTS email_account_id uuid
`);
console.log('✅ Column added to contacts\n');
// Step 4: Update existing contacts with email_account_id
console.log('Step 4: Updating existing contacts with email_account_id...');
await db.execute(sql`
UPDATE contacts
SET email_account_id = (
SELECT ea.id
FROM email_accounts ea
WHERE ea.user_id = contacts.user_id
AND ea.is_primary = true
LIMIT 1
)
WHERE email_account_id IS NULL
`);
const contactsUpdated = await db.execute(sql`
SELECT COUNT(*) as count FROM contacts WHERE email_account_id IS NOT NULL
`);
console.log(`✅ Updated ${contactsUpdated.rows[0].count} contacts\n`);
// Step 5: Make email_account_id NOT NULL and add foreign key
console.log('Step 5: Adding constraints to contacts...');
await db.execute(sql`
ALTER TABLE contacts
ALTER COLUMN email_account_id SET NOT NULL
`);
await db.execute(sql`
ALTER TABLE contacts
ADD CONSTRAINT "contacts_email_account_id_email_accounts_id_fk"
FOREIGN KEY ("email_account_id") REFERENCES "public"."email_accounts"("id")
ON DELETE cascade ON UPDATE no action
`);
console.log('✅ Constraints added to contacts\n');
// Step 6: Add email_account_id column to emails (nullable first)
console.log('Step 6: Adding email_account_id to emails table...');
await db.execute(sql`
ALTER TABLE emails
ADD COLUMN IF NOT EXISTS email_account_id uuid
`);
console.log('✅ Column added to emails\n');
// Step 7: Update existing emails with email_account_id
console.log('Step 7: Updating existing emails with email_account_id...');
await db.execute(sql`
UPDATE emails
SET email_account_id = (
SELECT ea.id
FROM email_accounts ea
WHERE ea.user_id = emails.user_id
AND ea.is_primary = true
LIMIT 1
)
WHERE email_account_id IS NULL
`);
const emailsUpdated = await db.execute(sql`
SELECT COUNT(*) as count FROM emails WHERE email_account_id IS NOT NULL
`);
console.log(`✅ Updated ${emailsUpdated.rows[0].count} emails\n`);
// Step 8: Make email_account_id NOT NULL and add foreign key
console.log('Step 8: Adding constraints to emails...');
await db.execute(sql`
ALTER TABLE emails
ALTER COLUMN email_account_id SET NOT NULL
`);
await db.execute(sql`
ALTER TABLE emails
ADD CONSTRAINT "emails_email_account_id_email_accounts_id_fk"
FOREIGN KEY ("email_account_id") REFERENCES "public"."email_accounts"("id")
ON DELETE cascade ON UPDATE no action
`);
console.log('✅ Constraints added to emails\n');
console.log('🎉 Migration completed successfully!\n');
console.log('Summary:');
console.log(` - Email accounts created: ${usersWithEmail.rows.length}`);
console.log(` - Contacts updated: ${contactsUpdated.rows[0].count}`);
console.log(` - Emails updated: ${emailsUpdated.rows[0].count}`);
} catch (error) {
console.error('❌ Migration failed:', error);
throw error;
} finally {
await pool.end();
}
}
// Run migration
migrateToEmailAccounts().catch((error) => {
console.error('Fatal error:', error);
process.exit(1);
});

View File

@@ -21,6 +21,19 @@ export const users = pgTable('users', {
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
// Email Accounts table - viacero emailových účtov pre jedného usera
export const emailAccounts = pgTable('email_accounts', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
email: text('email').notNull(),
emailPassword: text('email_password').notNull(), // Heslo k emailovému účtu (encrypted)
jmapAccountId: text('jmap_account_id').notNull(), // JMAP account ID z truemail
isPrimary: boolean('is_primary').default(false).notNull(), // primárny email účet
isActive: boolean('is_active').default(true).notNull(), // či je účet aktívny
createdAt: timestamp('created_at').defaultNow().notNull(),
updatedAt: timestamp('updated_at').defaultNow().notNull(),
});
// Audit logs - kompletný audit trail všetkých akcií
export const auditLogs = pgTable('audit_logs', {
id: uuid('id').primaryKey().defaultRandom(),
@@ -41,6 +54,7 @@ export const auditLogs = pgTable('audit_logs', {
export const contacts = pgTable('contacts', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
emailAccountId: uuid('email_account_id').references(() => emailAccounts.id, { onDelete: 'cascade' }).notNull(),
email: text('email').notNull(),
name: text('name'),
notes: text('notes'),
@@ -53,6 +67,7 @@ export const contacts = pgTable('contacts', {
export const emails = pgTable('emails', {
id: uuid('id').primaryKey().defaultRandom(),
userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }).notNull(),
emailAccountId: uuid('email_account_id').references(() => emailAccounts.id, { onDelete: 'cascade' }).notNull(),
contactId: uuid('contact_id').references(() => contacts.id, { onDelete: 'cascade' }),
jmapId: text('jmap_id').unique(),
messageId: text('message_id').unique(),