package bleve import ( "fmt" "log" "os" "tercul/internal/domain" blevelib "github.com/blevesearch/bleve/v2" "gorm.io/gorm" ) type BleveClient struct { index blevelib.Index } // NewBleveClient initializes or opens a Bleve index at the given path. func NewBleveClient(indexPath string) (*BleveClient, error) { var index blevelib.Index var err error if _, err = os.Stat(indexPath); os.IsNotExist(err) { // Create a new index if it doesn't exist indexMapping := blevelib.NewIndexMapping() index, err = blevelib.New(indexPath, indexMapping) if err != nil { return nil, err } log.Println("Created a new Bleve index at:", indexPath) } else { // Open an existing index index, err = blevelib.Open(indexPath) if err != nil { return nil, err } log.Println("Opened an existing Bleve index at:", indexPath) } return &BleveClient{index: index}, nil } // AddTranslation indexes a single translation into the Bleve index. func (bc *BleveClient) AddTranslation(translation domain.Translation) error { // Create a structured document containing all relevant translation fields document := map[string]interface{}{ "id": translation.ID, "title": translation.Title, "content": translation.Content, "description": translation.Description, "language": translation.Language, "status": translation.Status, "translatable_id": translation.TranslatableID, "translatable_type": translation.TranslatableType, "translator_id": func() uint { if translation.TranslatorID != nil { return *translation.TranslatorID } return 0 }(), } // Use the translation's ID as the unique document ID docID := fmt.Sprintf("%d", translation.ID) return bc.index.Index(docID, document) } // AddTranslations indexes all translations from the database into the Bleve index. func (bc *BleveClient) AddTranslations(db *gorm.DB) error { const batchSize = 50000 offset := 0 for { // Fetch translations in batches var translations []domain.Translation err := db.Offset(offset).Limit(batchSize).Find(&translations).Error if err != nil { log.Printf("Error fetching translations from the database: %v", err) return err } // Break if no more translations to process if len(translations) == 0 { break } // Create a Bleve batch for better indexing performance batch := bc.index.NewBatch() for _, translation := range translations { // Create a structured document for each translation document := map[string]interface{}{ "id": translation.ID, "title": translation.Title, "content": translation.Content, "description": translation.Description, "language": translation.Language, "status": translation.Status, "translatable_id": translation.TranslatableID, "translatable_type": translation.TranslatableType, "translator_id": func() uint { if translation.TranslatorID != nil { return *translation.TranslatorID } return 0 }(), } docID := fmt.Sprintf("%d", translation.ID) err = batch.Index(docID, document) if err != nil { log.Printf("Error indexing translation ID %s: %v", docID, err) } } // Commit the batch to the index err = bc.index.Batch(batch) if err != nil { log.Printf("Error committing batch to the Bleve index: %v", err) return err } log.Printf("Indexed %d translations into Bleve.", len(translations)) offset += batchSize } log.Println("All translations have been indexed into Bleve.") return nil } // Search performs a search with multiple filters and a full-text query. func (bc *BleveClient) Search(queryString string, filters map[string]string, size int) (*blevelib.SearchResult, error) { // Create the main query for full-text search mainQuery := blevelib.NewMatchQuery(queryString) mainQuery.SetFuzziness(2) // Create a boolean query booleanQuery := blevelib.NewBooleanQuery() // Add the main query to the "must" clause booleanQuery.AddMust(mainQuery) // Add filter queries to the "must" clause for field, value := range filters { termQuery := blevelib.NewTermQuery(value) termQuery.SetField(field) booleanQuery.AddMust(termQuery) } // Build the search request searchRequest := blevelib.NewSearchRequest(booleanQuery) searchRequest.Size = size // Execute the search results, err := bc.index.Search(searchRequest) if err != nil { log.Printf("Search failed: %v", err) return nil, err } return results, nil } // Close closes the Bleve index. func (bc *BleveClient) Close() error { return bc.index.Close() }