create-rebe 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 (100) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +54 -0
  3. package/bin/create-rebe.js +310 -0
  4. package/package.json +40 -0
  5. package/template/.env.example +141 -0
  6. package/template/.gitattributes +16 -0
  7. package/template/.nvmrc +1 -0
  8. package/template/.prettierignore +10 -0
  9. package/template/.prettierrc +12 -0
  10. package/template/LICENSE +21 -0
  11. package/template/README.md +150 -0
  12. package/template/SECURITY.md +133 -0
  13. package/template/app/hooks/register.hook.js +22 -0
  14. package/template/app/http/controllers/auth.controller.js +34 -0
  15. package/template/app/http/middlewares/auth.middleware.js +25 -0
  16. package/template/app/http/middlewares/register.middleware.js +18 -0
  17. package/template/app/http/validators/auth.validator.js +8 -0
  18. package/template/app/jobs/register.job.js +17 -0
  19. package/template/app/queue/register.queue.js +11 -0
  20. package/template/app/routes/api.route.js +18 -0
  21. package/template/app/routes/register.route.js +8 -0
  22. package/template/app/routes/web.route.js +7 -0
  23. package/template/app/socket/register.socket.js +15 -0
  24. package/template/config/app.config.js +12 -0
  25. package/template/config/bcrypt.config.js +7 -0
  26. package/template/config/cache.config.js +7 -0
  27. package/template/config/cors.config.js +37 -0
  28. package/template/config/cron.config.js +9 -0
  29. package/template/config/database.config.js +51 -0
  30. package/template/config/express.config.js +37 -0
  31. package/template/config/index.js +19 -0
  32. package/template/config/jwt.config.js +10 -0
  33. package/template/config/logger.config.js +10 -0
  34. package/template/config/mail.config.js +14 -0
  35. package/template/config/queue.config.js +11 -0
  36. package/template/config/redis.config.js +32 -0
  37. package/template/config/runtime.config.js +11 -0
  38. package/template/config/socket.config.js +15 -0
  39. package/template/config/storage.config.js +10 -0
  40. package/template/core/bootstrap.core.js +92 -0
  41. package/template/core/common/array.js +144 -0
  42. package/template/core/common/cache.js +100 -0
  43. package/template/core/common/collection.js +173 -0
  44. package/template/core/common/crypt.js +69 -0
  45. package/template/core/common/date.js +254 -0
  46. package/template/core/common/hash.js +61 -0
  47. package/template/core/common/object.js +155 -0
  48. package/template/core/common/path.js +80 -0
  49. package/template/core/common/storage.js +97 -0
  50. package/template/core/common/string.js +137 -0
  51. package/template/core/common/url.js +81 -0
  52. package/template/core/common.core.js +93 -0
  53. package/template/core/cron.core.js +141 -0
  54. package/template/core/database.core.js +113 -0
  55. package/template/core/error.core.js +83 -0
  56. package/template/core/express.core.js +161 -0
  57. package/template/core/hooks.core.js +47 -0
  58. package/template/core/jwt.core.js +81 -0
  59. package/template/core/logger.core.js +100 -0
  60. package/template/core/mailer.core.js +65 -0
  61. package/template/core/queue.core.js +226 -0
  62. package/template/core/redis.core.js +75 -0
  63. package/template/core/register.core.js +91 -0
  64. package/template/core/runtime.core.js +27 -0
  65. package/template/core/socket.core.js +93 -0
  66. package/template/core/validator.core.js +34 -0
  67. package/template/database/.gitkeep +0 -0
  68. package/template/database/models/post.model.js +26 -0
  69. package/template/database/models/user.model.js +30 -0
  70. package/template/docs/Bootstrap.md +50 -0
  71. package/template/docs/Common.md +48 -0
  72. package/template/docs/Cron.md +47 -0
  73. package/template/docs/Database.md +61 -0
  74. package/template/docs/Express.md +63 -0
  75. package/template/docs/Hooks.md +48 -0
  76. package/template/docs/Jwt.md +63 -0
  77. package/template/docs/Logger.md +60 -0
  78. package/template/docs/Mailer.md +50 -0
  79. package/template/docs/Queue.md +57 -0
  80. package/template/docs/README.md +32 -0
  81. package/template/docs/Redis.md +54 -0
  82. package/template/docs/Register.md +52 -0
  83. package/template/docs/Socket.md +55 -0
  84. package/template/docs/Validator.md +45 -0
  85. package/template/ecosystem.config.js +54 -0
  86. package/template/index.js +6 -0
  87. package/template/jest.config.js +27 -0
  88. package/template/jsconfig.json +37 -0
  89. package/template/logs/.gitkeep +0 -0
  90. package/template/nodemon.json +23 -0
  91. package/template/package-lock.json +7033 -0
  92. package/template/package.json +82 -0
  93. package/template/scripts/cli.js +258 -0
  94. package/template/storage/.gitkeep +0 -0
  95. package/template/tests/common.test.js +45 -0
  96. package/template/tests/crypt-hash-storage.test.js +44 -0
  97. package/template/tests/jwt.test.js +45 -0
  98. package/template/tests/register.test.js +55 -0
  99. package/template/tests/setup.js +11 -0
  100. package/template/tests/validator.test.js +65 -0
@@ -0,0 +1,150 @@
1
+ # rebe
2
+
3
+ Ready-to-use Node.js backend framework. One process serves a JSON API, Socket.IO,
4
+ cron jobs, and an in-process queue on a clean four-layer architecture
5
+ (config → core → app) with centralized configuration.
6
+
7
+ ## Create a project
8
+
9
+ ```bash
10
+ npm create rebe@latest my-api
11
+ cd my-api
12
+ npm install
13
+ npm run dev
14
+ ```
15
+
16
+ Flags (all optional): `--name <name>`, `--pm <npm|pnpm|yarn|bun>`, `--install`,
17
+ `--git`, `--no-env`, `--force`, `-y`. Unless `--no-env` is set, a `.env` is generated
18
+ with `APP_NAME` set to the project name and fresh `APP_KEY` / JWT secrets.
19
+
20
+ ## What's inside
21
+
22
+ - **HTTP** — Express 5 + Laravel-style routing, helmet, CORS, compression, rate
23
+ limiting, body limits, multipart uploads.
24
+ - **Realtime** — Socket.IO on the same port, optional Redis adapter.
25
+ - **Background** — node-cron scheduler and an in-process job queue (optional DB
26
+ persistence).
27
+ - **Data** — Sequelize (MySQL/MariaDB/Postgres/MSSQL/SQLite), enabled on demand.
28
+ - **Auth** — JWT (access + refresh, HS256-pinned), bcrypt.
29
+ - **Ops** — Winston logging with daily rotation, lifecycle hooks, graceful shutdown,
30
+ PM2 config.
31
+ - **Optional Redis** — standalone; each feature opts in and falls back cleanly.
32
+
33
+ ## Manual setup (from a clone)
34
+
35
+ ```bash
36
+ npm install
37
+ npm run cli -- setup # copy .env, refresh deps, generate keys
38
+ npm run dev # nodemon, development
39
+ ```
40
+
41
+ `setup` copies `.env.example` to `.env` and generates `APP_KEY`, `JWT_SECRET`, and
42
+ `JWT_REFRESH_SECRET`. Generate keys individually with `npm run cli -- key:generate`
43
+ and `npm run cli -- key:jwt [--refresh]`.
44
+
45
+ ## Development vs production
46
+
47
+ | | Development | Production |
48
+ | --------------- | -------------------------------- | ------------------------------------------- |
49
+ | Start | `npm run dev` (nodemon) | `npm start` (`node .`) or PM2 |
50
+ | `APP_ENV` | `development` | `production` |
51
+ | Logs | colored console + rotating files | console filtered, rotating files in `logs/` |
52
+ | Error responses | include stack | stack hidden |
53
+
54
+ Production with PM2 (single fork instance):
55
+
56
+ ```bash
57
+ pm2 start ecosystem.config.js
58
+ pm2 logs <APP_NAME>
59
+ ```
60
+
61
+ > Run a **single** instance (`exec_mode: 'fork', instances: 1`). State is kept
62
+ > in-process (Socket.IO rooms, the in-process queue, node-cron schedules). Cluster
63
+ > mode would duplicate cron ticks, split socket rooms, and break queue state. For
64
+ > horizontal scale, enable Redis (`REDIS_USE_SOCKET`/`REDIS_USE_QUEUE`) first.
65
+
66
+ ## Architecture
67
+
68
+ Four layers with a strict, one-directional dependency arrow:
69
+
70
+ ```
71
+ Entry (index.js -> bootstrap.core)
72
+ -> Application (app/: http, routes, jobs, queue, socket, hooks; database/ models)
73
+ -> Core (core/: infrastructure, static classes, no business logic)
74
+ -> Config (config/: reads .env via Common.getEnv*, exposes `config`)
75
+ -> .env
76
+ ```
77
+
78
+ The core layer never imports application code statically. Each core loads a single
79
+ application entry point at runtime (`register.*.js`) through the hardened register
80
+ loader — Inversion of Control keeps the arrow pointing one way.
81
+
82
+ ## Project structure
83
+
84
+ ```
85
+ .
86
+ ├── config/ # Centralized config (one file per domain) -> @config
87
+ ├── core/ # Core layer (static classes) -> @core
88
+ │ ├── common.core.js + common/ # Shared utils + env readers
89
+ │ ├── runtime · logger · error · register
90
+ │ ├── database · cron · queue
91
+ │ ├── jwt · mailer · hooks · redis
92
+ │ ├── express · socket · validator
93
+ │ └── bootstrap.core.js # Boot orchestrator
94
+ ├── app/ # Application layer -> @app
95
+ │ ├── http/controllers|middlewares|validators
96
+ │ ├── routes/ # web.route, api.route, register.route
97
+ │ ├── jobs/register.job.js # (Cron) => {}
98
+ │ ├── queue/register.queue.js # (Queue) => {}
99
+ │ ├── socket/register.socket.js # (io, Socket) => {}
100
+ │ └── hooks/register.hook.js # { before, after, shutdown }
101
+ ├── database/ # Sequelize models -> @database
102
+ ├── storage/ # Local file storage / uploads -> @storage
103
+ ├── tests/ # Jest test suite
104
+ ├── create-rebe/ # The `npm create rebe` scaffolder package
105
+ ├── docs/ # Per-core API reference
106
+ ├── scripts/cli.js # Project CLI (npm run cli)
107
+ ├── index.js # Entry: dotenv -> module-alias -> runtime -> Bootstrap.run()
108
+ └── ecosystem.config.js # PM2 (fork, single instance)
109
+ ```
110
+
111
+ ## Boot flow
112
+
113
+ ```
114
+ index.js -> dotenv -> module-alias -> runtime.core -> Bootstrap.run()
115
+ 1. error handlers 2. Hooks.before 3. Redis.connect (if enabled)
116
+ 4. Database.connect (if enabled) 5. Express.create -> { app, server }
117
+ 6. Socket.attach 7. Cron + Queue 8. Express.listen 9. Hooks.after
118
+ 10. SIGINT/SIGTERM -> graceful shutdown
119
+ ```
120
+
121
+ ## Response envelope
122
+
123
+ There is no `response.core`. Every API handler returns a manual envelope:
124
+
125
+ ```js
126
+ res.json({ status: true, code: 200, message: 'OK', data: null, meta: null })
127
+ ```
128
+
129
+ Validation failures add an `errors` array (422). Unhandled errors and 404s are
130
+ formatted by `error.core` (JSON or HTML).
131
+
132
+ ## Documentation
133
+
134
+ - [docs/](./docs/README.md) — per-core API reference (one file per core).
135
+ - [SECURITY.md](./SECURITY.md) — security model, hardening checklist, audit findings.
136
+ - `.env.example` — every configuration variable with its default.
137
+
138
+ ## Scripts
139
+
140
+ ```bash
141
+ npm run dev # nodemon (development)
142
+ npm start # node . (production)
143
+ npm test # jest
144
+ npm run format # prettier --write .
145
+ npm run cli -- help
146
+ ```
147
+
148
+ ## License
149
+
150
+ MIT
@@ -0,0 +1,133 @@
1
+ # SECURITY
2
+
3
+ Security model, audit findings, and production hardening guidance for `rebe`.
4
+
5
+ This document records a code- and dependency-level audit of the framework. Findings
6
+ are rated Critical / High / Medium / Low / Info and mapped to CWE. Items marked
7
+ **Fixed** are already addressed in the codebase; **Guidance** items are safe defaults
8
+ that you should review against your threat model before deploying.
9
+
10
+ ## Audit summary
11
+
12
+ | ID | Severity | CWE | Issue | Status |
13
+ | ---- | -------- | -------- | -------------------------------------------------------------------------------------------------- | ------------------- |
14
+ | F-01 | Medium | CWE-79 | Reflected XSS in error/404 HTML pages | Fixed |
15
+ | F-02 | Medium | CWE-942 | CORS wildcard origin combined with credentials | Fixed |
16
+ | F-03 | Medium | CWE-440 | `common/*` utils required a non-existent module (`@app/config`), crashing Crypt/Hash/Storage/Cache | Fixed |
17
+ | F-04 | Low | CWE-1395 | Transitive `uuid` bounds-check advisory via `sequelize` | Accepted / monitor |
18
+ | F-05 | Medium | CWE-434 | File-type enforcement relies on client MIME; active content (SVG/HTML) could be stored XSS | Guidance |
19
+ | F-06 | Info | CWE-345 | JWT algorithm confusion | Mitigated by design |
20
+ | F-07 | Low | CWE-400 | Large default JSON/body limit (10 MB) | Guidance |
21
+ | F-08 | Info | CWE-352 | No CSRF protection | N/A for bearer API |
22
+ | F-09 | Low | CWE-798 | Demo credentials in sample code; secrets in local `.env` | Guidance |
23
+ | F-10 | Info | CWE-770 | Rate limit / trust-proxy defaults | Guidance |
24
+
25
+ ## Findings
26
+
27
+ ### F-01 — Reflected XSS in error pages (Fixed)
28
+
29
+ `error.core` rendered `req.originalUrl` and error messages directly into HTML 404/500
30
+ responses. A crafted URL could inject markup. The CSP (`default-src 'self'`) blocks
31
+ inline script execution, but the values are now HTML-escaped (`Common.Str.escapeHtml`)
32
+ as defense in depth. JSON responses were never affected.
33
+
34
+ ### F-02 — CORS wildcard + credentials (Fixed)
35
+
36
+ The previous config used `origin: ['*']` with `credentials: true`. Reflecting any
37
+ origin with credentials enabled is unsafe, and browsers reject the combination.
38
+ `cors.config.js` now reads `CORS_ORIGIN` / `SOCKET_CORS_ORIGIN` as an allowlist and
39
+ **forces credentials off** whenever the origin is `*`. Set explicit origins in
40
+ production to allow credentialed cross-origin requests.
41
+
42
+ ### F-03 — Broken module reference in shared utils (Fixed)
43
+
44
+ `crypt.js`, `hash.js`, `storage.js`, and `cache.js` required `@app/config`, which does
45
+ not resolve (`@app` → the application folder; config lives at `@config`). Any use of
46
+ `Common.Crypt`, `Common.Hash`, `Common.Storage`, or the config-default path of
47
+ `Common.Cache` would throw `MODULE_NOT_FOUND`. All references now use `@config/index`.
48
+ This is primarily a reliability bug, but a crash in a crypto/hash helper invoked on a
49
+ request path is a denial-of-service vector.
50
+
51
+ ### F-04 — Transitive uuid advisory (Accepted / monitor)
52
+
53
+ `npm audit` reports a moderate advisory (GHSA-w5hq-g745-h8pq) for `uuid` pulled in by
54
+ `sequelize`. The only automated fix downgrades `sequelize` to a 3.x release (a major
55
+ breaking change), so it is not applied. Real-world impact here is low: the affected
56
+ code path requires an attacker-controlled `buf` argument, which the framework never
57
+ passes, and the database is disabled by default. Re-run `npm audit` periodically and
58
+ bump `sequelize` once an upstream fix ships.
59
+
60
+ ### F-05 — File upload type enforcement (Guidance)
61
+
62
+ `express.core.upload()` filters by `file.mimetype`, which the client controls, and by
63
+ `STORAGE_UPLOAD_ALLOWED_TYPES`. The default allowlist (`image/jpeg,image/png,
64
+ image/webp,application/pdf`) is safe, and `/uploads` is served with
65
+ `X-Content-Type-Options: nosniff`. If you broaden the allowlist:
66
+
67
+ - Do **not** allow `image/svg+xml` or `text/html` for files served back to browsers —
68
+ they can carry script (stored XSS). Serve such files with
69
+ `Content-Disposition: attachment`, from a separate origin, or convert/sanitize them.
70
+ - Consider validating magic bytes, not just the MIME header.
71
+
72
+ ### F-06 — JWT algorithm (Mitigated by design)
73
+
74
+ `jwt.core` pins HS256 on both sign and verify, so forged `alg: none` or RS/HS-swap
75
+ tokens are rejected. Keep access-token TTLs short and store only non-sensitive claims
76
+ (the payload is signed, not encrypted).
77
+
78
+ ### F-07 — Body size limit (Guidance)
79
+
80
+ `EXPRESS_BODY_LIMIT` defaults to `10mb`. For pure-JSON APIs, lower it (e.g. `256kb`)
81
+ to reduce the memory-exhaustion surface; raise it only for endpoints that genuinely
82
+ need large payloads.
83
+
84
+ ### F-08 — CSRF (N/A for bearer auth)
85
+
86
+ The sample auth uses `Authorization: Bearer` tokens, which are not sent automatically
87
+ by browsers, so CSRF does not apply. If you switch to cookie-based sessions, add CSRF
88
+ protection (e.g. double-submit token) and set cookies `HttpOnly`, `Secure`,
89
+ `SameSite`.
90
+
91
+ ### F-09 — Demo credentials and local secrets (Guidance)
92
+
93
+ `auth.controller.js` ships an in-memory demo user (`demo@example.com` /
94
+ `password123`) so the sample runs without a database — replace it with a real model
95
+ lookup before production. `.env` is git-ignored; never commit it, and rotate any
96
+ secret that was ever committed. The scaffolder (`create-rebe`) generates fresh
97
+ `APP_KEY` and JWT secrets per project.
98
+
99
+ ### F-10 — Rate limit & trust proxy (Guidance)
100
+
101
+ A global rate limit is on by default (`100` / 15 min / IP). Behind a proxy or load
102
+ balancer, set `EXPRESS_TRUST_PROXY=1` (or the real hop count) so the limiter and logs
103
+ see the true client IP; leaving it at `0` while behind a proxy lets clients share one
104
+ bucket. Do **not** set an overly permissive trust-proxy value when directly exposed —
105
+ it would let clients spoof `X-Forwarded-For`.
106
+
107
+ ## Positive controls
108
+
109
+ - `helmet` with a restrictive CSP; `x-powered-by` disabled.
110
+ - Only `./public` and `/uploads` are served statically — the full `storage/` root is
111
+ not exposed.
112
+ - `Common.Hash.equals` uses `crypto.timingSafeEqual`; `Crypt` uses AES-256-GCM
113
+ (authenticated) with a random IV and salt per message.
114
+ - `Common.Storage` resolves every path inside the storage root and rejects traversal.
115
+ - Graceful shutdown drains the queue and closes connections.
116
+
117
+ ## Production hardening checklist
118
+
119
+ - [ ] `APP_ENV=production` (hides stack traces).
120
+ - [ ] Set explicit `CORS_ORIGIN` / `SOCKET_CORS_ORIGIN` (no `*`).
121
+ - [ ] Strong, unique `APP_KEY`, `JWT_SECRET`, `JWT_REFRESH_SECRET`; never committed.
122
+ - [ ] `EXPRESS_TRUST_PROXY` matches your proxy topology.
123
+ - [ ] Review `EXPRESS_BODY_LIMIT` and the rate-limit window/max.
124
+ - [ ] Replace demo auth with real user storage and password hashing.
125
+ - [ ] Restrict upload types; treat SVG/HTML as attachments.
126
+ - [ ] Run behind TLS; enable `MAIL_SECURE`/`REDIS_TLS` where applicable.
127
+ - [ ] `npm audit` clean (or risks accepted and tracked).
128
+ - [ ] Single PM2 fork instance, or Redis-backed for multi-instance.
129
+
130
+ ## Reporting
131
+
132
+ Report vulnerabilities privately to the maintainer rather than via public issues.
133
+ Include a description, affected version, and reproduction steps.
@@ -0,0 +1,22 @@
1
+ 'use strict'
2
+
3
+ // Application lifecycle hooks, invoked by the bootstrap core:
4
+ // before — config is loaded; services have not started yet
5
+ // after — the HTTP server is listening
6
+ // shutdown — a termination signal was received (cleanup here)
7
+ //
8
+ // Each stage receives a context object ({ config }). Keep them fast and resilient;
9
+ // a thrown error in `before`/`after` aborts boot.
10
+ module.exports = {
11
+ async before(ctx) {
12
+ // e.g. warm caches, assert required external services are reachable
13
+ },
14
+
15
+ async after(ctx) {
16
+ // e.g. emit a "ready" event, register health probes
17
+ },
18
+
19
+ async shutdown(ctx) {
20
+ // e.g. flush buffers, close third-party clients
21
+ },
22
+ }
@@ -0,0 +1,34 @@
1
+ 'use strict'
2
+
3
+ const bcrypt = require('bcrypt')
4
+
5
+ const config = require('@config/index')
6
+ const Jwt = require('@core/jwt.core')
7
+
8
+ // In-memory demo user so the sample runs without a database. The password hash is
9
+ // computed once at load. Replace this with a real model lookup
10
+ // (e.g. Database.model('User').findOne(...)) in a production app.
11
+ const DEMO_USER = { id: 1, email: 'demo@example.com', name: 'Demo User' }
12
+ const DEMO_PASSWORD_HASH = bcrypt.hashSync('password123', config.bcrypt.saltRounds)
13
+
14
+ class AuthController {
15
+ // Input is already validated by Validator.make(loginSchema) on the route.
16
+ static async login({ req, res }) {
17
+ const { email, password } = req.body
18
+
19
+ const ok = email === DEMO_USER.email && (await bcrypt.compare(password, DEMO_PASSWORD_HASH))
20
+ if (!ok) {
21
+ return res.status(401).json({ status: false, code: 401, message: 'Invalid credentials', data: null, meta: null })
22
+ }
23
+
24
+ const tokens = Jwt.issue({ sub: DEMO_USER.id, email: DEMO_USER.email })
25
+ return res.json({ status: true, code: 200, message: 'Login successful', data: { user: DEMO_USER, ...tokens }, meta: null })
26
+ }
27
+
28
+ // Protected by the auth middleware, which sets req.user from the access token.
29
+ static async me({ req, res }) {
30
+ return res.json({ status: true, code: 200, message: 'OK', data: req.user, meta: null })
31
+ }
32
+ }
33
+
34
+ module.exports = AuthController
@@ -0,0 +1,25 @@
1
+ 'use strict'
2
+
3
+ const Jwt = require('@core/jwt.core')
4
+
5
+ // Route middleware: require a valid Bearer access token and expose the decoded
6
+ // payload as req.user. Use per-route as a handle-based middleware:
7
+ // Routes.get('auth/me', AuthController.me, [Auth])
8
+ class Auth {
9
+ static handle({ req, res, next }) {
10
+ const token = Jwt.fromHeader(req.headers.authorization)
11
+ if (!token) {
12
+ return res.status(401).json({ status: false, code: 401, message: 'Missing bearer token', data: null, meta: null })
13
+ }
14
+
15
+ const { valid, payload } = Jwt.tryVerify(token)
16
+ if (!valid) {
17
+ return res.status(401).json({ status: false, code: 401, message: 'Invalid or expired token', data: null, meta: null })
18
+ }
19
+
20
+ req.user = payload
21
+ next()
22
+ }
23
+ }
24
+
25
+ module.exports = Auth
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const logger = require('@core/logger.core')
4
+
5
+ // Global application middleware. The Express core calls this with the app instance
6
+ // during create(), after the security/parsing stack and before routes. Register
7
+ // cross-cutting middleware here.
8
+ module.exports = (app) => {
9
+ // Lightweight access log: method, URL, status, and latency once the response
10
+ // has been sent.
11
+ app.use((req, res, next) => {
12
+ const start = Date.now()
13
+ res.on('finish', () => {
14
+ logger.http(`${req.method} ${req.originalUrl} ${res.statusCode} ${Date.now() - start}ms`)
15
+ })
16
+ next()
17
+ })
18
+ }
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ const { z } = require('zod')
4
+
5
+ module.exports.loginSchema = z.object({
6
+ email: z.string().email(),
7
+ password: z.string().min(6),
8
+ })
@@ -0,0 +1,17 @@
1
+ 'use strict'
2
+
3
+ const logger = require('@core/logger.core')
4
+
5
+ // Cron job definitions. The Cron core calls this function during boot and
6
+ // passes the Cron facade. Declare jobs with Cron.define(name, expression, fn).
7
+ //
8
+ // Expression format (node-cron): second? minute hour day-of-month month day-of-week
9
+ // '*/5 * * * *' -> every 5 minutes
10
+ // '0 0 * * *' -> every day at midnight
11
+ // '0 */6 * * *' -> every 6 hours
12
+ module.exports = (Cron) => {
13
+ // Heartbeat — handy while developing to confirm the scheduler is alive.
14
+ Cron.define('heartbeat', '*/30 * * * *', async () => {
15
+ logger.debug('Cron heartbeat tick')
16
+ })
17
+ }
@@ -0,0 +1,11 @@
1
+ 'use strict'
2
+
3
+ // Queue worker definitions. The Queue core calls this function during boot and
4
+ // passes the Queue facade. Register a processor per queue name; dispatch jobs
5
+ // from anywhere with Queue.dispatch('emails', { ... }).
6
+ module.exports = (Queue) => {
7
+ // sample
8
+ // Queue.define('name', async (payload) => {
9
+ // // action here
10
+ // }, { concurrency: 2, maxRetries: 3 })
11
+ }
@@ -0,0 +1,18 @@
1
+ 'use strict'
2
+
3
+ const Routes = require('@refkinscallv/express-routing')
4
+
5
+ const Validator = require('@core/validator.core')
6
+ const AuthController = require('@http/controllers/auth.controller')
7
+ const Auth = require('@http/middlewares/auth.middleware')
8
+ const { loginSchema } = require('@http/validators/auth.validator')
9
+
10
+ Routes.group('api', () => {
11
+ Routes.get('status', ({ res }) => {
12
+ res.json({ status: true, code: 200, message: 'OK', data: null, meta: null })
13
+ })
14
+
15
+ // Sample auth flow (runs without a database; see auth.controller.js).
16
+ Routes.post('auth/login', AuthController.login, [Validator.make(loginSchema)])
17
+ Routes.get('auth/me', AuthController.me, [Auth])
18
+ })
@@ -0,0 +1,8 @@
1
+ 'use strict'
2
+
3
+ const Routes = require('@refkinscallv/express-routing')
4
+
5
+ require('@routes/web.route')
6
+ require('@routes/api.route')
7
+
8
+ module.exports = Routes
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const Routes = require('@refkinscallv/express-routing')
4
+
5
+ Routes.get('', ({ res }) => {
6
+ res.send('Hello World')
7
+ })
@@ -0,0 +1,15 @@
1
+ 'use strict'
2
+
3
+ const logger = require('@core/logger.core')
4
+
5
+ // Socket.IO connection handlers. The socket core calls this with the io server and
6
+ // the Socket facade after the server is attached. Register namespaces, rooms, and
7
+ // event listeners here.
8
+ module.exports = (io, Socket) => {
9
+ io.on('connection', (socket) => {
10
+ // Example: echo a ping back to the sender.
11
+ socket.on('ping', (payload) => {
12
+ socket.emit('pong', { received: payload, at: Date.now() })
13
+ })
14
+ })
15
+ }
@@ -0,0 +1,12 @@
1
+ 'use strict'
2
+
3
+ const Common = require('@core/common.core')
4
+
5
+ module.exports = {
6
+ env: Common.getEnv('APP_ENV', 'development'),
7
+ name: Common.getEnv('APP_NAME', 'rebe'),
8
+ url: Common.getEnv('APP_URL', 'http://localhost:3000'),
9
+ port: Common.getEnvInt('APP_PORT', 3000),
10
+ timezone: Common.getEnv('APP_TIMEZONE', 'UTC'),
11
+ key: Common.getEnv('APP_KEY'),
12
+ }
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const Common = require('@core/common.core')
4
+
5
+ module.exports = {
6
+ saltRounds: Common.getEnvInt('BCRYPT_SALT_ROUNDS', 10),
7
+ }
@@ -0,0 +1,7 @@
1
+ 'use strict'
2
+
3
+ const Common = require('@core/common.core')
4
+
5
+ module.exports = {
6
+ ttl: Common.getEnvInt('CACHE_TTL', 3600),
7
+ }
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ const cors = require('cors')
4
+
5
+ const Common = require('@core/common.core')
6
+
7
+ // Allowed origins are read from the environment so deployments can lock CORS down
8
+ // without code changes. A literal '*' means "any origin"; any other value is parsed
9
+ // as a comma-separated allowlist.
10
+ //
11
+ // Security note: the browser CORS spec forbids combining a wildcard origin with
12
+ // `credentials: true`. When the origin is '*' we therefore force credentials off.
13
+ // Set explicit origins (e.g. CORS_ORIGIN=https://app.example.com) to enable
14
+ // credentialed cross-origin requests.
15
+ const expressOrigins = Common.getEnvArray('CORS_ORIGIN', ['*'])
16
+ const socketOrigins = Common.getEnvArray('SOCKET_CORS_ORIGIN', ['*'])
17
+
18
+ const expressWildcard = expressOrigins.length === 1 && expressOrigins[0] === '*'
19
+ const socketWildcard = socketOrigins.length === 1 && socketOrigins[0] === '*'
20
+
21
+ module.exports = {
22
+ express: cors({
23
+ origin: expressWildcard ? '*' : expressOrigins,
24
+ methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'],
25
+ allowedHeaders: ['Content-Type', 'Authorization'],
26
+ credentials: Common.getEnvBool('CORS_CREDENTIALS', !expressWildcard),
27
+ maxAge: Common.getEnvInt('CORS_MAX_AGE', 86400),
28
+ }),
29
+ socket: {
30
+ cors: {
31
+ origin: socketWildcard ? '*' : socketOrigins,
32
+ methods: ['GET', 'POST'],
33
+ allowedHeaders: ['Content-Type', 'Authorization'],
34
+ credentials: Common.getEnvBool('SOCKET_CORS_CREDENTIALS', !socketWildcard),
35
+ },
36
+ },
37
+ }
@@ -0,0 +1,9 @@
1
+ 'use strict'
2
+
3
+ const Common = require('@core/common.core')
4
+
5
+ module.exports = {
6
+ enabled: Common.getEnvBool('CRON_ENABLED', true),
7
+ timezone: Common.getEnv('CRON_TIMEZONE', 'Asia/Jakarta'),
8
+ history: Common.getEnvBool('CRON_HISTORY', true),
9
+ }
@@ -0,0 +1,51 @@
1
+ 'use strict'
2
+
3
+ const Common = require('@core/common.core')
4
+ const dialect = Common.getEnv('DB_DIALECT', 'mysql')
5
+
6
+ const dialectOptions = {
7
+ mysql: { charset: 'utf8mb4', connectTimeout: Common.getEnvInt('DB_CONNECT_TIMEOUT', 10000) },
8
+ mariadb: { charset: 'utf8mb4', connectTimeout: Common.getEnvInt('DB_CONNECT_TIMEOUT', 10000) },
9
+ postgres: { ssl: Common.getEnvBool('DB_SSL', false) ? { rejectUnauthorized: false } : false },
10
+ mssql: { options: { encrypt: Common.getEnvBool('DB_SSL', false), trustServerCertificate: Common.getEnvBool('DB_TRUST_CERT', true), requestTimeout: Common.getEnvInt('DB_REQUEST_TIMEOUT', 30000) } },
11
+ sqlite: {},
12
+ }
13
+
14
+ module.exports = {
15
+ enabled: Common.getEnvBool('DB_ENABLED', false),
16
+ dialect,
17
+ host: Common.getEnv('DB_HOST', 'localhost'),
18
+ port: Common.getEnvInt('DB_PORT', getDefaultPort(dialect)),
19
+ username: Common.getEnv('DB_USER', null),
20
+ password: Common.getEnv('DB_PASS', null),
21
+ database: Common.getEnv('DB_NAME', null),
22
+ timezone: Common.getEnv('DB_TIMEZONE', '+00:00'),
23
+ logging: Common.getEnvBool('DB_LOGGING', false),
24
+ benchmark: Common.getEnvBool('DB_BENCHMARK', false),
25
+ logQueryParameters: Common.getEnvBool('DB_LOG_PARAMS', false),
26
+ define: {
27
+ underscored: Common.getEnvBool('DB_UNDERSCORED', false),
28
+ freezeTableName: Common.getEnvBool('DB_FREEZE_TABLE', false),
29
+ timestamps: Common.getEnvBool('DB_TIMESTAMPS', true),
30
+ paranoid: Common.getEnvBool('DB_PARANOID', false),
31
+ },
32
+ sync: {
33
+ force: Common.getEnvBool('DB_FORCE', false),
34
+ alter: Common.getEnvBool('DB_ALTER', false),
35
+ },
36
+ pool: {
37
+ max: Common.getEnvInt('DB_POOL_MAX', 10),
38
+ min: Common.getEnvInt('DB_POOL_MIN', 0),
39
+ acquire: Common.getEnvInt('DB_POOL_ACQUIRE', 30000),
40
+ idle: Common.getEnvInt('DB_POOL_IDLE', 10000),
41
+ },
42
+ retry: {
43
+ max: Common.getEnvInt('DB_RETRY_MAX', 3),
44
+ },
45
+ dialectOptions: dialectOptions[dialect] ?? {},
46
+ }
47
+
48
+ function getDefaultPort(dialect) {
49
+ const ports = { mysql: 3306, mariadb: 3306, postgres: 5432, mssql: 1433, sqlite: null }
50
+ return ports[dialect] ?? 3306
51
+ }
@@ -0,0 +1,37 @@
1
+ 'use strict'
2
+
3
+ const Common = require('@core/common.core')
4
+
5
+ // HTTP layer configuration consumed by express.core.
6
+ //
7
+ // - bodyLimit: max request body size for json/urlencoded parsers (DoS guard).
8
+ // - trustProxy: number of proxy hops to trust, or false. Required for correct
9
+ // client IPs behind a load balancer / reverse proxy; also makes
10
+ // express-rate-limit key on the real client IP. Leave 0/false
11
+ // when the app is directly exposed.
12
+ // - rateLimit: global request throttle. Disable per-route logic in app code.
13
+ module.exports = {
14
+ bodyLimit: Common.getEnv('EXPRESS_BODY_LIMIT', '10mb'),
15
+ trustProxy: Common.getEnvInt('EXPRESS_TRUST_PROXY', 0),
16
+ rateLimit: {
17
+ enabled: Common.getEnvBool('EXPRESS_RATE_LIMIT_ENABLED', true),
18
+ windowMs: Common.getEnvInt('EXPRESS_RATE_LIMIT_WINDOW_MS', 900000),
19
+ max: Common.getEnvInt('EXPRESS_RATE_LIMIT_MAX', 100),
20
+ standardHeaders: true,
21
+ legacyHeaders: false,
22
+ message: { status: false, code: 429, message: 'Too many requests', data: null, meta: null },
23
+ },
24
+ helmet: {
25
+ referrerPolicy: {
26
+ policy: 'origin',
27
+ },
28
+ contentSecurityPolicy: {
29
+ directives: {
30
+ defaultSrc: ["'self'"],
31
+ scriptSrc: ["'self'"],
32
+ imgSrc: ["'self'", 'data:'],
33
+ connectSrc: ["'self'"],
34
+ },
35
+ },
36
+ },
37
+ }