Production deployment with Docker and full system fixes

- Added Docker support (Dockerfiles, docker-compose.yml)
- Fixed authentication and authorization (token storage, CORS, permissions)
- Fixed API response transformations for all modules
- Added production deployment scripts and guides
- Fixed frontend permission checks and module access
- Added database seeding script for production
- Complete documentation for deployment and configuration

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Talal Sharabi
2026-02-11 11:25:20 +04:00
parent 35daa52767
commit f31d71ff5a
52 changed files with 9359 additions and 1578 deletions

View File

@@ -3,6 +3,7 @@
generator client {
provider = "prisma-client-js"
binaryTargets = ["native", "linux-musl-openssl-3.0.x"]
}
datasource db {

303
backend/prisma/seed-prod.js Normal file
View File

@@ -0,0 +1,303 @@
const { PrismaClient } = require('@prisma/client');
const bcrypt = require('bcryptjs');
const prisma = new PrismaClient();
async function main() {
console.log('🌱 Starting database seeding...');
// Create Departments
const salesDept = await prisma.department.create({
data: {
name: 'Sales Department',
nameAr: 'قسم المبيعات',
code: 'SALES',
description: 'Sales and Business Development',
},
});
const itDept = await prisma.department.create({
data: {
name: 'IT Department',
nameAr: 'قسم تقنية المعلومات',
code: 'IT',
description: 'Information Technology',
},
});
const hrDept = await prisma.department.create({
data: {
name: 'HR Department',
nameAr: 'قسم الموارد البشرية',
code: 'HR',
description: 'Human Resources',
},
});
console.log('✅ Created departments');
// Create Positions
const gmPosition = await prisma.position.create({
data: {
title: 'General Manager',
titleAr: 'المدير العام',
code: 'GM',
departmentId: salesDept.id,
level: 1,
description: 'Chief Executive - Full Access',
},
});
const salesManagerPosition = await prisma.position.create({
data: {
title: 'Sales Manager',
titleAr: 'مدير المبيعات',
code: 'SM',
departmentId: salesDept.id,
level: 2,
description: 'Sales Department Manager',
},
});
const salesRepPosition = await prisma.position.create({
data: {
title: 'Sales Representative',
titleAr: 'مندوب مبيعات',
code: 'SR',
departmentId: salesDept.id,
level: 3,
description: 'Sales Representative',
},
});
console.log('✅ Created positions');
// Create Permissions for GM (Full Access)
const modules = ['contacts', 'crm', 'inventory', 'projects', 'hr', 'marketing'];
for (const module of modules) {
await prisma.positionPermission.create({
data: {
positionId: gmPosition.id,
module: module,
resource: 'all',
actions: ['create', 'read', 'update', 'delete', 'export', 'approve'],
},
});
}
// Create Permissions for Sales Manager
await prisma.positionPermission.create({
data: {
positionId: salesManagerPosition.id,
module: 'contacts',
resource: 'contacts',
actions: ['create', 'read', 'update', 'delete', 'export'],
},
});
await prisma.positionPermission.create({
data: {
positionId: salesManagerPosition.id,
module: 'crm',
resource: 'deals',
actions: ['create', 'read', 'update', 'approve', 'export'],
},
});
// Create Permissions for Sales Rep
await prisma.positionPermission.create({
data: {
positionId: salesRepPosition.id,
module: 'contacts',
resource: 'contacts',
actions: ['create', 'read', 'update'],
},
});
await prisma.positionPermission.create({
data: {
positionId: salesRepPosition.id,
module: 'crm',
resource: 'deals',
actions: ['create', 'read', 'update'],
},
});
console.log('✅ Created permissions');
// Create Employees
const hashedPassword = await bcrypt.hash('Admin@123', 10);
const gmEmployee = await prisma.employee.create({
data: {
uniqueEmployeeId: 'EMP-001',
firstName: 'Ahmed',
lastName: 'Al-Mansour',
firstNameAr: 'أحمد',
lastNameAr: 'المنصور',
email: 'gm@atmata.com',
mobile: '+966501234567',
employmentType: 'Full-time',
hireDate: new Date('2020-01-01'),
departmentId: salesDept.id,
positionId: gmPosition.id,
basicSalary: 50000,
status: 'ACTIVE',
},
});
const salesManager = await prisma.employee.create({
data: {
uniqueEmployeeId: 'EMP-002',
firstName: 'Fahd',
lastName: 'Al-Sayed',
firstNameAr: 'فهد',
lastNameAr: 'السيد',
email: 'sales.manager@atmata.com',
mobile: '+966507654321',
employmentType: 'Full-time',
hireDate: new Date('2021-01-01'),
departmentId: salesDept.id,
positionId: salesManagerPosition.id,
reportingToId: gmEmployee.id,
basicSalary: 30000,
status: 'ACTIVE',
},
});
const salesRep = await prisma.employee.create({
data: {
uniqueEmployeeId: 'EMP-003',
firstName: 'Omar',
lastName: 'Al-Hassan',
firstNameAr: 'عمر',
lastNameAr: 'الحسن',
email: 'sales.rep@atmata.com',
mobile: '+966509876543',
employmentType: 'Full-time',
hireDate: new Date('2022-01-01'),
departmentId: salesDept.id,
positionId: salesRepPosition.id,
reportingToId: salesManager.id,
basicSalary: 15000,
status: 'ACTIVE',
},
});
console.log('✅ Created employees');
// Create Users
await prisma.user.create({
data: {
email: 'gm@atmata.com',
username: 'general.manager',
password: hashedPassword,
isActive: true,
employeeId: gmEmployee.id,
},
});
await prisma.user.create({
data: {
email: 'sales.manager@atmata.com',
username: 'sales.manager',
password: hashedPassword,
isActive: true,
employeeId: salesManager.id,
},
});
await prisma.user.create({
data: {
email: 'sales.rep@atmata.com',
username: 'sales.rep',
password: hashedPassword,
isActive: true,
employeeId: salesRep.id,
},
});
console.log('✅ Created users');
// Create Contact Categories
await prisma.contactCategory.create({
data: {
name: 'Client',
nameAr: 'عميل',
},
});
await prisma.contactCategory.create({
data: {
name: 'Supplier',
nameAr: 'مورّد',
},
});
await prisma.contactCategory.create({
data: {
name: 'Partner',
nameAr: 'شريك',
},
});
console.log('✅ Created contact categories');
// Create Pipelines
await prisma.pipeline.create({
data: {
name: 'B2B Sales Pipeline',
nameAr: 'مسار مبيعات الشركات',
structure: 'B2B',
stages: [
{ name: 'OPEN', order: 1 },
{ name: 'NEGOTIATION', order: 2 },
{ name: 'PENDING_INTERNAL', order: 3 },
{ name: 'PENDING_CLIENT', order: 4 },
{ name: 'WON', order: 5 },
{ name: 'LOST', order: 6 },
],
},
});
await prisma.pipeline.create({
data: {
name: 'B2C Sales Pipeline',
nameAr: 'مسار المبيعات الفردية',
structure: 'B2C',
stages: [
{ name: 'OPEN', order: 1 },
{ name: 'NEGOTIATION', order: 2 },
{ name: 'WON', order: 3 },
{ name: 'LOST', order: 4 },
],
},
});
console.log('✅ Created pipelines');
console.log('\n🎉 Database seeding completed successfully!');
console.log('\n📝 Login Credentials:');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
console.log('👤 General Manager:');
console.log(' Email: gm@atmata.com');
console.log(' Password: Admin@123');
console.log('');
console.log('👤 Sales Manager:');
console.log(' Email: sales.manager@atmata.com');
console.log(' Password: Admin@123');
console.log('');
console.log('👤 Sales Representative:');
console.log(' Email: sales.rep@atmata.com');
console.log(' Password: Admin@123');
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
}
main()
.catch((e) => {
console.error('❌ Error seeding database:', e);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});