offbyt 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/README.md +2 -0
- package/cli/index.js +2 -0
- package/cli.js +206 -0
- package/core/detector/detectAxios.js +107 -0
- package/core/detector/detectFetch.js +148 -0
- package/core/detector/detectForms.js +55 -0
- package/core/detector/detectSocket.js +341 -0
- package/core/generator/generateControllers.js +17 -0
- package/core/generator/generateModels.js +25 -0
- package/core/generator/generateRoutes.js +17 -0
- package/core/generator/generateServer.js +18 -0
- package/core/generator/generateSocket.js +160 -0
- package/core/index.js +14 -0
- package/core/ir/IRTypes.js +25 -0
- package/core/ir/buildIR.js +83 -0
- package/core/parser/parseJS.js +26 -0
- package/core/parser/parseTS.js +27 -0
- package/core/rules/relationRules.js +38 -0
- package/core/rules/resourceRules.js +32 -0
- package/core/rules/schemaInference.js +26 -0
- package/core/scanner/scanProject.js +58 -0
- package/deploy/cloudflare.js +41 -0
- package/deploy/cloudflareWorker.js +122 -0
- package/deploy/connect.js +198 -0
- package/deploy/flyio.js +51 -0
- package/deploy/index.js +322 -0
- package/deploy/netlify.js +29 -0
- package/deploy/railway.js +215 -0
- package/deploy/render.js +195 -0
- package/deploy/utils.js +383 -0
- package/deploy/vercel.js +29 -0
- package/index.js +18 -0
- package/lib/generator/advancedCrudGenerator.js +475 -0
- package/lib/generator/crudCodeGenerator.js +486 -0
- package/lib/generator/irBasedGenerator.js +360 -0
- package/lib/ir-builder/index.js +16 -0
- package/lib/ir-builder/irBuilder.js +330 -0
- package/lib/ir-builder/rulesEngine.js +353 -0
- package/lib/ir-builder/templateEngine.js +193 -0
- package/lib/ir-builder/templates/index.js +14 -0
- package/lib/ir-builder/templates/model.template.js +47 -0
- package/lib/ir-builder/templates/routes-generic.template.js +66 -0
- package/lib/ir-builder/templates/routes-user.template.js +105 -0
- package/lib/ir-builder/templates/routes.template.js +102 -0
- package/lib/ir-builder/templates/validation.template.js +15 -0
- package/lib/ir-integration.js +349 -0
- package/lib/modes/benchmark.js +162 -0
- package/lib/modes/configBasedGenerator.js +2258 -0
- package/lib/modes/connect.js +1125 -0
- package/lib/modes/doctorAi.js +172 -0
- package/lib/modes/generateApi.js +435 -0
- package/lib/modes/interactiveSetup.js +548 -0
- package/lib/modes/offline.clean.js +14 -0
- package/lib/modes/offline.enhanced.js +787 -0
- package/lib/modes/offline.js +295 -0
- package/lib/modes/offline.v2.js +13 -0
- package/lib/modes/sync.js +629 -0
- package/lib/scanner/apiEndpointExtractor.js +387 -0
- package/lib/scanner/authPatternDetector.js +54 -0
- package/lib/scanner/frontendScanner.js +642 -0
- package/lib/utils/apiClientGenerator.js +242 -0
- package/lib/utils/apiScanner.js +95 -0
- package/lib/utils/codeInjector.js +350 -0
- package/lib/utils/doctor.js +381 -0
- package/lib/utils/envGenerator.js +36 -0
- package/lib/utils/loadTester.js +61 -0
- package/lib/utils/performanceAnalyzer.js +298 -0
- package/lib/utils/resourceDetector.js +281 -0
- package/package.json +20 -0
- package/templates/.env.template +31 -0
- package/templates/advanced.model.template.js +201 -0
- package/templates/advanced.route.template.js +341 -0
- package/templates/auth.middleware.template.js +87 -0
- package/templates/auth.routes.template.js +238 -0
- package/templates/auth.user.model.template.js +78 -0
- package/templates/cache.middleware.js +34 -0
- package/templates/chat.models.template.js +260 -0
- package/templates/chat.routes.template.js +478 -0
- package/templates/compression.middleware.js +19 -0
- package/templates/database.config.js +74 -0
- package/templates/errorHandler.middleware.js +54 -0
- package/templates/express/controller.ejs +26 -0
- package/templates/express/model.ejs +9 -0
- package/templates/express/route.ejs +18 -0
- package/templates/express/server.ejs +16 -0
- package/templates/frontend.env.template +14 -0
- package/templates/model.template.js +86 -0
- package/templates/package.production.json +51 -0
- package/templates/package.template.json +41 -0
- package/templates/pagination.utility.js +110 -0
- package/templates/production.server.template.js +233 -0
- package/templates/rateLimiter.middleware.js +36 -0
- package/templates/requestLogger.middleware.js +19 -0
- package/templates/response.helper.js +179 -0
- package/templates/route.template.js +130 -0
- package/templates/security.middleware.js +78 -0
- package/templates/server.template.js +91 -0
- package/templates/socket.server.template.js +433 -0
- package/templates/utils.helper.js +157 -0
- package/templates/validation.middleware.js +63 -0
- package/templates/validation.schema.js +128 -0
- package/utils/fileWriter.js +15 -0
- package/utils/logger.js +18 -0
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const __MODEL_NAME__Schema = new mongoose.Schema(
|
|
4
|
+
{
|
|
5
|
+
/* __FIELDS__ */
|
|
6
|
+
isActive: {
|
|
7
|
+
type: Boolean,
|
|
8
|
+
default: true,
|
|
9
|
+
index: true
|
|
10
|
+
},
|
|
11
|
+
isDeleted: {
|
|
12
|
+
type: Boolean,
|
|
13
|
+
default: false,
|
|
14
|
+
index: true
|
|
15
|
+
},
|
|
16
|
+
createdBy: {
|
|
17
|
+
type: mongoose.Schema.Types.ObjectId,
|
|
18
|
+
ref: 'User'
|
|
19
|
+
},
|
|
20
|
+
metadata: {
|
|
21
|
+
updatedBy: mongoose.Schema.Types.ObjectId,
|
|
22
|
+
version: { type: Number, default: 1 },
|
|
23
|
+
tags: [String]
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
timestamps: true,
|
|
28
|
+
collection: '__COLLECTION_NAME__'
|
|
29
|
+
}
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// ============================================================
|
|
33
|
+
// INDEXES FOR PERFORMANCE
|
|
34
|
+
// ============================================================
|
|
35
|
+
__MODEL_NAME__Schema.index({ createdAt: -1 });
|
|
36
|
+
__MODEL_NAME__Schema.index({ updatedAt: -1 });
|
|
37
|
+
__MODEL_NAME__Schema.index({ isActive: 1 });
|
|
38
|
+
__MODEL_NAME__Schema.index({ isDeleted: 1, createdAt: -1 });
|
|
39
|
+
|
|
40
|
+
// ============================================================
|
|
41
|
+
// HOOKS
|
|
42
|
+
// ============================================================
|
|
43
|
+
__MODEL_NAME__Schema.pre('save', function(next) {
|
|
44
|
+
if (this.isModified()) {
|
|
45
|
+
this.metadata.version = (this.metadata.version || 0) + 1;
|
|
46
|
+
}
|
|
47
|
+
next();
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
// ============================================================
|
|
51
|
+
// QUERY HELPERS
|
|
52
|
+
// ============================================================
|
|
53
|
+
__MODEL_NAME__Schema.query.active = function() {
|
|
54
|
+
return this.find({ isActive: true, isDeleted: false });
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
// ============================================================
|
|
58
|
+
// STATIC METHODS
|
|
59
|
+
// ============================================================
|
|
60
|
+
__MODEL_NAME__Schema.statics.findAllActive = async function(options = {}) {
|
|
61
|
+
return this.find({ isActive: true, isDeleted: false })
|
|
62
|
+
.sort(options.sort || { createdAt: -1 })
|
|
63
|
+
.limit(options.limit || 100)
|
|
64
|
+
.skip(options.skip || 0);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
__MODEL_NAME__Schema.statics.softDelete = async function(id) {
|
|
68
|
+
return this.findByIdAndUpdate(
|
|
69
|
+
id,
|
|
70
|
+
{ isDeleted: true },
|
|
71
|
+
{ new: true }
|
|
72
|
+
);
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
// ============================================================
|
|
76
|
+
// INSTANCE METHODS
|
|
77
|
+
// ============================================================
|
|
78
|
+
__MODEL_NAME__Schema.methods.toJSON = function() {
|
|
79
|
+
const obj = this.toObject();
|
|
80
|
+
delete obj.__v;
|
|
81
|
+
return obj;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const __MODEL_NAME__ = mongoose.model('__MODEL_NAME__', __MODEL_NAME__Schema);
|
|
85
|
+
|
|
86
|
+
export default __MODEL_NAME__;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Production-Ready Backend API - Generated by offbyt",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node server.js",
|
|
9
|
+
"dev": "NODE_ENV=development node --watch server.js",
|
|
10
|
+
"test": "NODE_ENV=test echo \"Test suite coming soon\"",
|
|
11
|
+
"build": "echo \"Build complete\"",
|
|
12
|
+
"lint": "echo \"Linting enabled in production setup\""
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"backend",
|
|
16
|
+
"express",
|
|
17
|
+
"mongoose",
|
|
18
|
+
"rest-api",
|
|
19
|
+
"mongodb",
|
|
20
|
+
"production",
|
|
21
|
+
"api",
|
|
22
|
+
"full-stack"
|
|
23
|
+
],
|
|
24
|
+
"author": "offbyt Generator",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"dependencies": {
|
|
27
|
+
"axios": "^1.6.5",
|
|
28
|
+
"bcryptjs": "^2.4.3",
|
|
29
|
+
"chalk": "^5.3.0",
|
|
30
|
+
"compression": "^1.7.4",
|
|
31
|
+
"cors": "^2.8.5",
|
|
32
|
+
"dotenv": "^16.3.1",
|
|
33
|
+
"express": "^4.18.2",
|
|
34
|
+
"express-async-errors": "^3.1.1",
|
|
35
|
+
"express-rate-limit": "^7.1.5",
|
|
36
|
+
"express-validator": "^7.0.0",
|
|
37
|
+
"helmet": "^7.1.0",
|
|
38
|
+
"jsonwebtoken": "^9.0.2",
|
|
39
|
+
"express-mongo-sanitize": "^2.2.0",
|
|
40
|
+
"mongoose": "^8.0.3",
|
|
41
|
+
"ora": "^8.0.1",
|
|
42
|
+
"validator": "^13.11.0"
|
|
43
|
+
},
|
|
44
|
+
"devDependencies": {
|
|
45
|
+
"nodemon": "^3.0.2"
|
|
46
|
+
},
|
|
47
|
+
"engines": {
|
|
48
|
+
"node": ">=18.0.0",
|
|
49
|
+
"npm": ">=9.0.0"
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "backend",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Backend Server - Generated by offbyt",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node server.js",
|
|
9
|
+
"dev": "NODE_ENV=development node --watch server.js",
|
|
10
|
+
"dev:ai": "offbyt doctor-ai",
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 0"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"backend",
|
|
15
|
+
"express",
|
|
16
|
+
"mongoose",
|
|
17
|
+
"rest-api",
|
|
18
|
+
"mongodb"
|
|
19
|
+
],
|
|
20
|
+
"author": "offbyt Generator",
|
|
21
|
+
"license": "MIT",
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"axios": "^1.6.0",
|
|
24
|
+
"bcryptjs": "^2.4.3",
|
|
25
|
+
"cors": "^2.8.5",
|
|
26
|
+
"dotenv": "^16.3.1",
|
|
27
|
+
"express": "^4.18.2",
|
|
28
|
+
"express-async-errors": "^3.1.1",
|
|
29
|
+
"jsonwebtoken": "^9.0.2",
|
|
30
|
+
"express-mongo-sanitize": "^2.2.0",
|
|
31
|
+
"mongoose": "^8.0.3",
|
|
32
|
+
"validator": "^13.11.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"nodemon": "^3.0.1"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=18.0.0",
|
|
39
|
+
"npm": ">=9.0.0"
|
|
40
|
+
}
|
|
41
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pagination Utility
|
|
3
|
+
* Provides pagination, filtering, sorting for queries
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
export class PaginationHelper {
|
|
7
|
+
static getPaginationParams(req) {
|
|
8
|
+
const page = Math.max(1, parseInt(req.query.page) || 1);
|
|
9
|
+
const limit = Math.min(100, parseInt(req.query.limit) || 10);
|
|
10
|
+
const skip = (page - 1) * limit;
|
|
11
|
+
|
|
12
|
+
return { page, limit, skip };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static async paginate(model, query = {}, options = {}) {
|
|
16
|
+
const {
|
|
17
|
+
page = 1,
|
|
18
|
+
limit = 10,
|
|
19
|
+
skip = 0,
|
|
20
|
+
sort = { createdAt: -1 },
|
|
21
|
+
select = null,
|
|
22
|
+
populate = []
|
|
23
|
+
} = options;
|
|
24
|
+
|
|
25
|
+
const total = await model.countDocuments(query);
|
|
26
|
+
const data = await model
|
|
27
|
+
.find(query)
|
|
28
|
+
.sort(sort)
|
|
29
|
+
.skip(skip)
|
|
30
|
+
.limit(limit)
|
|
31
|
+
.select(select);
|
|
32
|
+
|
|
33
|
+
if (populate.length > 0) {
|
|
34
|
+
for (const field of populate) {
|
|
35
|
+
await model.populate(data, field);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
data,
|
|
41
|
+
pagination: {
|
|
42
|
+
page,
|
|
43
|
+
limit,
|
|
44
|
+
total,
|
|
45
|
+
pages: Math.ceil(total / limit),
|
|
46
|
+
hasMore: page * limit < total,
|
|
47
|
+
skip
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
static getSortObject(sortParam) {
|
|
53
|
+
if (!sortParam) return { createdAt: -1 };
|
|
54
|
+
|
|
55
|
+
const sorts = {};
|
|
56
|
+
const fields = sortParam.split(',');
|
|
57
|
+
|
|
58
|
+
for (const field of fields) {
|
|
59
|
+
if (field.startsWith('-')) {
|
|
60
|
+
sorts[field.substring(1)] = -1;
|
|
61
|
+
} else {
|
|
62
|
+
sorts[field] = 1;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return sorts;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
static buildFilterQuery(req, allowedFields = []) {
|
|
70
|
+
const query = {};
|
|
71
|
+
|
|
72
|
+
for (const field of allowedFields) {
|
|
73
|
+
if (req.query[field]) {
|
|
74
|
+
const value = req.query[field];
|
|
75
|
+
|
|
76
|
+
// Handle range queries
|
|
77
|
+
if (value.includes('..')) {
|
|
78
|
+
const [min, max] = value.split('..');
|
|
79
|
+
query[field] = { $gte: isNaN(min) ? min : parseFloat(min), $lte: isNaN(max) ? max : parseFloat(max) };
|
|
80
|
+
}
|
|
81
|
+
// Handle multiple values
|
|
82
|
+
else if (value.includes(',')) {
|
|
83
|
+
query[field] = { $in: value.split(',') };
|
|
84
|
+
}
|
|
85
|
+
// Handle regex search
|
|
86
|
+
else if (value.includes('*')) {
|
|
87
|
+
query[field] = { $regex: value.replace(/\*/g, '.*'), $options: 'i' };
|
|
88
|
+
}
|
|
89
|
+
// Direct match
|
|
90
|
+
else {
|
|
91
|
+
query[field] = value;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return query;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
static buildSearchQuery(searchTerm, searchFields = []) {
|
|
100
|
+
if (!searchTerm || searchFields.length === 0) return {};
|
|
101
|
+
|
|
102
|
+
return {
|
|
103
|
+
$or: searchFields.map(field => ({
|
|
104
|
+
[field]: { $regex: searchTerm, $options: 'i' }
|
|
105
|
+
}))
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export default PaginationHelper;
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Production Server Template
|
|
3
|
+
* Fully configured Express server with all middleware
|
|
4
|
+
* Generated by offbyt - Enterprise Ready
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import express from 'express';
|
|
8
|
+
import 'dotenv/config';
|
|
9
|
+
import dns from 'dns';
|
|
10
|
+
import cors from 'cors';
|
|
11
|
+
import compression from 'compression';
|
|
12
|
+
import helmet from 'helmet';
|
|
13
|
+
import rateLimit from 'express-rate-limit';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
|
|
16
|
+
// Import database
|
|
17
|
+
import { connectDatabase, disconnectDatabase, isDatabaseConnected } from './config/database.js';
|
|
18
|
+
|
|
19
|
+
// Import middleware
|
|
20
|
+
import requestLogger from './middleware/requestLogger.js';
|
|
21
|
+
import errorHandler from './middleware/errorHandler.js';
|
|
22
|
+
import { securityHeaders, sanitizeData } from './middleware/security.js';
|
|
23
|
+
import { globalLimiter, authLimiter } from './middleware/rateLimiter.js';
|
|
24
|
+
import { cacheMiddleware } from './middleware/cache.js';
|
|
25
|
+
|
|
26
|
+
// Import utilities
|
|
27
|
+
import { ResponseHelper, Logger } from './utils/helper.js';
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
const app = express();
|
|
31
|
+
const PORT = process.env.PORT || 5000;
|
|
32
|
+
const NODE_ENV = process.env.NODE_ENV || 'development';
|
|
33
|
+
const API_VERSION = 'v1';
|
|
34
|
+
|
|
35
|
+
// ============================================
|
|
36
|
+
// MIDDLEWARE SETUP
|
|
37
|
+
// ============================================
|
|
38
|
+
|
|
39
|
+
// Security Headers
|
|
40
|
+
app.use(securityHeaders);
|
|
41
|
+
|
|
42
|
+
// Body Parsing
|
|
43
|
+
app.use(express.json({ limit: '50mb' }));
|
|
44
|
+
app.use(express.urlencoded({ limit: '50mb', extended: true }));
|
|
45
|
+
|
|
46
|
+
// Compression
|
|
47
|
+
app.use(compression({
|
|
48
|
+
level: NODE_ENV === 'production' ? 6 : 3,
|
|
49
|
+
threshold: 1024
|
|
50
|
+
}));
|
|
51
|
+
|
|
52
|
+
// CORS Configuration
|
|
53
|
+
app.use(cors({
|
|
54
|
+
origin: process.env.CORS_ORIGIN?.split(',') || '*',
|
|
55
|
+
credentials: true,
|
|
56
|
+
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE', 'OPTIONS'],
|
|
57
|
+
allowedHeaders: ['Content-Type', 'Authorization'],
|
|
58
|
+
maxAge: 86400 // 24 hours
|
|
59
|
+
}));
|
|
60
|
+
|
|
61
|
+
// Rate Limiting
|
|
62
|
+
app.use(`/api/auth`, authLimiter);
|
|
63
|
+
app.use(`/api`, globalLimiter);
|
|
64
|
+
|
|
65
|
+
// Data Sanitization
|
|
66
|
+
app.use(sanitizeData);
|
|
67
|
+
|
|
68
|
+
// Request Logging
|
|
69
|
+
app.use(requestLogger);
|
|
70
|
+
|
|
71
|
+
// Cache Middleware (for GET requests)
|
|
72
|
+
app.use(cacheMiddleware(300)); // 5 minutes cache
|
|
73
|
+
|
|
74
|
+
// ============================================
|
|
75
|
+
// ROUTES SETUP
|
|
76
|
+
// ============================================
|
|
77
|
+
|
|
78
|
+
// Health Check & API Info
|
|
79
|
+
app.get('/health', (req, res) => {
|
|
80
|
+
res.status(200).json({
|
|
81
|
+
status: 'OK',
|
|
82
|
+
uptime: process.uptime(),
|
|
83
|
+
timestamp: new Date().toISOString(),
|
|
84
|
+
environment: NODE_ENV,
|
|
85
|
+
database: isDatabaseConnected() ? 'Connected' : 'Disconnected',
|
|
86
|
+
version: API_VERSION
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
app.get(`/api/health`, (req, res) => {
|
|
91
|
+
res.status(200).json({
|
|
92
|
+
status: 'OK',
|
|
93
|
+
uptime: process.uptime(),
|
|
94
|
+
timestamp: new Date().toISOString(),
|
|
95
|
+
environment: NODE_ENV,
|
|
96
|
+
database: isDatabaseConnected() ? 'Connected' : 'Disconnected',
|
|
97
|
+
version: API_VERSION
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
app.get(`/api`, (req, res) => {
|
|
102
|
+
res.status(200).json({
|
|
103
|
+
success: true,
|
|
104
|
+
message: 'Backend API Server',
|
|
105
|
+
version: API_VERSION,
|
|
106
|
+
environment: NODE_ENV,
|
|
107
|
+
endpoints: {
|
|
108
|
+
health: '/health',
|
|
109
|
+
documentation: `/api/docs`,
|
|
110
|
+
status: `/api/status`
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
app.get(`/api/status`, (req, res) => {
|
|
116
|
+
res.status(200).json({
|
|
117
|
+
success: true,
|
|
118
|
+
status: 'running',
|
|
119
|
+
database: isDatabaseConnected() ? 'connected' : 'disconnected',
|
|
120
|
+
memory: process.memoryUsage(),
|
|
121
|
+
uptime: process.uptime()
|
|
122
|
+
});
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
// Import all routes
|
|
126
|
+
// Routes will be auto-registered here
|
|
127
|
+
// __ROUTES__
|
|
128
|
+
|
|
129
|
+
// ============================================
|
|
130
|
+
// IMPORTANT: API URL Configuration
|
|
131
|
+
// ============================================
|
|
132
|
+
// Routes are registered with /api prefix (e.g., app.use(`/api/users`, usersRoutes))
|
|
133
|
+
// Frontend VITE_API_URL should be: http://localhost:5000 (WITHOUT /api)
|
|
134
|
+
// Frontend will then call: http://localhost:5000/api/users
|
|
135
|
+
//
|
|
136
|
+
// ⌠WRONG: VITE_API_URL = http://localhost:5000/api
|
|
137
|
+
// This would result in: http://localhost:5000/api/api/users (404!)
|
|
138
|
+
//
|
|
139
|
+
// ✅ CORRECT: VITE_API_URL = http://localhost:5000
|
|
140
|
+
// This results in: http://localhost:5000/api/users (✓ works!)
|
|
141
|
+
// ============================================
|
|
142
|
+
|
|
143
|
+
// ============================================
|
|
144
|
+
// 404 ERROR HANDLER
|
|
145
|
+
// ============================================
|
|
146
|
+
app.use((req, res) => {
|
|
147
|
+
res.status(404).json({
|
|
148
|
+
success: false,
|
|
149
|
+
message: 'Endpoint not found',
|
|
150
|
+
path: req.path,
|
|
151
|
+
method: req.method,
|
|
152
|
+
status: 404
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
// ============================================
|
|
157
|
+
// GLOBAL ERROR HANDLER
|
|
158
|
+
// ============================================
|
|
159
|
+
app.use(errorHandler);
|
|
160
|
+
|
|
161
|
+
// ============================================
|
|
162
|
+
// SERVER STARTUP
|
|
163
|
+
// ============================================
|
|
164
|
+
|
|
165
|
+
async function startServer() {
|
|
166
|
+
try {
|
|
167
|
+
// Connect to database
|
|
168
|
+
await connectDatabase();
|
|
169
|
+
|
|
170
|
+
// Start server
|
|
171
|
+
const server = app.listen(PORT, () => {
|
|
172
|
+
Logger.info('Server started successfully', {
|
|
173
|
+
port: PORT,
|
|
174
|
+
environment: NODE_ENV,
|
|
175
|
+
pid: process.pid
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
console.log(chalk.cyan('\n🚀 Backend Server Running\n'));
|
|
179
|
+
console.log(chalk.white(' URL: '), chalk.bold(`http://localhost:${PORT}`));
|
|
180
|
+
console.log(chalk.white(' API: '), chalk.bold(`http://localhost:${PORT}/api`));
|
|
181
|
+
console.log(chalk.white(' Health: '), chalk.bold(`http://localhost:${PORT}/health`));
|
|
182
|
+
console.log(chalk.white(' Status: '), chalk.bold(`http://localhost:${PORT}/api/status`));
|
|
183
|
+
console.log(chalk.white(' Env: '), chalk.bold(NODE_ENV));
|
|
184
|
+
console.log(chalk.gray('\nâ”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”\n'));
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
// Graceful shutdown
|
|
188
|
+
process.on('SIGINT', async () => {
|
|
189
|
+
console.log(chalk.yellow('\nâ›” Shutting down gracefully...\n'));
|
|
190
|
+
|
|
191
|
+
server.close(async () => {
|
|
192
|
+
await disconnectDatabase();
|
|
193
|
+
Logger.info('Server shut down successfully');
|
|
194
|
+
process.exit(0);
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
// Force shutdown after 10 seconds
|
|
198
|
+
setTimeout(() => {
|
|
199
|
+
console.error(chalk.red('Force shutting down...'));
|
|
200
|
+
process.exit(1);
|
|
201
|
+
}, 10000);
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
// Handle uncaught exceptions
|
|
205
|
+
process.on('uncaughtException', (error) => {
|
|
206
|
+
Logger.error('Uncaught Exception', error);
|
|
207
|
+
console.error(chalk.red('⌠Uncaught Exception:'), error.message);
|
|
208
|
+
process.exit(1);
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
// Handle unhandled promise rejections
|
|
212
|
+
process.on('unhandledRejection', (reason, promise) => {
|
|
213
|
+
Logger.error('Unhandled Rejection', reason);
|
|
214
|
+
console.error(chalk.red('⌠Unhandled Rejection:'), reason);
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
} catch (error) {
|
|
218
|
+
Logger.error('Server startup failed', error);
|
|
219
|
+
console.error(chalk.red('⌠Server startup failed:'), error.message);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// ============================================
|
|
225
|
+
// START APPLICATION
|
|
226
|
+
// ============================================
|
|
227
|
+
|
|
228
|
+
if (process.env.NODE_ENV !== 'test') {
|
|
229
|
+
startServer();
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export default app;
|
|
233
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Rate Limiter Middleware
|
|
3
|
+
* Prevents abuse by limiting requests from IP addresses
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import rateLimit from 'express-rate-limit';
|
|
7
|
+
|
|
8
|
+
// Global rate limiter
|
|
9
|
+
export const globalLimiter = rateLimit({
|
|
10
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
11
|
+
max: 100, // limit each IP to 100 requests per windowMs
|
|
12
|
+
message: 'Too many requests from this IP, please try again later.',
|
|
13
|
+
standardHeaders: true, // Return rate limit info in the `RateLimit-*` headers
|
|
14
|
+
legacyHeaders: false, // Disable the `X-RateLimit-*` headers
|
|
15
|
+
skip: (req) => process.env.NODE_ENV === 'development', // Skip in development
|
|
16
|
+
keyGenerator: (req) => req.ip || req.socket.remoteAddress // Use IP address as key
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Strict rate limiter for auth endpoints
|
|
20
|
+
export const authLimiter = rateLimit({
|
|
21
|
+
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
22
|
+
max: 5, // limit each IP to 5 requests per windowMs
|
|
23
|
+
message: 'Too many login attempts, please try again later.',
|
|
24
|
+
standardHeaders: true,
|
|
25
|
+
legacyHeaders: false,
|
|
26
|
+
skipSuccessfulRequests: true // Don't count successful requests
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
// API endpoint limiter
|
|
30
|
+
export const apiLimiter = rateLimit({
|
|
31
|
+
windowMs: 60 * 1000, // 1 minute
|
|
32
|
+
max: 30, // limit each IP to 30 requests per minute
|
|
33
|
+
message: 'Too many API requests, please try again later.'
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
export default globalLimiter;
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// Request Logger Middleware
|
|
2
|
+
const requestLogger = (req, res, next) => {
|
|
3
|
+
const start = Date.now();
|
|
4
|
+
|
|
5
|
+
// Log request
|
|
6
|
+
console.log(`📨 ${req.method} ${req.path}`);
|
|
7
|
+
|
|
8
|
+
// Override res.json to log response
|
|
9
|
+
const originalJson = res.json.bind(res);
|
|
10
|
+
res.json = function(data) {
|
|
11
|
+
const duration = Date.now() - start;
|
|
12
|
+
console.log(`✅ ${res.statusCode} ${req.method} ${req.path} [${duration}ms]`);
|
|
13
|
+
return originalJson(data);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
next();
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
export default requestLogger;
|