millas 0.2.11 → 0.2.12-beta-1
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/package.json +6 -5
- package/src/auth/Auth.js +13 -8
- package/src/auth/AuthController.js +45 -134
- package/src/auth/AuthMiddleware.js +12 -23
- package/src/auth/AuthUser.js +98 -0
- package/src/auth/RoleMiddleware.js +7 -17
- package/src/cli.js +1 -1
- package/src/commands/migrate.js +46 -31
- package/src/commands/serve.js +238 -38
- package/src/container/AppInitializer.js +158 -0
- package/src/container/Application.js +288 -183
- package/src/container/HttpServer.js +156 -0
- package/src/container/MillasApp.js +23 -280
- package/src/container/MillasConfig.js +163 -0
- package/src/controller/Controller.js +79 -300
- package/src/core/auth.js +9 -0
- package/src/core/db.js +8 -0
- package/src/core/foundation.js +67 -0
- package/src/core/http.js +11 -0
- package/src/core/mail.js +6 -0
- package/src/core/queue.js +7 -0
- package/src/core/validation.js +29 -0
- package/src/errors/ErrorRenderer.js +640 -0
- package/src/facades/Admin.js +49 -0
- package/src/facades/Auth.js +29 -0
- package/src/facades/Cache.js +28 -0
- package/src/facades/Database.js +43 -0
- package/src/facades/Events.js +25 -0
- package/src/facades/Facade.js +197 -0
- package/src/facades/Http.js +51 -0
- package/src/facades/Log.js +32 -0
- package/src/facades/Mail.js +35 -0
- package/src/facades/Queue.js +30 -0
- package/src/facades/Storage.js +25 -0
- package/src/facades/Url.js +53 -0
- package/src/http/HttpClient.js +673 -0
- package/src/http/MillasRequest.js +253 -0
- package/src/http/MillasResponse.js +196 -0
- package/src/http/RequestContext.js +176 -0
- package/src/http/ResponseDispatcher.js +51 -0
- package/src/http/UrlGenerator.js +375 -0
- package/src/http/WelcomePage.js +273 -0
- package/src/http/adapters/ExpressAdapter.js +315 -0
- package/src/http/adapters/HttpAdapter.js +168 -0
- package/src/http/adapters/index.js +9 -0
- package/src/http/helpers.js +164 -0
- package/src/http/index.js +13 -0
- package/src/index.js +5 -91
- package/src/logger/formatters/PrettyFormatter.js +15 -5
- package/src/logger/internal.js +76 -0
- package/src/logger/patchConsole.js +145 -0
- package/src/middleware/CorsMiddleware.js +22 -30
- package/src/middleware/LogMiddleware.js +27 -59
- package/src/middleware/Middleware.js +24 -15
- package/src/middleware/MiddlewarePipeline.js +30 -67
- package/src/middleware/MiddlewareRegistry.js +106 -0
- package/src/middleware/ThrottleMiddleware.js +22 -26
- package/src/orm/fields/index.js +124 -56
- package/src/orm/migration/ModelInspector.js +339 -336
- package/src/orm/model/Model.js +96 -6
- package/src/orm/query/QueryBuilder.js +141 -3
- package/src/providers/AuthServiceProvider.js +9 -5
- package/src/providers/CacheStorageServiceProvider.js +3 -1
- package/src/providers/EventServiceProvider.js +2 -1
- package/src/providers/LogServiceProvider.js +88 -17
- package/src/providers/MailServiceProvider.js +3 -2
- package/src/providers/ProviderRegistry.js +14 -1
- package/src/providers/QueueServiceProvider.js +3 -2
- package/src/providers/ServiceProvider.js +40 -8
- package/src/router/Router.js +121 -222
- package/src/scaffold/maker.js +24 -59
- package/src/scaffold/templates.js +21 -19
- package/src/validation/BaseValidator.js +193 -0
- package/src/validation/Validator.js +680 -0
package/src/router/Router.js
CHANGED
|
@@ -1,278 +1,177 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
const MiddlewareRegistry = require('./MiddlewareRegistry');
|
|
4
|
-
|
|
5
|
-
// ── Welcome page — shown at / when no route is defined ────────────────────────
|
|
6
|
-
const WELCOME_PAGE = `<!DOCTYPE html>
|
|
7
|
-
<html lang="en">
|
|
8
|
-
<head>
|
|
9
|
-
<meta charset="UTF-8">
|
|
10
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
11
|
-
|
|
12
|
-
<title>Welcome to Millas</title>
|
|
13
|
-
|
|
14
|
-
<link rel="stylesheet"
|
|
15
|
-
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css"
|
|
16
|
-
crossorigin="anonymous" referrerpolicy="no-referrer"/>
|
|
17
|
-
|
|
18
|
-
<style>
|
|
19
|
-
|
|
20
|
-
body{
|
|
21
|
-
margin:0;
|
|
22
|
-
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto;
|
|
23
|
-
background:#fff;
|
|
24
|
-
color:#333;
|
|
25
|
-
display:flex;
|
|
26
|
-
align-items:center;
|
|
27
|
-
justify-content:center;
|
|
28
|
-
height:100vh;
|
|
29
|
-
text-align:center;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
.container{
|
|
33
|
-
max-width:720px;
|
|
34
|
-
padding:40px;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
.icon{
|
|
38
|
-
margin-bottom:20px;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
h1{
|
|
42
|
-
font-size:32px;
|
|
43
|
-
margin-bottom:12px;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
.subtitle{
|
|
47
|
-
color:#555;
|
|
48
|
-
font-size:16px;
|
|
49
|
-
line-height:1.7;
|
|
50
|
-
margin-bottom:25px;
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
code{
|
|
54
|
-
background:#fff4ed;
|
|
55
|
-
color:#ea580c;
|
|
56
|
-
padding:4px 8px;
|
|
57
|
-
border-radius:6px;
|
|
58
|
-
font-size:13px;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
.steps{
|
|
62
|
-
margin-top:30px;
|
|
63
|
-
text-align:left;
|
|
64
|
-
display:inline-block;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
.steps h3{
|
|
68
|
-
margin-bottom:10px;
|
|
69
|
-
font-size:16px;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
.steps ul{
|
|
73
|
-
margin:0;
|
|
74
|
-
padding-left:20px;
|
|
75
|
-
color:#555;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
.links{
|
|
79
|
-
margin-top:35px;
|
|
80
|
-
display:flex;
|
|
81
|
-
justify-content:center;
|
|
82
|
-
gap:18px;
|
|
83
|
-
flex-wrap:wrap;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
.links a{
|
|
87
|
-
color:#f97316;
|
|
88
|
-
text-decoration:none;
|
|
89
|
-
font-size:14px;
|
|
90
|
-
display:flex;
|
|
91
|
-
align-items:center;
|
|
92
|
-
gap:6px;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
.links a:hover{
|
|
96
|
-
text-decoration:underline;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
.footer{
|
|
100
|
-
margin-top:35px;
|
|
101
|
-
font-size:13px;
|
|
102
|
-
color:#888;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
</style>
|
|
106
|
-
</head>
|
|
107
|
-
|
|
108
|
-
<body>
|
|
109
|
-
|
|
110
|
-
<div class="container">
|
|
111
|
-
|
|
112
|
-
<div class="icon">
|
|
113
|
-
<img src="https://www.saaspegasus.com/static/images/web/landing-page/rocket-laptop.39d6be7451e6.svg" alt="Millas" style="width:140px;height:auto;">
|
|
114
|
-
</div>
|
|
115
|
-
|
|
116
|
-
<h1>It worked!</h1>
|
|
117
|
-
|
|
118
|
-
<p class="subtitle">
|
|
119
|
-
Millas is installed and running correctly.
|
|
120
|
-
</p>
|
|
121
|
-
|
|
122
|
-
<div class="steps">
|
|
123
|
-
<h3>Next steps</h3>
|
|
124
|
-
<ul>
|
|
125
|
-
<li>Create your first route in <code>routes/web.js</code></li>
|
|
126
|
-
</ul>
|
|
127
|
-
</div>
|
|
128
|
-
|
|
129
|
-
<div class="links">
|
|
130
|
-
|
|
131
|
-
<a href="/admin">
|
|
132
|
-
<i class="fa-solid fa-gauge"></i>
|
|
133
|
-
Admin panel
|
|
134
|
-
</a>
|
|
135
|
-
|
|
136
|
-
<a href="/api/health">
|
|
137
|
-
<i class="fa-solid fa-heart-pulse"></i>
|
|
138
|
-
Health check
|
|
139
|
-
</a>
|
|
140
|
-
|
|
141
|
-
<a href="https://github.com/millas-framework/millas" target="_blank">
|
|
142
|
-
<i class="fa-brands fa-github"></i>
|
|
143
|
-
GitHub
|
|
144
|
-
</a>
|
|
145
|
-
|
|
146
|
-
</div>
|
|
147
|
-
|
|
148
|
-
<div class="footer">
|
|
149
|
-
Millas v0.1.2
|
|
150
|
-
</div>
|
|
151
|
-
|
|
152
|
-
</div>
|
|
153
|
-
|
|
154
|
-
</body>
|
|
155
|
-
</html>`;
|
|
156
|
-
|
|
157
3
|
/**
|
|
158
4
|
* Router
|
|
159
5
|
*
|
|
160
|
-
*
|
|
161
|
-
*
|
|
6
|
+
* Bridges the Millas RouteRegistry to any HttpAdapter.
|
|
7
|
+
* Zero knowledge of Express (or any HTTP engine) — it only calls
|
|
8
|
+
* the adapter interface defined in HttpAdapter.js.
|
|
162
9
|
*
|
|
163
|
-
*
|
|
164
|
-
*
|
|
10
|
+
* Responsibilities:
|
|
11
|
+
* - Resolve route handlers from the RouteRegistry
|
|
12
|
+
* - Resolve middleware aliases from the MiddlewareRegistry
|
|
13
|
+
* - Ask the adapter to mount each route
|
|
14
|
+
* - Ask the adapter to mount the welcome page, 404, and error handler
|
|
165
15
|
*/
|
|
166
16
|
class Router {
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('../http/adapters/HttpAdapter')} adapter
|
|
19
|
+
* @param {import('./RouteRegistry')} registry
|
|
20
|
+
* @param {import('./MiddlewareRegistry')} middlewareRegistry
|
|
21
|
+
* @param {import('../container/Container')|null} container
|
|
22
|
+
*/
|
|
23
|
+
constructor(adapter, registry, middlewareRegistry, container = null) {
|
|
24
|
+
this._adapter = adapter;
|
|
25
|
+
this._registry = registry;
|
|
26
|
+
this._mw = middlewareRegistry;
|
|
27
|
+
this._container = container;
|
|
171
28
|
}
|
|
172
29
|
|
|
30
|
+
// ── Public API ─────────────────────────────────────────────────────────────
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Mount all registered routes onto the adapter.
|
|
34
|
+
* Does NOT add fallbacks — call mountFallbacks() after all routes
|
|
35
|
+
* and any extra middleware (e.g. Admin panel) have been added.
|
|
36
|
+
*/
|
|
173
37
|
mountRoutes() {
|
|
174
|
-
const
|
|
175
|
-
for (const route of routes) {
|
|
38
|
+
for (const route of this._registry.all()) {
|
|
176
39
|
this._bindRoute(route);
|
|
177
40
|
}
|
|
178
41
|
return this;
|
|
179
42
|
}
|
|
180
43
|
|
|
44
|
+
/**
|
|
45
|
+
* Mount the 404 + error handlers.
|
|
46
|
+
* Must be called LAST — after all routes and the admin panel.
|
|
47
|
+
*/
|
|
181
48
|
mountFallbacks() {
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
);
|
|
185
|
-
|
|
186
|
-
if (!hasRootRoute) {
|
|
187
|
-
this._app.get('/', (req, res) => {
|
|
188
|
-
res.setHeader('Content-Type', 'text/html; charset=utf-8');
|
|
189
|
-
res.send(WELCOME_PAGE);
|
|
190
|
-
});
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
this._app.use((req, res) => {
|
|
194
|
-
res.status(404).json({
|
|
195
|
-
error: 'Not Found',
|
|
196
|
-
message: `Cannot ${req.method} ${req.path}`,
|
|
197
|
-
status: 404,
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
|
|
201
|
-
this._app.use((err, req, res, _next) => {
|
|
202
|
-
const status = err.status || err.statusCode || 500;
|
|
203
|
-
const message = err.message || 'Internal Server Error';
|
|
204
|
-
|
|
205
|
-
if (status >= 500 && process.env.NODE_ENV !== 'production') {
|
|
206
|
-
console.error(err.stack);
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
res.status(status).json({
|
|
210
|
-
error: status >= 500 ? 'Internal Server Error' : message,
|
|
211
|
-
message,
|
|
212
|
-
status,
|
|
213
|
-
...(err.errors && { errors: err.errors }),
|
|
214
|
-
...(status >= 500 && process.env.NODE_ENV !== 'production' && { stack: err.stack }),
|
|
215
|
-
});
|
|
216
|
-
});
|
|
49
|
+
this._maybeInjectWelcome();
|
|
50
|
+
this._adapter.mountNotFound();
|
|
51
|
+
this._adapter.mountErrorHandler();
|
|
217
52
|
return this;
|
|
218
53
|
}
|
|
219
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Mount routes + fallbacks in one call.
|
|
57
|
+
*/
|
|
220
58
|
mount() {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
this.mountFallbacks();
|
|
59
|
+
this.mountRoutes();
|
|
60
|
+
this._maybeInjectWelcome();
|
|
61
|
+
this._adapter.mountNotFound();
|
|
62
|
+
this._adapter.mountErrorHandler();
|
|
226
63
|
return this;
|
|
227
64
|
}
|
|
228
65
|
|
|
66
|
+
// ── Private ────────────────────────────────────────────────────────────────
|
|
67
|
+
|
|
229
68
|
_bindRoute(route) {
|
|
230
|
-
const
|
|
231
|
-
const
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
69
|
+
const middlewareHandlers = this._resolveMiddleware(route.middleware || []);
|
|
70
|
+
const terminalHandler = this._resolveTerminalHandler(
|
|
71
|
+
route.handler,
|
|
72
|
+
route.method,
|
|
73
|
+
route.verb,
|
|
74
|
+
route.path,
|
|
75
|
+
route.name
|
|
76
|
+
);
|
|
77
|
+
|
|
78
|
+
this._adapter.mountRoute(route.verb, route.path, [
|
|
79
|
+
...middlewareHandlers,
|
|
80
|
+
terminalHandler,
|
|
81
|
+
]);
|
|
235
82
|
}
|
|
236
83
|
|
|
237
84
|
_resolveMiddleware(list) {
|
|
238
85
|
return list.map(alias => {
|
|
239
86
|
try {
|
|
240
|
-
return this._mw.resolve(alias);
|
|
87
|
+
return this._mw.resolve(alias, this._adapter, this._container);
|
|
241
88
|
} catch (err) {
|
|
242
|
-
console.warn(`[Millas]
|
|
243
|
-
return (
|
|
89
|
+
console.warn(`[Millas] Middleware warning: ${err.message} — skipping.`);
|
|
90
|
+
return this._mw.resolvePassthrough(this._adapter);
|
|
244
91
|
}
|
|
245
92
|
});
|
|
246
93
|
}
|
|
247
94
|
|
|
248
|
-
|
|
95
|
+
_resolveTerminalHandler(handler, method, verb, path, routeName) {
|
|
96
|
+
const kernelFn = this._extractKernelFn(handler, method);
|
|
97
|
+
const displayName = this._buildDisplayName(handler, method, verb, path, routeName);
|
|
98
|
+
return this._adapter.wrapKernelHandler(kernelFn, displayName, this._container);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Pull the actual function out of the handler definition.
|
|
103
|
+
* Three forms:
|
|
104
|
+
* 1. Bare function/arrow: Route.get('/', () => jsonify({}))
|
|
105
|
+
* 2. Controller class + method: Route.get('/', UserController, 'index')
|
|
106
|
+
* 3. Controller instance + method: Route.get('/', controllerInstance, 'index')
|
|
107
|
+
*/
|
|
108
|
+
_extractKernelFn(handler, method) {
|
|
249
109
|
if (typeof handler === 'function' && !method) {
|
|
250
|
-
return
|
|
110
|
+
return handler;
|
|
251
111
|
}
|
|
112
|
+
|
|
252
113
|
if (typeof handler === 'function' && typeof method === 'string') {
|
|
253
114
|
const instance = new handler();
|
|
254
115
|
if (typeof instance[method] !== 'function') {
|
|
255
|
-
throw new Error(
|
|
116
|
+
throw new Error(
|
|
117
|
+
`Method "${method}" not found on controller "${handler.name}".`
|
|
118
|
+
);
|
|
256
119
|
}
|
|
257
|
-
return
|
|
120
|
+
return instance[method].bind(instance);
|
|
258
121
|
}
|
|
122
|
+
|
|
259
123
|
if (typeof handler === 'object' && handler !== null && typeof method === 'string') {
|
|
260
124
|
if (typeof handler[method] !== 'function') {
|
|
261
125
|
throw new Error(`Method "${method}" not found on handler object.`);
|
|
262
126
|
}
|
|
263
|
-
return
|
|
127
|
+
return handler[method].bind(handler);
|
|
264
128
|
}
|
|
129
|
+
|
|
265
130
|
if (typeof handler === 'function') {
|
|
266
|
-
return
|
|
131
|
+
return handler;
|
|
267
132
|
}
|
|
133
|
+
|
|
268
134
|
throw new Error(`Invalid route handler: ${JSON.stringify(handler)}`);
|
|
269
135
|
}
|
|
270
136
|
|
|
271
|
-
|
|
272
|
-
if (
|
|
273
|
-
|
|
137
|
+
_buildDisplayName(handler, method, verb, path, routeName) {
|
|
138
|
+
if (routeName) return routeName;
|
|
139
|
+
|
|
140
|
+
if (typeof handler === 'function' && method) {
|
|
141
|
+
return `${handler.name}.${method}`;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (
|
|
145
|
+
typeof handler === 'function' &&
|
|
146
|
+
handler.name &&
|
|
147
|
+
handler.name !== 'anonymous' &&
|
|
148
|
+
handler.name !== ''
|
|
149
|
+
) {
|
|
150
|
+
return handler.name;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return verb && path ? `${verb.toUpperCase()} ${path}` : 'anonymous';
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* If no user-defined GET / route exists, ask the adapter to serve
|
|
158
|
+
* a developer-friendly welcome page.
|
|
159
|
+
* Only active outside production — silently skipped in prod.
|
|
160
|
+
*/
|
|
161
|
+
_maybeInjectWelcome() {
|
|
162
|
+
if (process.env.NODE_ENV === 'production') return;
|
|
163
|
+
|
|
164
|
+
const hasRoot = this._registry.all().some(
|
|
165
|
+
r => r.verb === 'GET' && (r.path === '/' || r.path === '')
|
|
166
|
+
);
|
|
167
|
+
|
|
168
|
+
if (!hasRoot) {
|
|
169
|
+
let version = '';
|
|
170
|
+
try { version = require('../../package.json').version; } catch {}
|
|
171
|
+
this._adapter.mountWelcome(
|
|
172
|
+
this._adapter.makeWelcomeHandler(version)
|
|
173
|
+
);
|
|
274
174
|
}
|
|
275
|
-
return fn;
|
|
276
175
|
}
|
|
277
176
|
}
|
|
278
177
|
|
package/src/scaffold/maker.js
CHANGED
|
@@ -38,6 +38,7 @@ async function makeController(name, options = {}) {
|
|
|
38
38
|
? `'use strict';
|
|
39
39
|
|
|
40
40
|
const { Controller } = require('millas');
|
|
41
|
+
const { string, email, number } = require('millas/validation');
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* ${className}
|
|
@@ -46,34 +47,32 @@ const { Controller } = require('millas');
|
|
|
46
47
|
*/
|
|
47
48
|
class ${className} extends Controller {
|
|
48
49
|
/** GET /${name.toLowerCase()}s */
|
|
49
|
-
async index(
|
|
50
|
-
return this.ok(
|
|
50
|
+
async index({ query }) {
|
|
51
|
+
return this.ok({ data: [] });
|
|
51
52
|
}
|
|
52
53
|
|
|
53
54
|
/** GET /${name.toLowerCase()}s/:id */
|
|
54
|
-
async show(
|
|
55
|
-
|
|
56
|
-
return this.ok(res, { data: { id } });
|
|
55
|
+
async show({ params }) {
|
|
56
|
+
return this.ok({ data: { id: params.id } });
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** POST /${name.toLowerCase()}s */
|
|
60
|
-
async store(
|
|
61
|
-
const data = await
|
|
62
|
-
//
|
|
60
|
+
async store({ body }) {
|
|
61
|
+
const data = await body.validate({
|
|
62
|
+
// name: string().required().max(255),
|
|
63
63
|
});
|
|
64
|
-
return this.created(
|
|
64
|
+
return this.created({ data });
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
/** PUT /${name.toLowerCase()}s/:id */
|
|
68
|
-
async update(
|
|
69
|
-
const
|
|
70
|
-
|
|
71
|
-
return this.ok(res, { data: { id, ...data } });
|
|
68
|
+
async update({ params, body }) {
|
|
69
|
+
const data = body.except(['id']);
|
|
70
|
+
return this.ok({ data: { id: params.id, ...data } });
|
|
72
71
|
}
|
|
73
72
|
|
|
74
73
|
/** DELETE /${name.toLowerCase()}s/:id */
|
|
75
|
-
async destroy(
|
|
76
|
-
return this.noContent(
|
|
74
|
+
async destroy({ params }) {
|
|
75
|
+
return this.noContent();
|
|
77
76
|
}
|
|
78
77
|
}
|
|
79
78
|
|
|
@@ -87,8 +86,8 @@ const { Controller } = require('millas');
|
|
|
87
86
|
* ${className}
|
|
88
87
|
*/
|
|
89
88
|
class ${className} extends Controller {
|
|
90
|
-
async index(
|
|
91
|
-
return this.ok(
|
|
89
|
+
async index({ query }) {
|
|
90
|
+
return this.ok({ message: 'Hello from ${className}' });
|
|
92
91
|
}
|
|
93
92
|
}
|
|
94
93
|
|
|
@@ -105,58 +104,23 @@ async function makeModel(name, options = {}) {
|
|
|
105
104
|
|
|
106
105
|
const content = `'use strict';
|
|
107
106
|
|
|
108
|
-
const { Model, fields
|
|
107
|
+
const { Model, fields } = require('millas');
|
|
109
108
|
|
|
110
109
|
/**
|
|
111
110
|
* ${className} Model
|
|
112
111
|
*
|
|
113
|
-
*
|
|
114
|
-
*
|
|
115
|
-
*
|
|
116
|
-
* millas makemigrations — generates migration files from your changes
|
|
117
|
-
* millas migrate — applies them to the database
|
|
112
|
+
* Represents the "${tableName}" table.
|
|
113
|
+
* Run: millas makemigrations — to generate the migration.
|
|
114
|
+
* Run: millas migrate — to apply it.
|
|
118
115
|
*/
|
|
119
116
|
class ${className} extends Model {
|
|
120
117
|
static table = '${tableName}';
|
|
121
118
|
|
|
122
|
-
// ── Fields ──────────────────────────────────────────────────────
|
|
123
119
|
static fields = {
|
|
124
120
|
id: fields.id(),
|
|
125
121
|
created_at: fields.timestamp(),
|
|
126
122
|
updated_at: fields.timestamp(),
|
|
127
|
-
// deleted_at: fields.timestamp({ nullable: true }), // uncomment + set softDeletes = true
|
|
128
123
|
};
|
|
129
|
-
|
|
130
|
-
// ── Soft deletes ─────────────────────────────────────────────────
|
|
131
|
-
// static softDeletes = true;
|
|
132
|
-
|
|
133
|
-
// ── Relations ─────────────────────────────────────────────────────
|
|
134
|
-
// static relations = {
|
|
135
|
-
// posts: new HasMany(() => require('./Post'), 'user_id'),
|
|
136
|
-
// profile: new HasOne(() => require('./Profile'), 'user_id'),
|
|
137
|
-
// role: new BelongsTo(() => require('./Role'), 'role_id'),
|
|
138
|
-
// tags: new BelongsToMany(() => require('./Tag'), '${tableName.replace(/s$/, '')}_tag', '${tableName.replace(/s$/, '')}_id', 'tag_id'),
|
|
139
|
-
// };
|
|
140
|
-
|
|
141
|
-
// ── Scopes ───────────────────────────────────────────────────────
|
|
142
|
-
// static scopes = {
|
|
143
|
-
// active: qb => qb.where('active', true),
|
|
144
|
-
// recent: qb => qb.latest().limit(10),
|
|
145
|
-
// byUser: (qb, userId) => qb.where('user_id', userId),
|
|
146
|
-
// };
|
|
147
|
-
|
|
148
|
-
// ── Validation ───────────────────────────────────────────────────
|
|
149
|
-
// static validate(data) {
|
|
150
|
-
// if (!data.name) throw new Error('name is required');
|
|
151
|
-
// }
|
|
152
|
-
|
|
153
|
-
// ── Lifecycle hooks ──────────────────────────────────────────────
|
|
154
|
-
// static async beforeCreate(data) { return data; }
|
|
155
|
-
// static async afterCreate(instance) {}
|
|
156
|
-
// static async beforeUpdate(data) { return data; }
|
|
157
|
-
// static async afterUpdate(instance) {}
|
|
158
|
-
// static async beforeDelete(instance){}
|
|
159
|
-
// static async afterDelete(instance) {}
|
|
160
124
|
}
|
|
161
125
|
|
|
162
126
|
module.exports = ${className};
|
|
@@ -194,10 +158,11 @@ class ${className} extends Middleware {
|
|
|
194
158
|
* Handle the incoming request.
|
|
195
159
|
* Call next() to continue, or return a response to halt the chain.
|
|
196
160
|
*/
|
|
197
|
-
async handle(req
|
|
198
|
-
//
|
|
161
|
+
async handle({ req }, next) {
|
|
162
|
+
// Destructure what you need: { params, body, query, user, headers, req }
|
|
163
|
+
// Return a MillasResponse to short-circuit, or call next() to continue.
|
|
199
164
|
|
|
200
|
-
next();
|
|
165
|
+
return next();
|
|
201
166
|
}
|
|
202
167
|
}
|
|
203
168
|
|
|
@@ -12,6 +12,8 @@ function getProjectFiles(projectName) {
|
|
|
12
12
|
scripts: {
|
|
13
13
|
start: 'node bootstrap/app.js',
|
|
14
14
|
dev: 'millas serve',
|
|
15
|
+
makemigrations: 'millas makemigration',
|
|
16
|
+
migrate: 'millas migrate',
|
|
15
17
|
serve: 'millas serve',
|
|
16
18
|
},
|
|
17
19
|
dependencies: {
|
|
@@ -88,18 +90,17 @@ module.exports = {
|
|
|
88
90
|
|
|
89
91
|
require('dotenv').config();
|
|
90
92
|
|
|
91
|
-
const {
|
|
93
|
+
const { Millas } = require('millas');
|
|
92
94
|
const AppServiceProvider = require('../providers/AppServiceProvider');
|
|
93
95
|
|
|
94
|
-
|
|
96
|
+
module.exports = Millas.config()
|
|
95
97
|
.providers([AppServiceProvider])
|
|
96
98
|
.routes(Route => {
|
|
97
99
|
require('../routes/web')(Route);
|
|
98
100
|
require('../routes/api')(Route);
|
|
99
101
|
})
|
|
100
|
-
.withAdmin()
|
|
101
|
-
|
|
102
|
-
module.exports = app.start();
|
|
102
|
+
.withAdmin()
|
|
103
|
+
.create();
|
|
103
104
|
`,
|
|
104
105
|
|
|
105
106
|
// ─── routes/web.js ────────────────────────────────────────────
|
|
@@ -130,12 +131,13 @@ module.exports = function (Route) {
|
|
|
130
131
|
module.exports = function (Route) {
|
|
131
132
|
Route.prefix('/api').group(() => {
|
|
132
133
|
|
|
133
|
-
Route.get('/health', (
|
|
134
|
-
|
|
135
|
-
|
|
134
|
+
Route.get('/health', () =>
|
|
135
|
+
jsonify({ status: 'ok', timestamp: new Date().toISOString() })
|
|
136
|
+
);
|
|
136
137
|
|
|
137
|
-
// Your API routes here
|
|
138
|
+
// Your API routes here:
|
|
138
139
|
// Route.resource('/users', UserController);
|
|
140
|
+
// Route.auth('/auth');
|
|
139
141
|
|
|
140
142
|
});
|
|
141
143
|
};
|
|
@@ -241,29 +243,29 @@ module.exports = {
|
|
|
241
243
|
// ─── providers/AppServiceProvider.js ──────────────────────────
|
|
242
244
|
'providers/AppServiceProvider.js': `'use strict';
|
|
243
245
|
|
|
244
|
-
const { ServiceProvider } = require('millas');
|
|
246
|
+
const { ServiceProvider } = require('millas/core/foundation');
|
|
245
247
|
|
|
246
248
|
/**
|
|
247
249
|
* AppServiceProvider
|
|
248
250
|
*
|
|
249
251
|
* Register and bootstrap your application services here.
|
|
250
252
|
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
+
* beforeBoot(container) — runs first, before any register(). Synchronous.
|
|
254
|
+
* Good for patching globals, loading config.
|
|
255
|
+
* register(container) — bind singletons, factories, instances.
|
|
256
|
+
* boot(container, app) — all providers registered. Async OK.
|
|
257
|
+
* Good for routes, event listeners, admin resources.
|
|
253
258
|
*/
|
|
254
259
|
class AppServiceProvider extends ServiceProvider {
|
|
255
260
|
register(container) {
|
|
256
261
|
// container.bind('UserService', UserService);
|
|
257
|
-
// container.singleton('
|
|
262
|
+
// container.singleton('Cache', CacheService);
|
|
258
263
|
// container.instance('Config', require('../config/app'));
|
|
259
264
|
}
|
|
260
265
|
|
|
261
266
|
async boot(container, app) {
|
|
262
|
-
//
|
|
263
|
-
//
|
|
264
|
-
|
|
265
|
-
// Register resources in the Admin panel:
|
|
266
|
-
// const { Admin, AdminResource } = require('millas');
|
|
267
|
+
// Register Admin resources:
|
|
268
|
+
// const { Admin } = require('millas');
|
|
267
269
|
// const Post = require('../app/models/Post');
|
|
268
270
|
// Admin.register(Post);
|
|
269
271
|
}
|
|
@@ -325,4 +327,4 @@ providers/ # Service providers
|
|
|
325
327
|
};
|
|
326
328
|
}
|
|
327
329
|
|
|
328
|
-
module.exports = { getProjectFiles };
|
|
330
|
+
module.exports = { getProjectFiles };
|