@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 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
- chalk.bold.cyan("App URL: ") +
294
- chalk.green(`${this.appUrl}`) +
295
- "\n" +
296
- chalk.bold.cyan("Internal: ") +
297
- chalk.green(`${this.host}:${this.port}`) +
298
- "\n" +
299
- chalk.gray("────────────────────────────────"),
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 || "http://localhost").replace(/\/$/, "");
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
- return `${props.host}:${props.port}/${folder}/${filename}`;
72
+ const appUrl = props.appUrl ?? `${props.host}:${props.port}`;
73
+ return `${appUrl}/${folder}/${filename}`;
73
74
  }
74
75
  }
75
76
 
@@ -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 href="https://fonts.googleapis.com/css2?family=Outfit:wght@100..900&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap" rel="stylesheet">
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__
@@ -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.6",
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
  }