-
-
Notifications
You must be signed in to change notification settings - Fork 18
feat: implement schema change chat functionality with message persistence and thread management #1770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: implement schema change chat functionality with message persistence and thread management #1770
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -13,4 +13,12 @@ export class SchemaChangeBatchResponseDto { | |
| description: 'Generated changes ordered by orderInBatch (dependency order — parents first).', | ||
| }) | ||
| changes: SchemaChangeResponseDto[]; | ||
|
|
||
| @ApiProperty({ | ||
| required: false, | ||
| nullable: true, | ||
| description: | ||
| 'Conversation thread ID. Present when the request used or created a chat thread. Pass it back as the threadId query param on subsequent generate calls to continue the conversation with full prior context.', | ||
|
|
||
| }) | ||
|
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix response docs: The description currently instructs clients to send 🤖 Prompt for AI Agents |
||
| threadId?: string | null; | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| import { MessageRole } from '../../../../ai/ai-conversation-history/ai-chat-messages/message-role.enum.js'; | ||
| import { SchemaChangeChatMessageEntity } from '../schema-change-chat-message.entity.js'; | ||
| import { ISchemaChangeChatMessageRepository } from './schema-change-chat-message-repository.interface.js'; | ||
|
|
||
| export const schemaChangeChatMessageRepositoryExtension: ISchemaChangeChatMessageRepository = { | ||
| async findMessagesForChat(chatId: string): Promise<SchemaChangeChatMessageEntity[]> { | ||
| return await this.createQueryBuilder('schema_change_chat_message') | ||
| .where('schema_change_chat_message.chat_id = :chatId', { chatId }) | ||
| .orderBy('schema_change_chat_message.created_at', 'ASC') | ||
| .getMany(); | ||
| }, | ||
|
|
||
| async deleteMessagesForChat(chatId: string): Promise<void> { | ||
| await this.createQueryBuilder() | ||
| .delete() | ||
| .from('schema_change_chat_message') | ||
| .where('chat_id = :chatId', { chatId }) | ||
| .execute(); | ||
| }, | ||
|
|
||
| async saveMessage( | ||
| chatId: string, | ||
| message: string, | ||
| role: MessageRole, | ||
| batchId?: string | null, | ||
| ): Promise<SchemaChangeChatMessageEntity> { | ||
| const newMessage = this.create({ | ||
| chat_id: chatId, | ||
| message, | ||
| role, | ||
| batch_id: batchId ?? null, | ||
| }); | ||
| return await this.save(newMessage); | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| import { MessageRole } from '../../../../ai/ai-conversation-history/ai-chat-messages/message-role.enum.js'; | ||
| import { SchemaChangeChatMessageEntity } from '../schema-change-chat-message.entity.js'; | ||
|
|
||
| export interface ISchemaChangeChatMessageRepository { | ||
| findMessagesForChat(chatId: string): Promise<SchemaChangeChatMessageEntity[]>; | ||
| deleteMessagesForChat(chatId: string): Promise<void>; | ||
| saveMessage( | ||
| chatId: string, | ||
| message: string, | ||
| role: MessageRole, | ||
| batchId?: string | null, | ||
| ): Promise<SchemaChangeChatMessageEntity>; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,44 @@ | ||||||||||
| import { | ||||||||||
| Column, | ||||||||||
| CreateDateColumn, | ||||||||||
| Entity, | ||||||||||
| JoinColumn, | ||||||||||
| ManyToOne, | ||||||||||
| PrimaryGeneratedColumn, | ||||||||||
| Relation, | ||||||||||
| UpdateDateColumn, | ||||||||||
| } from 'typeorm'; | ||||||||||
| import { MessageRole } from '../../../ai/ai-conversation-history/ai-chat-messages/message-role.enum.js'; | ||||||||||
| import { SchemaChangeChatEntity } from '../schema-change-chat/schema-change-chat.entity.js'; | ||||||||||
|
|
||||||||||
| @Entity('schema_change_chat_message') | ||||||||||
| export class SchemaChangeChatMessageEntity { | ||||||||||
| @PrimaryGeneratedColumn('uuid') | ||||||||||
| id: string; | ||||||||||
|
|
||||||||||
| @Column({ default: null, type: 'text' }) | ||||||||||
| message: string; | ||||||||||
|
|
||||||||||
| @Column({ nullable: true, default: null, type: 'enum', enum: MessageRole }) | ||||||||||
| role: MessageRole; | ||||||||||
|
Comment on lines
+22
to
+23
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Enforce non-null
Proposed fix- `@Column`({ nullable: true, default: null, type: 'enum', enum: MessageRole })
+ `@Column`({ type: 'enum', enum: MessageRole })
role: MessageRole;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| @Column({ type: 'uuid', nullable: true, default: null }) | ||||||||||
| batch_id: string | null; | ||||||||||
|
|
||||||||||
| @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) | ||||||||||
| created_at: Date; | ||||||||||
|
|
||||||||||
| @UpdateDateColumn({ type: 'timestamp', nullable: true, default: null }) | ||||||||||
| updated_at: Date; | ||||||||||
|
|
||||||||||
| @ManyToOne( | ||||||||||
| () => SchemaChangeChatEntity, | ||||||||||
| (chat) => chat.messages, | ||||||||||
| { onDelete: 'CASCADE' }, | ||||||||||
| ) | ||||||||||
| @JoinColumn({ name: 'chat_id' }) | ||||||||||
| chat: Relation<SchemaChangeChatEntity>; | ||||||||||
|
|
||||||||||
| @Column() | ||||||||||
| chat_id: string; | ||||||||||
| } | ||||||||||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| import { SchemaChangeChatEntity } from '../schema-change-chat.entity.js'; | ||
| import { ISchemaChangeChatRepository } from './schema-change-chat-repository.interface.js'; | ||
|
|
||
| export const schemaChangeChatRepositoryExtension: ISchemaChangeChatRepository = { | ||
| async findChatByIdAndUserId(chatId: string, userId: string): Promise<SchemaChangeChatEntity | null> { | ||
| return await this.createQueryBuilder('schema_change_chat') | ||
| .where('schema_change_chat.id = :chatId', { chatId }) | ||
| .andWhere('schema_change_chat.user_id = :userId', { userId }) | ||
| .getOne(); | ||
| }, | ||
|
|
||
| async findChatWithMessagesByIdAndUserId(chatId: string, userId: string): Promise<SchemaChangeChatEntity | null> { | ||
| return await this.createQueryBuilder('schema_change_chat') | ||
| .leftJoinAndSelect('schema_change_chat.messages', 'messages') | ||
| .where('schema_change_chat.id = :chatId', { chatId }) | ||
| .andWhere('schema_change_chat.user_id = :userId', { userId }) | ||
| .orderBy('messages.created_at', 'ASC') | ||
| .getOne(); | ||
| }, | ||
|
|
||
| async findChatsForConnection(connectionId: string, userId: string): Promise<SchemaChangeChatEntity[]> { | ||
| return await this.createQueryBuilder('schema_change_chat') | ||
| .where('schema_change_chat.connection_id = :connectionId', { connectionId }) | ||
| .andWhere('schema_change_chat.user_id = :userId', { userId }) | ||
| .orderBy('schema_change_chat.created_at', 'DESC') | ||
| .getMany(); | ||
| }, | ||
|
|
||
| async createChatForUser(userId: string, connectionId: string, name?: string): Promise<SchemaChangeChatEntity> { | ||
| const newChat = this.create({ | ||
| user_id: userId, | ||
| connection_id: connectionId, | ||
| name: name || null, | ||
| }); | ||
| return await this.save(newChat); | ||
| }, | ||
|
|
||
| async updateChatName(chatId: string, name: string): Promise<void> { | ||
| await this.createQueryBuilder() | ||
| .update(SchemaChangeChatEntity) | ||
| .set({ name }) | ||
| .where('id = :chatId', { chatId }) | ||
| .execute(); | ||
| }, | ||
|
|
||
| async updateLastBatchId(chatId: string, batchId: string): Promise<void> { | ||
| await this.createQueryBuilder() | ||
| .update(SchemaChangeChatEntity) | ||
| .set({ last_batch_id: batchId }) | ||
| .where('id = :chatId', { chatId }) | ||
| .execute(); | ||
| }, | ||
| }; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| import { SchemaChangeChatEntity } from '../schema-change-chat.entity.js'; | ||
|
|
||
| export interface ISchemaChangeChatRepository { | ||
| findChatByIdAndUserId(chatId: string, userId: string): Promise<SchemaChangeChatEntity | null>; | ||
| findChatWithMessagesByIdAndUserId(chatId: string, userId: string): Promise<SchemaChangeChatEntity | null>; | ||
| findChatsForConnection(connectionId: string, userId: string): Promise<SchemaChangeChatEntity[]>; | ||
| createChatForUser(userId: string, connectionId: string, name?: string): Promise<SchemaChangeChatEntity>; | ||
| updateChatName(chatId: string, name: string): Promise<void>; | ||
| updateLastBatchId(chatId: string, batchId: string): Promise<void>; | ||
| } |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,52 @@ | ||||||||||
| import { | ||||||||||
| Column, | ||||||||||
| CreateDateColumn, | ||||||||||
| Entity, | ||||||||||
| JoinColumn, | ||||||||||
| ManyToOne, | ||||||||||
| OneToMany, | ||||||||||
| PrimaryGeneratedColumn, | ||||||||||
| Relation, | ||||||||||
| UpdateDateColumn, | ||||||||||
| } from 'typeorm'; | ||||||||||
| import { ConnectionEntity } from '../../../connection/connection.entity.js'; | ||||||||||
| import { UserEntity } from '../../../user/user.entity.js'; | ||||||||||
| import { SchemaChangeChatMessageEntity } from '../schema-change-chat-message/schema-change-chat-message.entity.js'; | ||||||||||
|
|
||||||||||
| @Entity('schema_change_chat') | ||||||||||
| export class SchemaChangeChatEntity { | ||||||||||
| @PrimaryGeneratedColumn('uuid') | ||||||||||
| id: string; | ||||||||||
|
|
||||||||||
| @Column({ default: null }) | ||||||||||
| name: string; | ||||||||||
|
Comment on lines
+21
to
+22
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Proposed fix- `@Column`({ default: null })
- name: string;
+ `@Column`({ nullable: true, default: null })
+ name: string | null;📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||
|
|
||||||||||
| @CreateDateColumn({ type: 'timestamp', default: () => 'CURRENT_TIMESTAMP' }) | ||||||||||
| created_at: Date; | ||||||||||
|
|
||||||||||
| @UpdateDateColumn({ type: 'timestamp', nullable: true, default: null }) | ||||||||||
| updated_at: Date; | ||||||||||
|
|
||||||||||
| @ManyToOne(() => UserEntity, { onDelete: 'CASCADE' }) | ||||||||||
| @JoinColumn({ name: 'user_id' }) | ||||||||||
| user: Relation<UserEntity>; | ||||||||||
|
|
||||||||||
| @Column() | ||||||||||
| user_id: string; | ||||||||||
|
|
||||||||||
| @ManyToOne(() => ConnectionEntity, { onDelete: 'CASCADE' }) | ||||||||||
| @JoinColumn({ name: 'connection_id' }) | ||||||||||
| connection: Relation<ConnectionEntity>; | ||||||||||
|
|
||||||||||
| @Column({ type: 'varchar', length: 38 }) | ||||||||||
| connection_id: string; | ||||||||||
|
|
||||||||||
| @Column({ type: 'uuid', nullable: true, default: null }) | ||||||||||
| last_batch_id: string | null; | ||||||||||
|
|
||||||||||
| @OneToMany( | ||||||||||
| () => SchemaChangeChatMessageEntity, | ||||||||||
| (message) => message.chat, | ||||||||||
| ) | ||||||||||
| messages: Relation<SchemaChangeChatMessageEntity>[]; | ||||||||||
| } | ||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Align nullable API contract with TypeScript type.
@ApiPropertymarksthreadIdas nullable, but the property type excludesnull. This creates a type-level mismatch for consumers and internal mappings.Proposed fix
📝 Committable suggestion
🤖 Prompt for AI Agents