@organizasyon/meeting-nanaman-app-backend 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/controllers/auth/index.d.ts +65 -0
- package/dist/controllers/auth/index.js +525 -0
- package/dist/controllers/employees/index.d.ts +38 -0
- package/dist/controllers/employees/index.js +185 -0
- package/dist/controllers/health/index.d.ts +9 -0
- package/dist/controllers/health/index.js +42 -0
- package/dist/controllers/index.d.ts +16 -0
- package/dist/controllers/index.js +19 -0
- package/dist/controllers/meetings/index.d.ts +23 -0
- package/dist/controllers/meetings/index.js +233 -0
- package/dist/controllers/modules/index.d.ts +5 -0
- package/dist/controllers/modules/index.js +104 -0
- package/dist/controllers/users/index.d.ts +103 -0
- package/dist/controllers/users/index.js +841 -0
- package/dist/data/modules.json +94 -0
- package/dist/database/config/index.d.ts +2 -0
- package/dist/database/config/index.js +32 -0
- package/dist/database/index.d.ts +9 -0
- package/dist/database/index.js +9 -0
- package/dist/database/seeder/employees/index.d.ts +1 -0
- package/dist/database/seeder/employees/index.js +40 -0
- package/dist/database/seeder/index.d.ts +4 -0
- package/dist/database/seeder/index.js +20 -0
- package/dist/database/seeder/users/index.d.ts +1 -0
- package/dist/database/seeder/users/index.js +46 -0
- package/dist/index.d.ts +9 -0
- package/dist/index.js +23 -0
- package/dist/jobs/index.d.ts +1 -0
- package/dist/jobs/index.js +18 -0
- package/dist/jobs/mailer/index.d.ts +4 -0
- package/dist/jobs/mailer/index.js +186 -0
- package/dist/jobs/mailer/templates/auth.d.ts +11 -0
- package/dist/jobs/mailer/templates/auth.js +117 -0
- package/dist/jobs/mailer/templates/index.d.ts +1 -0
- package/dist/jobs/mailer/templates/index.js +17 -0
- package/dist/jobs/queues/index.d.ts +3 -0
- package/dist/jobs/queues/index.js +115 -0
- package/dist/middlewares/audit/index.d.ts +0 -0
- package/dist/middlewares/audit/index.js +1 -0
- package/dist/middlewares/guard/index.d.ts +11 -0
- package/dist/middlewares/guard/index.js +53 -0
- package/dist/middlewares/index.d.ts +2 -0
- package/dist/middlewares/index.js +7 -0
- package/dist/middlewares/meeting.d.ts +9 -0
- package/dist/middlewares/meeting.js +34 -0
- package/dist/models/employees/index.d.ts +83 -0
- package/dist/models/employees/index.js +70 -0
- package/dist/models/index.d.ts +570 -0
- package/dist/models/index.js +17 -0
- package/dist/models/meetings/index.d.ts +227 -0
- package/dist/models/meetings/index.js +112 -0
- package/dist/models/passkeys/index.d.ts +77 -0
- package/dist/models/passkeys/index.js +55 -0
- package/dist/models/queues/index.d.ts +77 -0
- package/dist/models/queues/index.js +57 -0
- package/dist/models/users/index.d.ts +107 -0
- package/dist/models/users/index.js +92 -0
- package/dist/queues/index.d.ts +1 -0
- package/dist/queues/index.js +17 -0
- package/dist/queues/mailer/index.d.ts +4 -0
- package/dist/queues/mailer/index.js +74 -0
- package/dist/types/index.d.ts +33 -0
- package/dist/types/index.js +2 -0
- package/dist/utils/notifications.d.ts +2 -0
- package/dist/utils/notifications.js +51 -0
- package/package.json +39 -0
- package/public/health.html +215 -0
- package/src/controllers/auth/index.ts +609 -0
- package/src/controllers/employees/index.ts +210 -0
- package/src/controllers/health/index.ts +41 -0
- package/src/controllers/index.ts +9 -0
- package/src/controllers/meetings/index.ts +251 -0
- package/src/controllers/modules/index.ts +74 -0
- package/src/controllers/users/index.ts +981 -0
- package/src/data/modules.json +94 -0
- package/src/database/config/index.ts +26 -0
- package/src/database/index.ts +5 -0
- package/src/database/seeder/employees/index.ts +35 -0
- package/src/database/seeder/index.ts +18 -0
- package/src/database/seeder/users/index.ts +44 -0
- package/src/index.ts +10 -0
- package/src/jobs/index.ts +2 -0
- package/src/jobs/mailer/index.ts +154 -0
- package/src/jobs/mailer/templates/auth.ts +113 -0
- package/src/jobs/mailer/templates/index.ts +1 -0
- package/src/jobs/queues/index.ts +125 -0
- package/src/middlewares/audit/index.ts +0 -0
- package/src/middlewares/guard/index.ts +64 -0
- package/src/middlewares/index.ts +5 -0
- package/src/middlewares/meeting.ts +45 -0
- package/src/models/employees/index.ts +70 -0
- package/src/models/index.ts +8 -0
- package/src/models/meetings/index.ts +112 -0
- package/src/models/passkeys/index.ts +53 -0
- package/src/models/queues/index.ts +55 -0
- package/src/models/users/index.ts +92 -0
- package/src/queues/index.ts +1 -0
- package/src/queues/mailer/index.ts +80 -0
- package/src/types/index.ts +38 -0
- package/src/utils/notifications.ts +66 -0
- package/tsconfig.json +18 -0
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { Response } from 'express'
|
|
2
|
+
import EmployeesModel from '../../models/employees'
|
|
3
|
+
import type { MulterRequest } from '../../types'
|
|
4
|
+
|
|
5
|
+
class Employees {
|
|
6
|
+
/**
|
|
7
|
+
* Create a new employee
|
|
8
|
+
* Accepts form data with:
|
|
9
|
+
* - first_name (required)
|
|
10
|
+
* - middle_name (optional)
|
|
11
|
+
* - last_name (required)
|
|
12
|
+
* - address (optional)
|
|
13
|
+
* - contact_number (optional)
|
|
14
|
+
* - email (optional)
|
|
15
|
+
* - position (optional)
|
|
16
|
+
* - department (optional)
|
|
17
|
+
*/
|
|
18
|
+
public async create(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
const body = req.body || {}
|
|
21
|
+
|
|
22
|
+
const firstName = (body.first_name ?? '').toString().trim()
|
|
23
|
+
const lastName = (body.last_name ?? '').toString().trim()
|
|
24
|
+
|
|
25
|
+
if (!firstName || !lastName) {
|
|
26
|
+
res.status(400).json({ message: 'first_name and last_name are required' })
|
|
27
|
+
return
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
// Create employee record
|
|
31
|
+
const employee = await EmployeesModel.create({
|
|
32
|
+
first_name: firstName,
|
|
33
|
+
middle_name: (body.middle_name ?? null),
|
|
34
|
+
last_name: lastName,
|
|
35
|
+
address: (body.address ?? null),
|
|
36
|
+
contact_number: (body.contact_number ?? null),
|
|
37
|
+
email: (body.email ?? null),
|
|
38
|
+
position: (body.position ?? null),
|
|
39
|
+
department: (body.department ?? null),
|
|
40
|
+
created_at: Date.now(),
|
|
41
|
+
updated_at: Date.now()
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
res.status(201).json({
|
|
45
|
+
message: 'Employee created successfully',
|
|
46
|
+
employee: {
|
|
47
|
+
id: employee._id,
|
|
48
|
+
first_name: employee.first_name,
|
|
49
|
+
middle_name: employee.middle_name,
|
|
50
|
+
last_name: employee.last_name,
|
|
51
|
+
email: employee.email,
|
|
52
|
+
position: employee.position,
|
|
53
|
+
department: employee.department,
|
|
54
|
+
contact_number: employee.contact_number,
|
|
55
|
+
address: employee.address
|
|
56
|
+
}
|
|
57
|
+
})
|
|
58
|
+
} catch (err) {
|
|
59
|
+
console.error('Create employee error', err)
|
|
60
|
+
res.status(500).json({ message: 'Failed to create employee', error: String(err) })
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Get all employees
|
|
66
|
+
*/
|
|
67
|
+
public async getAll(req: MulterRequest, res: Response): Promise<void> {
|
|
68
|
+
try {
|
|
69
|
+
const employees = await EmployeesModel.find({ deleted_at: null })
|
|
70
|
+
.select('-__v')
|
|
71
|
+
.sort({ created_at: -1 })
|
|
72
|
+
.lean()
|
|
73
|
+
.exec()
|
|
74
|
+
|
|
75
|
+
res.json({
|
|
76
|
+
message: 'Employees retrieved successfully',
|
|
77
|
+
count: employees.length,
|
|
78
|
+
employees
|
|
79
|
+
})
|
|
80
|
+
} catch (err) {
|
|
81
|
+
console.error('Get employees error', err)
|
|
82
|
+
res.status(500).json({ message: 'Failed to retrieve employees', error: String(err) })
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get employee by ID
|
|
88
|
+
*/
|
|
89
|
+
public async getById(req: MulterRequest, res: Response): Promise<void> {
|
|
90
|
+
try {
|
|
91
|
+
const { id } = req.params
|
|
92
|
+
|
|
93
|
+
if (!id) {
|
|
94
|
+
res.status(400).json({ message: 'Employee ID is required' })
|
|
95
|
+
return
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
const employee = await EmployeesModel.findOne({
|
|
99
|
+
_id: id,
|
|
100
|
+
deleted_at: null
|
|
101
|
+
})
|
|
102
|
+
.select('-__v')
|
|
103
|
+
.lean()
|
|
104
|
+
.exec()
|
|
105
|
+
|
|
106
|
+
if (!employee) {
|
|
107
|
+
res.status(404).json({ message: 'Employee not found' })
|
|
108
|
+
return
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
res.json({
|
|
112
|
+
message: 'Employee retrieved successfully',
|
|
113
|
+
employee
|
|
114
|
+
})
|
|
115
|
+
} catch (err) {
|
|
116
|
+
console.error('Get employee error', err)
|
|
117
|
+
res.status(500).json({ message: 'Failed to retrieve employee', error: String(err) })
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Update employee
|
|
123
|
+
*/
|
|
124
|
+
public async update(req: MulterRequest & { body?: Record<string, any> }, res: Response): Promise<void> {
|
|
125
|
+
try {
|
|
126
|
+
const { id } = req.params
|
|
127
|
+
const body = req.body || {}
|
|
128
|
+
|
|
129
|
+
if (!id) {
|
|
130
|
+
res.status(400).json({ message: 'Employee ID is required' })
|
|
131
|
+
return
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const updateData: any = {
|
|
135
|
+
updated_at: Date.now()
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// Only update provided fields
|
|
139
|
+
const allowedFields = ['first_name', 'middle_name', 'last_name', 'address', 'contact_number', 'email', 'position', 'department']
|
|
140
|
+
for (const field of allowedFields) {
|
|
141
|
+
if (body[field] !== undefined) {
|
|
142
|
+
updateData[field] = body[field]
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
const employee = await EmployeesModel.findOneAndUpdate(
|
|
147
|
+
{ _id: id, deleted_at: null },
|
|
148
|
+
updateData,
|
|
149
|
+
{ new: true, runValidators: true }
|
|
150
|
+
)
|
|
151
|
+
.select('-__v')
|
|
152
|
+
.lean()
|
|
153
|
+
.exec()
|
|
154
|
+
|
|
155
|
+
if (!employee) {
|
|
156
|
+
res.status(404).json({ message: 'Employee not found' })
|
|
157
|
+
return
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
res.json({
|
|
161
|
+
message: 'Employee updated successfully',
|
|
162
|
+
employee
|
|
163
|
+
})
|
|
164
|
+
} catch (err) {
|
|
165
|
+
console.error('Update employee error', err)
|
|
166
|
+
res.status(500).json({ message: 'Failed to update employee', error: String(err) })
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Soft delete employee
|
|
172
|
+
*/
|
|
173
|
+
public async delete(req: MulterRequest, res: Response): Promise<void> {
|
|
174
|
+
try {
|
|
175
|
+
const { id } = req.params
|
|
176
|
+
|
|
177
|
+
if (!id) {
|
|
178
|
+
res.status(400).json({ message: 'Employee ID is required' })
|
|
179
|
+
return
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const employee = await EmployeesModel.findOneAndUpdate(
|
|
183
|
+
{ _id: id, deleted_at: null },
|
|
184
|
+
{ deleted_at: Date.now() },
|
|
185
|
+
{ new: true }
|
|
186
|
+
)
|
|
187
|
+
.lean()
|
|
188
|
+
.exec()
|
|
189
|
+
|
|
190
|
+
if (!employee) {
|
|
191
|
+
res.status(404).json({ message: 'Employee not found' })
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
res.json({
|
|
196
|
+
message: 'Employee deleted successfully',
|
|
197
|
+
employee: {
|
|
198
|
+
id: employee._id,
|
|
199
|
+
first_name: employee.first_name,
|
|
200
|
+
last_name: employee.last_name
|
|
201
|
+
}
|
|
202
|
+
})
|
|
203
|
+
} catch (err) {
|
|
204
|
+
console.error('Delete employee error', err)
|
|
205
|
+
res.status(500).json({ message: 'Failed to delete employee', error: String(err) })
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
export default Employees;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import path from 'path';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Health Check Controller
|
|
7
|
+
* Serves a fancy HTML page to show API is running
|
|
8
|
+
*/
|
|
9
|
+
export class HealthController {
|
|
10
|
+
async healthCheck(req: Request, res: Response) {
|
|
11
|
+
try {
|
|
12
|
+
// Get the path to the HTML file
|
|
13
|
+
const htmlPath = path.join(__dirname, '../../../public/health.html');
|
|
14
|
+
|
|
15
|
+
// Read the HTML file
|
|
16
|
+
const htmlContent = fs.readFileSync(htmlPath, 'utf8');
|
|
17
|
+
|
|
18
|
+
// Set appropriate headers
|
|
19
|
+
res.setHeader('Content-Type', 'text/html');
|
|
20
|
+
res.setHeader('Cache-Control', 'no-cache, no-store, must-revalidate');
|
|
21
|
+
res.setHeader('Pragma', 'no-cache');
|
|
22
|
+
res.setHeader('Expires', '0');
|
|
23
|
+
|
|
24
|
+
// Send the HTML content
|
|
25
|
+
res.status(200).send(htmlContent);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
console.error('Health check error:', error);
|
|
28
|
+
|
|
29
|
+
// If HTML file not found, return simple JSON response
|
|
30
|
+
res.status(500).json({
|
|
31
|
+
status: 'error',
|
|
32
|
+
message: 'Health check page unavailable',
|
|
33
|
+
timestamp: new Date().toISOString(),
|
|
34
|
+
api: 'Inventory Management System',
|
|
35
|
+
version: '1.0.0'
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export default HealthController;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import EmployeesController from './employees';
|
|
2
|
+
import UsersController from './users';
|
|
3
|
+
import { ModulesController } from './modules';
|
|
4
|
+
import { AuthController } from './auth';
|
|
5
|
+
import { HealthController } from './health';
|
|
6
|
+
import MeetingsController from './meetings';
|
|
7
|
+
|
|
8
|
+
export { EmployeesController, UsersController, ModulesController, AuthController, HealthController, MeetingsController };
|
|
9
|
+
export default { EmployeesController, UsersController, ModulesController, AuthController, HealthController, MeetingsController };
|
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
import { Meetings as MeetingModel, Users as UserModel } from '../../models';
|
|
2
|
+
// import { canJoinMeeting } from '../middlewares/meeting';
|
|
3
|
+
// import { sendNotification, sendMeetingInvitation } from '../utils/notifications';
|
|
4
|
+
|
|
5
|
+
class Meetings {
|
|
6
|
+
/**
|
|
7
|
+
* Create a new meeting
|
|
8
|
+
*/
|
|
9
|
+
public static async create(req: any, res: any): Promise<void> {
|
|
10
|
+
try {
|
|
11
|
+
const { title, description, meeting_type = 'video', scheduled_at, participants, max_participants = 50 } = req.body;
|
|
12
|
+
const userId = req.user.userId;
|
|
13
|
+
|
|
14
|
+
// Generate unique meeting URL
|
|
15
|
+
const meeting_url = `${Date.now()}-${Math.random().toString(36).substring(2, 15)}`;
|
|
16
|
+
|
|
17
|
+
// Process participants - handle both user IDs and email objects
|
|
18
|
+
const participantsArray: any[] = [];
|
|
19
|
+
if (Array.isArray(participants)) {
|
|
20
|
+
for (const participant of participants) {
|
|
21
|
+
if (typeof participant === 'string') {
|
|
22
|
+
// Check if it's an email or user ID
|
|
23
|
+
if (participant.includes('@')) {
|
|
24
|
+
// Email provided - try to find user or store email
|
|
25
|
+
const user = await UserModel.findOne({ email: participant.toLowerCase(), deleted_at: null }).lean().exec();
|
|
26
|
+
if (user) {
|
|
27
|
+
participantsArray.push({
|
|
28
|
+
user_id: user._id,
|
|
29
|
+
is_active: false
|
|
30
|
+
});
|
|
31
|
+
} else {
|
|
32
|
+
// User doesn't exist - store as pending invitation
|
|
33
|
+
participantsArray.push({
|
|
34
|
+
email: participant,
|
|
35
|
+
is_active: false,
|
|
36
|
+
status: 'pending'
|
|
37
|
+
});
|
|
38
|
+
}
|
|
39
|
+
} else {
|
|
40
|
+
// Direct user ID
|
|
41
|
+
participantsArray.push({
|
|
42
|
+
user_id: participant,
|
|
43
|
+
is_active: false
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
} else if (typeof participant === 'object' && participant !== null) {
|
|
47
|
+
// Object with id or email
|
|
48
|
+
if (participant.id) {
|
|
49
|
+
participantsArray.push({
|
|
50
|
+
user_id: participant.id,
|
|
51
|
+
is_active: false
|
|
52
|
+
});
|
|
53
|
+
} else if (participant.email) {
|
|
54
|
+
// Email provided - try to find user or store email
|
|
55
|
+
const user = await UserModel.findOne({ email: participant.email.toLowerCase(), deleted_at: null }).lean().exec();
|
|
56
|
+
if (user) {
|
|
57
|
+
participantsArray.push({
|
|
58
|
+
user_id: user._id,
|
|
59
|
+
is_active: false
|
|
60
|
+
});
|
|
61
|
+
} else {
|
|
62
|
+
// User doesn't exist - store as pending invitation (could be enhanced later)
|
|
63
|
+
participantsArray.push({
|
|
64
|
+
email: participant.email,
|
|
65
|
+
is_active: false,
|
|
66
|
+
status: 'pending'
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const meeting = new MeetingModel({
|
|
75
|
+
title,
|
|
76
|
+
description,
|
|
77
|
+
host_id: userId,
|
|
78
|
+
meeting_type,
|
|
79
|
+
scheduled_at: scheduled_at ? new Date(scheduled_at).getTime() : Date.now(),
|
|
80
|
+
max_participants,
|
|
81
|
+
meeting_url,
|
|
82
|
+
participants: participantsArray
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
await meeting.save();
|
|
86
|
+
await meeting.populate('host_id', 'first_name last_name email');
|
|
87
|
+
await meeting.populate('participants.user_id', 'first_name last_name email');
|
|
88
|
+
|
|
89
|
+
// Send notifications to participants
|
|
90
|
+
// if (participants && participants.length > 0) {
|
|
91
|
+
// await sendMeetingInvitation(meeting._id, participants, title, description, userId);
|
|
92
|
+
// }
|
|
93
|
+
|
|
94
|
+
res.status(201).json({ success: true, meeting });
|
|
95
|
+
} catch (error) {
|
|
96
|
+
console.error('Error creating meeting:', error);
|
|
97
|
+
res.status(500).json({ error: 'Failed to create meeting' });
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Join a meeting
|
|
103
|
+
*/
|
|
104
|
+
public static async join(req: any, res: any): Promise<void> {
|
|
105
|
+
try {
|
|
106
|
+
const { meetingId } = req.params;
|
|
107
|
+
const userId = req.user.userId;
|
|
108
|
+
|
|
109
|
+
const meeting = req.meeting;
|
|
110
|
+
|
|
111
|
+
// Mark user as active participant
|
|
112
|
+
const participantIndex = meeting.participants.findIndex((p: any) => p.user_id.toString() === userId);
|
|
113
|
+
if (participantIndex !== -1) {
|
|
114
|
+
meeting.participants[participantIndex].is_active = true;
|
|
115
|
+
meeting.participants[participantIndex].joined_at = Date.now();
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Start meeting if not started
|
|
119
|
+
if (!meeting.started_at) {
|
|
120
|
+
meeting.started_at = Date.now();
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
await meeting.save();
|
|
124
|
+
|
|
125
|
+
// Notify other participants
|
|
126
|
+
const otherParticipants = meeting.participants
|
|
127
|
+
.filter((p: any) => p.user_id.toString() !== userId && p.is_active)
|
|
128
|
+
.map((p: any) => p.user_id.toString());
|
|
129
|
+
|
|
130
|
+
// if (otherParticipants.length > 0) {
|
|
131
|
+
// await sendNotification(
|
|
132
|
+
// otherParticipants,
|
|
133
|
+
// 'User Joined Meeting',
|
|
134
|
+
// `${req.user.email} has joined the meeting`,
|
|
135
|
+
// { type: 'user_joined', userId, meetingId },
|
|
136
|
+
// 'meeting_update'
|
|
137
|
+
// );
|
|
138
|
+
// }
|
|
139
|
+
|
|
140
|
+
res.json({ success: true, meeting_url: meeting.meeting_url });
|
|
141
|
+
} catch (error) {
|
|
142
|
+
console.error('Error joining meeting:', error);
|
|
143
|
+
res.status(500).json({ error: 'Failed to join meeting' });
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Leave a meeting
|
|
149
|
+
*/
|
|
150
|
+
public static async leave(req: any, res: any): Promise<void> {
|
|
151
|
+
try {
|
|
152
|
+
const { meetingId } = req.params;
|
|
153
|
+
const userId = req.user.userId;
|
|
154
|
+
|
|
155
|
+
const meeting = req.meeting;
|
|
156
|
+
|
|
157
|
+
// Mark user as inactive participant
|
|
158
|
+
const participantIndex = meeting.participants.findIndex((p: any) => p.user_id.toString() === userId);
|
|
159
|
+
if (participantIndex !== -1) {
|
|
160
|
+
meeting.participants[participantIndex].is_active = false;
|
|
161
|
+
meeting.participants[participantIndex].left_at = Date.now();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Check if meeting should end (only host left or no active participants)
|
|
165
|
+
const isHost = meeting.host_id.toString() === userId;
|
|
166
|
+
const activeParticipants = meeting.participants.filter((p: any) => p.is_active);
|
|
167
|
+
|
|
168
|
+
if (isHost || activeParticipants.length === 0) {
|
|
169
|
+
meeting.ended_at = Date.now();
|
|
170
|
+
meeting.is_active = false;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
await meeting.save();
|
|
174
|
+
|
|
175
|
+
// Notify other participants
|
|
176
|
+
const remainingParticipants = activeParticipants
|
|
177
|
+
.filter((p: any) => p.user_id.toString() !== userId)
|
|
178
|
+
.map((p: any) => p.user_id.toString());
|
|
179
|
+
|
|
180
|
+
// if (remainingParticipants.length > 0) {
|
|
181
|
+
// await sendNotification(
|
|
182
|
+
// remainingParticipants,
|
|
183
|
+
// 'User Left Meeting',
|
|
184
|
+
// `${req.user.email} has left the meeting`,
|
|
185
|
+
// { type: 'user_left', userId, meetingId },
|
|
186
|
+
// 'meeting_update'
|
|
187
|
+
// );
|
|
188
|
+
// }
|
|
189
|
+
|
|
190
|
+
res.json({ success: true });
|
|
191
|
+
} catch (error) {
|
|
192
|
+
console.error('Error leaving meeting:', error);
|
|
193
|
+
res.status(500).json({ error: 'Failed to leave meeting' });
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
/**
|
|
198
|
+
* Get user's meetings
|
|
199
|
+
*/
|
|
200
|
+
public static async getMyMeetings(req: any, res: any): Promise<void> {
|
|
201
|
+
try {
|
|
202
|
+
const userId = req.user.userId;
|
|
203
|
+
const { status = 'upcoming' } = req.query;
|
|
204
|
+
|
|
205
|
+
let filter: any = {
|
|
206
|
+
$or: [
|
|
207
|
+
{ host_id: userId },
|
|
208
|
+
{ 'participants.user_id': userId }
|
|
209
|
+
]
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
if (status === 'upcoming') {
|
|
213
|
+
filter.scheduled_at = { $gt: Date.now() };
|
|
214
|
+
} else if (status === 'ongoing') {
|
|
215
|
+
filter.started_at = { $ne: null };
|
|
216
|
+
filter.ended_at = null;
|
|
217
|
+
filter.is_active = true;
|
|
218
|
+
} else if (status === 'completed') {
|
|
219
|
+
filter.ended_at = { $ne: null };
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const meetings = await MeetingModel.find(filter)
|
|
223
|
+
.populate('host_id', 'first_name last_name email')
|
|
224
|
+
.populate('participants.user_id', 'first_name last_name email')
|
|
225
|
+
.sort({ scheduled_at: -1 });
|
|
226
|
+
|
|
227
|
+
res.json({ success: true, meetings });
|
|
228
|
+
} catch (error) {
|
|
229
|
+
console.error('Error fetching meetings:', error);
|
|
230
|
+
res.status(500).json({ error: 'Failed to fetch meetings' });
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/**
|
|
235
|
+
* Get meeting details
|
|
236
|
+
*/
|
|
237
|
+
public static async getMeeting(req: any, res: any): Promise<void> {
|
|
238
|
+
try {
|
|
239
|
+
const meeting = req.meeting;
|
|
240
|
+
|
|
241
|
+
await meeting.populate('host_id', 'first_name last_name email');
|
|
242
|
+
await meeting.populate('participants.user_id', 'first_name last_name email');
|
|
243
|
+
|
|
244
|
+
res.json({ success: true, meeting });
|
|
245
|
+
} catch (error) {
|
|
246
|
+
console.error('Error fetching meeting:', error);
|
|
247
|
+
res.status(500).json({ error: 'Failed to fetch meeting' });
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
export default Meetings;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { Request, Response } from 'express';
|
|
2
|
+
import * as fs from 'fs';
|
|
3
|
+
import * as path from 'path';
|
|
4
|
+
import { runSeeders } from '../../database/seeder';
|
|
5
|
+
|
|
6
|
+
export class ModulesController {
|
|
7
|
+
async getModules(req: Request, res: Response) {
|
|
8
|
+
try {
|
|
9
|
+
// Try multiple possible paths for modules.json
|
|
10
|
+
let modulesPath: string | undefined;
|
|
11
|
+
|
|
12
|
+
// List of possible paths to check
|
|
13
|
+
const possiblePaths = [
|
|
14
|
+
// Development path
|
|
15
|
+
path.join(__dirname, '../data/modules.json'),
|
|
16
|
+
// Production path (when called from user-api)
|
|
17
|
+
path.join(__dirname, '../../data/modules.json'),
|
|
18
|
+
// Alternative production path
|
|
19
|
+
path.join(__dirname, '../../../backend/src/data/modules.json'),
|
|
20
|
+
// Relative to backend project root
|
|
21
|
+
path.join(process.cwd(), 'src/data/modules.json'),
|
|
22
|
+
// Relative to backend project root (dist)
|
|
23
|
+
path.join(process.cwd(), 'dist/data/modules.json'),
|
|
24
|
+
// Absolute path fallback
|
|
25
|
+
'/Users/abdegracia/aldrin-degracia/code/rnd/inventory-management-system/backend/src/data/modules.json'
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
// Find the first existing path
|
|
29
|
+
for (const testPath of possiblePaths) {
|
|
30
|
+
if (fs.existsSync(testPath)) {
|
|
31
|
+
modulesPath = testPath;
|
|
32
|
+
break;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
if (!modulesPath) {
|
|
37
|
+
throw new Error('modules.json file not found in any expected location');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const modulesData = JSON.parse(fs.readFileSync(modulesPath, 'utf-8'));
|
|
41
|
+
|
|
42
|
+
res.json({
|
|
43
|
+
success: true,
|
|
44
|
+
data: modulesData
|
|
45
|
+
});
|
|
46
|
+
} catch (error) {
|
|
47
|
+
console.error('Error reading modules data:', error);
|
|
48
|
+
res.status(500).json({
|
|
49
|
+
success: false,
|
|
50
|
+
message: 'Failed to load modules data',
|
|
51
|
+
error: String(error)
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async runSeeders(req: Request, res: Response) {
|
|
57
|
+
try {
|
|
58
|
+
console.log('Running seeders via API call...');
|
|
59
|
+
await runSeeders();
|
|
60
|
+
|
|
61
|
+
res.json({
|
|
62
|
+
success: true,
|
|
63
|
+
message: 'Seeders executed successfully'
|
|
64
|
+
});
|
|
65
|
+
} catch (error) {
|
|
66
|
+
console.error('Error running seeders:', error);
|
|
67
|
+
res.status(500).json({
|
|
68
|
+
success: false,
|
|
69
|
+
message: 'Failed to run seeders',
|
|
70
|
+
error: String(error)
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|