Backend Features:
- Timesheets database table (id, userId, fileName, filePath, fileType, fileSize, year, month, timestamps)
- File upload with multer (memory storage, 10MB limit, PDF/Excel validation)
- Structured file storage: uploads/timesheets/{userId}/{year}/{month}/
- RESTful API endpoints:
* POST /api/timesheets/upload - Upload timesheet
* GET /api/timesheets/my - Get user's timesheets (with filters)
* GET /api/timesheets/all - Get all timesheets (admin only)
* GET /api/timesheets/:id/download - Download file
* DELETE /api/timesheets/:id - Delete timesheet
- Role-based permissions: users access own files, admins access all
- Proper error handling and file cleanup on errors
- Database migration for timesheets table
Technical:
- Uses req.user.role for permission checks
- Automatic directory creation for user/year/month structure
- Blob URL cleanup and proper file handling
- Integration with existing auth middleware
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
446 lines
12 KiB
Markdown
446 lines
12 KiB
Markdown
# 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/<contactId>/read \
|
|
-H "Authorization: Bearer <token>"
|
|
```
|
|
|
|
---
|
|
|
|
## 📊 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
|