@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.
Files changed (101) hide show
  1. package/dist/controllers/auth/index.d.ts +65 -0
  2. package/dist/controllers/auth/index.js +525 -0
  3. package/dist/controllers/employees/index.d.ts +38 -0
  4. package/dist/controllers/employees/index.js +185 -0
  5. package/dist/controllers/health/index.d.ts +9 -0
  6. package/dist/controllers/health/index.js +42 -0
  7. package/dist/controllers/index.d.ts +16 -0
  8. package/dist/controllers/index.js +19 -0
  9. package/dist/controllers/meetings/index.d.ts +23 -0
  10. package/dist/controllers/meetings/index.js +233 -0
  11. package/dist/controllers/modules/index.d.ts +5 -0
  12. package/dist/controllers/modules/index.js +104 -0
  13. package/dist/controllers/users/index.d.ts +103 -0
  14. package/dist/controllers/users/index.js +841 -0
  15. package/dist/data/modules.json +94 -0
  16. package/dist/database/config/index.d.ts +2 -0
  17. package/dist/database/config/index.js +32 -0
  18. package/dist/database/index.d.ts +9 -0
  19. package/dist/database/index.js +9 -0
  20. package/dist/database/seeder/employees/index.d.ts +1 -0
  21. package/dist/database/seeder/employees/index.js +40 -0
  22. package/dist/database/seeder/index.d.ts +4 -0
  23. package/dist/database/seeder/index.js +20 -0
  24. package/dist/database/seeder/users/index.d.ts +1 -0
  25. package/dist/database/seeder/users/index.js +46 -0
  26. package/dist/index.d.ts +9 -0
  27. package/dist/index.js +23 -0
  28. package/dist/jobs/index.d.ts +1 -0
  29. package/dist/jobs/index.js +18 -0
  30. package/dist/jobs/mailer/index.d.ts +4 -0
  31. package/dist/jobs/mailer/index.js +186 -0
  32. package/dist/jobs/mailer/templates/auth.d.ts +11 -0
  33. package/dist/jobs/mailer/templates/auth.js +117 -0
  34. package/dist/jobs/mailer/templates/index.d.ts +1 -0
  35. package/dist/jobs/mailer/templates/index.js +17 -0
  36. package/dist/jobs/queues/index.d.ts +3 -0
  37. package/dist/jobs/queues/index.js +115 -0
  38. package/dist/middlewares/audit/index.d.ts +0 -0
  39. package/dist/middlewares/audit/index.js +1 -0
  40. package/dist/middlewares/guard/index.d.ts +11 -0
  41. package/dist/middlewares/guard/index.js +53 -0
  42. package/dist/middlewares/index.d.ts +2 -0
  43. package/dist/middlewares/index.js +7 -0
  44. package/dist/middlewares/meeting.d.ts +9 -0
  45. package/dist/middlewares/meeting.js +34 -0
  46. package/dist/models/employees/index.d.ts +83 -0
  47. package/dist/models/employees/index.js +70 -0
  48. package/dist/models/index.d.ts +570 -0
  49. package/dist/models/index.js +17 -0
  50. package/dist/models/meetings/index.d.ts +227 -0
  51. package/dist/models/meetings/index.js +112 -0
  52. package/dist/models/passkeys/index.d.ts +77 -0
  53. package/dist/models/passkeys/index.js +55 -0
  54. package/dist/models/queues/index.d.ts +77 -0
  55. package/dist/models/queues/index.js +57 -0
  56. package/dist/models/users/index.d.ts +107 -0
  57. package/dist/models/users/index.js +92 -0
  58. package/dist/queues/index.d.ts +1 -0
  59. package/dist/queues/index.js +17 -0
  60. package/dist/queues/mailer/index.d.ts +4 -0
  61. package/dist/queues/mailer/index.js +74 -0
  62. package/dist/types/index.d.ts +33 -0
  63. package/dist/types/index.js +2 -0
  64. package/dist/utils/notifications.d.ts +2 -0
  65. package/dist/utils/notifications.js +51 -0
  66. package/package.json +39 -0
  67. package/public/health.html +215 -0
  68. package/src/controllers/auth/index.ts +609 -0
  69. package/src/controllers/employees/index.ts +210 -0
  70. package/src/controllers/health/index.ts +41 -0
  71. package/src/controllers/index.ts +9 -0
  72. package/src/controllers/meetings/index.ts +251 -0
  73. package/src/controllers/modules/index.ts +74 -0
  74. package/src/controllers/users/index.ts +981 -0
  75. package/src/data/modules.json +94 -0
  76. package/src/database/config/index.ts +26 -0
  77. package/src/database/index.ts +5 -0
  78. package/src/database/seeder/employees/index.ts +35 -0
  79. package/src/database/seeder/index.ts +18 -0
  80. package/src/database/seeder/users/index.ts +44 -0
  81. package/src/index.ts +10 -0
  82. package/src/jobs/index.ts +2 -0
  83. package/src/jobs/mailer/index.ts +154 -0
  84. package/src/jobs/mailer/templates/auth.ts +113 -0
  85. package/src/jobs/mailer/templates/index.ts +1 -0
  86. package/src/jobs/queues/index.ts +125 -0
  87. package/src/middlewares/audit/index.ts +0 -0
  88. package/src/middlewares/guard/index.ts +64 -0
  89. package/src/middlewares/index.ts +5 -0
  90. package/src/middlewares/meeting.ts +45 -0
  91. package/src/models/employees/index.ts +70 -0
  92. package/src/models/index.ts +8 -0
  93. package/src/models/meetings/index.ts +112 -0
  94. package/src/models/passkeys/index.ts +53 -0
  95. package/src/models/queues/index.ts +55 -0
  96. package/src/models/users/index.ts +92 -0
  97. package/src/queues/index.ts +1 -0
  98. package/src/queues/mailer/index.ts +80 -0
  99. package/src/types/index.ts +38 -0
  100. package/src/utils/notifications.ts +66 -0
  101. package/tsconfig.json +18 -0
@@ -0,0 +1,94 @@
1
+ {
2
+ "modules": [
3
+ {
4
+ "id": "dashboard",
5
+ "name": "Dashboard",
6
+ "icon": "LayoutDashboard",
7
+ "path": "/dashboard",
8
+ "children": []
9
+ },
10
+ {
11
+ "id": "inventory",
12
+ "name": "Inventory",
13
+ "icon": "Package",
14
+ "path": "/inventory",
15
+ "children": [
16
+ {
17
+ "id": "products",
18
+ "name": "Products",
19
+ "path": "/inventory/products"
20
+ },
21
+ {
22
+ "id": "categories",
23
+ "name": "Categories",
24
+ "path": "/inventory/categories"
25
+ },
26
+ {
27
+ "id": "suppliers",
28
+ "name": "Suppliers",
29
+ "path": "/inventory/suppliers"
30
+ }
31
+ ]
32
+ },
33
+ {
34
+ "id": "reports",
35
+ "name": "Reports",
36
+ "icon": "BarChart3",
37
+ "path": "/reports",
38
+ "children": [
39
+ {
40
+ "id": "sales",
41
+ "name": "Sales",
42
+ "path": "/reports/sales"
43
+ },
44
+ {
45
+ "id": "inventory-reports",
46
+ "name": "Inventory",
47
+ "path": "/reports/inventory"
48
+ },
49
+ {
50
+ "id": "financial",
51
+ "name": "Financial",
52
+ "path": "/reports/financial"
53
+ }
54
+ ]
55
+ },
56
+ {
57
+ "id": "settings",
58
+ "name": "Settings",
59
+ "icon": "Settings",
60
+ "path": "/settings",
61
+ "children": [
62
+ {
63
+ "id": "general",
64
+ "name": "General",
65
+ "path": "/settings/general"
66
+ },
67
+ {
68
+ "id": "users",
69
+ "name": "Users",
70
+ "icon": "Users",
71
+ "path": "/settings/users",
72
+ "children": [
73
+ {
74
+ "id": "roles",
75
+ "name": "Roles",
76
+ "path": "/settings/users/roles"
77
+ },
78
+ {
79
+ "id": "permissions",
80
+ "name": "Permissions",
81
+ "path": "/settings/users/permissions"
82
+ }
83
+ ]
84
+ },
85
+ {
86
+ "id": "notifications",
87
+ "name": "Notifications",
88
+ "icon": "Bell",
89
+ "path": "/settings/notifications"
90
+ }
91
+ ]
92
+ }
93
+ ]
94
+ }
@@ -0,0 +1,26 @@
1
+ import mongoose from 'mongoose';
2
+
3
+ const mongoUri = process.env.APP_MONGO_URI;
4
+ if (!mongoUri) {
5
+ throw new Error('APP_MONGO_URI environment variable is not set');
6
+ }
7
+
8
+ export async function connectDB(): Promise<void> {
9
+ try {
10
+ await mongoose.connect(mongoUri as string);
11
+ console.log('MongoDB connected');
12
+ } catch (err) {
13
+ console.error('MongoDB connection error:', err);
14
+ throw err;
15
+ }
16
+ }
17
+
18
+ export async function disconnectDB(): Promise<void> {
19
+ try {
20
+ await mongoose.disconnect();
21
+ console.log('MongoDB disconnected');
22
+ } catch (err) {
23
+ console.error('MongoDB disconnection error:', err);
24
+ throw err;
25
+ }
26
+ }
@@ -0,0 +1,5 @@
1
+ import { connectDB, disconnectDB } from './config';
2
+ import { runSeeders } from './seeder';
3
+
4
+ export { connectDB, disconnectDB, runSeeders };
5
+ export default { connectDB, disconnectDB, runSeeders };
@@ -0,0 +1,35 @@
1
+ import EmployeesModel from '../../../models/employees';
2
+
3
+ export const seedEmployees = async () => {
4
+ try {
5
+ const existingEmployees = await EmployeesModel.find({}).lean().exec();
6
+
7
+ if (existingEmployees.length === 0) {
8
+ const seederEmail = process.env.SEEDER_USER_EMAIL;
9
+ if (!seederEmail) {
10
+ throw new Error('SEEDER_USER_EMAIL environment variable is required');
11
+ }
12
+
13
+ const sampleEmployee = {
14
+ first_name: 'System',
15
+ middle_name: '',
16
+ last_name: 'Administrator',
17
+ email: seederEmail,
18
+ contact_number: '+1234567890',
19
+ position: 'System Administrator',
20
+ department: 'IT',
21
+ address: 'System Administration',
22
+ created_at: Date.now(),
23
+ updated_at: Date.now()
24
+ };
25
+
26
+ await EmployeesModel.create(sampleEmployee);
27
+ console.log('Sample employee seeded successfully');
28
+ } else {
29
+ console.log('Employees already exist, skipping seeding');
30
+ }
31
+ } catch (error) {
32
+ console.error('Error seeding employees:', error);
33
+ throw error;
34
+ }
35
+ };
@@ -0,0 +1,18 @@
1
+ import { seedEmployees } from './employees';
2
+ import { seedUsers } from './users';
3
+
4
+ export const runSeeders = async () => {
5
+ try {
6
+ console.log('Running database seeders...');
7
+
8
+ await seedEmployees();
9
+ await seedUsers();
10
+
11
+ console.log('All seeders completed successfully');
12
+ } catch (error) {
13
+ console.error('Error running seeders:', error);
14
+ throw error;
15
+ }
16
+ };
17
+
18
+ export { seedEmployees, seedUsers };
@@ -0,0 +1,44 @@
1
+ import UsersModel from '../../../models/users';
2
+ import EmployeesModel from '../../../models/employees';
3
+ import bcrypt from 'bcrypt';
4
+
5
+ export const seedUsers = async () => {
6
+ try {
7
+ const existingUsers = await UsersModel.find({}).lean().exec();
8
+
9
+ if (existingUsers.length === 0) {
10
+ // Get employees to link with users
11
+ const employees = await EmployeesModel.find({}).lean().exec();
12
+
13
+ const seederPassword = process.env.SEEDER_USER_PASSWORD;
14
+ const seederEmail = process.env.SEEDER_USER_EMAIL;
15
+
16
+ if (!seederPassword || !seederEmail) {
17
+ throw new Error('SEEDER_USER_EMAIL and SEEDER_USER_PASSWORD environment variables are required');
18
+ }
19
+
20
+ const hashedPassword = await bcrypt.hash(seederPassword, 10);
21
+
22
+ const sampleUser = {
23
+ email: seederEmail,
24
+ password: hashedPassword,
25
+ first_name: 'System',
26
+ middle_name: '',
27
+ last_name: 'Administrator',
28
+ role: 'admin',
29
+ employee_id: employees[0]?._id || null,
30
+ is_active: true,
31
+ created_at: Date.now(),
32
+ updated_at: Date.now()
33
+ };
34
+
35
+ await UsersModel.create(sampleUser);
36
+ console.log('Sample user seeded successfully');
37
+ } else {
38
+ console.log('Users already exist, skipping seeding');
39
+ }
40
+ } catch (error) {
41
+ console.error('Error seeding users:', error);
42
+ throw error;
43
+ }
44
+ };
package/src/index.ts ADDED
@@ -0,0 +1,10 @@
1
+ import 'dotenv/config'
2
+ import { connectDB } from './database/config'
3
+ import { EmployeesController, UsersController, ModulesController, AuthController, HealthController, MeetingsController } from './controllers'
4
+ import { authenticateToken } from './middlewares'
5
+ import { runSeeders } from './database/seeder'
6
+ import { Employees, Users, Passkeys, Queues, Meetings } from './models'
7
+ import type { MulterRequest } from './types'
8
+
9
+ export { connectDB, EmployeesController, UsersController, ModulesController, AuthController, authenticateToken, HealthController, MeetingsController, runSeeders, Employees, Users, Passkeys, Queues, Meetings }
10
+ export type { MulterRequest }
@@ -0,0 +1,2 @@
1
+ // Export queue modules and worker starters
2
+ export * from '../queues';
@@ -0,0 +1,154 @@
1
+ import * as nodemailer from 'nodemailer';
2
+
3
+ const transporter = nodemailer.createTransport({
4
+ host: process.env.APP_MAILER_HOST,
5
+ port: Number(process.env.APP_MAILER_PORT) || 587,
6
+ secure: process.env.APP_MAILER_SERVICE === 'gmail' ? false : true,
7
+ service: process.env.APP_MAILER_SERVICE,
8
+ auth: {
9
+ user: process.env.APP_MAILER_USER,
10
+ pass: process.env.APP_MAILER_PASS,
11
+ },
12
+ });
13
+
14
+ export const sendEmail = async (to: string, subject: string, html: string) => {
15
+ try {
16
+ const mailOptions = {
17
+ from: process.env.APP_MAILER_USER,
18
+ to,
19
+ subject,
20
+ html,
21
+ };
22
+
23
+ await transporter.sendMail(mailOptions);
24
+ console.log(`Email sent successfully to ${to}`);
25
+ } catch (error) {
26
+ console.error('Error sending email:', error);
27
+ throw error;
28
+ }
29
+ };
30
+
31
+ export const sendPasswordResetEmail = async (email: string, name: string, resetToken: string) => {
32
+ const subject = 'Password Reset Request - Inventory Management System';
33
+ const html = `<!DOCTYPE html>
34
+ <html lang="en">
35
+ <head>
36
+ <meta charset="UTF-8">
37
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
38
+ <title>Password Reset - Inventory Management System</title>
39
+ <style>
40
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
41
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
42
+ .header { background: #f8f9fa; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; }
43
+ .content { background: #ffffff; padding: 30px; border-radius: 0 0 8px 8px; }
44
+ .button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin: 20px 0; }
45
+ .footer { text-align: center; padding: 20px; font-size: 12px; color: #666; }
46
+ </style>
47
+ </head>
48
+ <body>
49
+ <div class="container">
50
+ <div class="header">
51
+ <h1>🔐 Password Reset Request</h1>
52
+ </div>
53
+ <div class="content">
54
+ <p>Hello <strong>${name}</strong>,</p>
55
+ <p>We received a request to reset your password for the Inventory Management System. Click the button below to reset your password:</p>
56
+ <p style="text-align: center;">
57
+ <a href="${process.env.APP_MOBILE_APP_URL}/reset-password?token=${resetToken}" class="button">Reset Password</a>
58
+ </p>
59
+ <p>This link will expire in 1 hour for security reasons.</p>
60
+ <p>If you didn't request this password reset, please ignore this email.</p>
61
+ </div>
62
+ <div class="footer">
63
+ <p>© 2024 Inventory Management System. All rights reserved.</p>
64
+ </div>
65
+ </div>
66
+ </body>
67
+ </html>`;
68
+
69
+ await sendEmail(email, subject, html);
70
+ };
71
+
72
+ export const sendPasswordChangedEmail = async (email: string, name: string) => {
73
+ const subject = 'Password Changed - Inventory Management System';
74
+ const html = `<!DOCTYPE html>
75
+ <html lang="en">
76
+ <head>
77
+ <meta charset="UTF-8">
78
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
79
+ <title>Password Changed - Inventory Management System</title>
80
+ <style>
81
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
82
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
83
+ .header { background: #28a745; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; color: white; }
84
+ .content { background: #ffffff; padding: 30px; border-radius: 0 0 8px 8px; }
85
+ .button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin: 20px 0; }
86
+ .footer { text-align: center; padding: 20px; font-size: 12px; color: #666; }
87
+ </style>
88
+ </head>
89
+ <body>
90
+ <div class="container">
91
+ <div class="header">
92
+ <h1>✅ Password Successfully Changed</h1>
93
+ </div>
94
+ <div class="content">
95
+ <p>Hello <strong>${name}</strong>,</p>
96
+ <p>Your password for the Inventory Management System has been successfully changed.</p>
97
+ <p>If you didn't make this change, please contact our support team immediately.</p>
98
+ <p>For security reasons, we recommend:</p>
99
+ <ul>
100
+ <li>Using a strong, unique password</li>
101
+ <li>Not sharing your password with anyone</li>
102
+ <li>Enabling two-factor authentication if available</li>
103
+ </ul>
104
+ </div>
105
+ <div class="footer">
106
+ <p>© 2024 Inventory Management System. All rights reserved.</p>
107
+ </div>
108
+ </div>
109
+ </body>
110
+ </html>`;
111
+
112
+ await sendEmail(email, subject, html);
113
+ };
114
+
115
+ export const sendPasskeyRegisteredEmail = async (email: string, name: string, deviceName: string) => {
116
+ const subject = 'New Passkey Registered - Inventory Management System';
117
+ const html = `<!DOCTYPE html>
118
+ <html lang="en">
119
+ <head>
120
+ <meta charset="UTF-8">
121
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
122
+ <title>Passkey Registered - Inventory Management System</title>
123
+ <style>
124
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
125
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
126
+ .header { background: #6f42c1; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; color: white; }
127
+ .content { background: #ffffff; padding: 30px; border-radius: 0 0 8px 8px; }
128
+ .button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin: 20px 0; }
129
+ .footer { text-align: center; padding: 20px; font-size: 12px; color: #666; }
130
+ </style>
131
+ </head>
132
+ <body>
133
+ <div class="container">
134
+ <div class="header">
135
+ <h1>🔑 New Passkey Registered</h1>
136
+ </div>
137
+ <div class="content">
138
+ <p>Hello <strong>${name}</strong>,</p>
139
+ <p>A new passkey has been registered for your Inventory Management System account:</p>
140
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0;">
141
+ <p><strong>Device:</strong> ${deviceName}</p>
142
+ </div>
143
+ <p>You can now use this passkey to sign in to your account without a password.</p>
144
+ <p>If you didn't register this passkey, please secure your account immediately.</p>
145
+ </div>
146
+ <div class="footer">
147
+ <p>© 2024 Inventory Management System. All rights reserved.</p>
148
+ </div>
149
+ </div>
150
+ </body>
151
+ </html>`;
152
+
153
+ await sendEmail(email, subject, html);
154
+ };
@@ -0,0 +1,113 @@
1
+ export const forgotPasswordTemplate = (data: { name: string; reset_token: string }) => `
2
+ <!DOCTYPE html>
3
+ <html lang="en">
4
+ <head>
5
+ <meta charset="UTF-8">
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
+ <title>Password Reset - Inventory Management System</title>
8
+ <style>
9
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
10
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
11
+ .header { background: #f8f9fa; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; }
12
+ .content { background: #ffffff; padding: 30px; border-radius: 0 0 8px 8px; }
13
+ .button { display: inline-block; padding: 12px 24px; background: #007bff; color: white; text-decoration: none; border-radius: 5px; margin: 20px 0; }
14
+ .footer { text-align: center; padding: 20px; font-size: 12px; color: #666; }
15
+ </style>
16
+ </head>
17
+ <body>
18
+ <div class="container">
19
+ <div class="header">
20
+ <h1>🔐 Password Reset Request</h1>
21
+ </div>
22
+ <div class="content">
23
+ <p>Hello <strong>${data.name}</strong>,</p>
24
+ <p>We received a request to reset your password for the Inventory Management System. Click the button below to reset your password:</p>
25
+ <p style="text-align: center;">
26
+ <a href="${process.env.APP_MOBILE_APP_URL}/reset-password?token=${data.reset_token}" class="button">Reset Password</a>
27
+ </p>
28
+ <p>This link will expire in 1 hour for security reasons.</p>
29
+ <p>If you didn't request this password reset, please ignore this email.</p>
30
+ </div>
31
+ <div class="footer">
32
+ <p>© 2024 Inventory Management System. All rights reserved.</p>
33
+ </div>
34
+ </div>
35
+ </body>
36
+ </html>
37
+ `;
38
+
39
+ export const passwordChangedTemplate = (data: { name: string }) => `
40
+ <!DOCTYPE html>
41
+ <html lang="en">
42
+ <head>
43
+ <meta charset="UTF-8">
44
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
45
+ <title>Password Changed - Inventory Management System</title>
46
+ <style>
47
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
48
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
49
+ .header { background: #28a745; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; color: white; }
50
+ .content { background: #ffffff; padding: 30px; border-radius: 0 0 8px 8px; }
51
+ .footer { text-align: center; padding: 20px; font-size: 12px; color: #666; }
52
+ </style>
53
+ </head>
54
+ <body>
55
+ <div class="container">
56
+ <div class="header">
57
+ <h1>✅ Password Successfully Changed</h1>
58
+ </div>
59
+ <div class="content">
60
+ <p>Hello <strong>${data.name}</strong>,</p>
61
+ <p>Your password for the Inventory Management System has been successfully changed.</p>
62
+ <p>If you didn't make this change, please contact our support team immediately.</p>
63
+ <p>For security reasons, we recommend:</p>
64
+ <ul>
65
+ <li>Using a strong, unique password</li>
66
+ <li>Not sharing your password with anyone</li>
67
+ <li>Enabling two-factor authentication if available</li>
68
+ </ul>
69
+ </div>
70
+ <div class="footer">
71
+ <p>© 2024 Inventory Management System. All rights reserved.</p>
72
+ </div>
73
+ </div>
74
+ </body>
75
+ </html>
76
+ `;
77
+
78
+ export const passkeyRegisteredTemplate = (data: { name: string; device_name: string }) => `
79
+ <!DOCTYPE html>
80
+ <html lang="en">
81
+ <head>
82
+ <meta charset="UTF-8">
83
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
84
+ <title>Passkey Registered - Inventory Management System</title>
85
+ <style>
86
+ body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; }
87
+ .container { max-width: 600px; margin: 0 auto; padding: 20px; }
88
+ .header { background: #6f42c1; padding: 20px; text-align: center; border-radius: 8px 8px 0 0; color: white; }
89
+ .content { background: #ffffff; padding: 30px; border-radius: 0 0 8px 8px; }
90
+ .footer { text-align: center; padding: 20px; font-size: 12px; color: #666; }
91
+ </style>
92
+ </head>
93
+ <body>
94
+ <div class="container">
95
+ <div class="header">
96
+ <h1>🔑 New Passkey Registered</h1>
97
+ </div>
98
+ <div class="content">
99
+ <p>Hello <strong>${data.name}</strong>,</p>
100
+ <p>A new passkey has been registered for your Inventory Management System account:</p>
101
+ <div style="background: #f8f9fa; padding: 15px; border-radius: 5px; margin: 20px 0;">
102
+ <p><strong>Device:</strong> ${data.device_name}</p>
103
+ </div>
104
+ <p>You can now use this passkey to sign in to your account without a password.</p>
105
+ <p>If you didn't register this passkey, please secure your account immediately.</p>
106
+ </div>
107
+ <div class="footer">
108
+ <p>© 2024 Inventory Management System. All rights reserved.</p>
109
+ </div>
110
+ </div>
111
+ </body>
112
+ </html>
113
+ `;
@@ -0,0 +1 @@
1
+ export * from './auth';
@@ -0,0 +1,125 @@
1
+ import Redis from 'ioredis';
2
+ import { sendPasswordResetEmail, sendPasswordChangedEmail, sendPasskeyRegisteredEmail } from '../mailer';
3
+ import QueuesModel from '../../models/queues';
4
+
5
+ const redis = new Redis({
6
+ host: process.env.REDIS_HOST || 'localhost',
7
+ port: Number(process.env.REDIS_PORT) || 6379,
8
+ password: process.env.REDIS_PASSWORD,
9
+ db: 0,
10
+ });
11
+
12
+ export const processEmailQueue = async () => {
13
+ try {
14
+ const pendingEmails = await QueuesModel.find({
15
+ type: 'email',
16
+ status: 'pending',
17
+ deleted_at: null
18
+ })
19
+ .sort({ created_at: 1 })
20
+ .limit(10)
21
+ .lean()
22
+ .exec();
23
+
24
+ for (const emailJob of pendingEmails) {
25
+ try {
26
+ const { data } = emailJob;
27
+
28
+ if (data.action === 'password_changed') {
29
+ await sendPasswordChangedEmail(data.email, data.name);
30
+ } else if (data.action === 'passkey_registered') {
31
+ await sendPasskeyRegisteredEmail(data.email, data.name, data.device_name || 'Unknown Device');
32
+ }
33
+
34
+ // Update queue status
35
+ await QueuesModel.updateOne(
36
+ { _id: emailJob._id },
37
+ {
38
+ status: 'completed',
39
+ processed_at: Date.now(),
40
+ updated_at: Date.now()
41
+ }
42
+ );
43
+
44
+ console.log(`Email sent successfully to ${data.email}`);
45
+ } catch (error) {
46
+ console.error(`Failed to send email to ${emailJob.data.email}:`, error);
47
+
48
+ // Update queue status to failed
49
+ await QueuesModel.updateOne(
50
+ { _id: emailJob._id },
51
+ {
52
+ status: 'failed',
53
+ error_message: String(error),
54
+ attempts: emailJob.attempts + 1,
55
+ updated_at: Date.now()
56
+ }
57
+ );
58
+ }
59
+ }
60
+ } catch (error) {
61
+ console.error('Error processing email queue:', error);
62
+ }
63
+ };
64
+
65
+ export const processPasswordResetQueue = async () => {
66
+ try {
67
+ const pendingResets = await QueuesModel.find({
68
+ type: 'password_reset',
69
+ status: 'pending',
70
+ deleted_at: null
71
+ })
72
+ .sort({ created_at: 1 })
73
+ .limit(10)
74
+ .lean()
75
+ .exec();
76
+
77
+ for (const resetJob of pendingResets) {
78
+ try {
79
+ const { data } = resetJob;
80
+ await sendPasswordResetEmail(data.email, data.name, data.reset_token);
81
+
82
+ // Update queue status
83
+ await QueuesModel.updateOne(
84
+ { _id: resetJob._id },
85
+ {
86
+ status: 'completed',
87
+ processed_at: Date.now(),
88
+ updated_at: Date.now()
89
+ }
90
+ );
91
+
92
+ console.log(`Password reset email sent successfully to ${data.email}`);
93
+ } catch (error) {
94
+ console.error(`Failed to send password reset email to ${resetJob.data.email}:`, error);
95
+
96
+ // Update queue status to failed
97
+ await QueuesModel.updateOne(
98
+ { _id: resetJob._id },
99
+ {
100
+ status: 'failed',
101
+ error_message: String(error),
102
+ attempts: resetJob.attempts + 1,
103
+ updated_at: Date.now()
104
+ }
105
+ );
106
+ }
107
+ }
108
+ } catch (error) {
109
+ console.error('Error processing password reset queue:', error);
110
+ }
111
+ };
112
+
113
+ export const addToQueue = async (type: string, data: any) => {
114
+ try {
115
+ await QueuesModel.create({
116
+ type,
117
+ data,
118
+ status: 'pending',
119
+ created_at: Date.now()
120
+ });
121
+ } catch (error) {
122
+ console.error('Error adding to queue:', error);
123
+ throw error;
124
+ }
125
+ };
File without changes