canxjs 1.2.2 → 1.2.3
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 +23 -1
- package/dist/auth/Auth.d.ts +19 -2
- package/dist/auth/Auth.d.ts.map +1 -1
- package/dist/auth/drivers/DatabaseSessionDriver.d.ts +13 -0
- package/dist/auth/drivers/DatabaseSessionDriver.d.ts.map +1 -0
- package/dist/database/Migration.d.ts +2 -1
- package/dist/database/Migration.d.ts.map +1 -1
- package/dist/database/migrations/20240114000000_create_sessions_table.d.ts +3 -0
- package/dist/database/migrations/20240114000000_create_sessions_table.d.ts.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +395 -23
- package/dist/mvc/Model.d.ts +55 -10
- package/dist/mvc/Model.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/utils/Validator.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/auth/Auth.ts +51 -3
- package/src/auth/drivers/DatabaseSessionDriver.ts +80 -0
- package/src/database/Migration.ts +37 -8
- package/src/database/migrations/20240114000000_create_sessions_table.ts +18 -0
- package/src/index.ts +1 -0
- package/src/mvc/Model.ts +325 -24
- package/src/types/index.ts +1 -0
- package/src/utils/Validator.ts +124 -2
package/dist/index.js
CHANGED
|
@@ -27164,6 +27164,7 @@ class TestResponse {
|
|
|
27164
27164
|
// src/mvc/Model.ts
|
|
27165
27165
|
var mysqlPool = null;
|
|
27166
27166
|
var pgPool = null;
|
|
27167
|
+
var sqliteDb = null;
|
|
27167
27168
|
var currentDriver = "mysql";
|
|
27168
27169
|
var dbConfig = null;
|
|
27169
27170
|
async function initDatabase(config) {
|
|
@@ -27194,6 +27195,10 @@ async function initDatabase(config) {
|
|
|
27194
27195
|
idleTimeoutMillis: config.pool?.idle || 30000
|
|
27195
27196
|
});
|
|
27196
27197
|
console.log("[CanxJS] PostgreSQL connection pool created");
|
|
27198
|
+
} else if (config.driver === "sqlite") {
|
|
27199
|
+
const { Database } = await import("bun:sqlite");
|
|
27200
|
+
sqliteDb = new Database(config.database);
|
|
27201
|
+
console.log("[CanxJS] SQLite database connected");
|
|
27197
27202
|
}
|
|
27198
27203
|
}
|
|
27199
27204
|
async function closeDatabase() {
|
|
@@ -27205,6 +27210,13 @@ async function closeDatabase() {
|
|
|
27205
27210
|
await pgPool.end();
|
|
27206
27211
|
pgPool = null;
|
|
27207
27212
|
}
|
|
27213
|
+
if (sqliteDb) {
|
|
27214
|
+
sqliteDb.close();
|
|
27215
|
+
sqliteDb = null;
|
|
27216
|
+
}
|
|
27217
|
+
}
|
|
27218
|
+
function getCurrentDriver() {
|
|
27219
|
+
return currentDriver;
|
|
27208
27220
|
}
|
|
27209
27221
|
async function query(sql, params = []) {
|
|
27210
27222
|
if (dbConfig?.logging)
|
|
@@ -27217,6 +27229,9 @@ async function query(sql, params = []) {
|
|
|
27217
27229
|
const pgSql = sql.replace(/\?/g, () => `$${++idx}`);
|
|
27218
27230
|
const result = await pgPool.query(pgSql, params);
|
|
27219
27231
|
return result.rows;
|
|
27232
|
+
} else if (currentDriver === "sqlite" && sqliteDb) {
|
|
27233
|
+
const query2 = sqliteDb.query(sql);
|
|
27234
|
+
return query2.all(...params);
|
|
27220
27235
|
}
|
|
27221
27236
|
throw new Error("No database connection");
|
|
27222
27237
|
}
|
|
@@ -27231,6 +27246,10 @@ async function execute(sql, params = []) {
|
|
|
27231
27246
|
const pgSql = sql.replace(/\?/g, () => `$${++idx}`);
|
|
27232
27247
|
const result = await pgPool.query(pgSql + " RETURNING *", params);
|
|
27233
27248
|
return { affectedRows: result.rowCount || 0, insertId: result.rows[0]?.id || 0 };
|
|
27249
|
+
} else if (currentDriver === "sqlite" && sqliteDb) {
|
|
27250
|
+
const query2 = sqliteDb.query(sql);
|
|
27251
|
+
const result = query2.run(...params);
|
|
27252
|
+
return { affectedRows: result.changes, insertId: result.lastInsertRowid };
|
|
27234
27253
|
}
|
|
27235
27254
|
throw new Error("No database connection");
|
|
27236
27255
|
}
|
|
@@ -27246,8 +27265,12 @@ class QueryBuilderImpl {
|
|
|
27246
27265
|
groupClauses = [];
|
|
27247
27266
|
havingClauses = [];
|
|
27248
27267
|
bindings = [];
|
|
27249
|
-
|
|
27268
|
+
modelClass;
|
|
27269
|
+
withRelations = [];
|
|
27270
|
+
_relationInfo;
|
|
27271
|
+
constructor(table, modelClass) {
|
|
27250
27272
|
this.table = table;
|
|
27273
|
+
this.modelClass = modelClass;
|
|
27251
27274
|
}
|
|
27252
27275
|
select(...cols) {
|
|
27253
27276
|
this.selectCols = cols.length ? cols : ["*"];
|
|
@@ -27259,6 +27282,10 @@ class QueryBuilderImpl {
|
|
|
27259
27282
|
return this;
|
|
27260
27283
|
}
|
|
27261
27284
|
whereIn(col, vals) {
|
|
27285
|
+
if (vals.length === 0) {
|
|
27286
|
+
this.whereClauses.push("1 = 0");
|
|
27287
|
+
return this;
|
|
27288
|
+
}
|
|
27262
27289
|
this.whereClauses.push(`${String(col)} IN (${vals.map(() => "?").join(",")})`);
|
|
27263
27290
|
this.bindings.push(...vals);
|
|
27264
27291
|
return this;
|
|
@@ -27310,6 +27337,10 @@ class QueryBuilderImpl {
|
|
|
27310
27337
|
this.bindings.push(val);
|
|
27311
27338
|
return this;
|
|
27312
27339
|
}
|
|
27340
|
+
with(...relations) {
|
|
27341
|
+
this.withRelations.push(...relations);
|
|
27342
|
+
return this;
|
|
27343
|
+
}
|
|
27313
27344
|
buildSelect() {
|
|
27314
27345
|
let sql = `SELECT ${this.selectCols.join(", ")} FROM ${this.table}`;
|
|
27315
27346
|
if (this.joinClauses.length)
|
|
@@ -27329,26 +27360,105 @@ class QueryBuilderImpl {
|
|
|
27329
27360
|
return sql;
|
|
27330
27361
|
}
|
|
27331
27362
|
async get() {
|
|
27332
|
-
|
|
27363
|
+
const rows = await query(this.buildSelect(), this.bindings);
|
|
27364
|
+
let results = rows;
|
|
27365
|
+
if (this.modelClass) {
|
|
27366
|
+
results = rows.map((r) => new this.modelClass().fill(r));
|
|
27367
|
+
}
|
|
27368
|
+
if (this.withRelations.length > 0 && this.modelClass && results.length > 0) {
|
|
27369
|
+
await this.eagerLoad(results);
|
|
27370
|
+
}
|
|
27371
|
+
return results;
|
|
27372
|
+
}
|
|
27373
|
+
async eagerLoad(results) {
|
|
27374
|
+
const instance = new this.modelClass;
|
|
27375
|
+
for (const relationName of this.withRelations) {
|
|
27376
|
+
if (typeof instance[relationName] !== "function") {
|
|
27377
|
+
console.warn(`[Model] Relation method '${relationName}' not found on ${this.modelClass.name}`);
|
|
27378
|
+
continue;
|
|
27379
|
+
}
|
|
27380
|
+
const relationQuery = instance[relationName]();
|
|
27381
|
+
const info = relationQuery._relationInfo;
|
|
27382
|
+
if (!info) {
|
|
27383
|
+
console.warn(`[Model] Method '${relationName}' did not return a valid relation QueryBuilder`);
|
|
27384
|
+
continue;
|
|
27385
|
+
}
|
|
27386
|
+
if (info.type === "hasMany" || info.type === "hasOne") {
|
|
27387
|
+
const localKey = info.localKey || "id";
|
|
27388
|
+
const foreignKey = info.foreignKey;
|
|
27389
|
+
const parentIds = results.map((r) => r[localKey]).filter((id) => id !== undefined && id !== null);
|
|
27390
|
+
if (parentIds.length === 0)
|
|
27391
|
+
continue;
|
|
27392
|
+
const uniqueIds = [...new Set(parentIds)];
|
|
27393
|
+
const relatedResults = await info.relatedClass.query().whereIn(foreignKey, uniqueIds).get();
|
|
27394
|
+
for (const parent of results) {
|
|
27395
|
+
const parentId = parent[localKey];
|
|
27396
|
+
if (info.type === "hasMany") {
|
|
27397
|
+
parent.startRelation(relationName);
|
|
27398
|
+
parent.relations[relationName] = relatedResults.filter((r) => r[foreignKey] == parentId);
|
|
27399
|
+
} else {
|
|
27400
|
+
parent.startRelation(relationName);
|
|
27401
|
+
parent.relations[relationName] = relatedResults.find((r) => r[foreignKey] == parentId) || null;
|
|
27402
|
+
}
|
|
27403
|
+
}
|
|
27404
|
+
} else if (info.type === "belongsTo") {
|
|
27405
|
+
const foreignKey = info.foreignKey;
|
|
27406
|
+
const ownerKey = info.ownerKey || "id";
|
|
27407
|
+
const relatedIds = results.map((r) => r[foreignKey]).filter((id) => id !== undefined && id !== null);
|
|
27408
|
+
if (relatedIds.length === 0)
|
|
27409
|
+
continue;
|
|
27410
|
+
const uniqueIds = [...new Set(relatedIds)];
|
|
27411
|
+
const relatedResults = await info.relatedClass.query().whereIn(ownerKey, uniqueIds).get();
|
|
27412
|
+
for (const parent of results) {
|
|
27413
|
+
const relatedId = parent[foreignKey];
|
|
27414
|
+
parent.startRelation(relationName);
|
|
27415
|
+
parent.relations[relationName] = relatedResults.find((r) => r[ownerKey] == relatedId) || null;
|
|
27416
|
+
}
|
|
27417
|
+
} else if (info.type === "belongsToMany") {
|
|
27418
|
+
const localKey = "id";
|
|
27419
|
+
const parentIds = results.map((r) => r[localKey]).filter((id) => id !== undefined && id !== null);
|
|
27420
|
+
if (parentIds.length === 0)
|
|
27421
|
+
continue;
|
|
27422
|
+
const uniqueIds = [...new Set(parentIds)];
|
|
27423
|
+
const pivotSql = `SELECT * FROM ${info.pivotTable} WHERE ${info.foreignPivotKey} IN (${uniqueIds.map(() => "?").join(",")})`;
|
|
27424
|
+
const pivotRows = await query(pivotSql, uniqueIds);
|
|
27425
|
+
if (pivotRows.length === 0) {
|
|
27426
|
+
for (const parent of results) {
|
|
27427
|
+
parent.startRelation(relationName);
|
|
27428
|
+
parent.relations[relationName] = [];
|
|
27429
|
+
}
|
|
27430
|
+
continue;
|
|
27431
|
+
}
|
|
27432
|
+
const relatedIds = pivotRows.map((r) => r[info.relatedPivotKey]);
|
|
27433
|
+
const uniqueRelatedIds = [...new Set(relatedIds)];
|
|
27434
|
+
const relatedResults = await info.relatedClass.query().whereIn("id", uniqueRelatedIds).get();
|
|
27435
|
+
for (const parent of results) {
|
|
27436
|
+
parent.startRelation(relationName);
|
|
27437
|
+
const myPivotRows = pivotRows.filter((r) => r[info.foreignPivotKey] == parent[localKey]);
|
|
27438
|
+
const myRelatedIds = myPivotRows.map((r) => r[info.relatedPivotKey]);
|
|
27439
|
+
parent.relations[relationName] = relatedResults.filter((r) => myRelatedIds.includes(r.id));
|
|
27440
|
+
}
|
|
27441
|
+
}
|
|
27442
|
+
}
|
|
27333
27443
|
}
|
|
27334
27444
|
async first() {
|
|
27335
27445
|
this.limitVal = 1;
|
|
27336
|
-
const
|
|
27337
|
-
return
|
|
27446
|
+
const rows = await this.get();
|
|
27447
|
+
return rows[0] || null;
|
|
27338
27448
|
}
|
|
27339
27449
|
async count() {
|
|
27340
27450
|
this.selectCols = ["COUNT(*) as count"];
|
|
27341
|
-
const r = await this.
|
|
27451
|
+
const r = await query(this.buildSelect(), this.bindings);
|
|
27342
27452
|
return r[0]?.count || 0;
|
|
27343
27453
|
}
|
|
27344
27454
|
async sum(col) {
|
|
27345
27455
|
this.selectCols = [`SUM(${String(col)}) as sum`];
|
|
27346
|
-
const r = await this.
|
|
27456
|
+
const r = await query(this.buildSelect(), this.bindings);
|
|
27347
27457
|
return r[0]?.sum || 0;
|
|
27348
27458
|
}
|
|
27349
27459
|
async avg(col) {
|
|
27350
27460
|
this.selectCols = [`AVG(${String(col)}) as avg`];
|
|
27351
|
-
const r = await this.
|
|
27461
|
+
const r = await query(this.buildSelect(), this.bindings);
|
|
27352
27462
|
return r[0]?.avg || 0;
|
|
27353
27463
|
}
|
|
27354
27464
|
async insert(data) {
|
|
@@ -27358,6 +27468,9 @@ class QueryBuilderImpl {
|
|
|
27358
27468
|
const placeholders = values.map(() => `(${keys.map(() => "?").join(",")})`).join(",");
|
|
27359
27469
|
const sql = `INSERT INTO ${this.table} (${keys.join(",")}) VALUES ${placeholders}`;
|
|
27360
27470
|
const result = await execute(sql, values.flat());
|
|
27471
|
+
if (this.modelClass) {
|
|
27472
|
+
return new this.modelClass().fill({ ...items[0], id: result.insertId });
|
|
27473
|
+
}
|
|
27361
27474
|
return { ...items[0], id: result.insertId };
|
|
27362
27475
|
}
|
|
27363
27476
|
async update(data) {
|
|
@@ -27386,14 +27499,39 @@ class Model {
|
|
|
27386
27499
|
static tableName;
|
|
27387
27500
|
static primaryKey = "id";
|
|
27388
27501
|
static timestamps = true;
|
|
27502
|
+
relations = {};
|
|
27503
|
+
constructor(data) {
|
|
27504
|
+
if (data)
|
|
27505
|
+
Object.assign(this, data);
|
|
27506
|
+
}
|
|
27389
27507
|
static table() {
|
|
27390
|
-
return new QueryBuilderImpl(this.tableName);
|
|
27508
|
+
return new QueryBuilderImpl(this.tableName, this);
|
|
27391
27509
|
}
|
|
27392
27510
|
static async find(id) {
|
|
27393
|
-
return
|
|
27511
|
+
return this.table().where(this.primaryKey, "=", id).first();
|
|
27512
|
+
}
|
|
27513
|
+
static query() {
|
|
27514
|
+
return new QueryBuilderImpl(this.tableName, this);
|
|
27394
27515
|
}
|
|
27395
27516
|
static async all() {
|
|
27396
|
-
return
|
|
27517
|
+
return this.query().get();
|
|
27518
|
+
}
|
|
27519
|
+
startRelation(name) {
|
|
27520
|
+
if (!this.relations)
|
|
27521
|
+
this.relations = {};
|
|
27522
|
+
}
|
|
27523
|
+
async load(...relations) {
|
|
27524
|
+
const builder = new QueryBuilderImpl(this.constructor.tableName, this.constructor);
|
|
27525
|
+
builder.with(...relations);
|
|
27526
|
+
await builder.eagerLoad([this]);
|
|
27527
|
+
return this;
|
|
27528
|
+
}
|
|
27529
|
+
static with(...relations) {
|
|
27530
|
+
return this.query().with(...relations);
|
|
27531
|
+
}
|
|
27532
|
+
fill(data) {
|
|
27533
|
+
Object.assign(this, data);
|
|
27534
|
+
return this;
|
|
27397
27535
|
}
|
|
27398
27536
|
static async create(data) {
|
|
27399
27537
|
if (this.timestamps) {
|
|
@@ -27401,7 +27539,7 @@ class Model {
|
|
|
27401
27539
|
data.created_at = now;
|
|
27402
27540
|
data.updated_at = now;
|
|
27403
27541
|
}
|
|
27404
|
-
return
|
|
27542
|
+
return this.query().insert(data);
|
|
27405
27543
|
}
|
|
27406
27544
|
static async updateById(id, data) {
|
|
27407
27545
|
if (this.timestamps) {
|
|
@@ -27412,8 +27550,49 @@ class Model {
|
|
|
27412
27550
|
static async deleteById(id) {
|
|
27413
27551
|
return new QueryBuilderImpl(this.tableName).where(this.primaryKey, "=", id).delete();
|
|
27414
27552
|
}
|
|
27415
|
-
|
|
27416
|
-
|
|
27553
|
+
hasOne(related, foreignKey, localKey = "id") {
|
|
27554
|
+
const fk = foreignKey || `${this.constructor.name.toLowerCase()}_id`;
|
|
27555
|
+
const pk = this[localKey];
|
|
27556
|
+
const qb = related.query();
|
|
27557
|
+
const info = { type: "hasOne", relatedClass: related, foreignKey: fk, localKey };
|
|
27558
|
+
qb._relationInfo = info;
|
|
27559
|
+
const promise = qb.where(fk, "=", pk).first();
|
|
27560
|
+
promise._relationInfo = info;
|
|
27561
|
+
return promise;
|
|
27562
|
+
}
|
|
27563
|
+
hasMany(related, foreignKey, localKey = "id") {
|
|
27564
|
+
const fk = foreignKey || `${this.constructor.name.toLowerCase()}_id`;
|
|
27565
|
+
const pk = this[localKey];
|
|
27566
|
+
const qb = related.query().where(fk, "=", pk);
|
|
27567
|
+
const info = { type: "hasMany", relatedClass: related, foreignKey: fk, localKey };
|
|
27568
|
+
qb._relationInfo = info;
|
|
27569
|
+
return qb;
|
|
27570
|
+
}
|
|
27571
|
+
belongsTo(related, foreignKey, ownerKey = "id") {
|
|
27572
|
+
const fk = foreignKey || `${related.name.toLowerCase()}_id`;
|
|
27573
|
+
const val = this[fk];
|
|
27574
|
+
const qb = related.query();
|
|
27575
|
+
const info = { type: "belongsTo", relatedClass: related, foreignKey: fk, ownerKey };
|
|
27576
|
+
qb._relationInfo = info;
|
|
27577
|
+
const promise = qb.where(ownerKey, "=", val).first();
|
|
27578
|
+
promise._relationInfo = info;
|
|
27579
|
+
return promise;
|
|
27580
|
+
}
|
|
27581
|
+
belongsToMany(related, pivotTable, foreignPivotKey, relatedPivotKey) {
|
|
27582
|
+
const foreignKey = foreignPivotKey || `${this.constructor.name.toLowerCase()}_id`;
|
|
27583
|
+
const relatedKey = relatedPivotKey || `${related.name.toLowerCase()}_id`;
|
|
27584
|
+
const pk = this.id;
|
|
27585
|
+
const qb = related.query();
|
|
27586
|
+
const info = {
|
|
27587
|
+
type: "belongsToMany",
|
|
27588
|
+
relatedClass: related,
|
|
27589
|
+
foreignKey: "",
|
|
27590
|
+
pivotTable,
|
|
27591
|
+
foreignPivotKey: foreignKey,
|
|
27592
|
+
relatedPivotKey: relatedKey
|
|
27593
|
+
};
|
|
27594
|
+
qb._relationInfo = info;
|
|
27595
|
+
return qb.select(`${related.tableName}.*`).join(pivotTable, `${pivotTable}.${relatedKey}`, "=", `${related.tableName}.id`).where(`${pivotTable}.${foreignKey}`, "=", pk);
|
|
27417
27596
|
}
|
|
27418
27597
|
}
|
|
27419
27598
|
// src/mvc/View.ts
|
|
@@ -27987,6 +28166,66 @@ var jitCompiler = new JITCompiler;
|
|
|
27987
28166
|
function createJITCompiler() {
|
|
27988
28167
|
return new JITCompiler;
|
|
27989
28168
|
}
|
|
28169
|
+
// src/auth/drivers/DatabaseSessionDriver.ts
|
|
28170
|
+
class SessionModel extends Model {
|
|
28171
|
+
static tableName = "sessions";
|
|
28172
|
+
static primaryKey = "id";
|
|
28173
|
+
static timestamps = false;
|
|
28174
|
+
}
|
|
28175
|
+
|
|
28176
|
+
class DatabaseSessionDriver {
|
|
28177
|
+
generateId() {
|
|
28178
|
+
return `sess_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
28179
|
+
}
|
|
28180
|
+
async create(userId, data, maxAge) {
|
|
28181
|
+
const id = this.generateId();
|
|
28182
|
+
const expiresAt = Date.now() + maxAge;
|
|
28183
|
+
const payload = JSON.stringify(data);
|
|
28184
|
+
await SessionModel.create({
|
|
28185
|
+
id,
|
|
28186
|
+
user_id: userId,
|
|
28187
|
+
payload,
|
|
28188
|
+
last_activity: new Date(Date.now()).toISOString().slice(0, 19).replace("T", " "),
|
|
28189
|
+
expires_at: expiresAt
|
|
28190
|
+
});
|
|
28191
|
+
return {
|
|
28192
|
+
id,
|
|
28193
|
+
userId,
|
|
28194
|
+
data,
|
|
28195
|
+
expiresAt
|
|
28196
|
+
};
|
|
28197
|
+
}
|
|
28198
|
+
async get(id) {
|
|
28199
|
+
const record = await SessionModel.find(id);
|
|
28200
|
+
if (!record)
|
|
28201
|
+
return null;
|
|
28202
|
+
const now = Date.now();
|
|
28203
|
+
if (record.expires_at < now) {
|
|
28204
|
+
this.destroy(id);
|
|
28205
|
+
return null;
|
|
28206
|
+
}
|
|
28207
|
+
let data = {};
|
|
28208
|
+
try {
|
|
28209
|
+
data = JSON.parse(record.payload);
|
|
28210
|
+
} catch (e) {
|
|
28211
|
+
}
|
|
28212
|
+
return {
|
|
28213
|
+
id,
|
|
28214
|
+
userId: record.user_id,
|
|
28215
|
+
data,
|
|
28216
|
+
expiresAt: record.expires_at
|
|
28217
|
+
};
|
|
28218
|
+
}
|
|
28219
|
+
async destroy(id) {
|
|
28220
|
+
const deleted = await SessionModel.deleteById(id);
|
|
28221
|
+
return deleted > 0;
|
|
28222
|
+
}
|
|
28223
|
+
async cleanup() {
|
|
28224
|
+
const now = Date.now();
|
|
28225
|
+
await SessionModel.query().where("expires_at", "<", now).delete();
|
|
28226
|
+
}
|
|
28227
|
+
}
|
|
28228
|
+
|
|
27990
28229
|
// src/auth/Auth.ts
|
|
27991
28230
|
async function hashPassword(password) {
|
|
27992
28231
|
return Bun.password.hash(password, {
|
|
@@ -28115,7 +28354,7 @@ function roles(...allowedRoles) {
|
|
|
28115
28354
|
};
|
|
28116
28355
|
}
|
|
28117
28356
|
|
|
28118
|
-
class
|
|
28357
|
+
class MemorySessionDriver {
|
|
28119
28358
|
sessions = new Map;
|
|
28120
28359
|
generateId() {
|
|
28121
28360
|
return `sess_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "")}`;
|
|
@@ -28151,12 +28390,37 @@ class SessionStore {
|
|
|
28151
28390
|
}
|
|
28152
28391
|
}
|
|
28153
28392
|
}
|
|
28393
|
+
|
|
28394
|
+
class SessionStore {
|
|
28395
|
+
driver;
|
|
28396
|
+
constructor(driver) {
|
|
28397
|
+
this.driver = driver || new MemorySessionDriver;
|
|
28398
|
+
}
|
|
28399
|
+
useDatabase() {
|
|
28400
|
+
this.driver = new DatabaseSessionDriver;
|
|
28401
|
+
}
|
|
28402
|
+
use(driver) {
|
|
28403
|
+
this.driver = driver;
|
|
28404
|
+
}
|
|
28405
|
+
async create(userId, data = {}, maxAge = 86400000) {
|
|
28406
|
+
return this.driver.create(userId, data, maxAge);
|
|
28407
|
+
}
|
|
28408
|
+
async get(id) {
|
|
28409
|
+
return this.driver.get(id);
|
|
28410
|
+
}
|
|
28411
|
+
async destroy(id) {
|
|
28412
|
+
return this.driver.destroy(id);
|
|
28413
|
+
}
|
|
28414
|
+
async cleanup() {
|
|
28415
|
+
return this.driver.cleanup();
|
|
28416
|
+
}
|
|
28417
|
+
}
|
|
28154
28418
|
var sessionStore = new SessionStore;
|
|
28155
28419
|
function sessionAuth(cookieName = "canx_session") {
|
|
28156
28420
|
return async (req, res, next) => {
|
|
28157
28421
|
const sessionId = req.cookie(cookieName);
|
|
28158
28422
|
if (sessionId) {
|
|
28159
|
-
const session = sessionStore.get(sessionId);
|
|
28423
|
+
const session = await sessionStore.get(sessionId);
|
|
28160
28424
|
if (session) {
|
|
28161
28425
|
req.context.set("session", session);
|
|
28162
28426
|
req.context.set("user", { sub: session.userId, ...session.data });
|
|
@@ -28263,6 +28527,11 @@ class TableBuilder {
|
|
|
28263
28527
|
this.columns[this.columns.length - 1].unique = true;
|
|
28264
28528
|
return this;
|
|
28265
28529
|
}
|
|
28530
|
+
primary() {
|
|
28531
|
+
if (this.columns.length)
|
|
28532
|
+
this.columns[this.columns.length - 1].primary = true;
|
|
28533
|
+
return this;
|
|
28534
|
+
}
|
|
28266
28535
|
index() {
|
|
28267
28536
|
if (this.columns.length)
|
|
28268
28537
|
this.columns[this.columns.length - 1].index = true;
|
|
@@ -28286,11 +28555,16 @@ class TableBuilder {
|
|
|
28286
28555
|
return this;
|
|
28287
28556
|
}
|
|
28288
28557
|
toSQL() {
|
|
28558
|
+
const driver = getCurrentDriver();
|
|
28289
28559
|
const cols = this.columns.map((col) => {
|
|
28290
28560
|
let sql = `\`${col.name}\` `;
|
|
28291
28561
|
switch (col.type) {
|
|
28292
28562
|
case "id":
|
|
28293
|
-
|
|
28563
|
+
if (driver === "sqlite") {
|
|
28564
|
+
sql += "INTEGER PRIMARY KEY AUTOINCREMENT";
|
|
28565
|
+
} else {
|
|
28566
|
+
sql += "BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY";
|
|
28567
|
+
}
|
|
28294
28568
|
break;
|
|
28295
28569
|
case "string":
|
|
28296
28570
|
sql += `VARCHAR(${col.length || 255})`;
|
|
@@ -28299,10 +28573,10 @@ class TableBuilder {
|
|
|
28299
28573
|
sql += "TEXT";
|
|
28300
28574
|
break;
|
|
28301
28575
|
case "int":
|
|
28302
|
-
sql += col.unsigned ? "INT UNSIGNED" : "INT";
|
|
28576
|
+
sql += col.unsigned ? driver === "sqlite" ? "INTEGER" : "INT UNSIGNED" : "INT";
|
|
28303
28577
|
break;
|
|
28304
28578
|
case "bigint":
|
|
28305
|
-
sql += col.unsigned ? "BIGINT UNSIGNED" : "BIGINT";
|
|
28579
|
+
sql += col.unsigned ? driver === "sqlite" ? "INTEGER" : "BIGINT UNSIGNED" : "BIGINT";
|
|
28306
28580
|
break;
|
|
28307
28581
|
case "float":
|
|
28308
28582
|
sql += "FLOAT";
|
|
@@ -28311,7 +28585,7 @@ class TableBuilder {
|
|
|
28311
28585
|
sql += `DECIMAL(${col.length || 10}, 2)`;
|
|
28312
28586
|
break;
|
|
28313
28587
|
case "boolean":
|
|
28314
|
-
sql += "TINYINT(1)";
|
|
28588
|
+
sql += driver === "sqlite" ? "INTEGER" : "TINYINT(1)";
|
|
28315
28589
|
break;
|
|
28316
28590
|
case "date":
|
|
28317
28591
|
sql += "DATE";
|
|
@@ -28338,6 +28612,8 @@ class TableBuilder {
|
|
|
28338
28612
|
}
|
|
28339
28613
|
if (col.unique)
|
|
28340
28614
|
sql += " UNIQUE";
|
|
28615
|
+
if (col.primary)
|
|
28616
|
+
sql += " PRIMARY KEY";
|
|
28341
28617
|
}
|
|
28342
28618
|
return sql;
|
|
28343
28619
|
});
|
|
@@ -28350,10 +28626,11 @@ class TableBuilder {
|
|
|
28350
28626
|
fk += ` ON UPDATE ${ref.onUpdate}`;
|
|
28351
28627
|
return fk;
|
|
28352
28628
|
});
|
|
28629
|
+
const suffix = driver === "sqlite" ? "" : " ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
|
|
28353
28630
|
return `CREATE TABLE \`${this.tableName}\` (
|
|
28354
28631
|
${[...cols, ...fks].join(`,
|
|
28355
28632
|
`)}
|
|
28356
|
-
)
|
|
28633
|
+
)${suffix}`;
|
|
28357
28634
|
}
|
|
28358
28635
|
}
|
|
28359
28636
|
var Schema2 = {
|
|
@@ -28375,10 +28652,20 @@ var Schema2 = {
|
|
|
28375
28652
|
console.log(`[Migration] Renamed table: ${from} -> ${to}`);
|
|
28376
28653
|
},
|
|
28377
28654
|
async hasTable(table) {
|
|
28655
|
+
const driver = getCurrentDriver();
|
|
28656
|
+
if (driver === "sqlite") {
|
|
28657
|
+
const result2 = await query(`SELECT count(*) as count FROM sqlite_master WHERE type='table' AND name='${table}'`);
|
|
28658
|
+
return (result2[0]?.count || result2[0]?.["count(*)"]) > 0;
|
|
28659
|
+
}
|
|
28378
28660
|
const result = await query(`SHOW TABLES LIKE '${table}'`);
|
|
28379
28661
|
return result.length > 0;
|
|
28380
28662
|
},
|
|
28381
28663
|
async hasColumn(table, column) {
|
|
28664
|
+
const driver = getCurrentDriver();
|
|
28665
|
+
if (driver === "sqlite") {
|
|
28666
|
+
const result2 = await query(`PRAGMA table_info(${table})`);
|
|
28667
|
+
return result2.some((c) => c.name === column);
|
|
28668
|
+
}
|
|
28382
28669
|
const result = await query(`SHOW COLUMNS FROM \`${table}\` LIKE '${column}'`);
|
|
28383
28670
|
return result.length > 0;
|
|
28384
28671
|
}
|
|
@@ -28421,7 +28708,7 @@ class Migrator {
|
|
|
28421
28708
|
for (const migration of pending) {
|
|
28422
28709
|
console.log(`[Migration] Running: ${migration.name}`);
|
|
28423
28710
|
await migration.up();
|
|
28424
|
-
await execute("INSERT INTO migrations (name, batch, executed_at) VALUES (?, ?,
|
|
28711
|
+
await execute("INSERT INTO migrations (name, batch, executed_at) VALUES (?, ?, CURRENT_TIMESTAMP)", [migration.name, batch]);
|
|
28425
28712
|
}
|
|
28426
28713
|
console.log(`[Migration] Completed ${pending.length} migrations.`);
|
|
28427
28714
|
}
|
|
@@ -30758,6 +31045,9 @@ function validate(data, schema) {
|
|
|
30758
31045
|
if (name !== "required" && (value === undefined || value === null || value === "")) {
|
|
30759
31046
|
continue;
|
|
30760
31047
|
}
|
|
31048
|
+
if (["unique", "exists"].includes(name)) {
|
|
31049
|
+
continue;
|
|
31050
|
+
}
|
|
30761
31051
|
let valid = false;
|
|
30762
31052
|
if (validators[name]) {
|
|
30763
31053
|
valid = validators[name](value);
|
|
@@ -30781,8 +31071,89 @@ function validate(data, schema) {
|
|
|
30781
31071
|
}
|
|
30782
31072
|
return { valid: errors2.size === 0, errors: errors2, data: validData };
|
|
30783
31073
|
}
|
|
30784
|
-
|
|
30785
|
-
|
|
31074
|
+
var asyncValidators = {
|
|
31075
|
+
unique: async (v, p) => {
|
|
31076
|
+
if (!p)
|
|
31077
|
+
return false;
|
|
31078
|
+
const [table, column, exceptId, idColumn = "id"] = p.split(",").map((s) => s.trim());
|
|
31079
|
+
let sql = `SELECT COUNT(*) as count FROM ${table} WHERE ${column} = ?`;
|
|
31080
|
+
const params = [v];
|
|
31081
|
+
if (exceptId) {
|
|
31082
|
+
sql += ` AND ${idColumn} != ?`;
|
|
31083
|
+
params.push(exceptId);
|
|
31084
|
+
}
|
|
31085
|
+
try {
|
|
31086
|
+
const rows = await query(sql, params);
|
|
31087
|
+
return (rows[0]?.count || 0) === 0;
|
|
31088
|
+
} catch (e) {
|
|
31089
|
+
console.error("[Validator] Database error in unique:", e);
|
|
31090
|
+
return false;
|
|
31091
|
+
}
|
|
31092
|
+
},
|
|
31093
|
+
exists: async (v, p) => {
|
|
31094
|
+
if (!p)
|
|
31095
|
+
return false;
|
|
31096
|
+
const [table, column = "id"] = p.split(",").map((s) => s.trim());
|
|
31097
|
+
const sql = `SELECT COUNT(*) as count FROM ${table} WHERE ${column} = ?`;
|
|
31098
|
+
try {
|
|
31099
|
+
const rows = await query(sql, [v]);
|
|
31100
|
+
return (rows[0]?.count || 0) > 0;
|
|
31101
|
+
} catch (e) {
|
|
31102
|
+
console.error("[Validator] Database error in exists:", e);
|
|
31103
|
+
return false;
|
|
31104
|
+
}
|
|
31105
|
+
}
|
|
31106
|
+
};
|
|
31107
|
+
var defaultAsyncMessages = {
|
|
31108
|
+
unique: "Field {field} has already been taken",
|
|
31109
|
+
exists: "Selected {field} is invalid"
|
|
31110
|
+
};
|
|
31111
|
+
async function validateAsync(data, schema) {
|
|
31112
|
+
const errors2 = new Map;
|
|
31113
|
+
const validData = {};
|
|
31114
|
+
const syncResult = validate(data, schema);
|
|
31115
|
+
if (!syncResult.valid) {
|
|
31116
|
+
syncResult.errors.forEach((msgs, field) => errors2.set(field, msgs));
|
|
31117
|
+
} else {
|
|
31118
|
+
Object.assign(validData, syncResult.data);
|
|
31119
|
+
}
|
|
31120
|
+
for (const [field, rules] of Object.entries(schema)) {
|
|
31121
|
+
const value = data[field];
|
|
31122
|
+
let ruleList;
|
|
31123
|
+
let customMessages = {};
|
|
31124
|
+
if (Array.isArray(rules)) {
|
|
31125
|
+
ruleList = rules;
|
|
31126
|
+
} else if (typeof rules === "object") {
|
|
31127
|
+
ruleList = rules.rules;
|
|
31128
|
+
customMessages = rules.messages || {};
|
|
31129
|
+
} else {
|
|
31130
|
+
ruleList = [rules];
|
|
31131
|
+
}
|
|
31132
|
+
for (const rule of ruleList) {
|
|
31133
|
+
const { name, param } = parseRule(rule);
|
|
31134
|
+
if (!asyncValidators[name] || (value === undefined || value === null || value === "")) {
|
|
31135
|
+
continue;
|
|
31136
|
+
}
|
|
31137
|
+
if (errors2.has(field))
|
|
31138
|
+
continue;
|
|
31139
|
+
try {
|
|
31140
|
+
const valid = await asyncValidators[name](value, param);
|
|
31141
|
+
if (!valid) {
|
|
31142
|
+
const msg = customMessages[name] || defaultAsyncMessages[name] || defaultMessages[name] || `Validation failed for {field}`;
|
|
31143
|
+
const finalMsg = msg.replace("{field}", field).replace("{param}", param || "");
|
|
31144
|
+
const existing = errors2.get(field) || [];
|
|
31145
|
+
existing.push(finalMsg);
|
|
31146
|
+
errors2.set(field, existing);
|
|
31147
|
+
}
|
|
31148
|
+
} catch (err) {
|
|
31149
|
+
console.error("[Validator] Unexpected error in validateAsync", err);
|
|
31150
|
+
}
|
|
31151
|
+
}
|
|
31152
|
+
if (!errors2.has(field) && value !== undefined) {
|
|
31153
|
+
validData[field] = value;
|
|
31154
|
+
}
|
|
31155
|
+
}
|
|
31156
|
+
return { valid: errors2.size === 0, errors: errors2, data: validData };
|
|
30786
31157
|
}
|
|
30787
31158
|
var is = {
|
|
30788
31159
|
email: (v) => validators.email(v),
|
|
@@ -32139,6 +32510,7 @@ export {
|
|
|
32139
32510
|
EventServiceProvider,
|
|
32140
32511
|
EventEmitter,
|
|
32141
32512
|
Delete,
|
|
32513
|
+
DatabaseSessionDriver,
|
|
32142
32514
|
DatabaseError,
|
|
32143
32515
|
Controller,
|
|
32144
32516
|
Container,
|