Skip to content

Commit 3a1ce8c

Browse files
committed
feat: add integrity check
1 parent 64924e4 commit 3a1ce8c

33 files changed

+602
-68
lines changed

apps/nestjs-backend/src/app.module.ts

+2
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import { ExportOpenApiModule } from './features/export/open-api/export-open-api.
1818
import { FieldOpenApiModule } from './features/field/open-api/field-open-api.module';
1919
import { HealthModule } from './features/health/health.module';
2020
import { ImportOpenApiModule } from './features/import/open-api/import-open-api.module';
21+
import { IntegrityModule } from './features/integrity/integrity.module';
2122
import { InvitationModule } from './features/invitation/invitation.module';
2223
import { NextModule } from './features/next/next.module';
2324
import { NotificationModule } from './features/notification/notification.module';
@@ -44,6 +45,7 @@ export const appModules = {
4445
NextModule,
4546
FieldOpenApiModule,
4647
BaseModule,
48+
IntegrityModule,
4749
ChatModule,
4850
AttachmentsModule,
4951
WsModule,

apps/nestjs-backend/src/db-provider/db.provider.interface.ts

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import type { DriverClient, IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
1+
import type { DriverClient, FieldType, IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
22
import type { Prisma } from '@teable/db-main-prisma';
33
import type { IAggregationField, ISearchIndexByQueryRo } from '@teable/openapi';
44
import type { Knex } from 'knex';
@@ -72,6 +72,8 @@ export interface IDbProvider {
7272
prisma: Prisma.TransactionClient
7373
): Promise<boolean>;
7474

75+
checkTableExist(tableName: string): string;
76+
7577
dropColumnAndIndex(tableName: string, columnName: string, indexName: string): string[];
7678

7779
modifyColumnSchema(tableName: string, columnName: string, schemaType: SchemaType): string[];
@@ -165,5 +167,5 @@ export interface IDbProvider {
165167

166168
lookupOptionsQuery(optionsKey: keyof ILookupOptionsVo, value: string): string;
167169

168-
optionsQuery(optionsKey: string, value: string): string;
170+
optionsQuery(type: FieldType, optionsKey: string, value: string): string;
169171
}

apps/nestjs-backend/src/db-provider/postgres.provider.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable sonarjs/no-duplicate-string */
22
import { Logger } from '@nestjs/common';
3-
import type { IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
3+
import type { FieldType, IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
44
import { DriverClient } from '@teable/core';
55
import type { PrismaClient } from '@teable/db-main-prisma';
66
import type { IAggregationField, ISearchIndexByQueryRo } from '@teable/openapi';
@@ -75,6 +75,16 @@ export class PostgresProvider implements IDbProvider {
7575
return res[0].exists;
7676
}
7777

78+
checkTableExist(tableName: string): string {
79+
const [schemaName, dbTableName] = this.splitTableName(tableName);
80+
return this.knex
81+
.raw(
82+
'SELECT EXISTS (SELECT FROM information_schema.tables WHERE table_schema = ? AND table_name = ?) AS exists',
83+
[schemaName, dbTableName]
84+
)
85+
.toQuery();
86+
}
87+
7888
renameColumn(tableName: string, oldName: string, newName: string): string[] {
7989
return this.knex.schema
8090
.alterTable(tableName, (table) => {
@@ -424,20 +434,29 @@ export class PostgresProvider implements IDbProvider {
424434
lookupOptionsQuery(optionsKey: keyof ILookupOptionsVo, value: string): string {
425435
return this.knex('field')
426436
.select({
437+
tableId: 'table_id',
427438
id: 'id',
439+
type: 'type',
440+
name: 'name',
428441
lookupOptions: 'lookup_options',
429442
})
443+
.whereNull('deleted_time')
430444
.whereRaw(`lookup_options::json->>'${optionsKey}' = ?`, [value])
431445
.toQuery();
432446
}
433447

434-
optionsQuery(optionsKey: string, value: string): string {
448+
optionsQuery(type: FieldType, optionsKey: string, value: string): string {
435449
return this.knex('field')
436450
.select({
451+
tableId: 'table_id',
437452
id: 'id',
453+
type: 'type',
454+
name: 'name',
438455
options: 'options',
439456
})
457+
.whereNull('deleted_time')
440458
.whereRaw(`options::json->>'${optionsKey}' = ?`, [value])
459+
.where('type', type)
441460
.toQuery();
442461
}
443462
}

apps/nestjs-backend/src/db-provider/sqlite.provider.ts

+21-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable sonarjs/no-duplicate-string */
22
import { Logger } from '@nestjs/common';
3-
import type { IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
3+
import type { FieldType, IFilter, ILookupOptionsVo, ISortItem } from '@teable/core';
44
import { DriverClient } from '@teable/core';
55
import type { PrismaClient } from '@teable/db-main-prisma';
66
import type { IAggregationField, ISearchIndexByQueryRo } from '@teable/openapi';
@@ -65,6 +65,18 @@ export class SqliteProvider implements IDbProvider {
6565
return columns.some((column) => column.name === columnName);
6666
}
6767

68+
checkTableExist(tableName: string): string {
69+
return this.knex
70+
.raw(
71+
`SELECT EXISTS (
72+
SELECT 1 FROM sqlite_master
73+
WHERE type='table' AND name = ?
74+
) as exists`,
75+
[tableName]
76+
)
77+
.toQuery();
78+
}
79+
6880
renameColumn(tableName: string, oldName: string, newName: string): string[] {
6981
return [
7082
this.knex
@@ -382,18 +394,25 @@ export class SqliteProvider implements IDbProvider {
382394
return this.knex('field')
383395
.select({
384396
id: 'id',
397+
type: 'type',
398+
name: 'name',
385399
lookupOptions: 'lookup_options',
386400
})
401+
.whereNull('deleted_time')
387402
.whereRaw(`json_extract(lookup_options, '$."${optionsKey}"') = ?`, [value])
388403
.toQuery();
389404
}
390405

391-
optionsQuery(optionsKey: string, value: string): string {
406+
optionsQuery(type: FieldType, optionsKey: string, value: string): string {
392407
return this.knex('field')
393408
.select({
394409
id: 'id',
410+
type: 'type',
411+
name: 'name',
395412
options: 'options',
396413
})
414+
.where('type', type)
415+
.whereNull('deleted_time')
397416
.whereRaw(`json_extract(options, '$."${optionsKey}"') = ?`, [value])
398417
.toQuery();
399418
}

apps/nestjs-backend/src/features/calculation/reference.service.ts

+12
Original file line numberDiff line numberDiff line change
@@ -343,6 +343,10 @@ export class ReferenceService {
343343
if (!fromRecordIds?.length && !toRecordIds?.length) {
344344
continue;
345345
}
346+
347+
console.log('order', JSON.stringify(order, null, 2));
348+
console.log('order:fromRecordIds', fromRecordIds);
349+
console.log('order:toRecordIds', toRecordIds);
346350
const relatedRecordItems = await this.getAffectedRecordItems({
347351
fieldId,
348352
fieldMap,
@@ -351,6 +355,7 @@ export class ReferenceService {
351355
fkRecordMap,
352356
tableId2DbTableName,
353357
});
358+
console.log('relatedRecordItems', relatedRecordItems);
354359

355360
if (field.lookupOptions || field.type === FieldType.Link) {
356361
await this.calculateLinkRelatedRecords({
@@ -583,6 +588,13 @@ export class ReferenceService {
583588
: (field.options as ILinkFieldOptions);
584589
const { relationship } = lookupOptions;
585590
const linkFieldId = field.lookupOptions ? field.lookupOptions.linkFieldId : field.id;
591+
592+
if (!recordItem.record?.fields) {
593+
console.log('recordItem', JSON.stringify(recordItem, null, 2));
594+
console.log('recordItem.field', field);
595+
throw new InternalServerErrorException('record fields is undefined');
596+
}
597+
586598
const cellValue = recordItem.record.fields[linkFieldId];
587599
const dependenciesIndexed = keyBy(dependencies, 'id');
588600

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import { Controller, Get, Param, Post, UseGuards } from '@nestjs/common';
2+
import type { IIntegrityCheckVo } from '@teable/openapi';
3+
import { Permissions } from '../auth/decorators/permissions.decorator';
4+
import { PermissionGuard } from '../auth/guard/permission.guard';
5+
import { LinkIntegrityService } from './link-integrity.service';
6+
7+
@UseGuards(PermissionGuard)
8+
@Controller('api/integrity')
9+
export class IntegrityController {
10+
constructor(private readonly linkIntegrityService: LinkIntegrityService) {}
11+
12+
@Permissions('table|create')
13+
@Get('base/:baseId/link-check')
14+
async checkBaseIntegrity(@Param('baseId') baseId: string): Promise<IIntegrityCheckVo> {
15+
return await this.linkIntegrityService.linkIntegrityCheck(baseId);
16+
}
17+
18+
@Permissions('table|create')
19+
@Post('base/:baseId/link-fix')
20+
async fixBaseIntegrity(@Param('baseId') baseId: string): Promise<void> {
21+
return await this.linkIntegrityService.linkIntegrityFix(baseId);
22+
}
23+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import { Module } from '@nestjs/common';
2+
import { IntegrityController } from './integrity.controller';
3+
import { LinkIntegrityService } from './link-integrity.service';
4+
5+
@Module({
6+
controllers: [IntegrityController],
7+
providers: [LinkIntegrityService],
8+
exports: [LinkIntegrityService],
9+
})
10+
export class IntegrityModule {}

0 commit comments

Comments
 (0)