diff --git a/.env.example b/.env.example deleted file mode 100644 index 430587b..0000000 --- a/.env.example +++ /dev/null @@ -1,34 +0,0 @@ -PORT=5000 -NODE_ENV=development - -# Database Configuration -DB_HOST=localhost -DB_PORT=5432 -DB_USER=admin -DB_PASSWORD=heslo123 -DB_NAME=crm - -# JWT Configuration -JWT_SECRET=your-super-secret-jwt-key-change-this-in-production -JWT_EXPIRES_IN=1h -JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-in-production -JWT_REFRESH_EXPIRES_IN=7d - -# Better Auth Configuration -BETTER_AUTH_SECRET=your-super-secret-better-auth-key-change-this-in-production -BETTER_AUTH_URL=http://localhost:5000 - -# JMAP Email Configuration -JMAP_SERVER=https://mail.truemail.sk/jmap/ -JMAP_USERNAME=info1_test@truemail.sk -JMAP_PASSWORD=info1 -JMAP_ACCOUNT_ID=ba - -# Security -BCRYPT_ROUNDS=12 -RATE_LIMIT_WINDOW_MS=900000 -RATE_LIMIT_MAX_REQUESTS=100 -RATE_LIMIT_LOGIN_MAX=5 - -# CORS -CORS_ORIGIN=http://localhost:3000 diff --git a/SECURITY_CHECK.md b/SECURITY_CHECK.md new file mode 100644 index 0000000..ca4cfdd --- /dev/null +++ b/SECURITY_CHECK.md @@ -0,0 +1,626 @@ +# πŸ”’ SECURITY AUDIT REPORT - CRM Server + +**Date:** 2025-12-02 +**Auditor:** Automated Security Scan +**Project Version:** 1.0.0 +**Node Version:** 20.x + +--- + +## Executive Summary + +I've completed a comprehensive security audit of your CRM server project. The project has **good security foundations** with several protective measures in place, but there are **2 critical dependency vulnerabilities** and several **medium-priority security improvements** needed. + +--- + +## βœ… STRENGTHS + +### 1. **Authentication & Authorization** +- βœ… JWT tokens stored in httpOnly cookies (XSS protection) +- βœ… SameSite=Strict cookies (CSRF protection) +- βœ… Bcrypt password hashing (12 rounds) +- βœ… Separate access & refresh tokens +- βœ… Role-based access control (admin/member) +- βœ… Temporary password system for onboarding + +### 2. **Input Validation & Sanitization** +- βœ… Zod schemas for request validation +- βœ… XSS protection via xss-clean middleware +- βœ… Custom malicious pattern detection in `validateBody.js` +- βœ… SQL injection protection via Drizzle ORM (parameterized queries) + +### 3. **Rate Limiting** +- βœ… Login rate limiter (5 attempts/15min) +- βœ… API rate limiter (100 req/15min production, 1000 dev) +- βœ… Sensitive operations limiter (3 attempts/15min) + +### 4. **Security Headers & CORS** +- βœ… Helmet middleware with CSP and HSTS +- βœ… Configured CORS with credentials support +- βœ… Body size limits (10MB) + +### 5. **Data Encryption** +- βœ… AES-256-GCM for email passwords +- βœ… Crypto.randomUUID() for tokens +- βœ… Secure password generation + +### 6. **File Upload Security** +- βœ… File type whitelist (PDF, Excel only) +- βœ… File size limit (10MB) +- βœ… Memory storage (prevents path traversal) +- βœ… Filename sanitization + +### 7. **Docker Security** +- βœ… Non-root user in Dockerfile +- βœ… Alpine Linux base image (smaller attack surface) + +### 8. **Audit Logging** +- βœ… Comprehensive audit trail +- βœ… IP address and user-agent tracking + +--- + +## 🚨 CRITICAL VULNERABILITIES + +### 1. **NPM Dependencies - 2 Low Severity Issues** ⚠️ + +**better-auth v1.3.34** (2 vulnerabilities): +- **GHSA-wmjr-v86c-m9jj**: Multi-session sign-out hook allows forged cookies to revoke arbitrary sessions (CVSS 9.6) + - Severity: Low (but high CVSS score) + - Fix available: v1.4.4 + +- **GHSA-569q-mpph-wgww**: External request basePath modification DoS + - Severity: Low + - Fix available: v1.4.4 + +**express v4.21.2**: +- **GHSA-pj86-cfqh-vqx6**: Improper control of query properties modification + - Severity: Low + - Fix available: v4.22.0 + +**Fix Available**: Yes + +**Recommended Action**: +```bash +npm audit fix +# This will update: +# - better-auth: 1.3.34 β†’ 1.4.4 +# - express: 4.21.2 β†’ 4.22.1 +``` + +**Additional Outdated Packages**: +- `dotenv`: 16.6.1 β†’ 17.2.3 (latest) +- `zod`: 4.1.12 β†’ 4.1.13 + +--- + +## ⚠️ HIGH PRIORITY ISSUES + +### 2. **Environment File Security** πŸ”΄ + +**Issue**: `.env` file contains default/weak secrets + +**Findings**: +- βœ… `.env` is properly gitignored +- βœ… Never committed to git history +- ❌ Contains default/weak secrets that should be changed in production + +**Secrets Found**: +```env +JWT_SECRET=your-super-secret-jwt-key-change-this-in-production +JWT_REFRESH_SECRET=your-super-secret-refresh-key-change-this-in-production +BETTER_AUTH_SECRET=your-super-secret-better-auth-key-change-this-in-production +``` + +**Recommendation**: +```bash +# Generate strong secrets: +node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" + +# For each secret in .env: +JWT_SECRET= +JWT_REFRESH_SECRET= +BETTER_AUTH_SECRET= +``` + +**Best Practices**: +- Generate strong, random secrets for production +- Consider using secret management (HashiCorp Vault, AWS Secrets Manager) +- Rotate secrets regularly (every 90 days) +- Never commit secrets to version control + +### 3. **Database Credentials in docker-compose.yml** 🟑 + +**Issue**: Hardcoded database password in docker-compose.yml + +**Current Code**: +```yaml +environment: + POSTGRES_PASSWORD: heslo123 + POSTGRES_DB: crm + POSTGRES_USER: admin +``` + +**Recommendation**: +```yaml +environment: + POSTGRES_PASSWORD: ${DB_PASSWORD} + POSTGRES_DB: ${DB_NAME} + POSTGRES_USER: ${DB_USER} +``` + +### 4. **Password Encryption Salt** 🟑 + +**Location**: `src/utils/password.js:75` + +**Issue**: Hardcoded salt value 'salt' - should be unique per encryption + +**Current Code**: +```javascript +const key = crypto.scryptSync(process.env.JWT_SECRET, 'salt', 32); +``` + +**Problem**: Using a static salt means all encrypted passwords use the same key derivation, reducing security. + +**Recommendation**: +```javascript +// Generate random salt per encryption +const salt = crypto.randomBytes(16); +const key = crypto.scryptSync(process.env.JWT_SECRET, salt, 32); +// Store: `${salt.toString('hex')}:${iv.toString('hex')}:${authTag.toString('hex')}:${encrypted}` +``` + +--- + +## 🟑 MEDIUM PRIORITY IMPROVEMENTS + +### 5. **Session Security** + +**Current Issues**: +- ❌ No session invalidation on password change +- ❌ No maximum concurrent sessions per user +- ❌ No session timeout warnings +- ❌ Refresh tokens not stored in database (cannot revoke) + +**Recommendation**: +```javascript +// Add session tracking table +export const sessions = pgTable('sessions', { + id: uuid('id').primaryKey().defaultRandom(), + userId: uuid('user_id').references(() => users.id, { onDelete: 'cascade' }), + refreshToken: text('refresh_token').notNull(), + ipAddress: text('ip_address'), + userAgent: text('user_agent'), + expiresAt: timestamp('expires_at').notNull(), + createdAt: timestamp('created_at').defaultNow() +}); + +// Invalidate all sessions on password change +// Limit to 5 concurrent sessions per user +``` + +### 6. **Password Policy Enhancement** + +**Current Policy** (from code review): +- βœ… Minimum 8 characters +- βœ… Must contain uppercase, lowercase, number, symbol + +**Enhancements Needed**: +```javascript +// Add to password validation: +- Minimum 12 characters (currently 8) +- Check against common password list (e.g., have-i-been-pwned) +- Prevent password reuse (store hash of last 5 passwords) +- Add password strength meter on frontend +- Enforce password expiration (90 days for admin accounts) +``` + +### 7. **File Storage Security** + +**Current Issues**: +- ❌ Uploaded files stored locally without encryption at rest +- ❌ No virus scanning on uploads +- ❌ File paths somewhat predictable: `uploads/timesheets/{userId}/{year}/{month}/{filename}` + +**Recommendations**: +```bash +# 1. Add virus scanning +npm install clamscan + +# 2. Encrypt files at rest +# Use node's crypto to encrypt files before saving + +# 3. Use UUIDs in paths +uploads/timesheets/{uuid}/{uuid}.encrypted +``` + +**Alternative**: Migrate to cloud storage (AWS S3, Azure Blob) with server-side encryption + +### 8. **Logging Concerns** + +**Issues**: +- ❌ No check for sensitive data in logs +- ⚠️ Morgan logs all requests (could expose sensitive query params) +- ❌ Console.log statements in code (should use winston logger) + +**Recommendation**: +```javascript +// Configure morgan to skip sensitive routes +app.use(morgan('dev', { + skip: (req) => { + const sensitivePatterns = [ + /password/i, + /token/i, + /secret/i, + /api\/auth\/login/, + /api\/auth\/set-password/ + ]; + return sensitivePatterns.some(pattern => + pattern.test(req.url) || pattern.test(JSON.stringify(req.body)) + ); + } +})); + +// Replace all console.log with winston logger +import { logger } from './utils/logger.js'; +``` + +--- + +## πŸ”΅ LOW PRIORITY / BEST PRACTICES + +### 9. **Security Headers Enhancement** + +**Current CSP** is basic. Enhance with: + +```javascript +app.use(helmet({ + contentSecurityPolicy: { + directives: { + defaultSrc: ["'self'"], + scriptSrc: ["'self'"], + styleSrc: ["'self'", "'unsafe-inline'"], + imgSrc: ["'self'", "data:", "https:"], + connectSrc: ["'self'"], + fontSrc: ["'self'"], + objectSrc: ["'none'"], + mediaSrc: ["'none'"], + frameSrc: ["'none'"], + upgradeInsecureRequests: [], + }, + }, + hsts: { + maxAge: 31536000, + includeSubDomains: true, + preload: true, + }, + referrerPolicy: { policy: "strict-origin-when-cross-origin" }, + noSniff: true, + xssFilter: true, + hidePoweredBy: true +})); +``` + +### 10. **CORS Hardening** + +**Current**: +```javascript +const corsOptions = { + origin: process.env.CORS_ORIGIN || 'http://localhost:5173', + credentials: true, + optionsSuccessStatus: 200, +}; +``` + +**Enhanced**: +```javascript +const corsOptions = { + origin: process.env.CORS_ORIGIN || 'http://localhost:5173', + credentials: true, + methods: ['GET', 'POST', 'PATCH', 'DELETE'], + allowedHeaders: ['Content-Type', 'Authorization'], + exposedHeaders: ['Content-Range', 'X-Content-Range'], + maxAge: 86400, // 24 hours + optionsSuccessStatus: 200 +}; +``` + +### 11. **Rate Limiting - IP Spoofing Protection** + +Add trust proxy setting: +```javascript +// In app.js, before rate limiters +app.set('trust proxy', 1); // Trust first proxy (nginx, cloudflare, etc.) +``` + +### 12. **Audit Log Retention** + +**Current Issues**: +- ❌ No automatic cleanup of old audit logs +- ❌ No log rotation policy +- ❌ Unlimited log growth + +**Recommendation**: +```javascript +// Add cron job to clean old logs +import cron from 'node-cron'; + +// Run daily at 2 AM +cron.schedule('0 2 * * *', async () => { + const retentionDays = 90; + await db.delete(auditLogs) + .where( + sql`created_at < NOW() - INTERVAL '${retentionDays} days'` + ); +}); +``` + +### 13. **Additional Security Measures** + +**API Security**: +```javascript +// Add request ID tracking +import { v4 as uuidv4 } from 'uuid'; +app.use((req, res, next) => { + req.id = uuidv4(); + res.setHeader('X-Request-ID', req.id); + next(); +}); + +// Add timeout middleware +import timeout from 'connect-timeout'; +app.use(timeout('30s')); +``` + +**Brute Force Protection**: +```javascript +// Add progressive delays after failed login attempts +// Implement account lockout after 10 failed attempts +// Add CAPTCHA after 3 failed attempts +``` + +--- + +## πŸ“‹ SECURITY CHECKLIST + +### πŸ”΄ Immediate Actions (Do Now) +- [ ] Run `npm audit fix` to update dependencies +- [ ] Generate strong secrets for JWT_SECRET, JWT_REFRESH_SECRET, BETTER_AUTH_SECRET +- [ ] Move database password to environment variable in docker-compose.yml +- [ ] Fix hardcoded salt in password encryption (src/utils/password.js) + +### 🟑 Short Term (This Week) +- [ ] Implement session invalidation on password change +- [ ] Add session tracking in database +- [ ] Review and filter sensitive data from logs +- [ ] Update password policy to 12 characters minimum +- [ ] Add trust proxy setting for rate limiters + +### πŸ”΅ Medium Term (This Month) +- [ ] Add virus scanning for file uploads +- [ ] Implement password strength requirements and breach checking +- [ ] Set up automated security scanning (Snyk, Dependabot) +- [ ] Implement audit log retention policy +- [ ] Add 2FA support +- [ ] Enhance security headers (CSP, etc.) + +### 🟒 Long Term (This Quarter) +- [ ] Migrate to managed secrets (AWS Secrets Manager, HashiCorp Vault) +- [ ] Implement file encryption at rest +- [ ] Add honeypot endpoints +- [ ] Set up SIEM/log aggregation +- [ ] Conduct penetration testing +- [ ] Implement zero-trust architecture + +--- + +## πŸ”§ RECOMMENDED SECURITY TOOLS + +### 1. **Dependency Scanning** +```bash +# NPM Audit (built-in) +npm audit +npm audit fix + +# Snyk (more comprehensive) +npm install -g snyk +snyk auth +snyk test +snyk monitor + +# GitHub Dependabot +# Enable in: Settings β†’ Security & analysis β†’ Dependabot alerts +``` + +### 2. **Static Analysis** +```bash +# ESLint security plugin +npm install --save-dev eslint-plugin-security +# Add to .eslintrc.json: "plugins": ["security"] + +# SonarQube +# Self-hosted or SonarCloud for continuous inspection +``` + +### 3. **Runtime Protection** +```bash +# Helmet (already installed) βœ… +# Express rate limit (already installed) βœ… + +# Additional recommendations: +npm install express-mongo-sanitize # NoSQL injection prevention +npm install hpp # HTTP Parameter Pollution protection +npm install csurf # CSRF token middleware +``` + +### 4. **Secret Scanning** +```bash +# TruffleHog - scan git history for secrets +docker run --rm -v "$(pwd):/repo" trufflesecurity/trufflehog:latest git file:///repo + +# GitLeaks +docker run --rm -v "$(pwd):/path" zricethezav/gitleaks:latest detect --source="/path" +``` + +### 5. **Penetration Testing** +```bash +# OWASP ZAP +docker run -t owasp/zap2docker-stable zap-baseline.py -t http://your-api + +# Burp Suite Community Edition +# Manual testing of API endpoints +``` + +--- + +## πŸ“Š OVERALL SECURITY RATING + +### **Score: 7.5/10** 🟒 + +### Breakdown: +| Category | Score | Status | +|----------|-------|--------| +| Authentication/Authorization | 9/10 | βœ… Excellent | +| Input Validation | 8/10 | βœ… Good | +| Dependency Management | 6/10 | ⚠️ Needs Update | +| Encryption | 7/10 | 🟑 Good with improvements needed | +| Secret Management | 6/10 | ⚠️ Needs Improvement | +| Network Security | 9/10 | βœ… Excellent | +| Audit/Logging | 7/10 | 🟑 Good | +| File Upload Security | 7/10 | 🟑 Good | +| Session Management | 6/10 | 🟑 Needs Improvement | +| Error Handling | 8/10 | βœ… Good | + +### **Verdict**: +Your project has a **solid security foundation** with industry-standard practices including JWT authentication, bcrypt hashing, rate limiting, and input validation. The main concerns are: +1. Outdated dependencies with known vulnerabilities +2. Secret management (default secrets in .env) +3. Hardcoded salt in encryption +4. Session management improvements needed + +Addressing the critical and high-priority issues will bring your security posture to **production-ready** status. + +--- + +## 🎯 COMPLIANCE CONSIDERATIONS + +### GDPR (EU Data Protection) +- βœ… Audit logging for data access +- βœ… Data deletion (cascade deletes) +- ❌ Missing: Right to data portability (export user data) +- ❌ Missing: Consent management +- ❌ Missing: Data retention policies + +### SOC 2 (Security & Availability) +- βœ… Access controls +- βœ… Encryption in transit (HTTPS) +- βœ… Audit logging +- ⚠️ Missing: Encryption at rest for files +- ⚠️ Missing: Backup and disaster recovery procedures + +### OWASP Top 10 2021 +- βœ… A01: Broken Access Control - Protected βœ… +- βœ… A02: Cryptographic Failures - Mostly Protected ⚠️ +- βœ… A03: Injection - Protected βœ… +- βœ… A04: Insecure Design - Good architecture βœ… +- ⚠️ A05: Security Misconfiguration - Minor issues ⚠️ +- βœ… A06: Vulnerable Components - Needs update ⚠️ +- βœ… A07: Authentication Failures - Protected βœ… +- βœ… A08: Data Integrity Failures - Protected βœ… +- βœ… A09: Logging Failures - Good but can improve 🟑 +- βœ… A10: SSRF - Not applicable to this architecture βœ… + +--- + +## πŸ“ž INCIDENT RESPONSE PLAN + +### If Security Breach Detected: + +1. **Immediate Actions**: + - Isolate affected systems + - Revoke all active sessions + - Rotate all secrets (JWT, database passwords) + - Enable maintenance mode + +2. **Investigation**: + - Review audit logs + - Check for unauthorized access + - Identify attack vector + - Assess data exposure + +3. **Remediation**: + - Patch vulnerabilities + - Update dependencies + - Reset affected user passwords + - Notify affected users (if required by law) + +4. **Prevention**: + - Document incident + - Update security procedures + - Implement additional monitoring + - Conduct security training + +--- + +## πŸ“ NEXT STEPS + +### Week 1 (Critical) +1. βœ… **Update dependencies** (5 minutes) + ```bash + npm audit fix + npm update + ``` + +2. βœ… **Rotate all secrets** (15 minutes) + ```bash + # Generate new secrets + node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" + # Update .env with new values + ``` + +3. βœ… **Fix hardcoded salt** (10 minutes) + - Update `src/utils/password.js` + - Test encryption/decryption still works + +### Week 2-4 (High Priority) +4. Implement session tracking in database +5. Add session invalidation on password change +6. Set up Dependabot/Snyk +7. Enhance logging security + +### Month 2-3 (Medium Priority) +8. Add virus scanning for files +9. Implement 2FA +10. Set up audit log retention +11. Enhance password policies + +--- + +## πŸ“š SECURITY RESOURCES + +- [OWASP Top 10](https://owasp.org/www-project-top-ten/) +- [Node.js Security Best Practices](https://nodejs.org/en/docs/guides/security/) +- [Express.js Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html) +- [JWT Security Best Practices](https://datatracker.ietf.org/doc/html/rfc8725) +- [PostgreSQL Security](https://www.postgresql.org/docs/current/security.html) + +--- + +## βœ… CONCLUSION + +Your CRM server demonstrates **good security awareness** with proper implementation of authentication, authorization, input validation, and rate limiting. The identified vulnerabilities are **manageable and fixable** within a reasonable timeframe. + +**Priority focus areas**: +1. Update dependencies immediately +2. Strengthen secret management +3. Improve session security +4. Enhance file upload security + +With these improvements, your application will achieve **production-grade security** suitable for handling sensitive customer data. + +--- + +**Report Generated**: 2025-12-02 +**Next Review Recommended**: 2025-03-02 (Quarterly) +**Security Contact**: security@your-domain.com (update this) + diff --git a/docker-compose.yml b/docker-compose.yml deleted file mode 100644 index 55c3c03..0000000 --- a/docker-compose.yml +++ /dev/null @@ -1,13 +0,0 @@ -services: - postgres: - image: postgres:16 - container_name: postgres-db - restart: "no" # nebude sa spΓΊΕ‘Ε₯aΕ₯ automaticky - environment: - POSTGRES_PASSWORD: heslo123 - POSTGRES_DB: crm - POSTGRES_USER: admin - ports: - - "5432:5432" - volumes: - - ./postgres:/var/lib/postgresql/data diff --git a/package-lock.json b/package-lock.json index e060db6..0d78bf7 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,6 @@ "dependencies": { "axios": "^1.13.2", "bcryptjs": "^3.0.3", - "better-auth": "^1.3.34", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.5", @@ -535,43 +534,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@better-auth/core": { - "version": "1.3.34", - "resolved": "https://registry.npmjs.org/@better-auth/core/-/core-1.3.34.tgz", - "integrity": "sha512-rt/Bgl0Xa8OQ2DUMKCZEJ8vL9kUw4NCJsBP9Sj9uRhbsK8NEMPiznUOFMkUY2FvrslvfKN7H/fivwyHz9c7HzQ==", - "dependencies": { - "zod": "^4.1.5" - }, - "peerDependencies": { - "@better-auth/utils": "0.3.0", - "@better-fetch/fetch": "1.1.18", - "better-call": "1.0.19", - "jose": "^6.1.0", - "kysely": "^0.28.5", - "nanostores": "^1.0.1" - } - }, - "node_modules/@better-auth/telemetry": { - "version": "1.3.34", - "resolved": "https://registry.npmjs.org/@better-auth/telemetry/-/telemetry-1.3.34.tgz", - "integrity": "sha512-aQZ3wN90YMqV49diWxAMe1k7s2qb55KCsedCZne5PlgCjU4s3YtnqyjC5FEpzw2KY8l8rvR7DMAsDl13NjObKA==", - "dependencies": { - "@better-auth/core": "1.3.34", - "@better-auth/utils": "0.3.0", - "@better-fetch/fetch": "1.1.18" - } - }, - "node_modules/@better-auth/utils": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@better-auth/utils/-/utils-0.3.0.tgz", - "integrity": "sha512-W+Adw6ZA6mgvnSnhOki270rwJ42t4XzSK6YWGF//BbVXL6SwCLWfyzBc1lN2m/4RM28KubdBKQ4X5VMoLRNPQw==", - "license": "MIT" - }, - "node_modules/@better-fetch/fetch": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/@better-fetch/fetch/-/fetch-1.1.18.tgz", - "integrity": "sha512-rEFOE1MYIsBmoMJtQbl32PGHHXuG2hDxvEd7rUHE0vCBoFQVSDqaVs9hkZEtHCxRoY+CljXKFCOuJ8uxqw1LcA==" - }, "node_modules/@drizzle-team/brocli": { "version": "0.10.2", "resolved": "https://registry.npmjs.org/@drizzle-team/brocli/-/brocli-0.10.2.tgz", @@ -1653,12 +1615,6 @@ "integrity": "sha512-fAtCfv4jJg+ExtXhvCkCqUKZ+4ok/JQk01qDKhL5BDDoS3AxKXhV5/MAVUZyQnSEd2GT92fkgZl0pz0Q0AzcIQ==", "license": "MIT" }, - "node_modules/@hexagon/base64": { - "version": "1.1.28", - "resolved": "https://registry.npmjs.org/@hexagon/base64/-/base64-1.1.28.tgz", - "integrity": "sha512-lhqDEAvWixy3bZ+UOYbPwUbBkwBq5C1LAJ/xPC8Oi+lL54oyakv/npbA0aU2hgCsx/1NUd4IBvV03+aUBWxerw==", - "license": "MIT" - }, "node_modules/@humanfs/core": { "version": "0.19.1", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", @@ -2170,24 +2126,6 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, - "node_modules/@levischuck/tiny-cbor": { - "version": "0.2.11", - "resolved": "https://registry.npmjs.org/@levischuck/tiny-cbor/-/tiny-cbor-0.2.11.tgz", - "integrity": "sha512-llBRm4dT4Z89aRsm6u2oEZ8tfwL/2l6BwpZ7JcyieouniDECM5AqNgr/y08zalEIvW3RSK4upYyybDcmjXqAow==", - "license": "MIT" - }, - "node_modules/@noble/ciphers": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-2.0.1.tgz", - "integrity": "sha512-xHK3XHPUW8DTAobU+G0XT+/w+JLM7/8k1UFdB5xg/zTFPnFCobhftzw8wl4Lw2aq/Rvir5pxfZV5fEazmeCJ2g==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, "node_modules/@noble/hashes": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", @@ -2211,190 +2149,6 @@ "@noble/hashes": "^1.1.5" } }, - "node_modules/@peculiar/asn1-android": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-android/-/asn1-android-2.6.0.tgz", - "integrity": "sha512-cBRCKtYPF7vJGN76/yG8VbxRcHLPF3HnkoHhKOZeHpoVtbMYfY9ROKtH3DtYUY9m8uI1Mh47PRhHf2hSK3xcSQ==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-cms": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-cms/-/asn1-cms-2.6.0.tgz", - "integrity": "sha512-2uZqP+ggSncESeUF/9Su8rWqGclEfEiz1SyU02WX5fUONFfkjzS2Z/F1Li0ofSmf4JqYXIOdCAZqIXAIBAT1OA==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "@peculiar/asn1-x509-attr": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-csr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-csr/-/asn1-csr-2.6.0.tgz", - "integrity": "sha512-BeWIu5VpTIhfRysfEp73SGbwjjoLL/JWXhJ/9mo4vXnz3tRGm+NGm3KNcRzQ9VMVqwYS2RHlolz21svzRXIHPQ==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-ecc": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-ecc/-/asn1-ecc-2.6.0.tgz", - "integrity": "sha512-FF3LMGq6SfAOwUG2sKpPXblibn6XnEIKa+SryvUl5Pik+WR9rmRA3OCiwz8R3lVXnYnyRkSZsSLdml8H3UiOcw==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-pfx": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pfx/-/asn1-pfx-2.6.0.tgz", - "integrity": "sha512-rtUvtf+tyKGgokHHmZzeUojRZJYPxoD/jaN1+VAB4kKR7tXrnDCA/RAWXAIhMJJC+7W27IIRGe9djvxKgsldCQ==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-pkcs8": "^2.6.0", - "@peculiar/asn1-rsa": "^2.6.0", - "@peculiar/asn1-schema": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-pkcs8": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs8/-/asn1-pkcs8-2.6.0.tgz", - "integrity": "sha512-KyQ4D8G/NrS7Fw3XCJrngxmjwO/3htnA0lL9gDICvEQ+GJ+EPFqldcJQTwPIdvx98Tua+WjkdKHSC0/Km7T+lA==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-pkcs9": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-pkcs9/-/asn1-pkcs9-2.6.0.tgz", - "integrity": "sha512-b78OQ6OciW0aqZxdzliXGYHASeCvvw5caqidbpQRYW2mBtXIX2WhofNXTEe7NyxTb0P6J62kAAWLwn0HuMF1Fw==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-pfx": "^2.6.0", - "@peculiar/asn1-pkcs8": "^2.6.0", - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "@peculiar/asn1-x509-attr": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-rsa": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-rsa/-/asn1-rsa-2.6.0.tgz", - "integrity": "sha512-Nu4C19tsrTsCp9fDrH+sdcOKoVfdfoQQ7S3VqjJU6vedR7tY3RLkQ5oguOIB3zFW33USDUuYZnPEQYySlgha4w==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-schema": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.6.0.tgz", - "integrity": "sha512-xNLYLBFTBKkCzEZIw842BxytQQATQv+lDTCEMZ8C196iJcJJMBUZxrhSTxLaohMyKK8QlzRNTRkUmanucnDSqg==", - "license": "MIT", - "dependencies": { - "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-x509": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509/-/asn1-x509-2.6.0.tgz", - "integrity": "sha512-uzYbPEpoQiBoTq0/+jZtpM6Gq6zADBx+JNFP3yqRgziWBxQ/Dt/HcuvRfm9zJTPdRcBqPNdaRHTVwpyiq6iNMA==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "asn1js": "^3.0.6", - "pvtsutils": "^1.3.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/asn1-x509-attr": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-x509-attr/-/asn1-x509-attr-2.6.0.tgz", - "integrity": "sha512-MuIAXFX3/dc8gmoZBkwJWxUWOSvG4MMDntXhrOZpJVMkYX+MYc/rUAU2uJOved9iJEoiUx7//3D8oG83a78UJA==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "asn1js": "^3.0.6", - "tslib": "^2.8.1" - } - }, - "node_modules/@peculiar/x509": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/@peculiar/x509/-/x509-1.14.2.tgz", - "integrity": "sha512-r2w1Hg6pODDs0zfAKHkSS5HLkOLSeburtcgwvlLLWWCixw+MmW3U6kD5ddyvc2Y2YdbGuVwCF2S2ASoU1cFAag==", - "license": "MIT", - "dependencies": { - "@peculiar/asn1-cms": "^2.6.0", - "@peculiar/asn1-csr": "^2.6.0", - "@peculiar/asn1-ecc": "^2.6.0", - "@peculiar/asn1-pkcs9": "^2.6.0", - "@peculiar/asn1-rsa": "^2.6.0", - "@peculiar/asn1-schema": "^2.6.0", - "@peculiar/asn1-x509": "^2.6.0", - "pvtsutils": "^1.3.6", - "reflect-metadata": "^0.2.2", - "tslib": "^2.8.1", - "tsyringe": "^4.10.0" - }, - "engines": { - "node": ">=22.0.0" - } - }, - "node_modules/@simplewebauthn/browser": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/@simplewebauthn/browser/-/browser-13.2.2.tgz", - "integrity": "sha512-FNW1oLQpTJyqG5kkDg5ZsotvWgmBaC6jCHR7Ej0qUNep36Wl9tj2eZu7J5rP+uhXgHaLk+QQ3lqcw2vS5MX1IA==", - "license": "MIT" - }, - "node_modules/@simplewebauthn/server": { - "version": "13.2.2", - "resolved": "https://registry.npmjs.org/@simplewebauthn/server/-/server-13.2.2.tgz", - "integrity": "sha512-HcWLW28yTMGXpwE9VLx9J+N2KEUaELadLrkPEEI9tpI5la70xNEVEsu/C+m3u7uoq4FulLqZQhgBCzR9IZhFpA==", - "license": "MIT", - "dependencies": { - "@hexagon/base64": "^1.1.27", - "@levischuck/tiny-cbor": "^0.2.2", - "@peculiar/asn1-android": "^2.3.10", - "@peculiar/asn1-ecc": "^2.3.8", - "@peculiar/asn1-rsa": "^2.3.8", - "@peculiar/asn1-schema": "^2.3.8", - "@peculiar/asn1-x509": "^2.3.8", - "@peculiar/x509": "^1.13.0" - }, - "engines": { - "node": ">=20.0.0" - } - }, "node_modules/@sinclair/typebox": { "version": "0.27.8", "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", @@ -2793,20 +2547,6 @@ "dev": true, "license": "MIT" }, - "node_modules/asn1js": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/asn1js/-/asn1js-3.0.6.tgz", - "integrity": "sha512-UOCGPYbl0tv8+006qks/dTgV9ajs97X2p0FAbyS2iyCRrmLSRolDaHdp+v/CLgnzHc3fVB+CwYiUmei7ndFcgA==", - "license": "BSD-3-Clause", - "dependencies": { - "pvtsutils": "^1.3.6", - "pvutils": "^1.1.3", - "tslib": "^2.8.1" - }, - "engines": { - "node": ">=12.0.0" - } - }, "node_modules/async": { "version": "3.2.6", "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", @@ -3009,78 +2749,6 @@ "bcrypt": "bin/bcrypt" } }, - "node_modules/better-auth": { - "version": "1.3.34", - "resolved": "https://registry.npmjs.org/better-auth/-/better-auth-1.3.34.tgz", - "integrity": "sha512-LWA52SlvnUBJRbN8VLSTLILPomZY3zZAiLxVJCeSQ5uVmaIKkMBhERitkfJcXB9RJcfl4uP+3EqKkb6hX1/uiw==", - "license": "MIT", - "dependencies": { - "@better-auth/core": "1.3.34", - "@better-auth/telemetry": "1.3.34", - "@better-auth/utils": "0.3.0", - "@better-fetch/fetch": "1.1.18", - "@noble/ciphers": "^2.0.0", - "@noble/hashes": "^2.0.0", - "@simplewebauthn/browser": "^13.1.2", - "@simplewebauthn/server": "^13.1.2", - "better-call": "1.0.19", - "defu": "^6.1.4", - "jose": "^6.1.0", - "kysely": "^0.28.5", - "nanostores": "^1.0.1", - "zod": "^4.1.5" - }, - "peerDependenciesMeta": { - "@lynx-js/react": { - "optional": true - }, - "@sveltejs/kit": { - "optional": true - }, - "next": { - "optional": true - }, - "react": { - "optional": true - }, - "react-dom": { - "optional": true - }, - "solid-js": { - "optional": true - }, - "svelte": { - "optional": true - }, - "vue": { - "optional": true - } - } - }, - "node_modules/better-auth/node_modules/@noble/hashes": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.0.1.tgz", - "integrity": "sha512-XlOlEbQcE9fmuXxrVTXCTlG2nlRXa9Rj3rr5Ue/+tX+nmkgbX720YHh0VR3hBF9xDvwnb8D2shVGOwNx+ulArw==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/better-call": { - "version": "1.0.19", - "resolved": "https://registry.npmjs.org/better-call/-/better-call-1.0.19.tgz", - "integrity": "sha512-sI3GcA1SCVa3H+CDHl8W8qzhlrckwXOTKhqq3OOPXjgn5aTOMIqGY34zLY/pHA6tRRMjTUC3lz5Mi7EbDA24Kw==", - "dependencies": { - "@better-auth/utils": "^0.3.0", - "@better-fetch/fetch": "^1.1.4", - "rou3": "^0.5.1", - "set-cookie-parser": "^2.7.1", - "uncrypto": "^0.1.3" - } - }, "node_modules/big-integer": { "version": "1.6.52", "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.52.tgz", @@ -3811,12 +3479,6 @@ "node": ">=0.10.0" } }, - "node_modules/defu": { - "version": "6.1.4", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", - "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", - "license": "MIT" - }, "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", @@ -4538,39 +4200,39 @@ } }, "node_modules/express": { - "version": "4.21.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.21.2.tgz", - "integrity": "sha512-28HqgMZAmih1Czt9ny7qr6ek2qddF4FclbMzwhCREB6OFfH+rXAnuNCwo1/wFvrtbgsQDb4kSbX9de9lFbrXnA==", + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", "license": "MIT", "dependencies": { "accepts": "~1.3.8", "array-flatten": "1.1.1", - "body-parser": "1.20.3", - "content-disposition": "0.5.4", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", "content-type": "~1.0.4", - "cookie": "0.7.1", - "cookie-signature": "1.0.6", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", "debug": "2.6.9", "depd": "2.0.0", "encodeurl": "~2.0.0", "escape-html": "~1.0.3", "etag": "~1.8.1", - "finalhandler": "1.3.1", - "fresh": "0.5.2", - "http-errors": "2.0.0", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", "merge-descriptors": "1.0.3", "methods": "~1.1.2", - "on-finished": "2.4.1", + "on-finished": "~2.4.1", "parseurl": "~1.3.3", - "path-to-regexp": "0.1.12", + "path-to-regexp": "~0.1.12", "proxy-addr": "~2.0.7", - "qs": "6.13.0", + "qs": "~6.14.0", "range-parser": "~1.2.1", "safe-buffer": "5.2.1", - "send": "0.19.0", - "serve-static": "1.16.2", + "send": "~0.19.0", + "serve-static": "~1.16.2", "setprototypeof": "1.2.0", - "statuses": "2.0.1", + "statuses": "~2.0.1", "type-is": "~1.6.18", "utils-merge": "1.0.1", "vary": "~1.1.2" @@ -4616,6 +4278,21 @@ "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", "license": "MIT" }, + "node_modules/express/node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/fast-csv": { "version": "4.3.6", "resolved": "https://registry.npmjs.org/fast-csv/-/fast-csv-4.3.6.tgz", @@ -6067,15 +5744,6 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "node_modules/jose": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.2.tgz", - "integrity": "sha512-MpcPtHLE5EmztuFIqB0vzHAWJPpmN1E6L4oo+kze56LIs3MyXIj9ZHMDxqOvkP38gBR7K1v3jqd4WU2+nrfONQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6272,6 +5940,8 @@ "resolved": "https://registry.npmjs.org/kysely/-/kysely-0.28.8.tgz", "integrity": "sha512-QUOgl5ZrS9IRuhq5FvOKFSsD/3+IA6MLE81/bOOTRA/YQpKDza2sFdN5g6JCB9BOpqMJDGefLCQ9F12hRS13TA==", "license": "MIT", + "optional": true, + "peer": true, "engines": { "node": ">=20.0.0" } @@ -6745,21 +6415,6 @@ "node": ">= 10.16.0" } }, - "node_modules/nanostores": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/nanostores/-/nanostores-1.0.1.tgz", - "integrity": "sha512-kNZ9xnoJYKg/AfxjrVL4SS0fKX++4awQReGqWnwTRHxeHGZ1FJFVgTqr/eMrNQdp0Tz7M7tG/TDaX8QfHDwVCw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/ai" - } - ], - "license": "MIT", - "engines": { - "node": "^20.0.0 || >=22.0.0" - } - }, "node_modules/natural-compare": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", @@ -7431,24 +7086,6 @@ ], "license": "MIT" }, - "node_modules/pvtsutils": { - "version": "1.3.6", - "resolved": "https://registry.npmjs.org/pvtsutils/-/pvtsutils-1.3.6.tgz", - "integrity": "sha512-PLgQXQ6H2FWCaeRak8vvk1GW462lMxB5s3Jm673N82zI4vqtVUPuZdffdZbPDFRoU8kAhItWFtPCWiPpp4/EDg==", - "license": "MIT", - "dependencies": { - "tslib": "^2.8.1" - } - }, - "node_modules/pvutils": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/pvutils/-/pvutils-1.1.5.tgz", - "integrity": "sha512-KTqnxsgGiQ6ZAzZCVlJH5eOjSnvlyEgx1m8bkRJfOhmGRqfo5KLvmAlACQkrjEtOQ4B7wF9TdSLIs9O90MX9xA==", - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -7552,12 +7189,6 @@ "node": ">=8.10.0" } }, - "node_modules/reflect-metadata": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.2.2.tgz", - "integrity": "sha512-urBwgfrvVP/eAyXx4hluJivBKzuEbSQs9rKWCrCkbSxNv8mxPcUZKeuoF3Uy4mJl3Lwprp6yy5/39VWigZ4K6Q==", - "license": "Apache-2.0" - }, "node_modules/require-directory": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", @@ -7655,12 +7286,6 @@ "rimraf": "bin.js" } }, - "node_modules/rou3": { - "version": "0.5.1", - "resolved": "https://registry.npmjs.org/rou3/-/rou3-0.5.1.tgz", - "integrity": "sha512-OXMmJ3zRk2xeXFGfA3K+EOPHC5u7RDFG7lIOx0X1pdnhUkI8MdVrbV+sNsD80ElpUZ+MRHdyxPnFthq9VHs8uQ==", - "license": "MIT" - }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -7772,12 +7397,6 @@ "node": ">= 0.8.0" } }, - "node_modules/set-cookie-parser": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.2.tgz", - "integrity": "sha512-oeM1lpU/UvhTxw+g3cIfxXHyJRc/uidd3yK1P242gzHds0udQBYzs3y8j4gCCW+ZJ7ad0yctld8RYO+bdurlvw==", - "license": "MIT" - }, "node_modules/setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", @@ -8274,30 +7893,6 @@ "node": "*" } }, - "node_modules/tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", - "license": "0BSD" - }, - "node_modules/tsyringe": { - "version": "4.10.0", - "resolved": "https://registry.npmjs.org/tsyringe/-/tsyringe-4.10.0.tgz", - "integrity": "sha512-axr3IdNuVIxnaK5XGEUFTu3YmAQ6lllgrvqfEoR16g/HGnYY/6We4oWENtAnzK6/LpJ2ur9PAb80RBt7/U4ugw==", - "license": "MIT", - "dependencies": { - "tslib": "^1.9.3" - }, - "engines": { - "node": ">= 6.0.0" - } - }, - "node_modules/tsyringe/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -8353,12 +7948,6 @@ "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "license": "MIT" }, - "node_modules/uncrypto": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", - "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", - "license": "MIT" - }, "node_modules/undefsafe": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", diff --git a/package.json b/package.json index 791f899..c27bef8 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,6 @@ "start": "node src/index.js", "test": "node --experimental-vm-modules node_modules/.bin/jest", "db:generate": "drizzle-kit generate", - "db:migrate": "node src/db/migrate.js", "db:push": "drizzle-kit push", "db:studio": "drizzle-kit studio", "db:seed": "node src/db/seeds/admin.seed.js", @@ -20,7 +19,6 @@ "dependencies": { "axios": "^1.13.2", "bcryptjs": "^3.0.3", - "better-auth": "^1.3.34", "cookie-parser": "^1.4.7", "cors": "^2.8.5", "dotenv": "^16.4.5", diff --git a/run-migration.js b/run-migration.js deleted file mode 100644 index 15eb5ef..0000000 --- a/run-migration.js +++ /dev/null @@ -1,32 +0,0 @@ -import pkg from 'pg'; -const { Pool } = pkg; -import dotenv from 'dotenv'; -import { readFileSync } from 'fs'; - -dotenv.config(); - -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', -}); - -async function runMigration() { - console.log('⏳ Running project_users migration...'); - - try { - const sql = readFileSync('./src/db/migrations/add_project_users.sql', 'utf8'); - await pool.query(sql); - console.log('βœ… Migration completed successfully'); - process.exit(0); - } catch (error) { - console.error('❌ Migration failed:', error.message); - process.exit(1); - } finally { - await pool.end(); - } -} - -runMigration(); diff --git a/src/controllers/admin.controller.js b/src/controllers/admin.controller.js index 9464e8c..ea4eeb5 100644 --- a/src/controllers/admin.controller.js +++ b/src/controllers/admin.controller.js @@ -12,7 +12,7 @@ import * as emailAccountService from '../services/email-account.service.js'; * POST /api/admin/users */ export const createUser = async (req, res) => { - const { username, email, emailPassword, firstName, lastName } = req.body; + const { username, email, emailPassword, firstName, lastName, role } = req.body; const adminId = req.userId; const ipAddress = req.ip || req.connection.remoteAddress; const userAgent = req.headers['user-agent']; @@ -33,13 +33,16 @@ export const createUser = async (req, res) => { const tempPassword = generateTempPassword(12); const hashedTempPassword = await hashPassword(tempPassword); + // Validuj role - iba 'admin' alebo 'member' + const validRole = role === 'admin' ? 'admin' : 'member'; + // Vytvor usera const [newUser] = await db .insert(users) .values({ username, tempPassword: hashedTempPassword, - role: 'member', // VΕΎdy member, nie admin + role: validRole, firstName: firstName || null, lastName: lastName || null, changedPassword: false, @@ -74,7 +77,7 @@ export const createUser = async (req, res) => { } // Log user creation - await logUserCreation(adminId, newUser.id, username, 'member', ipAddress, userAgent); + await logUserCreation(adminId, newUser.id, username, validRole, ipAddress, userAgent); res.status(201).json({ success: true, diff --git a/src/controllers/todo.controller.js b/src/controllers/todo.controller.js index ded874a..539c5b3 100644 --- a/src/controllers/todo.controller.js +++ b/src/controllers/todo.controller.js @@ -7,14 +7,21 @@ import { formatErrorResponse } from '../utils/errors.js'; */ export const getAllTodos = async (req, res) => { try { - const { search, projectId, companyId, assignedTo, status } = req.query; + const { search, projectId, companyId, assignedTo, status, completed, priority } = req.query; + + // Handle both 'status' and 'completed' query params + let statusFilter = status; + if (completed !== undefined) { + statusFilter = completed === 'true' ? 'completed' : 'pending'; + } const filters = { searchTerm: search, projectId, companyId, assignedTo, - status, + status: statusFilter, + priority, }; const todos = await todoService.getAllTodos(filters); diff --git a/src/db/create_project_users_table.js b/src/db/create_project_users_table.js deleted file mode 100644 index 541af9b..0000000 --- a/src/db/create_project_users_table.js +++ /dev/null @@ -1,54 +0,0 @@ -import { db } from '../config/database.js'; -import { sql } from 'drizzle-orm'; - -async function createProjectUsersTable() { - console.log('⏳ Creating project_users table...'); - - try { - // Check if table exists - const result = await db.execute(sql` - SELECT EXISTS ( - SELECT FROM information_schema.tables - WHERE table_schema = 'public' - AND table_name = 'project_users' - ); - `); - - const tableExists = result.rows[0]?.exists; - - if (tableExists) { - console.log('βœ… project_users table already exists'); - process.exit(0); - } - - // Create the table - await db.execute(sql` - CREATE TABLE IF NOT EXISTS project_users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - role TEXT, - added_by UUID REFERENCES users(id) ON DELETE SET NULL, - added_at TIMESTAMP NOT NULL DEFAULT NOW(), - CONSTRAINT project_user_unique UNIQUE(project_id, user_id) - ); - `); - - // Create indexes - await db.execute(sql` - CREATE INDEX IF NOT EXISTS idx_project_users_project_id ON project_users(project_id); - `); - - await db.execute(sql` - CREATE INDEX IF NOT EXISTS idx_project_users_user_id ON project_users(user_id); - `); - - console.log('βœ… project_users table created successfully'); - process.exit(0); - } catch (error) { - console.error('❌ Failed to create table:', error); - process.exit(1); - } -} - -createProjectUsersTable(); diff --git a/src/db/migrate.js b/src/db/migrate.js deleted file mode 100644 index e905060..0000000 --- a/src/db/migrate.js +++ /dev/null @@ -1,32 +0,0 @@ -import { drizzle } from 'drizzle-orm/node-postgres'; -import { migrate } from 'drizzle-orm/node-postgres/migrator'; -import pkg from 'pg'; -const { Pool } = pkg; -import dotenv from 'dotenv'; - -dotenv.config(); - -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 runMigrations() { - console.log('⏳ Running migrations...'); - - try { - await migrate(db, { migrationsFolder: './src/db/migrations' }); - console.log('βœ… Migrations completed successfully'); - process.exit(0); - } catch (error) { - console.error('❌ Migration failed:', error); - process.exit(1); - } -} - -runMigrations(); diff --git a/src/db/migrations/0000_legal_karnak.sql b/src/db/migrations/0000_legal_karnak.sql deleted file mode 100644 index a3de756..0000000 --- a/src/db/migrations/0000_legal_karnak.sql +++ /dev/null @@ -1,36 +0,0 @@ -CREATE TYPE "public"."role" AS ENUM('admin', 'member');--> statement-breakpoint -CREATE TABLE "audit_logs" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "user_id" uuid, - "action" text NOT NULL, - "resource" text NOT NULL, - "resource_id" text, - "old_value" text, - "new_value" text, - "ip_address" text, - "user_agent" text, - "success" boolean DEFAULT true NOT NULL, - "error_message" text, - "created_at" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE TABLE "users" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "username" text NOT NULL, - "email" text, - "email_password" text, - "jmap_account_id" text, - "first_name" text, - "last_name" text, - "password" text, - "temp_password" text, - "changed_password" boolean DEFAULT false, - "role" "role" DEFAULT 'member' NOT NULL, - "last_login" timestamp, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL, - CONSTRAINT "users_username_unique" UNIQUE("username"), - CONSTRAINT "users_email_unique" UNIQUE("email") -); ---> statement-breakpoint -ALTER TABLE "audit_logs" ADD CONSTRAINT "audit_logs_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE set null ON UPDATE no action; \ No newline at end of file diff --git a/src/db/migrations/0001_slow_drax.sql b/src/db/migrations/0001_slow_drax.sql deleted file mode 100644 index 8a782b4..0000000 --- a/src/db/migrations/0001_slow_drax.sql +++ /dev/null @@ -1,34 +0,0 @@ -CREATE TABLE "contacts" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "user_id" uuid NOT NULL, - "email" text NOT NULL, - "name" text, - "notes" text, - "added_at" timestamp DEFAULT now() NOT NULL, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -CREATE TABLE "emails" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "user_id" uuid NOT NULL, - "contact_id" uuid, - "jmap_id" text, - "message_id" text, - "thread_id" text, - "in_reply_to" text, - "from" text, - "to" text, - "subject" text, - "body" text, - "is_read" boolean DEFAULT false NOT NULL, - "date" timestamp, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL, - CONSTRAINT "emails_jmap_id_unique" UNIQUE("jmap_id"), - CONSTRAINT "emails_message_id_unique" UNIQUE("message_id") -); ---> statement-breakpoint -ALTER TABLE "contacts" ADD CONSTRAINT "contacts_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "emails" ADD CONSTRAINT "emails_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint -ALTER TABLE "emails" ADD CONSTRAINT "emails_contact_id_contacts_id_fk" FOREIGN KEY ("contact_id") REFERENCES "public"."contacts"("id") ON DELETE cascade ON UPDATE no action; \ No newline at end of file diff --git a/src/db/migrations/0002_parallel_guardian.sql b/src/db/migrations/0002_parallel_guardian.sql deleted file mode 100644 index 8dbc6f9..0000000 --- a/src/db/migrations/0002_parallel_guardian.sql +++ /dev/null @@ -1,17 +0,0 @@ -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; \ No newline at end of file diff --git a/src/db/migrations/0003_add_timesheets_table.sql b/src/db/migrations/0003_add_timesheets_table.sql deleted file mode 100644 index 94add12..0000000 --- a/src/db/migrations/0003_add_timesheets_table.sql +++ /dev/null @@ -1,15 +0,0 @@ -CREATE TABLE "timesheets" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "user_id" uuid NOT NULL, - "file_name" text NOT NULL, - "file_path" text NOT NULL, - "file_type" text NOT NULL, - "file_size" integer NOT NULL, - "year" integer NOT NULL, - "month" integer NOT NULL, - "uploaded_at" timestamp DEFAULT now() NOT NULL, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "timesheets" ADD CONSTRAINT "timesheets_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; diff --git a/src/db/migrations/0004_add_time_entries_table.sql b/src/db/migrations/0004_add_time_entries_table.sql deleted file mode 100644 index c201213..0000000 --- a/src/db/migrations/0004_add_time_entries_table.sql +++ /dev/null @@ -1,23 +0,0 @@ -CREATE TABLE "time_entries" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "user_id" uuid NOT NULL, - "project_id" uuid, - "todo_id" uuid, - "company_id" uuid, - "start_time" timestamp NOT NULL, - "end_time" timestamp, - "duration" integer, - "description" text, - "is_running" boolean DEFAULT false NOT NULL, - "is_edited" boolean DEFAULT false NOT NULL, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "time_entries" ADD CONSTRAINT "time_entries_user_id_users_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."users"("id") ON DELETE cascade ON UPDATE no action; ---> statement-breakpoint -ALTER TABLE "time_entries" ADD CONSTRAINT "time_entries_project_id_projects_id_fk" FOREIGN KEY ("project_id") REFERENCES "public"."projects"("id") ON DELETE set null ON UPDATE no action; ---> statement-breakpoint -ALTER TABLE "time_entries" ADD CONSTRAINT "time_entries_todo_id_todos_id_fk" FOREIGN KEY ("todo_id") REFERENCES "public"."todos"("id") ON DELETE set null ON UPDATE no action; ---> statement-breakpoint -ALTER TABLE "time_entries" ADD CONSTRAINT "time_entries_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE set null ON UPDATE no action; diff --git a/src/db/migrations/0005_add_is_generated_timesheets.sql b/src/db/migrations/0005_add_is_generated_timesheets.sql deleted file mode 100644 index ea0af16..0000000 --- a/src/db/migrations/0005_add_is_generated_timesheets.sql +++ /dev/null @@ -1,3 +0,0 @@ --- Add flag to mark system-generated timesheets -ALTER TABLE timesheets -ADD COLUMN IF NOT EXISTS is_generated BOOLEAN NOT NULL DEFAULT FALSE; diff --git a/src/db/migrations/0006_add_todo_users_table.sql b/src/db/migrations/0006_add_todo_users_table.sql deleted file mode 100644 index 89cd5b4..0000000 --- a/src/db/migrations/0006_add_todo_users_table.sql +++ /dev/null @@ -1,29 +0,0 @@ --- Migration: Add todo_users junction table and migrate from assignedTo --- Created: 2025-11-24 --- Description: Allows many-to-many relationship between todos and users - --- Create todo_users junction table -CREATE TABLE IF NOT EXISTS todo_users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - todo_id UUID NOT NULL REFERENCES todos(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - assigned_by UUID REFERENCES users(id) ON DELETE SET NULL, - assigned_at TIMESTAMP NOT NULL DEFAULT NOW(), - CONSTRAINT todo_user_unique UNIQUE(todo_id, user_id) -); - --- Create indexes for better query performance -CREATE INDEX IF NOT EXISTS idx_todo_users_todo_id ON todo_users(todo_id); -CREATE INDEX IF NOT EXISTS idx_todo_users_user_id ON todo_users(user_id); - --- Migrate existing assignedTo data to todo_users table -INSERT INTO todo_users (todo_id, user_id, assigned_by, assigned_at) -SELECT id, assigned_to, created_by, created_at -FROM todos -WHERE assigned_to IS NOT NULL; - --- Drop the old assigned_to column -ALTER TABLE todos DROP COLUMN IF EXISTS assigned_to; - --- Add comment -COMMENT ON TABLE todo_users IS 'Junction table for many-to-many relationship between todos and users (assigned users)'; diff --git a/src/db/migrations/0007_add_company_remind_table.sql b/src/db/migrations/0007_add_company_remind_table.sql deleted file mode 100644 index fba40fb..0000000 --- a/src/db/migrations/0007_add_company_remind_table.sql +++ /dev/null @@ -1,12 +0,0 @@ -CREATE TABLE IF NOT EXISTS "company_remind" ( - "id" uuid PRIMARY KEY DEFAULT gen_random_uuid() NOT NULL, - "company_id" uuid NOT NULL, - "description" text NOT NULL, - "is_checked" boolean DEFAULT false NOT NULL, - "created_at" timestamp DEFAULT now() NOT NULL, - "updated_at" timestamp DEFAULT now() NOT NULL -); ---> statement-breakpoint -ALTER TABLE "company_remind" ADD CONSTRAINT "company_remind_company_id_companies_id_fk" FOREIGN KEY ("company_id") REFERENCES "public"."companies"("id") ON DELETE cascade ON UPDATE no action; ---> statement-breakpoint -CREATE INDEX IF NOT EXISTS "company_remind_company_id_idx" ON "company_remind" ("company_id"); diff --git a/src/db/migrations/0008_add_company_id_to_emails.sql b/src/db/migrations/0008_add_company_id_to_emails.sql deleted file mode 100644 index dc3a957..0000000 --- a/src/db/migrations/0008_add_company_id_to_emails.sql +++ /dev/null @@ -1,20 +0,0 @@ -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name = 'emails' AND column_name = 'company_id' - ) THEN - ALTER TABLE emails - ADD COLUMN company_id UUID REFERENCES companies(id) ON DELETE SET NULL; - END IF; -END $$; - -CREATE INDEX IF NOT EXISTS idx_emails_company_id ON emails(company_id); -CREATE INDEX IF NOT EXISTS idx_emails_company_thread ON emails(company_id, thread_id); - -UPDATE emails e -SET company_id = c.company_id -FROM contacts c -WHERE e.contact_id = c.id - AND c.company_id IS NOT NULL - AND (e.company_id IS NULL OR e.company_id <> c.company_id); diff --git a/src/db/migrations/add_company_link_and_reminders.sql b/src/db/migrations/add_company_link_and_reminders.sql deleted file mode 100644 index c07cd02..0000000 --- a/src/db/migrations/add_company_link_and_reminders.sql +++ /dev/null @@ -1,31 +0,0 @@ --- Add company_id to contacts table -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name='contacts' AND column_name='company_id' - ) THEN - ALTER TABLE contacts ADD COLUMN company_id UUID REFERENCES companies(id) ON DELETE SET NULL; - CREATE INDEX idx_contacts_company_id ON contacts(company_id); - END IF; -END $$; - --- Add reminder fields to notes table -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name='notes' AND column_name='reminder_date' - ) THEN - ALTER TABLE notes ADD COLUMN reminder_date TIMESTAMP; - CREATE INDEX idx_notes_reminder_date ON notes(reminder_date) WHERE reminder_date IS NOT NULL; - END IF; - - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name='notes' AND column_name='reminder_sent' - ) THEN - ALTER TABLE notes ADD COLUMN reminder_sent BOOLEAN NOT NULL DEFAULT false; - CREATE INDEX idx_notes_reminder_pending ON notes(reminder_date, reminder_sent) WHERE reminder_date IS NOT NULL AND reminder_sent = false; - END IF; -END $$; diff --git a/src/db/migrations/add_crm_tables.sql b/src/db/migrations/add_crm_tables.sql deleted file mode 100644 index ff82ee9..0000000 --- a/src/db/migrations/add_crm_tables.sql +++ /dev/null @@ -1,118 +0,0 @@ --- Add new enum types -DO $$ BEGIN - CREATE TYPE project_status AS ENUM('active', 'completed', 'on_hold', 'cancelled'); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; - -DO $$ BEGIN - CREATE TYPE todo_status AS ENUM('pending', 'in_progress', 'completed', 'cancelled'); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; - -DO $$ BEGIN - CREATE TYPE todo_priority AS ENUM('low', 'medium', 'high', 'urgent'); -EXCEPTION - WHEN duplicate_object THEN null; -END $$; - --- Create companies table -CREATE TABLE IF NOT EXISTS companies ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name TEXT NOT NULL, - description TEXT, - address TEXT, - city TEXT, - country TEXT, - phone TEXT, - email TEXT, - website TEXT, - created_by UUID REFERENCES users(id) ON DELETE SET NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW() -); - --- Create projects table -CREATE TABLE IF NOT EXISTS projects ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - name TEXT NOT NULL, - description TEXT, - company_id UUID REFERENCES companies(id) ON DELETE CASCADE, - status project_status NOT NULL DEFAULT 'active', - start_date TIMESTAMP, - end_date TIMESTAMP, - created_by UUID REFERENCES users(id) ON DELETE SET NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW() -); - --- Create todos table -CREATE TABLE IF NOT EXISTS todos ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - title TEXT NOT NULL, - description TEXT, - project_id UUID REFERENCES projects(id) ON DELETE CASCADE, - company_id UUID REFERENCES companies(id) ON DELETE CASCADE, - assigned_to UUID REFERENCES users(id) ON DELETE SET NULL, - status todo_status NOT NULL DEFAULT 'pending', - priority todo_priority NOT NULL DEFAULT 'medium', - due_date TIMESTAMP, - completed_at TIMESTAMP, - created_by UUID REFERENCES users(id) ON DELETE SET NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW() -); - --- Create notes table -CREATE TABLE IF NOT EXISTS notes ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - title TEXT, - content TEXT NOT NULL, - company_id UUID REFERENCES companies(id) ON DELETE CASCADE, - project_id UUID REFERENCES projects(id) ON DELETE CASCADE, - todo_id UUID REFERENCES todos(id) ON DELETE CASCADE, - contact_id UUID REFERENCES contacts(id) ON DELETE CASCADE, - created_by UUID REFERENCES users(id) ON DELETE SET NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - updated_at TIMESTAMP NOT NULL DEFAULT NOW() -); - --- Add project_id to timesheets table if not exists -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name='timesheets' AND column_name='project_id' - ) THEN - ALTER TABLE timesheets ADD COLUMN project_id UUID REFERENCES projects(id) ON DELETE SET NULL; - END IF; -END $$; - --- Add is_generated flag to timesheets if not exists -DO $$ -BEGIN - IF NOT EXISTS ( - SELECT 1 FROM information_schema.columns - WHERE table_name='timesheets' AND column_name='is_generated' - ) THEN - ALTER TABLE timesheets ADD COLUMN is_generated BOOLEAN NOT NULL DEFAULT FALSE; - END IF; -END $$; - --- Create indexes for better query performance -CREATE INDEX IF NOT EXISTS idx_companies_created_at ON companies(created_at); -CREATE INDEX IF NOT EXISTS idx_projects_company_id ON projects(company_id); -CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status); -CREATE INDEX IF NOT EXISTS idx_projects_created_at ON projects(created_at); -CREATE INDEX IF NOT EXISTS idx_todos_project_id ON todos(project_id); -CREATE INDEX IF NOT EXISTS idx_todos_company_id ON todos(company_id); -CREATE INDEX IF NOT EXISTS idx_todos_assigned_to ON todos(assigned_to); -CREATE INDEX IF NOT EXISTS idx_todos_status ON todos(status); -CREATE INDEX IF NOT EXISTS idx_todos_created_at ON todos(created_at); -CREATE INDEX IF NOT EXISTS idx_notes_company_id ON notes(company_id); -CREATE INDEX IF NOT EXISTS idx_notes_project_id ON notes(project_id); -CREATE INDEX IF NOT EXISTS idx_notes_todo_id ON notes(todo_id); -CREATE INDEX IF NOT EXISTS idx_notes_contact_id ON notes(contact_id); -CREATE INDEX IF NOT EXISTS idx_notes_created_at ON notes(created_at); -CREATE INDEX IF NOT EXISTS idx_timesheets_project_id ON timesheets(project_id); diff --git a/src/db/migrations/add_project_users.sql b/src/db/migrations/add_project_users.sql deleted file mode 100644 index f46901b..0000000 --- a/src/db/migrations/add_project_users.sql +++ /dev/null @@ -1,21 +0,0 @@ --- Migration: Add project_users junction table for project team management --- Created: 2025-11-21 --- Description: Allows many-to-many relationship between projects and users - --- Create project_users junction table -CREATE TABLE IF NOT EXISTS project_users ( - id UUID PRIMARY KEY DEFAULT gen_random_uuid(), - project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE, - user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, - role TEXT, - added_by UUID REFERENCES users(id) ON DELETE SET NULL, - added_at TIMESTAMP NOT NULL DEFAULT NOW(), - CONSTRAINT project_user_unique UNIQUE(project_id, user_id) -); - --- Create indexes for better query performance -CREATE INDEX IF NOT EXISTS idx_project_users_project_id ON project_users(project_id); -CREATE INDEX IF NOT EXISTS idx_project_users_user_id ON project_users(user_id); - --- Add comment -COMMENT ON TABLE project_users IS 'Junction table for many-to-many relationship between projects and users (project team members)'; diff --git a/src/db/migrations/archive/migrate-data-only.js b/src/db/migrations/archive/migrate-data-only.js deleted file mode 100644 index 875cea4..0000000 --- a/src/db/migrations/archive/migrate-data-only.js +++ /dev/null @@ -1,138 +0,0 @@ -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); -}); diff --git a/src/db/migrations/archive/migrate-to-email-accounts.js b/src/db/migrations/archive/migrate-to-email-accounts.js deleted file mode 100644 index f93d89b..0000000 --- a/src/db/migrations/archive/migrate-to-email-accounts.js +++ /dev/null @@ -1,179 +0,0 @@ -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); -}); diff --git a/src/db/migrations/meta/0000_snapshot.json b/src/db/migrations/meta/0000_snapshot.json deleted file mode 100644 index ae30929..0000000 --- a/src/db/migrations/meta/0000_snapshot.json +++ /dev/null @@ -1,248 +0,0 @@ -{ - "id": "d81153f7-e0c6-4843-bee9-21a7129f7d01", - "prevId": "00000000-0000-0000-0000-000000000000", - "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.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": {} - } -} \ No newline at end of file diff --git a/src/db/migrations/meta/0001_snapshot.json b/src/db/migrations/meta/0001_snapshot.json deleted file mode 100644 index 2477f74..0000000 --- a/src/db/migrations/meta/0001_snapshot.json +++ /dev/null @@ -1,476 +0,0 @@ -{ - "id": "1b8c1e0f-8476-470c-a641-b3c350a2c1a4", - "prevId": "d81153f7-e0c6-4843-bee9-21a7129f7d01", - "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": { - "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" - } - }, - "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 - }, - "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_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": {} - } -} \ No newline at end of file diff --git a/src/db/migrations/meta/0002_snapshot.json b/src/db/migrations/meta/0002_snapshot.json deleted file mode 100644 index e32a6ff..0000000 --- a/src/db/migrations/meta/0002_snapshot.json +++ /dev/null @@ -1,600 +0,0 @@ -{ - "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": {} - } -} \ No newline at end of file diff --git a/src/db/migrations/meta/_journal.json b/src/db/migrations/meta/_journal.json deleted file mode 100644 index 372134e..0000000 --- a/src/db/migrations/meta/_journal.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "version": "7", - "dialect": "postgresql", - "entries": [ - { - "idx": 0, - "version": "7", - "when": 1763450484405, - "tag": "0000_legal_karnak", - "breakpoints": true - }, - { - "idx": 1, - "version": "7", - "when": 1763457837858, - "tag": "0001_slow_drax", - "breakpoints": true - }, - { - "idx": 2, - "version": "7", - "when": 1763547133084, - "tag": "0002_parallel_guardian", - "breakpoints": true - } - ] -} \ No newline at end of file diff --git a/src/middlewares/security/rateLimiter.js b/src/middlewares/security/rateLimiter.js index 3a3e459..b38fd37 100644 --- a/src/middlewares/security/rateLimiter.js +++ b/src/middlewares/security/rateLimiter.js @@ -70,7 +70,7 @@ export const apiRateLimiter = rateLimit({ */ export const sensitiveOperationLimiter = rateLimit({ windowMs: 15 * 60 * 1000, - max: process.env.NODE_ENV === 'production' ? 3 : 50, + max: process.env.NODE_ENV === 'production' ? 10 : 50, message: { success: false, error: { diff --git a/src/routes/timesheet.routes.js b/src/routes/timesheet.routes.js index e41f6bb..170f2d5 100644 --- a/src/routes/timesheet.routes.js +++ b/src/routes/timesheet.routes.js @@ -22,7 +22,7 @@ await fs.mkdir(uploadsDir, { recursive: true }); const upload = multer({ storage: multer.memoryStorage(), limits: { - fileSize: 10 * 1024 * 1024, // 10MB limit + fileSize: 5 * 1024 * 1024, // 5MB limit }, fileFilter: (req, file, cb) => { const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet', 'application/vnd.ms-excel']; diff --git a/src/services/todo.service.js b/src/services/todo.service.js index b23dbc3..b07f085 100644 --- a/src/services/todo.service.js +++ b/src/services/todo.service.js @@ -8,7 +8,7 @@ import { NotFoundError } from '../utils/errors.js'; * Optionally filter by search, project, company, assigned user, or status */ export const getAllTodos = async (filters = {}) => { - const { searchTerm, projectId, companyId, assignedTo, status } = filters; + const { searchTerm, projectId, companyId, assignedTo, status, priority } = filters; // If filtering by assignedTo, we need to join with todo_users if (assignedTo) { @@ -48,6 +48,10 @@ export const getAllTodos = async (filters = {}) => { conditions.push(eq(todos.status, status)); } + if (priority) { + conditions.push(eq(todos.priority, priority)); + } + if (conditions.length > 0) { query = query.where(and(...conditions)); } @@ -119,6 +123,10 @@ export const getAllTodos = async (filters = {}) => { conditions.push(eq(todos.status, status)); } + if (priority) { + conditions.push(eq(todos.priority, priority)); + } + if (conditions.length > 0) { query = query.where(and(...conditions)); } diff --git a/src/utils/password.js b/src/utils/password.js index 1dc4d86..f89678e 100644 --- a/src/utils/password.js +++ b/src/utils/password.js @@ -70,9 +70,12 @@ export const encryptPassword = (text) => { if (!process.env.JWT_SECRET) { throw new Error('JWT_SECRET environment variable is required for password encryption'); } + if (!process.env.ENCRYPTION_SALT) { + throw new Error('ENCRYPTION_SALT environment variable is required for password encryption'); + } const algorithm = 'aes-256-gcm'; - const key = crypto.scryptSync(process.env.JWT_SECRET, 'salt', 32); + const key = crypto.scryptSync(process.env.JWT_SECRET, process.env.ENCRYPTION_SALT, 32); const iv = crypto.randomBytes(16); const cipher = crypto.createCipheriv(algorithm, key, iv); @@ -93,9 +96,12 @@ export const decryptPassword = (encryptedText) => { if (!process.env.JWT_SECRET) { throw new Error('JWT_SECRET environment variable is required for password decryption'); } + if (!process.env.ENCRYPTION_SALT) { + throw new Error('ENCRYPTION_SALT environment variable is required for password decryption'); + } const algorithm = 'aes-256-gcm'; - const key = crypto.scryptSync(process.env.JWT_SECRET, 'salt', 32); + const key = crypto.scryptSync(process.env.JWT_SECRET, process.env.ENCRYPTION_SALT, 32); const parts = encryptedText.split(':'); const iv = Buffer.from(parts[0], 'hex'); diff --git a/src/validators/auth.validators.js b/src/validators/auth.validators.js index 37f8247..bcc5173 100644 --- a/src/validators/auth.validators.js +++ b/src/validators/auth.validators.js @@ -75,6 +75,7 @@ export const createUserSchema = z.object({ emailPassword: z.string().min(1).optional(), firstName: z.string().max(100).optional(), lastName: z.string().max(100).optional(), + role: z.enum(['admin', 'member']).optional(), }); // Update user schema