knowledge-group.service.ts 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  1. import { Injectable, NotFoundException, Inject, forwardRef } from '@nestjs/common';
  2. import { I18nService } from '../i18n/i18n.service';
  3. import { InjectRepository } from '@nestjs/typeorm';
  4. import { Repository } from 'typeorm';
  5. import { KnowledgeGroup } from './knowledge-group.entity';
  6. import { KnowledgeBase } from '../knowledge-base/knowledge-base.entity';
  7. import { KnowledgeBaseService } from '../knowledge-base/knowledge-base.service';
  8. export interface CreateGroupDto {
  9. name: string;
  10. description?: string;
  11. color?: string;
  12. }
  13. export interface UpdateGroupDto {
  14. name?: string;
  15. description?: string;
  16. color?: string;
  17. }
  18. export interface GroupWithFileCount {
  19. id: string;
  20. name: string;
  21. description?: string;
  22. color: string;
  23. fileCount: number;
  24. createdAt: Date;
  25. }
  26. @Injectable()
  27. export class KnowledgeGroupService {
  28. constructor(
  29. @InjectRepository(KnowledgeGroup)
  30. private groupRepository: Repository<KnowledgeGroup>,
  31. @InjectRepository(KnowledgeBase)
  32. private knowledgeBaseRepository: Repository<KnowledgeBase>,
  33. @Inject(forwardRef(() => KnowledgeBaseService))
  34. private knowledgeBaseService: KnowledgeBaseService,
  35. private i18nService: I18nService,
  36. ) { }
  37. async findAll(userId: string, tenantId: string): Promise<GroupWithFileCount[]> {
  38. // Return all groups for the tenant
  39. const groups = await this.groupRepository
  40. .createQueryBuilder('group')
  41. .leftJoin('group.knowledgeBases', 'kb')
  42. .where('group.tenantId = :tenantId', { tenantId })
  43. .addSelect('COUNT(kb.id)', 'fileCount')
  44. .groupBy('group.id')
  45. .orderBy('group.createdAt', 'DESC')
  46. .getRawAndEntities();
  47. return groups.entities.map((group, index) => ({
  48. id: group.id,
  49. name: group.name,
  50. description: group.description,
  51. color: group.color,
  52. fileCount: parseInt(groups.raw[index].fileCount) || 0,
  53. createdAt: group.createdAt,
  54. }));
  55. }
  56. async findOne(id: string, userId: string, tenantId: string): Promise<KnowledgeGroup> {
  57. // Restrict group to tenant
  58. const group = await this.groupRepository.findOne({
  59. where: { id, tenantId },
  60. relations: ['knowledgeBases'],
  61. });
  62. if (!group) {
  63. throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
  64. }
  65. return group;
  66. }
  67. async create(userId: string, tenantId: string, createGroupDto: CreateGroupDto): Promise<KnowledgeGroup> {
  68. const group = this.groupRepository.create({
  69. ...createGroupDto,
  70. tenantId,
  71. });
  72. return await this.groupRepository.save(group);
  73. }
  74. async update(id: string, userId: string, tenantId: string, updateGroupDto: UpdateGroupDto): Promise<KnowledgeGroup> {
  75. // Update group within the tenant
  76. const group = await this.groupRepository.findOne({
  77. where: { id, tenantId },
  78. });
  79. if (!group) {
  80. throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
  81. }
  82. Object.assign(group, updateGroupDto);
  83. return await this.groupRepository.save(group);
  84. }
  85. async remove(id: string, userId: string, tenantId: string): Promise<void> {
  86. // Remove group within the tenant
  87. const group = await this.groupRepository.findOne({
  88. where: { id, tenantId },
  89. });
  90. if (!group) {
  91. throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
  92. }
  93. // Find all files associated with this group (without user restriction)
  94. const files = await this.knowledgeBaseRepository
  95. .createQueryBuilder('kb')
  96. .innerJoin('kb.groups', 'group')
  97. .where('group.id = :groupId', { groupId: id })
  98. .select('kb.id')
  99. .getMany();
  100. // Delete each file
  101. for (const file of files) {
  102. try {
  103. // We need to get the file's owner to delete it properly
  104. const fullFile = await this.knowledgeBaseRepository.findOne({
  105. where: { id: file.id },
  106. select: ['id', 'userId', 'tenantId'] // Get the owner of the file
  107. });
  108. if (fullFile) {
  109. await this.knowledgeBaseService.deleteFile(fullFile.id, fullFile.userId, fullFile.tenantId as string);
  110. }
  111. } catch (error) {
  112. console.error(`Failed to delete file ${file.id} when deleting group ${id}`, error);
  113. }
  114. }
  115. // Delete notes in this group - call the findAll method with groupId parameter only
  116. // We'll fetch notes for the group without userId restriction
  117. // For this, we'll call the note service differently
  118. // Actually, we need to think about this carefully
  119. // Notes belong to users, so we can't remove notes from other users' groups
  120. // We'll just remove the group from the association
  121. // Or fetch notes by groupId only (need to modify note service)
  122. // Since note service is user-restricted, let's only handle file removal
  123. // and leave note management to users individually
  124. // Delete the group itself
  125. await this.groupRepository.remove(group);
  126. }
  127. async getGroupFiles(groupId: string, userId: string, tenantId: string): Promise<KnowledgeBase[]> {
  128. const group = await this.groupRepository.findOne({
  129. where: { id: groupId, tenantId },
  130. relations: ['knowledgeBases'],
  131. });
  132. if (!group) {
  133. throw new NotFoundException(this.i18nService.getMessage('groupNotFound'));
  134. }
  135. return group.knowledgeBases;
  136. }
  137. async addFilesToGroup(fileId: string, groupIds: string[], userId: string, tenantId: string): Promise<void> {
  138. const file = await this.knowledgeBaseRepository.findOne({
  139. where: { id: fileId, userId, tenantId },
  140. relations: ['groups'],
  141. });
  142. if (!file) {
  143. throw new NotFoundException(this.i18nService.getMessage('fileNotFound'));
  144. }
  145. // Load all groups by ID without user restriction
  146. const groups = await this.groupRepository.findByIds(groupIds);
  147. const validGroups = groups.filter(g => g.tenantId === tenantId);
  148. if (validGroups.length !== groupIds.length) {
  149. throw new NotFoundException(this.i18nService.getMessage('someGroupsNotFound'));
  150. }
  151. file.groups = validGroups;
  152. await this.knowledgeBaseRepository.save(file);
  153. }
  154. async removeFileFromGroup(fileId: string, groupId: string, userId: string, tenantId: string): Promise<void> {
  155. const file = await this.knowledgeBaseRepository.findOne({
  156. where: { id: fileId, userId, tenantId },
  157. relations: ['groups'],
  158. });
  159. if (!file) {
  160. throw new NotFoundException(this.i18nService.getMessage('fileNotFound'));
  161. }
  162. file.groups = file.groups.filter(group => group.id !== groupId);
  163. await this.knowledgeBaseRepository.save(file);
  164. }
  165. async getFileIdsByGroups(groupIds: string[], userId: string, tenantId: string): Promise<string[]> {
  166. if (!groupIds || groupIds.length === 0) {
  167. return [];
  168. }
  169. const result = await this.knowledgeBaseRepository
  170. .createQueryBuilder('kb')
  171. .innerJoin('kb.groups', 'group')
  172. .where('group.id IN (:...groupIds)', { groupIds })
  173. .andWhere('kb.tenantId = :tenantId', { tenantId })
  174. .select('DISTINCT kb.id', 'id')
  175. .getRawMany();
  176. return result.map(row => row.id);
  177. }
  178. }