Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions backend/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ import { EmailModule } from './entities/email/email/email.module.js';
import { CompanyLogoModule } from './entities/company-logo/company-logo.module.js';
import { CompanyFaviconModule } from './entities/company-favicon/company-favicon.module.js';
import { CompanyTabTitleModule } from './entities/company-tab-title/company-tab-title.module.js';
import { TableFiltersModule } from './entities/table-filters/table-filters.module.js';

@Module({
imports: [
Expand Down Expand Up @@ -62,6 +63,7 @@ import { CompanyTabTitleModule } from './entities/company-tab-title/company-tab-
CompanyLogoModule,
CompanyFaviconModule,
CompanyTabTitleModule,
TableFiltersModule,
],
controllers: [AppController],
providers: [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ import { AiUserFileEntity } from '../../entities/ai/ai-data-entities/ai-user-fil
import { CompanyLogoEntity } from '../../entities/company-logo/company-logo.entity.js';
import { CompanyFaviconEntity } from '../../entities/company-favicon/company-favicon.entity.js';
import { CompanyTabTitleEntity } from '../../entities/company-tab-title/company-tab-title.entity.js';
import { TableFiltersEntity } from '../../entities/table-filters/table-filters.entity.js';
import { ITableFiltersCustomRepository } from '../../entities/table-filters/repository/table-filters-custom-repository.interface.js';

export interface IGlobalDatabaseContext extends IDatabaseContext {
userRepository: Repository<UserEntity> & IUserRepository;
Expand Down Expand Up @@ -77,4 +79,5 @@ export interface IGlobalDatabaseContext extends IDatabaseContext {
companyLogoRepository: Repository<CompanyLogoEntity>;
companyFaviconRepository: Repository<CompanyFaviconEntity>;
companyTabTitleRepository: Repository<CompanyTabTitleEntity>;
tableFiltersRepository: Repository<TableFiltersEntity> & ITableFiltersCustomRepository;
}
11 changes: 11 additions & 0 deletions backend/src/common/application/global-database-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@ import { aiUserFileRepositoryExtension } from '../../entities/ai/ai-data-entitie
import { CompanyLogoEntity } from '../../entities/company-logo/company-logo.entity.js';
import { CompanyFaviconEntity } from '../../entities/company-favicon/company-favicon.entity.js';
import { CompanyTabTitleEntity } from '../../entities/company-tab-title/company-tab-title.entity.js';
import { TableFiltersEntity } from '../../entities/table-filters/table-filters.entity.js';
import { ITableFiltersCustomRepository } from '../../entities/table-filters/repository/table-filters-custom-repository.interface.js';
import { tableFiltersCustomRepositoryExtension } from '../../entities/table-filters/repository/table-filters-custom-repository-extension.js';

@Injectable({ scope: Scope.REQUEST })
export class GlobalDatabaseContext implements IGlobalDatabaseContext {
Expand Down Expand Up @@ -124,6 +127,7 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
private _companyLogoRepository: Repository<CompanyLogoEntity>;
private _companyFaviconRepository: Repository<CompanyFaviconEntity>;
private _companyTabTitleRepository: Repository<CompanyTabTitleEntity>;
private _tableFiltersRepository: Repository<TableFiltersEntity> & ITableFiltersCustomRepository;

public constructor(
@Inject(BaseType.DATA_SOURCE)
Expand Down Expand Up @@ -209,6 +213,9 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
this._companyLogoRepository = this.appDataSource.getRepository(CompanyLogoEntity);
this._companyFaviconRepository = this.appDataSource.getRepository(CompanyFaviconEntity);
this._companyTabTitleRepository = this.appDataSource.getRepository(CompanyTabTitleEntity);
this._tableFiltersRepository = this.appDataSource
.getRepository(TableFiltersEntity)
.extend(tableFiltersCustomRepositoryExtension);
}

public get userRepository(): Repository<UserEntity> & IUserRepository {
Expand Down Expand Up @@ -339,6 +346,10 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
return this._companyTabTitleRepository;
}

public get tableFiltersRepository(): Repository<TableFiltersEntity> & ITableFiltersCustomRepository {
return this._tableFiltersRepository;
}

public startTransaction(): Promise<void> {
this._queryRunner = this.appDataSource.createQueryRunner();
this._queryRunner.startTransaction();
Expand Down
4 changes: 4 additions & 0 deletions backend/src/common/data-injection.tokens.ts
Original file line number Diff line number Diff line change
Expand Up @@ -185,4 +185,8 @@ export enum UseCaseType {
GET_ALL_USER_THREADS_WITH_AI_ASSISTANT = 'GET_ALL_USER_THREADS_WITH_AI_ASSISTANT',
GET_ALL_THREAD_MESSAGES = 'GET_ALL_THREAD_MESSAGES',
DELETE_THREAD_WITH_AI_ASSISTANT = 'DELETE_THREAD_WITH_AI_ASSISTANT',

CREATE_TABLE_FILTERS = 'CREATE_TABLE_FILTERS',
FIND_TABLE_FILTERS = 'FIND_TABLE_FILTERS',
DELETE_TABLE_FILTERS = 'DELETE_TABLE_FILTERS',
}
4 changes: 4 additions & 0 deletions backend/src/entities/connection/connection.entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { CompanyInfoEntity } from '../company-info/company-info.entity.js';
import { ActionRulesEntity } from '../table-actions/table-action-rules-module/action-rules.entity.js';
import { nanoid } from 'nanoid';
import { Constants } from '../../helpers/constants/constants.js';
import { TableFiltersEntity } from '../table-filters/table-filters.entity.js';

@Entity('connection')
export class ConnectionEntity {
Expand Down Expand Up @@ -234,4 +235,7 @@ export class ConnectionEntity {
@ManyToOne((_) => CompanyInfoEntity, (company) => company.connections)
@JoinTable()
company: Relation<CompanyInfoEntity>;

@OneToMany((_) => TableFiltersEntity, (table_filters) => table_filters.connection)
table_filters: Relation<TableFiltersEntity>[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export class CreateTableFiltersDto {
table_name: string;
connection_id: string;
filters: Record<string, any>;
masterPwd: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class FindTableFiltersDs {
table_name: string;
connection_id: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { ApiProperty } from '@nestjs/swagger';

export class CreatedTableFiltersRO {
@ApiProperty()
id: string;

@ApiProperty()
tableName: string;

@ApiProperty()
connectionId: string;

@ApiProperty({ type: Object })
filters: Record<string, any>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { TableFiltersEntity } from '../table-filters.entity.js';
import { ITableFiltersCustomRepository } from './table-filters-custom-repository.interface.js';

export const tableFiltersCustomRepositoryExtension: ITableFiltersCustomRepository = {
async findTableFiltersForTableInConnection(tableName: string, connectionId: string): Promise<TableFiltersEntity> {
const qb = this.createQueryBuilder('table_filters')
.leftJoin('table_filters.connection', 'connection')
.where('table_filters.table_name = :tableName', { tableName: tableName })
.andWhere('connection.id = :connectionId', { connectionId: connectionId });
return await qb.getOne();
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
import { TableFiltersEntity } from '../table-filters.entity.js';

export interface ITableFiltersCustomRepository {
findTableFiltersForTableInConnection(tableName: string, connectionId: string): Promise<TableFiltersEntity>;
}
126 changes: 126 additions & 0 deletions backend/src/entities/table-filters/table-filters.controller.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
import {
BadRequestException,
Body,
Controller,
Delete,
Get,
Inject,
Injectable,
Post,
UseGuards,
UseInterceptors,
} from '@nestjs/common';
import { ApiBearerAuth, ApiBody, ApiOperation, ApiParam, ApiQuery, ApiResponse, ApiTags } from '@nestjs/swagger';
import { UseCaseType } from '../../common/data-injection.tokens.js';
import { MasterPassword } from '../../decorators/master-password.decorator.js';
import { QueryTableName } from '../../decorators/query-table-name.decorator.js';
import { SlugUuid } from '../../decorators/slug-uuid.decorator.js';
import { InTransactionEnum } from '../../enums/in-transaction.enum.js';
import { Messages } from '../../exceptions/text/messages.js';
import { ConnectionEditGuard } from '../../guards/connection-edit.guard.js';
import { ConnectionReadGuard } from '../../guards/connection-read.guard.js';
import { SentryInterceptor } from '../../interceptors/index.js';
import { FindAllRowsWithBodyFiltersDto } from '../table/dto/find-rows-with-body-filters.dto.js';
import { CreateTableFiltersDto } from './application/data-structures/create-table-filters.ds.js';
import { FindTableFiltersDs } from './application/data-structures/find-table-filters.ds.js';
import { CreatedTableFiltersRO } from './application/response-objects/created-table-filters.ro.js';
import {
ICreateTableFilters,
IDeleteTableFilters,
IFindTableFilters,
} from './use-cases/table-filters-use-cases.interface.js';

@UseInterceptors(SentryInterceptor)
@Controller('table-filters')
@ApiBearerAuth()
@ApiTags('Table filters')
@Injectable()
export class TableFiltersController {
constructor(
@Inject(UseCaseType.CREATE_TABLE_FILTERS)
private readonly createTableFiltersUseCase: ICreateTableFilters,
@Inject(UseCaseType.FIND_TABLE_FILTERS)
private readonly findTableFiltersUseCase: IFindTableFilters,
@Inject(UseCaseType.DELETE_TABLE_FILTERS)
private readonly deleteTableFiltersUseCase: IDeleteTableFilters,
) {}

@ApiOperation({ summary: 'Add new table filters' })
@ApiBody({ type: FindAllRowsWithBodyFiltersDto })
@ApiResponse({
status: 201,
description: 'Table filters created.',
type: CreatedTableFiltersRO,
})
@ApiQuery({ name: 'tableName', required: true })
@ApiParam({ name: 'connectionId', required: true })
@UseGuards(ConnectionEditGuard)
@Post('/:connectionId')
async addTableFilters(
@QueryTableName() tableName: string,
@Body() body: FindAllRowsWithBodyFiltersDto,
@SlugUuid('connectionId') connectionId: string,
@MasterPassword() masterPwd: string,
): Promise<CreatedTableFiltersRO> {
if (!tableName) {
throw new BadRequestException(Messages.TABLE_NAME_MISSING);
}
const inputData: CreateTableFiltersDto = {
table_name: tableName,
connection_id: connectionId,
filters: body.filters,
masterPwd: masterPwd,
};
return await this.createTableFiltersUseCase.execute(inputData, InTransactionEnum.ON);
}

@ApiOperation({ summary: 'Find table filters' })
@ApiBody({ type: FindAllRowsWithBodyFiltersDto })
@ApiResponse({
status: 200,
description: 'Table filters found.',
type: CreatedTableFiltersRO,
})
@ApiQuery({ name: 'tableName', required: true })
@ApiParam({ name: 'connectionId', required: true })
@UseGuards(ConnectionReadGuard)
@Get('/:connectionId')
async findTableFilters(
@QueryTableName() tableName: string,
@SlugUuid('connectionId') connectionId: string,
): Promise<CreatedTableFiltersRO> {
if (!tableName) {
throw new BadRequestException(Messages.TABLE_NAME_MISSING);
}
const inputData: FindTableFiltersDs = {
table_name: tableName,
connection_id: connectionId,
};
return await this.findTableFiltersUseCase.execute(inputData, InTransactionEnum.OFF);
}

@ApiOperation({ summary: 'Delete table filters' })
@ApiBody({ type: FindAllRowsWithBodyFiltersDto })
@ApiResponse({
status: 200,
description: 'Table filters Deleted.',
type: CreatedTableFiltersRO,
})
@ApiQuery({ name: 'tableName', required: true })
@ApiParam({ name: 'connectionId', required: true })
@UseGuards(ConnectionEditGuard)
@Delete('/:connectionId')
async deleteTableFilters(
@QueryTableName() tableName: string,
@SlugUuid('connectionId') connectionId: string,
): Promise<CreatedTableFiltersRO> {
if (!tableName) {
throw new BadRequestException(Messages.TABLE_NAME_MISSING);
}
const inputData: FindTableFiltersDs = {
table_name: tableName,
connection_id: connectionId,
};
return await this.deleteTableFiltersUseCase.execute(inputData, InTransactionEnum.ON);
}
}
24 changes: 24 additions & 0 deletions backend/src/entities/table-filters/table-filters.entity.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { Column, Entity, JoinColumn, ManyToOne, PrimaryGeneratedColumn, Relation, Unique } from 'typeorm';
import { ConnectionEntity } from '../connection/connection.entity.js';

@Entity('table_filters')
@Unique(['connectionId', 'table_name'])
export class TableFiltersEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ type: 'jsonb', nullable: true })
filters: Record<string, any>;

@Column({ default: null })
table_name: string;

@ManyToOne((_) => ConnectionEntity, (connection) => connection.table_filters, {
onDelete: 'CASCADE',
})
@JoinColumn({ name: 'connectionId' })
connection: Relation<ConnectionEntity>;

@Column()
connectionId: string;
}
46 changes: 46 additions & 0 deletions backend/src/entities/table-filters/table-filters.module.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { MiddlewareConsumer, Module, RequestMethod } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { AuthMiddleware } from '../../authorization/auth.middleware.js';
import { GlobalDatabaseContext } from '../../common/application/global-database-context.js';
import { BaseType, UseCaseType } from '../../common/data-injection.tokens.js';
import { TableFiltersController } from './table-filters.controller.js';
import { TableFiltersEntity } from './table-filters.entity.js';
import { CreateTableFiltersUseCase } from './use-cases/create-table-filters.use.case.js';
import { UserEntity } from '../user/user.entity.js';
import { LogOutEntity } from '../log-out/log-out.entity.js';
import { FindTableFiltersUseCase } from './use-cases/find-table-filters.use.case.js';
import { DeleteTableFiltersUseCase } from './use-cases/delete-table-filters.use.case.js';

@Module({
imports: [TypeOrmModule.forFeature([TableFiltersEntity, UserEntity, LogOutEntity])],
providers: [
{
provide: BaseType.GLOBAL_DB_CONTEXT,
useClass: GlobalDatabaseContext,
},
{
provide: UseCaseType.CREATE_TABLE_FILTERS,
useClass: CreateTableFiltersUseCase,
},
{
provide: UseCaseType.FIND_TABLE_FILTERS,
useClass: FindTableFiltersUseCase,
},
{
provide: UseCaseType.DELETE_TABLE_FILTERS,
useClass: DeleteTableFiltersUseCase,
},
],
controllers: [TableFiltersController],
})
export class TableFiltersModule {
public configure(consumer: MiddlewareConsumer): any {
consumer
.apply(AuthMiddleware)
.forRoutes(
{ path: '/table-filters/:connectionId', method: RequestMethod.POST },
{ path: '/table-filters/:connectionId', method: RequestMethod.GET },
{ path: '/table-filters/:connectionId', method: RequestMethod.DELETE },
);
}
}
Loading