package document import ( "bytes" "context" "encoding/json" "fmt" "github.com/google/uuid" "resume-platform/internal/model" "resume-platform/internal/parser" "resume-platform/internal/repository" "resume-platform/internal/service/knowledge" "resume-platform/internal/service/resume" "resume-platform/pkg/logger" ) type DocumentService interface { UploadDocument(ctx context.Context, userID string, fileName string, fileType string, fileSize int64, content string) (*model.Document, error) GetDocuments(ctx context.Context, userID string) ([]*model.Document, error) GetDocument(ctx context.Context, userID string, documentID string) (*model.Document, error) DeleteDocument(ctx context.Context, userID string, documentID string) error ProcessDocument(ctx context.Context, documentID string) error ParseAndGenerateResume(ctx context.Context, userID string, fileName string, content []byte) (*model.Resume, error) } type documentService struct { repo repository.ResumeRepository resumeGenerator *resume.ResumeGeneratorService knowledgeBaseService *knowledge.KnowledgeBaseService } func NewDocumentService(repo repository.ResumeRepository, resumeGenerator *resume.ResumeGeneratorService) DocumentService { return &documentService{repo: repo, resumeGenerator: resumeGenerator} } func NewDocumentServiceWithKnowledge(repo repository.ResumeRepository, resumeGenerator *resume.ResumeGeneratorService, knowledgeBaseService *knowledge.KnowledgeBaseService) DocumentService { return &documentService{repo: repo, resumeGenerator: resumeGenerator, knowledgeBaseService: knowledgeBaseService} } func (s *documentService) UploadDocument(ctx context.Context, userID string, fileName string, fileType string, fileSize int64, content string) (*model.Document, error) { document := &model.Document{ ID: generateID(), UserID: userID, Name: fileName, Type: fileType, Size: fileSize, Content: content, Status: "uploading", } err := s.repo.CreateDocument(document) if err != nil { return nil, err } go s.ProcessDocument(ctx, document.ID) return document, nil } func (s *documentService) GetDocuments(ctx context.Context, userID string) ([]*model.Document, error) { return s.repo.GetDocumentsByUserID(userID) } func (s *documentService) GetDocument(ctx context.Context, userID string, documentID string) (*model.Document, error) { return s.repo.GetDocument(documentID, userID) } func (s *documentService) DeleteDocument(ctx context.Context, userID string, documentID string) error { return s.repo.DeleteDocument(documentID, userID) } func (s *documentService) ProcessDocument(ctx context.Context, documentID string) error { document, err := s.repo.GetDocumentByID(documentID) if err != nil { return err } if document.Content == "" { document.Status = "failed" document.ErrorMsg = "document content is empty" return s.repo.UpdateDocument(document) } chunks := s.splitText(document.Content, 512) for i, chunk := range chunks { metadata := map[string]interface{}{ "document_id": document.ID, "chunk_index": i, "user_id": document.UserID, } metadataJSON, _ := json.Marshal(metadata) documentChunk := &model.DocumentChunk{ ID: generateID(), DocumentID: document.ID, ChunkIndex: i, Content: chunk, Metadata: string(metadataJSON), } err := s.repo.CreateDocumentChunk(documentChunk) if err != nil { document.Status = "failed" document.ErrorMsg = err.Error() s.repo.UpdateDocument(document) return err } } if s.knowledgeBaseService != nil { err = s.knowledgeBaseService.AddDocument(ctx, document.ID, document.Content, document.UserID) if err != nil { logger.Warnf("Failed to add document to knowledge base: %v", err) } else { logger.Infof("Document %s added to knowledge base", document.ID) } } document.Status = "processed" return s.repo.UpdateDocument(document) } func (s *documentService) splitText(text string, chunkSize int) []string { var chunks []string words := []rune(text) for i := 0; i < len(words); i += chunkSize { end := i + chunkSize if end > len(words) { end = len(words) } chunks = append(chunks, string(words[i:end])) } return chunks } func generateID() string { return uuid.New().String() } func (s *documentService) ParseAndGenerateResume(ctx context.Context, userID string, fileName string, content []byte) (*model.Resume, error) { documentText, err := parser.ParseFile(fileName, bytes.NewReader(content)) if err != nil { logger.Warnf("Failed to parse document %s: %v", fileName, err) return nil, err } if documentText == "" { return nil, fmt.Errorf("document content is empty after parsing") } resume, err := s.resumeGenerator.GenerateFromDocument(ctx, documentText) if err != nil { return nil, err } resume.UserID = userID fmt.Printf("DEBUG document.go: resume.UserID set to: '%s'\n", resume.UserID) return resume, nil }