db-model-router 1.0.3 → 1.0.5
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/README.md +283 -25
- package/TODO.md +14 -0
- package/dbmr.schema.json +333 -0
- package/demo/.dockerignore +7 -0
- package/demo/.env.example +13 -0
- package/demo/Dockerfile +20 -0
- package/demo/app.js +37 -0
- package/demo/commons/add_migration.js +43 -0
- package/demo/commons/db.js +17 -0
- package/demo/commons/migrate.js +65 -0
- package/demo/commons/security.js +30 -0
- package/demo/commons/session.js +13 -0
- package/demo/dbmr.schema.json +362 -0
- package/demo/docs/llm.md +197 -0
- package/demo/llms.txt +70 -0
- package/demo/middleware/logger.js +67 -0
- package/demo/migrations/20260430155808_create_migrations_table.sql +6 -0
- package/demo/migrations/20260430155809_create_tables.sql +207 -0
- package/demo/models/addresses.js +22 -0
- package/demo/models/cart_items.js +18 -0
- package/demo/models/carts.js +16 -0
- package/demo/models/categories.js +20 -0
- package/demo/models/coupons.js +23 -0
- package/demo/models/order_items.js +21 -0
- package/demo/models/orders.js +25 -0
- package/demo/models/payments.js +21 -0
- package/demo/models/product_images.js +18 -0
- package/demo/models/product_reviews.js +20 -0
- package/demo/models/product_variants.js +20 -0
- package/demo/models/products.js +30 -0
- package/demo/models/shipments.js +19 -0
- package/demo/models/users.js +19 -0
- package/demo/models/wishlists.js +15 -0
- package/demo/openapi.json +5872 -0
- package/demo/package-lock.json +2810 -0
- package/demo/package.json +34 -0
- package/demo/routes/addresses.js +6 -0
- package/demo/routes/carts/cart_items.js +7 -0
- package/demo/routes/carts.js +6 -0
- package/demo/routes/categories.js +6 -0
- package/demo/routes/coupons.js +6 -0
- package/demo/routes/docs.js +18 -0
- package/demo/routes/health.js +35 -0
- package/demo/routes/index.js +39 -0
- package/demo/routes/orders/order_items.js +7 -0
- package/demo/routes/orders/payments.js +7 -0
- package/demo/routes/orders/shipments.js +7 -0
- package/demo/routes/orders.js +6 -0
- package/demo/routes/products/product_images.js +7 -0
- package/demo/routes/products/product_reviews.js +7 -0
- package/demo/routes/products/product_variants.js +7 -0
- package/demo/routes/products.js +6 -0
- package/demo/routes/users.js +6 -0
- package/demo/routes/wishlists.js +6 -0
- package/docker-compose.yml +1 -1
- package/package.json +16 -7
- package/scripts/demo-create.js +47 -0
- package/skill/SKILL.md +464 -0
- package/skill/references/cockroachdb.md +49 -0
- package/skill/references/dynamodb.md +53 -0
- package/skill/references/mongodb.md +56 -0
- package/skill/references/mssql.md +55 -0
- package/skill/references/oracle.md +52 -0
- package/skill/references/postgres.md +50 -0
- package/skill/references/redis.md +53 -0
- package/skill/references/sqlite3.md +43 -0
- package/src/cli/commands/generate.js +58 -17
- package/src/cli/commands/help.js +185 -0
- package/src/cli/commands/init.js +42 -14
- package/src/cli/commands/inspect.js +21 -3
- package/src/cli/diff-engine.js +52 -22
- package/src/cli/generate-docs-route.js +31 -0
- package/src/cli/generate-migration.js +356 -0
- package/src/cli/generate-model.js +5 -4
- package/src/cli/generate-route.js +79 -45
- package/src/cli/init/dependencies.js +17 -5
- package/src/cli/init/generators.js +1073 -64
- package/src/cli/init/prompt.js +37 -5
- package/src/cli/init.js +148 -25
- package/src/cli/main.js +90 -10
- package/src/cockroachdb/db.js +90 -59
- package/src/commons/route.js +20 -20
- package/src/commons/validator.js +58 -1
- package/src/dynamodb/db.js +50 -27
- package/src/index.js +2 -0
- package/src/mongodb/db.js +1 -0
- package/src/mssql/db.js +89 -61
- package/src/mysql/db.js +1 -0
- package/src/oracle/db.js +1 -0
- package/src/postgres/db.js +61 -41
- package/src/redis/db.js +1 -0
- package/src/schema/schema-parser.js +43 -1
- package/src/schema/schema-printer.js +8 -5
- package/src/schema/schema-to-meta.js +4 -0
- package/src/schema/schema-validator.js +20 -1
- package/src/sqlite3/db.js +1 -0
- package/docs/SKILL.md +0 -374
package/dbmr.schema.json
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
{
|
|
2
|
+
"adapter": "sqlite3",
|
|
3
|
+
"framework": "express",
|
|
4
|
+
"options": {
|
|
5
|
+
"session": "redis",
|
|
6
|
+
"rateLimiting": true,
|
|
7
|
+
"helmet": true,
|
|
8
|
+
"logger": true,
|
|
9
|
+
"loki": false
|
|
10
|
+
},
|
|
11
|
+
"tables": {
|
|
12
|
+
"users": {
|
|
13
|
+
"columns": {
|
|
14
|
+
"user_id": "auto_increment",
|
|
15
|
+
"name": "required|string",
|
|
16
|
+
"email": "required|string",
|
|
17
|
+
"password_hash": "required|string",
|
|
18
|
+
"phone": "string",
|
|
19
|
+
"avatar_url": "string",
|
|
20
|
+
"role": "required|string",
|
|
21
|
+
"is_deleted": "boolean",
|
|
22
|
+
"created_at": "datetime",
|
|
23
|
+
"updated_at": "datetime"
|
|
24
|
+
},
|
|
25
|
+
"pk": "user_id",
|
|
26
|
+
"unique": ["email"],
|
|
27
|
+
"softDelete": "is_deleted",
|
|
28
|
+
"timestamps": {
|
|
29
|
+
"created_at": "created_at",
|
|
30
|
+
"modified_at": "updated_at"
|
|
31
|
+
},
|
|
32
|
+
"parent": null
|
|
33
|
+
},
|
|
34
|
+
"addresses": {
|
|
35
|
+
"columns": {
|
|
36
|
+
"address_id": "auto_increment",
|
|
37
|
+
"user_id": "required|integer",
|
|
38
|
+
"label": "string",
|
|
39
|
+
"line1": "required|string",
|
|
40
|
+
"line2": "string",
|
|
41
|
+
"city": "required|string",
|
|
42
|
+
"state": "required|string",
|
|
43
|
+
"postal_code": "required|string",
|
|
44
|
+
"country": "required|string",
|
|
45
|
+
"is_default": "boolean",
|
|
46
|
+
"created_at": "datetime",
|
|
47
|
+
"updated_at": "datetime"
|
|
48
|
+
},
|
|
49
|
+
"pk": "address_id",
|
|
50
|
+
"unique": ["address_id"],
|
|
51
|
+
"timestamps": {
|
|
52
|
+
"created_at": "created_at",
|
|
53
|
+
"modified_at": "updated_at"
|
|
54
|
+
},
|
|
55
|
+
"parent": null
|
|
56
|
+
},
|
|
57
|
+
"categories": {
|
|
58
|
+
"columns": {
|
|
59
|
+
"category_id": "auto_increment",
|
|
60
|
+
"name": "required|string",
|
|
61
|
+
"slug": "required|string",
|
|
62
|
+
"description": "string",
|
|
63
|
+
"parent_category_id": "integer",
|
|
64
|
+
"image_url": "string",
|
|
65
|
+
"sort_order": "integer",
|
|
66
|
+
"is_active": "boolean",
|
|
67
|
+
"created_at": "datetime",
|
|
68
|
+
"updated_at": "datetime"
|
|
69
|
+
},
|
|
70
|
+
"pk": "category_id",
|
|
71
|
+
"unique": ["slug"],
|
|
72
|
+
"timestamps": {
|
|
73
|
+
"created_at": "created_at",
|
|
74
|
+
"modified_at": "updated_at"
|
|
75
|
+
},
|
|
76
|
+
"parent": null
|
|
77
|
+
},
|
|
78
|
+
"products": {
|
|
79
|
+
"columns": {
|
|
80
|
+
"product_id": "auto_increment",
|
|
81
|
+
"category_id": "required|integer",
|
|
82
|
+
"name": "required|string",
|
|
83
|
+
"slug": "required|string",
|
|
84
|
+
"description": "string",
|
|
85
|
+
"short_description": "string",
|
|
86
|
+
"sku": "required|string",
|
|
87
|
+
"price": "required|numeric",
|
|
88
|
+
"compare_at_price": "numeric",
|
|
89
|
+
"cost_price": "numeric",
|
|
90
|
+
"currency": "required|string",
|
|
91
|
+
"stock_quantity": "required|integer",
|
|
92
|
+
"low_stock_threshold": "integer",
|
|
93
|
+
"weight": "numeric",
|
|
94
|
+
"weight_unit": "string",
|
|
95
|
+
"is_active": "boolean",
|
|
96
|
+
"is_featured": "boolean",
|
|
97
|
+
"is_deleted": "boolean",
|
|
98
|
+
"meta": "object",
|
|
99
|
+
"created_at": "datetime",
|
|
100
|
+
"updated_at": "datetime"
|
|
101
|
+
},
|
|
102
|
+
"pk": "product_id",
|
|
103
|
+
"unique": ["sku", "slug"],
|
|
104
|
+
"softDelete": "is_deleted",
|
|
105
|
+
"timestamps": {
|
|
106
|
+
"created_at": "created_at",
|
|
107
|
+
"modified_at": "updated_at"
|
|
108
|
+
},
|
|
109
|
+
"parent": null
|
|
110
|
+
},
|
|
111
|
+
"product_images": {
|
|
112
|
+
"columns": {
|
|
113
|
+
"product_image_id": "auto_increment",
|
|
114
|
+
"product_id": "required|integer",
|
|
115
|
+
"url": "required|string",
|
|
116
|
+
"alt_text": "string",
|
|
117
|
+
"sort_order": "integer",
|
|
118
|
+
"is_primary": "boolean",
|
|
119
|
+
"created_at": "datetime"
|
|
120
|
+
},
|
|
121
|
+
"pk": "product_image_id",
|
|
122
|
+
"unique": ["product_image_id"],
|
|
123
|
+
"timestamps": {
|
|
124
|
+
"created_at": "created_at"
|
|
125
|
+
},
|
|
126
|
+
"parent": "products"
|
|
127
|
+
},
|
|
128
|
+
"product_variants": {
|
|
129
|
+
"columns": {
|
|
130
|
+
"variant_id": "auto_increment",
|
|
131
|
+
"product_id": "required|integer",
|
|
132
|
+
"name": "required|string",
|
|
133
|
+
"sku": "required|string",
|
|
134
|
+
"price": "required|numeric",
|
|
135
|
+
"stock_quantity": "required|integer",
|
|
136
|
+
"attributes": "object",
|
|
137
|
+
"is_active": "boolean",
|
|
138
|
+
"created_at": "datetime",
|
|
139
|
+
"updated_at": "datetime"
|
|
140
|
+
},
|
|
141
|
+
"pk": "variant_id",
|
|
142
|
+
"unique": ["sku"],
|
|
143
|
+
"timestamps": {
|
|
144
|
+
"created_at": "created_at",
|
|
145
|
+
"modified_at": "updated_at"
|
|
146
|
+
},
|
|
147
|
+
"parent": "products"
|
|
148
|
+
},
|
|
149
|
+
"product_reviews": {
|
|
150
|
+
"columns": {
|
|
151
|
+
"review_id": "auto_increment",
|
|
152
|
+
"product_id": "required|integer",
|
|
153
|
+
"user_id": "required|integer",
|
|
154
|
+
"rating": "required|integer",
|
|
155
|
+
"title": "string",
|
|
156
|
+
"body": "string",
|
|
157
|
+
"is_verified": "boolean",
|
|
158
|
+
"is_approved": "boolean",
|
|
159
|
+
"created_at": "datetime",
|
|
160
|
+
"updated_at": "datetime"
|
|
161
|
+
},
|
|
162
|
+
"pk": "review_id",
|
|
163
|
+
"unique": ["review_id"],
|
|
164
|
+
"timestamps": {
|
|
165
|
+
"created_at": "created_at",
|
|
166
|
+
"modified_at": "updated_at"
|
|
167
|
+
},
|
|
168
|
+
"parent": "products"
|
|
169
|
+
},
|
|
170
|
+
"carts": {
|
|
171
|
+
"columns": {
|
|
172
|
+
"cart_id": "auto_increment",
|
|
173
|
+
"user_id": "integer",
|
|
174
|
+
"session_id": "string",
|
|
175
|
+
"currency": "required|string",
|
|
176
|
+
"created_at": "datetime",
|
|
177
|
+
"updated_at": "datetime"
|
|
178
|
+
},
|
|
179
|
+
"pk": "cart_id",
|
|
180
|
+
"unique": ["cart_id"],
|
|
181
|
+
"timestamps": {
|
|
182
|
+
"created_at": "created_at",
|
|
183
|
+
"modified_at": "updated_at"
|
|
184
|
+
},
|
|
185
|
+
"parent": null
|
|
186
|
+
},
|
|
187
|
+
"cart_items": {
|
|
188
|
+
"columns": {
|
|
189
|
+
"cart_item_id": "auto_increment",
|
|
190
|
+
"cart_id": "required|integer",
|
|
191
|
+
"product_id": "required|integer",
|
|
192
|
+
"variant_id": "integer",
|
|
193
|
+
"quantity": "required|integer",
|
|
194
|
+
"unit_price": "required|numeric",
|
|
195
|
+
"created_at": "datetime",
|
|
196
|
+
"updated_at": "datetime"
|
|
197
|
+
},
|
|
198
|
+
"pk": "cart_item_id",
|
|
199
|
+
"unique": ["cart_item_id"],
|
|
200
|
+
"timestamps": {
|
|
201
|
+
"created_at": "created_at",
|
|
202
|
+
"modified_at": "updated_at"
|
|
203
|
+
},
|
|
204
|
+
"parent": "carts"
|
|
205
|
+
},
|
|
206
|
+
"orders": {
|
|
207
|
+
"columns": {
|
|
208
|
+
"order_id": "auto_increment",
|
|
209
|
+
"user_id": "required|integer",
|
|
210
|
+
"order_number": "required|string",
|
|
211
|
+
"status": "required|string",
|
|
212
|
+
"subtotal": "required|numeric",
|
|
213
|
+
"tax_amount": "required|numeric",
|
|
214
|
+
"shipping_amount": "required|numeric",
|
|
215
|
+
"discount_amount": "numeric",
|
|
216
|
+
"total": "required|numeric",
|
|
217
|
+
"currency": "required|string",
|
|
218
|
+
"shipping_address_id": "integer",
|
|
219
|
+
"billing_address_id": "integer",
|
|
220
|
+
"notes": "string",
|
|
221
|
+
"created_at": "datetime",
|
|
222
|
+
"updated_at": "datetime"
|
|
223
|
+
},
|
|
224
|
+
"pk": "order_id",
|
|
225
|
+
"unique": ["order_number"],
|
|
226
|
+
"timestamps": {
|
|
227
|
+
"created_at": "created_at",
|
|
228
|
+
"modified_at": "updated_at"
|
|
229
|
+
},
|
|
230
|
+
"parent": null
|
|
231
|
+
},
|
|
232
|
+
"order_items": {
|
|
233
|
+
"columns": {
|
|
234
|
+
"order_item_id": "auto_increment",
|
|
235
|
+
"order_id": "required|integer",
|
|
236
|
+
"product_id": "required|integer",
|
|
237
|
+
"variant_id": "integer",
|
|
238
|
+
"product_name": "required|string",
|
|
239
|
+
"sku": "required|string",
|
|
240
|
+
"quantity": "required|integer",
|
|
241
|
+
"unit_price": "required|numeric",
|
|
242
|
+
"total_price": "required|numeric",
|
|
243
|
+
"created_at": "datetime"
|
|
244
|
+
},
|
|
245
|
+
"pk": "order_item_id",
|
|
246
|
+
"unique": ["order_item_id"],
|
|
247
|
+
"timestamps": {
|
|
248
|
+
"created_at": "created_at"
|
|
249
|
+
},
|
|
250
|
+
"parent": "orders"
|
|
251
|
+
},
|
|
252
|
+
"payments": {
|
|
253
|
+
"columns": {
|
|
254
|
+
"payment_id": "auto_increment",
|
|
255
|
+
"order_id": "required|integer",
|
|
256
|
+
"method": "required|string",
|
|
257
|
+
"provider": "string",
|
|
258
|
+
"provider_transaction_id": "string",
|
|
259
|
+
"amount": "required|numeric",
|
|
260
|
+
"currency": "required|string",
|
|
261
|
+
"status": "required|string",
|
|
262
|
+
"paid_at": "datetime",
|
|
263
|
+
"created_at": "datetime",
|
|
264
|
+
"updated_at": "datetime"
|
|
265
|
+
},
|
|
266
|
+
"pk": "payment_id",
|
|
267
|
+
"unique": ["payment_id"],
|
|
268
|
+
"timestamps": {
|
|
269
|
+
"created_at": "created_at",
|
|
270
|
+
"modified_at": "updated_at"
|
|
271
|
+
},
|
|
272
|
+
"parent": "orders"
|
|
273
|
+
},
|
|
274
|
+
"shipments": {
|
|
275
|
+
"columns": {
|
|
276
|
+
"shipment_id": "auto_increment",
|
|
277
|
+
"order_id": "required|integer",
|
|
278
|
+
"carrier": "required|string",
|
|
279
|
+
"tracking_number": "string",
|
|
280
|
+
"status": "required|string",
|
|
281
|
+
"shipped_at": "datetime",
|
|
282
|
+
"delivered_at": "datetime",
|
|
283
|
+
"created_at": "datetime",
|
|
284
|
+
"updated_at": "datetime"
|
|
285
|
+
},
|
|
286
|
+
"pk": "shipment_id",
|
|
287
|
+
"unique": ["shipment_id"],
|
|
288
|
+
"timestamps": {
|
|
289
|
+
"created_at": "created_at",
|
|
290
|
+
"modified_at": "updated_at"
|
|
291
|
+
},
|
|
292
|
+
"parent": "orders"
|
|
293
|
+
},
|
|
294
|
+
"coupons": {
|
|
295
|
+
"columns": {
|
|
296
|
+
"coupon_id": "auto_increment",
|
|
297
|
+
"code": "required|string",
|
|
298
|
+
"description": "string",
|
|
299
|
+
"discount_type": "required|string",
|
|
300
|
+
"discount_value": "required|numeric",
|
|
301
|
+
"min_order_amount": "numeric",
|
|
302
|
+
"max_uses": "integer",
|
|
303
|
+
"used_count": "integer",
|
|
304
|
+
"starts_at": "datetime",
|
|
305
|
+
"expires_at": "datetime",
|
|
306
|
+
"is_active": "boolean",
|
|
307
|
+
"created_at": "datetime",
|
|
308
|
+
"updated_at": "datetime"
|
|
309
|
+
},
|
|
310
|
+
"pk": "coupon_id",
|
|
311
|
+
"unique": ["code"],
|
|
312
|
+
"timestamps": {
|
|
313
|
+
"created_at": "created_at",
|
|
314
|
+
"modified_at": "updated_at"
|
|
315
|
+
},
|
|
316
|
+
"parent": null
|
|
317
|
+
},
|
|
318
|
+
"wishlists": {
|
|
319
|
+
"columns": {
|
|
320
|
+
"wishlist_id": "auto_increment",
|
|
321
|
+
"user_id": "required|integer",
|
|
322
|
+
"product_id": "required|integer",
|
|
323
|
+
"created_at": "datetime"
|
|
324
|
+
},
|
|
325
|
+
"pk": "wishlist_id",
|
|
326
|
+
"unique": ["wishlist_id"],
|
|
327
|
+
"timestamps": {
|
|
328
|
+
"created_at": "created_at"
|
|
329
|
+
},
|
|
330
|
+
"parent": null
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
}
|
package/demo/Dockerfile
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
FROM node:alpine
|
|
2
|
+
|
|
3
|
+
WORKDIR /app
|
|
4
|
+
|
|
5
|
+
# Install dependencies
|
|
6
|
+
COPY package*.json ./
|
|
7
|
+
RUN npm ci --omit=dev
|
|
8
|
+
|
|
9
|
+
# Copy application files
|
|
10
|
+
COPY app.js ./
|
|
11
|
+
COPY commons/ ./commons/
|
|
12
|
+
COPY middleware/ ./middleware/
|
|
13
|
+
COPY route/ ./route/
|
|
14
|
+
COPY migrations/ ./migrations/
|
|
15
|
+
|
|
16
|
+
# Expose port
|
|
17
|
+
EXPOSE 3000
|
|
18
|
+
|
|
19
|
+
# Start the application
|
|
20
|
+
CMD ["node", "app.js"]
|
package/demo/app.js
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import express from "express";
|
|
2
|
+
import "./commons/db.js";
|
|
3
|
+
import configureSession from "./commons/session.js";
|
|
4
|
+
import applySecurity from "./commons/security.js";
|
|
5
|
+
import logger from "./middleware/logger.js";
|
|
6
|
+
import route from "./routes/index.js";
|
|
7
|
+
|
|
8
|
+
const app = express();
|
|
9
|
+
const PORT = process.env.PORT || 3000;
|
|
10
|
+
|
|
11
|
+
// Middleware
|
|
12
|
+
app.use(express.json());
|
|
13
|
+
app.use(express.urlencoded({ extended: true }));
|
|
14
|
+
|
|
15
|
+
// Security (helmet, rate limiting, custom headers)
|
|
16
|
+
applySecurity(app);
|
|
17
|
+
|
|
18
|
+
// Session
|
|
19
|
+
app.use(configureSession());
|
|
20
|
+
|
|
21
|
+
// Logger
|
|
22
|
+
app.use(logger);
|
|
23
|
+
|
|
24
|
+
// Routes
|
|
25
|
+
app.use(route);
|
|
26
|
+
|
|
27
|
+
// Error handler
|
|
28
|
+
app.use((err, req, res, next) => {
|
|
29
|
+
console.error(err.stack);
|
|
30
|
+
res.status(500).json({ type: "danger", message: "Internal Server Error" });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
app.listen(PORT, () => {
|
|
34
|
+
console.log(`Server running on port ${PORT}`);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
export default app;
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
|
|
6
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Create a new timestamped migration file.
|
|
10
|
+
* @param {string} migrationsDir - absolute path to migrations folder
|
|
11
|
+
* @param {string} [name] - migration name (default: "migration")
|
|
12
|
+
* @returns {string} the created filename
|
|
13
|
+
*/
|
|
14
|
+
export default function addMigration(migrationsDir, name) {
|
|
15
|
+
const migrationName = name || "migration";
|
|
16
|
+
const now = new Date();
|
|
17
|
+
const y = String(now.getFullYear()).padStart(4, "0");
|
|
18
|
+
const mo = String(now.getMonth() + 1).padStart(2, "0");
|
|
19
|
+
const d = String(now.getDate()).padStart(2, "0");
|
|
20
|
+
const h = String(now.getHours()).padStart(2, "0");
|
|
21
|
+
const mi = String(now.getMinutes()).padStart(2, "0");
|
|
22
|
+
const s = String(now.getSeconds()).padStart(2, "0");
|
|
23
|
+
const ts = `${y}${mo}${d}${h}${mi}${s}`;
|
|
24
|
+
|
|
25
|
+
const filename = `${ts}_${migrationName}.sql`;
|
|
26
|
+
const filePath = path.join(migrationsDir, filename);
|
|
27
|
+
|
|
28
|
+
if (!fs.existsSync(migrationsDir)) {
|
|
29
|
+
fs.mkdirSync(migrationsDir, { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
fs.writeFileSync(filePath, "-- Write your migration SQL here\n");
|
|
33
|
+
console.log(`Created migration: ${filename}`);
|
|
34
|
+
return filename;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Run as standalone script
|
|
38
|
+
const isMain = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
39
|
+
if (isMain) {
|
|
40
|
+
const migrationsDir = path.join(__dirname, "../migrations");
|
|
41
|
+
const name = process.argv[2] || "migration";
|
|
42
|
+
addMigration(migrationsDir, name);
|
|
43
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import "dotenv/config";
|
|
2
|
+
import dbModelRouter from "db-model-router";
|
|
3
|
+
|
|
4
|
+
// Initialize database adapter
|
|
5
|
+
dbModelRouter.init("sqlite3");
|
|
6
|
+
|
|
7
|
+
// Connect to database
|
|
8
|
+
dbModelRouter.db.connect({
|
|
9
|
+
database: process.env.DB_NAME || "./data/data.db",
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
// Make db available globally across the application
|
|
13
|
+
const db = dbModelRouter.db;
|
|
14
|
+
global.db = db;
|
|
15
|
+
|
|
16
|
+
export { db };
|
|
17
|
+
export default db;
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from "fs";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import crypto from "crypto";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Run all pending SQL migrations from the migrations directory.
|
|
11
|
+
* @param {object} db - db-model-router db instance
|
|
12
|
+
* @param {string} migrationsDir - absolute path to migrations folder
|
|
13
|
+
*/
|
|
14
|
+
export default async function runMigrations(db, migrationsDir) {
|
|
15
|
+
const files = fs.readdirSync(migrationsDir)
|
|
16
|
+
.filter(f => f.endsWith(".sql"))
|
|
17
|
+
.sort();
|
|
18
|
+
|
|
19
|
+
let executed;
|
|
20
|
+
try {
|
|
21
|
+
const result = await db.query("SELECT filename FROM _migrations");
|
|
22
|
+
executed = new Set((result || []).map(r => r.filename));
|
|
23
|
+
} catch (e) {
|
|
24
|
+
executed = new Set();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
let ran = 0;
|
|
28
|
+
for (const file of files) {
|
|
29
|
+
if (executed.has(file)) {
|
|
30
|
+
console.log(` Skipping (already executed): ${file}`);
|
|
31
|
+
continue;
|
|
32
|
+
}
|
|
33
|
+
const filePath = path.join(migrationsDir, file);
|
|
34
|
+
const content = fs.readFileSync(filePath, "utf8");
|
|
35
|
+
const checksum = crypto.createHash("md5").update(content).digest("hex");
|
|
36
|
+
|
|
37
|
+
console.log(` Running migration: ${file}`);
|
|
38
|
+
await db.query(content);
|
|
39
|
+
await db.query(
|
|
40
|
+
"INSERT INTO _migrations (filename, checksum) VALUES (?, ?)",
|
|
41
|
+
[file, checksum]
|
|
42
|
+
);
|
|
43
|
+
console.log(` Completed: ${file}`);
|
|
44
|
+
ran++;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
if (ran === 0) {
|
|
48
|
+
console.log("No pending migrations.");
|
|
49
|
+
} else {
|
|
50
|
+
console.log(`\n${ran} migration(s) complete.`);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Run as standalone script
|
|
55
|
+
const isMain = process.argv[1] && fs.realpathSync(process.argv[1]) === fs.realpathSync(fileURLToPath(import.meta.url));
|
|
56
|
+
if (isMain) {
|
|
57
|
+
await import("dotenv/config");
|
|
58
|
+
const pkg = await import("db-model-router");
|
|
59
|
+
const mod = pkg.default || pkg;
|
|
60
|
+
mod.init("sqlite3");
|
|
61
|
+
const migrationsDir = path.join(__dirname, "../migrations");
|
|
62
|
+
runMigrations(mod.db, migrationsDir)
|
|
63
|
+
.then(() => process.exit(0))
|
|
64
|
+
.catch(err => { console.error("Migration failed:", err); process.exit(1); });
|
|
65
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import helmet from "helmet";
|
|
2
|
+
import rateLimit from "express-rate-limit";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Apply security middleware to the Express app.
|
|
6
|
+
* Includes: Helmet, rate limiting, custom security headers.
|
|
7
|
+
* @param {import("express").Application} app
|
|
8
|
+
*/
|
|
9
|
+
export default function applySecurity(app) {
|
|
10
|
+
// Helmet — sets various HTTP headers for security
|
|
11
|
+
app.use(helmet());
|
|
12
|
+
|
|
13
|
+
// Rate limiting
|
|
14
|
+
app.use(rateLimit({
|
|
15
|
+
windowMs: 15 * 60 * 1000,
|
|
16
|
+
max: 100,
|
|
17
|
+
standardHeaders: true,
|
|
18
|
+
legacyHeaders: false,
|
|
19
|
+
}));
|
|
20
|
+
|
|
21
|
+
// Custom security headers (override or extend as needed)
|
|
22
|
+
app.use((req, res, next) => {
|
|
23
|
+
res.setHeader("X-Content-Type-Options", "nosniff");
|
|
24
|
+
res.setHeader("X-Frame-Options", "DENY");
|
|
25
|
+
res.setHeader("X-XSS-Protection", "1; mode=block");
|
|
26
|
+
res.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
|
|
27
|
+
res.removeHeader("X-Powered-By");
|
|
28
|
+
next();
|
|
29
|
+
});
|
|
30
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import session from "express-session";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configure and return session middleware.
|
|
5
|
+
* Session store: memory
|
|
6
|
+
*/
|
|
7
|
+
export default function configureSession() {
|
|
8
|
+
return session({
|
|
9
|
+
secret: process.env.SESSION_SECRET || "change-me",
|
|
10
|
+
resave: false,
|
|
11
|
+
saveUninitialized: false,
|
|
12
|
+
});
|
|
13
|
+
}
|