# Migračný návod - Refaktorizácia emailových účtov ## Prehľad zmien Tento refaktoring rieši nasledujúce problémy: ### 🐛 Opravené bugy: 1. **Bug s vytváraním používateľov** - Email credentials sa teraz ukladajú do `emailAccounts` tabuľky namiesto `users` tabuľky 2. **Bug s duplikátnymi kontaktami** - Pridaný composite unique constraint zabezpečuje správnu izoláciu kontaktov 3. **Bug s linkovaním emailov** - `linkEmail` funkcia teraz používa `emailAccounts` tabuľku 4. **Nepoužívané stĺpce** - Odstránené `email`, `email_password`, `jmap_account_id` z `users` tabuľky ### ✨ Vylepšenia: - Čistejšia separácia concerns (users vs email accounts) - Podpora pre viacero emailových účtov na používateľa - Pripravené na zdieľané emailové účty (many-to-many) --- ## 📋 Migračný proces ### Krok 1: Backup databázy ```bash # PostgreSQL backup pg_dump -U your_username -d your_database > backup_$(date +%Y%m%d).sql ``` ### Krok 2: Migrovať existujúce email credentials Tento script presunie email credentials z `users` tabuľky do `emailAccounts` tabuľky: ```bash cd /home/richardtekula/Documents/WORK/crm-server node src/scripts/migrate-users-to-email-accounts.js ``` **Čo tento script robí:** - Nájde všetkých používateľov s `email`, `emailPassword`, `jmapAccountId` - Vytvorí zodpovedajúce záznamy v `emailAccounts` tabuľke - Nastaví prvý účet ako primárny (`isPrimary = true`) - Zachová originálne dáta v `users` tabuľke (pre bezpečnosť) **Výstup:** ``` 🔄 Starting migration from users table to emailAccounts table... Found 5 users with email credentials 📧 Processing user: admin (admin@example.com) ✅ Created email account: admin@example.com - Account ID: abc-123-def - Is Primary: true ✅ Migration complete! - Migrated: 5 accounts - Skipped: 0 accounts (already exist) ``` ### Krok 3: Opraviť duplikátne kontakty Ak máte duplikátne kontakty (ten istý email viackrát pre jedného usera), spustite: ```bash node src/scripts/fix-duplicate-contacts.js ``` **Čo tento script robí:** - Nájde duplikátne kontakty (rovnaký `userId` + `email`) - Zachová najnovší kontakt - Premapuje všetky emaily na najnovší kontakt - Vymaže staré duplikáty ### Krok 4: Pridať unique constraint na contacts ```bash node src/scripts/add-contacts-unique-constraint.js ``` **Čo tento script robí:** - Pridá composite unique constraint: `(user_id, email_account_id, email)` - Zabráni vzniku nových duplikátov **Poznámka:** Ak tento krok zlyhá kvôli existujúcim duplikátom, najprv spustite Krok 3. ### Krok 5: Aktualizovať kód aplikácie Kód je už aktualizovaný v týchto súboroch: **Backend:** - ✅ `src/db/schema.js` - odstránené staré stĺpce z users tabuľky - ✅ `src/controllers/admin.controller.js` - `createUser` používa emailAccounts - ✅ `src/services/auth.service.js` - `linkEmail` používa emailAccounts - ✅ `src/services/auth.service.js` - `getUserById` vracia emailAccounts - ✅ `src/controllers/admin.controller.js` - `getAllUsers` nevracia staré stĺpce **Žiadne zmeny nie sú potrebné na frontende** - API rozhranie zostáva kompatibilné. ### Krok 6: Reštartovať aplikáciu ```bash # Zastaviť aplikáciu pm2 stop crm-server # Reštartovať aplikáciu pm2 start crm-server # Alebo pomocou npm npm run dev ``` ### Krok 7: Overiť funkčnosť 1. **Prihlásenie:** - Skúste sa prihlásiť s existujúcim používateľom - Overte že vidíte svoje email účty v sidebari 2. **Vytvorenie nového používateľa:** ```bash # Admin vytvorí nového používateľa s emailom # V Profile -> Create User -> vyplniť email + heslo ``` - Overte že email účet sa objavil v `emailAccounts` tabuľke - Overte že používateľ sa môže prihlásiť a vidí svoj email účet 3. **Pridanie kontaktu:** - Prejdite do Inbox - Discover kontakty - Pridajte nový kontakt - Overte že kontakt sa zobrazuje len pre daný email účet 4. **Označenie emailov ako prečítané:** - Otvorte Email Conversations - Kliknite na "Mark all as read" button na kontakte - Overte že všetky emaily od daného kontaktu sú označené ako prečítané ### Krok 8: Vyčistiť staré dáta (VOLITEĽNÉ) **⚠️ POZOR:** Tento krok je nevratný! Spustite ho len po dôkladnom overení funkčnosti. ```bash # Odstráni staré stĺpce z users tabuľky node src/scripts/remove-old-user-email-columns.js ``` Tento script odstráni: - `users.email` - `users.email_password` - `users.jmap_account_id` **Po spustení tohto scriptu nie je možné sa vrátiť k starej verzii kódu!** --- ## 🔮 Budúce vylepšenia: Zdieľané emailové účty ### Súčasný stav: - Jeden emailový účet patrí jednému používateľovi - `emailAccounts.userId` je foreign key ### Plánovaný stav (many-to-many): - Jeden emailový účet môže byť zdieľaný medzi viacerými používateľmi - Dvaja admini môžu mať linknutý ten istý email account - Keď jeden odpíše, druhý to vidí ### Návrh implementácie: #### 1. Nová junction table: ```sql CREATE TABLE user_email_accounts ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, email_account_id UUID NOT NULL REFERENCES email_accounts(id) ON DELETE CASCADE, role TEXT NOT NULL DEFAULT 'member', -- 'owner' | 'member' can_send BOOLEAN NOT NULL DEFAULT true, can_read BOOLEAN NOT NULL DEFAULT true, added_at TIMESTAMP NOT NULL DEFAULT NOW(), added_by UUID REFERENCES users(id), UNIQUE(user_id, email_account_id) ); ``` #### 2. Zmeny v existujúcich tabuľkách: **emailAccounts:** - Odstráni `userId` foreign key - Pridá `ownerId` (prvý používateľ, ktorý vytvoril účet) - Pridá `shared` boolean flag **contacts:** - Zostáva `emailAccountId` (kontakty patria k email účtu, nie k používateľovi) - Odstráni `userId` (alebo ho ponechá ako "created_by") **emails:** - Zostáva `emailAccountId` - Pridá `sent_by_user_id` (kto poslal odpoveď) #### 3. Migračný script: ```javascript // Presunie existujúce väzby do user_email_accounts async function migrateToSharedAccounts() { const accounts = await db.select().from(emailAccounts); for (const account of accounts) { await db.insert(userEmailAccounts).values({ userId: account.userId, emailAccountId: account.id, role: 'owner', canSend: true, canRead: true, }); } // Alter table to remove userId, add ownerId await db.execute(sql` ALTER TABLE email_accounts DROP COLUMN user_id, ADD COLUMN owner_id UUID REFERENCES users(id), ADD COLUMN shared BOOLEAN DEFAULT false `); } ``` #### 4. API zmeny: **Nové endpointy:** ```javascript // Zdieľať email account s iným používateľom POST /api/email-accounts/:accountId/share Body: { userId, role: 'member', canSend: true, canRead: true } // Odobrať prístup DELETE /api/email-accounts/:accountId/share/:userId // Získať používateľov s prístupom k účtu GET /api/email-accounts/:accountId/users ``` #### 5. Frontend zmeny: - Zobrazenie ikony "zdieľané" pri zdieľaných účtoch - UI pre zdieľanie účtov (modal s výberom používateľov) - Zobrazenie "sent by User X" pri odpovediach --- ## 🐛 Riešenie problémov ### Problém: Script fail kvôli duplikátom **Chyba:** ``` could not create unique index "user_account_email_unique" Key (user_id, email_account_id, email)=(...) already exists. ``` **Riešenie:** ```bash # Najprv opravte duplikáty node src/scripts/fix-duplicate-contacts.js # Potom pridajte constraint node src/scripts/add-contacts-unique-constraint.js ``` ### Problém: Používateľ nevidí email účty po migrácii **Kontrola:** ```sql SELECT * FROM email_accounts WHERE user_id = 'user-id-here'; ``` **Riešenie:** - Skontrolujte či migračný script úspešne bežal - Skontrolujte logy servera - Reštartujte server ### Problém: "Mark as read" button nefunguje **Možné príčiny:** 1. Backend endpoint `/emails/contact/:contactId/read` neexistuje 2. `contactId` je null alebo nesprávne 3. Email account nie je správne linknutý **Kontrola:** ```bash # Skontrolujte logs tail -f logs/server.log # Test endpoint curl -X POST http://localhost:3000/api/emails/contact//read \ -H "Authorization: Bearer " ``` --- ## 📊 Databázová schéma (po migrácii) ### users ``` id | UUID | PRIMARY KEY username | TEXT | UNIQUE NOT NULL first_name | TEXT | last_name | TEXT | password | TEXT | bcrypt hash temp_password | TEXT | bcrypt hash changed_password| BOOLEAN | DEFAULT false role | ENUM | 'admin' | 'member' last_login | TIMESTAMP | created_at | TIMESTAMP | updated_at | TIMESTAMP | ``` ### email_accounts ``` id | UUID | PRIMARY KEY user_id | UUID | REFERENCES users(id) CASCADE email | TEXT | NOT NULL email_password | TEXT | NOT NULL (encrypted) jmap_account_id | TEXT | NOT NULL is_primary | BOOLEAN | DEFAULT false is_active | BOOLEAN | DEFAULT true created_at | TIMESTAMP | updated_at | TIMESTAMP | ``` ### contacts ``` id | UUID | PRIMARY KEY user_id | UUID | REFERENCES users(id) CASCADE email_account_id| UUID | REFERENCES email_accounts(id) CASCADE email | TEXT | NOT NULL name | TEXT | notes | TEXT | added_at | TIMESTAMP | created_at | TIMESTAMP | updated_at | TIMESTAMP | UNIQUE (user_id, email_account_id, email) ``` ### emails ``` id | UUID | PRIMARY KEY user_id | UUID | REFERENCES users(id) CASCADE email_account_id| UUID | REFERENCES email_accounts(id) CASCADE contact_id | UUID | REFERENCES contacts(id) CASCADE jmap_id | TEXT | UNIQUE message_id | TEXT | UNIQUE thread_id | TEXT | in_reply_to | TEXT | from | TEXT | to | TEXT | subject | TEXT | body | TEXT | is_read | BOOLEAN | DEFAULT false date | TIMESTAMP | created_at | TIMESTAMP | updated_at | TIMESTAMP | ``` --- ## ✅ Checklist - [ ] Backup databázy vytvorený - [ ] Migračný script 1: `migrate-users-to-email-accounts.js` spustený - [ ] Migračný script 2: `fix-duplicate-contacts.js` spustený - [ ] Migračný script 3: `add-contacts-unique-constraint.js` spustený - [ ] Backend kód aktualizovaný (už hotové) - [ ] Aplikácia reštartovaná - [ ] Funkčnosť otestovaná: - [ ] Prihlásenie existujúceho používateľa - [ ] Vytvorenie nového používateľa s emailom - [ ] Pridanie nového email účtu - [ ] Pridanie kontaktu - [ ] Označenie emailov ako prečítané - [ ] Odpoveď na email - [ ] (Voliteľné) Vyčistenie starých stĺpcov: `remove-old-user-email-columns.js` spustené --- ## 📞 Podpora Ak narazíte na problémy: 1. Skontrolujte logs: `tail -f logs/server.log` 2. Skontrolujte databázu: `psql -U username -d database` 3. Obnovte backup ak je potrebné --- ## 📝 Poznámky pre vývojárov ### Dôležité zmeny v API: **❌ Staré (nepoužívať):** ```javascript // users tabuľka obsahovala email credentials const user = await db.select().from(users).where(eq(users.email, email)); ``` **✅ Nové (použiť):** ```javascript // email credentials sú v emailAccounts tabuľke const accounts = await db.select() .from(emailAccounts) .where(eq(emailAccounts.userId, userId)); ``` ### Pri vytváraní nového používateľa: **❌ Staré:** ```javascript await db.insert(users).values({ username, email, emailPassword, jmapAccountId, }); ``` **✅ Nové:** ```javascript // 1. Vytvor usera const [user] = await db.insert(users).values({ username }).returning(); // 2. Vytvor email account await db.insert(emailAccounts).values({ userId: user.id, email, emailPassword, jmapAccountId, isPrimary: true, }); ``` --- Vytvoril: Claude Code Dátum: 2025-11-20 Verzia: 1.0