@fileverse/api 0.0.2 → 0.0.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/dist/cli/index.js +94 -287
- package/dist/cli/index.js.map +1 -1
- package/dist/commands/index.js +192 -356
- package/dist/commands/index.js.map +1 -1
- package/dist/index.js +255 -428
- package/dist/index.js.map +1 -1
- package/dist/worker.js +196 -367
- package/dist/worker.js.map +1 -1
- package/package.json +1 -3
package/dist/index.js
CHANGED
|
@@ -57,9 +57,6 @@ function getRuntimeConfig() {
|
|
|
57
57
|
get DB_PATH() {
|
|
58
58
|
return process.env.DB_PATH;
|
|
59
59
|
},
|
|
60
|
-
get DATABASE_URL() {
|
|
61
|
-
return process.env.DATABASE_URL;
|
|
62
|
-
},
|
|
63
60
|
get PORT() {
|
|
64
61
|
return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;
|
|
65
62
|
},
|
|
@@ -71,19 +68,16 @@ function getRuntimeConfig() {
|
|
|
71
68
|
}
|
|
72
69
|
};
|
|
73
70
|
}
|
|
74
|
-
function
|
|
71
|
+
function validateDbPath() {
|
|
75
72
|
const dbPath = process.env.DB_PATH;
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
console.error("
|
|
79
|
-
console.error("Please set DB_PATH or DATABASE_URL in your .env file (config/.env or ~/.fileverse/.env) or run the CLI first");
|
|
73
|
+
if (!dbPath) {
|
|
74
|
+
console.error("Error: DB_PATH environment variable is required");
|
|
75
|
+
console.error("Please set DB_PATH in your .env file (config/.env or ~/.fileverse/.env) or run the CLI first");
|
|
80
76
|
process.exit(1);
|
|
81
77
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
fs.mkdirSync(dbDir, { recursive: true });
|
|
86
|
-
}
|
|
78
|
+
const dbDir = path2.dirname(dbPath.trim());
|
|
79
|
+
if (!fs.existsSync(dbDir)) {
|
|
80
|
+
fs.mkdirSync(dbDir, { recursive: true });
|
|
87
81
|
}
|
|
88
82
|
}
|
|
89
83
|
var config = {
|
|
@@ -112,9 +106,6 @@ var config = {
|
|
|
112
106
|
get DB_PATH() {
|
|
113
107
|
return process.env.DB_PATH;
|
|
114
108
|
},
|
|
115
|
-
get DATABASE_URL() {
|
|
116
|
-
return process.env.DATABASE_URL;
|
|
117
|
-
},
|
|
118
109
|
get PORT() {
|
|
119
110
|
return process.env.PORT || STATIC_CONFIG.DEFAULT_PORT;
|
|
120
111
|
},
|
|
@@ -207,237 +198,71 @@ var asyncHandlerArray = (resolvers) => {
|
|
|
207
198
|
return resolvers.map(asyncHandler);
|
|
208
199
|
};
|
|
209
200
|
|
|
210
|
-
// src/infra/database/
|
|
201
|
+
// src/infra/database/connection.ts
|
|
211
202
|
import Database from "better-sqlite3";
|
|
212
|
-
var
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
verbose: config.NODE_ENV === "development" ? (msg) => logger.debug(String(msg)) : void 0
|
|
217
|
-
});
|
|
218
|
-
this.db.pragma("journal_mode = WAL");
|
|
219
|
-
this.db.pragma("foreign_keys = ON");
|
|
220
|
-
this.db.prepare("SELECT 1").get();
|
|
221
|
-
logger.info(`SQLite database connected: ${dbPath}`);
|
|
222
|
-
}
|
|
223
|
-
async select(sql, params = []) {
|
|
224
|
-
const stmt = this.db.prepare(sql);
|
|
225
|
-
return stmt.all(params);
|
|
226
|
-
}
|
|
227
|
-
async selectOne(sql, params = []) {
|
|
228
|
-
const stmt = this.db.prepare(sql);
|
|
229
|
-
return stmt.get(params);
|
|
230
|
-
}
|
|
231
|
-
async execute(sql, params = []) {
|
|
232
|
-
const stmt = this.db.prepare(sql);
|
|
233
|
-
const result = stmt.run(params);
|
|
234
|
-
return {
|
|
235
|
-
changes: result.changes,
|
|
236
|
-
lastInsertRowid: result.lastInsertRowid
|
|
237
|
-
};
|
|
203
|
+
var DatabaseConnectionManager = class _DatabaseConnectionManager {
|
|
204
|
+
static instance;
|
|
205
|
+
db = null;
|
|
206
|
+
constructor() {
|
|
238
207
|
}
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
const result = await callback(this);
|
|
243
|
-
this.db.exec("RELEASE txn");
|
|
244
|
-
return result;
|
|
245
|
-
} catch (err) {
|
|
246
|
-
this.db.exec("ROLLBACK TO txn");
|
|
247
|
-
throw err;
|
|
208
|
+
static getInstance() {
|
|
209
|
+
if (!_DatabaseConnectionManager.instance) {
|
|
210
|
+
_DatabaseConnectionManager.instance = new _DatabaseConnectionManager();
|
|
248
211
|
}
|
|
212
|
+
return _DatabaseConnectionManager.instance;
|
|
249
213
|
}
|
|
250
|
-
|
|
251
|
-
this.db
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
import pg from "pg";
|
|
261
|
-
var { Pool } = pg;
|
|
262
|
-
var COLUMN_NAME_MAP = {
|
|
263
|
-
ddocid: "ddocId",
|
|
264
|
-
localversion: "localVersion",
|
|
265
|
-
onchainversion: "onchainVersion",
|
|
266
|
-
syncstatus: "syncStatus",
|
|
267
|
-
isdeleted: "isDeleted",
|
|
268
|
-
onchainfileid: "onChainFileId",
|
|
269
|
-
portaladdress: "portalAddress",
|
|
270
|
-
createdat: "createdAt",
|
|
271
|
-
updatedat: "updatedAt",
|
|
272
|
-
linkkey: "linkKey",
|
|
273
|
-
linkkeynonce: "linkKeyNonce",
|
|
274
|
-
commentkey: "commentKey",
|
|
275
|
-
portalseed: "portalSeed",
|
|
276
|
-
owneraddress: "ownerAddress",
|
|
277
|
-
apikeyseed: "apiKeySeed",
|
|
278
|
-
collaboratoraddress: "collaboratorAddress",
|
|
279
|
-
fileid: "fileId",
|
|
280
|
-
retrycount: "retryCount",
|
|
281
|
-
lasterror: "lastError",
|
|
282
|
-
lockedat: "lockedAt",
|
|
283
|
-
nextretryat: "nextRetryAt",
|
|
284
|
-
userophash: "userOpHash",
|
|
285
|
-
pendingpayload: "pendingPayload",
|
|
286
|
-
folderid: "folderId",
|
|
287
|
-
folderref: "folderRef",
|
|
288
|
-
foldername: "folderName",
|
|
289
|
-
metadataipfshash: "metadataIPFSHash",
|
|
290
|
-
contentipfshash: "contentIPFSHash",
|
|
291
|
-
lasttransactionhash: "lastTransactionHash",
|
|
292
|
-
lasttransactionblocknumber: "lastTransactionBlockNumber",
|
|
293
|
-
lasttransactionblocktimestamp: "lastTransactionBlockTimestamp",
|
|
294
|
-
created_at: "created_at",
|
|
295
|
-
updated_at: "updated_at"
|
|
296
|
-
};
|
|
297
|
-
function translateParams(sql) {
|
|
298
|
-
let idx = 0;
|
|
299
|
-
return sql.replace(/\?/g, () => `$${++idx}`);
|
|
300
|
-
}
|
|
301
|
-
function mapRow(row) {
|
|
302
|
-
const mapped = {};
|
|
303
|
-
for (const [key, value] of Object.entries(row)) {
|
|
304
|
-
const mappedKey = COLUMN_NAME_MAP[key] ?? key;
|
|
305
|
-
mapped[mappedKey] = value instanceof Date ? value.toISOString() : value;
|
|
306
|
-
}
|
|
307
|
-
return mapped;
|
|
308
|
-
}
|
|
309
|
-
var PgClientAdapter = class {
|
|
310
|
-
constructor(client) {
|
|
311
|
-
this.client = client;
|
|
312
|
-
}
|
|
313
|
-
async select(sql, params = []) {
|
|
314
|
-
const result = await this.client.query(translateParams(sql), params);
|
|
315
|
-
return result.rows.map((row) => mapRow(row));
|
|
316
|
-
}
|
|
317
|
-
async selectOne(sql, params = []) {
|
|
318
|
-
const result = await this.client.query(translateParams(sql), params);
|
|
319
|
-
return result.rows[0] ? mapRow(result.rows[0]) : void 0;
|
|
320
|
-
}
|
|
321
|
-
async execute(sql, params = []) {
|
|
322
|
-
const result = await this.client.query(translateParams(sql), params);
|
|
323
|
-
return { changes: result.rowCount ?? 0, lastInsertRowid: 0 };
|
|
324
|
-
}
|
|
325
|
-
async transaction(callback) {
|
|
326
|
-
await this.client.query("SAVEPOINT nested_txn");
|
|
327
|
-
try {
|
|
328
|
-
const result = await callback(this);
|
|
329
|
-
await this.client.query("RELEASE SAVEPOINT nested_txn");
|
|
330
|
-
return result;
|
|
331
|
-
} catch (err) {
|
|
332
|
-
await this.client.query("ROLLBACK TO SAVEPOINT nested_txn");
|
|
333
|
-
throw err;
|
|
214
|
+
getConnection() {
|
|
215
|
+
if (!this.db) {
|
|
216
|
+
const dbPath = config.DB_PATH;
|
|
217
|
+
this.db = new Database(dbPath, {
|
|
218
|
+
verbose: config.NODE_ENV === "development" ? (msg) => logger.debug(String(msg)) : void 0
|
|
219
|
+
});
|
|
220
|
+
this.db.pragma("journal_mode = WAL");
|
|
221
|
+
this.db.pragma("foreign_keys = ON");
|
|
222
|
+
this.db.prepare("SELECT 1").get();
|
|
223
|
+
logger.info(`SQLite database connected: ${dbPath}`);
|
|
334
224
|
}
|
|
335
|
-
|
|
336
|
-
async exec(sql) {
|
|
337
|
-
await this.client.query(sql);
|
|
225
|
+
return this.db;
|
|
338
226
|
}
|
|
339
227
|
async close() {
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
constructor(connectionString) {
|
|
345
|
-
const url2 = new URL(connectionString);
|
|
346
|
-
const isLocal = url2.hostname === "localhost" || url2.hostname === "127.0.0.1" || url2.hostname === "::1";
|
|
347
|
-
const sslDisabled = connectionString.includes("sslmode=disable");
|
|
348
|
-
const needsSsl = !isLocal && !sslDisabled;
|
|
349
|
-
this.pool = new Pool({
|
|
350
|
-
connectionString,
|
|
351
|
-
// pg requires password to be a string; local trust/peer auth uses empty string
|
|
352
|
-
password: url2.password || "",
|
|
353
|
-
max: 20,
|
|
354
|
-
idleTimeoutMillis: 3e4,
|
|
355
|
-
ssl: needsSsl ? { rejectUnauthorized: false } : void 0
|
|
356
|
-
});
|
|
357
|
-
logger.info(`PostgreSQL pool created (ssl: ${needsSsl ? "on" : "off"})`);
|
|
358
|
-
}
|
|
359
|
-
async select(sql, params = []) {
|
|
360
|
-
const result = await this.pool.query(translateParams(sql), params);
|
|
361
|
-
return result.rows.map((row) => mapRow(row));
|
|
362
|
-
}
|
|
363
|
-
async selectOne(sql, params = []) {
|
|
364
|
-
const result = await this.pool.query(translateParams(sql), params);
|
|
365
|
-
return result.rows[0] ? mapRow(result.rows[0]) : void 0;
|
|
366
|
-
}
|
|
367
|
-
async execute(sql, params = []) {
|
|
368
|
-
const result = await this.pool.query(translateParams(sql), params);
|
|
369
|
-
return { changes: result.rowCount ?? 0, lastInsertRowid: 0 };
|
|
370
|
-
}
|
|
371
|
-
async transaction(callback) {
|
|
372
|
-
const client = await this.pool.connect();
|
|
373
|
-
try {
|
|
374
|
-
await client.query("BEGIN");
|
|
375
|
-
const clientAdapter = new PgClientAdapter(client);
|
|
376
|
-
const result = await callback(clientAdapter);
|
|
377
|
-
await client.query("COMMIT");
|
|
378
|
-
return result;
|
|
379
|
-
} catch (err) {
|
|
380
|
-
await client.query("ROLLBACK");
|
|
381
|
-
throw err;
|
|
382
|
-
} finally {
|
|
383
|
-
client.release();
|
|
228
|
+
if (this.db) {
|
|
229
|
+
this.db.close();
|
|
230
|
+
this.db = null;
|
|
231
|
+
logger.info("Database connection closed");
|
|
384
232
|
}
|
|
385
233
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
}
|
|
389
|
-
async close() {
|
|
390
|
-
await this.pool.end();
|
|
391
|
-
logger.info("PostgreSQL pool closed");
|
|
234
|
+
isConnected() {
|
|
235
|
+
return this.db !== null && this.db.open;
|
|
392
236
|
}
|
|
393
237
|
};
|
|
394
|
-
|
|
395
|
-
// src/infra/database/connection.ts
|
|
396
|
-
var adapter = null;
|
|
397
|
-
async function getAdapter() {
|
|
398
|
-
if (adapter) return adapter;
|
|
399
|
-
const databaseUrl = config.DATABASE_URL;
|
|
400
|
-
const dbPath = config.DB_PATH;
|
|
401
|
-
if (databaseUrl) {
|
|
402
|
-
adapter = new PgAdapter(databaseUrl);
|
|
403
|
-
} else if (dbPath) {
|
|
404
|
-
adapter = new SqliteAdapter(dbPath);
|
|
405
|
-
} else {
|
|
406
|
-
throw new Error("Either DATABASE_URL or DB_PATH must be set");
|
|
407
|
-
}
|
|
408
|
-
return adapter;
|
|
409
|
-
}
|
|
410
|
-
function getAdapterSync() {
|
|
411
|
-
if (!adapter) {
|
|
412
|
-
throw new Error(
|
|
413
|
-
"Database adapter not initialized. Call getAdapter() at startup first."
|
|
414
|
-
);
|
|
415
|
-
}
|
|
416
|
-
return adapter;
|
|
417
|
-
}
|
|
418
|
-
async function closeAdapter() {
|
|
419
|
-
if (adapter) {
|
|
420
|
-
await adapter.close();
|
|
421
|
-
adapter = null;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
238
|
+
var databaseConnectionManager = DatabaseConnectionManager.getInstance();
|
|
424
239
|
|
|
425
240
|
// src/domain/file/constants.ts
|
|
426
241
|
var DEFAULT_LIST_LIMIT = 10;
|
|
427
242
|
|
|
428
243
|
// src/infra/database/query-builder.ts
|
|
244
|
+
function getDb() {
|
|
245
|
+
return databaseConnectionManager.getConnection();
|
|
246
|
+
}
|
|
429
247
|
var QueryBuilder = class {
|
|
430
|
-
static
|
|
431
|
-
|
|
248
|
+
static select(sql, params = []) {
|
|
249
|
+
const stmt = getDb().prepare(sql);
|
|
250
|
+
return stmt.all(params);
|
|
432
251
|
}
|
|
433
|
-
static
|
|
434
|
-
|
|
252
|
+
static selectOne(sql, params = []) {
|
|
253
|
+
const stmt = getDb().prepare(sql);
|
|
254
|
+
return stmt.get(params);
|
|
435
255
|
}
|
|
436
|
-
static
|
|
437
|
-
|
|
256
|
+
static execute(sql, params = []) {
|
|
257
|
+
const stmt = getDb().prepare(sql);
|
|
258
|
+
const result = stmt.run(params);
|
|
259
|
+
return {
|
|
260
|
+
changes: result.changes,
|
|
261
|
+
lastInsertRowid: result.lastInsertRowid
|
|
262
|
+
};
|
|
438
263
|
}
|
|
439
|
-
static
|
|
440
|
-
return
|
|
264
|
+
static transaction(callback) {
|
|
265
|
+
return getDb().transaction(callback)();
|
|
441
266
|
}
|
|
442
267
|
static paginate(sql, options = {}) {
|
|
443
268
|
let query = sql;
|
|
@@ -456,6 +281,15 @@ var QueryBuilder = class {
|
|
|
456
281
|
}
|
|
457
282
|
};
|
|
458
283
|
|
|
284
|
+
// src/infra/database/index.ts
|
|
285
|
+
function getDb2() {
|
|
286
|
+
return databaseConnectionManager.getConnection();
|
|
287
|
+
}
|
|
288
|
+
var closeDatabase = async () => {
|
|
289
|
+
await databaseConnectionManager.close();
|
|
290
|
+
};
|
|
291
|
+
var database_default = getDb2;
|
|
292
|
+
|
|
459
293
|
// src/infra/database/models/files.model.ts
|
|
460
294
|
import { uuidv7 } from "uuidv7";
|
|
461
295
|
var FilesModel = class {
|
|
@@ -489,15 +323,15 @@ var FilesModel = class {
|
|
|
489
323
|
link: fileRaw.link
|
|
490
324
|
};
|
|
491
325
|
}
|
|
492
|
-
static
|
|
326
|
+
static findAll(portalAddress, limit, skip) {
|
|
493
327
|
const whereClause = "isDeleted = 0 AND portalAddress = ?";
|
|
494
328
|
const params = [portalAddress];
|
|
495
329
|
const countSql = `
|
|
496
|
-
SELECT COUNT(*) as count
|
|
497
|
-
FROM ${this.TABLE}
|
|
330
|
+
SELECT COUNT(*) as count
|
|
331
|
+
FROM ${this.TABLE}
|
|
498
332
|
WHERE ${whereClause}
|
|
499
333
|
`;
|
|
500
|
-
const totalResult =
|
|
334
|
+
const totalResult = QueryBuilder.selectOne(countSql, params);
|
|
501
335
|
const total = totalResult?.count || 0;
|
|
502
336
|
const sql = `
|
|
503
337
|
SELECT *
|
|
@@ -510,51 +344,51 @@ var FilesModel = class {
|
|
|
510
344
|
orderBy: "createdAt",
|
|
511
345
|
orderDirection: "DESC"
|
|
512
346
|
});
|
|
513
|
-
const filesRaw =
|
|
347
|
+
const filesRaw = QueryBuilder.select(completeSql, params);
|
|
514
348
|
const files = filesRaw.map(this.parseFile);
|
|
515
349
|
const hasNext = skip !== void 0 && limit !== void 0 ? skip + limit < total : false;
|
|
516
350
|
return { files, total, hasNext };
|
|
517
351
|
}
|
|
518
|
-
static
|
|
352
|
+
static findById(_id, portalAddress) {
|
|
519
353
|
const sql = `
|
|
520
354
|
SELECT *
|
|
521
|
-
FROM ${this.TABLE}
|
|
355
|
+
FROM ${this.TABLE}
|
|
522
356
|
WHERE _id = ? AND isDeleted = 0 AND portalAddress = ?
|
|
523
357
|
`;
|
|
524
|
-
const result =
|
|
358
|
+
const result = QueryBuilder.selectOne(sql, [_id, portalAddress]);
|
|
525
359
|
return result ? this.parseFile(result) : void 0;
|
|
526
360
|
}
|
|
527
|
-
static
|
|
361
|
+
static findByIdIncludingDeleted(_id) {
|
|
528
362
|
const sql = `
|
|
529
363
|
SELECT *
|
|
530
|
-
FROM ${this.TABLE}
|
|
364
|
+
FROM ${this.TABLE}
|
|
531
365
|
WHERE _id = ?
|
|
532
366
|
`;
|
|
533
|
-
const result =
|
|
367
|
+
const result = QueryBuilder.selectOne(sql, [_id]);
|
|
534
368
|
return result ? this.parseFile(result) : void 0;
|
|
535
369
|
}
|
|
536
|
-
static
|
|
370
|
+
static findByIdExcludingDeleted(_id) {
|
|
537
371
|
const sql = `
|
|
538
372
|
SELECT *
|
|
539
|
-
FROM ${this.TABLE}
|
|
373
|
+
FROM ${this.TABLE}
|
|
540
374
|
WHERE _id = ? AND isDeleted = 0
|
|
541
375
|
`;
|
|
542
|
-
const result =
|
|
376
|
+
const result = QueryBuilder.selectOne(sql, [_id]);
|
|
543
377
|
return result ? this.parseFile(result) : void 0;
|
|
544
378
|
}
|
|
545
|
-
static
|
|
379
|
+
static findByDDocId(ddocId, portalAddress) {
|
|
546
380
|
const sql = `
|
|
547
381
|
SELECT *
|
|
548
|
-
FROM ${this.TABLE}
|
|
382
|
+
FROM ${this.TABLE}
|
|
549
383
|
WHERE ddocId = ? AND isDeleted = 0 AND portalAddress = ?
|
|
550
384
|
`;
|
|
551
|
-
const result =
|
|
385
|
+
const result = QueryBuilder.selectOne(sql, [ddocId, portalAddress]);
|
|
552
386
|
return result ? this.parseFile(result) : void 0;
|
|
553
387
|
}
|
|
554
|
-
static
|
|
388
|
+
static searchByTitle(searchTerm, portalAddress, limit, skip) {
|
|
555
389
|
const sql = `
|
|
556
390
|
SELECT *
|
|
557
|
-
FROM ${this.TABLE}
|
|
391
|
+
FROM ${this.TABLE}
|
|
558
392
|
WHERE LOWER(title) LIKE LOWER(?) AND isDeleted = 0 AND portalAddress = ?
|
|
559
393
|
`;
|
|
560
394
|
const completeSql = QueryBuilder.paginate(sql, {
|
|
@@ -563,24 +397,24 @@ var FilesModel = class {
|
|
|
563
397
|
orderBy: "createdAt",
|
|
564
398
|
orderDirection: "DESC"
|
|
565
399
|
});
|
|
566
|
-
const filesRaw =
|
|
400
|
+
const filesRaw = QueryBuilder.select(completeSql, [`%${searchTerm}%`, portalAddress]);
|
|
567
401
|
return filesRaw.map(this.parseFile);
|
|
568
402
|
}
|
|
569
|
-
static
|
|
403
|
+
static create(input) {
|
|
570
404
|
const _id = uuidv7();
|
|
571
405
|
const sql = `
|
|
572
|
-
INSERT INTO ${this.TABLE}
|
|
573
|
-
(_id, title, content, ddocId, portalAddress)
|
|
406
|
+
INSERT INTO ${this.TABLE}
|
|
407
|
+
(_id, title, content, ddocId, portalAddress)
|
|
574
408
|
VALUES (?, ?, ?, ?, ?)
|
|
575
409
|
`;
|
|
576
|
-
|
|
577
|
-
const created =
|
|
410
|
+
QueryBuilder.execute(sql, [_id, input.title, input.content, input.ddocId, input.portalAddress]);
|
|
411
|
+
const created = this.findById(_id, input.portalAddress);
|
|
578
412
|
if (!created) {
|
|
579
413
|
throw new Error("Failed to create file");
|
|
580
414
|
}
|
|
581
415
|
return created;
|
|
582
416
|
}
|
|
583
|
-
static
|
|
417
|
+
static update(_id, payload, portalAddress) {
|
|
584
418
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
585
419
|
const keys = [];
|
|
586
420
|
const values = [];
|
|
@@ -599,22 +433,22 @@ var FilesModel = class {
|
|
|
599
433
|
values.push(now, _id, portalAddress);
|
|
600
434
|
const updateChain = keys.join(", ");
|
|
601
435
|
const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE _id = ? AND portalAddress = ?`;
|
|
602
|
-
|
|
603
|
-
const updated =
|
|
436
|
+
QueryBuilder.execute(sql, values);
|
|
437
|
+
const updated = this.findById(_id, portalAddress);
|
|
604
438
|
if (!updated) {
|
|
605
439
|
throw new Error("Failed to update file");
|
|
606
440
|
}
|
|
607
441
|
return updated;
|
|
608
442
|
}
|
|
609
|
-
static
|
|
443
|
+
static softDelete(_id) {
|
|
610
444
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
611
445
|
const sql = `
|
|
612
|
-
UPDATE ${this.TABLE}
|
|
446
|
+
UPDATE ${this.TABLE}
|
|
613
447
|
SET isDeleted = 1, syncStatus = 'pending', updatedAt = ?
|
|
614
448
|
WHERE _id = ?
|
|
615
449
|
`;
|
|
616
|
-
|
|
617
|
-
const deleted =
|
|
450
|
+
QueryBuilder.execute(sql, [now, _id]);
|
|
451
|
+
const deleted = this.findByIdIncludingDeleted(_id);
|
|
618
452
|
if (!deleted) {
|
|
619
453
|
throw new Error("Failed to delete file");
|
|
620
454
|
}
|
|
@@ -626,22 +460,22 @@ var FilesModel = class {
|
|
|
626
460
|
import { uuidv7 as uuidv72 } from "uuidv7";
|
|
627
461
|
var PortalsModel = class {
|
|
628
462
|
static TABLE = "portals";
|
|
629
|
-
static
|
|
463
|
+
static findByPortalAddress(portalAddress) {
|
|
630
464
|
const sql = `SELECT _id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt FROM ${this.TABLE} WHERE portalAddress = ?`;
|
|
631
465
|
return QueryBuilder.selectOne(sql, [portalAddress]);
|
|
632
466
|
}
|
|
633
|
-
static
|
|
467
|
+
static create(input) {
|
|
634
468
|
const _id = uuidv72();
|
|
635
469
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
636
470
|
const sql = `INSERT INTO ${this.TABLE} (_id, portalAddress, portalSeed, ownerAddress, createdAt, updatedAt) VALUES (?, ?, ?, ?, ?, ?)`;
|
|
637
|
-
|
|
638
|
-
const created =
|
|
471
|
+
QueryBuilder.execute(sql, [_id, input.portalAddress, input.portalSeed, input.ownerAddress, now, now]);
|
|
472
|
+
const created = this.findByPortalAddress(input.portalAddress);
|
|
639
473
|
if (!created) {
|
|
640
474
|
throw new Error("Failed to create portal");
|
|
641
475
|
}
|
|
642
476
|
return created;
|
|
643
477
|
}
|
|
644
|
-
static
|
|
478
|
+
static update(portalAddress, input) {
|
|
645
479
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
646
480
|
const keys = [];
|
|
647
481
|
const values = [];
|
|
@@ -656,15 +490,15 @@ var PortalsModel = class {
|
|
|
656
490
|
const updateChain = keys.join(", ");
|
|
657
491
|
const sql = `UPDATE ${this.TABLE} SET ${updateChain} WHERE portalAddress = ?`;
|
|
658
492
|
values.push(portalAddress);
|
|
659
|
-
|
|
660
|
-
const updated =
|
|
493
|
+
QueryBuilder.execute(sql, values);
|
|
494
|
+
const updated = this.findByPortalAddress(portalAddress);
|
|
661
495
|
if (!updated) {
|
|
662
496
|
throw new Error("Failed to update portal");
|
|
663
497
|
}
|
|
664
498
|
return updated;
|
|
665
499
|
}
|
|
666
|
-
static
|
|
667
|
-
const existing =
|
|
500
|
+
static upsert(input) {
|
|
501
|
+
const existing = this.findByPortalAddress(input.portalAddress);
|
|
668
502
|
if (existing) {
|
|
669
503
|
return this.update(input.portalAddress, {
|
|
670
504
|
portalSeed: input.portalSeed,
|
|
@@ -679,12 +513,12 @@ var PortalsModel = class {
|
|
|
679
513
|
import { uuidv7 as uuidv73 } from "uuidv7";
|
|
680
514
|
var ApiKeysModel = class {
|
|
681
515
|
static TABLE = "api_keys";
|
|
682
|
-
static
|
|
516
|
+
static create(input) {
|
|
683
517
|
const _id = uuidv73();
|
|
684
518
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
685
|
-
const sql = `INSERT INTO ${this.TABLE} (_id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt)
|
|
519
|
+
const sql = `INSERT INTO ${this.TABLE} (_id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt)
|
|
686
520
|
VALUES (?, ?, ?, ?, ?, ?)`;
|
|
687
|
-
const result =
|
|
521
|
+
const result = QueryBuilder.execute(sql, [
|
|
688
522
|
_id,
|
|
689
523
|
input.apiKeySeed,
|
|
690
524
|
input.name,
|
|
@@ -695,29 +529,29 @@ var ApiKeysModel = class {
|
|
|
695
529
|
if (result.changes === 0) {
|
|
696
530
|
throw new Error("Failed to create API key");
|
|
697
531
|
}
|
|
698
|
-
const created =
|
|
532
|
+
const created = this.findById(_id);
|
|
699
533
|
if (!created) {
|
|
700
534
|
throw new Error("Failed to create API key");
|
|
701
535
|
}
|
|
702
536
|
return created;
|
|
703
537
|
}
|
|
704
|
-
static
|
|
538
|
+
static findById(_id) {
|
|
705
539
|
const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE _id = ? AND isDeleted = 0`;
|
|
706
540
|
return QueryBuilder.selectOne(sql, [_id]);
|
|
707
541
|
}
|
|
708
|
-
static
|
|
542
|
+
static findByCollaboratorAddress(collaboratorAddress) {
|
|
709
543
|
const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE collaboratorAddress = ? AND isDeleted = 0 LIMIT 1`;
|
|
710
544
|
return QueryBuilder.selectOne(sql, [collaboratorAddress]);
|
|
711
545
|
}
|
|
712
|
-
static
|
|
546
|
+
static delete(_id) {
|
|
713
547
|
const sql = `UPDATE ${this.TABLE} SET isDeleted = 1 WHERE _id = ?`;
|
|
714
|
-
|
|
548
|
+
QueryBuilder.execute(sql, [_id]);
|
|
715
549
|
}
|
|
716
|
-
static
|
|
550
|
+
static findByPortalAddress(portalAddress) {
|
|
717
551
|
const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE portalAddress = ? AND isDeleted = 0`;
|
|
718
552
|
return QueryBuilder.selectOne(sql, [portalAddress]);
|
|
719
553
|
}
|
|
720
|
-
static
|
|
554
|
+
static findByApiKey(apiKey) {
|
|
721
555
|
const sql = `SELECT _id, apiKeySeed, name, collaboratorAddress, portalAddress, createdAt, isDeleted FROM ${this.TABLE} WHERE apiKeySeed = ? AND isDeleted = 0`;
|
|
722
556
|
return QueryBuilder.selectOne(sql, [apiKey]);
|
|
723
557
|
}
|
|
@@ -729,9 +563,9 @@ var FoldersModel = class {
|
|
|
729
563
|
/**
|
|
730
564
|
* List all folders with pagination
|
|
731
565
|
*/
|
|
732
|
-
static
|
|
566
|
+
static findAll(limit, skip) {
|
|
733
567
|
const countSql = `SELECT COUNT(*) as count FROM ${this.TABLE} WHERE isDeleted = 0`;
|
|
734
|
-
const totalResult =
|
|
568
|
+
const totalResult = QueryBuilder.selectOne(countSql);
|
|
735
569
|
const total = totalResult?.count || 0;
|
|
736
570
|
const sql = QueryBuilder.paginate(`SELECT * FROM ${this.TABLE} WHERE isDeleted = 0`, {
|
|
737
571
|
limit,
|
|
@@ -739,8 +573,7 @@ var FoldersModel = class {
|
|
|
739
573
|
orderBy: "created_at",
|
|
740
574
|
orderDirection: "DESC"
|
|
741
575
|
});
|
|
742
|
-
const
|
|
743
|
-
const folders = foldersRaw.map((folderRaw) => ({
|
|
576
|
+
const folders = QueryBuilder.select(sql).map((folderRaw) => ({
|
|
744
577
|
...folderRaw,
|
|
745
578
|
isDeleted: Boolean(folderRaw.isDeleted)
|
|
746
579
|
}));
|
|
@@ -751,9 +584,9 @@ var FoldersModel = class {
|
|
|
751
584
|
* Get a single folder by folderRef and folderId
|
|
752
585
|
* Includes ddocs array (as per API spec)
|
|
753
586
|
*/
|
|
754
|
-
static
|
|
587
|
+
static findByFolderRefAndId(folderRef, folderId) {
|
|
755
588
|
const sql = `SELECT * FROM ${this.TABLE} WHERE folderRef = ? AND folderId = ? AND isDeleted = 0`;
|
|
756
|
-
const folderRaw =
|
|
589
|
+
const folderRaw = QueryBuilder.selectOne(sql, [folderRef, folderId]);
|
|
757
590
|
if (!folderRaw) {
|
|
758
591
|
return void 0;
|
|
759
592
|
}
|
|
@@ -770,9 +603,9 @@ var FoldersModel = class {
|
|
|
770
603
|
/**
|
|
771
604
|
* Get folder by folderRef only
|
|
772
605
|
*/
|
|
773
|
-
static
|
|
606
|
+
static findByFolderRef(folderRef) {
|
|
774
607
|
const sql = `SELECT * FROM ${this.TABLE} WHERE folderRef = ? AND isDeleted = 0 LIMIT 1`;
|
|
775
|
-
const folderRaw =
|
|
608
|
+
const folderRaw = QueryBuilder.selectOne(sql, [folderRef]);
|
|
776
609
|
if (!folderRaw) {
|
|
777
610
|
return void 0;
|
|
778
611
|
}
|
|
@@ -784,9 +617,9 @@ var FoldersModel = class {
|
|
|
784
617
|
/**
|
|
785
618
|
* Search folders by folderName (case-insensitive substring match)
|
|
786
619
|
*/
|
|
787
|
-
static
|
|
620
|
+
static searchByName(searchTerm, limit, skip) {
|
|
788
621
|
const sql = QueryBuilder.paginate(
|
|
789
|
-
`SELECT * FROM ${this.TABLE}
|
|
622
|
+
`SELECT * FROM ${this.TABLE}
|
|
790
623
|
WHERE isDeleted = 0 AND LOWER(folderName) LIKE LOWER(?)`,
|
|
791
624
|
{
|
|
792
625
|
limit,
|
|
@@ -795,7 +628,7 @@ var FoldersModel = class {
|
|
|
795
628
|
orderDirection: "DESC"
|
|
796
629
|
}
|
|
797
630
|
);
|
|
798
|
-
const foldersRaw =
|
|
631
|
+
const foldersRaw = QueryBuilder.select(sql, [`%${searchTerm}%`]);
|
|
799
632
|
return foldersRaw.map((folderRaw) => ({
|
|
800
633
|
...folderRaw,
|
|
801
634
|
isDeleted: Boolean(folderRaw.isDeleted)
|
|
@@ -804,15 +637,15 @@ var FoldersModel = class {
|
|
|
804
637
|
/**
|
|
805
638
|
* Create a new folder
|
|
806
639
|
*/
|
|
807
|
-
static
|
|
640
|
+
static create(input) {
|
|
808
641
|
const _id = input._id || `folder_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`;
|
|
809
642
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
810
643
|
const sql = `INSERT INTO ${this.TABLE} (
|
|
811
644
|
_id, onchainFileId, folderId, folderRef, folderName, portalAddress, metadataIPFSHash,
|
|
812
|
-
contentIPFSHash, isDeleted, lastTransactionHash, lastTransactionBlockNumber,
|
|
645
|
+
contentIPFSHash, isDeleted, lastTransactionHash, lastTransactionBlockNumber,
|
|
813
646
|
lastTransactionBlockTimestamp, created_at, updated_at
|
|
814
647
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`;
|
|
815
|
-
|
|
648
|
+
QueryBuilder.execute(sql, [
|
|
816
649
|
_id,
|
|
817
650
|
input.onchainFileId,
|
|
818
651
|
input.folderId,
|
|
@@ -830,7 +663,7 @@ var FoldersModel = class {
|
|
|
830
663
|
now
|
|
831
664
|
]);
|
|
832
665
|
const selectSql = `SELECT * FROM ${this.TABLE} WHERE folderRef = ? AND folderId = ? AND isDeleted = 0`;
|
|
833
|
-
const folderRaw =
|
|
666
|
+
const folderRaw = QueryBuilder.selectOne(selectSql, [input.folderRef, input.folderId]);
|
|
834
667
|
if (!folderRaw) {
|
|
835
668
|
throw new Error("Failed to create folder");
|
|
836
669
|
}
|
|
@@ -862,16 +695,16 @@ function onNewEvent(callback) {
|
|
|
862
695
|
var RETRY_DELAYS_MS = [5e3, 3e4, 12e4];
|
|
863
696
|
var EventsModel = class {
|
|
864
697
|
static TABLE = "events";
|
|
865
|
-
static
|
|
698
|
+
static create(input) {
|
|
866
699
|
const _id = uuidv74();
|
|
867
700
|
const timestamp = Date.now();
|
|
868
701
|
const status = "pending";
|
|
869
702
|
const sql = `
|
|
870
|
-
INSERT INTO ${this.TABLE}
|
|
871
|
-
(_id, type, timestamp, fileId, portalAddress, status, retryCount, lastError, lockedAt, nextRetryAt)
|
|
703
|
+
INSERT INTO ${this.TABLE}
|
|
704
|
+
(_id, type, timestamp, fileId, portalAddress, status, retryCount, lastError, lockedAt, nextRetryAt)
|
|
872
705
|
VALUES (?, ?, ?, ?, ?, ?, 0, NULL, NULL, NULL)
|
|
873
706
|
`;
|
|
874
|
-
|
|
707
|
+
QueryBuilder.execute(sql, [_id, input.type, timestamp, input.fileId, input.portalAddress, status]);
|
|
875
708
|
notifyNewEvent();
|
|
876
709
|
return {
|
|
877
710
|
_id,
|
|
@@ -886,22 +719,22 @@ var EventsModel = class {
|
|
|
886
719
|
nextRetryAt: null
|
|
887
720
|
};
|
|
888
721
|
}
|
|
889
|
-
static
|
|
722
|
+
static findById(_id) {
|
|
890
723
|
const sql = `SELECT * FROM ${this.TABLE} WHERE _id = ?`;
|
|
891
|
-
const row =
|
|
724
|
+
const row = QueryBuilder.selectOne(sql, [_id]);
|
|
892
725
|
return row ? this.parseEvent(row) : void 0;
|
|
893
726
|
}
|
|
894
|
-
static
|
|
727
|
+
static findNextPending() {
|
|
895
728
|
const sql = `
|
|
896
729
|
SELECT * FROM ${this.TABLE}
|
|
897
730
|
WHERE status = 'pending'
|
|
898
731
|
ORDER BY timestamp ASC
|
|
899
732
|
LIMIT 1
|
|
900
733
|
`;
|
|
901
|
-
const row =
|
|
734
|
+
const row = QueryBuilder.selectOne(sql, []);
|
|
902
735
|
return row ? this.parseEvent(row) : void 0;
|
|
903
736
|
}
|
|
904
|
-
static
|
|
737
|
+
static findNextEligible(lockedFileIds) {
|
|
905
738
|
const now = Date.now();
|
|
906
739
|
const exclusionClause = lockedFileIds.length > 0 ? `AND e1.fileId NOT IN (${lockedFileIds.map(() => "?").join(", ")})` : "";
|
|
907
740
|
const sql = `
|
|
@@ -919,29 +752,29 @@ var EventsModel = class {
|
|
|
919
752
|
LIMIT 1
|
|
920
753
|
`;
|
|
921
754
|
const params = [now, ...lockedFileIds];
|
|
922
|
-
const row =
|
|
755
|
+
const row = QueryBuilder.selectOne(sql, params);
|
|
923
756
|
return row ? this.parseEvent(row) : void 0;
|
|
924
757
|
}
|
|
925
|
-
static
|
|
758
|
+
static markProcessing(_id) {
|
|
926
759
|
const sql = `
|
|
927
760
|
UPDATE ${this.TABLE}
|
|
928
761
|
SET status = 'processing',
|
|
929
762
|
lockedAt = ?
|
|
930
763
|
WHERE _id = ?
|
|
931
764
|
`;
|
|
932
|
-
|
|
765
|
+
QueryBuilder.execute(sql, [Date.now(), _id]);
|
|
933
766
|
}
|
|
934
|
-
static
|
|
767
|
+
static markProcessed(_id) {
|
|
935
768
|
const sql = `
|
|
936
769
|
UPDATE ${this.TABLE}
|
|
937
770
|
SET status = 'processed',
|
|
938
771
|
lockedAt = NULL
|
|
939
772
|
WHERE _id = ?
|
|
940
773
|
`;
|
|
941
|
-
|
|
774
|
+
QueryBuilder.execute(sql, [_id]);
|
|
942
775
|
}
|
|
943
|
-
static
|
|
944
|
-
const event =
|
|
776
|
+
static scheduleRetry(_id, errorMsg) {
|
|
777
|
+
const event = this.findById(_id);
|
|
945
778
|
if (!event) return;
|
|
946
779
|
const delay = RETRY_DELAYS_MS[Math.min(event.retryCount, RETRY_DELAYS_MS.length - 1)];
|
|
947
780
|
const nextRetryAt = Date.now() + delay;
|
|
@@ -954,9 +787,9 @@ var EventsModel = class {
|
|
|
954
787
|
lockedAt = NULL
|
|
955
788
|
WHERE _id = ?
|
|
956
789
|
`;
|
|
957
|
-
|
|
790
|
+
QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);
|
|
958
791
|
}
|
|
959
|
-
static
|
|
792
|
+
static scheduleRetryAfter(_id, errorMsg, retryAfterMs) {
|
|
960
793
|
const nextRetryAt = Date.now() + retryAfterMs;
|
|
961
794
|
const sql = `
|
|
962
795
|
UPDATE ${this.TABLE}
|
|
@@ -966,9 +799,9 @@ var EventsModel = class {
|
|
|
966
799
|
lockedAt = NULL
|
|
967
800
|
WHERE _id = ?
|
|
968
801
|
`;
|
|
969
|
-
|
|
802
|
+
QueryBuilder.execute(sql, [errorMsg, nextRetryAt, _id]);
|
|
970
803
|
}
|
|
971
|
-
static
|
|
804
|
+
static markFailed(_id, errorMsg) {
|
|
972
805
|
const sql = `
|
|
973
806
|
UPDATE ${this.TABLE}
|
|
974
807
|
SET status = 'failed',
|
|
@@ -976,9 +809,9 @@ var EventsModel = class {
|
|
|
976
809
|
lockedAt = NULL
|
|
977
810
|
WHERE _id = ?
|
|
978
811
|
`;
|
|
979
|
-
|
|
812
|
+
QueryBuilder.execute(sql, [errorMsg, _id]);
|
|
980
813
|
}
|
|
981
|
-
static
|
|
814
|
+
static listFailed(portalAddress) {
|
|
982
815
|
const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
|
|
983
816
|
const sql = `
|
|
984
817
|
SELECT * FROM ${this.TABLE}
|
|
@@ -987,10 +820,10 @@ var EventsModel = class {
|
|
|
987
820
|
ORDER BY timestamp ASC
|
|
988
821
|
`;
|
|
989
822
|
const params = portalAddress != null ? [portalAddress] : [];
|
|
990
|
-
const rows =
|
|
823
|
+
const rows = QueryBuilder.select(sql, params);
|
|
991
824
|
return rows.map((row) => this.parseEvent(row));
|
|
992
825
|
}
|
|
993
|
-
static
|
|
826
|
+
static resetFailedToPending(_id, portalAddress) {
|
|
994
827
|
const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
|
|
995
828
|
const sql = `
|
|
996
829
|
UPDATE ${this.TABLE}
|
|
@@ -1004,13 +837,13 @@ var EventsModel = class {
|
|
|
1004
837
|
${portalClause}
|
|
1005
838
|
`;
|
|
1006
839
|
const params = portalAddress != null ? [_id, portalAddress] : [_id];
|
|
1007
|
-
const result =
|
|
840
|
+
const result = QueryBuilder.execute(sql, params);
|
|
1008
841
|
if (result.changes > 0) {
|
|
1009
842
|
notifyNewEvent();
|
|
1010
843
|
}
|
|
1011
844
|
return result.changes > 0;
|
|
1012
845
|
}
|
|
1013
|
-
static
|
|
846
|
+
static resetAllFailedToPending(portalAddress) {
|
|
1014
847
|
const portalClause = portalAddress != null ? "AND portalAddress = ?" : "";
|
|
1015
848
|
const sql = `
|
|
1016
849
|
UPDATE ${this.TABLE}
|
|
@@ -1023,13 +856,13 @@ var EventsModel = class {
|
|
|
1023
856
|
${portalClause}
|
|
1024
857
|
`;
|
|
1025
858
|
const params = portalAddress != null ? [portalAddress] : [];
|
|
1026
|
-
const result =
|
|
859
|
+
const result = QueryBuilder.execute(sql, params);
|
|
1027
860
|
if (result.changes > 0) {
|
|
1028
861
|
notifyNewEvent();
|
|
1029
862
|
}
|
|
1030
863
|
return result.changes;
|
|
1031
864
|
}
|
|
1032
|
-
static
|
|
865
|
+
static resetStaleEvents(staleThreshold) {
|
|
1033
866
|
const sql = `
|
|
1034
867
|
UPDATE ${this.TABLE}
|
|
1035
868
|
SET status = 'pending',
|
|
@@ -1040,16 +873,16 @@ var EventsModel = class {
|
|
|
1040
873
|
AND lockedAt IS NOT NULL
|
|
1041
874
|
AND lockedAt < ?
|
|
1042
875
|
`;
|
|
1043
|
-
const result =
|
|
876
|
+
const result = QueryBuilder.execute(sql, [staleThreshold]);
|
|
1044
877
|
return result.changes;
|
|
1045
878
|
}
|
|
1046
|
-
static
|
|
879
|
+
static setEventPendingOp(_id, userOpHash, payload) {
|
|
1047
880
|
const sql = `UPDATE ${this.TABLE} SET userOpHash = ?, pendingPayload = ? WHERE _id = ?`;
|
|
1048
|
-
|
|
881
|
+
QueryBuilder.execute(sql, [userOpHash, JSON.stringify(payload), _id]);
|
|
1049
882
|
}
|
|
1050
|
-
static
|
|
883
|
+
static clearEventPendingOp(_id) {
|
|
1051
884
|
const sql = `UPDATE ${this.TABLE} SET userOpHash = NULL, pendingPayload = NULL WHERE _id = ?`;
|
|
1052
|
-
|
|
885
|
+
QueryBuilder.execute(sql, [_id]);
|
|
1053
886
|
}
|
|
1054
887
|
static parseEvent(row) {
|
|
1055
888
|
return {
|
|
@@ -2637,12 +2470,12 @@ var FileManager = class {
|
|
|
2637
2470
|
};
|
|
2638
2471
|
|
|
2639
2472
|
// src/domain/portal/publish.ts
|
|
2640
|
-
|
|
2641
|
-
const file2 =
|
|
2473
|
+
function getPortalData(fileId) {
|
|
2474
|
+
const file2 = FilesModel.findByIdIncludingDeleted(fileId);
|
|
2642
2475
|
if (!file2) {
|
|
2643
2476
|
throw new Error(`File with _id ${fileId} not found`);
|
|
2644
2477
|
}
|
|
2645
|
-
const portalDetails =
|
|
2478
|
+
const portalDetails = PortalsModel.findByPortalAddress(file2.portalAddress);
|
|
2646
2479
|
if (!portalDetails) {
|
|
2647
2480
|
throw new Error(`Portal with address ${file2.portalAddress} not found`);
|
|
2648
2481
|
}
|
|
@@ -2682,7 +2515,7 @@ var executeOperation = async (fileManager, file2, operation) => {
|
|
|
2682
2515
|
};
|
|
2683
2516
|
var handleExistingFileOp = async (fileId, operation) => {
|
|
2684
2517
|
try {
|
|
2685
|
-
const { file: file2, portalDetails, apiKey } =
|
|
2518
|
+
const { file: file2, portalDetails, apiKey } = getPortalData(fileId);
|
|
2686
2519
|
const apiKeySeed = toUint8Array3(apiKey);
|
|
2687
2520
|
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2688
2521
|
const fileManager = await createFileManager(
|
|
@@ -2698,7 +2531,7 @@ var handleExistingFileOp = async (fileId, operation) => {
|
|
|
2698
2531
|
}
|
|
2699
2532
|
};
|
|
2700
2533
|
var handleNewFileOp = async (fileId) => {
|
|
2701
|
-
const { file: file2, portalDetails, apiKey } =
|
|
2534
|
+
const { file: file2, portalDetails, apiKey } = getPortalData(fileId);
|
|
2702
2535
|
const apiKeySeed = toUint8Array3(apiKey);
|
|
2703
2536
|
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2704
2537
|
const fileManager = await createFileManager(
|
|
@@ -2710,7 +2543,7 @@ var handleNewFileOp = async (fileId) => {
|
|
|
2710
2543
|
return fileManager.submitAddFileTrx(file2);
|
|
2711
2544
|
};
|
|
2712
2545
|
var getProxyAuthParams = async (fileId) => {
|
|
2713
|
-
const { portalDetails, apiKey } =
|
|
2546
|
+
const { portalDetails, apiKey } = getPortalData(fileId);
|
|
2714
2547
|
const apiKeySeed = toUint8Array3(apiKey);
|
|
2715
2548
|
const { privateAccountKey, ucanSecret } = deriveCollaboratorKeys(apiKeySeed);
|
|
2716
2549
|
const fileManager = await createFileManager(
|
|
@@ -2723,7 +2556,7 @@ var getProxyAuthParams = async (fileId) => {
|
|
|
2723
2556
|
};
|
|
2724
2557
|
|
|
2725
2558
|
// src/domain/portal/savePortal.ts
|
|
2726
|
-
|
|
2559
|
+
function savePortal(input) {
|
|
2727
2560
|
if (!input.portalAddress || !input.portalSeed || !input.ownerAddress) {
|
|
2728
2561
|
throw new Error("portalAddress, portalSeed, and ownerAddress are required");
|
|
2729
2562
|
}
|
|
@@ -2731,11 +2564,11 @@ async function savePortal(input) {
|
|
|
2731
2564
|
}
|
|
2732
2565
|
|
|
2733
2566
|
// src/domain/portal/saveApiKey.ts
|
|
2734
|
-
|
|
2567
|
+
function addApiKey(input) {
|
|
2735
2568
|
if (!input.apiKeySeed || !input.name || !input.collaboratorAddress || !input.portalAddress) {
|
|
2736
2569
|
throw new Error("apiKeySeed, name, collaboratorAddress, and portalAddress are required");
|
|
2737
2570
|
}
|
|
2738
|
-
const portal =
|
|
2571
|
+
const portal = PortalsModel.findByPortalAddress(input.portalAddress);
|
|
2739
2572
|
if (!portal) {
|
|
2740
2573
|
throw new Error(`Portal with address ${input.portalAddress} does not exist`);
|
|
2741
2574
|
}
|
|
@@ -2799,7 +2632,7 @@ var processEvent = async (event) => {
|
|
|
2799
2632
|
return { success: false, error: errorMsg };
|
|
2800
2633
|
}
|
|
2801
2634
|
};
|
|
2802
|
-
var onTransactionSuccess =
|
|
2635
|
+
var onTransactionSuccess = (fileId, file2, onChainFileId, pending) => {
|
|
2803
2636
|
const frontendUrl = getRuntimeConfig().FRONTEND_URL;
|
|
2804
2637
|
const payload = {
|
|
2805
2638
|
onchainVersion: file2.localVersion,
|
|
@@ -2810,14 +2643,14 @@ var onTransactionSuccess = async (fileId, file2, onChainFileId, pending) => {
|
|
|
2810
2643
|
metadata: pending.metadata,
|
|
2811
2644
|
link: `${frontendUrl}/${file2.portalAddress}/${onChainFileId}#key=${pending.linkKey}`
|
|
2812
2645
|
};
|
|
2813
|
-
const updatedFile =
|
|
2646
|
+
const updatedFile = FilesModel.update(fileId, payload, file2.portalAddress);
|
|
2814
2647
|
if (updatedFile.localVersion === updatedFile.onchainVersion) {
|
|
2815
|
-
|
|
2648
|
+
FilesModel.update(fileId, { syncStatus: "synced" }, file2.portalAddress);
|
|
2816
2649
|
}
|
|
2817
2650
|
};
|
|
2818
2651
|
var processCreateEvent = async (event) => {
|
|
2819
2652
|
const { fileId } = event;
|
|
2820
|
-
const file2 =
|
|
2653
|
+
const file2 = FilesModel.findByIdIncludingDeleted(fileId);
|
|
2821
2654
|
if (!file2) {
|
|
2822
2655
|
throw new Error(`File ${fileId} not found`);
|
|
2823
2656
|
}
|
|
@@ -2836,18 +2669,18 @@ var processCreateEvent = async (event) => {
|
|
|
2836
2669
|
timeout
|
|
2837
2670
|
);
|
|
2838
2671
|
if (!receipt2.success) {
|
|
2839
|
-
|
|
2672
|
+
EventsModel.clearEventPendingOp(event._id);
|
|
2840
2673
|
throw new Error(`User operation failed: ${receipt2.reason}`);
|
|
2841
2674
|
}
|
|
2842
2675
|
const onChainFileId2 = parseFileEventLog(receipt2.logs, "AddedFile", ADDED_FILE_EVENT);
|
|
2843
2676
|
const pending = JSON.parse(event.pendingPayload);
|
|
2844
|
-
|
|
2845
|
-
|
|
2677
|
+
onTransactionSuccess(fileId, file2, onChainFileId2, pending);
|
|
2678
|
+
EventsModel.clearEventPendingOp(event._id);
|
|
2846
2679
|
logger.info(`File ${file2.ddocId} created and published successfully (resumed from pending op)`);
|
|
2847
2680
|
return;
|
|
2848
2681
|
}
|
|
2849
2682
|
const result = await handleNewFileOp(fileId);
|
|
2850
|
-
|
|
2683
|
+
EventsModel.setEventPendingOp(event._id, result.userOpHash, {
|
|
2851
2684
|
linkKey: result.linkKey,
|
|
2852
2685
|
linkKeyNonce: result.linkKeyNonce,
|
|
2853
2686
|
commentKey: result.commentKey,
|
|
@@ -2861,22 +2694,22 @@ var processCreateEvent = async (event) => {
|
|
|
2861
2694
|
timeout
|
|
2862
2695
|
);
|
|
2863
2696
|
if (!receipt.success) {
|
|
2864
|
-
|
|
2697
|
+
EventsModel.clearEventPendingOp(event._id);
|
|
2865
2698
|
throw new Error(`User operation failed: ${receipt.reason}`);
|
|
2866
2699
|
}
|
|
2867
2700
|
const onChainFileId = parseFileEventLog(receipt.logs, "AddedFile", ADDED_FILE_EVENT);
|
|
2868
|
-
|
|
2701
|
+
onTransactionSuccess(fileId, file2, onChainFileId, {
|
|
2869
2702
|
linkKey: result.linkKey,
|
|
2870
2703
|
linkKeyNonce: result.linkKeyNonce,
|
|
2871
2704
|
commentKey: result.commentKey,
|
|
2872
2705
|
metadata: result.metadata
|
|
2873
2706
|
});
|
|
2874
|
-
|
|
2707
|
+
EventsModel.clearEventPendingOp(event._id);
|
|
2875
2708
|
logger.info(`File ${file2.ddocId} created and published successfully`);
|
|
2876
2709
|
};
|
|
2877
2710
|
var processUpdateEvent = async (event) => {
|
|
2878
2711
|
const { fileId } = event;
|
|
2879
|
-
const file2 =
|
|
2712
|
+
const file2 = FilesModel.findByIdExcludingDeleted(fileId);
|
|
2880
2713
|
if (!file2) {
|
|
2881
2714
|
return;
|
|
2882
2715
|
}
|
|
@@ -2891,15 +2724,15 @@ var processUpdateEvent = async (event) => {
|
|
|
2891
2724
|
onchainVersion: file2.localVersion,
|
|
2892
2725
|
metadata: result.metadata
|
|
2893
2726
|
};
|
|
2894
|
-
const updatedFile =
|
|
2727
|
+
const updatedFile = FilesModel.update(fileId, payload, file2.portalAddress);
|
|
2895
2728
|
if (updatedFile.localVersion === updatedFile.onchainVersion) {
|
|
2896
|
-
|
|
2729
|
+
FilesModel.update(fileId, { syncStatus: "synced" }, file2.portalAddress);
|
|
2897
2730
|
}
|
|
2898
2731
|
logger.info(`File ${file2.ddocId} updated and published successfully`);
|
|
2899
2732
|
};
|
|
2900
2733
|
var processDeleteEvent = async (event) => {
|
|
2901
2734
|
const { fileId } = event;
|
|
2902
|
-
const file2 =
|
|
2735
|
+
const file2 = FilesModel.findByIdIncludingDeleted(fileId);
|
|
2903
2736
|
if (!file2) {
|
|
2904
2737
|
return;
|
|
2905
2738
|
}
|
|
@@ -2920,7 +2753,7 @@ var processDeleteEvent = async (event) => {
|
|
|
2920
2753
|
payload.metadata = result.metadata;
|
|
2921
2754
|
payload.isDeleted = 1;
|
|
2922
2755
|
}
|
|
2923
|
-
|
|
2756
|
+
FilesModel.update(fileId, payload, file2.portalAddress);
|
|
2924
2757
|
logger.info(`File ${fileId} delete event processed (syncStatus set to synced)`);
|
|
2925
2758
|
};
|
|
2926
2759
|
|
|
@@ -2946,11 +2779,10 @@ var FileEventsWorker = class {
|
|
|
2946
2779
|
return;
|
|
2947
2780
|
}
|
|
2948
2781
|
this.isRunning = true;
|
|
2949
|
-
this.recoverStaleEvents()
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
});
|
|
2782
|
+
const staleCount = this.recoverStaleEvents();
|
|
2783
|
+
if (staleCount > 0) {
|
|
2784
|
+
logger.info(`Recovered ${staleCount} stale event(s)`);
|
|
2785
|
+
}
|
|
2954
2786
|
this.signalCleanup = onNewEvent(() => {
|
|
2955
2787
|
this.pendingSignal = true;
|
|
2956
2788
|
this.wakeUp();
|
|
@@ -2979,10 +2811,10 @@ var FileEventsWorker = class {
|
|
|
2979
2811
|
let foundAny = false;
|
|
2980
2812
|
while (this.activeProcessors.size < this.concurrency && this.isRunning) {
|
|
2981
2813
|
const lockedFileIds = Array.from(this.activeProcessors.keys());
|
|
2982
|
-
const event =
|
|
2814
|
+
const event = EventsModel.findNextEligible(lockedFileIds);
|
|
2983
2815
|
if (!event) break;
|
|
2984
2816
|
foundAny = true;
|
|
2985
|
-
|
|
2817
|
+
EventsModel.markProcessing(event._id);
|
|
2986
2818
|
const processor = this.processEventWrapper(event);
|
|
2987
2819
|
this.activeProcessors.set(event.fileId, processor);
|
|
2988
2820
|
}
|
|
@@ -2993,33 +2825,33 @@ var FileEventsWorker = class {
|
|
|
2993
2825
|
try {
|
|
2994
2826
|
const result = await processEvent(event);
|
|
2995
2827
|
if (result.success) {
|
|
2996
|
-
|
|
2828
|
+
EventsModel.markProcessed(event._id);
|
|
2997
2829
|
} else {
|
|
2998
|
-
|
|
2830
|
+
this.handleFailure(event, result.error);
|
|
2999
2831
|
}
|
|
3000
2832
|
} catch (err) {
|
|
3001
|
-
|
|
2833
|
+
this.handleFailure(event, err);
|
|
3002
2834
|
} finally {
|
|
3003
2835
|
this.activeProcessors.delete(event.fileId);
|
|
3004
2836
|
}
|
|
3005
2837
|
}
|
|
3006
|
-
|
|
2838
|
+
handleFailure(event, error48) {
|
|
3007
2839
|
const errorMsg = error48 instanceof Error ? error48.message : String(error48);
|
|
3008
2840
|
if (error48 instanceof RateLimitError) {
|
|
3009
2841
|
const retryAfterMs = error48.retryAfterSeconds * 1e3;
|
|
3010
|
-
|
|
2842
|
+
EventsModel.scheduleRetryAfter(event._id, errorMsg, retryAfterMs);
|
|
3011
2843
|
logger.warn(`Event ${event._id} rate limited; retry after ${error48.retryAfterSeconds}s`);
|
|
3012
2844
|
return;
|
|
3013
2845
|
}
|
|
3014
2846
|
if (event.retryCount < MAX_RETRIES) {
|
|
3015
|
-
|
|
2847
|
+
EventsModel.scheduleRetry(event._id, errorMsg);
|
|
3016
2848
|
logger.warn(`Event ${event._id} failed (retry ${event.retryCount + 1}/${MAX_RETRIES}): ${errorMsg}`);
|
|
3017
2849
|
} else {
|
|
3018
|
-
|
|
2850
|
+
EventsModel.markFailed(event._id, errorMsg);
|
|
3019
2851
|
logger.error(`Event ${event._id} permanently failed after ${MAX_RETRIES} retries: ${errorMsg}`);
|
|
3020
2852
|
}
|
|
3021
2853
|
}
|
|
3022
|
-
|
|
2854
|
+
recoverStaleEvents() {
|
|
3023
2855
|
const staleThreshold = Date.now() - STALE_THRESHOLD_MS;
|
|
3024
2856
|
return EventsModel.resetStaleEvents(staleThreshold);
|
|
3025
2857
|
}
|
|
@@ -3108,8 +2940,8 @@ CREATE TABLE IF NOT EXISTS files (
|
|
|
3108
2940
|
localVersion INTEGER NOT NULL DEFAULT 1,
|
|
3109
2941
|
onchainVersion INTEGER NOT NULL DEFAULT 0,
|
|
3110
2942
|
syncStatus TEXT NOT NULL DEFAULT 'pending',
|
|
3111
|
-
createdAt
|
|
3112
|
-
updatedAt
|
|
2943
|
+
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
2944
|
+
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
3113
2945
|
isDeleted INTEGER NOT NULL DEFAULT 0,
|
|
3114
2946
|
portalAddress TEXT NOT NULL,
|
|
3115
2947
|
metadata TEXT DEFAULT '{}',
|
|
@@ -3129,8 +2961,8 @@ CREATE TABLE IF NOT EXISTS portals (
|
|
|
3129
2961
|
portalAddress TEXT NOT NULL UNIQUE,
|
|
3130
2962
|
portalSeed TEXT NOT NULL UNIQUE,
|
|
3131
2963
|
ownerAddress TEXT NOT NULL,
|
|
3132
|
-
createdAt
|
|
3133
|
-
updatedAt
|
|
2964
|
+
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
2965
|
+
updatedAt DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
3134
2966
|
);
|
|
3135
2967
|
|
|
3136
2968
|
CREATE TABLE IF NOT EXISTS api_keys (
|
|
@@ -3139,7 +2971,7 @@ CREATE TABLE IF NOT EXISTS api_keys (
|
|
|
3139
2971
|
name TEXT NOT NULL,
|
|
3140
2972
|
collaboratorAddress TEXT NOT NULL UNIQUE,
|
|
3141
2973
|
portalAddress TEXT NOT NULL,
|
|
3142
|
-
createdAt
|
|
2974
|
+
createdAt DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
3143
2975
|
isDeleted INTEGER NOT NULL DEFAULT 0
|
|
3144
2976
|
);
|
|
3145
2977
|
|
|
@@ -3175,16 +3007,16 @@ CREATE TABLE IF NOT EXISTS folders (
|
|
|
3175
3007
|
lastTransactionHash TEXT,
|
|
3176
3008
|
lastTransactionBlockNumber INTEGER NOT NULL,
|
|
3177
3009
|
lastTransactionBlockTimestamp INTEGER NOT NULL,
|
|
3178
|
-
created_at
|
|
3179
|
-
updated_at
|
|
3010
|
+
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
|
|
3011
|
+
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
|
3180
3012
|
);
|
|
3181
3013
|
CREATE INDEX IF NOT EXISTS idx_folders_folderRef_folderId ON folders(folderRef, folderId);
|
|
3182
3014
|
CREATE INDEX IF NOT EXISTS idx_folders_folderRef ON folders(folderRef);
|
|
3183
3015
|
CREATE INDEX IF NOT EXISTS idx_folders_created_at ON folders(created_at);
|
|
3184
3016
|
`;
|
|
3185
|
-
|
|
3186
|
-
const
|
|
3187
|
-
|
|
3017
|
+
function runMigrations() {
|
|
3018
|
+
const db = database_default();
|
|
3019
|
+
db.exec(STABLE_SCHEMA);
|
|
3188
3020
|
logger.debug("Database schema ready");
|
|
3189
3021
|
}
|
|
3190
3022
|
|
|
@@ -3222,16 +3054,16 @@ import { toUint8Array as toUint8Array5 } from "js-base64";
|
|
|
3222
3054
|
import { stringToBytes as stringToBytes2 } from "viem";
|
|
3223
3055
|
import { toAESKey as toAESKey2, aesDecrypt } from "@fileverse/crypto/webcrypto";
|
|
3224
3056
|
var SAVED_DATA_ENCRYPTION_KEY_INFO = "SAVED_DATA_ENCRYPTION_KEY";
|
|
3225
|
-
|
|
3057
|
+
function initializeWithData(data) {
|
|
3226
3058
|
const { keyMaterial, appMaterial } = data;
|
|
3227
|
-
|
|
3059
|
+
savePortal({
|
|
3228
3060
|
portalAddress: appMaterial.portalAddress,
|
|
3229
3061
|
portalSeed: appMaterial.portalSeed,
|
|
3230
3062
|
ownerAddress: appMaterial.ownerAddress
|
|
3231
3063
|
});
|
|
3232
|
-
const existingApiKey =
|
|
3064
|
+
const existingApiKey = ApiKeysModel.findByApiKey(keyMaterial.apiKeySeed);
|
|
3233
3065
|
if (!existingApiKey) {
|
|
3234
|
-
|
|
3066
|
+
addApiKey({
|
|
3235
3067
|
apiKeySeed: keyMaterial.apiKeySeed,
|
|
3236
3068
|
name: keyMaterial.name,
|
|
3237
3069
|
collaboratorAddress: keyMaterial.collaboratorAddress,
|
|
@@ -3264,7 +3096,7 @@ var initializeFromApiKey = async (apiKey) => {
|
|
|
3264
3096
|
logger.debug("API key data retrieved");
|
|
3265
3097
|
const keyMaterial = await decryptSavedData(apiKey, data.encryptedKeyMaterial);
|
|
3266
3098
|
const appMaterial = await decryptSavedData(apiKey, data.encryptedAppMaterial);
|
|
3267
|
-
const result =
|
|
3099
|
+
const result = initializeWithData({ keyMaterial, appMaterial, id: data.id });
|
|
3268
3100
|
logger.debug("Portal saved");
|
|
3269
3101
|
if (result.apiKeySaved) {
|
|
3270
3102
|
logger.debug("API key saved");
|
|
@@ -3289,10 +3121,10 @@ import { Router } from "express";
|
|
|
3289
3121
|
|
|
3290
3122
|
// src/domain/file/index.ts
|
|
3291
3123
|
import { generate } from "short-uuid";
|
|
3292
|
-
|
|
3124
|
+
function listFiles(params) {
|
|
3293
3125
|
const { limit, skip, portalAddress } = params;
|
|
3294
3126
|
const effectiveLimit = limit || DEFAULT_LIST_LIMIT;
|
|
3295
|
-
const result =
|
|
3127
|
+
const result = FilesModel.findAll(portalAddress, effectiveLimit, skip);
|
|
3296
3128
|
const processedFiles = result.files.map((file2) => ({
|
|
3297
3129
|
ddocId: file2.ddocId,
|
|
3298
3130
|
link: file2.link,
|
|
@@ -3313,11 +3145,11 @@ async function listFiles(params) {
|
|
|
3313
3145
|
hasNext: result.hasNext
|
|
3314
3146
|
};
|
|
3315
3147
|
}
|
|
3316
|
-
|
|
3148
|
+
function getFile(ddocId, portalAddress) {
|
|
3317
3149
|
if (!ddocId) {
|
|
3318
3150
|
throw new Error("ddocId is required");
|
|
3319
3151
|
}
|
|
3320
|
-
const file2 =
|
|
3152
|
+
const file2 = FilesModel.findByDDocId(ddocId, portalAddress);
|
|
3321
3153
|
if (!file2) {
|
|
3322
3154
|
return null;
|
|
3323
3155
|
}
|
|
@@ -3341,13 +3173,13 @@ var createFile = async (input) => {
|
|
|
3341
3173
|
throw new Error("title, content, and portalAddress are required");
|
|
3342
3174
|
}
|
|
3343
3175
|
const ddocId = generate();
|
|
3344
|
-
const file2 =
|
|
3176
|
+
const file2 = FilesModel.create({
|
|
3345
3177
|
title: input.title,
|
|
3346
3178
|
content: input.content,
|
|
3347
3179
|
ddocId,
|
|
3348
3180
|
portalAddress: input.portalAddress
|
|
3349
3181
|
});
|
|
3350
|
-
|
|
3182
|
+
EventsModel.create({ type: "create", fileId: file2._id, portalAddress: file2.portalAddress });
|
|
3351
3183
|
return file2;
|
|
3352
3184
|
};
|
|
3353
3185
|
var updateFile = async (ddocId, payload, portalAddress) => {
|
|
@@ -3357,7 +3189,7 @@ var updateFile = async (ddocId, payload, portalAddress) => {
|
|
|
3357
3189
|
if (!payload.title && !payload.content) {
|
|
3358
3190
|
throw new Error("At least one field is required: Either provide title, content, or both");
|
|
3359
3191
|
}
|
|
3360
|
-
const existingFile =
|
|
3192
|
+
const existingFile = FilesModel.findByDDocId(ddocId, portalAddress);
|
|
3361
3193
|
if (!existingFile) {
|
|
3362
3194
|
throw new Error(`File with ddocId ${ddocId} not found`);
|
|
3363
3195
|
}
|
|
@@ -3367,8 +3199,8 @@ var updateFile = async (ddocId, payload, portalAddress) => {
|
|
|
3367
3199
|
syncStatus: "pending"
|
|
3368
3200
|
// since the update is done in local db, it's not on the chain yet. hence pending
|
|
3369
3201
|
};
|
|
3370
|
-
const updatedFile =
|
|
3371
|
-
|
|
3202
|
+
const updatedFile = FilesModel.update(existingFile._id, updatePayload, portalAddress);
|
|
3203
|
+
EventsModel.create({ type: "update", fileId: updatedFile._id, portalAddress: updatedFile.portalAddress });
|
|
3372
3204
|
return {
|
|
3373
3205
|
ddocId: updatedFile.ddocId,
|
|
3374
3206
|
link: updatedFile.link,
|
|
@@ -3388,12 +3220,12 @@ var deleteFile = async (ddocId, portalAddress) => {
|
|
|
3388
3220
|
if (!ddocId) {
|
|
3389
3221
|
throw new Error("ddocId is required");
|
|
3390
3222
|
}
|
|
3391
|
-
const existingFile =
|
|
3223
|
+
const existingFile = FilesModel.findByDDocId(ddocId, portalAddress);
|
|
3392
3224
|
if (!existingFile) {
|
|
3393
3225
|
throw new Error(`File with ddocId ${ddocId} not found`);
|
|
3394
3226
|
}
|
|
3395
|
-
const deletedFile =
|
|
3396
|
-
|
|
3227
|
+
const deletedFile = FilesModel.softDelete(existingFile._id);
|
|
3228
|
+
EventsModel.create({ type: "delete", fileId: deletedFile._id, portalAddress: deletedFile.portalAddress });
|
|
3397
3229
|
return deletedFile;
|
|
3398
3230
|
};
|
|
3399
3231
|
|
|
@@ -3448,7 +3280,7 @@ var listHandler = async (req, res) => {
|
|
|
3448
3280
|
if (!apiKey) {
|
|
3449
3281
|
throw new Error("API key is required");
|
|
3450
3282
|
}
|
|
3451
|
-
const apiKeyInfo =
|
|
3283
|
+
const apiKeyInfo = ApiKeysModel.findByApiKey(apiKey);
|
|
3452
3284
|
if (!apiKeyInfo) {
|
|
3453
3285
|
throw new Error("Invalid API key");
|
|
3454
3286
|
}
|
|
@@ -3456,7 +3288,7 @@ var listHandler = async (req, res) => {
|
|
|
3456
3288
|
if (!portalAddress) {
|
|
3457
3289
|
throw new Error("Portal address is required");
|
|
3458
3290
|
}
|
|
3459
|
-
const result =
|
|
3291
|
+
const result = listFiles({ limit, skip, portalAddress });
|
|
3460
3292
|
res.json({
|
|
3461
3293
|
ddocs: result.ddocs,
|
|
3462
3294
|
total: result.total,
|
|
@@ -3469,7 +3301,7 @@ var getHandler = async (req, res) => {
|
|
|
3469
3301
|
if (!apiKey) {
|
|
3470
3302
|
throw new Error("API key is required");
|
|
3471
3303
|
}
|
|
3472
|
-
const apiKeyInfo =
|
|
3304
|
+
const apiKeyInfo = ApiKeysModel.findByApiKey(apiKey);
|
|
3473
3305
|
if (!apiKeyInfo) {
|
|
3474
3306
|
throw new Error("Invalid API key");
|
|
3475
3307
|
}
|
|
@@ -3481,7 +3313,7 @@ var getHandler = async (req, res) => {
|
|
|
3481
3313
|
return res.status(400).json({ error: "Missing required header: x-portal-address is required" });
|
|
3482
3314
|
}
|
|
3483
3315
|
try {
|
|
3484
|
-
const file2 =
|
|
3316
|
+
const file2 = getFile(ddocId, portalAddress);
|
|
3485
3317
|
if (!file2) {
|
|
3486
3318
|
return res.status(404).json({ error: "DDoc not found" });
|
|
3487
3319
|
}
|
|
@@ -3507,7 +3339,7 @@ var createHandler = async (req, res) => {
|
|
|
3507
3339
|
error: "Missing content: Either provide a file upload or fileContent text field"
|
|
3508
3340
|
});
|
|
3509
3341
|
}
|
|
3510
|
-
const apiKeyInfo =
|
|
3342
|
+
const apiKeyInfo = ApiKeysModel.findByApiKey(apiKey);
|
|
3511
3343
|
if (!apiKeyInfo) {
|
|
3512
3344
|
return res.status(400).json({ error: "Invalid API key" });
|
|
3513
3345
|
}
|
|
@@ -3552,7 +3384,7 @@ var updateHandler = async (req, res) => {
|
|
|
3552
3384
|
title: clientPayload.title,
|
|
3553
3385
|
content: clientPayload.content
|
|
3554
3386
|
};
|
|
3555
|
-
const apiKeyInfo =
|
|
3387
|
+
const apiKeyInfo = ApiKeysModel.findByApiKey(apiKeySeed);
|
|
3556
3388
|
if (!apiKeyInfo) {
|
|
3557
3389
|
return res.status(400).json({ error: "Invalid API key" });
|
|
3558
3390
|
}
|
|
@@ -3573,7 +3405,7 @@ var deleteHandler = async (req, res) => {
|
|
|
3573
3405
|
if (!apiKey) {
|
|
3574
3406
|
return res.status(400).json({ error: "API key is required" });
|
|
3575
3407
|
}
|
|
3576
|
-
const apiKeyInfo =
|
|
3408
|
+
const apiKeyInfo = ApiKeysModel.findByApiKey(apiKey);
|
|
3577
3409
|
if (!apiKeyInfo) {
|
|
3578
3410
|
return res.status(400).json({ error: "Invalid API key" });
|
|
3579
3411
|
}
|
|
@@ -3614,9 +3446,9 @@ var ddocs_default = router;
|
|
|
3614
3446
|
import { Router as Router2 } from "express";
|
|
3615
3447
|
|
|
3616
3448
|
// src/domain/folder/listFolders.ts
|
|
3617
|
-
|
|
3449
|
+
function listFolders(params) {
|
|
3618
3450
|
const { limit, skip } = params;
|
|
3619
|
-
const result =
|
|
3451
|
+
const result = FoldersModel.findAll(limit, skip);
|
|
3620
3452
|
return {
|
|
3621
3453
|
folders: result.folders,
|
|
3622
3454
|
total: result.total,
|
|
@@ -3625,11 +3457,11 @@ async function listFolders(params) {
|
|
|
3625
3457
|
}
|
|
3626
3458
|
|
|
3627
3459
|
// src/domain/folder/getFolder.ts
|
|
3628
|
-
|
|
3460
|
+
function getFolder(folderRef, folderId) {
|
|
3629
3461
|
if (!folderRef || !folderId) {
|
|
3630
3462
|
throw new Error("folderRef and folderId are required");
|
|
3631
3463
|
}
|
|
3632
|
-
const folder =
|
|
3464
|
+
const folder = FoldersModel.findByFolderRefAndId(folderRef, folderId);
|
|
3633
3465
|
if (!folder) {
|
|
3634
3466
|
return null;
|
|
3635
3467
|
}
|
|
@@ -3637,7 +3469,7 @@ async function getFolder(folderRef, folderId) {
|
|
|
3637
3469
|
}
|
|
3638
3470
|
|
|
3639
3471
|
// src/domain/folder/createFolder.ts
|
|
3640
|
-
|
|
3472
|
+
function createFolder(input) {
|
|
3641
3473
|
if (!input.folderId) {
|
|
3642
3474
|
throw new Error("folderId is required");
|
|
3643
3475
|
}
|
|
@@ -3650,7 +3482,7 @@ async function createFolder(input) {
|
|
|
3650
3482
|
if (!input.portalAddress) {
|
|
3651
3483
|
throw new Error("portalAddress is required");
|
|
3652
3484
|
}
|
|
3653
|
-
const existing =
|
|
3485
|
+
const existing = FoldersModel.findByFolderRefAndId(input.folderRef, input.folderId);
|
|
3654
3486
|
if (existing) {
|
|
3655
3487
|
throw new Error("Folder with this folderRef and folderId already exists");
|
|
3656
3488
|
}
|
|
@@ -3661,7 +3493,7 @@ async function createFolder(input) {
|
|
|
3661
3493
|
var listHandler2 = async (req, res) => {
|
|
3662
3494
|
const limit = req.query.limit ? parseInt(req.query.limit, 10) : void 0;
|
|
3663
3495
|
const skip = req.query.skip ? parseInt(req.query.skip, 10) : void 0;
|
|
3664
|
-
const result =
|
|
3496
|
+
const result = listFolders({ limit, skip });
|
|
3665
3497
|
res.json({
|
|
3666
3498
|
folders: result.folders,
|
|
3667
3499
|
total: result.total,
|
|
@@ -3677,7 +3509,7 @@ var getHandler2 = async (req, res) => {
|
|
|
3677
3509
|
return res.status(400).json({ error: "folderRef and folderId are required" });
|
|
3678
3510
|
}
|
|
3679
3511
|
try {
|
|
3680
|
-
const folder =
|
|
3512
|
+
const folder = getFolder(folderRef, folderId);
|
|
3681
3513
|
if (!folder) {
|
|
3682
3514
|
return res.status(404).json({ error: "Folder not found" });
|
|
3683
3515
|
}
|
|
@@ -3707,7 +3539,7 @@ var createHandler2 = async (req, res) => {
|
|
|
3707
3539
|
error: "Missing required fields: lastTransactionBlockNumber and lastTransactionBlockTimestamp are required"
|
|
3708
3540
|
});
|
|
3709
3541
|
}
|
|
3710
|
-
const folder =
|
|
3542
|
+
const folder = createFolder(input);
|
|
3711
3543
|
res.status(201).json(folder);
|
|
3712
3544
|
} catch (error48) {
|
|
3713
3545
|
if (error48.message.includes("already exists")) {
|
|
@@ -3729,18 +3561,18 @@ var folders_default = router2;
|
|
|
3729
3561
|
import { Router as Router3 } from "express";
|
|
3730
3562
|
|
|
3731
3563
|
// src/domain/search/searchNodes.ts
|
|
3732
|
-
|
|
3564
|
+
function searchNodes(params) {
|
|
3733
3565
|
const { query, limit, skip, portalAddress } = params;
|
|
3734
3566
|
if (!query || query.trim().length === 0) {
|
|
3735
3567
|
return { nodes: [], total: 0, hasNext: false };
|
|
3736
3568
|
}
|
|
3737
|
-
const files =
|
|
3569
|
+
const files = FilesModel.searchByTitle(query, portalAddress, limit, skip);
|
|
3738
3570
|
const normalizedNodes = files.map((file2) => ({
|
|
3739
3571
|
type: "file",
|
|
3740
3572
|
...file2
|
|
3741
3573
|
}));
|
|
3742
3574
|
const countSql = `SELECT COUNT(*) as count FROM files WHERE LOWER(title) LIKE LOWER(?) AND isDeleted = 0 AND portalAddress = ?`;
|
|
3743
|
-
const totalResult =
|
|
3575
|
+
const totalResult = QueryBuilder.selectOne(countSql, [`%${query}%`, portalAddress]);
|
|
3744
3576
|
const total = totalResult?.count || 0;
|
|
3745
3577
|
const hasNext = skip !== void 0 && limit !== void 0 ? skip + limit < total : false;
|
|
3746
3578
|
return {
|
|
@@ -3758,8 +3590,7 @@ var searchHandler = async (req, res) => {
|
|
|
3758
3590
|
const runtimConfig = getRuntimeConfig();
|
|
3759
3591
|
const apiKey = runtimConfig.API_KEY;
|
|
3760
3592
|
if (!apiKey) throw new Error("API key is required");
|
|
3761
|
-
const
|
|
3762
|
-
const portalAddress = apiKeyInfo?.portalAddress;
|
|
3593
|
+
const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress;
|
|
3763
3594
|
if (!portalAddress) throw new Error("Portal address is required");
|
|
3764
3595
|
if (!query) {
|
|
3765
3596
|
return res.status(400).json({ error: 'Query parameter "q" is required' });
|
|
@@ -3767,7 +3598,7 @@ var searchHandler = async (req, res) => {
|
|
|
3767
3598
|
if (!portalAddress) {
|
|
3768
3599
|
return res.status(400).json({ error: "Missing required header: x-portal-address is required" });
|
|
3769
3600
|
}
|
|
3770
|
-
const result =
|
|
3601
|
+
const result = searchNodes({ query, limit, skip, portalAddress });
|
|
3771
3602
|
res.json({
|
|
3772
3603
|
nodes: result.nodes,
|
|
3773
3604
|
total: result.total,
|
|
@@ -3787,12 +3618,11 @@ import { Router as Router4 } from "express";
|
|
|
3787
3618
|
// src/interface/api/router/events/listFailed.ts
|
|
3788
3619
|
var listFailedHandler = async (req, res) => {
|
|
3789
3620
|
const apiKey = req.query.apiKey;
|
|
3790
|
-
const
|
|
3791
|
-
const portalAddress = apiKeyInfo?.portalAddress;
|
|
3621
|
+
const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress;
|
|
3792
3622
|
if (!portalAddress) {
|
|
3793
3623
|
return res.status(401).json({ error: "Invalid or missing API key" });
|
|
3794
3624
|
}
|
|
3795
|
-
const events =
|
|
3625
|
+
const events = EventsModel.listFailed(portalAddress);
|
|
3796
3626
|
res.json(events);
|
|
3797
3627
|
};
|
|
3798
3628
|
var listFailed_default = [listFailedHandler];
|
|
@@ -3800,13 +3630,12 @@ var listFailed_default = [listFailedHandler];
|
|
|
3800
3630
|
// src/interface/api/router/events/retryOne.ts
|
|
3801
3631
|
var retryOneHandler = async (req, res) => {
|
|
3802
3632
|
const apiKey = req.query.apiKey;
|
|
3803
|
-
const
|
|
3804
|
-
const portalAddress = apiKeyInfo?.portalAddress;
|
|
3633
|
+
const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress;
|
|
3805
3634
|
if (!portalAddress) {
|
|
3806
3635
|
return res.status(401).json({ error: "Invalid or missing API key" });
|
|
3807
3636
|
}
|
|
3808
3637
|
const _id = req.params.id;
|
|
3809
|
-
const updated =
|
|
3638
|
+
const updated = EventsModel.resetFailedToPending(_id, portalAddress);
|
|
3810
3639
|
if (!updated) {
|
|
3811
3640
|
return res.status(404).json({ error: "Event not found or not in failed state" });
|
|
3812
3641
|
}
|
|
@@ -3817,12 +3646,11 @@ var retryOne_default = [retryOneHandler];
|
|
|
3817
3646
|
// src/interface/api/router/events/retryAllFailed.ts
|
|
3818
3647
|
var retryAllFailedHandler = async (req, res) => {
|
|
3819
3648
|
const apiKey = req.query.apiKey;
|
|
3820
|
-
const
|
|
3821
|
-
const portalAddress = apiKeyInfo?.portalAddress;
|
|
3649
|
+
const portalAddress = ApiKeysModel.findByApiKey(apiKey)?.portalAddress;
|
|
3822
3650
|
if (!portalAddress) {
|
|
3823
3651
|
return res.status(401).json({ error: "Invalid or missing API key" });
|
|
3824
3652
|
}
|
|
3825
|
-
const count =
|
|
3653
|
+
const count = EventsModel.resetAllFailedToPending(portalAddress);
|
|
3826
3654
|
res.json({ retried: count });
|
|
3827
3655
|
};
|
|
3828
3656
|
var retryAllFailed_default = [retryAllFailedHandler];
|
|
@@ -17911,9 +17739,8 @@ var port = parseInt(config.PORT || "8001", 10);
|
|
|
17911
17739
|
var ip = config.IP || "0.0.0.0";
|
|
17912
17740
|
var server;
|
|
17913
17741
|
var startServer = async () => {
|
|
17914
|
-
|
|
17915
|
-
|
|
17916
|
-
await runMigrations();
|
|
17742
|
+
validateDbPath();
|
|
17743
|
+
runMigrations();
|
|
17917
17744
|
const apiKey = config.API_KEY;
|
|
17918
17745
|
if (!apiKey) {
|
|
17919
17746
|
logger.error("API_KEY environment variable is not set");
|
|
@@ -17958,7 +17785,7 @@ var shutdown = async () => {
|
|
|
17958
17785
|
logger.error("Error closing worker:", error48);
|
|
17959
17786
|
}
|
|
17960
17787
|
try {
|
|
17961
|
-
await
|
|
17788
|
+
await closeDatabase();
|
|
17962
17789
|
logger.info("Database connection closed");
|
|
17963
17790
|
} catch (error48) {
|
|
17964
17791
|
logger.error("Error closing database:", error48);
|