@seip/blue-bird 0.4.6 → 0.4.7
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 +12 -10
- package/core/auth.js +32 -4
- package/core/config.js +4 -1
- package/core/seo.js +1 -1
- package/core/template.js +6 -0
- package/core/upload.js +2 -1
- package/frontend/index.html +6 -1
- package/frontend/landing.html +1 -0
- package/package.json +57 -57
package/core/app.js
CHANGED
|
@@ -160,7 +160,7 @@ class App {
|
|
|
160
160
|
this.app.use(middleware);
|
|
161
161
|
});
|
|
162
162
|
|
|
163
|
-
if (this.logger) this._middlewareLogger();
|
|
163
|
+
if (this.logger || props.debug) this._middlewareLogger(this.logger);
|
|
164
164
|
|
|
165
165
|
this.app.use((req, res, next) => {
|
|
166
166
|
res.setHeader("X-Powered-By", "Blue Bird");
|
|
@@ -202,13 +202,14 @@ class App {
|
|
|
202
202
|
* Middleware that logs incoming HTTP requests to the console and to a log file.
|
|
203
203
|
* @private
|
|
204
204
|
*/
|
|
205
|
-
_middlewareLogger() {
|
|
205
|
+
_middlewareLogger(logger = false) {
|
|
206
206
|
this.app.use((req, res, next) => {
|
|
207
207
|
const method = req.method;
|
|
208
208
|
const url = req.url.replace(
|
|
209
209
|
/(password|token|authorization)=([^&]+)/gi,
|
|
210
210
|
"$1=***",
|
|
211
211
|
);
|
|
212
|
+
if (url.includes("chrome")) return;
|
|
212
213
|
const params =
|
|
213
214
|
Object.keys(req.params).length > 0
|
|
214
215
|
? ` ${JSON.stringify(req.params)}`
|
|
@@ -218,7 +219,8 @@ class App {
|
|
|
218
219
|
const time = `${now.split("T")[0]} ${now.split("T")[1].split(".")[0]}`;
|
|
219
220
|
let message = ` ${time} -${ip} -[${method}] ${url} ${params}`;
|
|
220
221
|
|
|
221
|
-
this.loggerInstance.info(message);
|
|
222
|
+
if (logger) this.loggerInstance.info(message);
|
|
223
|
+
|
|
222
224
|
if (props.debug) {
|
|
223
225
|
message = `${chalk.bold.green(time)} - ${chalk.bold.cyan(ip)} -[${chalk.bold.red(method)}] ${chalk.bold.blue(url)} ${chalk.bold.yellow(params)}`;
|
|
224
226
|
console.log(message);
|
|
@@ -290,13 +292,13 @@ class App {
|
|
|
290
292
|
this.app.listen(this.port, () => {
|
|
291
293
|
console.log(
|
|
292
294
|
chalk.bold.blue("Blue Bird Server Online\n") +
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
295
|
+
chalk.bold.cyan("App URL: ") +
|
|
296
|
+
chalk.green(`${this.appUrl}`) +
|
|
297
|
+
"\n" +
|
|
298
|
+
chalk.bold.cyan("Internal: ") +
|
|
299
|
+
chalk.green(`${this.host}:${this.port}`) +
|
|
300
|
+
"\n" +
|
|
301
|
+
chalk.gray("────────────────────────────────"),
|
|
300
302
|
);
|
|
301
303
|
});
|
|
302
304
|
})
|
package/core/auth.js
CHANGED
|
@@ -94,21 +94,49 @@ class Auth {
|
|
|
94
94
|
const token =
|
|
95
95
|
req.cookies?.auth || req.headers.authorization?.split(" ")[1];
|
|
96
96
|
|
|
97
|
+
const isContentTypeJson = req.headers["content-type"] === "application/json";
|
|
98
|
+
|
|
97
99
|
if (!token) {
|
|
98
|
-
if (options.redirect) return res.redirect(options.redirect);
|
|
99
|
-
return res.status(401).json({ message: "Unauthorized" });
|
|
100
|
+
if (options.redirect && !isContentTypeJson) return res.redirect(options.redirect);
|
|
101
|
+
return isContentTypeJson ? res.status(401).json({ message: "Unauthorized" }) : res.status(401).send();
|
|
100
102
|
}
|
|
101
103
|
|
|
102
104
|
const decoded = this.verifyToken(token);
|
|
103
105
|
if (!decoded) {
|
|
104
|
-
if (options.redirect) return res.redirect(options.redirect);
|
|
105
|
-
return res.status(401).json({ message: "Unauthorized" });
|
|
106
|
+
if (options.redirect && !isContentTypeJson) return res.redirect(options.redirect);
|
|
107
|
+
return isContentTypeJson ? res.status(401).json({ message: "Unauthorized" }) : res.status(401).send();
|
|
106
108
|
}
|
|
107
109
|
|
|
108
110
|
req[options.key || "user"] = decoded;
|
|
109
111
|
next();
|
|
110
112
|
};
|
|
111
113
|
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Logs in a user by setting an authentication cookie.
|
|
117
|
+
* @param {import('express').Response} res - The response object.
|
|
118
|
+
* @param {Object} data - The data to store in the token.
|
|
119
|
+
* @param {string} [key="auth"] - The key for the cookie.
|
|
120
|
+
* @param {Object} [options={cookie: {maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict"}}] - Options for the cookie.
|
|
121
|
+
* @returns {string} The generated token.
|
|
122
|
+
*/
|
|
123
|
+
static login(res, data, key = "auth", options = { cookie: { maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict" } }) {
|
|
124
|
+
const token = this.generateToken(data);
|
|
125
|
+
res.cookie(key, token, options.cookie);
|
|
126
|
+
return token;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Logs out a user by clearing the authentication cookie.
|
|
131
|
+
* @param {import('express').Response} res - The response object.
|
|
132
|
+
* @param {string} [key="auth"] - The key for the cookie.
|
|
133
|
+
* @param {Object} [options={cookie: {maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict"}}] - Options for the cookie.
|
|
134
|
+
* @returns {boolean} True if the cookie was cleared successfully.
|
|
135
|
+
*/
|
|
136
|
+
static logout(res, key = "auth", options = { cookie: { maxAge: 60 * 60 * 1000, httpOnly: true, secure: true, sameSite: "strict" } }) {
|
|
137
|
+
res.clearCookie(key, options.cookie);
|
|
138
|
+
return true;
|
|
139
|
+
}
|
|
112
140
|
}
|
|
113
141
|
|
|
114
142
|
export default Auth;
|
package/core/config.js
CHANGED
|
@@ -17,7 +17,10 @@ class Config {
|
|
|
17
17
|
/**
|
|
18
18
|
* Retrieves application properties from environment variables or default values.
|
|
19
19
|
* Results are cached after first call for performance.
|
|
20
|
-
* @returns {Object} The configuration properties object.
|
|
20
|
+
* @returns {{debug: boolean, descriptionMeta: string, keywordsMeta: string, titleMeta: string, authorMeta: string, description: string, title: string, version: string, langMeta: string, host: string, appUrl: string, port: number, static: {path: string, options: Object}}} The configuration properties object.
|
|
21
|
+
* @example
|
|
22
|
+
* const props = Config.props();
|
|
23
|
+
* console.log(props);
|
|
21
24
|
*/
|
|
22
25
|
static props() {
|
|
23
26
|
if (_cachedProps) return _cachedProps;
|
package/core/seo.js
CHANGED
|
@@ -20,7 +20,7 @@ class SEO {
|
|
|
20
20
|
*/
|
|
21
21
|
static generateSitemap(routesConfig, options = {}) {
|
|
22
22
|
const { languages = [], defaultLanguage = "en" } = options;
|
|
23
|
-
const host = (props.appUrl ||
|
|
23
|
+
const host = (props.appUrl || `${props.host}:${props.port}`).replace(/\/$/, "");
|
|
24
24
|
const baseUrl = `${host}`;
|
|
25
25
|
const date = new Date().toISOString().split("T")[0];
|
|
26
26
|
|
package/core/template.js
CHANGED
|
@@ -184,6 +184,8 @@ class Template {
|
|
|
184
184
|
: "";
|
|
185
185
|
const skeletonHtml = skeleton ? this.skeletonHtml() : "";
|
|
186
186
|
|
|
187
|
+
const canonicalUrl = metaTags.canonicalUrl || props.appUrl || "";
|
|
188
|
+
|
|
187
189
|
const ogTags = `
|
|
188
190
|
<meta property="og:title" content="${title}" />
|
|
189
191
|
<meta property="og:description" content="${description}" />
|
|
@@ -210,6 +212,7 @@ class Template {
|
|
|
210
212
|
.replace(/__VITE_ASSETS__/g, this.vite_assets())
|
|
211
213
|
.replace(/__SCRIPTS_BODY__/g, scriptsBodyTags)
|
|
212
214
|
.replace(/__STYLES_SKELETON__/g, stylesSkeleton)
|
|
215
|
+
.replace(/__CANONICAL_URL__/g, canonicalUrl)
|
|
213
216
|
.replace(/__SKELETON__/g, skeletonHtml);
|
|
214
217
|
|
|
215
218
|
html = this.minifyHtml(html);
|
|
@@ -338,6 +341,8 @@ class Template {
|
|
|
338
341
|
${ogImage ? `<meta name="twitter:image" content="${ogImage}" />` : ""}
|
|
339
342
|
`;
|
|
340
343
|
|
|
344
|
+
const canonicalUrl = metaTags.canonicalUrl || props.appUrl || "";
|
|
345
|
+
|
|
341
346
|
html = html
|
|
342
347
|
.replace(/__LANG__/g, this.escapeHtml(langHtml))
|
|
343
348
|
.replace(/__TITLE__/g, title)
|
|
@@ -347,6 +352,7 @@ class Template {
|
|
|
347
352
|
.replace(/__HEAD_OPTIONS__/g, headOptions + ogTags)
|
|
348
353
|
.replace(/__CLASS_BODY__/g, classBody)
|
|
349
354
|
.replace(/__VITE_ASSETS__/g, withAssets ? this.vite_assets() : "")
|
|
355
|
+
.replace(/__CANONICAL_URL__/g, canonicalUrl)
|
|
350
356
|
.replace(/__STYLES_SKELETON__/g, "");
|
|
351
357
|
|
|
352
358
|
if (html.includes("__LINK_STYLES__")) {
|
package/core/upload.js
CHANGED
|
@@ -69,7 +69,8 @@ class Upload {
|
|
|
69
69
|
* const url = Upload.url("file.jpg", "uploads");
|
|
70
70
|
*/
|
|
71
71
|
static url(filename, folder = "uploads") {
|
|
72
|
-
|
|
72
|
+
const appUrl = props.appUrl ?? `${props.host}:${props.port}`;
|
|
73
|
+
return `${appUrl}/${folder}/${filename}`;
|
|
73
74
|
}
|
|
74
75
|
}
|
|
75
76
|
|
package/frontend/index.html
CHANGED
|
@@ -1,22 +1,27 @@
|
|
|
1
1
|
<!DOCTYPE html>
|
|
2
2
|
<html lang="__LANG__">
|
|
3
|
+
|
|
3
4
|
<head>
|
|
4
5
|
<meta charset="UTF-8">
|
|
5
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
7
|
<title>__TITLE__</title>
|
|
8
|
+
<link rel="canonical" href="__CANONICAL_URL__" />
|
|
7
9
|
<link rel="icon" href="/favicon.ico" />
|
|
8
10
|
<meta name="description" content="__DESCRIPTION__" />
|
|
9
11
|
<meta name="keywords" content="__KEYWORDS__" />
|
|
10
12
|
<meta name="author" content="__AUTHOR__" />
|
|
11
13
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
12
14
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
13
|
-
<link
|
|
15
|
+
<link
|
|
16
|
+
href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap"
|
|
17
|
+
rel="stylesheet">
|
|
14
18
|
__HEAD_OPTIONS__
|
|
15
19
|
__LINK_STYLES__
|
|
16
20
|
__SCRIPTS_HEAD__
|
|
17
21
|
__VITE_ASSETS__
|
|
18
22
|
__STYLES_SKELETON__
|
|
19
23
|
</head>
|
|
24
|
+
|
|
20
25
|
<body class="__CLASS_BODY__">
|
|
21
26
|
<div id="root" data-react-component="__COMPONENT__" data-props='__PROPS__'>
|
|
22
27
|
__SKELETON__
|
package/frontend/landing.html
CHANGED
|
@@ -5,6 +5,7 @@
|
|
|
5
5
|
<meta charset="UTF-8">
|
|
6
6
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
7
7
|
<title>__TITLE__</title>
|
|
8
|
+
<link rel="canonical" href="__CANONICAL_URL__" />
|
|
8
9
|
<meta name="description" content="__DESCRIPTION__">
|
|
9
10
|
<meta name="keywords" content="__KEYWORDS__">
|
|
10
11
|
<meta name="author" content="__AUTHOR__">
|
package/package.json
CHANGED
|
@@ -1,58 +1,58 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@seip/blue-bird",
|
|
3
|
-
"version": "0.4.
|
|
4
|
-
"description": "Express + React opinionated framework with SPA or API architecture and built-in JWT auth",
|
|
5
|
-
"type": "module",
|
|
6
|
-
"bin": {
|
|
7
|
-
"blue-bird": "core/cli/init.js"
|
|
8
|
-
},
|
|
9
|
-
"repository": {
|
|
10
|
-
"type": "git",
|
|
11
|
-
"url": "git+https://github.com/seip25/Blue-bird.git"
|
|
12
|
-
},
|
|
13
|
-
"keywords": [
|
|
14
|
-
"express",
|
|
15
|
-
"react",
|
|
16
|
-
"framework",
|
|
17
|
-
"vite"
|
|
18
|
-
],
|
|
19
|
-
"author": "Seip25",
|
|
20
|
-
"license": "MIT",
|
|
21
|
-
"bugs": {
|
|
22
|
-
"url": "https://github.com/seip25/Blue-bird/issues"
|
|
23
|
-
},
|
|
24
|
-
"homepage": "https://seip25.github.io/Blue-bird/",
|
|
25
|
-
"scripts": {
|
|
26
|
-
"dev": "node --watch --env-file=.env backend/index.js",
|
|
27
|
-
"start": "node --env-file=.env backend/index.js",
|
|
28
|
-
"create-react-app": "node core/cli/react.js",
|
|
29
|
-
"react": "node core/cli/react.js",
|
|
30
|
-
"init": "node core/cli/init.js",
|
|
31
|
-
"route": "node core/cli/route.js",
|
|
32
|
-
"component": "node core/cli/component.js",
|
|
33
|
-
"swagger-install": "node core/cli/swagger.js",
|
|
34
|
-
"vite:dev": "vite",
|
|
35
|
-
"vite:build": "vite build"
|
|
36
|
-
},
|
|
37
|
-
"dependencies": {
|
|
38
|
-
"@tailwindcss/vite": "^4.2.2",
|
|
39
|
-
"chalk": "^5.6.2",
|
|
40
|
-
"compression": "^1.8.1",
|
|
41
|
-
"cookie-parser": "^1.4.7",
|
|
42
|
-
"cors": "^2.8.6",
|
|
43
|
-
"express": "^5.2.1",
|
|
44
|
-
"express-rate-limit": "^8.2.1",
|
|
45
|
-
"helmet": "^8.1.0",
|
|
46
|
-
"jsonwebtoken": "^9.0.2",
|
|
47
|
-
"multer": "^2.0.2",
|
|
48
|
-
"react": "^19.2.4",
|
|
49
|
-
"react-dom": "^19.2.4",
|
|
50
|
-
"react-router-dom": "^7.2.0",
|
|
51
|
-
"tailwindcss": "^4.2.2",
|
|
52
|
-
"xss": "^1.0.15"
|
|
53
|
-
},
|
|
54
|
-
"devDependencies": {
|
|
55
|
-
"@vitejs/plugin-react": "^4.3.4",
|
|
56
|
-
"vite": "^7.3.1"
|
|
57
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@seip/blue-bird",
|
|
3
|
+
"version": "0.4.7",
|
|
4
|
+
"description": "Express + React opinionated framework with SPA or API architecture and built-in JWT auth",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"blue-bird": "core/cli/init.js"
|
|
8
|
+
},
|
|
9
|
+
"repository": {
|
|
10
|
+
"type": "git",
|
|
11
|
+
"url": "git+https://github.com/seip25/Blue-bird.git"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"express",
|
|
15
|
+
"react",
|
|
16
|
+
"framework",
|
|
17
|
+
"vite"
|
|
18
|
+
],
|
|
19
|
+
"author": "Seip25",
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"bugs": {
|
|
22
|
+
"url": "https://github.com/seip25/Blue-bird/issues"
|
|
23
|
+
},
|
|
24
|
+
"homepage": "https://seip25.github.io/Blue-bird/",
|
|
25
|
+
"scripts": {
|
|
26
|
+
"dev": "node --watch --env-file=.env backend/index.js",
|
|
27
|
+
"start": "node --env-file=.env backend/index.js",
|
|
28
|
+
"create-react-app": "node core/cli/react.js",
|
|
29
|
+
"react": "node core/cli/react.js",
|
|
30
|
+
"init": "node core/cli/init.js",
|
|
31
|
+
"route": "node core/cli/route.js",
|
|
32
|
+
"component": "node core/cli/component.js",
|
|
33
|
+
"swagger-install": "node core/cli/swagger.js",
|
|
34
|
+
"vite:dev": "vite",
|
|
35
|
+
"vite:build": "vite build"
|
|
36
|
+
},
|
|
37
|
+
"dependencies": {
|
|
38
|
+
"@tailwindcss/vite": "^4.2.2",
|
|
39
|
+
"chalk": "^5.6.2",
|
|
40
|
+
"compression": "^1.8.1",
|
|
41
|
+
"cookie-parser": "^1.4.7",
|
|
42
|
+
"cors": "^2.8.6",
|
|
43
|
+
"express": "^5.2.1",
|
|
44
|
+
"express-rate-limit": "^8.2.1",
|
|
45
|
+
"helmet": "^8.1.0",
|
|
46
|
+
"jsonwebtoken": "^9.0.2",
|
|
47
|
+
"multer": "^2.0.2",
|
|
48
|
+
"react": "^19.2.4",
|
|
49
|
+
"react-dom": "^19.2.4",
|
|
50
|
+
"react-router-dom": "^7.2.0",
|
|
51
|
+
"tailwindcss": "^4.2.2",
|
|
52
|
+
"xss": "^1.0.15"
|
|
53
|
+
},
|
|
54
|
+
"devDependencies": {
|
|
55
|
+
"@vitejs/plugin-react": "^4.3.4",
|
|
56
|
+
"vite": "^7.3.1"
|
|
57
|
+
}
|
|
58
58
|
}
|