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
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,10 @@ import { DashboardEntity } from '../../entities/visualizations/dashboard/dashboa
import { DashboardWidgetEntity } from '../../entities/visualizations/dashboard-widget/dashboard-widget.entity.js';
import { IDashboardRepository } from '../../entities/visualizations/dashboard/repository/dashboard.repository.interface.js';
import { IDashboardWidgetRepository } from '../../entities/visualizations/dashboard-widget/repository/dashboard-widget.repository.interface.js';
import { UserAiChatEntity } from '../../entities/ai/ai-conversation-history/user-ai-chat/user-ai-chat.entity.js';
import { IUserAiChatRepository } from '../../entities/ai/ai-conversation-history/user-ai-chat/repository/user-ai-chat-repository.interface.js';
import { AiChatMessageEntity } from '../../entities/ai/ai-conversation-history/ai-chat-messages/ai-chat-message.entity.js';
import { IAiChatMessageRepository } from '../../entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.interface.js';

export interface IGlobalDatabaseContext extends IDatabaseContext {
userRepository: Repository<UserEntity> & IUserRepository;
Expand Down Expand Up @@ -104,4 +108,6 @@ export interface IGlobalDatabaseContext extends IDatabaseContext {
savedDbQueryRepository: Repository<SavedDbQueryEntity> & ISavedDbQueryRepository;
dashboardRepository: Repository<DashboardEntity> & IDashboardRepository;
dashboardWidgetRepository: Repository<DashboardWidgetEntity> & IDashboardWidgetRepository;
userAiChatRepository: Repository<UserAiChatEntity> & IUserAiChatRepository;
aiChatMessageRepository: Repository<AiChatMessageEntity> & IAiChatMessageRepository;
}
22 changes: 22 additions & 0 deletions backend/src/common/application/global-database-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,12 @@ import { IDashboardRepository } from '../../entities/visualizations/dashboard/re
import { IDashboardWidgetRepository } from '../../entities/visualizations/dashboard-widget/repository/dashboard-widget.repository.interface.js';
import { dashboardCustomRepositoryExtension } from '../../entities/visualizations/dashboard/repository/dashboard-custom-repository-extension.js';
import { dashboardWidgetCustomRepositoryExtension } from '../../entities/visualizations/dashboard-widget/repository/dashboard-widget-custom-repository-extension.js';
import { UserAiChatEntity } from '../../entities/ai/ai-conversation-history/user-ai-chat/user-ai-chat.entity.js';
import { IUserAiChatRepository } from '../../entities/ai/ai-conversation-history/user-ai-chat/repository/user-ai-chat-repository.interface.js';
import { userAiChatRepositoryExtension } from '../../entities/ai/ai-conversation-history/user-ai-chat/repository/user-ai-chat-repository.extension.js';
import { AiChatMessageEntity } from '../../entities/ai/ai-conversation-history/ai-chat-messages/ai-chat-message.entity.js';
import { IAiChatMessageRepository } from '../../entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.interface.js';
import { aiChatMessageRepositoryExtension } from '../../entities/ai/ai-conversation-history/ai-chat-messages/repository/ai-chat-message-repository.extension.js';

@Injectable({ scope: Scope.REQUEST })
export class GlobalDatabaseContext implements IGlobalDatabaseContext {
Expand Down Expand Up @@ -156,6 +162,8 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
private _savedDbQueryRepository: Repository<SavedDbQueryEntity> & ISavedDbQueryRepository;
private _dashboardRepository: Repository<DashboardEntity> & IDashboardRepository;
private _dashboardWidgetRepository: Repository<DashboardWidgetEntity> & IDashboardWidgetRepository;
private _userAiChatRepository: Repository<UserAiChatEntity> & IUserAiChatRepository;
private _aiChatMessageRepository: Repository<AiChatMessageEntity> & IAiChatMessageRepository;

public constructor(
@Inject(BaseType.DATA_SOURCE)
Expand Down Expand Up @@ -265,6 +273,12 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
this._dashboardWidgetRepository = this.appDataSource
.getRepository(DashboardWidgetEntity)
.extend(dashboardWidgetCustomRepositoryExtension);
this._userAiChatRepository = this.appDataSource
.getRepository(UserAiChatEntity)
.extend(userAiChatRepositoryExtension);
this._aiChatMessageRepository = this.appDataSource
.getRepository(AiChatMessageEntity)
.extend(aiChatMessageRepositoryExtension);
}

public get userRepository(): Repository<UserEntity> & IUserRepository {
Expand Down Expand Up @@ -429,6 +443,14 @@ export class GlobalDatabaseContext implements IGlobalDatabaseContext {
return this._dashboardWidgetRepository;
}

public get userAiChatRepository(): Repository<UserAiChatEntity> & IUserAiChatRepository {
return this._userAiChatRepository;
}

public get aiChatMessageRepository(): Repository<AiChatMessageEntity> & IAiChatMessageRepository {
return this._aiChatMessageRepository;
}

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 @@ -159,7 +159,11 @@ export enum UseCaseType {

REQUEST_INFO_FROM_TABLE_WITH_AI_V2 = 'REQUEST_INFO_FROM_TABLE_WITH_AI_V2',
REQUEST_INFO_FROM_TABLE_WITH_AI_V3 = 'REQUEST_INFO_FROM_TABLE_WITH_AI_V3',
REQUEST_INFO_FROM_TABLE_WITH_AI_V4 = 'REQUEST_INFO_FROM_TABLE_WITH_AI_V4',
REQUEST_AI_SETTINGS_AND_WIDGETS_CREATION = 'REQUEST_AI_SETTINGS_AND_WIDGETS_CREATION',
FIND_USER_AI_CHATS = 'FIND_USER_AI_CHATS',
FIND_USER_AI_CHAT_BY_ID = 'FIND_USER_AI_CHAT_BY_ID',
DELETE_USER_AI_CHAT = 'DELETE_USER_AI_CHAT',

CREATE_TABLE_FILTERS = 'CREATE_TABLE_FILTERS',
FIND_TABLE_FILTERS = 'FIND_TABLE_FILTERS',
Expand Down
6 changes: 4 additions & 2 deletions backend/src/decorators/slug-uuid.decorator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ export type SlugUuidParameter =
| 'apiKeyId'
| 'companyId'
| 'threadId'
| 'filterId';
| 'filterId'
| 'chatId';
export const SlugUuid = createParamDecorator(
(parameterName: SlugUuidParameter = 'slug', ctx: ExecutionContext): string => {
const request: IRequestWithCognitoInfo = ctx.switchToHttp().getRequest();
Expand All @@ -29,7 +30,8 @@ export const SlugUuid = createParamDecorator(
'eventId',
'companyId',
'threadId',
'filterId'
'filterId',
'chatId'
];
if (!availableSlagParameters.includes(parameterName)) {
throw new BadRequestException(Messages.UUID_INVALID);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import {
Column,
CreateDateColumn,
Entity,
JoinColumn,
ManyToOne,
PrimaryGeneratedColumn,
Relation,
UpdateDateColumn,
} from 'typeorm';
import { MessageRole } from './message-role.enum.js';
import { UserAiChatEntity } from '../user-ai-chat/user-ai-chat.entity.js';

@Entity('ai_chat_message')
export class AiChatMessageEntity {
@PrimaryGeneratedColumn('uuid')
id: string;

@Column({ default: null, type: 'text' })
message: string;

@Column({ nullable: true, default: null, type: 'enum', enum: MessageRole })
role: MessageRole;

@CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' })
created_at: Date;

@UpdateDateColumn({ type: 'timestamp', nullable: true, default: null })
updated_at: Date;

@ManyToOne(
() => UserAiChatEntity,
(ai_chat) => ai_chat.messages,

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ManyToOne relationship with UserAiChatEntity should specify the onDelete behavior to ensure messages are deleted when their parent chat is deleted. Add onDelete: 'CASCADE' to the decorator options to match the database migration and maintain referential integrity.

Suggested change
(ai_chat) => ai_chat.messages,
(ai_chat) => ai_chat.messages,
{ onDelete: 'CASCADE' },

Copilot uses AI. Check for mistakes.
)
@JoinColumn({ name: 'ai_chat_id' })
ai_chat: Relation<UserAiChatEntity>;

@Column()
ai_chat_id: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export enum MessageRole {
user = 'user',
ai = 'ai',
system = 'system',
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import { IAiChatMessageRepository } from './ai-chat-message-repository.interface.js';
import { AiChatMessageEntity } from '../ai-chat-message.entity.js';
import { MessageRole } from '../message-role.enum.js';

export const aiChatMessageRepositoryExtension: IAiChatMessageRepository = {
async findMessagesForChat(chatId: string): Promise<AiChatMessageEntity[]> {
return await this.createQueryBuilder('ai_chat_message')
.where('ai_chat_message.ai_chat_id = :chatId', { chatId })
.orderBy('ai_chat_message.created_at', 'ASC')
.getMany();
},

async deleteMessagesForChat(chatId: string): Promise<void> {
await this.createQueryBuilder()
.delete()
.from('ai_chat_message')
.where('ai_chat_id = :chatId', { chatId })
.execute();
},

async saveMessage(chatId: string, message: string, role: MessageRole): Promise<AiChatMessageEntity> {
const newMessage = this.create({
ai_chat_id: chatId,
message,
role,
});
return await this.save(newMessage);
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { AiChatMessageEntity } from '../ai-chat-message.entity.js';
import { MessageRole } from '../message-role.enum.js';

export interface IAiChatMessageRepository {
findMessagesForChat(chatId: string): Promise<AiChatMessageEntity[]>;
deleteMessagesForChat(chatId: string): Promise<void>;
saveMessage(chatId: string, message: string, role: MessageRole): Promise<AiChatMessageEntity>;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export class FindUserAiChatsDs {
userId: string;
}

export class FindUserAiChatByIdDs {
userId: string;
chatId: string;
}

export class DeleteUserAiChatDs {
userId: string;
chatId: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import { MessageRole } from '../../ai-chat-messages/message-role.enum.js';

export class AiChatMessageRO {
id: string;
message: string;
role: MessageRole;
created_at: Date;
}

export class UserAiChatRO {
id: string;
name: string;
created_at: Date;
updated_at: Date;
}

export class UserAiChatWithMessagesRO extends UserAiChatRO {
Comment on lines +1 to +17

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The response object classes are missing @ApiProperty decorators which are consistently used throughout the codebase for Swagger documentation. Looking at similar DTOs like FoundUserDto or FoundConnectionPropertiesDs, all properties should have @ApiProperty() decorators to generate proper API documentation. Add these decorators to all properties in AiChatMessageRO, UserAiChatRO, and UserAiChatWithMessagesRO classes.

Suggested change
import { MessageRole } from '../../ai-chat-messages/message-role.enum.js';
export class AiChatMessageRO {
id: string;
message: string;
role: MessageRole;
created_at: Date;
}
export class UserAiChatRO {
id: string;
name: string;
created_at: Date;
updated_at: Date;
}
export class UserAiChatWithMessagesRO extends UserAiChatRO {
import { MessageRole } from '../../ai-chat-messages/message-role.enum.js';
import { ApiProperty } from '@nestjs/swagger';
export class AiChatMessageRO {
@ApiProperty()
id: string;
@ApiProperty()
message: string;
@ApiProperty({ enum: MessageRole })
role: MessageRole;
@ApiProperty()
created_at: Date;
}
export class UserAiChatRO {
@ApiProperty()
id: string;
@ApiProperty()
name: string;
@ApiProperty()
created_at: Date;
@ApiProperty()
updated_at: Date;
}
export class UserAiChatWithMessagesRO extends UserAiChatRO {
@ApiProperty({ type: () => [AiChatMessageRO] })

Copilot uses AI. Check for mistakes.
messages: AiChatMessageRO[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { AiChatMessageEntity } from '../../ai-chat-messages/ai-chat-message.entity.js';
import { UserAiChatEntity } from '../../user-ai-chat/user-ai-chat.entity.js';
import { AiChatMessageRO, UserAiChatRO, UserAiChatWithMessagesRO } from '../response-objects/user-ai-chat.ro.js';

export function buildUserAiChatRO(chat: UserAiChatEntity): UserAiChatRO {
return {
id: chat.id,
name: chat.name,
created_at: chat.created_at,
updated_at: chat.updated_at,
};
}

export function buildAiChatMessageRO(message: AiChatMessageEntity): AiChatMessageRO {
return {
id: message.id,
message: message.message,
role: message.role,
created_at: message.created_at,
};
}

export function buildUserAiChatWithMessagesRO(chat: UserAiChatEntity): UserAiChatWithMessagesRO {
return {
id: chat.id,
name: chat.name,
created_at: chat.created_at,
updated_at: chat.updated_at,
messages: chat.messages?.map((message) => buildAiChatMessageRO(message)) ?? [],
};
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Inject, Injectable, NotFoundException, Scope } from '@nestjs/common';
import AbstractUseCase from '../../../../common/abstract-use.case.js';
import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js';
import { BaseType } from '../../../../common/data-injection.tokens.js';
import { Messages } from '../../../../exceptions/text/messages.js';
import { SuccessResponse } from '../../../../microservices/saas-microservice/data-structures/common-responce.ds.js';
import { DeleteUserAiChatDs } from '../application/data-structures/user-ai-chat.ds.js';
import { IDeleteUserAiChat } from './user-ai-chat-use-cases.interface.js';

@Injectable({ scope: Scope.REQUEST })
export class DeleteUserAiChatUseCase
extends AbstractUseCase<DeleteUserAiChatDs, SuccessResponse>
implements IDeleteUserAiChat
{
constructor(
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
) {
super();
}

protected async implementation(inputData: DeleteUserAiChatDs): Promise<SuccessResponse> {
const { userId, chatId } = inputData;
const foundChat = await this._dbContext.userAiChatRepository.findChatByIdAndUserId(chatId, userId);

if (!foundChat) {
throw new NotFoundException(Messages.AI_CHAT_NOT_FOUND);
}

await this._dbContext.aiChatMessageRepository.deleteMessagesForChat(chatId);

Copilot AI Jan 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The manual deletion of messages at line 30 is unnecessary if the database foreign key constraint is properly set to CASCADE. Once the migration is fixed to use ON DELETE CASCADE, this explicit deletion can be removed since the database will automatically delete related messages when the chat is deleted. This would simplify the code and ensure atomicity of the deletion operation.

Suggested change
await this._dbContext.aiChatMessageRepository.deleteMessagesForChat(chatId);

Copilot uses AI. Check for mistakes.
await this._dbContext.userAiChatRepository.remove(foundChat);

return { success: true };
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { Inject, Injectable, NotFoundException } from '@nestjs/common';
import AbstractUseCase from '../../../../common/abstract-use.case.js';
import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js';
import { BaseType } from '../../../../common/data-injection.tokens.js';
import { Messages } from '../../../../exceptions/text/messages.js';
import { FindUserAiChatByIdDs } from '../application/data-structures/user-ai-chat.ds.js';
import { UserAiChatWithMessagesRO } from '../application/response-objects/user-ai-chat.ro.js';
import { buildUserAiChatWithMessagesRO } from '../application/utils/build-user-ai-chat-ro.util.js';
import { IFindUserAiChatById } from './user-ai-chat-use-cases.interface.js';

@Injectable()
export class FindUserAiChatByIdUseCase
extends AbstractUseCase<FindUserAiChatByIdDs, UserAiChatWithMessagesRO>
implements IFindUserAiChatById
{
constructor(
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
) {
super();
}

protected async implementation(inputData: FindUserAiChatByIdDs): Promise<UserAiChatWithMessagesRO> {
const { userId, chatId } = inputData;
const foundChat = await this._dbContext.userAiChatRepository.findChatWithMessagesByIdAndUserId(chatId, userId);

if (!foundChat) {
throw new NotFoundException(Messages.AI_CHAT_NOT_FOUND);
}

return buildUserAiChatWithMessagesRO(foundChat);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Inject, Injectable } from '@nestjs/common';
import AbstractUseCase from '../../../../common/abstract-use.case.js';
import { IGlobalDatabaseContext } from '../../../../common/application/global-database-context.interface.js';
import { BaseType } from '../../../../common/data-injection.tokens.js';
import { FindUserAiChatsDs } from '../application/data-structures/user-ai-chat.ds.js';
import { UserAiChatRO } from '../application/response-objects/user-ai-chat.ro.js';
import { buildUserAiChatRO } from '../application/utils/build-user-ai-chat-ro.util.js';
import { IFindUserAiChats } from './user-ai-chat-use-cases.interface.js';

@Injectable()
export class FindUserAiChatsUseCase
extends AbstractUseCase<FindUserAiChatsDs, UserAiChatRO[]>
implements IFindUserAiChats
{
constructor(
@Inject(BaseType.GLOBAL_DB_CONTEXT)
protected _dbContext: IGlobalDatabaseContext,
) {
super();
}

protected async implementation(inputData: FindUserAiChatsDs): Promise<UserAiChatRO[]> {
const { userId } = inputData;
const foundChats = await this._dbContext.userAiChatRepository.findAllChatsForUser(userId);
return foundChats.map((chat) => buildUserAiChatRO(chat));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { InTransactionEnum } from '../../../../enums/in-transaction.enum.js';
import { SuccessResponse } from '../../../../microservices/saas-microservice/data-structures/common-responce.ds.js';
import {
DeleteUserAiChatDs,
FindUserAiChatByIdDs,
FindUserAiChatsDs,
} from '../application/data-structures/user-ai-chat.ds.js';
import { UserAiChatRO, UserAiChatWithMessagesRO } from '../application/response-objects/user-ai-chat.ro.js';

export interface IFindUserAiChats {
execute(inputData: FindUserAiChatsDs, inTransaction: InTransactionEnum): Promise<UserAiChatRO[]>;
}

export interface IFindUserAiChatById {
execute(inputData: FindUserAiChatByIdDs, inTransaction: InTransactionEnum): Promise<UserAiChatWithMessagesRO>;
}

export interface IDeleteUserAiChat {
execute(inputData: DeleteUserAiChatDs, inTransaction: InTransactionEnum): Promise<SuccessResponse>;
}
Loading
Loading