@lobehub/lobehub 2.0.0-next.127 → 2.0.0-next.128
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/.env.example +23 -3
- package/.env.example.development +5 -0
- package/CHANGELOG.md +25 -0
- package/changelog/v1.json +9 -0
- package/docker-compose/local/docker-compose.yml +24 -1
- package/docker-compose/local/logto/docker-compose.yml +25 -2
- package/docker-compose.development.yml +6 -0
- package/locales/ar/auth.json +114 -1
- package/locales/bg-BG/auth.json +114 -1
- package/locales/de-DE/auth.json +114 -1
- package/locales/en-US/auth.json +42 -22
- package/locales/es-ES/auth.json +114 -1
- package/locales/fa-IR/auth.json +114 -1
- package/locales/fr-FR/auth.json +114 -1
- package/locales/it-IT/auth.json +114 -1
- package/locales/ja-JP/auth.json +114 -1
- package/locales/ko-KR/auth.json +114 -1
- package/locales/nl-NL/auth.json +114 -1
- package/locales/pl-PL/auth.json +114 -1
- package/locales/pt-BR/auth.json +114 -1
- package/locales/ru-RU/auth.json +114 -1
- package/locales/tr-TR/auth.json +114 -1
- package/locales/vi-VN/auth.json +114 -1
- package/locales/zh-CN/auth.json +36 -29
- package/locales/zh-TW/auth.json +114 -1
- package/package.json +4 -1
- package/packages/database/src/client/db.ts +21 -21
- package/packages/database/src/repositories/dataImporter/deprecated/index.ts +5 -5
- package/packages/database/src/repositories/dataImporter/index.ts +59 -59
- package/packages/database/src/schemas/generation.ts +16 -16
- package/packages/database/src/schemas/oidc.ts +36 -36
- package/packages/model-runtime/src/providers/newapi/index.ts +61 -18
- package/packages/model-runtime/src/runtimeMap.ts +1 -0
- package/src/app/[variants]/(main)/settings/provider/features/ProviderConfig/UpdateProviderInfo/SettingModal.tsx +10 -6
- package/src/envs/redis.ts +106 -0
- package/src/libs/redis/index.ts +5 -0
- package/src/libs/redis/manager.test.ts +107 -0
- package/src/libs/redis/manager.ts +56 -0
- package/src/libs/redis/redis.test.ts +158 -0
- package/src/libs/redis/redis.ts +117 -0
- package/src/libs/redis/types.ts +71 -0
- package/src/libs/redis/upstash.test.ts +154 -0
- package/src/libs/redis/upstash.ts +109 -0
- package/src/libs/redis/utils.test.ts +46 -0
- package/src/libs/redis/utils.ts +53 -0
- package/.github/workflows/check-console-log.yml +0 -117
|
@@ -67,7 +67,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
|
|
|
67
67
|
},
|
|
68
68
|
{
|
|
69
69
|
conflictStrategy: 'skip',
|
|
70
|
-
preserveId: true, //
|
|
70
|
+
preserveId: true, // Need to preserve original ID
|
|
71
71
|
relations: [
|
|
72
72
|
{
|
|
73
73
|
field: 'providerId',
|
|
@@ -89,7 +89,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
|
|
|
89
89
|
uniqueConstraints: ['slug'],
|
|
90
90
|
},
|
|
91
91
|
{
|
|
92
|
-
//
|
|
92
|
+
// Special processing for slug field
|
|
93
93
|
fieldProcessors: {
|
|
94
94
|
slug: (value) => `${value}-${uuid().slice(0, 8)}`,
|
|
95
95
|
},
|
|
@@ -113,7 +113,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
|
|
|
113
113
|
},
|
|
114
114
|
{
|
|
115
115
|
conflictStrategy: 'skip',
|
|
116
|
-
isCompositeKey: true, //
|
|
116
|
+
isCompositeKey: true, // Uses composite primary key [agentId, sessionId]
|
|
117
117
|
relations: [
|
|
118
118
|
{
|
|
119
119
|
field: 'agentId',
|
|
@@ -172,7 +172,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
|
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
conflictStrategy: 'skip',
|
|
175
|
-
preserveId: true, //
|
|
175
|
+
preserveId: true, // Uses message ID as primary key
|
|
176
176
|
relations: [
|
|
177
177
|
{
|
|
178
178
|
field: 'id',
|
|
@@ -182,7 +182,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
|
|
|
182
182
|
table: 'messagePlugins',
|
|
183
183
|
},
|
|
184
184
|
{
|
|
185
|
-
isCompositeKey: true, //
|
|
185
|
+
isCompositeKey: true, // Uses composite primary key [messageId, chunkId]
|
|
186
186
|
relations: [
|
|
187
187
|
{
|
|
188
188
|
field: 'messageId',
|
|
@@ -196,7 +196,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
|
|
|
196
196
|
table: 'messageChunks',
|
|
197
197
|
},
|
|
198
198
|
{
|
|
199
|
-
isCompositeKey: true, //
|
|
199
|
+
isCompositeKey: true, // Uses composite primary key [id, queryId, chunkId]
|
|
200
200
|
relations: [
|
|
201
201
|
{
|
|
202
202
|
field: 'id',
|
|
@@ -228,7 +228,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
|
|
|
228
228
|
// },
|
|
229
229
|
{
|
|
230
230
|
conflictStrategy: 'skip',
|
|
231
|
-
preserveId: true, //
|
|
231
|
+
preserveId: true, // Uses message ID as primary key
|
|
232
232
|
relations: [
|
|
233
233
|
{
|
|
234
234
|
field: 'id',
|
|
@@ -239,7 +239,7 @@ const IMPORT_TABLE_CONFIG: TableImportConfig[] = [
|
|
|
239
239
|
},
|
|
240
240
|
// {
|
|
241
241
|
// conflictStrategy: 'skip',
|
|
242
|
-
// preserveId: true, //
|
|
242
|
+
// preserveId: true, // Uses message ID as primary key
|
|
243
243
|
// relations: [
|
|
244
244
|
// {
|
|
245
245
|
// field: 'id',
|
|
@@ -273,7 +273,7 @@ export class DataImporterRepos {
|
|
|
273
273
|
};
|
|
274
274
|
|
|
275
275
|
/**
|
|
276
|
-
*
|
|
276
|
+
* Import PostgreSQL data
|
|
277
277
|
*/
|
|
278
278
|
async importPgData(
|
|
279
279
|
dbData: ImportPgDataStructure,
|
|
@@ -282,13 +282,13 @@ export class DataImporterRepos {
|
|
|
282
282
|
const results: Record<string, ImportResult> = {};
|
|
283
283
|
const { data } = dbData;
|
|
284
284
|
|
|
285
|
-
//
|
|
285
|
+
// Initialize ID mapping table and conflict records
|
|
286
286
|
this.idMaps = {};
|
|
287
287
|
this.conflictRecords = {};
|
|
288
288
|
|
|
289
289
|
try {
|
|
290
290
|
await this.db.transaction(async (trx) => {
|
|
291
|
-
//
|
|
291
|
+
// Import tables in configuration order
|
|
292
292
|
for (const config of IMPORT_TABLE_CONFIG) {
|
|
293
293
|
const { table: tableName } = config;
|
|
294
294
|
|
|
@@ -299,7 +299,7 @@ export class DataImporterRepos {
|
|
|
299
299
|
continue;
|
|
300
300
|
}
|
|
301
301
|
|
|
302
|
-
//
|
|
302
|
+
// Use unified import method
|
|
303
303
|
const result = await this.importTableData(trx, config, tableData, conflictStrategy);
|
|
304
304
|
console.log(`imported table: ${tableName}, records: ${tableData.length}`);
|
|
305
305
|
|
|
@@ -325,11 +325,11 @@ export class DataImporterRepos {
|
|
|
325
325
|
}
|
|
326
326
|
|
|
327
327
|
/**
|
|
328
|
-
*
|
|
328
|
+
* Extract detailed information from error
|
|
329
329
|
*/
|
|
330
330
|
private extractErrorDetails(error: any) {
|
|
331
331
|
if (error.code === '23505') {
|
|
332
|
-
// PostgreSQL
|
|
332
|
+
// PostgreSQL unique constraint error code
|
|
333
333
|
const match = error.detail?.match(/Key \((.+?)\)=\((.+?)\) already exists/);
|
|
334
334
|
if (match) {
|
|
335
335
|
return {
|
|
@@ -344,7 +344,7 @@ export class DataImporterRepos {
|
|
|
344
344
|
}
|
|
345
345
|
|
|
346
346
|
/**
|
|
347
|
-
*
|
|
347
|
+
* Unified table data import function - Handles all types of tables
|
|
348
348
|
*/
|
|
349
349
|
private async importTableData(
|
|
350
350
|
trx: any,
|
|
@@ -368,13 +368,13 @@ export class DataImporterRepos {
|
|
|
368
368
|
const table = EXPORT_TABLES[tableName];
|
|
369
369
|
const result: ImportResult = { added: 0, errors: 0, skips: 0, updated: 0 };
|
|
370
370
|
|
|
371
|
-
//
|
|
371
|
+
// Initialize ID mapping for this table
|
|
372
372
|
if (!this.idMaps[tableName]) {
|
|
373
373
|
this.idMaps[tableName] = {};
|
|
374
374
|
}
|
|
375
375
|
|
|
376
376
|
try {
|
|
377
|
-
// 1.
|
|
377
|
+
// 1. Find existing records (based on clientId and userId)
|
|
378
378
|
let existingRecords: any[] = [];
|
|
379
379
|
|
|
380
380
|
if ('clientId' in table && 'userId' in table) {
|
|
@@ -387,7 +387,7 @@ export class DataImporterRepos {
|
|
|
387
387
|
}
|
|
388
388
|
}
|
|
389
389
|
|
|
390
|
-
//
|
|
390
|
+
// If need to preserve original ID, also check if ID already exists
|
|
391
391
|
if (preserveId && !isCompositeKey) {
|
|
392
392
|
const ids = tableData.map((item) => item.id).filter(Boolean);
|
|
393
393
|
if (ids.length > 0) {
|
|
@@ -395,7 +395,7 @@ export class DataImporterRepos {
|
|
|
395
395
|
where: inArray(table.id, ids),
|
|
396
396
|
});
|
|
397
397
|
|
|
398
|
-
//
|
|
398
|
+
// Merge into existing records set
|
|
399
399
|
existingRecords = [
|
|
400
400
|
...existingRecords,
|
|
401
401
|
...idExistingRecords.filter(
|
|
@@ -407,28 +407,28 @@ export class DataImporterRepos {
|
|
|
407
407
|
|
|
408
408
|
result.skips = existingRecords.length;
|
|
409
409
|
|
|
410
|
-
// 2.
|
|
410
|
+
// 2. Establish ID mapping for existing records
|
|
411
411
|
for (const record of existingRecords) {
|
|
412
|
-
//
|
|
412
|
+
// Only non-composite key tables need ID mapping
|
|
413
413
|
if (!isCompositeKey) {
|
|
414
414
|
this.idMaps[tableName][record.id] = record.id;
|
|
415
415
|
if (record.clientId) {
|
|
416
416
|
this.idMaps[tableName][record.clientId] = record.id;
|
|
417
417
|
}
|
|
418
418
|
|
|
419
|
-
//
|
|
419
|
+
// Any other ID identifiers that may be used in records
|
|
420
420
|
const originalRecord = tableData.find(
|
|
421
421
|
(item) => item.id === record.id || item.clientId === record.clientId,
|
|
422
422
|
);
|
|
423
423
|
|
|
424
424
|
if (originalRecord) {
|
|
425
|
-
//
|
|
425
|
+
// Ensure original record ID also maps to database record ID
|
|
426
426
|
this.idMaps[tableName][originalRecord.id] = record.id;
|
|
427
427
|
}
|
|
428
428
|
}
|
|
429
429
|
}
|
|
430
430
|
|
|
431
|
-
// 3.
|
|
431
|
+
// 3. Filter out records that need to be inserted
|
|
432
432
|
const recordsToInsert = tableData.filter(
|
|
433
433
|
(item) =>
|
|
434
434
|
!existingRecords.some(
|
|
@@ -442,22 +442,22 @@ export class DataImporterRepos {
|
|
|
442
442
|
return result;
|
|
443
443
|
}
|
|
444
444
|
|
|
445
|
-
// 4.
|
|
445
|
+
// 4. Prepare import data
|
|
446
446
|
const preparedData = recordsToInsert.map((item) => {
|
|
447
447
|
const originalId = item.id;
|
|
448
448
|
|
|
449
|
-
//
|
|
449
|
+
// Process date fields
|
|
450
450
|
const dateFields: any = {};
|
|
451
451
|
if (item.createdAt) dateFields.createdAt = new Date(item.createdAt);
|
|
452
452
|
if (item.updatedAt) dateFields.updatedAt = new Date(item.updatedAt);
|
|
453
453
|
if (item.accessedAt) dateFields.accessedAt = new Date(item.accessedAt);
|
|
454
454
|
|
|
455
|
-
//
|
|
455
|
+
// Create new record object
|
|
456
456
|
let newRecord: any = {};
|
|
457
457
|
|
|
458
|
-
//
|
|
458
|
+
// Decide how to process based on whether it's composite key and whether to preserve ID
|
|
459
459
|
if (isCompositeKey) {
|
|
460
|
-
//
|
|
460
|
+
// For composite key tables, don't include id field
|
|
461
461
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
462
462
|
const { id: _, ...rest } = item;
|
|
463
463
|
newRecord = {
|
|
@@ -467,7 +467,7 @@ export class DataImporterRepos {
|
|
|
467
467
|
userId: this.userId,
|
|
468
468
|
};
|
|
469
469
|
} else {
|
|
470
|
-
//
|
|
470
|
+
// Non-composite key table processing
|
|
471
471
|
newRecord = {
|
|
472
472
|
...(preserveId ? item : { ...item, id: undefined }),
|
|
473
473
|
...dateFields,
|
|
@@ -476,19 +476,19 @@ export class DataImporterRepos {
|
|
|
476
476
|
};
|
|
477
477
|
}
|
|
478
478
|
|
|
479
|
-
//
|
|
479
|
+
// Apply field processors
|
|
480
480
|
for (const field in fieldProcessors) {
|
|
481
481
|
if (newRecord[field] !== undefined) {
|
|
482
482
|
newRecord[field] = fieldProcessors[field](newRecord[field]);
|
|
483
483
|
}
|
|
484
484
|
}
|
|
485
485
|
|
|
486
|
-
//
|
|
486
|
+
// Special table processing
|
|
487
487
|
if (tableName === 'userSettings') {
|
|
488
488
|
newRecord.id = this.userId;
|
|
489
489
|
}
|
|
490
490
|
|
|
491
|
-
//
|
|
491
|
+
// Process relation fields (foreign key references)
|
|
492
492
|
for (const relation of relations) {
|
|
493
493
|
const { field, sourceTable } = relation;
|
|
494
494
|
|
|
@@ -498,7 +498,7 @@ export class DataImporterRepos {
|
|
|
498
498
|
if (mappedId) {
|
|
499
499
|
newRecord[field] = mappedId;
|
|
500
500
|
} else {
|
|
501
|
-
//
|
|
501
|
+
// Cannot find mapping, set to null
|
|
502
502
|
console.warn(
|
|
503
503
|
`Could not find mapped ID for ${field}=${newRecord[field]} in table ${sourceTable}`,
|
|
504
504
|
);
|
|
@@ -507,7 +507,7 @@ export class DataImporterRepos {
|
|
|
507
507
|
}
|
|
508
508
|
}
|
|
509
509
|
|
|
510
|
-
//
|
|
510
|
+
// Simplified processing of self-reference fields - directly set to null
|
|
511
511
|
for (const selfRef of selfReferences) {
|
|
512
512
|
const { field } = selfRef;
|
|
513
513
|
if (newRecord[field] !== undefined) {
|
|
@@ -518,15 +518,15 @@ export class DataImporterRepos {
|
|
|
518
518
|
return { newRecord, originalId };
|
|
519
519
|
});
|
|
520
520
|
|
|
521
|
-
// 5.
|
|
521
|
+
// 5. Check unique constraints and apply conflict strategy
|
|
522
522
|
for (const record of preparedData) {
|
|
523
523
|
if (isCompositeKey && uniqueConstraints.length > 0) {
|
|
524
|
-
//
|
|
524
|
+
// For composite key tables, treat all unique constraint fields as a combined condition
|
|
525
525
|
const whereConditions = uniqueConstraints
|
|
526
526
|
.filter((field) => record.newRecord[field] !== undefined)
|
|
527
527
|
.map((field) => eq(table[field], record.newRecord[field]));
|
|
528
528
|
|
|
529
|
-
//
|
|
529
|
+
// Add userId condition (if table has userId field)
|
|
530
530
|
if ('userId' in table) {
|
|
531
531
|
whereConditions.push(eq(table.userId, this.userId));
|
|
532
532
|
}
|
|
@@ -537,7 +537,7 @@ export class DataImporterRepos {
|
|
|
537
537
|
});
|
|
538
538
|
|
|
539
539
|
if (exists) {
|
|
540
|
-
//
|
|
540
|
+
// Record conflict
|
|
541
541
|
if (!this.conflictRecords[tableName]) this.conflictRecords[tableName] = [];
|
|
542
542
|
this.conflictRecords[tableName].push({
|
|
543
543
|
field: uniqueConstraints.join(','),
|
|
@@ -546,13 +546,13 @@ export class DataImporterRepos {
|
|
|
546
546
|
.join(','),
|
|
547
547
|
});
|
|
548
548
|
|
|
549
|
-
//
|
|
549
|
+
// Apply conflict strategy
|
|
550
550
|
switch (conflictStrategy) {
|
|
551
551
|
case 'skip': {
|
|
552
552
|
record.newRecord._skip = true;
|
|
553
553
|
result.skips++;
|
|
554
554
|
|
|
555
|
-
//
|
|
555
|
+
// Key improvement: establish ID mapping even if skipped
|
|
556
556
|
if (!isCompositeKey) {
|
|
557
557
|
this.idMaps[tableName][record.originalId] = exists.id;
|
|
558
558
|
if (record.newRecord.clientId) {
|
|
@@ -562,11 +562,11 @@ export class DataImporterRepos {
|
|
|
562
562
|
break;
|
|
563
563
|
}
|
|
564
564
|
case 'override': {
|
|
565
|
-
//
|
|
565
|
+
// No additional operation needed, will be overridden on insert
|
|
566
566
|
break;
|
|
567
567
|
}
|
|
568
568
|
case 'merge': {
|
|
569
|
-
//
|
|
569
|
+
// Merge data
|
|
570
570
|
await trx
|
|
571
571
|
.update(table)
|
|
572
572
|
.set(record.newRecord)
|
|
@@ -582,30 +582,30 @@ export class DataImporterRepos {
|
|
|
582
582
|
}
|
|
583
583
|
}
|
|
584
584
|
} else {
|
|
585
|
-
//
|
|
585
|
+
// Process unique constraints
|
|
586
586
|
for (const field of uniqueConstraints) {
|
|
587
587
|
if (!record.newRecord[field]) continue;
|
|
588
588
|
|
|
589
|
-
//
|
|
589
|
+
// Check if field value already exists
|
|
590
590
|
const exists = await trx.query[tableName].findFirst({
|
|
591
591
|
where: eq(table[field], record.newRecord[field]),
|
|
592
592
|
});
|
|
593
593
|
|
|
594
594
|
if (exists) {
|
|
595
|
-
//
|
|
595
|
+
// Record conflict
|
|
596
596
|
if (!this.conflictRecords[tableName]) this.conflictRecords[tableName] = [];
|
|
597
597
|
this.conflictRecords[tableName].push({
|
|
598
598
|
field,
|
|
599
599
|
value: record.newRecord[field],
|
|
600
600
|
});
|
|
601
601
|
|
|
602
|
-
//
|
|
602
|
+
// Apply conflict strategy
|
|
603
603
|
switch (conflictStrategy) {
|
|
604
604
|
case 'skip': {
|
|
605
605
|
record.newRecord._skip = true;
|
|
606
606
|
result.skips++;
|
|
607
607
|
|
|
608
|
-
//
|
|
608
|
+
// Key improvement: establish ID mapping even if skipped
|
|
609
609
|
if (!isCompositeKey) {
|
|
610
610
|
this.idMaps[tableName][record.originalId] = exists.id;
|
|
611
611
|
if (record.newRecord.clientId) {
|
|
@@ -615,7 +615,7 @@ export class DataImporterRepos {
|
|
|
615
615
|
break;
|
|
616
616
|
}
|
|
617
617
|
case 'override': {
|
|
618
|
-
//
|
|
618
|
+
// Apply field processor
|
|
619
619
|
if (field in fieldProcessors) {
|
|
620
620
|
record.newRecord[field] = fieldProcessors[field](record.newRecord[field]);
|
|
621
621
|
}
|
|
@@ -623,7 +623,7 @@ export class DataImporterRepos {
|
|
|
623
623
|
}
|
|
624
624
|
|
|
625
625
|
case 'merge': {
|
|
626
|
-
//
|
|
626
|
+
// Merge data
|
|
627
627
|
await trx
|
|
628
628
|
.update(table)
|
|
629
629
|
.set(record.newRecord)
|
|
@@ -641,13 +641,13 @@ export class DataImporterRepos {
|
|
|
641
641
|
}
|
|
642
642
|
}
|
|
643
643
|
|
|
644
|
-
//
|
|
644
|
+
// Filter out records marked to be skipped
|
|
645
645
|
const filteredData = preparedData.filter((record) => !record.newRecord._skip);
|
|
646
646
|
|
|
647
|
-
//
|
|
647
|
+
// Clear temporary markers
|
|
648
648
|
filteredData.forEach((record) => delete record.newRecord._skip);
|
|
649
649
|
|
|
650
|
-
// 6.
|
|
650
|
+
// 6. Batch insert data
|
|
651
651
|
const BATCH_SIZE = 100;
|
|
652
652
|
|
|
653
653
|
for (let i = 0; i < filteredData.length; i += BATCH_SIZE) {
|
|
@@ -657,12 +657,12 @@ export class DataImporterRepos {
|
|
|
657
657
|
const originalIds = batch.map((item) => item.originalId);
|
|
658
658
|
|
|
659
659
|
try {
|
|
660
|
-
//
|
|
660
|
+
// Insert and return result
|
|
661
661
|
const insertQuery = trx.insert(table).values(itemsToInsert);
|
|
662
662
|
|
|
663
663
|
let insertResult;
|
|
664
664
|
|
|
665
|
-
//
|
|
665
|
+
// Only non-composite key tables need to return ID
|
|
666
666
|
if (!isCompositeKey) {
|
|
667
667
|
const res = await insertQuery.returning();
|
|
668
668
|
insertResult = res.map((item: any) => ({
|
|
@@ -671,18 +671,18 @@ export class DataImporterRepos {
|
|
|
671
671
|
}));
|
|
672
672
|
} else {
|
|
673
673
|
await insertQuery;
|
|
674
|
-
insertResult = itemsToInsert.map(() => ({})); //
|
|
674
|
+
insertResult = itemsToInsert.map(() => ({})); // Create empty result to maintain count
|
|
675
675
|
}
|
|
676
676
|
|
|
677
677
|
result.added += insertResult.length;
|
|
678
678
|
|
|
679
|
-
//
|
|
679
|
+
// Establish ID mapping relationship (only for non-composite key tables)
|
|
680
680
|
if (!isCompositeKey) {
|
|
681
681
|
for (const [j, newRecord] of insertResult.entries()) {
|
|
682
682
|
const originalId = originalIds[j];
|
|
683
683
|
this.idMaps[tableName][originalId] = newRecord.id;
|
|
684
684
|
|
|
685
|
-
//
|
|
685
|
+
// Also ensure clientId can map to the correct ID
|
|
686
686
|
const originalRecord = tableData.find((item) => item.id === originalId);
|
|
687
687
|
if (originalRecord && originalRecord.clientId) {
|
|
688
688
|
this.idMaps[tableName][originalRecord.clientId] = newRecord.id;
|
|
@@ -692,7 +692,7 @@ export class DataImporterRepos {
|
|
|
692
692
|
} catch (error) {
|
|
693
693
|
console.error(`Error batch inserting ${tableName}:`, error);
|
|
694
694
|
|
|
695
|
-
//
|
|
695
|
+
// Handle error and record
|
|
696
696
|
if ((error as any).code === '23505') {
|
|
697
697
|
const match = (error as any).detail?.match(/Key \((.+?)\)=\((.+?)\) already exists/);
|
|
698
698
|
if (match) {
|
|
@@ -10,7 +10,7 @@ import { files } from './file';
|
|
|
10
10
|
import { users } from './user';
|
|
11
11
|
|
|
12
12
|
/**
|
|
13
|
-
*
|
|
13
|
+
* Generation topics table - Used to organize and manage AI-generated content topics
|
|
14
14
|
*/
|
|
15
15
|
export const generationTopics = pgTable('generation_topics', {
|
|
16
16
|
id: text('id')
|
|
@@ -22,10 +22,10 @@ export const generationTopics = pgTable('generation_topics', {
|
|
|
22
22
|
.references(() => users.id, { onDelete: 'cascade' })
|
|
23
23
|
.notNull(),
|
|
24
24
|
|
|
25
|
-
/**
|
|
25
|
+
/** Brief description of topic content, generated by LLM */
|
|
26
26
|
title: text('title'),
|
|
27
27
|
|
|
28
|
-
/**
|
|
28
|
+
/** Topic cover image URL */
|
|
29
29
|
coverUrl: text('cover_url'),
|
|
30
30
|
|
|
31
31
|
...timestamps,
|
|
@@ -37,7 +37,7 @@ export type NewGenerationTopic = typeof generationTopics.$inferInsert;
|
|
|
37
37
|
export type GenerationTopicItem = typeof generationTopics.$inferSelect;
|
|
38
38
|
|
|
39
39
|
/**
|
|
40
|
-
*
|
|
40
|
+
* Generation batches table - Stores configuration information for a single generation request
|
|
41
41
|
*/
|
|
42
42
|
export const generationBatches = pgTable('generation_batches', {
|
|
43
43
|
id: text('id')
|
|
@@ -53,25 +53,25 @@ export const generationBatches = pgTable('generation_batches', {
|
|
|
53
53
|
.notNull()
|
|
54
54
|
.references(() => generationTopics.id, { onDelete: 'cascade' }),
|
|
55
55
|
|
|
56
|
-
/**
|
|
56
|
+
/** Provider name */
|
|
57
57
|
provider: text('provider').notNull(),
|
|
58
58
|
|
|
59
|
-
/**
|
|
59
|
+
/** Model name */
|
|
60
60
|
model: text('model').notNull(),
|
|
61
61
|
|
|
62
|
-
/**
|
|
62
|
+
/** Generation prompt */
|
|
63
63
|
prompt: text('prompt').notNull(),
|
|
64
64
|
|
|
65
|
-
/**
|
|
65
|
+
/** Image width */
|
|
66
66
|
width: integer('width'),
|
|
67
67
|
|
|
68
|
-
/**
|
|
68
|
+
/** Image height */
|
|
69
69
|
height: integer('height'),
|
|
70
70
|
|
|
71
|
-
/**
|
|
71
|
+
/** Image aspect ratio */
|
|
72
72
|
ratio: varchar('ratio', { length: 64 }),
|
|
73
73
|
|
|
74
|
-
/**
|
|
74
|
+
/** Stores generation batch configuration for common settings that don't need indexing */
|
|
75
75
|
config: jsonb('config'),
|
|
76
76
|
|
|
77
77
|
...timestamps,
|
|
@@ -86,7 +86,7 @@ export type GenerationBatchWithGenerations = GenerationBatchItem & {
|
|
|
86
86
|
};
|
|
87
87
|
|
|
88
88
|
/**
|
|
89
|
-
*
|
|
89
|
+
* Stores individual AI generation information
|
|
90
90
|
*/
|
|
91
91
|
export const generations = pgTable('generations', {
|
|
92
92
|
id: text('id')
|
|
@@ -102,18 +102,18 @@ export const generations = pgTable('generations', {
|
|
|
102
102
|
.notNull()
|
|
103
103
|
.references(() => generationBatches.id, { onDelete: 'cascade' }),
|
|
104
104
|
|
|
105
|
-
/**
|
|
105
|
+
/** Associated async task ID */
|
|
106
106
|
asyncTaskId: uuid('async_task_id').references(() => asyncTasks.id, {
|
|
107
107
|
onDelete: 'set null',
|
|
108
108
|
}),
|
|
109
109
|
|
|
110
|
-
/**
|
|
110
|
+
/** Associated generated file ID, deletes generation record when file is deleted */
|
|
111
111
|
fileId: text('file_id').references(() => files.id, { onDelete: 'cascade' }),
|
|
112
112
|
|
|
113
|
-
/**
|
|
113
|
+
/** Generation seed value */
|
|
114
114
|
seed: integer('seed'),
|
|
115
115
|
|
|
116
|
-
/**
|
|
116
|
+
/** Generated asset information, including S3 storage key, actual image dimensions, thumbnail key, etc. */
|
|
117
117
|
asset: jsonb('asset').$type<ImageGenerationAsset>(),
|
|
118
118
|
|
|
119
119
|
...timestamps,
|