@seip/blue-bird 0.7.4 → 0.7.6

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/database.js CHANGED
@@ -1,47 +1,96 @@
1
1
  import crypto from "node:crypto";
2
2
  import { getRedisClient } from "./cache.js";
3
3
 
4
+ let DB_TYPE = (process.env.DB_TYPE || "").toLowerCase();
5
+
6
+ if (
7
+ !DB_TYPE &&
8
+ process.env.DATABASE_URL &&
9
+ !process.env.DATABASE_URL.startsWith("#")
10
+ ) {
11
+ if (
12
+ process.env.DATABASE_URL.startsWith("postgres://") ||
13
+ process.env.DATABASE_URL.startsWith("postgresql://")
14
+ ) {
15
+ DB_TYPE = "postgres";
16
+ } else if (process.env.DATABASE_URL.startsWith("mysql://")) {
17
+ DB_TYPE = "mysql";
18
+ }
19
+ }
20
+ if (!DB_TYPE) {
21
+ DB_TYPE = "mysql";
22
+ }
23
+
4
24
  let mysqlPromise = null;
5
- try {
6
- mysqlPromise = await import("mysql2/promise");
7
- } catch (err) {
8
- console.error(
9
- "[DATABASE ERROR] mysql2 package is not installed. Database wrapper is disabled.",
10
- );
25
+ let pgPromise = null;
26
+
27
+ if (DB_TYPE === "postgres") {
28
+ try {
29
+ pgPromise = await import("pg");
30
+ } catch (err) {
31
+ console.error(
32
+ "[DATABASE ERROR] pg package is not installed. Database wrapper is disabled.",
33
+ );
34
+ }
35
+ } else if (DB_TYPE === "mysql") {
36
+ try {
37
+ mysqlPromise = await import("mysql2/promise");
38
+ } catch (err) {
39
+ console.error(
40
+ "[DATABASE ERROR] mysql2 package is not installed. Database wrapper is disabled.",
41
+ );
42
+ }
11
43
  }
12
44
 
13
45
  /**
14
- * Database class wrapping mysql2 with reconnection retries, connection pool, and query caching.
46
+ * Database class wrapping mysql2 and pg with reconnection retries, connection pooling, and query caching.
15
47
  */
16
48
  class Database {
17
49
  /**
18
50
  * Initializes config from DATABASE_URL or DB_* environment variables.
51
+ * For default Database use .env DB_HOST, DB_USER, DB_PASSWORD...
52
+ * @param {number} [connectionLimit=10] - Maximum number of connections in the pool.
53
+ * @param {number} [queueLimit=0] - Maximum number of queued connections.
54
+ * @param {Object} [config={}] - Additional configuration options: DB_HOST, DB_USER, DB_PASSWORD, DB_NAME, DB_PORT, DB_TYPE.
55
+ * @example const connection = new Database(10, 0, { DB_HOST: "localhost", DB_USER: "root", DB_PASSWORD: "password", DB_NAME: "blue_bird", DB_PORT: 3306, DB_TYPE: "mysql" });
19
56
  */
20
- constructor(connectionLimit = 10, queueLimit = 0) {
57
+ constructor(connectionLimit = 10, queueLimit = 0, config = {}) {
21
58
  this.pool = null;
59
+ this.type = DB_TYPE;
60
+
22
61
  this.config = {
62
+ ...config,
23
63
  host: process.env.DB_HOST || "localhost",
24
- user: process.env.DB_USER || "root",
64
+ user:
65
+ process.env.DB_USER || (this.type === "postgres" ? "postgres" : "root"),
25
66
  password: process.env.DB_PASSWORD || "root",
26
67
  database: process.env.DB_NAME || "blue_bird",
27
- port: parseInt(process.env.DB_PORT) || 3306,
68
+ port:
69
+ parseInt(process.env.DB_PORT) ||
70
+ (this.type === "postgres" ? 5432 : 3306),
28
71
  charset: "utf8mb4",
29
72
  waitForConnections: true,
30
73
  connectionLimit: connectionLimit,
31
74
  queueLimit: queueLimit,
75
+ max: connectionLimit,
32
76
  };
33
77
 
34
- if (
35
- process.env.DATABASE_URL &&
36
- process.env.DATABASE_URL.startsWith("mysql://")
37
- ) {
78
+ if (process.env.DATABASE_URL && !process.env.DATABASE_URL.startsWith("#")) {
38
79
  try {
39
- const url = new URL(process.env.DATABASE_URL);
40
- this.config.host = url.hostname;
41
- this.config.port = parseInt(url.port) || 3306;
42
- this.config.user = url.username;
43
- this.config.password = url.password;
44
- this.config.database = url.pathname.substring(1);
80
+ if (
81
+ process.env.DATABASE_URL.startsWith("mysql://") ||
82
+ process.env.DATABASE_URL.startsWith("postgres://") ||
83
+ process.env.DATABASE_URL.startsWith("postgresql://")
84
+ ) {
85
+ const url = new URL(process.env.DATABASE_URL);
86
+ this.config.host = url.hostname;
87
+ this.config.port =
88
+ parseInt(url.port) || (this.type === "postgres" ? 5432 : 3306);
89
+ this.config.user = url.username;
90
+ this.config.password = url.password;
91
+ this.config.database = url.pathname.substring(1);
92
+ this.config.connectionString = process.env.DATABASE_URL;
93
+ }
45
94
  } catch (err) {
46
95
  console.error(
47
96
  "[DATABASE ERROR] Failed to parse DATABASE_URL:",
@@ -52,18 +101,25 @@ class Database {
52
101
  }
53
102
 
54
103
  /**
55
- * Creates the MySQL connection pool with 3 retry attempts on failure.
104
+ * Creates the database connection pool with 3 retry attempts on failure.
56
105
  * @param {number} [retries=3] - Number of connection attempts.
57
106
  * @returns {Promise<boolean>} True if connection pool was created.
58
107
  */
59
108
  async init(retries = 3) {
60
- if (!mysqlPromise) return false;
109
+ if (!mysqlPromise && !pgPromise) return false;
61
110
  if (this.pool) return true;
62
111
 
63
112
  for (let attempt = 1; attempt <= retries; attempt++) {
64
113
  try {
65
- this.pool = mysqlPromise.createPool(this.config);
66
- await this.pool.query("SELECT 1");
114
+ if (this.type === "postgres" && pgPromise) {
115
+ const pg = pgPromise.default || pgPromise;
116
+ this.pool = new pg.Pool(this.config);
117
+ await this.pool.query("SELECT 1");
118
+ } else if (mysqlPromise) {
119
+ const { max, connectionString, ...mysqlConfig } = this.config;
120
+ this.pool = mysqlPromise.createPool(mysqlConfig);
121
+ await this.pool.query("SELECT 1");
122
+ }
67
123
  return true;
68
124
  } catch (err) {
69
125
  this.pool = null;
@@ -82,22 +138,19 @@ class Database {
82
138
 
83
139
  /**
84
140
  * Runs a SQL query with parameters and formatting options.
141
+ * Supports both MySQL and PostgreSQL (converting ? to $1, $2 for Postgres automatically).
85
142
  *
86
143
  * @param {string} sql - SQL query string.
87
144
  * @param {Array} [params=[]] - Query parameter array.
88
145
  * @param {Object|string} [options={}] - Query options. Supports 'return_row', 'return_rows', and 'cache' (seconds).
89
146
  * @returns {Promise<*>| int | boolean} Formatted query result or false on error, or insert id of insert query.
90
- * @example select
91
- * const result = await connection.query("SELECT * FROM users", [], { return_row: true, cache: 60 });
92
- * @example insert
93
- * const result = await connection.query("INSERT INTO users (name, email, password) VALUES (?, ?, ?)", ["John Doe", "[EMAIL_ADDRESS]", "123456"]);
94
- * @example update
95
- * const result = await connection.query("UPDATE users SET name = ? WHERE id = ?", ["John Doe", 1]);
96
- * @example delete
97
- * const result = await connection.query("DELETE FROM users WHERE id = ?", [1]);
147
+ * @example const result = await connection.query("SELECT * FROM users WHERE id = ?", [1], "return_row");
148
+ * @example const result = await connection.query("SELECT * FROM users WHERE id = ?", [1], { cache: 60 });
149
+ * @example const result = await connection.query("SELECT * FROM users WHERE id = ?", [1], { debug: true });
150
+ * @example const insert_id = await connection.query("INSERT INTO users (name, email, password) VALUES (?, ?, ?)", ["John Doe", "john@example.com", "password"]);
98
151
  */
99
152
  async query(sql, params = [], options = {}) {
100
- if (!mysqlPromise) return false;
153
+ if (!mysqlPromise && !pgPromise) return false;
101
154
  if (!this.pool) {
102
155
  const initialized = await this.init();
103
156
  if (!initialized) return false;
@@ -126,17 +179,17 @@ class Database {
126
179
  cacheKey = `db:${hash}`;
127
180
  try {
128
181
  if (isDebug) {
129
- console.log("[DATABASE DEBUG ][Redis] CACHE KEY:", cacheKey);
182
+ console.log("[DATABASE DEBUG][Redis] CACHE KEY:", cacheKey);
130
183
  }
131
184
  const cached = await redisClient.get(cacheKey);
132
185
  if (cached) {
133
186
  if (isDebug) {
134
- console.log("[DATABASE DEBUG ][Redis] CACHE HIT");
187
+ console.log("[DATABASE DEBUG][Redis] CACHE HIT");
135
188
  }
136
189
  return JSON.parse(cached);
137
190
  } else {
138
191
  if (isDebug) {
139
- console.log("[DATABASE DEBUG ][Redis] CACHE MISS");
192
+ console.log("[DATABASE DEBUG][Redis] CACHE MISS");
140
193
  }
141
194
  }
142
195
  } catch (err) {
@@ -148,28 +201,58 @@ class Database {
148
201
  }
149
202
 
150
203
  try {
151
- const [results] = await this.pool.execute(cleanSql, params);
152
-
153
- if (isSelect) {
154
- const rows = Array.isArray(results) ? results : [];
155
- if (cacheKey && queryOptions.cache && redisClient) {
156
- await redisClient
157
- .set(cacheKey, JSON.stringify(rows), {
158
- EX: parseInt(queryOptions.cache),
159
- })
160
- .catch(() => {});
204
+ if (this.type === "postgres" && pgPromise) {
205
+ let paramIndex = 1;
206
+ const pgSql = cleanSql.replace(/\?/g, () => `$${paramIndex++}`);
207
+ const res = await this.pool.query(pgSql, params);
208
+
209
+ if (isSelect) {
210
+ const rows = res.rows || [];
211
+ if (cacheKey && queryOptions.cache && redisClient) {
212
+ await redisClient
213
+ .set(cacheKey, JSON.stringify(rows), {
214
+ EX: parseInt(queryOptions.cache),
215
+ })
216
+ .catch(() => {});
217
+ }
218
+ if (queryOptions.return_row) {
219
+ return rows.length > 0 ? rows[0] : null;
220
+ }
221
+ return rows;
161
222
  }
162
- if (queryOptions.return_row) {
163
- return rows.length > 0 ? rows[0] : null;
223
+
224
+ if (isInsert) {
225
+ if (res.rows && res.rows.length > 0) {
226
+ return res.rows[0].id || res.rows[0];
227
+ }
228
+ return res.rowCount;
164
229
  }
165
- return rows;
166
- }
167
230
 
168
- if (isInsert) {
169
- return results.insertId || results;
170
- }
231
+ return res.rowCount;
232
+ } else {
233
+ const [results] = await this.pool.execute(cleanSql, params);
234
+
235
+ if (isSelect) {
236
+ const rows = Array.isArray(results) ? results : [];
237
+ if (cacheKey && queryOptions.cache && redisClient) {
238
+ await redisClient
239
+ .set(cacheKey, JSON.stringify(rows), {
240
+ EX: parseInt(queryOptions.cache),
241
+ })
242
+ .catch(() => {});
243
+ }
244
+ if (queryOptions.return_row) {
245
+ return rows.length > 0 ? rows[0] : null;
246
+ }
247
+ return rows;
248
+ }
171
249
 
172
- return results;
250
+ if (isInsert) {
251
+ return results.insertId || results;
252
+ }
253
+
254
+ return results;
255
+ }
173
256
  } catch (err) {
174
257
  console.error("[DATABASE ERROR] Query execution failed:", err.message);
175
258
  throw err;
@@ -177,6 +260,4 @@ class Database {
177
260
  }
178
261
  }
179
262
 
180
- const connection = new Database();
181
- export default connection;
182
- export { Database };
263
+ export { Database, DB_TYPE };
@@ -0,0 +1,92 @@
1
+ services:
2
+ app:
3
+ build:
4
+ context: .
5
+ dockerfile: docker/Dockerfile
6
+ container_name: ${TITLE:-bluebird}-app
7
+ restart: unless-stopped
8
+ expose:
9
+ - "3000"
10
+ volumes:
11
+ - .:/app
12
+ - /app/node_modules
13
+ env_file:
14
+ - .env
15
+ environment:
16
+ - NODE_ENV=production
17
+ - DEBUG=false
18
+ - DATABASE_URL=mysql://${DB_USER:-root}:${DB_PASSWORD:-root}@mysql:${DB_PORT:-3306}/${DB_NAME:-blue_bird}
19
+ - REDIS_HOST=redis
20
+ - REDIS_PORT=6379
21
+ - PORT=3000
22
+ depends_on:
23
+ mysql:
24
+ condition: service_healthy
25
+ redis:
26
+ condition: service_healthy
27
+ networks:
28
+ - bluebird_net
29
+ profiles:
30
+ - prod
31
+
32
+ nginx:
33
+ image: nginx:1.27-alpine
34
+ container_name: ${TITLE:-bluebird}-nginx
35
+ restart: unless-stopped
36
+ ports:
37
+ - "${PORT:-3000}:80"
38
+ volumes:
39
+ - .:/app:ro
40
+ - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
41
+ depends_on:
42
+ app:
43
+ condition: service_started
44
+ networks:
45
+ - bluebird_net
46
+ profiles:
47
+ - prod
48
+
49
+ mysql:
50
+ image: mysql:8.0
51
+ container_name: ${TITLE:-bluebird}-mysql
52
+ restart: unless-stopped
53
+ environment:
54
+ MYSQL_ROOT_PASSWORD: ${DB_PASSWORD:-root}
55
+ MYSQL_DATABASE: ${DB_NAME:-blue_bird}
56
+ ports:
57
+ - "${DB_PORT:-3306}:3306"
58
+ volumes:
59
+ - mysql_data:/var/lib/mysql
60
+ healthcheck:
61
+ test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-p${DB_PASSWORD:-root}"]
62
+ interval: 10s
63
+ timeout: 5s
64
+ retries: 5
65
+ start_period: 30s
66
+ networks:
67
+ - bluebird_net
68
+
69
+ redis:
70
+ image: redis:7-alpine
71
+ container_name: ${TITLE:-bluebird}-redis
72
+ restart: unless-stopped
73
+ ports:
74
+ - "${REDIS_PORT:-6379}:6379"
75
+ volumes:
76
+ - redis_data:/data
77
+ networks:
78
+ - bluebird_net
79
+ healthcheck:
80
+ test: ["CMD", "redis-cli", "ping"]
81
+ interval: 5s
82
+ timeout: 3s
83
+ retries: 5
84
+
85
+ volumes:
86
+ mysql_data:
87
+ redis_data:
88
+
89
+ networks:
90
+ bluebird_net:
91
+ name: ${TITLE:-bluebird}_network
92
+ driver: bridge
@@ -0,0 +1,68 @@
1
+ services:
2
+ app:
3
+ build:
4
+ context: .
5
+ dockerfile: docker/Dockerfile
6
+ container_name: ${TITLE:-bluebird}-app
7
+ restart: unless-stopped
8
+ expose:
9
+ - "3000"
10
+ volumes:
11
+ - .:/app
12
+ - /app/node_modules
13
+ env_file:
14
+ - .env
15
+ environment:
16
+ - NODE_ENV=production
17
+ - DEBUG=false
18
+ - REDIS_HOST=redis
19
+ - REDIS_PORT=6379
20
+ - PORT=3000
21
+ depends_on:
22
+ redis:
23
+ condition: service_healthy
24
+ networks:
25
+ - bluebird_net
26
+ profiles:
27
+ - prod
28
+
29
+ nginx:
30
+ image: nginx:1.27-alpine
31
+ container_name: ${TITLE:-bluebird}-nginx
32
+ restart: unless-stopped
33
+ ports:
34
+ - "${PORT:-3000}:80"
35
+ volumes:
36
+ - .:/app:ro
37
+ - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
38
+ depends_on:
39
+ app:
40
+ condition: service_started
41
+ networks:
42
+ - bluebird_net
43
+ profiles:
44
+ - prod
45
+
46
+ redis:
47
+ image: redis:7-alpine
48
+ container_name: ${TITLE:-bluebird}-redis
49
+ restart: unless-stopped
50
+ ports:
51
+ - "${REDIS_PORT:-6379}:6379"
52
+ volumes:
53
+ - redis_data:/data
54
+ networks:
55
+ - bluebird_net
56
+ healthcheck:
57
+ test: ["CMD", "redis-cli", "ping"]
58
+ interval: 5s
59
+ timeout: 3s
60
+ retries: 5
61
+
62
+ volumes:
63
+ redis_data:
64
+
65
+ networks:
66
+ bluebird_net:
67
+ name: ${TITLE:-bluebird}_network
68
+ driver: bridge
@@ -0,0 +1,93 @@
1
+ services:
2
+ app:
3
+ build:
4
+ context: .
5
+ dockerfile: docker/Dockerfile
6
+ container_name: ${TITLE:-bluebird}-app
7
+ restart: unless-stopped
8
+ expose:
9
+ - "3000"
10
+ volumes:
11
+ - .:/app
12
+ - /app/node_modules
13
+ env_file:
14
+ - .env
15
+ environment:
16
+ - NODE_ENV=production
17
+ - DEBUG=false
18
+ - DATABASE_URL=postgresql://${DB_USER:-postgres}:${DB_PASSWORD:-root}@postgres:${DB_PORT:-5432}/${DB_NAME:-blue_bird}?schema=public
19
+ - REDIS_HOST=redis
20
+ - REDIS_PORT=6379
21
+ - PORT=3000
22
+ depends_on:
23
+ postgres:
24
+ condition: service_healthy
25
+ redis:
26
+ condition: service_healthy
27
+ networks:
28
+ - bluebird_net
29
+ profiles:
30
+ - prod
31
+
32
+ nginx:
33
+ image: nginx:1.27-alpine
34
+ container_name: ${TITLE:-bluebird}-nginx
35
+ restart: unless-stopped
36
+ ports:
37
+ - "${PORT:-3000}:80"
38
+ volumes:
39
+ - .:/app:ro
40
+ - ./docker/nginx.conf:/etc/nginx/nginx.conf:ro
41
+ depends_on:
42
+ app:
43
+ condition: service_started
44
+ networks:
45
+ - bluebird_net
46
+ profiles:
47
+ - prod
48
+
49
+ postgres:
50
+ image: postgres:18-alpine
51
+ container_name: ${TITLE:-bluebird}-postgres
52
+ restart: unless-stopped
53
+ environment:
54
+ POSTGRES_USER: ${DB_USER:-postgres}
55
+ POSTGRES_PASSWORD: ${DB_PASSWORD:-root}
56
+ POSTGRES_DB: ${DB_NAME:-blue_bird}
57
+ ports:
58
+ - "${DB_PORT:-5432}:5432"
59
+ volumes:
60
+ - postgres_data:/var/lib/postgresql
61
+ healthcheck:
62
+ test: ["CMD-SHELL", "pg_isready -U ${DB_USER:-postgres} -d ${DB_NAME:-blue_bird}"]
63
+ interval: 10s
64
+ timeout: 5s
65
+ retries: 5
66
+ start_period: 30s
67
+ networks:
68
+ - bluebird_net
69
+
70
+ redis:
71
+ image: redis:7-alpine
72
+ container_name: ${TITLE:-bluebird}-redis
73
+ restart: unless-stopped
74
+ ports:
75
+ - "${REDIS_PORT:-6379}:6379"
76
+ volumes:
77
+ - redis_data:/data
78
+ networks:
79
+ - bluebird_net
80
+ healthcheck:
81
+ test: ["CMD", "redis-cli", "ping"]
82
+ interval: 5s
83
+ timeout: 3s
84
+ retries: 5
85
+
86
+ volumes:
87
+ postgres_data:
88
+ redis_data:
89
+
90
+ networks:
91
+ bluebird_net:
92
+ name: ${TITLE:-bluebird}_network
93
+ driver: bridge
@@ -15,7 +15,7 @@ services:
15
15
  environment:
16
16
  - NODE_ENV=production
17
17
  - DEBUG=false
18
- - DATABASE_URL=mysql://root:${DB_PASSWORD:-root}@mysql:3306/${DB_NAME:-blue_bird}
18
+ - DATABASE_URL=mysql://${DB_USER:-root}:${DB_PASSWORD:-root}@mysql:${DB_PORT:-3306}/${DB_NAME:-blue_bird}
19
19
  - REDIS_HOST=redis
20
20
  - REDIS_PORT=6379
21
21
  - PORT=3000
@@ -90,4 +90,3 @@ networks:
90
90
  bluebird_net:
91
91
  name: ${TITLE:-bluebird}_network
92
92
  driver: bridge
93
-
@@ -1,4 +1,7 @@
1
- const port = (import.meta.env.PORT || "3000").replace(/^["']|["']$/g, "");
1
+ const defaultPort = (import.meta.env.PORT || "3000").replace(
2
+ /^["']|["']$/g,
3
+ "",
4
+ );
2
5
  const host = (import.meta.env.HOST || "localhost").replace(/^["']|["']$/g, "");
3
6
 
4
7
  /**
@@ -7,18 +10,20 @@ const host = (import.meta.env.HOST || "localhost").replace(/^["']|["']$/g, "");
7
10
  * @param {string} [path] - API path relative to the server root (e.g. "api/users").
8
11
  * @param {URL} [requestUrl] - Current request URL (pass `Astro.url` from the page). Required in production.
9
12
  * @returns {string} Absolute URL ready for fetch.
13
+ * @example apiUrl('api/', Astro.url)
10
14
  */
11
15
  export function apiUrl(path = "", requestUrl) {
12
- if (import.meta.env.DEV) {
13
- return `http://${host}:${port}/${path}`;
14
- }
15
16
  if (!requestUrl) {
16
- throw new Error("apiUrl: requestUrl is required in production (pass Astro.url)");
17
+ throw new Error(
18
+ "apiUrl: requestUrl is required in production (pass Astro.url)",
19
+ );
17
20
  }
18
21
  const url = new URL(`/${path}`, requestUrl);
19
- if (url.hostname === "localhost") {
20
- url.hostname = "127.0.0.1";
21
- url.port = port;
22
- }
22
+ const runtimePort =
23
+ (typeof process !== "undefined" && process.env && process.env.PORT) ||
24
+ defaultPort;
25
+ url.hostname = "127.0.0.1";
26
+ url.port = runtimePort;
27
+ url.protocol = "http:";
23
28
  return url.href;
24
29
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@seip/blue-bird",
3
- "version": "0.7.4",
3
+ "version": "0.7.6",
4
4
  "description": "Express opinionated framework with HTML rendering, API architecture, built-in JWT auth, validation, caching, and SEO",
5
5
  "type": "module",
6
6
  "exports": {
@@ -64,9 +64,8 @@
64
64
  "express-rate-limit": "^8.2.1",
65
65
  "helmet": "^8.1.0",
66
66
  "jsonwebtoken": "^9.0.2",
67
- "multer": "^2.0.2",
68
- "mysql2": "^3.22.6",
69
- "redis": "^4.7.0",
67
+ "multer": "^2.0.2",
68
+ "redis": "^6.1.0",
70
69
  "xss": "^1.0.15"
71
70
  }
72
71
  }
@@ -1,48 +0,0 @@
1
- 2026-07-14 22:04:34 -::1 -[GET] /
2
- 2026-07-14 22:04:38 -::1 -[GET] /about
3
- 2026-07-14 22:04:38 -::1 -[GET] /about
4
- 2026-07-14 22:04:39 -::1 -[GET] /
5
- 2026-07-14 22:04:39 -::1 -[GET] /
6
- 2026-07-14 22:06:26 -::1 -[GET] /api/users
7
- 2026-07-14 22:08:02 -::1 -[GET] /api/users
8
- 2026-07-14 22:08:05 -::1 -[GET] /api/
9
- 2026-07-14 22:42:04 -::1 -[GET] /api/auth_generate
10
- 2026-07-14 22:42:05 -::1 -[GET] /api/auth_generate
11
- 2026-07-14 22:42:10 -::1 -[GET] /api/auth_verify
12
- 2026-07-14 22:42:12 -::1 -[GET] /api/auth_verify
13
- 2026-07-14 22:42:21 -::1 -[GET] /api/auth_logout
14
- 2026-07-14 22:42:24 -::1 -[GET] /api/auth_verify
15
- 2026-07-14 22:42:28 -::1 -[GET] /
16
- 2026-07-14 22:42:28 -::ffff:127.0.0.1 -[GET] /api/
17
- 2026-07-14 22:42:29 -::1 -[GET] /
18
- 2026-07-14 22:42:29 -::ffff:127.0.0.1 -[GET] /api/
19
- 2026-07-14 22:42:30 -::1 -[GET] /about
20
- 2026-07-14 22:42:31 -::1 -[GET] /about
21
- 2026-07-14 22:42:32 -::1 -[GET] /
22
- 2026-07-14 22:42:32 -::ffff:127.0.0.1 -[GET] /api/
23
- 2026-07-14 22:42:32 -::1 -[GET] /
24
- 2026-07-14 22:42:32 -::ffff:127.0.0.1 -[GET] /api/
25
- 2026-07-14 22:42:33 -::1 -[GET] /about
26
- 2026-07-14 22:42:35 -::1 -[GET] /
27
- 2026-07-14 22:42:35 -::ffff:127.0.0.1 -[GET] /api/
28
- 2026-07-14 22:42:37 -::1 -[GET] /about
29
- 2026-07-14 22:42:38 -::1 -[GET] /
30
- 2026-07-14 22:42:38 -::ffff:127.0.0.1 -[GET] /api/
31
- 2026-07-14 22:48:54 -::1 -[GET] /
32
- 2026-07-14 22:48:54 -::ffff:127.0.0.1 -[GET] /api/
33
- 2026-07-14 22:48:56 -::1 -[GET] /about
34
- 2026-07-14 22:48:58 -::1 -[GET] /about
35
- 2026-07-14 22:48:59 -::1 -[GET] /
36
- 2026-07-14 22:48:59 -::ffff:127.0.0.1 -[GET] /api/
37
- 2026-07-14 22:48:59 -::1 -[GET] /
38
- 2026-07-14 22:48:59 -::ffff:127.0.0.1 -[GET] /api/
39
- 2026-07-14 22:49:01 -::1 -[GET] /about
40
- 2026-07-14 22:49:02 -::1 -[GET] /
41
- 2026-07-14 22:49:02 -::ffff:127.0.0.1 -[GET] /api/
42
- 2026-07-14 22:49:03 -::1 -[GET] /about
43
- 2026-07-14 22:49:04 -::1 -[GET] /
44
- 2026-07-14 22:49:04 -::ffff:127.0.0.1 -[GET] /api/
45
- 2026-07-14 22:49:05 -::1 -[GET] /about
46
- 2026-07-14 22:49:07 -::1 -[GET] /
47
- 2026-07-14 22:49:07 -::ffff:127.0.0.1 -[GET] /api/
48
- 2026-07-14 22:49:07 -::1 -[GET] /about