Add Timesheets API with file upload and role-based access
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>
This commit is contained in:
445
MIGRATION_GUIDE.md
Normal file
445
MIGRATION_GUIDE.md
Normal file
@@ -0,0 +1,445 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user