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

@@ -0,0 +1,140 @@
import { api } from '../api'
// Users API
export interface User {
id: string
email: string
username: string
status: string
employeeId?: string
employee?: any
createdAt: string
updatedAt: string
}
export interface CreateUserData {
email: string
username: string
password: string
employeeId?: string
}
export interface UpdateUserData {
email?: string
username?: string
password?: string
status?: string
employeeId?: string
}
export const usersAPI = {
getAll: async (): Promise<User[]> => {
const response = await api.get('/auth/users')
return response.data.data || response.data
},
getById: async (id: string): Promise<User> => {
const response = await api.get(`/auth/users/${id}`)
return response.data.data
},
create: async (data: CreateUserData): Promise<User> => {
const response = await api.post('/auth/register', data)
return response.data.data
},
update: async (id: string, data: UpdateUserData): Promise<User> => {
const response = await api.put(`/auth/users/${id}`, data)
return response.data.data
},
delete: async (id: string): Promise<void> => {
await api.delete(`/auth/users/${id}`)
}
}
// Roles & Permissions API
export interface Role {
id: string
name: string
nameAr?: string
permissions: Permission[]
}
export interface Permission {
id: string
module: string
resource: string
action: string
}
export const rolesAPI = {
getAll: async (): Promise<Role[]> => {
const response = await api.get('/admin/roles')
return response.data.data || []
},
update: async (id: string, permissions: Permission[]): Promise<Role> => {
const response = await api.put(`/admin/roles/${id}/permissions`, { permissions })
return response.data.data
}
}
// Audit Logs API
export interface AuditLog {
id: string
entityType: string
entityId: string
action: string
userId: string
user?: any
changes?: any
createdAt: string
}
export const auditLogsAPI = {
getAll: async (filters?: any): Promise<AuditLog[]> => {
const params = new URLSearchParams()
if (filters?.entityType) params.append('entityType', filters.entityType)
if (filters?.action) params.append('action', filters.action)
if (filters?.startDate) params.append('startDate', filters.startDate)
if (filters?.endDate) params.append('endDate', filters.endDate)
const response = await api.get(`/admin/audit-logs?${params.toString()}`)
return response.data.data || []
}
}
// System Settings API
export interface SystemSetting {
key: string
value: any
description?: string
}
export const settingsAPI = {
getAll: async (): Promise<SystemSetting[]> => {
const response = await api.get('/admin/settings')
return response.data.data || []
},
update: async (key: string, value: any): Promise<SystemSetting> => {
const response = await api.put(`/admin/settings/${key}`, { value })
return response.data.data
}
}
// System Health API
export interface SystemHealth {
status: string
database: string
memory: any
uptime: number
}
export const healthAPI = {
check: async (): Promise<SystemHealth> => {
const response = await api.get('/admin/health')
return response.data.data || response.data
}
}

View File

@@ -0,0 +1,99 @@
import { api } from '../api'
export interface Campaign {
id: string
campaignNumber: string
name: string
nameAr?: string
type: string // EMAIL, WHATSAPP, SOCIAL, EXHIBITION, MULTI_CHANNEL
description?: string
content?: any
targetAudience?: any
budget?: number
actualCost?: number
expectedROI?: number
actualROI?: number
startDate?: string
endDate?: string
status: string // PLANNED, ACTIVE, PAUSED, COMPLETED, CANCELLED
createdAt: string
updatedAt: string
}
export interface CreateCampaignData {
name: string
nameAr?: string
type: string
description?: string
budget?: number
expectedROI?: number
startDate?: string
endDate?: string
}
export interface UpdateCampaignData extends Partial<CreateCampaignData> {
actualCost?: number
actualROI?: number
status?: string
}
export interface CampaignFilters {
search?: string
type?: string
status?: string
page?: number
pageSize?: number
}
export interface CampaignsResponse {
campaigns: Campaign[]
total: number
page: number
pageSize: number
totalPages: number
}
export const campaignsAPI = {
// Get all campaigns with filters and pagination
getAll: async (filters: CampaignFilters = {}): Promise<CampaignsResponse> => {
const params = new URLSearchParams()
if (filters.search) params.append('search', filters.search)
if (filters.type) params.append('type', filters.type)
if (filters.status) params.append('status', filters.status)
if (filters.page) params.append('page', filters.page.toString())
if (filters.pageSize) params.append('pageSize', filters.pageSize.toString())
const response = await api.get(`/marketing/campaigns?${params.toString()}`)
const { data, pagination } = response.data
return {
campaigns: data || [],
total: pagination?.total || 0,
page: pagination?.page || 1,
pageSize: pagination?.pageSize || 20,
totalPages: pagination?.totalPages || 0,
}
},
// Get single campaign by ID
getById: async (id: string): Promise<Campaign> => {
const response = await api.get(`/marketing/campaigns/${id}`)
return response.data.data
},
// Create new campaign
create: async (data: CreateCampaignData): Promise<Campaign> => {
const response = await api.post('/marketing/campaigns', data)
return response.data.data
},
// Update existing campaign
update: async (id: string, data: UpdateCampaignData): Promise<Campaign> => {
const response = await api.put(`/marketing/campaigns/${id}`, data)
return response.data.data
},
// Delete campaign
delete: async (id: string): Promise<void> => {
await api.delete(`/marketing/campaigns/${id}`)
}
}

View File

@@ -0,0 +1,171 @@
import { api } from '../api'
export interface Contact {
id: string
uniqueContactId: string
type: string
name: string
nameAr?: string
email?: string
phone?: string
mobile?: string
website?: string
companyName?: string
companyNameAr?: string
taxNumber?: string
commercialRegister?: string
address?: string
city?: string
country?: string
postalCode?: string
status: string
rating?: number
source: string
tags?: string[]
customFields?: any
categories?: any[]
parent?: any
createdAt: string
updatedAt: string
createdBy?: any
}
export interface CreateContactData {
type: string
name: string
nameAr?: string
email?: string
phone?: string
mobile?: string
website?: string
companyName?: string
companyNameAr?: string
taxNumber?: string
commercialRegister?: string
address?: string
city?: string
country?: string
postalCode?: string
categories?: string[]
tags?: string[]
parentId?: string
source: string
customFields?: any
}
export interface UpdateContactData extends Partial<CreateContactData> {
status?: string
rating?: number
}
export interface ContactFilters {
search?: string
type?: string
status?: string
category?: string
source?: string
rating?: number
page?: number
pageSize?: number
}
export interface ContactsResponse {
contacts: Contact[]
total: number
page: number
pageSize: number
totalPages: number
}
export const contactsAPI = {
// Get all contacts with filters and pagination
getAll: async (filters: ContactFilters = {}): Promise<ContactsResponse> => {
const params = new URLSearchParams()
if (filters.search) params.append('search', filters.search)
if (filters.type) params.append('type', filters.type)
if (filters.status) params.append('status', filters.status)
if (filters.category) params.append('category', filters.category)
if (filters.source) params.append('source', filters.source)
if (filters.rating) params.append('rating', filters.rating.toString())
if (filters.page) params.append('page', filters.page.toString())
if (filters.pageSize) params.append('pageSize', filters.pageSize.toString())
const response = await api.get(`/contacts?${params.toString()}`)
const { data, pagination } = response.data
return {
contacts: data || [],
total: pagination?.total || 0,
page: pagination?.page || 1,
pageSize: pagination?.pageSize || 20,
totalPages: pagination?.totalPages || 0,
}
},
// Get single contact by ID
getById: async (id: string): Promise<Contact> => {
const response = await api.get(`/contacts/${id}`)
return response.data.data
},
// Create new contact
create: async (data: CreateContactData): Promise<Contact> => {
const response = await api.post('/contacts', data)
return response.data.data
},
// Update existing contact
update: async (id: string, data: UpdateContactData): Promise<Contact> => {
const response = await api.put(`/contacts/${id}`, data)
return response.data.data
},
// Archive contact (soft delete)
archive: async (id: string, reason?: string): Promise<Contact> => {
const response = await api.post(`/contacts/${id}/archive`, { reason })
return response.data.data
},
// Delete contact (hard delete)
delete: async (id: string, reason: string): Promise<void> => {
await api.delete(`/contacts/${id}`, { data: { reason } })
},
// Get contact history
getHistory: async (id: string): Promise<any[]> => {
const response = await api.get(`/contacts/${id}/history`)
return response.data.data
},
// Merge contacts
merge: async (sourceId: string, targetId: string, reason: string): Promise<Contact> => {
const response = await api.post('/contacts/merge', { sourceId, targetId, reason })
return response.data.data
},
// Export contacts
export: async (filters: ContactFilters = {}): Promise<Blob> => {
const params = new URLSearchParams()
if (filters.search) params.append('search', filters.search)
if (filters.type) params.append('type', filters.type)
if (filters.status) params.append('status', filters.status)
const response = await api.get(`/contacts/export?${params.toString()}`, {
responseType: 'blob'
})
return response.data
},
// Import contacts
import: async (file: File): Promise<{ success: number; errors: any[] }> => {
const formData = new FormData()
formData.append('file', file)
const response = await api.post('/contacts/import', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
return response.data.data
}
}

View File

@@ -0,0 +1,134 @@
import { api } from '../api'
export interface Deal {
id: string
dealNumber: string
name: string
contactId: string
contact?: any
structure: string // B2B, B2C, B2G, PARTNERSHIP
pipelineId: string
pipeline?: any
stage: string
estimatedValue: number
actualValue?: number
currency: string
probability?: number
expectedCloseDate?: string
actualCloseDate?: string
ownerId: string
owner?: any
wonReason?: string
lostReason?: string
fiscalYear: number
status: string
createdAt: string
updatedAt: string
}
export interface CreateDealData {
name: string
contactId: string
structure: string
pipelineId: string
stage: string
estimatedValue: number
probability?: number
expectedCloseDate?: string
ownerId?: string
fiscalYear?: number
}
export interface UpdateDealData extends Partial<CreateDealData> {
actualValue?: number
actualCloseDate?: string
wonReason?: string
lostReason?: string
status?: string
}
export interface DealFilters {
search?: string
structure?: string
stage?: string
status?: string
ownerId?: string
fiscalYear?: number
page?: number
pageSize?: number
}
export interface DealsResponse {
deals: Deal[]
total: number
page: number
pageSize: number
totalPages: number
}
export const dealsAPI = {
// Get all deals with filters and pagination
getAll: async (filters: DealFilters = {}): Promise<DealsResponse> => {
const params = new URLSearchParams()
if (filters.search) params.append('search', filters.search)
if (filters.structure) params.append('structure', filters.structure)
if (filters.stage) params.append('stage', filters.stage)
if (filters.status) params.append('status', filters.status)
if (filters.ownerId) params.append('ownerId', filters.ownerId)
if (filters.fiscalYear) params.append('fiscalYear', filters.fiscalYear.toString())
if (filters.page) params.append('page', filters.page.toString())
if (filters.pageSize) params.append('pageSize', filters.pageSize.toString())
const response = await api.get(`/crm/deals?${params.toString()}`)
const { data, pagination } = response.data
return {
deals: data || [],
total: pagination?.total || 0,
page: pagination?.page || 1,
pageSize: pagination?.pageSize || 20,
totalPages: pagination?.totalPages || 0,
}
},
// Get single deal by ID
getById: async (id: string): Promise<Deal> => {
const response = await api.get(`/crm/deals/${id}`)
return response.data.data
},
// Create new deal
create: async (data: CreateDealData): Promise<Deal> => {
const response = await api.post('/crm/deals', data)
return response.data.data
},
// Update existing deal
update: async (id: string, data: UpdateDealData): Promise<Deal> => {
const response = await api.put(`/crm/deals/${id}`, data)
return response.data.data
},
// Update deal stage
updateStage: async (id: string, stage: string): Promise<Deal> => {
const response = await api.patch(`/crm/deals/${id}/stage`, { stage })
return response.data.data
},
// Mark deal as won
win: async (id: string, actualValue: number, wonReason: string): Promise<Deal> => {
const response = await api.post(`/crm/deals/${id}/win`, { actualValue, wonReason })
return response.data.data
},
// Mark deal as lost
lose: async (id: string, lostReason: string): Promise<Deal> => {
const response = await api.post(`/crm/deals/${id}/lose`, { lostReason })
return response.data.data
},
// Get deal history
getHistory: async (id: string): Promise<any[]> => {
const response = await api.get(`/crm/deals/${id}/history`)
return response.data.data
}
}

View File

@@ -0,0 +1,134 @@
import { api } from '../api'
export interface Employee {
id: string
uniqueEmployeeId: string
firstName: string
lastName: string
firstNameAr?: string
lastNameAr?: string
email: string
phone?: string
mobile: string
dateOfBirth?: string
gender?: string
nationality?: string
nationalId?: string
passportNumber?: string
employmentType: string
contractType?: string
hireDate: string
endDate?: string
departmentId: string
department?: any
positionId: string
position?: any
reportingToId?: string
reportingTo?: any
baseSalary: number
status: string
createdAt: string
updatedAt: string
}
export interface CreateEmployeeData {
firstName: string
lastName: string
firstNameAr?: string
lastNameAr?: string
email: string
phone?: string
mobile: string
dateOfBirth?: string
gender?: string
nationality?: string
nationalId?: string
employmentType: string
contractType?: string
hireDate: string
departmentId: string
positionId: string
reportingToId?: string
baseSalary: number
}
export interface UpdateEmployeeData extends Partial<CreateEmployeeData> {}
export interface EmployeeFilters {
search?: string
departmentId?: string
positionId?: string
status?: string
page?: number
pageSize?: number
}
export interface EmployeesResponse {
employees: Employee[]
total: number
page: number
pageSize: number
totalPages: number
}
export const employeesAPI = {
// Get all employees with filters and pagination
getAll: async (filters: EmployeeFilters = {}): Promise<EmployeesResponse> => {
const params = new URLSearchParams()
if (filters.search) params.append('search', filters.search)
if (filters.departmentId) params.append('departmentId', filters.departmentId)
if (filters.positionId) params.append('positionId', filters.positionId)
if (filters.status) params.append('status', filters.status)
if (filters.page) params.append('page', filters.page.toString())
if (filters.pageSize) params.append('pageSize', filters.pageSize.toString())
const response = await api.get(`/hr/employees?${params.toString()}`)
const { data, pagination } = response.data
return {
employees: data || [],
total: pagination?.total || 0,
page: pagination?.page || 1,
pageSize: pagination?.pageSize || 20,
totalPages: pagination?.totalPages || 0,
}
},
// Get single employee by ID
getById: async (id: string): Promise<Employee> => {
const response = await api.get(`/hr/employees/${id}`)
return response.data.data
},
// Create new employee
create: async (data: CreateEmployeeData): Promise<Employee> => {
const response = await api.post('/hr/employees', data)
return response.data.data
},
// Update existing employee
update: async (id: string, data: UpdateEmployeeData): Promise<Employee> => {
const response = await api.put(`/hr/employees/${id}`, data)
return response.data.data
},
// Delete employee
delete: async (id: string): Promise<void> => {
await api.delete(`/hr/employees/${id}`)
}
}
// Departments API
export const departmentsAPI = {
getAll: async (): Promise<any[]> => {
const response = await api.get('/hr/departments')
return response.data.data
}
}
// Positions API
export const positionsAPI = {
getAll: async (): Promise<any[]> => {
const response = await api.get('/hr/positions')
return response.data.data
}
}

View File

@@ -0,0 +1,130 @@
import { api } from '../api'
export interface Product {
id: string
sku: string
name: string
nameAr?: string
description?: string
categoryId: string
category?: any
brand?: string
model?: string
specifications?: any
trackBy: string
costPrice: number
sellingPrice: number
minStock: number
maxStock?: number
totalStock?: number
inventoryItems?: any[]
createdAt: string
updatedAt: string
}
export interface CreateProductData {
sku: string
name: string
nameAr?: string
description?: string
categoryId: string
brand?: string
model?: string
trackBy?: string
costPrice: number
sellingPrice: number
minStock?: number
maxStock?: number
}
export interface UpdateProductData extends Partial<CreateProductData> {}
export interface ProductFilters {
search?: string
categoryId?: string
brand?: string
page?: number
pageSize?: number
}
export interface ProductsResponse {
products: Product[]
total: number
page: number
pageSize: number
totalPages: number
}
export const productsAPI = {
// Get all products with filters and pagination
getAll: async (filters: ProductFilters = {}): Promise<ProductsResponse> => {
const params = new URLSearchParams()
if (filters.search) params.append('search', filters.search)
if (filters.categoryId) params.append('categoryId', filters.categoryId)
if (filters.brand) params.append('brand', filters.brand)
if (filters.page) params.append('page', filters.page.toString())
if (filters.pageSize) params.append('pageSize', filters.pageSize.toString())
const response = await api.get(`/inventory/products?${params.toString()}`)
const { data, pagination } = response.data
return {
products: data || [],
total: pagination?.total || 0,
page: pagination?.page || 1,
pageSize: pagination?.pageSize || 20,
totalPages: pagination?.totalPages || 0,
}
},
// Get single product by ID
getById: async (id: string): Promise<Product> => {
const response = await api.get(`/inventory/products/${id}`)
return response.data.data
},
// Create new product
create: async (data: CreateProductData): Promise<Product> => {
const response = await api.post('/inventory/products', data)
return response.data.data
},
// Update existing product
update: async (id: string, data: UpdateProductData): Promise<Product> => {
const response = await api.put(`/inventory/products/${id}`, data)
return response.data.data
},
// Delete product
delete: async (id: string): Promise<void> => {
await api.delete(`/inventory/products/${id}`)
},
// Adjust stock
adjustStock: async (
productId: string,
warehouseId: string,
quantity: number,
type: 'ADD' | 'REMOVE'
): Promise<any> => {
const response = await api.post(`/inventory/products/${productId}/adjust-stock`, {
warehouseId,
quantity,
type
})
return response.data.data
},
// Get product history
getHistory: async (id: string): Promise<any[]> => {
const response = await api.get(`/inventory/products/${id}/history`)
return response.data.data
}
}
// Categories API
export const categoriesAPI = {
getAll: async (): Promise<any[]> => {
const response = await api.get('/inventory/categories')
return response.data.data
}
}

View File

@@ -0,0 +1,131 @@
import { api } from '../api'
export interface Task {
id: string
taskNumber: string
projectId?: string
project?: any
phaseId?: string
title: string
description?: string
assignedToId?: string
assignedTo?: any
priority: string // LOW, MEDIUM, HIGH, CRITICAL
status: string // PENDING, IN_PROGRESS, REVIEW, COMPLETED, CANCELLED
progress: number
startDate?: string
dueDate?: string
completedAt?: string
estimatedHours?: number
actualHours?: number
tags?: string[]
createdAt: string
updatedAt: string
}
export interface CreateTaskData {
projectId?: string
title: string
description?: string
assignedToId?: string
priority?: string
status?: string
progress?: number
startDate?: string
dueDate?: string
estimatedHours?: number
tags?: string[]
}
export interface UpdateTaskData extends Partial<CreateTaskData> {
progress?: number
completedAt?: string
actualHours?: number
}
export interface TaskFilters {
search?: string
projectId?: string
assignedToId?: string
priority?: string
status?: string
page?: number
pageSize?: number
}
export interface TasksResponse {
tasks: Task[]
total: number
page: number
pageSize: number
totalPages: number
}
export const tasksAPI = {
// Get all tasks with filters and pagination
getAll: async (filters: TaskFilters = {}): Promise<TasksResponse> => {
const params = new URLSearchParams()
if (filters.search) params.append('search', filters.search)
if (filters.projectId) params.append('projectId', filters.projectId)
if (filters.assignedToId) params.append('assignedToId', filters.assignedToId)
if (filters.priority) params.append('priority', filters.priority)
if (filters.status) params.append('status', filters.status)
if (filters.page) params.append('page', filters.page.toString())
if (filters.pageSize) params.append('pageSize', filters.pageSize.toString())
const response = await api.get(`/projects/tasks?${params.toString()}`)
const { data, pagination } = response.data
return {
tasks: data || [],
total: pagination?.total || 0,
page: pagination?.page || 1,
pageSize: pagination?.pageSize || 20,
totalPages: pagination?.totalPages || 0,
}
},
// Get single task by ID
getById: async (id: string): Promise<Task> => {
const response = await api.get(`/projects/tasks/${id}`)
return response.data.data
},
// Create new task
create: async (data: CreateTaskData): Promise<Task> => {
const response = await api.post('/projects/tasks', data)
return response.data.data
},
// Update existing task
update: async (id: string, data: UpdateTaskData): Promise<Task> => {
const response = await api.put(`/projects/tasks/${id}`, data)
return response.data.data
},
// Delete task
delete: async (id: string): Promise<void> => {
await api.delete(`/projects/tasks/${id}`)
}
}
// Projects API
export interface Project {
id: string
projectNumber: string
name: string
nameAr?: string
description?: string
status: string
startDate?: string
endDate?: string
budget?: number
createdAt: string
updatedAt: string
}
export const projectsAPI = {
getAll: async (): Promise<Project[]> => {
const response = await api.get('/projects/projects')
return response.data.data
}
}