@seip/blue-bird 0.1.7 → 0.2.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/core/app.js +32 -0
- package/core/auth.js +10 -0
- package/core/cli/component.js +42 -0
- package/core/cli/route.js +43 -0
- package/core/router.js +39 -0
- package/core/template.js +50 -53
- package/core/upload.js +6 -0
- package/core/validate.js +17 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/public/robots.txt +0 -0
- package/frontend/public/sitemap.xml +0 -0
- package/package.json +4 -2
package/core/app.js
CHANGED
|
@@ -27,6 +27,23 @@ class App {
|
|
|
27
27
|
* @param {boolean} [options.urlencoded=true] - Whether to enable URL-encoded body parsing.
|
|
28
28
|
* @param {Object} [options.static={path: null, options: {}}] - Static file configuration.
|
|
29
29
|
* @param {boolean} [options.cookieParser=true] - Whether to enable cookie parsing.
|
|
30
|
+
* @example
|
|
31
|
+
* const app = new App({
|
|
32
|
+
* routes: [],
|
|
33
|
+
* cors: {},
|
|
34
|
+
* middlewares: [],
|
|
35
|
+
* port: 3000,
|
|
36
|
+
* host: "http://localhost",
|
|
37
|
+
* logger: true,
|
|
38
|
+
* notFound: true,
|
|
39
|
+
* json: true,
|
|
40
|
+
* urlencoded: true,
|
|
41
|
+
* static: {
|
|
42
|
+
* path: "public",
|
|
43
|
+
* options: {}
|
|
44
|
+
* },
|
|
45
|
+
* cookieParser: true,
|
|
46
|
+
* });
|
|
30
47
|
*/
|
|
31
48
|
constructor(options = {
|
|
32
49
|
routes: [],
|
|
@@ -64,10 +81,25 @@ class App {
|
|
|
64
81
|
/**
|
|
65
82
|
* Registers a custom middleware or module in the Express application.
|
|
66
83
|
* @param {Function|import('express').Router} record - The middleware function or Express router to register.
|
|
84
|
+
* @example
|
|
85
|
+
* app.use((req, res, next) => {
|
|
86
|
+
* console.log("Middleware");
|
|
87
|
+
* next();
|
|
88
|
+
* });
|
|
67
89
|
*/
|
|
68
90
|
use(record) {
|
|
69
91
|
this.app.use(record)
|
|
70
92
|
}
|
|
93
|
+
/**
|
|
94
|
+
* Sets a configuration value in the Express application.
|
|
95
|
+
* @param {string} key - The configuration key.
|
|
96
|
+
* @param {*} value - The value to set for the configuration key.
|
|
97
|
+
* @example
|
|
98
|
+
* app.set("port", 3000);
|
|
99
|
+
*/
|
|
100
|
+
set(key, value) {
|
|
101
|
+
this.app.set(key, value)
|
|
102
|
+
}
|
|
71
103
|
|
|
72
104
|
/**
|
|
73
105
|
* Bootstraps the application by configuring global middlewares and routes.
|
package/core/auth.js
CHANGED
|
@@ -10,6 +10,10 @@ class Auth {
|
|
|
10
10
|
* @param {string} [secret=process.env.JWT_SECRET] - The secret key.
|
|
11
11
|
* @param {string|number} [expiresIn='24h'] - Expiration time.
|
|
12
12
|
* @returns {string} The generated token.
|
|
13
|
+
* @example
|
|
14
|
+
* const token = Auth.generateToken({ id: 1 });
|
|
15
|
+
* console.log(token);
|
|
16
|
+
*
|
|
13
17
|
*/
|
|
14
18
|
static generateToken(payload, secret = process.env.JWT_SECRET || 'blue-bird-secret', expiresIn = '24h') {
|
|
15
19
|
return jwt.sign(payload, secret, { expiresIn });
|
|
@@ -20,6 +24,10 @@ class Auth {
|
|
|
20
24
|
* @param {string} token - The token to verify.
|
|
21
25
|
* @param {string} [secret=process.env.JWT_SECRET] - The secret key.
|
|
22
26
|
* @returns {Object|null} The decoded payload or null if invalid.
|
|
27
|
+
* @example
|
|
28
|
+
* const token = Auth.generateToken({ id: 1 });
|
|
29
|
+
* const decoded = Auth.verifyToken(token);
|
|
30
|
+
* console.log(decoded);
|
|
23
31
|
*/
|
|
24
32
|
static verifyToken(token, secret = process.env.JWT_SECRET || 'blue-bird-secret') {
|
|
25
33
|
try {
|
|
@@ -34,6 +42,8 @@ class Auth {
|
|
|
34
42
|
* @param {Object} options - Options for protection.
|
|
35
43
|
* @param {string} [options.redirect] - URL to redirect if not authenticated (for web routes).
|
|
36
44
|
* @returns {Function} Express middleware.
|
|
45
|
+
* @example
|
|
46
|
+
* app.use(Auth.protect({ redirect: "/login" }));
|
|
37
47
|
*/
|
|
38
48
|
static protect(options = { redirect: null }) {
|
|
39
49
|
return (req, res, next) => {
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import path from "node:path";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import Config from "../config.js";
|
|
4
|
+
|
|
5
|
+
const __dirname = Config.dirname();
|
|
6
|
+
|
|
7
|
+
class ComponentCLI {
|
|
8
|
+
/**
|
|
9
|
+
* Create component react
|
|
10
|
+
* @return {void}
|
|
11
|
+
* /
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
create() {
|
|
15
|
+
const folder = path.join(process.cwd(), "frontend/resources/components");
|
|
16
|
+
if (!fs.existsSync(folder)) {
|
|
17
|
+
fs.mkdirSync(folder, { recursive: true });
|
|
18
|
+
}
|
|
19
|
+
let nameComponent =`Component-${Math.random().toString(36).substring(7)}`;
|
|
20
|
+
const nameParam = process.argv[2];
|
|
21
|
+
if (nameParam.length > 0 && typeof nameParam === "string") {
|
|
22
|
+
nameComponent = nameParam;
|
|
23
|
+
nameComponent = nameComponent.charAt(0).toUpperCase() + nameComponent.slice(1);
|
|
24
|
+
}
|
|
25
|
+
const filePath = path.join(folder, `${nameComponent}.jsx`);
|
|
26
|
+
const content = `import React from 'react';
|
|
27
|
+
|
|
28
|
+
export default function ${nameComponent}() {
|
|
29
|
+
return (
|
|
30
|
+
<div>
|
|
31
|
+
<h1>${nameComponent} Component</h1>
|
|
32
|
+
</div>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
`;
|
|
36
|
+
fs.writeFileSync(filePath, content);
|
|
37
|
+
console.log(`Component ${nameComponent} created at ${filePath}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const componentCLI = new ComponentCLI();
|
|
42
|
+
componentCLI.create();
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
import Config from "../config.js";
|
|
4
|
+
|
|
5
|
+
const __dirname = Config.dirname();
|
|
6
|
+
|
|
7
|
+
class RouteCLI {
|
|
8
|
+
/**
|
|
9
|
+
* Create route
|
|
10
|
+
*/
|
|
11
|
+
create() {
|
|
12
|
+
let nameRoute = process.argv[2];
|
|
13
|
+
if (!nameRoute) {
|
|
14
|
+
console.log("Please provide a route name. Usage: npm run route <route-name>");
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
nameRoute =nameRoute.charAt(0).toUpperCase() + nameRoute.slice(1);
|
|
18
|
+
const folder= path.join(__dirname, 'backend/routes');
|
|
19
|
+
if (!fs.existsSync(folder)){
|
|
20
|
+
fs.mkdirSync(folder, { recursive: true });
|
|
21
|
+
}
|
|
22
|
+
const filePath = path.join(folder, `${nameRoute}.js`);
|
|
23
|
+
if (fs.existsSync(filePath)) {
|
|
24
|
+
console.log(`Route ${nameRoute} already exists.`);
|
|
25
|
+
return;
|
|
26
|
+
}
|
|
27
|
+
const content =`import Router from "@seip/blue-bird/core/router.js"
|
|
28
|
+
|
|
29
|
+
const router${nameRoute} = new Router("/${nameRoute.toLowerCase()}");
|
|
30
|
+
|
|
31
|
+
router${nameRoute}.get("/", (req, res) => {
|
|
32
|
+
res.json({ message: "Hello from ${nameRoute} route!" });
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export default router${nameRoute};
|
|
36
|
+
`;
|
|
37
|
+
fs.writeFileSync(filePath, content);
|
|
38
|
+
console.log(`Route ${nameRoute} created successfully at ${filePath}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
const routeCLI = new RouteCLI();
|
|
43
|
+
routeCLI.create()
|
package/core/router.js
CHANGED
|
@@ -11,6 +11,11 @@ class Router {
|
|
|
11
11
|
/**
|
|
12
12
|
* Creates a new Router instance.
|
|
13
13
|
* @param {string} [path="/"] - The base path for this router.
|
|
14
|
+
* @example
|
|
15
|
+
* const router = new Router("/api")
|
|
16
|
+
* router.get("/", (req, res) => {
|
|
17
|
+
* res.json({ message: "Hello World!" })
|
|
18
|
+
* })
|
|
14
19
|
*/
|
|
15
20
|
constructor(path = "/") {
|
|
16
21
|
this.router = express.Router()
|
|
@@ -21,6 +26,20 @@ class Router {
|
|
|
21
26
|
* Registers a GET route handler.
|
|
22
27
|
* @param {string} path - The relative path for the GET route.
|
|
23
28
|
* @param {...Function} callback - One or more handler functions (middlewares and controller).
|
|
29
|
+
* @example
|
|
30
|
+
* router.get("/users", (req, res) => {
|
|
31
|
+
* const users = [
|
|
32
|
+
* {
|
|
33
|
+
* name: "John Doe",
|
|
34
|
+
* email: "john.doe@example.com",
|
|
35
|
+
* },
|
|
36
|
+
* {
|
|
37
|
+
* name: "Jane Doe2",
|
|
38
|
+
* email: "jane.doe2@example.com",
|
|
39
|
+
* },
|
|
40
|
+
* ]
|
|
41
|
+
* res.json(users)
|
|
42
|
+
* })
|
|
24
43
|
*/
|
|
25
44
|
get(path, ...callback) {
|
|
26
45
|
if (path === "/*" || path === "*") {
|
|
@@ -33,6 +52,10 @@ class Router {
|
|
|
33
52
|
* Registers a POST route handler.
|
|
34
53
|
* @param {string} path - The relative path for the POST route.
|
|
35
54
|
* @param {...Function} callback - One or more handler functions (middlewares and controller).
|
|
55
|
+
* @example
|
|
56
|
+
* router.post("/users", (req, res) => {
|
|
57
|
+
* return res.json({ message: "User created successfully" })
|
|
58
|
+
* })
|
|
36
59
|
*/
|
|
37
60
|
post(path, ...callback) {
|
|
38
61
|
if (path === "/*" || path === "*") {
|
|
@@ -45,6 +68,10 @@ class Router {
|
|
|
45
68
|
* Registers a PUT route handler.
|
|
46
69
|
* @param {string} path - The relative path for the PUT route.
|
|
47
70
|
* @param {...Function} callback - One or more handler functions (middlewares and controller).
|
|
71
|
+
* @example
|
|
72
|
+
* router.put("/users", (req, res) => {
|
|
73
|
+
* return res.json({ message: "User updated successfully" })
|
|
74
|
+
* })
|
|
48
75
|
*/
|
|
49
76
|
put(path, ...callback) {
|
|
50
77
|
this.router.put(path, callback)
|
|
@@ -54,6 +81,10 @@ class Router {
|
|
|
54
81
|
* Registers a DELETE route handler.
|
|
55
82
|
* @param {string} path - The relative path for the DELETE route.
|
|
56
83
|
* @param {...Function} callback - One or more handler functions (middlewares and controller).
|
|
84
|
+
* @example
|
|
85
|
+
* router.delete("/users", (req, res) => {
|
|
86
|
+
* return res.json({ message: "User deleted successfully" })
|
|
87
|
+
* })
|
|
57
88
|
*/
|
|
58
89
|
delete(path, ...callback) {
|
|
59
90
|
this.router.delete(path, callback)
|
|
@@ -63,6 +94,10 @@ class Router {
|
|
|
63
94
|
* Registers a PATCH route handler.
|
|
64
95
|
* @param {string} path - The relative path for the PATCH route.
|
|
65
96
|
* @param {...Function} callback - One or more handler functions (middlewares and controller).
|
|
97
|
+
* @example
|
|
98
|
+
* router.patch("/users", (req, res) => {
|
|
99
|
+
* return res.json({ message: "User patched successfully" })
|
|
100
|
+
* })
|
|
66
101
|
*/
|
|
67
102
|
patch(path, ...callback) {
|
|
68
103
|
this.router.patch(path, callback)
|
|
@@ -72,6 +107,10 @@ class Router {
|
|
|
72
107
|
* Registers an OPTIONS route handler.
|
|
73
108
|
* @param {string} path - The relative path for the OPTIONS route.
|
|
74
109
|
* @param {...Function} callback - One or more handler functions (middlewares and controller).
|
|
110
|
+
* @example
|
|
111
|
+
* router.options("/users", (req, res) => {
|
|
112
|
+
* return res.json({ message: "User options successfully" })
|
|
113
|
+
* })
|
|
75
114
|
*/
|
|
76
115
|
options(path, ...callback) {
|
|
77
116
|
this.router.options(path, callback)
|
package/core/template.js
CHANGED
|
@@ -74,22 +74,44 @@ class Template {
|
|
|
74
74
|
* Renders a React component as an HTML string.
|
|
75
75
|
* @param {string} component - The React component name.
|
|
76
76
|
* @param {Object} [componentProps={}] - Props to pass to the component.
|
|
77
|
-
* @
|
|
78
|
-
* @param {Array<Object>} [optionsHead=[]] - Array of objects with tag and attributes for head tags.
|
|
77
|
+
* @options {Object} options - Options for the template.
|
|
79
78
|
* @returns {string} The HTML string of the React component.
|
|
79
|
+
* @example
|
|
80
|
+
* const options = {
|
|
81
|
+
* head: [
|
|
82
|
+
* { tag: "meta", attrs: { name: "description", content: "Description" } },
|
|
83
|
+
* { tag: "link", attrs: { rel: "stylesheet", href: "style.css" } }
|
|
84
|
+
* ],
|
|
85
|
+
* classBody: "bg-gray-100",
|
|
86
|
+
* linkStyles: [
|
|
87
|
+
* { href: "style.css" }
|
|
88
|
+
* ],
|
|
89
|
+
* scriptScripts: [
|
|
90
|
+
* { src: "script.js" }
|
|
91
|
+
* ]
|
|
92
|
+
* };
|
|
93
|
+
*
|
|
94
|
+
* Template.renderReact(res, "App", { title: "Example title" }, options);
|
|
80
95
|
*/
|
|
81
|
-
static renderReact(res, component = "App", propsReact = {},
|
|
96
|
+
static renderReact(res, component = "App", propsReact = {}, options = {}) {
|
|
97
|
+
const optionsHead = options.head || [];
|
|
98
|
+
const classBody = options.classBody || "";
|
|
99
|
+
const linkStyles = options.linkStyles || [];
|
|
100
|
+
const scriptScripts = options.scriptScripts || [];
|
|
82
101
|
const html = `
|
|
83
102
|
<!DOCTYPE html>
|
|
84
|
-
<html lang="${props.langMeta}">
|
|
103
|
+
<html lang="${this.escapeHtml(props.langMeta)}">
|
|
85
104
|
<head>
|
|
86
105
|
<meta charset="UTF-8">
|
|
87
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0"
|
|
88
|
-
<title>${props.titleMeta}</title>
|
|
89
|
-
<
|
|
90
|
-
<meta name="
|
|
91
|
-
<meta name="
|
|
92
|
-
|
|
106
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
107
|
+
<title>${this.escapeHtml(props.titleMeta)}</title>
|
|
108
|
+
<link rel="icon" href="favicon.ico" />
|
|
109
|
+
<meta name="description" content="${this.escapeHtml(props.descriptionMeta)}"/>
|
|
110
|
+
<meta name="keywords" content="${this.escapeHtml(props.keywordsMeta)}"/>
|
|
111
|
+
<meta name="author" content="${this.escapeHtml(props.authorMeta)}"/>
|
|
112
|
+
${linkStyles.map(item => `<link rel="stylesheet" href="${item.href}" />`).join("")}
|
|
113
|
+
${scriptScripts.map(item => `<script src="${item.src}"></script>`).join("")}
|
|
114
|
+
${optionsHead.map(item => `<${item.tag} ${Object.entries(item.attrs).map(([key, value]) => `${key}="${value}"`).join(" ")} />`).join("")}
|
|
93
115
|
</head>
|
|
94
116
|
<body class="${classBody}">
|
|
95
117
|
${this.react(component, propsReact)}
|
|
@@ -99,7 +121,6 @@ class Template {
|
|
|
99
121
|
`
|
|
100
122
|
res.type("text/html");
|
|
101
123
|
res.status(200);
|
|
102
|
-
res.setHeader("Content-Type", "text/html");
|
|
103
124
|
return res.send(this.minifyHtml(html));
|
|
104
125
|
}
|
|
105
126
|
|
|
@@ -109,8 +130,8 @@ class Template {
|
|
|
109
130
|
* @param {Object} [componentProps={}] - Props to pass to the component.
|
|
110
131
|
* @returns {string} The HTML container with data attributes for hydration.
|
|
111
132
|
*/
|
|
112
|
-
static react(component, componentProps = {}) {
|
|
113
|
-
const id = `react-${Math.random().toString(36).substr(2, 9)}`;
|
|
133
|
+
static react(component, componentProps = {}, divId = "root") {
|
|
134
|
+
const id = divId || `react-${Math.random().toString(36).substr(2, 9)}`;
|
|
114
135
|
const propsJson = JSON.stringify(componentProps).replace(/'/g, "'");
|
|
115
136
|
return `<div id="${id}" data-react-component="${component}" data-props='${propsJson}'></div>`;
|
|
116
137
|
}
|
|
@@ -151,10 +172,12 @@ class Template {
|
|
|
151
172
|
const file = entry.file;
|
|
152
173
|
const css = entry.css || [];
|
|
153
174
|
|
|
154
|
-
let html =
|
|
175
|
+
let html = "";
|
|
155
176
|
css.forEach(cssFile => {
|
|
156
177
|
html += `<link rel="stylesheet" href="/build/${cssFile}">`;
|
|
157
178
|
});
|
|
179
|
+
html += `<script type="module" src="/build/${file}"></script>`;
|
|
180
|
+
|
|
158
181
|
return html;
|
|
159
182
|
}
|
|
160
183
|
} catch (e) {
|
|
@@ -165,46 +188,7 @@ class Template {
|
|
|
165
188
|
return `<!-- Vite Manifest not found at ${manifestPath} -->`;
|
|
166
189
|
}
|
|
167
190
|
|
|
168
|
-
/**
|
|
169
|
-
* Renders a full HTML page with a React component as the main entry point.
|
|
170
|
-
* Useful for SPAs or full-page React modules.
|
|
171
|
-
* @param {import('express').Response} res - The Express response object.
|
|
172
|
-
* @param {string} component - The React component name.
|
|
173
|
-
* @param {Object} [componentProps={}] - Props to pass to the React component.
|
|
174
|
-
* @param {Object} [metaOverrides={}] - Metadata overrides (title, description, keywords, etc.).
|
|
175
|
-
*/
|
|
176
|
-
static reactRender(res, component, componentProps = {}, metaOverrides = {}) {
|
|
177
|
-
const meta = {
|
|
178
|
-
title: props.titleMeta,
|
|
179
|
-
description: props.descriptionMeta,
|
|
180
|
-
keywords: props.keywordsMeta,
|
|
181
|
-
author: props.authorMeta,
|
|
182
|
-
lang: props.langMeta,
|
|
183
|
-
...metaOverrides
|
|
184
|
-
};
|
|
185
|
-
|
|
186
|
-
const scriptsReact = this.vite_assets();
|
|
187
|
-
const componentHtml = this.react(component, componentProps);
|
|
188
191
|
|
|
189
|
-
const html = `
|
|
190
|
-
<!DOCTYPE html>
|
|
191
|
-
<html lang="${meta.lang}">
|
|
192
|
-
<head>
|
|
193
|
-
<meta charset="UTF-8">
|
|
194
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
195
|
-
<title>${meta.title}</title>
|
|
196
|
-
<meta name="description" content="${meta.description}">
|
|
197
|
-
<meta name="keywords" content="${meta.keywords}">
|
|
198
|
-
<meta name="author" content="${meta.author}">
|
|
199
|
-
${scriptsReact}
|
|
200
|
-
</head>
|
|
201
|
-
<body>
|
|
202
|
-
${componentHtml}
|
|
203
|
-
</body>
|
|
204
|
-
</html>`;
|
|
205
|
-
|
|
206
|
-
res.send(this.minifyHtml(html));
|
|
207
|
-
}
|
|
208
192
|
|
|
209
193
|
/**
|
|
210
194
|
* Minifies the HTML output by removing comments and excessive whitespace.
|
|
@@ -218,6 +202,19 @@ class Template {
|
|
|
218
202
|
.replace(/\s{2,}/g, " ")
|
|
219
203
|
.trim();
|
|
220
204
|
}
|
|
205
|
+
/**
|
|
206
|
+
* Escapes HTML special characters in a string to prevent XSS attacks.
|
|
207
|
+
* @param {string} str - The input string.
|
|
208
|
+
* @returns {string} The escaped string.
|
|
209
|
+
*/
|
|
210
|
+
static escapeHtml(str = "") {
|
|
211
|
+
return String(str)
|
|
212
|
+
.replace(/&/g, "&")
|
|
213
|
+
.replace(/</g, "<")
|
|
214
|
+
.replace(/>/g, ">")
|
|
215
|
+
.replace(/"/g, """)
|
|
216
|
+
.replace(/'/g, "'");
|
|
217
|
+
}
|
|
221
218
|
}
|
|
222
219
|
|
|
223
220
|
export default Template;
|
package/core/upload.js
CHANGED
|
@@ -14,6 +14,8 @@ class Upload {
|
|
|
14
14
|
* Configures storage for uploaded files.
|
|
15
15
|
* @param {string} folder - The destination folder within the static path.
|
|
16
16
|
* @returns {import('multer').StorageEngine}
|
|
17
|
+
* @example
|
|
18
|
+
* const storage = Upload.storage("uploads");
|
|
17
19
|
*/
|
|
18
20
|
static storage(folder = "uploads") {
|
|
19
21
|
const dest = path.join(__dirname, props.static.path, folder);
|
|
@@ -40,6 +42,8 @@ class Upload {
|
|
|
40
42
|
* @param {number} [options.fileSize=5000000] - Max file size in bytes (default 5MB).
|
|
41
43
|
* @param {Array<string>} [options.allowedTypes=[]] - Allowed mime types (e.g. ['image/png', 'image/jpeg']).
|
|
42
44
|
* @returns {import('multer').Multer}
|
|
45
|
+
* @example
|
|
46
|
+
* const upload = Upload.disk({ folder: "uploads", fileSize: 5000000, allowedTypes: ["image/png", "image/jpeg"] });
|
|
43
47
|
*/
|
|
44
48
|
static disk(options = {}) {
|
|
45
49
|
const { folder = "uploads", fileSize = 5000000, allowedTypes = [] } = options;
|
|
@@ -61,6 +65,8 @@ class Upload {
|
|
|
61
65
|
* @param {string} filename - The name of the file.
|
|
62
66
|
* @param {string} [folder='uploads'] - The folder where the file is stored.
|
|
63
67
|
* @returns {string} The full public URL.
|
|
68
|
+
* @example
|
|
69
|
+
* const url = Upload.url("file.jpg", "uploads");
|
|
64
70
|
*/
|
|
65
71
|
static url(filename, folder = "uploads") {
|
|
66
72
|
return `${props.host}:${props.port}/public/${folder}/${filename}`;
|
package/core/validate.js
CHANGED
|
@@ -128,6 +128,13 @@ class Validator {
|
|
|
128
128
|
* Validates the request body against the defined schema.
|
|
129
129
|
* @param {import('express').Request} req - The Express request object containing the body to validate.
|
|
130
130
|
* @returns {Promise<{success: boolean, error: boolean, errors: Array<{field: string, message: string}>, message: Array<string>, html: Array<string>}>} Validation results.
|
|
131
|
+
* @example
|
|
132
|
+
* const loginSchema = {
|
|
133
|
+
* email: { required: true, email: true },
|
|
134
|
+
* password: { required: true, min: 6 }
|
|
135
|
+
* };
|
|
136
|
+
* const loginValidator = new Validator(loginSchema, 'es');
|
|
137
|
+
* const result = await loginValidator.validate(req);
|
|
131
138
|
*/
|
|
132
139
|
async validate(req) {
|
|
133
140
|
const lang = this.lang_default ? this.lang_default : req?.session?.lang || "es";
|
|
@@ -260,6 +267,16 @@ class Validator {
|
|
|
260
267
|
* Express middleware for automated validation of the request body.
|
|
261
268
|
* Returns a 400 Bad Request response with validation results if errors occur.
|
|
262
269
|
* @returns {Function} Express middleware function (req, res, next).
|
|
270
|
+
* @example
|
|
271
|
+
*
|
|
272
|
+
* const loginSchema = {
|
|
273
|
+
* email: { required: true, email: true },
|
|
274
|
+
* password: { required: true, min: 6 }
|
|
275
|
+
* };
|
|
276
|
+
* const loginValidator = new Validator(loginSchema, 'es');
|
|
277
|
+
* routerUsers.post('/login', loginValidator.middleware(), (req, res) => {
|
|
278
|
+
* res.json({ message: 'Login successful' });
|
|
279
|
+
* });
|
|
263
280
|
*/
|
|
264
281
|
middleware() {
|
|
265
282
|
return async (req, res, next) => {
|
|
Binary file
|
|
File without changes
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seip/blue-bird",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Express + React opinionated framework with island architecture and built-in JWT auth",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -28,6 +28,8 @@
|
|
|
28
28
|
"start": "node --env-file=.env backend/index.js",
|
|
29
29
|
"create-react-app": "node core/cli/react.js",
|
|
30
30
|
"react": "node core/cli/react.js",
|
|
31
|
+
"component": "node core/cli/component.js",
|
|
32
|
+
"route": "node core/cli/route.js",
|
|
31
33
|
"init": "node core/cli/init.js"
|
|
32
34
|
},
|
|
33
35
|
"dependencies": {
|
|
@@ -39,4 +41,4 @@
|
|
|
39
41
|
"jsonwebtoken": "^9.0.2",
|
|
40
42
|
"multer": "^2.0.2"
|
|
41
43
|
}
|
|
42
|
-
}
|
|
44
|
+
}
|