create-forgeapi 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 (64) hide show
  1. package/README.md +79 -0
  2. package/dist/bin/index.d.ts +2 -0
  3. package/dist/bin/index.js +286 -0
  4. package/package.json +37 -0
  5. package/template/.env.example +7 -0
  6. package/template/nodemon.json +7 -0
  7. package/template/package.json +21 -0
  8. package/template/src/App.ts +1 -0
  9. package/template/src/api/controllers/UserController.ts +54 -0
  10. package/template/src/api/middlewares/AuthMiddleware.ts +24 -0
  11. package/template/src/base/BaseRepository.ts +112 -0
  12. package/template/src/base/BaseResponse.ts +45 -0
  13. package/template/src/base/BaseResponseError.ts +12 -0
  14. package/template/src/base/BaseSchema.ts +82 -0
  15. package/template/src/config/DateTimeConfig.ts +4 -0
  16. package/template/src/config/GlobalHandlerError.ts +81 -0
  17. package/template/src/config/LoggerConfig.ts +157 -0
  18. package/template/src/config/LookupConfig.ts +68 -0
  19. package/template/src/config/MetaDataConfig.ts +25 -0
  20. package/template/src/config/RouteConfig.ts +98 -0
  21. package/template/src/config/SwaggerConfig.ts +147 -0
  22. package/template/src/config/envConfig.ts +5 -0
  23. package/template/src/core/Server.ts +75 -0
  24. package/template/src/core/exceptions/BadRequestException.ts +7 -0
  25. package/template/src/core/exceptions/ForbiddenException.ts +7 -0
  26. package/template/src/core/exceptions/NotFoundException.ts +7 -0
  27. package/template/src/core/exceptions/UnauthorizedException.ts +7 -0
  28. package/template/src/core/repositories/UserRepository.ts +53 -0
  29. package/template/src/database/DatabaseConnection.ts +11 -0
  30. package/template/src/database/builder/CustomBuilder.ts +177 -0
  31. package/template/src/database/builder/CustomFilter.ts +12 -0
  32. package/template/src/database/builder/MultipleSearchCriteria.ts +25 -0
  33. package/template/src/database/builder/SearchCriteria.ts +79 -0
  34. package/template/src/database/entity/ProfileModel.ts +54 -0
  35. package/template/src/database/entity/UserModel.ts +46 -0
  36. package/template/src/shared/decorators/ApiDecorator.ts +88 -0
  37. package/template/src/shared/decorators/LookupDecorator.ts +51 -0
  38. package/template/src/shared/decorators/MethodeDecorator.ts +38 -0
  39. package/template/src/shared/helpers/VirtualHelper.ts +53 -0
  40. package/template/src/shared/interfaces/BaseInterface.ts +14 -0
  41. package/template/src/shared/interfaces/DatabaseInterface.ts +41 -0
  42. package/template/src/shared/interfaces/HttpInterface.ts +37 -0
  43. package/template/src/shared/interfaces/LoggerOptionsInterface.ts +16 -0
  44. package/template/src/shared/interfaces/MiddlewareInterface.ts +14 -0
  45. package/template/src/shared/interfaces/RouteMetadataInterface.ts +10 -0
  46. package/template/src/shared/interfaces/SoftDeleteInterface.ts +8 -0
  47. package/template/src/shared/interfaces/SwaggerMetadataInterface.ts +12 -0
  48. package/template/src/shared/interfaces/UserInterface.ts +37 -0
  49. package/template/src/shared/interfaces/user/UserCreateInterface.ts +4 -0
  50. package/template/src/shared/models/enum/MiddlewareEnum.ts +19 -0
  51. package/template/src/shared/models/request/UserCreateRequest.ts +12 -0
  52. package/template/src/shared/models/response/ApiResponse.ts +27 -0
  53. package/template/src/shared/types/express.d.ts +14 -0
  54. package/template/src/shared/types/mongoose.d.ts +7 -0
  55. package/template/src/shared/utils/ColorUtil.ts +17 -0
  56. package/template/src/shared/utils/DatabaseUtil.ts +3 -0
  57. package/template/src/shared/utils/DecoratorUtil.ts +4 -0
  58. package/template/src/shared/utils/FolderUtil.ts +15 -0
  59. package/template/src/shared/utils/JwtUtil.ts +21 -0
  60. package/template/src/shared/utils/SwaggerUtil.ts +107 -0
  61. package/template/src/shared/utils/TimeUtil.ts +74 -0
  62. package/template/tsconfig.dev.json +9 -0
  63. package/template/tsconfig.json +33 -0
  64. package/template/tsconfig.prod.json +10 -0
@@ -0,0 +1,45 @@
1
+ import {Response} from "express";
2
+ import {SuccessResponseData} from "@shared/interfaces/HttpInterface";
3
+
4
+ export default class BaseResponse {
5
+
6
+ static async ok<T>(
7
+ res: Response,
8
+ serviceFunction: () => Promise<T>,
9
+ message: string = 'Success'
10
+ ): Promise<Response> {
11
+ const data = await serviceFunction();
12
+
13
+ const response: SuccessResponseData<T> = {
14
+ success: true,
15
+ message,
16
+ data,
17
+ };
18
+
19
+ return res.status(200).json(response);
20
+ }
21
+
22
+ static async created<T>(
23
+ res: Response,
24
+ serviceFunction: () => Promise<T>,
25
+ message: string = 'Resource created successfully'
26
+ ): Promise<Response> {
27
+ const data = await serviceFunction();
28
+
29
+ const response: SuccessResponseData<T> = {
30
+ success: true,
31
+ message,
32
+ data,
33
+ };
34
+
35
+ return res.status(201).json(response);
36
+ }
37
+
38
+ static async noContent(
39
+ res: Response,
40
+ serviceFunction: () => Promise<void>
41
+ ): Promise<Response> {
42
+ await serviceFunction();
43
+ return res.status(204).send();
44
+ }
45
+ }
@@ -0,0 +1,12 @@
1
+
2
+ export default class BaseResponseError extends Error {
3
+ public statusCode: number;
4
+ public errors?: Record<string, unknown>[];
5
+ constructor(statusCode: number, message: string, errors?: Record<string, unknown>[]) {
6
+ super(message);
7
+ this.statusCode = statusCode;
8
+ this.errors = errors;
9
+ this.name = this.constructor.name;
10
+ Error.captureStackTrace(this, this.constructor);
11
+ }
12
+ }
@@ -0,0 +1,82 @@
1
+ import {Schema} from "mongoose";
2
+
3
+ export default class BaseSchema {
4
+ public static create <T>(schemaDefinition: any, options?: any): Schema<T> {
5
+ const baseFields = {
6
+ createdAt: {
7
+ type: Number,
8
+ required: false,
9
+ index: true
10
+ },
11
+ updatedAt: {
12
+ type: Number,
13
+ required: false
14
+ },
15
+ createdBy: {
16
+ type: Schema.Types.ObjectId,
17
+ ref: 'users',
18
+ required: false,
19
+ index: true
20
+ },
21
+ modifiedBy: {
22
+ type: Schema.Types.ObjectId,
23
+ ref: 'users',
24
+ required: false
25
+ },
26
+ deletedAt: {
27
+ type: Number,
28
+ required: false
29
+ },
30
+ deletedBy: {
31
+ type: Schema.Types.ObjectId,
32
+ ref: 'users',
33
+ required: false
34
+ }
35
+ };
36
+ const mergedDefinition = {
37
+ ...schemaDefinition,
38
+ ...baseFields
39
+ };
40
+ const mergedOptions = {
41
+ timestamps: false,
42
+ versionKey: false,
43
+ ...options
44
+ };
45
+
46
+ const schema = new Schema<T>(mergedDefinition, mergedOptions);
47
+
48
+ schema.pre('save', function(next) {
49
+ const now = Date.now();
50
+ const doc = this as any;
51
+ if (doc.isNew) {
52
+ doc.createdAt = now;
53
+ }
54
+ doc.updatedAt = now;
55
+ next();
56
+ });
57
+
58
+ schema.pre(['findOneAndUpdate', 'updateOne', 'updateMany'], function(next) {
59
+ this.set({ updatedAt: Date.now() });
60
+ next();
61
+ });
62
+
63
+ return schema;
64
+ }
65
+ public static addSoftDelete<T>(schema: Schema<T>): void {
66
+ schema.methods.delete = async function(deletedBy: any) {
67
+ this.deletedAt = Date.now();
68
+ this.deletedBy = deletedBy;
69
+ return await this.save();
70
+ };
71
+
72
+ schema.methods.restore = async function() {
73
+ this.deletedAt = undefined;
74
+ this.deletedBy = undefined;
75
+ return await this.save();
76
+ };
77
+
78
+ schema.methods.isDeleted = function() {
79
+ return !!this.deletedAt;
80
+ };
81
+ }
82
+ }
@@ -0,0 +1,4 @@
1
+ import {DayEnum, MonthEnum} from "@shared/models/enum/DateTimeEnum";
2
+
3
+ export const DAY_VALUES = Object.values(DayEnum) as DayEnum[];
4
+ export const MONTH_VALUES = Object.values(MonthEnum) as MonthEnum[];
@@ -0,0 +1,81 @@
1
+ import {Response, Request, NextFunction} from "express";
2
+ import BaseResponseError from "@base/BaseResponseError";
3
+ import {ErrorContext, ErrorResponseData, NotFoundResponse} from "@shared/interfaces/HttpInterface";
4
+ import {log} from "@config/LoggerConfig";
5
+ import TimeUtil from "@shared/utils/TimeUtil";
6
+
7
+ const findSimilarRoute = (requestedPath: string, routes: string[]): string | null => {
8
+ // Simple similarity check - bisa diganti dengan library seperti 'string-similarity'
9
+ for (const route of routes) {
10
+ if (requestedPath.toLowerCase().includes(route.toLowerCase().split('/')[2])) {
11
+ return route;
12
+ }
13
+ }
14
+ return null;
15
+ }
16
+
17
+ export default class {
18
+ static error(
19
+ err: Error | BaseResponseError,
20
+ req: Request,
21
+ res: Response,
22
+ next: NextFunction
23
+ ) {
24
+ const context: ErrorContext = {
25
+ endpoint: req.path,
26
+ method: req.method,
27
+ userId: (req as any).user?.id, // jika ada authentication
28
+ body: req.body,
29
+ query: req.query,
30
+ params: req.params
31
+ };
32
+ log.server(err, context);
33
+
34
+ // Jika error adalah BaseResponseError
35
+ if (err instanceof BaseResponseError) {
36
+ const response: ErrorResponseData = {
37
+ success: false,
38
+ message: err.message,
39
+ ...(err.errors && { errors: err.errors }),
40
+ ...(process.env.NODE_ENV === 'development' && { stack: err.stack })
41
+ };
42
+ res.status(err.statusCode).json(response);
43
+ return;
44
+ }
45
+
46
+ // Default error (500)
47
+ const response: ErrorResponseData = {
48
+ success: false,
49
+ message: err.message || 'Internal server error',
50
+ ...(process.env.NODE_ENV === 'development' && {
51
+ error: err.toString(),
52
+ stack: err.stack
53
+ })
54
+ };
55
+ res.status(500).json(response);
56
+ };
57
+
58
+ static notFound(
59
+ req: Request,
60
+ res: Response,
61
+ next: NextFunction
62
+ ) {
63
+ const response: NotFoundResponse = {
64
+ success: false,
65
+ message: 'API endpoint not found',
66
+ path: req.originalUrl || req.url,
67
+ method: req.method,
68
+ timestamp: TimeUtil.getReadableDayDateTime(new Date())
69
+ };
70
+
71
+ // Optional: Tambahkan suggestion untuk typo umum
72
+ const availableRoutes = [''];
73
+ const similarRoute = findSimilarRoute(req.path, availableRoutes);
74
+
75
+ if (similarRoute) {
76
+ response.suggestion = `Did you mean ${similarRoute}?`;
77
+ }
78
+
79
+ res.status(404).json(response);
80
+ };
81
+ }
@@ -0,0 +1,157 @@
1
+ import TimeUtil from "@shared/utils/TimeUtil";
2
+ import {LogData, LoggerOptions} from "@shared/interfaces/LoggerOptionsInterface";
3
+ import path from "node:path";
4
+ import FolderUtil from "@shared/utils/FolderUtil";
5
+ import {NextFunction, Request, Response} from "express";
6
+ import {getStatusColor, getStatusEmoji} from "@shared/utils/ColorUtil";
7
+ import fs from "node:fs";
8
+ import {ErrorContext, ErrorLogData} from "@shared/interfaces/HttpInterface";
9
+ import BaseResponseError from "@base/BaseResponseError";
10
+ import {Color} from "@shared/models/enum/ColorEnum";
11
+
12
+ export const serverLogger = (options: LoggerOptions = {}) => {
13
+ const {
14
+ enableFileLog = true,
15
+ enableConsoleLog = true,
16
+ logFileName = `app-${new Date().toISOString().split('T')[0]}.log`,
17
+ folderPath = 'logs'
18
+ } = options;
19
+
20
+ const logFilePath = path.join(FolderUtil.mkdir(folderPath), logFileName);
21
+
22
+ return (req: Request, res: Response, next: NextFunction): void => {
23
+ const startTime = Date.now();
24
+
25
+ // Capture response
26
+ const originalSend = res.send;
27
+ let responseBody: any;
28
+
29
+ res.send = function (data: any): Response {
30
+ responseBody = data;
31
+ return originalSend.apply(res, [data]);
32
+ };
33
+
34
+ // Log ketika response selesai
35
+ res.on('finish', () => {
36
+ const duration = Date.now() - startTime;
37
+ const timestamp = TimeUtil.getTimestamp();
38
+
39
+ // Data log
40
+ const logData: LogData = {
41
+ timestamp,
42
+ method: req.method,
43
+ url: req.originalUrl || req.url,
44
+ status: res.statusCode,
45
+ duration: `${duration}ms`,
46
+ ip: req.ip || req.socket.remoteAddress || 'Unknown',
47
+ userAgent: req.get('user-agent') || 'Unknown'
48
+ };
49
+
50
+ // Console log dengan warna dan emoji
51
+ if (enableConsoleLog) {
52
+ const statusColor = getStatusColor(res.statusCode);
53
+ const emoji = getStatusEmoji(res.statusCode);
54
+
55
+ console.log(
56
+ `${Color.GRAY}[${timestamp}]${Color.RESET} ` +
57
+ `${emoji} ` +
58
+ `${Color.BRIGHT}${req.method}${Color.RESET} ` +
59
+ `${Color.CYAN}${req.originalUrl || req.url}${Color.RESET} ` +
60
+ `${statusColor}${res.statusCode}${Color.RESET} ` +
61
+ `${Color.MAGENTA}${TimeUtil.formatDuration(duration)}${Color.RESET} ` +
62
+ `${Color.GRAY}(${logData.ip})${Color.RESET}`
63
+ );
64
+ }
65
+
66
+ // File log (format yang lebih mudah dibaca)
67
+ if (enableFileLog) {
68
+ const logEntry = [
69
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`,
70
+ `Timestamp : ${timestamp}`,
71
+ `Method : ${logData.method}`,
72
+ `URL : ${logData.url}`,
73
+ `Status : ${logData.status}`,
74
+ `Duration : ${logData.duration}`,
75
+ `IP Address : ${logData.ip}`,
76
+ `User Agent : ${logData.userAgent}`,
77
+ `━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`
78
+ ].join('\n');
79
+
80
+ fs.appendFile(logFilePath, logEntry, (err) => {
81
+ if (err) console.error('Error writing to log file:', err);
82
+ });
83
+ }
84
+ });
85
+
86
+ next();
87
+ };
88
+ };
89
+
90
+ export class log {
91
+ static info (...message: unknown[]) {
92
+ const text = message
93
+ .map(item =>
94
+ typeof item === 'string'
95
+ ? item
96
+ : JSON.stringify(item)
97
+ )
98
+ .join(' ');
99
+
100
+ const log = `${Color.BLUE}ℹ️ INFO${Color.RESET} [${TimeUtil.getTimeString()}] ${text}`;
101
+ console.log(log);
102
+ // const log = `${Color.BLUE}ℹ️ INFO${Color.RESET} [${TimeUtil.getTimestamp()}] ${message}`;
103
+ // console.log(log);
104
+ }
105
+
106
+ static success(message: string) {
107
+ const log = `${Color.GREEN}✅ SUCCESS${Color.RESET} [${TimeUtil.getTimeString()}] ${message}`;
108
+ console.log(log);
109
+ }
110
+
111
+ static warning(message: string) {
112
+ const log = `${Color.YELLOW}⚠️ WARNING${Color.RESET} [${TimeUtil.getTimeString()}] ${message}`;
113
+ console.log(log);
114
+ }
115
+
116
+ static error(...message: unknown[]) {
117
+ const text = message
118
+ .map(item =>
119
+ typeof item === 'string'
120
+ ? item
121
+ : JSON.stringify(item)
122
+ )
123
+ .join(' ');
124
+
125
+ const log =
126
+ `${Color.YELLOW}❌ ERROR${Color.RED} ` +
127
+ `[${TimeUtil.getTimeString()}] ${text}`;
128
+
129
+ console.log(log);
130
+ }
131
+
132
+ static server(
133
+ error: Error,
134
+ context?: ErrorContext
135
+ ) {
136
+ const errorLog: ErrorLogData = {
137
+ timestamp: TimeUtil.getTimeString(),
138
+ error: error,
139
+ context: context,
140
+ };
141
+ log.error('Time:', errorLog.timestamp);
142
+ log.error('Error Name:', error.name);
143
+ log.error('Message:', error.message);
144
+ log.error('Stack:', error.stack);
145
+
146
+ if (error instanceof BaseResponseError) {
147
+ log.error('Status Code:', error.statusCode);
148
+ if (error.errors) {
149
+ log.error('Validation Errors:', JSON.stringify(error.errors, null, 2));
150
+ }
151
+ }
152
+
153
+ if (context) {
154
+ log.error('Context:', JSON.stringify(context, null, 2));
155
+ }
156
+ }
157
+ }
@@ -0,0 +1,68 @@
1
+ import {ILookupConfig} from "@shared/interfaces/DatabaseInterface";
2
+ import {log} from "@config/LoggerConfig";
3
+
4
+ export default class LookupConfig {
5
+ private static lookups: Map<string, ILookupConfig> = new Map();
6
+
7
+ static register(prefix: string, config: ILookupConfig): void {
8
+ this.lookups.set(prefix, config);
9
+ }
10
+
11
+ static get(prefix: string): ILookupConfig | undefined {
12
+ return this.lookups.get(prefix);
13
+ }
14
+
15
+ static getLookupForPath(path: string): { prefix: string; config: ILookupConfig } | null {
16
+ for (const [prefix, config] of this.lookups.entries()) {
17
+ if (path.startsWith(prefix + '.')) {
18
+ return { prefix, config };
19
+ }
20
+ }
21
+ return null;
22
+ }
23
+
24
+ static getAll(): Map<string, ILookupConfig> {
25
+ return this.lookups;
26
+ }
27
+
28
+ static init(entities: any[]) {
29
+ log.info("━".repeat(60));
30
+ log.info('🚀 Initializing lookups...');
31
+
32
+ // Auto-register dari decorators
33
+ for (const entity of entities) {
34
+ if (typeof entity.registerLookups === 'function') {
35
+ entity.registerLookups();
36
+ }
37
+ }
38
+
39
+ // Register lookup untuk audit fields (GLOBAL - untuk semua entity)
40
+ LookupConfig.register('creatorUser', {
41
+ from: 'users',
42
+ localField: 'createdBy',
43
+ foreignField: '_id',
44
+ as: 'creatorUser',
45
+ unwind: true
46
+ });
47
+
48
+ LookupConfig.register('modifierUser', {
49
+ from: 'users',
50
+ localField: 'modifiedBy',
51
+ foreignField: '_id',
52
+ as: 'modifierUser',
53
+ unwind: true
54
+ });
55
+
56
+ LookupConfig.register('deleterUser', {
57
+ from: 'users',
58
+ localField: 'deletedBy',
59
+ foreignField: '_id',
60
+ as: 'deleterUser',
61
+ unwind: true
62
+ });
63
+
64
+ log.info('✅ All lookups initialized');
65
+ log.info('📋 Registered lookups:', Array.from(LookupConfig.getAll().keys()));
66
+ log.info("━".repeat(60));
67
+ }
68
+ }
@@ -0,0 +1,25 @@
1
+ import {LookupDecoratorOptions, VirtualFieldConfig} from "@shared/interfaces/DatabaseInterface";
2
+
3
+ export default class MetaDataConfig {
4
+ public static lookupMetadata = new Map<string, Map<string, LookupDecoratorOptions>>();
5
+
6
+ public static virtualMetadata = new Map<string, Map<string, VirtualFieldConfig>>();
7
+
8
+ public static lookupGetMetadata(className: string): Map<string, LookupDecoratorOptions> {
9
+ return this.lookupMetadata.get(className) || new Map();
10
+ }
11
+
12
+ public static lookupGetKeys(className: string): string[] {
13
+ const metadata = this.lookupMetadata.get(className);
14
+ return metadata ? Array.from(metadata.keys()) : [];
15
+ }
16
+
17
+ public static virtualGetMetadata(className: string): Map<string, VirtualFieldConfig> {
18
+ return this.virtualMetadata.get(className) || new Map();
19
+ }
20
+
21
+ public static virtualGetFields(className: string): VirtualFieldConfig[] {
22
+ const metadata = this.virtualMetadata.get(className);
23
+ return metadata ? Array.from(metadata.values()) : [];
24
+ }
25
+ }
@@ -0,0 +1,98 @@
1
+ import {Router, Request, Response, NextFunction} from "express";
2
+ import {GetControllerMetadata} from "@shared/decorators/ApiDecorator";
3
+ import AuthMiddleware from "@api/middlewares/AuthMiddleware";
4
+ import {IRouteDefinition} from "@shared/interfaces/RouteMetadataInterface";
5
+
6
+ export default class RouteConfig {
7
+ protected static controllers: any[] = [];
8
+
9
+ static register(...controller: any) {
10
+ console.log('📝 Registering controllers:', controller.map((c: any) => c.name));
11
+ this.controllers.push(...controller);
12
+ console.log('📝 Total controllers:', this.controllers.length);
13
+ }
14
+
15
+ static getControllers() {
16
+ return this.controllers;
17
+ }
18
+
19
+ static buildRoutes(router: Router) {
20
+ console.log('🔷 buildRoutes called, controllers count:', this.controllers.length);
21
+
22
+ try {
23
+ this.controllers.forEach((ControllerClass, index) => {
24
+ console.log(`🔷 Processing controller ${index+1}:`, ControllerClass.name);
25
+
26
+ try {
27
+ const instance = new ControllerClass();
28
+ const metadata = GetControllerMetadata(ControllerClass);
29
+
30
+ // console.log('🔷 Metadata:', metadata);
31
+
32
+ if (!metadata) {
33
+ console.error('❌ No metadata found for controller:', ControllerClass.name);
34
+ return;
35
+ }
36
+
37
+ if (!metadata.routes || !Array.isArray(metadata.routes)) {
38
+ console.error('❌ No routes found in metadata for controller:', ControllerClass.name);
39
+ return;
40
+ }
41
+
42
+ metadata.routes.forEach((route: IRouteDefinition) => {
43
+ const fullPath = `${metadata.basePath}${route.path}`;
44
+ const handler = (instance as any)[route.handlerName as string].bind(instance);
45
+
46
+ console.log(`🔷 Registering route: ${route.method.toString().toUpperCase()} ${fullPath}`);
47
+
48
+ // requireAuth: true hanya jika authenticate.authenticate === true (default: false)
49
+ const requireAuth = route.authenticate?.authenticate === true;
50
+
51
+ const middleware = requireAuth
52
+ ? [
53
+ // Inject route.authenticate ke req sebelum AuthMiddleware berjalan
54
+ (req: Request, _res: Response, next: NextFunction) => {
55
+ req.authenticate = route.authenticate;
56
+ next();
57
+ },
58
+ AuthMiddleware,
59
+ async (req: Request, res: Response, next: NextFunction) => {
60
+ try {
61
+ const result = await handler(req, res, next);
62
+ if (result !== undefined && !res.headersSent) {
63
+ res.json(result);
64
+ }
65
+ } catch (error) {
66
+ next(error);
67
+ }
68
+ }
69
+ ]
70
+ : [async (req: Request, res: Response, next: NextFunction) => {
71
+ try {
72
+ const result = await handler(req, res, next);
73
+ if (result !== undefined && !res.headersSent) {
74
+ res.json(result);
75
+ }
76
+ } catch (error) {
77
+ console.log('🟡 Error caught in RouteConfig (no auth):', error);
78
+ next(error);
79
+ }
80
+ }];
81
+
82
+ (router as any)[route.method](fullPath, ...middleware);
83
+ });
84
+ } catch (controllerError) {
85
+ console.error('❌ Error processing controller:', ControllerClass.name, controllerError);
86
+ throw controllerError;
87
+ }
88
+ });
89
+
90
+ console.log('✅ All routes registered successfully');
91
+ } catch (error) {
92
+ console.error('❌ Error in buildRoutes:', error);
93
+ throw error;
94
+ }
95
+
96
+ return router;
97
+ }
98
+ }