4bnode 4.1.2 → 4.1.3
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/add-security.js +0 -18
- package/app-1/.4bnode/.passkey +1 -0
- package/app-1/.4bnode/assets/fonts/NotoSans-Bold.woff2 +0 -0
- package/app-1/.4bnode/assets/fonts/NotoSans-Light.woff2 +0 -0
- package/app-1/.4bnode/assets/fonts/NotoSans-Medium.woff2 +0 -0
- package/app-1/.4bnode/assets/fonts/NotoSans-Regular.woff2 +0 -0
- package/app-1/.4bnode/assets/fonts/NotoSans-SemiBold.woff2 +0 -0
- package/app-1/.4bnode/assets/images/mongo.png +0 -0
- package/app-1/.4bnode/assets/js/App.js +139 -0
- package/app-1/.4bnode/assets/js/Root.js +40 -0
- package/app-1/.4bnode/assets/js/components/Modal.js +52 -0
- package/app-1/.4bnode/assets/js/components/Sidebar.js +73 -0
- package/app-1/.4bnode/assets/js/components/ToastContainer.js +15 -0
- package/app-1/.4bnode/assets/js/context.js +7 -0
- package/app-1/.4bnode/assets/js/lib.js +113 -0
- package/app-1/.4bnode/assets/js/main.js +5 -0
- package/app-1/.4bnode/assets/js/pages/AiBuilderPage.js +268 -0
- package/app-1/.4bnode/assets/js/pages/ApiTesterPage.js +1879 -0
- package/app-1/.4bnode/assets/js/pages/DashboardPage.js +435 -0
- package/app-1/.4bnode/assets/js/pages/DatabasePage.js +300 -0
- package/app-1/.4bnode/assets/js/pages/DocsPage.js +150 -0
- package/app-1/.4bnode/assets/js/pages/EnvironmentPage.js +103 -0
- package/app-1/.4bnode/assets/js/pages/LogsPage.js +205 -0
- package/app-1/.4bnode/assets/js/pages/MailPage.js +134 -0
- package/app-1/.4bnode/assets/js/pages/RealtimePage.js +186 -0
- package/app-1/.4bnode/assets/js/pages/RoutesPage.js +1436 -0
- package/app-1/.4bnode/assets/js/pages/SchemasPage.js +838 -0
- package/app-1/.4bnode/assets/js/pages/SecurityPage.js +74 -0
- package/app-1/.4bnode/assets/js/pages/SerialPortPage.js +170 -0
- package/app-1/.4bnode/assets/js/screens/PasskeyScreen.js +105 -0
- package/app-1/.4bnode/codegen.js +1249 -0
- package/app-1/.4bnode/dev-api.js +3621 -0
- package/app-1/.4bnode/ui.html +1578 -0
- package/app-1/README.md +47 -0
- package/app-1/ecosystem.config.cjs +25 -0
- package/app-1/index.js +145 -0
- package/app-1/package.json +35 -0
- package/app-1/public/index.html +292 -0
- package/app-1/src/db.js +13 -0
- package/app-1/src/docs.js +84 -0
- package/app-1/src/middleware/errorHandler.js +15 -0
- package/app-1/src/middleware/roles.js +14 -0
- package/app-1/src/middleware/validate.js +20 -0
- package/app-1/src/models/login.js +9 -0
- package/app-1/src/openapi.json +62 -0
- package/app-1/src/routes/products.js +26 -0
- package/app-1/src/validators/products.js +7 -0
- package/app-1/src/validators/user.js +7 -0
- package/index.js +2 -17
- package/lib/codegen.js +69 -43
- package/lib/codegen.test.js +21 -2
- package/package.json +1 -1
- package/skeleton/.4bnode/assets/js/App.js +0 -2
- package/skeleton/.4bnode/assets/js/components/Sidebar.js +2 -3
- package/skeleton/.4bnode/assets/js/lib.js +0 -3
- package/skeleton/.4bnode/assets/js/pages/DocsPage.js +29 -21
- package/skeleton/.4bnode/assets/js/pages/RoutesPage.js +12 -0
- package/skeleton/.4bnode/codegen.js +69 -43
- package/skeleton/.4bnode/dev-api.js +173 -87
- package/skeleton/_gitignore +0 -1
- package/skeleton/index.js +4 -8
- package/skeleton/.4bnode/assets/js/pages/ApiKeysPage.js +0 -164
package/app-1/README.md
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# node-skeleton-app
|
|
2
|
+
|
|
3
|
+
Express 5 + Dotenv + CORS app, scaffolded by [4bnode](https://github.com/pawan-4brains/4bnode).
|
|
4
|
+
|
|
5
|
+
## Getting started
|
|
6
|
+
|
|
7
|
+
### 1. Install dependencies
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
### 2. Configure environment
|
|
14
|
+
|
|
15
|
+
Edit `.env` in the project root and fill in the values your app needs:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
PORT=3000
|
|
19
|
+
# Add DATABASE_URL, API keys, etc. here
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
### 3. Run it
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
npm run dev
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The app starts at `http://localhost:PORT` and the dev dashboard at `http://localhost:PORT/_dev`.
|
|
29
|
+
|
|
30
|
+
## Project structure
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
.
|
|
34
|
+
├── index.js # Express app entry point
|
|
35
|
+
├── package.json
|
|
36
|
+
├── .env # Environment variables
|
|
37
|
+
├── .4bnode/ # Dashboard engine & data (managed by 4bnode)
|
|
38
|
+
├── public/ # Static files
|
|
39
|
+
└── src/
|
|
40
|
+
├── db.js # MongoDB connection (after `4bnode add mongo`)
|
|
41
|
+
├── models/ # Mongoose schemas
|
|
42
|
+
└── routes/ # API route files
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Extending with 4bnode
|
|
46
|
+
|
|
47
|
+
Run `4bnode add` inside this project to scaffold MongoDB, CRUD, login, sockets, and more. See the [4bnode README](https://github.com/pawan-4brains/4bnode) for the full feature list.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// PM2 process config for production.
|
|
2
|
+
//
|
|
3
|
+
// pm2 start ecosystem.config.cjs # start (NODE_ENV=production)
|
|
4
|
+
// pm2 save # remember across reboots
|
|
5
|
+
// pm2 startup # enable boot startup (run the printed command)
|
|
6
|
+
// pm2 logs / pm2 restart / pm2 status
|
|
7
|
+
//
|
|
8
|
+
// NODE_ENV=production keeps the /_dev dashboard OFF and security on. Other config
|
|
9
|
+
// (PORT, MONGO_URI, secrets) comes from your .env. CommonJS (.cjs) on purpose —
|
|
10
|
+
// the app itself is an ES module.
|
|
11
|
+
const pkg = require("./package.json");
|
|
12
|
+
|
|
13
|
+
module.exports = {
|
|
14
|
+
apps: [
|
|
15
|
+
{
|
|
16
|
+
name: pkg.name || "app",
|
|
17
|
+
script: "index.js",
|
|
18
|
+
instances: 1,
|
|
19
|
+
autorestart: true,
|
|
20
|
+
env: {
|
|
21
|
+
NODE_ENV: "production",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
],
|
|
25
|
+
};
|
package/app-1/index.js
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import dotenv from "dotenv";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs";
|
|
5
|
+
import cors from "cors";
|
|
6
|
+
import os from "os";
|
|
7
|
+
import helmet from "helmet";
|
|
8
|
+
import rateLimit from "express-rate-limit";
|
|
9
|
+
import { createInterface } from "readline";
|
|
10
|
+
import { notFound, errorHandler } from "./src/middleware/errorHandler.js";
|
|
11
|
+
import connectDB from './src/db.js';
|
|
12
|
+
import docsRouter from './src/docs.js';
|
|
13
|
+
import productsRouter from './src/routes/products.js';
|
|
14
|
+
|
|
15
|
+
// ── Environment ─────────────────────────────────────
|
|
16
|
+
dotenv.config();
|
|
17
|
+
|
|
18
|
+
connectDB();
|
|
19
|
+
const app = express();
|
|
20
|
+
// Behind reverse proxies (e.g. nginx → load balancer). Lets Express read the real
|
|
21
|
+
// client IP from X-Forwarded-For so rate limiting and req.ip are correct. The number
|
|
22
|
+
// is how many proxy hops to trust — adjust to your infrastructure.
|
|
23
|
+
app.set("trust proxy", 2);
|
|
24
|
+
const port = process.env.PORT || 3000;
|
|
25
|
+
const __dirname = path.resolve();
|
|
26
|
+
|
|
27
|
+
// ── Middleware ───────────────────────────────────────
|
|
28
|
+
app.use(express.json({ limit: "50mb" }));
|
|
29
|
+
app.use(express.urlencoded({ extended: true, limit: "50mb" }));
|
|
30
|
+
app.use(cors({ origin: "*" }));
|
|
31
|
+
|
|
32
|
+
// ── Dev Dashboard (local development only) ───────────
|
|
33
|
+
// The dashboard at /_dev can write files and run shell commands — it is a
|
|
34
|
+
// remote-code-execution surface and must NEVER be reachable in production.
|
|
35
|
+
// In production it is not even imported. It additionally restricts itself to
|
|
36
|
+
// loopback requests and requires a passkey (see .4bnode/dev-api.js).
|
|
37
|
+
// • production (NODE_ENV=production): OFF — set _4BNODE_DASHBOARD=on to force.
|
|
38
|
+
// • non-production: ON — set _4BNODE_DASHBOARD=off to disable.
|
|
39
|
+
const dashboardEnabled =
|
|
40
|
+
process.env._4BNODE_DASHBOARD === "on" ||
|
|
41
|
+
(process.env.NODE_ENV !== "production" && process.env._4BNODE_DASHBOARD !== "off");
|
|
42
|
+
if (dashboardEnabled) {
|
|
43
|
+
try {
|
|
44
|
+
const devApi = await import("./.4bnode/dev-api.js");
|
|
45
|
+
app.use("/_dev", devApi.default);
|
|
46
|
+
} catch {}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// ── Security (built-in by default) ───────────────────
|
|
50
|
+
// Mounted AFTER the /_dev dashboard so its CDN/inline scripts aren't blocked by
|
|
51
|
+
// helmet's CSP and the dashboard isn't throttled by the rate limiter.
|
|
52
|
+
app.use(helmet());
|
|
53
|
+
|
|
54
|
+
// ── Rate limiting ────────────────────────────────────
|
|
55
|
+
// Tune via .env: RATE_LIMIT_WINDOW_MS (ms) and RATE_LIMIT_MAX (per window).
|
|
56
|
+
const apiLimiter = rateLimit({
|
|
57
|
+
windowMs: Number(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000,
|
|
58
|
+
max: Number(process.env.RATE_LIMIT_MAX) || 100,
|
|
59
|
+
standardHeaders: true,
|
|
60
|
+
legacyHeaders: false,
|
|
61
|
+
});
|
|
62
|
+
app.use(apiLimiter);
|
|
63
|
+
|
|
64
|
+
// ── Static Files ────────────────────────────────────
|
|
65
|
+
app.use(express.static(path.join(__dirname, "public")));
|
|
66
|
+
|
|
67
|
+
// ── Helpers ─────────────────────────────────────────
|
|
68
|
+
function getLocalNetworkIp() {
|
|
69
|
+
const interfaces = os.networkInterfaces();
|
|
70
|
+
for (const name of Object.keys(interfaces)) {
|
|
71
|
+
for (const iface of interfaces[name]) {
|
|
72
|
+
if (iface.family === "IPv4" && !iface.internal) {
|
|
73
|
+
return iface.address;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
return null;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function updateEnvPort(newPort) {
|
|
81
|
+
try {
|
|
82
|
+
if (fs.existsSync(".env")) {
|
|
83
|
+
let content = fs.readFileSync(".env", "utf8");
|
|
84
|
+
if (content.match(/^PORT=.*/m)) {
|
|
85
|
+
content = content.replace(/^PORT=.*/m, `PORT=${newPort}`);
|
|
86
|
+
} else {
|
|
87
|
+
content += `\nPORT=${newPort}`;
|
|
88
|
+
}
|
|
89
|
+
fs.writeFileSync(".env", content, "utf8");
|
|
90
|
+
}
|
|
91
|
+
} catch {}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function askPort(busyPort) {
|
|
95
|
+
return new Promise((resolve) => {
|
|
96
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
97
|
+
console.log(`\nPort ${busyPort} is already in use.`);
|
|
98
|
+
rl.question(`Press Enter to use ${busyPort + 1}, or type a port number: `, (answer) => {
|
|
99
|
+
rl.close();
|
|
100
|
+
const custom = parseInt(answer.trim(), 10);
|
|
101
|
+
resolve(custom && custom > 0 ? custom : busyPort + 1);
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
app.use('/docs', docsRouter);
|
|
108
|
+
|
|
109
|
+
app.use('/api/products', productsRouter);
|
|
110
|
+
|
|
111
|
+
// ── Error handling (must be last) ──
|
|
112
|
+
app.use(notFound);
|
|
113
|
+
app.use(errorHandler);
|
|
114
|
+
|
|
115
|
+
// ── Start Server ────────────────────────────────────
|
|
116
|
+
async function startServer(tryPort) {
|
|
117
|
+
const listener = app.listen(tryPort);
|
|
118
|
+
listener.on("listening", () => {
|
|
119
|
+
const localIp = getLocalNetworkIp();
|
|
120
|
+
console.log(`App is running on http://localhost:${tryPort}`);
|
|
121
|
+
if (localIp) {
|
|
122
|
+
console.log(`App is also accessible on your local network at http://${localIp}:${tryPort}`);
|
|
123
|
+
}
|
|
124
|
+
if (tryPort !== Number(port)) {
|
|
125
|
+
updateEnvPort(tryPort);
|
|
126
|
+
console.log(`Updated .env with PORT=${tryPort}`);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
listener.on("error", async (err) => {
|
|
130
|
+
if (err.code === "EADDRINUSE") {
|
|
131
|
+
if (!process.stdin.isTTY) {
|
|
132
|
+
// Running under pm2 / systemd / IntraApp — no terminal to prompt.
|
|
133
|
+
console.error(`Port ${tryPort} is in use and no TTY is available. Exiting.`);
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
const nextPort = await askPort(tryPort);
|
|
137
|
+
startServer(nextPort);
|
|
138
|
+
} else {
|
|
139
|
+
console.error(err);
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
startServer(Number(port));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "app-1",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node index.js",
|
|
8
|
+
"dev": "nodemon index.js"
|
|
9
|
+
},
|
|
10
|
+
"nodemonConfig": {
|
|
11
|
+
"watch": [
|
|
12
|
+
"."
|
|
13
|
+
],
|
|
14
|
+
"ext": "js,mjs,cjs",
|
|
15
|
+
"ignore": [
|
|
16
|
+
"node_modules/",
|
|
17
|
+
"package.json",
|
|
18
|
+
"package-lock.json",
|
|
19
|
+
".4bnode/"
|
|
20
|
+
]
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"cors": "^2.8.6",
|
|
24
|
+
"dotenv": "^17.3.1",
|
|
25
|
+
"express": "^5.2.1",
|
|
26
|
+
"express-rate-limit": "^7.4.0",
|
|
27
|
+
"helmet": "^8.0.0",
|
|
28
|
+
"jsonwebtoken": "^9.0.3",
|
|
29
|
+
"mongoose": "^8.24.0",
|
|
30
|
+
"zod": "^3.23.0"
|
|
31
|
+
},
|
|
32
|
+
"devDependencies": {
|
|
33
|
+
"nodemon": "^3.1.14"
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>4bnode App</title>
|
|
7
|
+
<style>
|
|
8
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
9
|
+
|
|
10
|
+
@font-face { font-family: "Noto Sans"; src: url("/_dev/assets/fonts/NotoSans-Light.woff2") format("woff2"); font-weight: 300; font-style: normal; font-display: swap; }
|
|
11
|
+
@font-face { font-family: "Noto Sans"; src: url("/_dev/assets/fonts/NotoSans-Regular.woff2") format("woff2"); font-weight: 400; font-style: normal; font-display: swap; }
|
|
12
|
+
@font-face { font-family: "Noto Sans"; src: url("/_dev/assets/fonts/NotoSans-Medium.woff2") format("woff2"); font-weight: 500; font-style: normal; font-display: swap; }
|
|
13
|
+
@font-face { font-family: "Noto Sans"; src: url("/_dev/assets/fonts/NotoSans-SemiBold.woff2") format("woff2"); font-weight: 600; font-style: normal; font-display: swap; }
|
|
14
|
+
@font-face { font-family: "Noto Sans"; src: url("/_dev/assets/fonts/NotoSans-Bold.woff2") format("woff2"); font-weight: 700; font-style: normal; font-display: swap; }
|
|
15
|
+
|
|
16
|
+
body {
|
|
17
|
+
font-family: "Noto Sans", -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
18
|
+
background: #0a0b10;
|
|
19
|
+
color: #e2e8f0;
|
|
20
|
+
min-height: 100vh;
|
|
21
|
+
display: flex;
|
|
22
|
+
align-items: center;
|
|
23
|
+
justify-content: center;
|
|
24
|
+
overflow: hidden;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
.scene {
|
|
28
|
+
position: fixed;
|
|
29
|
+
inset: 0;
|
|
30
|
+
z-index: 0;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/* Animated grid floor */
|
|
34
|
+
.grid {
|
|
35
|
+
position: absolute;
|
|
36
|
+
bottom: 0;
|
|
37
|
+
left: 50%;
|
|
38
|
+
transform: translateX(-50%) perspective(600px) rotateX(55deg);
|
|
39
|
+
width: 200%;
|
|
40
|
+
height: 70vh;
|
|
41
|
+
background-image:
|
|
42
|
+
linear-gradient(rgba(0,114,255,0.08) 1px, transparent 1px),
|
|
43
|
+
linear-gradient(90deg, rgba(0,114,255,0.08) 1px, transparent 1px);
|
|
44
|
+
background-size: 60px 60px;
|
|
45
|
+
animation: gridScroll 12s linear infinite;
|
|
46
|
+
mask-image: linear-gradient(to top, rgba(0,0,0,0.4) 0%, transparent 80%);
|
|
47
|
+
-webkit-mask-image: linear-gradient(to top, rgba(0,0,0,0.4) 0%, transparent 80%);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
@keyframes gridScroll {
|
|
51
|
+
0% { background-position: 0 0; }
|
|
52
|
+
100% { background-position: 0 60px; }
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* Floating orbs */
|
|
56
|
+
.orb {
|
|
57
|
+
position: absolute;
|
|
58
|
+
border-radius: 50%;
|
|
59
|
+
filter: blur(80px);
|
|
60
|
+
opacity: 0.35;
|
|
61
|
+
animation: float 8s ease-in-out infinite;
|
|
62
|
+
}
|
|
63
|
+
.orb-1 {
|
|
64
|
+
width: 400px; height: 400px;
|
|
65
|
+
background: radial-gradient(circle, #0072ff, transparent 70%);
|
|
66
|
+
top: 10%; left: 15%;
|
|
67
|
+
animation-delay: 0s;
|
|
68
|
+
}
|
|
69
|
+
.orb-2 {
|
|
70
|
+
width: 350px; height: 350px;
|
|
71
|
+
background: radial-gradient(circle, #7b2ff7, transparent 70%);
|
|
72
|
+
top: 30%; right: 10%;
|
|
73
|
+
animation-delay: -3s;
|
|
74
|
+
animation-duration: 10s;
|
|
75
|
+
}
|
|
76
|
+
.orb-3 {
|
|
77
|
+
width: 300px; height: 300px;
|
|
78
|
+
background: radial-gradient(circle, #00c6ff, transparent 70%);
|
|
79
|
+
bottom: 15%; left: 40%;
|
|
80
|
+
animation-delay: -5s;
|
|
81
|
+
animation-duration: 12s;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
@keyframes float {
|
|
85
|
+
0%, 100% { transform: translate(0, 0) scale(1); }
|
|
86
|
+
33% { transform: translate(30px, -20px) scale(1.05); }
|
|
87
|
+
66% { transform: translate(-20px, 15px) scale(0.95); }
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* Content */
|
|
91
|
+
.container {
|
|
92
|
+
position: relative;
|
|
93
|
+
z-index: 1;
|
|
94
|
+
text-align: center;
|
|
95
|
+
padding: 40px;
|
|
96
|
+
animation: fadeUp 1s ease-out;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
@keyframes fadeUp {
|
|
100
|
+
from { opacity: 0; transform: translateY(30px); }
|
|
101
|
+
to { opacity: 1; transform: translateY(0); }
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/* Animated node SVG */
|
|
105
|
+
.node-graphic {
|
|
106
|
+
width: 120px;
|
|
107
|
+
height: 120px;
|
|
108
|
+
margin: 0 auto 32px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.node-ring {
|
|
112
|
+
fill: none;
|
|
113
|
+
stroke: url(#ringGrad);
|
|
114
|
+
stroke-width: 1.5;
|
|
115
|
+
stroke-dasharray: 8 6;
|
|
116
|
+
transform-origin: 60px 60px;
|
|
117
|
+
animation: spin 20s linear infinite;
|
|
118
|
+
}
|
|
119
|
+
.node-ring-2 {
|
|
120
|
+
animation: spin 15s linear infinite reverse;
|
|
121
|
+
stroke-dasharray: 12 8;
|
|
122
|
+
}
|
|
123
|
+
.node-ring-3 {
|
|
124
|
+
animation: spin 25s linear infinite;
|
|
125
|
+
stroke-dasharray: 4 10;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
@keyframes spin {
|
|
129
|
+
to { transform: rotate(360deg); }
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
.node-dot {
|
|
133
|
+
fill: #0072ff;
|
|
134
|
+
animation: pulse 2s ease-in-out infinite;
|
|
135
|
+
}
|
|
136
|
+
.node-dot:nth-child(2) { animation-delay: 0.3s; fill: #7b2ff7; }
|
|
137
|
+
.node-dot:nth-child(3) { animation-delay: 0.6s; fill: #00c6ff; }
|
|
138
|
+
.node-dot:nth-child(4) { animation-delay: 0.9s; fill: #0072ff; }
|
|
139
|
+
.node-dot:nth-child(5) { animation-delay: 1.2s; fill: #7b2ff7; }
|
|
140
|
+
.node-dot:nth-child(6) { animation-delay: 1.5s; fill: #00c6ff; }
|
|
141
|
+
|
|
142
|
+
@keyframes pulse {
|
|
143
|
+
0%, 100% { opacity: 0.5; r: 3; }
|
|
144
|
+
50% { opacity: 1; r: 5; }
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.node-line {
|
|
148
|
+
stroke: url(#lineGrad);
|
|
149
|
+
stroke-width: 0.8;
|
|
150
|
+
opacity: 0.4;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
.node-center {
|
|
154
|
+
fill: url(#centerGrad);
|
|
155
|
+
filter: drop-shadow(0 0 8px rgba(0,114,255,0.4));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
h1 {
|
|
159
|
+
font-size: 36px;
|
|
160
|
+
font-weight: 800;
|
|
161
|
+
letter-spacing: -0.5px;
|
|
162
|
+
margin-bottom: 12px;
|
|
163
|
+
background: linear-gradient(135deg, #ffffff 0%, #94a3b8 100%);
|
|
164
|
+
-webkit-background-clip: text;
|
|
165
|
+
-webkit-text-fill-color: transparent;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
.tagline {
|
|
169
|
+
font-size: 15px;
|
|
170
|
+
color: #64748b;
|
|
171
|
+
font-weight: 400;
|
|
172
|
+
letter-spacing: 0.3px;
|
|
173
|
+
margin-bottom: 32px;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
.status {
|
|
177
|
+
display: inline-flex;
|
|
178
|
+
align-items: center;
|
|
179
|
+
gap: 8px;
|
|
180
|
+
padding: 8px 20px;
|
|
181
|
+
border-radius: 100px;
|
|
182
|
+
background: rgba(16, 185, 129, 0.08);
|
|
183
|
+
border: 1px solid rgba(16, 185, 129, 0.2);
|
|
184
|
+
font-size: 13px;
|
|
185
|
+
color: #10b981;
|
|
186
|
+
font-weight: 500;
|
|
187
|
+
animation: fadeUp 1s ease-out 0.3s both;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
.status-dot {
|
|
191
|
+
width: 8px;
|
|
192
|
+
height: 8px;
|
|
193
|
+
border-radius: 50%;
|
|
194
|
+
background: #10b981;
|
|
195
|
+
animation: blink 2s ease-in-out infinite;
|
|
196
|
+
box-shadow: 0 0 8px rgba(16, 185, 129, 0.5);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
@keyframes blink {
|
|
200
|
+
0%, 100% { opacity: 1; }
|
|
201
|
+
50% { opacity: 0.4; }
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
.powered {
|
|
205
|
+
position: fixed;
|
|
206
|
+
bottom: 28px;
|
|
207
|
+
left: 0;
|
|
208
|
+
right: 0;
|
|
209
|
+
text-align: center;
|
|
210
|
+
font-size: 12px;
|
|
211
|
+
color: #334155;
|
|
212
|
+
letter-spacing: 0.3px;
|
|
213
|
+
animation: fadeUp 1s ease-out 0.6s both;
|
|
214
|
+
}
|
|
215
|
+
.powered a {
|
|
216
|
+
text-decoration: none;
|
|
217
|
+
}
|
|
218
|
+
.powered span {
|
|
219
|
+
font-weight: 600;
|
|
220
|
+
background: linear-gradient(135deg, #00c6ff, #7b2ff7);
|
|
221
|
+
-webkit-background-clip: text;
|
|
222
|
+
-webkit-text-fill-color: transparent;
|
|
223
|
+
transition: opacity 0.2s;
|
|
224
|
+
}
|
|
225
|
+
.powered a:hover span {
|
|
226
|
+
opacity: 0.75;
|
|
227
|
+
}
|
|
228
|
+
</style>
|
|
229
|
+
</head>
|
|
230
|
+
<body>
|
|
231
|
+
|
|
232
|
+
<div class="scene">
|
|
233
|
+
<div class="orb orb-1"></div>
|
|
234
|
+
<div class="orb orb-2"></div>
|
|
235
|
+
<div class="orb orb-3"></div>
|
|
236
|
+
<div class="grid"></div>
|
|
237
|
+
</div>
|
|
238
|
+
|
|
239
|
+
<div class="container">
|
|
240
|
+
<svg class="node-graphic" viewBox="0 0 120 120">
|
|
241
|
+
<defs>
|
|
242
|
+
<linearGradient id="ringGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
243
|
+
<stop offset="0%" stop-color="#0072ff" />
|
|
244
|
+
<stop offset="100%" stop-color="#7b2ff7" />
|
|
245
|
+
</linearGradient>
|
|
246
|
+
<linearGradient id="lineGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
247
|
+
<stop offset="0%" stop-color="#0072ff" stop-opacity="0.6" />
|
|
248
|
+
<stop offset="100%" stop-color="#7b2ff7" stop-opacity="0.2" />
|
|
249
|
+
</linearGradient>
|
|
250
|
+
<radialGradient id="centerGrad" cx="50%" cy="50%" r="50%">
|
|
251
|
+
<stop offset="0%" stop-color="#0072ff" />
|
|
252
|
+
<stop offset="100%" stop-color="#7b2ff7" />
|
|
253
|
+
</radialGradient>
|
|
254
|
+
</defs>
|
|
255
|
+
|
|
256
|
+
<!-- Connection lines -->
|
|
257
|
+
<line class="node-line" x1="60" y1="60" x2="60" y2="14" />
|
|
258
|
+
<line class="node-line" x1="60" y1="60" x2="100" y2="37" />
|
|
259
|
+
<line class="node-line" x1="60" y1="60" x2="100" y2="83" />
|
|
260
|
+
<line class="node-line" x1="60" y1="60" x2="60" y2="106" />
|
|
261
|
+
<line class="node-line" x1="60" y1="60" x2="20" y2="83" />
|
|
262
|
+
<line class="node-line" x1="60" y1="60" x2="20" y2="37" />
|
|
263
|
+
|
|
264
|
+
<!-- Rotating rings -->
|
|
265
|
+
<circle class="node-ring" cx="60" cy="60" r="50" />
|
|
266
|
+
<circle class="node-ring node-ring-2" cx="60" cy="60" r="38" />
|
|
267
|
+
<circle class="node-ring node-ring-3" cx="60" cy="60" r="26" />
|
|
268
|
+
|
|
269
|
+
<!-- Orbiting dots -->
|
|
270
|
+
<circle class="node-dot" cx="60" cy="14" r="4" />
|
|
271
|
+
<circle class="node-dot" cx="100" cy="37" r="4" />
|
|
272
|
+
<circle class="node-dot" cx="100" cy="83" r="4" />
|
|
273
|
+
<circle class="node-dot" cx="60" cy="106" r="4" />
|
|
274
|
+
<circle class="node-dot" cx="20" cy="83" r="4" />
|
|
275
|
+
<circle class="node-dot" cx="20" cy="37" r="4" />
|
|
276
|
+
|
|
277
|
+
<!-- Center node -->
|
|
278
|
+
<circle class="node-center" cx="60" cy="60" r="8" />
|
|
279
|
+
</svg>
|
|
280
|
+
|
|
281
|
+
<h1>Your app is running</h1>
|
|
282
|
+
<p class="tagline">Server is live and ready to accept requests</p>
|
|
283
|
+
<div class="status">
|
|
284
|
+
<span class="status-dot"></span>
|
|
285
|
+
All systems operational
|
|
286
|
+
</div>
|
|
287
|
+
</div>
|
|
288
|
+
|
|
289
|
+
<div class="powered">Powered by <a href="https://www.npmjs.com/package/4bnode" target="_blank" rel="noopener noreferrer"><span>4bnode</span></a></div>
|
|
290
|
+
|
|
291
|
+
</body>
|
|
292
|
+
</html>
|
package/app-1/src/db.js
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import mongoose from 'mongoose';
|
|
2
|
+
|
|
3
|
+
const connectDB = async () => {
|
|
4
|
+
try {
|
|
5
|
+
const conn = await mongoose.connect(process.env.MONGO_URI);
|
|
6
|
+
console.log(`MongoDB connected: ${conn.connection.host}`);
|
|
7
|
+
} catch (err) {
|
|
8
|
+
console.error('MongoDB connection error:', err.message);
|
|
9
|
+
console.error('Check your MONGO_URI in .env and ensure MongoDB is running.');
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
export default connectDB;
|