@fr-data-fabric/chatbot-api-nest 0.0.1

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.
Files changed (120) hide show
  1. package/dist/chatbot-api.module.d.ts +27 -0
  2. package/dist/chatbot-api.module.js +63 -0
  3. package/dist/chatbot-api.module.js.map +1 -0
  4. package/dist/domain/entities/ChatEntity.d.ts +11 -0
  5. package/dist/domain/entities/ChatEntity.js +3 -0
  6. package/dist/domain/entities/ChatEntity.js.map +1 -0
  7. package/dist/domain/entities/ChatSourceEntity.d.ts +6 -0
  8. package/dist/domain/entities/ChatSourceEntity.js +3 -0
  9. package/dist/domain/entities/ChatSourceEntity.js.map +1 -0
  10. package/dist/domain/entities/ChatTextContentEntity.d.ts +4 -0
  11. package/dist/domain/entities/ChatTextContentEntity.js +3 -0
  12. package/dist/domain/entities/ChatTextContentEntity.js.map +1 -0
  13. package/dist/domain/entities/ConversationEntity.d.ts +9 -0
  14. package/dist/domain/entities/ConversationEntity.js +3 -0
  15. package/dist/domain/entities/ConversationEntity.js.map +1 -0
  16. package/dist/domain/entities/DocumentEntity.d.ts +3 -0
  17. package/dist/domain/entities/DocumentEntity.js +3 -0
  18. package/dist/domain/entities/DocumentEntity.js.map +1 -0
  19. package/dist/domain/entities/ToolUseEntity.d.ts +6 -0
  20. package/dist/domain/entities/ToolUseEntity.js +3 -0
  21. package/dist/domain/entities/ToolUseEntity.js.map +1 -0
  22. package/dist/domain/entities/UserEntity.d.ts +3 -0
  23. package/dist/domain/entities/UserEntity.js +3 -0
  24. package/dist/domain/entities/UserEntity.js.map +1 -0
  25. package/dist/domain/interfaces/ChatRepository.d.ts +5 -0
  26. package/dist/domain/interfaces/ChatRepository.js +5 -0
  27. package/dist/domain/interfaces/ChatRepository.js.map +1 -0
  28. package/dist/domain/interfaces/ConversationRepository.d.ts +6 -0
  29. package/dist/domain/interfaces/ConversationRepository.js +5 -0
  30. package/dist/domain/interfaces/ConversationRepository.js.map +1 -0
  31. package/dist/domain/interfaces/LLMService.d.ts +13 -0
  32. package/dist/domain/interfaces/LLMService.js +5 -0
  33. package/dist/domain/interfaces/LLMService.js.map +1 -0
  34. package/dist/domain/interfaces/PromptService.d.ts +8 -0
  35. package/dist/domain/interfaces/PromptService.js +5 -0
  36. package/dist/domain/interfaces/PromptService.js.map +1 -0
  37. package/dist/domain/use-cases/_common.d.ts +17 -0
  38. package/dist/domain/use-cases/_common.js +41 -0
  39. package/dist/domain/use-cases/_common.js.map +1 -0
  40. package/dist/domain/use-cases/chats/GetChatCompletionUC.d.ts +28 -0
  41. package/dist/domain/use-cases/chats/GetChatCompletionUC.js +83 -0
  42. package/dist/domain/use-cases/chats/GetChatCompletionUC.js.map +1 -0
  43. package/dist/domain/use-cases/chats/SendChatUC.d.ts +35 -0
  44. package/dist/domain/use-cases/chats/SendChatUC.js +166 -0
  45. package/dist/domain/use-cases/chats/SendChatUC.js.map +1 -0
  46. package/dist/domain/use-cases/chats/chat.types.d.ts +54 -0
  47. package/dist/domain/use-cases/chats/chat.types.js +3 -0
  48. package/dist/domain/use-cases/chats/chat.types.js.map +1 -0
  49. package/dist/domain/use-cases/conversations/CreateConversationUC.d.ts +6 -0
  50. package/dist/domain/use-cases/conversations/CreateConversationUC.js +5 -0
  51. package/dist/domain/use-cases/conversations/CreateConversationUC.js.map +1 -0
  52. package/dist/domain/use-cases/conversations/DeleteConversationUC.d.ts +10 -0
  53. package/dist/domain/use-cases/conversations/DeleteConversationUC.js +41 -0
  54. package/dist/domain/use-cases/conversations/DeleteConversationUC.js.map +1 -0
  55. package/dist/domain/use-cases/conversations/GenerateConversationTitleUC.d.ts +18 -0
  56. package/dist/domain/use-cases/conversations/GenerateConversationTitleUC.js +80 -0
  57. package/dist/domain/use-cases/conversations/GenerateConversationTitleUC.js.map +1 -0
  58. package/dist/domain/use-cases/conversations/GetConversationUC.d.ts +10 -0
  59. package/dist/domain/use-cases/conversations/GetConversationUC.js +31 -0
  60. package/dist/domain/use-cases/conversations/GetConversationUC.js.map +1 -0
  61. package/dist/domain/use-cases/conversations/GetConversationsUC.d.ts +9 -0
  62. package/dist/domain/use-cases/conversations/GetConversationsUC.js +27 -0
  63. package/dist/domain/use-cases/conversations/GetConversationsUC.js.map +1 -0
  64. package/dist/domain/use-cases/conversations/UpdateConversationUC.d.ts +15 -0
  65. package/dist/domain/use-cases/conversations/UpdateConversationUC.js +74 -0
  66. package/dist/domain/use-cases/conversations/UpdateConversationUC.js.map +1 -0
  67. package/dist/index.d.ts +1 -0
  68. package/dist/index.js +18 -0
  69. package/dist/index.js.map +1 -0
  70. package/dist/inputs/controllers/ChatsController.d.ts +8 -0
  71. package/dist/inputs/controllers/ChatsController.js +62 -0
  72. package/dist/inputs/controllers/ChatsController.js.map +1 -0
  73. package/dist/inputs/controllers/_common/CurrentUserRest.d.ts +1 -0
  74. package/dist/inputs/controllers/_common/CurrentUserRest.js +9 -0
  75. package/dist/inputs/controllers/_common/CurrentUserRest.js.map +1 -0
  76. package/dist/inputs/resolvers/ChatsResolver.d.ts +9 -0
  77. package/dist/inputs/resolvers/ChatsResolver.js +57 -0
  78. package/dist/inputs/resolvers/ChatsResolver.js.map +1 -0
  79. package/dist/inputs/resolvers/ConversationsResolver.d.ts +34 -0
  80. package/dist/inputs/resolvers/ConversationsResolver.js +170 -0
  81. package/dist/inputs/resolvers/ConversationsResolver.js.map +1 -0
  82. package/dist/inputs/resolvers/_common/CurrentUserGql.d.ts +1 -0
  83. package/dist/inputs/resolvers/_common/CurrentUserGql.js +10 -0
  84. package/dist/inputs/resolvers/_common/CurrentUserGql.js.map +1 -0
  85. package/dist/inputs/resolvers/_common/GqlPaginatedOutput.d.ts +8 -0
  86. package/dist/inputs/resolvers/_common/GqlPaginatedOutput.js +37 -0
  87. package/dist/inputs/resolvers/_common/GqlPaginatedOutput.js.map +1 -0
  88. package/dist/tsconfig.tsbuildinfo +1 -0
  89. package/docs/entities.excalidraw.svg +2 -0
  90. package/package.json +25 -0
  91. package/src/chatbot-api.module.ts +90 -0
  92. package/src/domain/entities/ChatEntity.ts +13 -0
  93. package/src/domain/entities/ChatSourceEntity.ts +6 -0
  94. package/src/domain/entities/ChatTextContentEntity.ts +4 -0
  95. package/src/domain/entities/ConversationEntity.ts +10 -0
  96. package/src/domain/entities/DocumentEntity.ts +3 -0
  97. package/src/domain/entities/ToolUseEntity.ts +6 -0
  98. package/src/domain/entities/UserEntity.ts +3 -0
  99. package/src/domain/interfaces/ChatRepository.ts +7 -0
  100. package/src/domain/interfaces/ConversationRepository.ts +8 -0
  101. package/src/domain/interfaces/LLMService.ts +15 -0
  102. package/src/domain/interfaces/PromptService.ts +10 -0
  103. package/src/domain/use-cases/_common.ts +28 -0
  104. package/src/domain/use-cases/chats/GetChatCompletionUC.ts +126 -0
  105. package/src/domain/use-cases/chats/SendChatUC.ts +202 -0
  106. package/src/domain/use-cases/chats/chat.types.ts +51 -0
  107. package/src/domain/use-cases/conversations/CreateConversationUC.ts +7 -0
  108. package/src/domain/use-cases/conversations/DeleteConversationUC.ts +29 -0
  109. package/src/domain/use-cases/conversations/GenerateConversationTitleUC.ts +69 -0
  110. package/src/domain/use-cases/conversations/GetConversationUC.ts +18 -0
  111. package/src/domain/use-cases/conversations/GetConversationsUC.ts +21 -0
  112. package/src/domain/use-cases/conversations/UpdateConversationUC.ts +54 -0
  113. package/src/index.ts +1 -0
  114. package/src/inputs/controllers/ChatsController.ts +47 -0
  115. package/src/inputs/controllers/_common/CurrentUserRest.ts +10 -0
  116. package/src/inputs/resolvers/ChatsResolver.ts +40 -0
  117. package/src/inputs/resolvers/ConversationsResolver.ts +149 -0
  118. package/src/inputs/resolvers/_common/CurrentUserGql.ts +11 -0
  119. package/src/inputs/resolvers/_common/GqlPaginatedOutput.ts +25 -0
  120. package/tsconfig.json +28 -0
@@ -0,0 +1,51 @@
1
+ import { UserEntity } from '@domain/entities/UserEntity';
2
+ import { DynamicStructuredTool } from 'langchain';
3
+
4
+ export type AvailableTool = {
5
+ name: string;
6
+ options: Record<string, unknown>;
7
+ };
8
+
9
+ export type ToolNames<AvailableTools extends AvailableTool[]> =
10
+ AvailableTools[number]['name'];
11
+
12
+ export type StreamEvents<AvailableTools extends AvailableTool[]> =
13
+ | { event: 'chat_stream'; content: string }
14
+ | { event: 'sources_add'; sources: Source[] }
15
+ | { event: 'tool_stream_start'; name: ToolNames<AvailableTools>; id: string }
16
+ | {
17
+ event: 'tool_start';
18
+ name: ToolNames<AvailableTools>;
19
+ id: string;
20
+ input: unknown;
21
+ }
22
+ | { event: 'tool_stream'; id: string; input: string }
23
+ | {
24
+ event: 'tool_end';
25
+ name: ToolNames<AvailableTools>;
26
+ input: string;
27
+ output: unknown;
28
+ id: string;
29
+ };
30
+
31
+ export type ChatStreamEvents<AvailableTools extends AvailableTool[]> =
32
+ | StreamEvents<AvailableTools>
33
+ | { event: 'user_message_id'; id: string }
34
+ | { event: 'assistant_message_id'; id: string };
35
+
36
+ export type Source = {
37
+ title: string;
38
+ link: string;
39
+ content: string;
40
+ index: number;
41
+ };
42
+
43
+ export type ToolOptions = {
44
+ getSourceIndex: () => number;
45
+ addSources: (sources: Source[]) => void;
46
+ currentUser: UserEntity;
47
+ };
48
+
49
+ export interface ToolClass {
50
+ getTool(options: ToolOptions): DynamicStructuredTool;
51
+ }
@@ -0,0 +1,7 @@
1
+ import { ConversationEntity } from '@domain/entities/ConversationEntity';
2
+ import { UCInput } from '@domain/use-cases/_common';
3
+
4
+ export const CREATE_CONVERSATION_UC = 'CreateConversationUC';
5
+ export interface ICreateConversationUC {
6
+ execute(input: UCInput<undefined>): Promise<ConversationEntity>;
7
+ }
@@ -0,0 +1,29 @@
1
+ import { ConversationEntity } from '@domain/entities/ConversationEntity';
2
+ import type { ConversationRepository } from '@domain/interfaces/ConversationRepository';
3
+ import { CONVERSATION_REPOSITORY } from '@domain/interfaces/ConversationRepository';
4
+ import { DeleteByIdInput, UCInput } from '@domain/use-cases/_common';
5
+ import type { IGetConversationUC } from '@domain/use-cases/conversations/GetConversationUC';
6
+ import { GET_CONVERSATION_UC } from '@domain/use-cases/conversations/GetConversationUC';
7
+ import { Inject, Injectable } from '@nestjs/common';
8
+
9
+ @Injectable()
10
+ export class DeleteConversationUC {
11
+ constructor(
12
+ @Inject(CONVERSATION_REPOSITORY)
13
+ private readonly conversationRepository: ConversationRepository,
14
+ @Inject(GET_CONVERSATION_UC)
15
+ private readonly getConversationUC: IGetConversationUC,
16
+ ) {}
17
+
18
+ async execute({
19
+ input,
20
+ currentUser,
21
+ }: UCInput<DeleteByIdInput>): Promise<ConversationEntity> {
22
+ const conversation = await this.getConversationUC.execute({
23
+ input: { id: input.id },
24
+ currentUser,
25
+ });
26
+
27
+ return await this.conversationRepository.remove(conversation);
28
+ }
29
+ }
@@ -0,0 +1,69 @@
1
+ import { ConversationEntity } from '@domain/entities/ConversationEntity';
2
+ import type { ConversationRepository } from '@domain/interfaces/ConversationRepository';
3
+ import { CONVERSATION_REPOSITORY } from '@domain/interfaces/ConversationRepository';
4
+ import type { LLMService } from '@domain/interfaces/LLMService';
5
+ import { LLM_SERVICE } from '@domain/interfaces/LLMService';
6
+ import type { PromptService } from '@domain/interfaces/PromptService';
7
+ import { PROMPT_SERVICE } from '@domain/interfaces/PromptService';
8
+ import { UCInput } from '@domain/use-cases/_common';
9
+ import type { IGetConversationUC } from '@domain/use-cases/conversations/GetConversationUC';
10
+ import { GET_CONVERSATION_UC } from '@domain/use-cases/conversations/GetConversationUC';
11
+ import { Inject, Injectable } from '@nestjs/common';
12
+ import { Field, ID, InputType } from '@nestjs/graphql';
13
+
14
+ @InputType()
15
+ export class GenerateConversationTitleInput {
16
+ @Field(() => ID)
17
+ conversationId!: string;
18
+
19
+ @Field(() => String)
20
+ firstMessage!: string;
21
+ }
22
+
23
+ @Injectable()
24
+ export class GenerateConversationTitleUC {
25
+ constructor(
26
+ @Inject(LLM_SERVICE)
27
+ private readonly llmService: LLMService,
28
+ @Inject(PROMPT_SERVICE)
29
+ private readonly promptService: PromptService,
30
+ @Inject(GET_CONVERSATION_UC)
31
+ private readonly getConversationUC: IGetConversationUC,
32
+ @Inject(CONVERSATION_REPOSITORY)
33
+ private readonly conversationRepository: ConversationRepository,
34
+ ) {}
35
+
36
+ async execute({
37
+ input,
38
+ currentUser,
39
+ }: UCInput<GenerateConversationTitleInput>): Promise<ConversationEntity> {
40
+ const conversation = await this.getConversationUC.execute({
41
+ currentUser,
42
+ input: { id: input.conversationId },
43
+ });
44
+
45
+ const model = this.llmService.getLLM({
46
+ model: 'gpt-5-nano', // TODO: make this configurable
47
+ provider: 'openai',
48
+ temperature: 0.1,
49
+ streaming: false,
50
+ });
51
+
52
+ const result = await model.invoke(
53
+ await this.promptService.getPrompt(
54
+ {
55
+ name: 'generate_conversation_title',
56
+ version: 'latest',
57
+ },
58
+ {
59
+ first_message: input.firstMessage,
60
+ },
61
+ ),
62
+ );
63
+
64
+ conversation.title = result.text;
65
+ await this.conversationRepository.save(conversation);
66
+
67
+ return conversation;
68
+ }
69
+ }
@@ -0,0 +1,18 @@
1
+ import { ConversationEntity } from '@domain/entities/ConversationEntity';
2
+ import { UCInput } from '@domain/use-cases/_common';
3
+ import { Field, ID, InputType } from '@nestjs/graphql';
4
+
5
+ @InputType()
6
+ export class GetConversationInput {
7
+ @Field(() => ID, { nullable: true })
8
+ id?: string;
9
+
10
+ @Field(() => String, { nullable: true })
11
+ shareId?: string;
12
+ }
13
+
14
+ export const GET_CONVERSATION_UC = 'GetConversationUC';
15
+
16
+ export interface IGetConversationUC {
17
+ execute(input: UCInput<GetConversationInput>): Promise<ConversationEntity>;
18
+ }
@@ -0,0 +1,21 @@
1
+ import { ConversationEntity } from '@domain/entities/ConversationEntity';
2
+ import {
3
+ PaginatedInput,
4
+ PaginatedOutput,
5
+ UCInput,
6
+ } from '@domain/use-cases/_common';
7
+ import { Field, InputType } from '@nestjs/graphql';
8
+
9
+ @InputType()
10
+ export class GetConversationsInput extends PaginatedInput {
11
+ @Field(() => String, { nullable: true })
12
+ title?: string;
13
+ }
14
+
15
+ export const GET_CONVERSATIONS_UC = 'GetConversationsUC';
16
+
17
+ export interface IGetConversationsUC {
18
+ execute(
19
+ input: UCInput<GetConversationsInput>,
20
+ ): Promise<PaginatedOutput<ConversationEntity>>;
21
+ }
@@ -0,0 +1,54 @@
1
+ import { ConversationEntity } from '@domain/entities/ConversationEntity';
2
+ import type { ConversationRepository } from '@domain/interfaces/ConversationRepository';
3
+ import { CONVERSATION_REPOSITORY } from '@domain/interfaces/ConversationRepository';
4
+ import { UCInput } from '@domain/use-cases/_common';
5
+ import type { IGetConversationUC } from '@domain/use-cases/conversations/GetConversationUC';
6
+ import { GET_CONVERSATION_UC } from '@domain/use-cases/conversations/GetConversationUC';
7
+ import { Inject, Injectable } from '@nestjs/common';
8
+ import { Field, ID, InputType } from '@nestjs/graphql';
9
+
10
+ @InputType()
11
+ export class UpdateConversationInput {
12
+ @Field(() => ID)
13
+ id!: string;
14
+
15
+ @Field({ nullable: true })
16
+ title?: string;
17
+
18
+ @Field({ nullable: true })
19
+ shared?: boolean;
20
+ }
21
+
22
+ @Injectable()
23
+ export class UpdateConversationUC {
24
+ constructor(
25
+ @Inject(CONVERSATION_REPOSITORY)
26
+ private readonly conversationRepository: ConversationRepository,
27
+ @Inject(GET_CONVERSATION_UC)
28
+ private readonly getConversationUC: IGetConversationUC,
29
+ ) {}
30
+
31
+ async execute({
32
+ input,
33
+ currentUser,
34
+ }: UCInput<UpdateConversationInput>): Promise<ConversationEntity> {
35
+ const conversation = await this.getConversationUC.execute({
36
+ input: { id: input.id },
37
+ currentUser,
38
+ });
39
+
40
+ if (typeof input.title === 'string') {
41
+ conversation.title = input.title;
42
+ }
43
+
44
+ if (typeof input.shared === 'boolean') {
45
+ if (input.shared) {
46
+ conversation.shareId = crypto.randomUUID();
47
+ } else {
48
+ conversation.shareId = null;
49
+ }
50
+ }
51
+
52
+ return this.conversationRepository.save(conversation);
53
+ }
54
+ }
package/src/index.ts ADDED
@@ -0,0 +1 @@
1
+ export * from './chatbot-api.module';
@@ -0,0 +1,47 @@
1
+ import type { UserEntity } from '@domain/entities/UserEntity';
2
+ import {
3
+ SEND_CHAT_UC,
4
+ SendChatUC,
5
+ type SendMessageInput,
6
+ } from '@domain/use-cases/chats/SendChatUC';
7
+ import { Body, Controller, Inject, Post, Res } from '@nestjs/common';
8
+ import type { Response } from 'express';
9
+ import { CurrentUserRest } from 'src/inputs/controllers/_common/CurrentUserRest';
10
+
11
+ @Controller('chat')
12
+ export class ChatsController {
13
+ constructor(
14
+ @Inject(SEND_CHAT_UC)
15
+ private readonly sendChatUC: SendChatUC<never>,
16
+ ) {}
17
+
18
+ @Post('/')
19
+ async sendMessage(
20
+ @Res() res: Response,
21
+ @Body() input: SendMessageInput<never>,
22
+ @CurrentUserRest() currentUser: UserEntity,
23
+ ) {
24
+ res.writeHead(200, {
25
+ 'Content-Type': 'text/event-stream',
26
+ 'Cache-Control': 'no-cache',
27
+ Connection: 'keep-alive',
28
+ });
29
+
30
+ const stream = await this.sendChatUC.execute({
31
+ input,
32
+ currentUser,
33
+ });
34
+
35
+ stream.subscribe({
36
+ next: (event) => {
37
+ res.write(JSON.stringify(event));
38
+ },
39
+ complete: () => {
40
+ res.end();
41
+ },
42
+ error: () => {
43
+ res.end();
44
+ },
45
+ });
46
+ }
47
+ }
@@ -0,0 +1,10 @@
1
+ import { UserEntity } from '@domain/entities/UserEntity';
2
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
3
+
4
+ export const CurrentUserRest = createParamDecorator<unknown, UserEntity>(
5
+ (data: unknown, context: ExecutionContext) => {
6
+ const ctx = context.switchToHttp();
7
+
8
+ return ctx.getRequest<Request & { user: UserEntity }>().user;
9
+ },
10
+ );
@@ -0,0 +1,40 @@
1
+ import type { ChatEntity } from '@domain/entities/ChatEntity';
2
+ import {
3
+ GqlTypeReference,
4
+ Parent,
5
+ ResolveField,
6
+ Resolver,
7
+ } from '@nestjs/graphql';
8
+
9
+ export function GetChatsResolver(
10
+ GqlChat: GqlTypeReference,
11
+ GqlChatSource: GqlTypeReference,
12
+ GqlChatTextContent: GqlTypeReference,
13
+ GqlToolUse: GqlTypeReference,
14
+ ) {
15
+ @Resolver(() => GqlChat)
16
+ class ChatsResolver {
17
+ constructor() {}
18
+
19
+ @ResolveField(() => [GqlToolUse])
20
+ async tools(@Parent() chat: ChatEntity): Promise<(typeof GqlToolUse)[]> {
21
+ return (await chat._toolUses).sort((a, b) => a.order - b.order);
22
+ }
23
+
24
+ @ResolveField(() => [GqlChatSource])
25
+ async sources(
26
+ @Parent() chat: ChatEntity,
27
+ ): Promise<(typeof GqlChatSource)[]> {
28
+ return await chat._sources;
29
+ }
30
+
31
+ @ResolveField(() => [GqlChatTextContent])
32
+ async textContents(
33
+ @Parent() chat: ChatEntity,
34
+ ): Promise<(typeof GqlChatTextContent)[]> {
35
+ return await chat._textContents;
36
+ }
37
+ }
38
+
39
+ return ChatsResolver;
40
+ }
@@ -0,0 +1,149 @@
1
+ import type { ConversationEntity } from '@domain/entities/ConversationEntity';
2
+ import type { UserEntity } from '@domain/entities/UserEntity';
3
+ import { DeleteByIdInput } from '@domain/use-cases/_common';
4
+ import type { ICreateConversationUC } from '@domain/use-cases/conversations/CreateConversationUC';
5
+ import { CREATE_CONVERSATION_UC } from '@domain/use-cases/conversations/CreateConversationUC';
6
+ import { DeleteConversationUC } from '@domain/use-cases/conversations/DeleteConversationUC';
7
+ import {
8
+ GenerateConversationTitleInput,
9
+ GenerateConversationTitleUC,
10
+ } from '@domain/use-cases/conversations/GenerateConversationTitleUC';
11
+ import type { IGetConversationsUC } from '@domain/use-cases/conversations/GetConversationsUC';
12
+ import {
13
+ GET_CONVERSATIONS_UC,
14
+ GetConversationsInput,
15
+ } from '@domain/use-cases/conversations/GetConversationsUC';
16
+ import type { IGetConversationUC } from '@domain/use-cases/conversations/GetConversationUC';
17
+ import {
18
+ GET_CONVERSATION_UC,
19
+ GetConversationInput,
20
+ } from '@domain/use-cases/conversations/GetConversationUC';
21
+ import {
22
+ UpdateConversationInput,
23
+ UpdateConversationUC,
24
+ } from '@domain/use-cases/conversations/UpdateConversationUC';
25
+ import { Inject } from '@nestjs/common';
26
+ import {
27
+ Args,
28
+ GqlTypeReference,
29
+ Mutation,
30
+ ObjectType,
31
+ Parent,
32
+ Query,
33
+ ResolveField,
34
+ Resolver,
35
+ } from '@nestjs/graphql';
36
+ import { CurrentUserGql } from 'src/inputs/resolvers/_common/CurrentUserGql';
37
+ import { GqlPaginatedOutput } from 'src/inputs/resolvers/_common/GqlPaginatedOutput';
38
+
39
+ export function GetConversationsResolver(
40
+ GqlConversation: GqlTypeReference,
41
+ GqlChat: GqlTypeReference,
42
+ GqlUser: GqlTypeReference,
43
+ ) {
44
+ @ObjectType('PaginatedConversations')
45
+ class GqlPaginatedConversations extends GqlPaginatedOutput(GqlConversation) {}
46
+
47
+ @Resolver(() => GqlConversation)
48
+ class ConversationsResolver {
49
+ constructor(
50
+ @Inject(GET_CONVERSATIONS_UC)
51
+ readonly getConversationsUC: IGetConversationsUC,
52
+ @Inject(GET_CONVERSATION_UC)
53
+ readonly getConversationUC: IGetConversationUC,
54
+ @Inject(CREATE_CONVERSATION_UC)
55
+ readonly createConversationUC: ICreateConversationUC,
56
+ readonly updateConversationUC: UpdateConversationUC,
57
+ readonly deleteConversationUC: DeleteConversationUC,
58
+ readonly generateConversationTitleUC: GenerateConversationTitleUC,
59
+ ) {}
60
+
61
+ @Query(() => GqlPaginatedConversations)
62
+ getConversations(
63
+ @Args('input') input: GetConversationsInput,
64
+ @CurrentUserGql() currentUser: UserEntity,
65
+ ): Promise<GqlPaginatedConversations> {
66
+ return this.getConversationsUC.execute({
67
+ input,
68
+ currentUser,
69
+ });
70
+ }
71
+
72
+ @Query(() => GqlConversation)
73
+ getConversation(
74
+ @Args('input') input: GetConversationInput,
75
+ @CurrentUserGql() currentUser: UserEntity,
76
+ ): Promise<typeof GqlConversation> {
77
+ return this.getConversationUC.execute({
78
+ input,
79
+ currentUser,
80
+ });
81
+ }
82
+
83
+ @Mutation(() => GqlConversation)
84
+ createConversation(
85
+ @CurrentUserGql() currentUser: UserEntity,
86
+ ): Promise<typeof GqlConversation> {
87
+ return this.createConversationUC.execute({
88
+ input: undefined,
89
+ currentUser,
90
+ });
91
+ }
92
+
93
+ @Mutation(() => GqlConversation)
94
+ updateConversation(
95
+ @Args('input') input: UpdateConversationInput,
96
+ @CurrentUserGql() currentUser: UserEntity,
97
+ ): Promise<typeof GqlConversation> {
98
+ return this.updateConversationUC.execute({
99
+ input,
100
+ currentUser,
101
+ });
102
+ }
103
+
104
+ @Mutation(() => GqlConversation)
105
+ deleteConversation(
106
+ @Args('input') input: DeleteByIdInput,
107
+ @CurrentUserGql() currentUser: UserEntity,
108
+ ): Promise<typeof GqlConversation> {
109
+ return this.deleteConversationUC.execute({
110
+ input,
111
+ currentUser,
112
+ });
113
+ }
114
+
115
+ @Mutation(() => GqlConversation)
116
+ generateConversationTitle(
117
+ @Args('input') input: GenerateConversationTitleInput,
118
+ @CurrentUserGql() currentUser: UserEntity,
119
+ ): Promise<typeof GqlConversation> {
120
+ return this.generateConversationTitleUC.execute({
121
+ input,
122
+ currentUser,
123
+ });
124
+ }
125
+
126
+ @ResolveField(() => GqlUser)
127
+ user(@Parent() conversation: ConversationEntity): Promise<typeof GqlUser> {
128
+ if (conversation._user instanceof Promise) {
129
+ return conversation._user;
130
+ } else {
131
+ return Promise.resolve(conversation._user);
132
+ }
133
+ }
134
+
135
+ @ResolveField(() => [GqlChat])
136
+ async chats(
137
+ @Parent() conversation: ConversationEntity,
138
+ ): Promise<(typeof GqlChat)[]> {
139
+ const chats = await conversation._chats;
140
+
141
+ return (
142
+ chats?.sort((a, b) => a.createdAt.getTime() - b.createdAt.getTime()) ??
143
+ []
144
+ );
145
+ }
146
+ }
147
+
148
+ return ConversationsResolver;
149
+ }
@@ -0,0 +1,11 @@
1
+ import { UserEntity } from '@domain/entities/UserEntity';
2
+ import { createParamDecorator, ExecutionContext } from '@nestjs/common';
3
+ import { GqlExecutionContext } from '@nestjs/graphql';
4
+
5
+ export const CurrentUserGql = createParamDecorator<unknown, UserEntity>(
6
+ (data: unknown, context: ExecutionContext) => {
7
+ const ctx = GqlExecutionContext.create(context);
8
+
9
+ return ctx.getContext<{ user: UserEntity }>().user;
10
+ },
11
+ );
@@ -0,0 +1,25 @@
1
+ import { Type } from '@nestjs/common';
2
+ import { Field, GqlTypeReference, Int, ObjectType } from '@nestjs/graphql';
3
+
4
+ export interface IPaginatedType<T> {
5
+ items: T[];
6
+ totalCount: number;
7
+ hasNextPage: boolean;
8
+ }
9
+
10
+ export function GqlPaginatedOutput<T>(
11
+ classRef: GqlTypeReference<T>,
12
+ ): Type<IPaginatedType<T>> {
13
+ @ObjectType({ isAbstract: true })
14
+ abstract class PaginatedType implements IPaginatedType<T> {
15
+ @Field(() => [classRef], { nullable: true })
16
+ items!: T[];
17
+
18
+ @Field(() => Int)
19
+ totalCount!: number;
20
+
21
+ @Field()
22
+ hasNextPage!: boolean;
23
+ }
24
+ return PaginatedType as Type<IPaginatedType<T>>;
25
+ }
package/tsconfig.json ADDED
@@ -0,0 +1,28 @@
1
+ {
2
+ "compilerOptions": {
3
+ "module": "commonjs",
4
+ "declaration": true,
5
+ "removeComments": false,
6
+ "emitDecoratorMetadata": true,
7
+ "experimentalDecorators": true,
8
+ "allowSyntheticDefaultImports": true,
9
+ "strict": true,
10
+ "target": "es2024",
11
+ "sourceMap": true,
12
+ "outDir": "./dist",
13
+ "baseUrl": "./",
14
+ "incremental": true,
15
+ "skipLibCheck": true,
16
+ "strictNullChecks": true,
17
+ "forceConsistentCasingInFileNames": true,
18
+ "noImplicitAny": false,
19
+ "strictBindCallApply": false,
20
+ "noFallthroughCasesInSwitch": false,
21
+ "sourceRoot": "/",
22
+ "paths": {
23
+ "@domain/*": ["src/domain/*"],
24
+ "@inputs/*": ["src/inputs/*"]
25
+ }
26
+ },
27
+ "exclude": ["node_modules", "**/*.spec.ts", "tests", "dist"]
28
+ }