Skip to main content
Glama
documents.service.ts7.17 kB
import { Injectable, Logger, NotFoundException, BadRequestException, ForbiddenException, } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { Repository, Like, In } from 'typeorm'; import { OpenAPIDocument, DocumentStatus } from '../../../database/entities/openapi-document.entity'; import { User } from '../../../database/entities/user.entity'; import { CreateDocumentDto } from '../dto/create-document.dto'; import { UpdateDocumentDto } from '../dto/update-document.dto'; import { QueryDocumentDto } from '../dto/query-document.dto'; import { DocumentResponseDto, DocumentDetailResponseDto, DocumentListResponseDto, DocumentInfoDto, } from '../dto/document-response.dto'; @Injectable() export class DocumentsService { private readonly logger = new Logger(DocumentsService.name); constructor( @InjectRepository(OpenAPIDocument) private readonly documentRepository: Repository<OpenAPIDocument>, @InjectRepository(User) private readonly userRepository: Repository<User>, ) {} /** * 创建新文档 */ async create(userId: string, createDocumentDto: CreateDocumentDto): Promise<DocumentDetailResponseDto> { try { // 验证内容是否为有效的JSON this.validateJsonContent(createDocumentDto.content); const document = this.documentRepository.create({ ...createDocumentDto, userId, }); const savedDocument = await this.documentRepository.save(document); this.logger.log(`Created document ${savedDocument.id} for user ${userId}`); return this.toDetailResponseDto(savedDocument); } catch (error) { this.logger.error(`Failed to create document for user ${userId}: ${error.message}`); throw error; } } /** * 获取用户文档列表 */ async findAll(userId: string, queryDto: QueryDocumentDto): Promise<DocumentListResponseDto> { try { const { page, limit, search, status, tags, sortBy, sortOrder } = queryDto; const skip = (page - 1) * limit; const queryBuilder = this.documentRepository .createQueryBuilder('document') .where('document.userId = :userId', { userId }); // 搜索过滤 if (search) { queryBuilder.andWhere( '(document.name ILIKE :search OR document.description ILIKE :search)', { search: `%${search}%` } ); } // 状态过滤 if (status) { queryBuilder.andWhere('document.status = :status', { status }); } // 标签过滤 if (tags && tags.length > 0) { queryBuilder.andWhere('document.tags && :tags', { tags }); } // 排序 queryBuilder.orderBy(`document.${sortBy}`, sortOrder); // 分页 queryBuilder.skip(skip).take(limit); const [documents, total] = await queryBuilder.getManyAndCount(); const totalPages = Math.ceil(total / limit); this.logger.log(`Found ${documents.length} documents for user ${userId}`); return { documents: documents.map(doc => this.toResponseDto(doc)), total, page, limit, totalPages, }; } catch (error) { this.logger.error(`Failed to get documents for user ${userId}: ${error.message}`); throw error; } } /** * 获取单个文档详情 */ async findOne(userId: string | null, id: string): Promise<DocumentDetailResponseDto> { try { const whereCondition = userId ? { id, userId } : { id }; const document = await this.documentRepository.findOne({ where: whereCondition, }); if (!document) { throw new NotFoundException(`Document with ID ${id} not found`); } this.logger.log(`Retrieved document ${id}${userId ? ` for user ${userId}` : ''}`); return this.toDetailResponseDto(document); } catch (error) { this.logger.error(`Failed to get document ${id}${userId ? ` for user ${userId}` : ''}: ${error.message}`); throw error; } } /** * 更新文档 */ async update(userId: string, id: string, updateDocumentDto: UpdateDocumentDto): Promise<DocumentDetailResponseDto> { try { const document = await this.documentRepository.findOne({ where: { id, userId }, }); if (!document) { throw new NotFoundException(`Document with ID ${id} not found`); } // 如果更新内容,验证JSON格式 if (updateDocumentDto.content) { this.validateJsonContent(updateDocumentDto.content); } // 更新文档 Object.assign(document, updateDocumentDto); const updatedDocument = await this.documentRepository.save(document); this.logger.log(`Updated document ${id} for user ${userId}`); return this.toDetailResponseDto(updatedDocument); } catch (error) { this.logger.error(`Failed to update document ${id} for user ${userId}: ${error.message}`); throw error; } } /** * 删除文档 */ async remove(userId: string, id: string): Promise<void> { try { const document = await this.documentRepository.findOne({ where: { id, userId }, }); if (!document) { throw new NotFoundException(`Document with ID ${id} not found`); } await this.documentRepository.remove(document); this.logger.log(`Deleted document ${id} for user ${userId}`); } catch (error) { this.logger.error(`Failed to delete document ${id} for user ${userId}: ${error.message}`); throw error; } } /** * 验证JSON内容格式 */ private validateJsonContent(content: string): void { try { const parsed = JSON.parse(content); // 基本的OpenAPI规范验证 if (!parsed.openapi && !parsed.swagger) { throw new BadRequestException('Content must be a valid OpenAPI/Swagger specification'); } if (!parsed.info || !parsed.info.title || !parsed.info.version) { throw new BadRequestException('OpenAPI specification must have info.title and info.version'); } } catch (error) { if (error instanceof BadRequestException) { throw error; } throw new BadRequestException('Content must be valid JSON'); } } /** * 转换为响应DTO(不包含content) */ private toResponseDto(document: OpenAPIDocument): DocumentResponseDto { const info = document.getInfo(); const endpointCount = document.getEndpointCount(); return { id: document.id, name: document.name, description: document.description, status: document.status, version: document.version, tags: document.tags, metadata: document.metadata, userId: document.userId, createdAt: document.createdAt, updatedAt: document.updatedAt, info, endpointCount, }; } /** * 转换为详细响应DTO(包含content) */ private toDetailResponseDto(document: OpenAPIDocument): DocumentDetailResponseDto { const baseDto = this.toResponseDto(document); return { ...baseDto, content: document.content, }; } }

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/zaizaizhao/mcp-swagger-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server