create-nara 1.0.23 → 1.0.25
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.
Potentially problematic release.
This version of create-nara might be problematic. Click here for more details.
- package/package.json +1 -1
- package/templates/base/app/config/constants.ts +251 -0
- package/templates/base/app/config/env.ts +110 -0
- package/templates/base/app/config/index.ts +8 -0
- package/templates/base/app/services/Logger.ts +161 -0
- package/templates/features/auth/app/middlewares/csrf.ts +197 -0
- package/templates/features/auth/app/middlewares/rateLimit.ts +220 -0
- package/templates/features/auth/app/middlewares/requestLogger.ts +200 -0
- package/templates/features/auth/app/middlewares/securityHeaders.ts +291 -0
- package/templates/features/auth/app/validators/index.ts +49 -0
- package/templates/features/auth/app/validators/schemas.ts +455 -0
- package/templates/features/auth/app/validators/validate.ts +103 -0
- package/templates/features/auth/routes/auth.ts +27 -26
- package/templates/features/db/app/models/EmailVerificationToken.ts +77 -0
- package/templates/features/db/app/models/PasswordResetToken.ts +83 -0
- package/templates/features/db/migrations/20240101000002_create_password_reset_tokens.ts +15 -0
- package/templates/features/db/migrations/20240101000003_create_email_verification_tokens.ts +18 -0
- package/templates/features/db/seeds/users.ts +36 -0
- package/templates/features/uploads/app/services/Storage.ts +266 -0
- package/templates/features/uploads/routes/uploads.ts +5 -4
- package/templates/minimal/app/utils/route-helper.ts +26 -0
- package/templates/minimal/server.ts +7 -1
- package/templates/svelte/app/utils/route-helper.ts +26 -0
- package/templates/svelte/server.ts +7 -1
- package/templates/vue/app/utils/route-helper.ts +26 -0
- package/templates/vue/server.ts +7 -1
package/package.json
CHANGED
|
@@ -0,0 +1,251 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Application Constants
|
|
3
|
+
*
|
|
4
|
+
* Centralized configuration values to avoid magic numbers/strings
|
|
5
|
+
* scattered throughout the codebase.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// ============================================
|
|
9
|
+
// Server Configuration
|
|
10
|
+
// ============================================
|
|
11
|
+
|
|
12
|
+
export const SERVER = {
|
|
13
|
+
/** Maximum request body size in bytes (10MB) */
|
|
14
|
+
MAX_BODY_SIZE: 10 * 1024 * 1024,
|
|
15
|
+
|
|
16
|
+
/** Default server port */
|
|
17
|
+
DEFAULT_PORT: 3000,
|
|
18
|
+
|
|
19
|
+
/** Default Vite dev server port */
|
|
20
|
+
DEFAULT_VITE_PORT: 5173,
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
// ============================================
|
|
24
|
+
// Authentication & Security
|
|
25
|
+
// ============================================
|
|
26
|
+
|
|
27
|
+
export const AUTH = {
|
|
28
|
+
/** JWT expiry in seconds (7 days) */
|
|
29
|
+
JWT_EXPIRY_SECONDS: 7 * 24 * 60 * 60,
|
|
30
|
+
|
|
31
|
+
/** Session cookie expiry in milliseconds (30 days) */
|
|
32
|
+
SESSION_EXPIRY_MS: 30 * 24 * 60 * 60 * 1000,
|
|
33
|
+
|
|
34
|
+
/** Error cookie expiry in milliseconds (5 minutes) */
|
|
35
|
+
ERROR_COOKIE_EXPIRY_MS: 5 * 60 * 1000,
|
|
36
|
+
|
|
37
|
+
/** Minimum password length */
|
|
38
|
+
MIN_PASSWORD_LENGTH: 8,
|
|
39
|
+
|
|
40
|
+
/** Maximum password length */
|
|
41
|
+
MAX_PASSWORD_LENGTH: 100,
|
|
42
|
+
|
|
43
|
+
/** Bcrypt salt rounds */
|
|
44
|
+
BCRYPT_SALT_ROUNDS: 10,
|
|
45
|
+
} as const;
|
|
46
|
+
|
|
47
|
+
// ============================================
|
|
48
|
+
// Pagination
|
|
49
|
+
// ============================================
|
|
50
|
+
|
|
51
|
+
export const PAGINATION = {
|
|
52
|
+
/** Default page size */
|
|
53
|
+
DEFAULT_PAGE_SIZE: 10,
|
|
54
|
+
|
|
55
|
+
/** Maximum page size allowed */
|
|
56
|
+
MAX_PAGE_SIZE: 100,
|
|
57
|
+
|
|
58
|
+
/** Default page number */
|
|
59
|
+
DEFAULT_PAGE: 1,
|
|
60
|
+
} as const;
|
|
61
|
+
|
|
62
|
+
// ============================================
|
|
63
|
+
// User & Profile
|
|
64
|
+
// ============================================
|
|
65
|
+
|
|
66
|
+
export const USER = {
|
|
67
|
+
/** Minimum name length */
|
|
68
|
+
MIN_NAME_LENGTH: 2,
|
|
69
|
+
|
|
70
|
+
/** Maximum name length */
|
|
71
|
+
MAX_NAME_LENGTH: 100,
|
|
72
|
+
|
|
73
|
+
/** Minimum phone length */
|
|
74
|
+
MIN_PHONE_LENGTH: 10,
|
|
75
|
+
|
|
76
|
+
/** Maximum phone length */
|
|
77
|
+
MAX_PHONE_LENGTH: 20,
|
|
78
|
+
} as const;
|
|
79
|
+
|
|
80
|
+
// ============================================
|
|
81
|
+
// File Upload
|
|
82
|
+
// ============================================
|
|
83
|
+
|
|
84
|
+
export const UPLOAD = {
|
|
85
|
+
/** Maximum file size in bytes (5MB) */
|
|
86
|
+
MAX_FILE_SIZE: 5 * 1024 * 1024,
|
|
87
|
+
|
|
88
|
+
/** Allowed image extensions */
|
|
89
|
+
ALLOWED_IMAGE_EXTENSIONS: ['jpg', 'jpeg', 'png', 'gif', 'webp', 'svg'],
|
|
90
|
+
|
|
91
|
+
/** Allowed document extensions */
|
|
92
|
+
ALLOWED_DOC_EXTENSIONS: ['pdf', 'doc', 'docx', 'xls', 'xlsx'],
|
|
93
|
+
|
|
94
|
+
/** Avatar upload directory */
|
|
95
|
+
AVATAR_DIR: 'avatars',
|
|
96
|
+
|
|
97
|
+
/** General assets directory */
|
|
98
|
+
ASSETS_DIR: 'assets',
|
|
99
|
+
} as const;
|
|
100
|
+
|
|
101
|
+
// ============================================
|
|
102
|
+
// Cache & Performance
|
|
103
|
+
// ============================================
|
|
104
|
+
|
|
105
|
+
export const CACHE = {
|
|
106
|
+
/** Static asset cache duration in seconds (1 year) */
|
|
107
|
+
STATIC_ASSET_MAX_AGE: 365 * 24 * 60 * 60,
|
|
108
|
+
|
|
109
|
+
/** API response cache duration in seconds (5 minutes) */
|
|
110
|
+
API_CACHE_DURATION: 5 * 60,
|
|
111
|
+
|
|
112
|
+
/** Session cache duration in seconds (1 hour) */
|
|
113
|
+
SESSION_CACHE_DURATION: 60 * 60,
|
|
114
|
+
} as const;
|
|
115
|
+
|
|
116
|
+
// ============================================
|
|
117
|
+
// Rate Limiting
|
|
118
|
+
// ============================================
|
|
119
|
+
|
|
120
|
+
export const RATE_LIMIT = {
|
|
121
|
+
/** Maximum requests per window (general) */
|
|
122
|
+
MAX_REQUESTS: 100,
|
|
123
|
+
|
|
124
|
+
/** Window duration in milliseconds (15 minutes) */
|
|
125
|
+
WINDOW_MS: 15 * 60 * 1000,
|
|
126
|
+
|
|
127
|
+
/** Login attempts before lockout */
|
|
128
|
+
MAX_LOGIN_ATTEMPTS: 5,
|
|
129
|
+
|
|
130
|
+
/** Login lockout duration in milliseconds (15 minutes) */
|
|
131
|
+
LOGIN_LOCKOUT_MS: 15 * 60 * 1000,
|
|
132
|
+
|
|
133
|
+
/** API rate limit (requests per minute) */
|
|
134
|
+
API_MAX_REQUESTS: 60,
|
|
135
|
+
|
|
136
|
+
/** API rate limit window (1 minute) */
|
|
137
|
+
API_WINDOW_MS: 60 * 1000,
|
|
138
|
+
|
|
139
|
+
/** Strict rate limit for sensitive endpoints */
|
|
140
|
+
STRICT_MAX_REQUESTS: 10,
|
|
141
|
+
|
|
142
|
+
/** Strict rate limit window (1 minute) */
|
|
143
|
+
STRICT_WINDOW_MS: 60 * 1000,
|
|
144
|
+
} as const;
|
|
145
|
+
|
|
146
|
+
// ============================================
|
|
147
|
+
// Security Headers
|
|
148
|
+
// ============================================
|
|
149
|
+
|
|
150
|
+
export const SECURITY = {
|
|
151
|
+
/** HSTS max-age in seconds (1 year) */
|
|
152
|
+
HSTS_MAX_AGE: 365 * 24 * 60 * 60,
|
|
153
|
+
|
|
154
|
+
/** CSRF token length in bytes */
|
|
155
|
+
CSRF_TOKEN_LENGTH: 32,
|
|
156
|
+
|
|
157
|
+
/** CSRF cookie max age in seconds (24 hours) */
|
|
158
|
+
CSRF_COOKIE_MAX_AGE: 24 * 60 * 60,
|
|
159
|
+
|
|
160
|
+
/** CSRF cookie name */
|
|
161
|
+
CSRF_COOKIE_NAME: 'csrf_token',
|
|
162
|
+
|
|
163
|
+
/** CSRF header name */
|
|
164
|
+
CSRF_HEADER_NAME: 'X-CSRF-Token',
|
|
165
|
+
} as const;
|
|
166
|
+
|
|
167
|
+
// ============================================
|
|
168
|
+
// Database
|
|
169
|
+
// ============================================
|
|
170
|
+
|
|
171
|
+
export const DATABASE = {
|
|
172
|
+
/** Default connection type */
|
|
173
|
+
DEFAULT_CONNECTION: 'development',
|
|
174
|
+
|
|
175
|
+
/** Connection pool min */
|
|
176
|
+
POOL_MIN: 2,
|
|
177
|
+
|
|
178
|
+
/** Connection pool max */
|
|
179
|
+
POOL_MAX: 10,
|
|
180
|
+
} as const;
|
|
181
|
+
|
|
182
|
+
// ============================================
|
|
183
|
+
// Logging
|
|
184
|
+
// ============================================
|
|
185
|
+
|
|
186
|
+
export const LOGGING = {
|
|
187
|
+
/** Available log levels */
|
|
188
|
+
LEVELS: ['trace', 'debug', 'info', 'warn', 'error', 'fatal'] as const,
|
|
189
|
+
|
|
190
|
+
/** Default log level */
|
|
191
|
+
DEFAULT_LEVEL: 'info',
|
|
192
|
+
|
|
193
|
+
/** Log file directory */
|
|
194
|
+
LOG_DIR: 'logs',
|
|
195
|
+
|
|
196
|
+
/** Max log file size in bytes (10MB) */
|
|
197
|
+
MAX_LOG_SIZE: 10 * 1024 * 1024,
|
|
198
|
+
|
|
199
|
+
/** Number of log files to keep */
|
|
200
|
+
MAX_LOG_FILES: 10,
|
|
201
|
+
} as const;
|
|
202
|
+
|
|
203
|
+
// ============================================
|
|
204
|
+
// HTTP Status Codes (commonly used)
|
|
205
|
+
// ============================================
|
|
206
|
+
|
|
207
|
+
export const HTTP_STATUS = {
|
|
208
|
+
OK: 200,
|
|
209
|
+
CREATED: 201,
|
|
210
|
+
NO_CONTENT: 204,
|
|
211
|
+
BAD_REQUEST: 400,
|
|
212
|
+
UNAUTHORIZED: 401,
|
|
213
|
+
FORBIDDEN: 403,
|
|
214
|
+
NOT_FOUND: 404,
|
|
215
|
+
CONFLICT: 409,
|
|
216
|
+
UNPROCESSABLE_ENTITY: 422,
|
|
217
|
+
TOO_MANY_REQUESTS: 429,
|
|
218
|
+
INTERNAL_SERVER_ERROR: 500,
|
|
219
|
+
SERVICE_UNAVAILABLE: 503,
|
|
220
|
+
} as const;
|
|
221
|
+
|
|
222
|
+
// ============================================
|
|
223
|
+
// Error Messages
|
|
224
|
+
// ============================================
|
|
225
|
+
|
|
226
|
+
export const ERROR_MESSAGES = {
|
|
227
|
+
UNAUTHORIZED: 'Unauthorized',
|
|
228
|
+
FORBIDDEN: 'Access denied',
|
|
229
|
+
NOT_FOUND: 'Not found',
|
|
230
|
+
VALIDATION_FAILED: 'Validation failed',
|
|
231
|
+
INTERNAL_ERROR: 'Internal server error',
|
|
232
|
+
INVALID_CREDENTIALS: 'Invalid email or password',
|
|
233
|
+
EMAIL_EXISTS: 'Email already in use',
|
|
234
|
+
TOKEN_EXPIRED: 'Invalid or expired token',
|
|
235
|
+
SESSION_EXPIRED: 'Session expired, please login again',
|
|
236
|
+
} as const;
|
|
237
|
+
|
|
238
|
+
// ============================================
|
|
239
|
+
// Success Messages
|
|
240
|
+
// ============================================
|
|
241
|
+
|
|
242
|
+
export const SUCCESS_MESSAGES = {
|
|
243
|
+
USER_CREATED: 'User created successfully',
|
|
244
|
+
USER_UPDATED: 'User updated successfully',
|
|
245
|
+
USER_DELETED: 'User deleted successfully',
|
|
246
|
+
PROFILE_UPDATED: 'Profile updated successfully',
|
|
247
|
+
PASSWORD_CHANGED: 'Password changed successfully',
|
|
248
|
+
PASSWORD_RESET_SENT: 'Password reset link sent',
|
|
249
|
+
LOGIN_SUCCESS: 'Login successful',
|
|
250
|
+
LOGOUT_SUCCESS: 'Logout successful',
|
|
251
|
+
} as const;
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Environment Configuration & Validation
|
|
3
|
+
*
|
|
4
|
+
* Validates required environment variables on application startup.
|
|
5
|
+
* Provides typed access to environment configuration.
|
|
6
|
+
*/
|
|
7
|
+
import { SERVER, LOGGING } from './constants.js';
|
|
8
|
+
|
|
9
|
+
// ============================================
|
|
10
|
+
// Type Definitions
|
|
11
|
+
// ============================================
|
|
12
|
+
|
|
13
|
+
export interface Env {
|
|
14
|
+
// Server
|
|
15
|
+
NODE_ENV: 'development' | 'production' | 'test';
|
|
16
|
+
PORT: number;
|
|
17
|
+
APP_URL: string;
|
|
18
|
+
|
|
19
|
+
// Database
|
|
20
|
+
DB_CONNECTION: string;
|
|
21
|
+
|
|
22
|
+
// Auth
|
|
23
|
+
JWT_SECRET: string;
|
|
24
|
+
|
|
25
|
+
// Logging
|
|
26
|
+
LOG_LEVEL: typeof LOGGING.LEVELS[number];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// ============================================
|
|
30
|
+
// Validation Function
|
|
31
|
+
// ============================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Validate environment variables
|
|
35
|
+
* @returns Validated and typed environment object
|
|
36
|
+
* @throws Error if validation fails
|
|
37
|
+
*/
|
|
38
|
+
export function validateEnv(): Env {
|
|
39
|
+
const errors: string[] = [];
|
|
40
|
+
const env = process.env;
|
|
41
|
+
|
|
42
|
+
// Validate NODE_ENV
|
|
43
|
+
const validNodeEnvs = ['development', 'production', 'test'] as const;
|
|
44
|
+
const nodeEnv = env.NODE_ENV || 'development';
|
|
45
|
+
if (!validNodeEnvs.includes(nodeEnv as any)) {
|
|
46
|
+
errors.push(` - NODE_ENV: Must be one of ${validNodeEnvs.join(', ')}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Validate PORT
|
|
50
|
+
const port = parseInt(env.PORT || String(SERVER.DEFAULT_PORT), 10);
|
|
51
|
+
if (isNaN(port) || port <= 0) {
|
|
52
|
+
errors.push(' - PORT: Must be a positive number');
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Validate LOG_LEVEL
|
|
56
|
+
const logLevel = env.LOG_LEVEL || 'info';
|
|
57
|
+
if (!LOGGING.LEVELS.includes(logLevel as any)) {
|
|
58
|
+
errors.push(` - LOG_LEVEL: Must be one of ${LOGGING.LEVELS.join(', ')}`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Warning for missing JWT_SECRET in production
|
|
62
|
+
if (nodeEnv === 'production' && !env.JWT_SECRET) {
|
|
63
|
+
errors.push(' - JWT_SECRET: Required in production');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (errors.length > 0) {
|
|
67
|
+
console.error('\n❌ Environment validation failed:\n');
|
|
68
|
+
console.error(errors.join('\n'));
|
|
69
|
+
console.error('\nPlease check your .env file and ensure all required variables are set.\n');
|
|
70
|
+
process.exit(1);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return {
|
|
74
|
+
NODE_ENV: nodeEnv as 'development' | 'production' | 'test',
|
|
75
|
+
PORT: port,
|
|
76
|
+
APP_URL: env.APP_URL || `http://localhost:${port}`,
|
|
77
|
+
DB_CONNECTION: env.DB_CONNECTION || 'development',
|
|
78
|
+
JWT_SECRET: env.JWT_SECRET || 'your-secret-key',
|
|
79
|
+
LOG_LEVEL: logLevel as typeof LOGGING.LEVELS[number],
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Get environment summary for logging
|
|
85
|
+
*/
|
|
86
|
+
export function getEnvSummary(env: Env) {
|
|
87
|
+
return {
|
|
88
|
+
nodeEnv: env.NODE_ENV,
|
|
89
|
+
port: env.PORT,
|
|
90
|
+
appUrl: env.APP_URL,
|
|
91
|
+
dbConnection: env.DB_CONNECTION,
|
|
92
|
+
logLevel: env.LOG_LEVEL,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// ============================================
|
|
97
|
+
// Singleton Instance
|
|
98
|
+
// ============================================
|
|
99
|
+
|
|
100
|
+
let _env: Env | null = null;
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* Get validated environment (lazy initialization)
|
|
104
|
+
*/
|
|
105
|
+
export function getEnv(): Env {
|
|
106
|
+
if (!_env) {
|
|
107
|
+
_env = validateEnv();
|
|
108
|
+
}
|
|
109
|
+
return _env;
|
|
110
|
+
}
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Logger Service
|
|
3
|
+
*
|
|
4
|
+
* Simple structured logging without external dependencies.
|
|
5
|
+
* Supports multiple log levels and structured data.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
|
9
|
+
|
|
10
|
+
const LOG_LEVELS: Record<LogLevel, number> = {
|
|
11
|
+
trace: 10,
|
|
12
|
+
debug: 20,
|
|
13
|
+
info: 30,
|
|
14
|
+
warn: 40,
|
|
15
|
+
error: 50,
|
|
16
|
+
fatal: 60,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Get current log level from environment
|
|
21
|
+
*/
|
|
22
|
+
function getLogLevel(): LogLevel {
|
|
23
|
+
const level = (process.env.LOG_LEVEL || 'info').toLowerCase() as LogLevel;
|
|
24
|
+
return LOG_LEVELS[level] !== undefined ? level : 'info';
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Check if a log level should be logged
|
|
29
|
+
*/
|
|
30
|
+
function shouldLog(level: LogLevel): boolean {
|
|
31
|
+
const currentLevel = getLogLevel();
|
|
32
|
+
return LOG_LEVELS[level] >= LOG_LEVELS[currentLevel];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Format log message with timestamp and metadata
|
|
37
|
+
*/
|
|
38
|
+
function formatLog(level: LogLevel, message: string, data?: Record<string, any>): string {
|
|
39
|
+
const timestamp = new Date().toISOString();
|
|
40
|
+
const levelUpper = level.toUpperCase().padEnd(5);
|
|
41
|
+
|
|
42
|
+
if (data && Object.keys(data).length > 0) {
|
|
43
|
+
return `[${timestamp}] ${levelUpper} ${message} ${JSON.stringify(data)}`;
|
|
44
|
+
}
|
|
45
|
+
return `[${timestamp}] ${levelUpper} ${message}`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Logger class with utility methods
|
|
50
|
+
*/
|
|
51
|
+
class Logger {
|
|
52
|
+
/**
|
|
53
|
+
* Log trace level message (very detailed debugging)
|
|
54
|
+
*/
|
|
55
|
+
trace(message: string, data?: Record<string, any>): void {
|
|
56
|
+
if (shouldLog('trace')) {
|
|
57
|
+
console.log(formatLog('trace', message, data));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Log debug level message
|
|
63
|
+
*/
|
|
64
|
+
debug(message: string, data?: Record<string, any>): void {
|
|
65
|
+
if (shouldLog('debug')) {
|
|
66
|
+
console.log(formatLog('debug', message, data));
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Log info level message
|
|
72
|
+
*/
|
|
73
|
+
info(message: string, data?: Record<string, any>): void {
|
|
74
|
+
if (shouldLog('info')) {
|
|
75
|
+
console.log(formatLog('info', message, data));
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Log warning level message
|
|
81
|
+
*/
|
|
82
|
+
warn(message: string, data?: Record<string, any>): void {
|
|
83
|
+
if (shouldLog('warn')) {
|
|
84
|
+
console.warn(formatLog('warn', message, data));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Log error level message
|
|
90
|
+
*/
|
|
91
|
+
error(message: string, error?: Error | Record<string, any>): void {
|
|
92
|
+
if (shouldLog('error')) {
|
|
93
|
+
if (error instanceof Error) {
|
|
94
|
+
console.error(formatLog('error', message, {
|
|
95
|
+
error: error.message,
|
|
96
|
+
stack: error.stack,
|
|
97
|
+
}));
|
|
98
|
+
} else if (error) {
|
|
99
|
+
console.error(formatLog('error', message, error));
|
|
100
|
+
} else {
|
|
101
|
+
console.error(formatLog('error', message));
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Log fatal level message (application crash)
|
|
108
|
+
*/
|
|
109
|
+
fatal(message: string, error?: Error | Record<string, any>): void {
|
|
110
|
+
if (shouldLog('fatal')) {
|
|
111
|
+
if (error instanceof Error) {
|
|
112
|
+
console.error(formatLog('fatal', message, {
|
|
113
|
+
error: error.message,
|
|
114
|
+
stack: error.stack,
|
|
115
|
+
}));
|
|
116
|
+
} else if (error) {
|
|
117
|
+
console.error(formatLog('fatal', message, error));
|
|
118
|
+
} else {
|
|
119
|
+
console.error(formatLog('fatal', message));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Log HTTP request
|
|
126
|
+
*/
|
|
127
|
+
logRequest(data: {
|
|
128
|
+
method: string;
|
|
129
|
+
url: string;
|
|
130
|
+
statusCode?: number;
|
|
131
|
+
responseTime?: number;
|
|
132
|
+
userId?: string;
|
|
133
|
+
ip?: string;
|
|
134
|
+
}): void {
|
|
135
|
+
this.info('HTTP Request', data);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Log database query (use sparingly in production)
|
|
140
|
+
*/
|
|
141
|
+
logQuery(query: string, duration?: number): void {
|
|
142
|
+
this.debug('Database Query', { query, duration });
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Log authentication event
|
|
147
|
+
*/
|
|
148
|
+
logAuth(event: string, data: Record<string, any>): void {
|
|
149
|
+
this.info(`Auth: ${event}`, data);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Log security event
|
|
154
|
+
*/
|
|
155
|
+
logSecurity(event: string, data: Record<string, any>): void {
|
|
156
|
+
this.warn(`Security: ${event}`, data);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Export singleton instance
|
|
161
|
+
export default new Logger();
|