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>
12 KiB
Migračný návod - Refaktorizácia emailových účtov
Prehľad zmien
Tento refaktoring rieši nasledujúce problémy:
🐛 Opravené bugy:
- Bug s vytváraním používateľov - Email credentials sa teraz ukladajú do
emailAccountstabuľky namiestouserstabuľky - Bug s duplikátnymi kontaktami - Pridaný composite unique constraint zabezpečuje správnu izoláciu kontaktov
- Bug s linkovaním emailov -
linkEmailfunkcia teraz používaemailAccountstabuľku - Nepoužívané stĺpce - Odstránené
email,email_password,jmap_account_idzuserstabuľ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
# 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:
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
emailAccountstabuľke - Nastaví prvý účet ako primárny (
isPrimary = true) - Zachová originálne dáta v
userstabuľ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:
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
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-createUserpoužíva emailAccounts - ✅
src/services/auth.service.js-linkEmailpoužíva emailAccounts - ✅
src/services/auth.service.js-getUserByIdvracia emailAccounts - ✅
src/controllers/admin.controller.js-getAllUsersnevracia staré stĺpce
Žiadne zmeny nie sú potrebné na frontende - API rozhranie zostáva kompatibilné.
Krok 6: Reštartovať aplikáciu
# 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ť
-
Prihlásenie:
- Skúste sa prihlásiť s existujúcim používateľom
- Overte že vidíte svoje email účty v sidebari
-
Vytvorenie nového používateľa:
# Admin vytvorí nového používateľa s emailom # V Profile -> Create User -> vyplniť email + heslo- Overte že email účet sa objavil v
emailAccountstabuľke - Overte že používateľ sa môže prihlásiť a vidí svoj email účet
- Overte že email účet sa objavil v
-
Pridanie kontaktu:
- Prejdite do Inbox
- Discover kontakty
- Pridajte nový kontakt
- Overte že kontakt sa zobrazuje len pre daný email účet
-
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.
# Odstráni staré stĺpce z users tabuľky
node src/scripts/remove-old-user-email-columns.js
Tento script odstráni:
users.emailusers.email_passwordusers.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.userIdje 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:
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
userIdforeign key - Pridá
ownerId(prvý používateľ, ktorý vytvoril účet) - Pridá
sharedboolean 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:
// 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:
// 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:
# 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:
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:
- Backend endpoint
/emails/contact/:contactId/readneexistuje contactIdje null alebo nesprávne- Email account nie je správne linknutý
Kontrola:
# 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.jsspustený - Migračný script 2:
fix-duplicate-contacts.jsspustený - Migračný script 3:
add-contacts-unique-constraint.jsspustený - 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.jsspustené
📞 Podpora
Ak narazíte na problémy:
- Skontrolujte logs:
tail -f logs/server.log - Skontrolujte databázu:
psql -U username -d database - Obnovte backup ak je potrebné
📝 Poznámky pre vývojárov
Dôležité zmeny v API:
❌ Staré (nepoužívať):
// users tabuľka obsahovala email credentials
const user = await db.select().from(users).where(eq(users.email, email));
✅ Nové (použiť):
// 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é:
await db.insert(users).values({
username,
email,
emailPassword,
jmapAccountId,
});
✅ Nové:
// 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