create-arktos 1.2.0 โ†’ 1.4.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/bin/cli.js CHANGED
@@ -3,11 +3,13 @@
3
3
  const fs = require('fs');
4
4
  const path = require('path');
5
5
  const { execSync } = require('child_process');
6
+ const { input, select } = require('inquirer');
6
7
 
7
- const TEMPLATE_DIR = path.join(__dirname, '../src');
8
+ const TEMPLATE_TS_DIR = path.join(__dirname, '../src');
9
+ const TEMPLATE_JS_DIR = path.join(__dirname, '../src-js');
8
10
  const ROOT_DIR = path.join(__dirname, '..');
9
11
 
10
- function createProject(projectName) {
12
+ async function createProject(projectName) {
11
13
  console.log(`๐Ÿš€ Creating Arktos project: ${projectName}`);
12
14
  console.log();
13
15
 
@@ -23,15 +25,29 @@ function createProject(projectName) {
23
25
  process.exit(1);
24
26
  }
25
27
 
28
+ // Ask user for language preference
29
+ const language = await select({
30
+ message: '๐Ÿ“ Which language would you like to use?',
31
+ choices: [
32
+ { name: 'TypeScript (Recommended)', value: 'typescript' },
33
+ { name: 'JavaScript', value: 'javascript' }
34
+ ],
35
+ default: 'typescript'
36
+ });
37
+
38
+ const useTypeScript = language === 'typescript';
39
+ const templateDir = useTypeScript ? TEMPLATE_TS_DIR : TEMPLATE_JS_DIR;
40
+ const packageTemplate = useTypeScript ? 'template.package.json' : 'template.js.package.json';
41
+
26
42
  try {
27
43
  // Create project directory
28
44
  console.log('๐Ÿ“ Creating project directory...');
29
45
  fs.mkdirSync(projectName);
30
46
 
31
47
  // Copy src directory
32
- console.log('๐Ÿ“‹ Copying source files...');
48
+ console.log(`๐Ÿ“‹ Copying ${useTypeScript ? 'TypeScript' : 'JavaScript'} source files...`);
33
49
  const targetSrcDir = path.join(projectName, 'src');
34
- copyDir(TEMPLATE_DIR, targetSrcDir);
50
+ copyDir(templateDir, targetSrcDir);
35
51
 
36
52
  // Copy prisma directory
37
53
  console.log('๐Ÿ—„๏ธ Copying database schema...');
@@ -43,11 +59,11 @@ function createProject(projectName) {
43
59
 
44
60
  // Copy template config files
45
61
  console.log('๐Ÿ“ Copying configuration files...');
46
- copyTemplateFiles(projectName);
62
+ copyTemplateFiles(projectName, useTypeScript);
47
63
 
48
64
  // Copy and update package.json
49
65
  console.log('๐Ÿ”ง Updating project configuration...');
50
- const templatePackageJsonPath = path.join(ROOT_DIR, 'template.package.json');
66
+ const templatePackageJsonPath = path.join(ROOT_DIR, packageTemplate);
51
67
  const targetPackageJsonPath = path.join(projectName, 'package.json');
52
68
 
53
69
  if (fs.existsSync(templatePackageJsonPath)) {
@@ -59,6 +75,12 @@ function createProject(projectName) {
59
75
  fs.writeFileSync(targetPackageJsonPath, JSON.stringify(packageJson, null, 2));
60
76
  }
61
77
 
78
+ // Clean up TypeScript specific files if JavaScript is selected
79
+ if (!useTypeScript) {
80
+ console.log('๐Ÿงน Cleaning up TypeScript specific files...');
81
+ cleanupTypeScriptFiles(projectName);
82
+ }
83
+
62
84
  console.log('โœ… Project created successfully!');
63
85
  console.log();
64
86
  console.log('๐ŸŽฏ Next steps:');
@@ -122,7 +144,30 @@ function copyDir(src, dest) {
122
144
  }
123
145
  }
124
146
 
125
- function copyTemplateFiles(dest) {
147
+ function cleanupTypeScriptFiles(projectPath) {
148
+ try {
149
+ // Remove TypeScript specific directories and files
150
+ const tsSpecificPaths = [
151
+ path.join(projectPath, 'src', 'types'),
152
+ path.join(projectPath, 'tsconfig.json')
153
+ ];
154
+
155
+ for (const tsPath of tsSpecificPaths) {
156
+ if (fs.existsSync(tsPath)) {
157
+ const stats = fs.statSync(tsPath);
158
+ if (stats.isDirectory()) {
159
+ fs.rmSync(tsPath, { recursive: true, force: true });
160
+ } else {
161
+ fs.unlinkSync(tsPath);
162
+ }
163
+ }
164
+ }
165
+ } catch (error) {
166
+ console.warn('โš ๏ธ Warning: Could not clean up some TypeScript files:', error.message);
167
+ }
168
+ }
169
+
170
+ function copyTemplateFiles(dest, useTypeScript = true) {
126
171
  // Create common config files
127
172
  const configFiles = {
128
173
  '.gitignore': `# Dependencies
@@ -263,8 +308,11 @@ Happy coding! ๐ŸŽ‰`
263
308
  fs.writeFileSync(path.join(dest, fileName), content);
264
309
  }
265
310
 
266
- // Copy template files from root
267
- const templateFiles = ['.env.example', 'vercel.json', 'tsconfig.json', 'eslint.config.js'];
311
+ // Copy template files from root based on language choice
312
+ const commonTemplateFiles = ['.env.example', 'vercel.json'];
313
+ const templateFiles = useTypeScript
314
+ ? [...commonTemplateFiles, 'tsconfig.json', 'eslint.config.js']
315
+ : [...commonTemplateFiles];
268
316
 
269
317
  for (const templateFile of templateFiles) {
270
318
  const srcPath = path.join(ROOT_DIR, templateFile);
@@ -274,6 +322,16 @@ Happy coding! ๐ŸŽ‰`
274
322
  fs.copyFileSync(srcPath, destPath);
275
323
  }
276
324
  }
325
+
326
+ // Copy appropriate ESLint config for JavaScript projects
327
+ if (!useTypeScript) {
328
+ const jsEslintConfigPath = path.join(ROOT_DIR, 'eslint.config.js.template');
329
+ const destEslintConfigPath = path.join(dest, 'eslint.config.js');
330
+
331
+ if (fs.existsSync(jsEslintConfigPath)) {
332
+ fs.copyFileSync(jsEslintConfigPath, destEslintConfigPath);
333
+ }
334
+ }
277
335
  }
278
336
 
279
337
  function showHelp() {
@@ -324,4 +382,7 @@ if (args[0] === '-v' || args[0] === '--version') {
324
382
  }
325
383
 
326
384
  const projectName = args[0];
327
- createProject(projectName);
385
+ createProject(projectName).catch((error) => {
386
+ console.error('โŒ Error creating project:', error.message);
387
+ process.exit(1);
388
+ });
@@ -0,0 +1,31 @@
1
+ export default [
2
+ {
3
+ files: ['**/*.js'],
4
+ languageOptions: {
5
+ ecmaVersion: 'latest',
6
+ sourceType: 'module',
7
+ globals: {
8
+ process: 'readonly',
9
+ Buffer: 'readonly',
10
+ __dirname: 'readonly',
11
+ __filename: 'readonly',
12
+ console: 'readonly',
13
+ global: 'readonly',
14
+ }
15
+ },
16
+ rules: {
17
+ 'no-unused-vars': ['warn', {
18
+ argsIgnorePattern: '^_',
19
+ varsIgnorePattern: '^_'
20
+ }],
21
+ 'no-console': 'off',
22
+ 'prefer-const': 'error',
23
+ 'no-var': 'error',
24
+ 'no-undef': 'error',
25
+ 'semi': ['error', 'always'],
26
+ 'quotes': ['error', 'single'],
27
+ 'indent': ['error', 2],
28
+ 'comma-dangle': ['error', 'always-multiline'],
29
+ }
30
+ }
31
+ ];
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-arktos",
3
- "version": "1.2.0",
3
+ "version": "1.4.0",
4
4
  "description": "๐Ÿš€ A modern Node.js backend boilerplate with TypeScript, Express, JWT authentication, Prisma ORM, PostgreSQL, and Resend email service. Includes complete authentication flow, security middleware, and database management.",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -9,12 +9,15 @@
9
9
  "files": [
10
10
  "bin/",
11
11
  "src/",
12
+ "src-js/",
12
13
  "prisma/",
13
14
  "template.package.json",
15
+ "template.js.package.json",
14
16
  ".env.example",
15
17
  "vercel.json",
16
18
  "tsconfig.json",
17
19
  "eslint.config.js",
20
+ "eslint.config.js.template",
18
21
  "README.md",
19
22
  "LICENSE"
20
23
  ],
package/src-js/app.js ADDED
@@ -0,0 +1,97 @@
1
+ import express from 'express';
2
+ import dotenv from 'dotenv';
3
+ import routes from './routes/index.js';
4
+ import { errorHandling, securityMiddleware, rateLimiter } from './middleware/index.js';
5
+ import DatabaseService from './services/database.service.js';
6
+ import logger from './config/logger.js';
7
+
8
+ // Load environment variables
9
+ dotenv.config();
10
+
11
+ const app = express();
12
+ const PORT = process.env.PORT || 3001;
13
+
14
+ // Security middleware stack
15
+ app.use(securityMiddleware);
16
+
17
+ // Rate limiting
18
+ app.use('/api', rateLimiter.general);
19
+
20
+ // Body parsing middleware
21
+ app.use(express.json({ limit: '10mb' }));
22
+ app.use(express.urlencoded({ extended: true, limit: '10mb' }));
23
+
24
+ // Trust proxy (for accurate IP addresses)
25
+ app.set('trust proxy', 1);
26
+
27
+ // API routes
28
+ app.use('/api', routes);
29
+
30
+ // Health check route (outside of rate limiting)
31
+ app.get('/health', async (req, res) => {
32
+ try {
33
+ const dbService = DatabaseService.getInstance();
34
+ const dbHealth = await dbService.healthCheck();
35
+
36
+ res.json({
37
+ status: dbHealth.connected ? 'ok' : 'error',
38
+ timestamp: new Date().toISOString(),
39
+ uptime: process.uptime(),
40
+ database: dbHealth,
41
+ });
42
+ } catch (error) {
43
+ res.status(503).json({
44
+ status: 'error',
45
+ message: 'Health check failed',
46
+ });
47
+ }
48
+ });
49
+
50
+ // 404 handler
51
+ app.use('*', (req, res) => {
52
+ res.status(404).json({
53
+ success: false,
54
+ message: 'Route not found',
55
+ error: `Cannot ${req.method} ${req.originalUrl}`,
56
+ });
57
+ });
58
+
59
+ // Global error handler
60
+ app.use(errorHandling);
61
+
62
+ // Graceful shutdown
63
+ const gracefulShutdown = async (signal) => {
64
+ logger.info(`Received ${signal}, shutting down gracefully...`);
65
+
66
+ try {
67
+ const dbService = DatabaseService.getInstance();
68
+ await dbService.disconnect();
69
+ logger.info('Database connection closed');
70
+ } catch (error) {
71
+ logger.error('Error during graceful shutdown:', error);
72
+ }
73
+
74
+ process.exit(0);
75
+ };
76
+
77
+ process.on('SIGTERM', () => gracefulShutdown('SIGTERM'));
78
+ process.on('SIGINT', () => gracefulShutdown('SIGINT'));
79
+
80
+ const startServer = async () => {
81
+ try {
82
+ // Initialize database connection
83
+ const dbService = DatabaseService.getInstance();
84
+ await dbService.connect();
85
+
86
+ app.listen(PORT, () => {
87
+ logger.info(`Server running on port ${PORT}`);
88
+ logger.info(`Health check: http://localhost:${PORT}/health`);
89
+ logger.info(`API base URL: http://localhost:${PORT}/api`);
90
+ });
91
+ } catch (error) {
92
+ logger.error('Failed to start server:', error);
93
+ process.exit(1);
94
+ }
95
+ };
96
+
97
+ startServer();
@@ -0,0 +1,15 @@
1
+ import logger from './logger.js';
2
+ import { envSchema } from '../schemas/index.js';
3
+
4
+ export function validateEnv() {
5
+ try {
6
+ const env = envSchema.parse(process.env);
7
+ logger.info('Environment validation successful');
8
+ return env;
9
+ } catch (error) {
10
+ logger.error('Environment validation failed:', error);
11
+ process.exit(1);
12
+ }
13
+ }
14
+
15
+ export const config = validateEnv();
@@ -0,0 +1,76 @@
1
+ import winston from 'winston';
2
+ import path from 'path';
3
+ import fs from 'fs';
4
+
5
+ const logLevel = process.env.LOG_LEVEL || 'info';
6
+ const logFile = process.env.LOG_FILE || 'logs/app.log';
7
+
8
+ // Ensure logs directory exists
9
+ const logDir = path.dirname(logFile);
10
+ if (!fs.existsSync(logDir)) {
11
+ fs.mkdirSync(logDir, { recursive: true });
12
+ }
13
+
14
+ const logFormat = winston.format.combine(
15
+ winston.format.timestamp({
16
+ format: 'YYYY-MM-DD HH:mm:ss',
17
+ }),
18
+ winston.format.errors({ stack: true }),
19
+ winston.format.json(),
20
+ winston.format.prettyPrint()
21
+ );
22
+
23
+ const consoleFormat = winston.format.combine(
24
+ winston.format.colorize(),
25
+ winston.format.timestamp({
26
+ format: 'HH:mm:ss',
27
+ }),
28
+ winston.format.printf(({ timestamp, level, message, ...meta }) => {
29
+ return `${timestamp} [${level}]: ${message} ${Object.keys(meta).length ? JSON.stringify(meta, null, 2) : ''}`;
30
+ })
31
+ );
32
+
33
+ const logger = winston.createLogger({
34
+ level: logLevel,
35
+ format: logFormat,
36
+ defaultMeta: { service: 'arktos-backend' },
37
+ transports: [
38
+ // File transport
39
+ new winston.transports.File({
40
+ filename: logFile,
41
+ handleExceptions: true,
42
+ maxsize: 5242880, // 5MB
43
+ maxFiles: 5,
44
+ }),
45
+ // Error file transport
46
+ new winston.transports.File({
47
+ filename: path.join(logDir, 'error.log'),
48
+ level: 'error',
49
+ handleExceptions: true,
50
+ maxsize: 5242880, // 5MB
51
+ maxFiles: 5,
52
+ }),
53
+ ],
54
+ });
55
+
56
+ // Console transport for non-production environments
57
+ if (process.env.NODE_ENV !== 'production') {
58
+ logger.add(
59
+ new winston.transports.Console({
60
+ format: consoleFormat,
61
+ handleExceptions: true,
62
+ })
63
+ );
64
+ }
65
+
66
+ // Handle uncaught exceptions and unhandled rejections
67
+ process.on('uncaughtException', (error) => {
68
+ logger.error('Uncaught Exception:', error);
69
+ process.exit(1);
70
+ });
71
+
72
+ process.on('unhandledRejection', (reason, promise) => {
73
+ logger.error('Unhandled Rejection at:', promise, 'reason:', reason);
74
+ });
75
+
76
+ export default logger;
@@ -0,0 +1,57 @@
1
+ export const ERROR_CODES = {
2
+ // Authentication errors
3
+ AUTH_INVALID_CREDENTIALS: 'AUTH_001',
4
+ AUTH_TOKEN_EXPIRED: 'AUTH_002',
5
+ AUTH_TOKEN_INVALID: 'AUTH_003',
6
+ AUTH_USER_NOT_FOUND: 'AUTH_004',
7
+ AUTH_EMAIL_NOT_VERIFIED: 'AUTH_005',
8
+ AUTH_ACCOUNT_DISABLED: 'AUTH_006',
9
+ AUTH_INSUFFICIENT_PERMISSIONS: 'AUTH_007',
10
+ AUTH_USER_EXISTS: 'AUTH_008',
11
+ AUTH_USERNAME_TAKEN: 'AUTH_009',
12
+ AUTH_INVALID_PASSWORD: 'AUTH_010',
13
+ AUTH_TOKEN_REQUIRED: 'AUTH_011',
14
+ AUTH_INVALID_TOKEN: 'AUTH_003', // Alias for AUTH_TOKEN_INVALID
15
+ AUTH_ACCOUNT_DEACTIVATED: 'AUTH_006', // Alias for AUTH_ACCOUNT_DISABLED
16
+
17
+ // Validation errors
18
+ VALIDATION_REQUIRED_FIELD: 'VAL_001',
19
+ VALIDATION_INVALID_FORMAT: 'VAL_002',
20
+ VALIDATION_INVALID_LENGTH: 'VAL_003',
21
+ VALIDATION_INVALID_TYPE: 'VAL_004',
22
+ VALIDATION_ERROR: 'VAL_005',
23
+
24
+ // Database errors
25
+ DB_CONNECTION_ERROR: 'DB_001',
26
+ DB_QUERY_ERROR: 'DB_002',
27
+ DB_CONSTRAINT_VIOLATION: 'DB_003',
28
+ DB_RECORD_NOT_FOUND: 'DB_004',
29
+ DB_DUPLICATE_ENTRY: 'DB_005',
30
+ DATABASE_CONNECTION: 'DB_001', // Alias
31
+ DATABASE_ERROR: 'DB_002', // Alias
32
+ DATABASE_CONSTRAINT: 'DB_003', // Alias
33
+ DATABASE_NOT_FOUND: 'DB_004', // Alias
34
+ DATABASE_CONFLICT: 'DB_005', // Alias
35
+
36
+ // File/Upload errors
37
+ FILE_TOO_LARGE: 'FILE_001',
38
+ FILE_INVALID_TYPE: 'FILE_002',
39
+ FILE_UPLOAD_FAILED: 'FILE_003',
40
+ FILE_NOT_FOUND: 'FILE_004',
41
+
42
+ // Rate limiting errors
43
+ RATE_LIMIT_EXCEEDED: 'RATE_001',
44
+
45
+ // Server errors
46
+ INTERNAL_SERVER_ERROR: 'SRV_001',
47
+ SERVICE_UNAVAILABLE: 'SRV_002',
48
+ EXTERNAL_SERVICE_ERROR: 'SRV_003',
49
+ INTERNAL_ERROR: 'SRV_001', // Alias
50
+ GENERIC_ERROR: 'SRV_004',
51
+ ROUTE_NOT_FOUND: 'SRV_005',
52
+
53
+ // Email service errors
54
+ EMAIL_SEND_FAILED: 'EMAIL_001',
55
+ EMAIL_TEMPLATE_NOT_FOUND: 'EMAIL_002',
56
+ EMAIL_INVALID_RECIPIENT: 'EMAIL_003',
57
+ };
@@ -0,0 +1,240 @@
1
+ import { ERROR_CODES } from './errorCodes.js';
2
+
3
+ export const SERVICE_MESSAGES = {
4
+ // Application messages
5
+ APP: {
6
+ API_RUNNING: {
7
+ code: 'APP_001',
8
+ message: 'API is running successfully',
9
+ statusCode: 200,
10
+ },
11
+ HEALTH_CHECK_FAILED: {
12
+ code: 'APP_002',
13
+ message: 'Health check failed',
14
+ statusCode: 503,
15
+ },
16
+ ENDPOINT_NOT_FOUND: {
17
+ code: 'APP_003',
18
+ message: 'Endpoint not found',
19
+ statusCode: 404,
20
+ },
21
+ },
22
+
23
+ // Authentication messages
24
+ AUTH: {
25
+ LOGIN_SUCCESS: {
26
+ code: 'AUTH_S001',
27
+ message: 'Login successful',
28
+ statusCode: 200,
29
+ },
30
+ REGISTER_SUCCESS: {
31
+ code: 'AUTH_S002',
32
+ message: 'Registration successful',
33
+ statusCode: 201,
34
+ },
35
+ LOGOUT_SUCCESS: {
36
+ code: 'AUTH_S003',
37
+ message: 'Logout successful',
38
+ statusCode: 200,
39
+ },
40
+ EMAIL_VERIFICATION_SUCCESS: {
41
+ code: 'AUTH_S004',
42
+ message: 'Email verified successfully',
43
+ statusCode: 200,
44
+ },
45
+ PASSWORD_RESET_SUCCESS: {
46
+ code: 'AUTH_S005',
47
+ message: 'Password reset successful',
48
+ statusCode: 200,
49
+ },
50
+ INVALID_CREDENTIALS: {
51
+ code: ERROR_CODES.AUTH_INVALID_CREDENTIALS,
52
+ message: 'Invalid email or password',
53
+ statusCode: 401,
54
+ },
55
+ TOKEN_EXPIRED: {
56
+ code: ERROR_CODES.AUTH_TOKEN_EXPIRED,
57
+ message: 'Token has expired',
58
+ statusCode: 401,
59
+ },
60
+ TOKEN_INVALID: {
61
+ code: ERROR_CODES.AUTH_TOKEN_INVALID,
62
+ message: 'Invalid token provided',
63
+ statusCode: 401,
64
+ },
65
+ USER_NOT_FOUND: {
66
+ code: ERROR_CODES.AUTH_USER_NOT_FOUND,
67
+ message: 'User not found',
68
+ statusCode: 404,
69
+ },
70
+ EMAIL_NOT_VERIFIED: {
71
+ code: ERROR_CODES.AUTH_EMAIL_NOT_VERIFIED,
72
+ message: 'Email address is not verified',
73
+ statusCode: 403,
74
+ },
75
+ ACCOUNT_DISABLED: {
76
+ code: ERROR_CODES.AUTH_ACCOUNT_DISABLED,
77
+ message: 'Account has been disabled',
78
+ statusCode: 403,
79
+ },
80
+ INSUFFICIENT_PERMISSIONS: {
81
+ code: ERROR_CODES.AUTH_INSUFFICIENT_PERMISSIONS,
82
+ message: 'Insufficient permissions to access this resource',
83
+ statusCode: 403,
84
+ },
85
+ USER_EXISTS: {
86
+ code: ERROR_CODES.AUTH_USER_EXISTS,
87
+ message: 'User already exists with this email',
88
+ statusCode: 409,
89
+ },
90
+ USERNAME_TAKEN: {
91
+ code: ERROR_CODES.AUTH_USERNAME_TAKEN,
92
+ message: 'Username is already taken',
93
+ statusCode: 409,
94
+ },
95
+ TOKEN_REQUIRED: {
96
+ code: ERROR_CODES.AUTH_TOKEN_REQUIRED,
97
+ message: 'Authentication token is required',
98
+ statusCode: 401,
99
+ },
100
+ },
101
+
102
+ // Validation messages
103
+ VALIDATION: {
104
+ REQUIRED_FIELD: {
105
+ code: ERROR_CODES.VALIDATION_REQUIRED_FIELD,
106
+ message: 'Required field is missing',
107
+ statusCode: 400,
108
+ },
109
+ INVALID_FORMAT: {
110
+ code: ERROR_CODES.VALIDATION_INVALID_FORMAT,
111
+ message: 'Invalid format provided',
112
+ statusCode: 400,
113
+ },
114
+ INVALID_LENGTH: {
115
+ code: ERROR_CODES.VALIDATION_INVALID_LENGTH,
116
+ message: 'Invalid length for field',
117
+ statusCode: 400,
118
+ },
119
+ INVALID_TYPE: {
120
+ code: ERROR_CODES.VALIDATION_INVALID_TYPE,
121
+ message: 'Invalid type provided',
122
+ statusCode: 400,
123
+ },
124
+ VALIDATION_ERROR: {
125
+ code: ERROR_CODES.VALIDATION_ERROR,
126
+ message: 'Validation failed',
127
+ statusCode: 400,
128
+ },
129
+ },
130
+
131
+ // Database messages
132
+ DATABASE: {
133
+ CONNECTION_ERROR: {
134
+ code: ERROR_CODES.DB_CONNECTION_ERROR,
135
+ message: 'Database connection failed',
136
+ statusCode: 503,
137
+ },
138
+ QUERY_ERROR: {
139
+ code: ERROR_CODES.DB_QUERY_ERROR,
140
+ message: 'Database query failed',
141
+ statusCode: 500,
142
+ },
143
+ CONSTRAINT_VIOLATION: {
144
+ code: ERROR_CODES.DB_CONSTRAINT_VIOLATION,
145
+ message: 'Database constraint violation',
146
+ statusCode: 409,
147
+ },
148
+ RECORD_NOT_FOUND: {
149
+ code: ERROR_CODES.DB_RECORD_NOT_FOUND,
150
+ message: 'Record not found',
151
+ statusCode: 404,
152
+ },
153
+ DUPLICATE_ENTRY: {
154
+ code: ERROR_CODES.DB_DUPLICATE_ENTRY,
155
+ message: 'Duplicate entry detected',
156
+ statusCode: 409,
157
+ },
158
+ },
159
+
160
+ // File upload messages
161
+ FILE: {
162
+ TOO_LARGE: {
163
+ code: ERROR_CODES.FILE_TOO_LARGE,
164
+ message: 'File size exceeds maximum limit',
165
+ statusCode: 413,
166
+ },
167
+ INVALID_TYPE: {
168
+ code: ERROR_CODES.FILE_INVALID_TYPE,
169
+ message: 'Invalid file type',
170
+ statusCode: 400,
171
+ },
172
+ UPLOAD_FAILED: {
173
+ code: ERROR_CODES.FILE_UPLOAD_FAILED,
174
+ message: 'File upload failed',
175
+ statusCode: 500,
176
+ },
177
+ NOT_FOUND: {
178
+ code: ERROR_CODES.FILE_NOT_FOUND,
179
+ message: 'File not found',
180
+ statusCode: 404,
181
+ },
182
+ },
183
+
184
+ // Rate limiting messages
185
+ RATE_LIMIT: {
186
+ EXCEEDED: {
187
+ code: ERROR_CODES.RATE_LIMIT_EXCEEDED,
188
+ message: 'Rate limit exceeded. Please try again later',
189
+ statusCode: 429,
190
+ },
191
+ },
192
+
193
+ // Server messages
194
+ SERVER: {
195
+ INTERNAL_ERROR: {
196
+ code: ERROR_CODES.INTERNAL_SERVER_ERROR,
197
+ message: 'Internal server error occurred',
198
+ statusCode: 500,
199
+ },
200
+ SERVICE_UNAVAILABLE: {
201
+ code: ERROR_CODES.SERVICE_UNAVAILABLE,
202
+ message: 'Service temporarily unavailable',
203
+ statusCode: 503,
204
+ },
205
+ EXTERNAL_SERVICE_ERROR: {
206
+ code: ERROR_CODES.EXTERNAL_SERVICE_ERROR,
207
+ message: 'External service error',
208
+ statusCode: 502,
209
+ },
210
+ GENERIC_ERROR: {
211
+ code: ERROR_CODES.GENERIC_ERROR,
212
+ message: 'An unexpected error occurred',
213
+ statusCode: 500,
214
+ },
215
+ ROUTE_NOT_FOUND: {
216
+ code: ERROR_CODES.ROUTE_NOT_FOUND,
217
+ message: 'Route not found',
218
+ statusCode: 404,
219
+ },
220
+ },
221
+
222
+ // Email service messages
223
+ EMAIL: {
224
+ SEND_FAILED: {
225
+ code: ERROR_CODES.EMAIL_SEND_FAILED,
226
+ message: 'Failed to send email',
227
+ statusCode: 500,
228
+ },
229
+ TEMPLATE_NOT_FOUND: {
230
+ code: ERROR_CODES.EMAIL_TEMPLATE_NOT_FOUND,
231
+ message: 'Email template not found',
232
+ statusCode: 404,
233
+ },
234
+ INVALID_RECIPIENT: {
235
+ code: ERROR_CODES.EMAIL_INVALID_RECIPIENT,
236
+ message: 'Invalid email recipient',
237
+ statusCode: 400,
238
+ },
239
+ },
240
+ };