typesense/pkg/api/utils.go
2025-02-19 16:51:47 +01:00

174 lines
5.2 KiB
Go

package typesenseapi
import (
"context"
"fmt"
"sort"
"strings"
"time"
pkgtypesense "github.com/foomo/typesense/pkg"
"github.com/typesense/typesense-go/v3/typesense/api"
"github.com/typesense/typesense-go/v3/typesense/api/pointer"
"go.uber.org/zap"
)
// buildSearchParams will return the search collection parameters
// this is meant as a utility function to create the search collection parameters
// for the typesense search API without any knowledge of the typesense API
func buildSearchParams(
q string,
filterBy map[string]string,
page, perPage int,
sortBy string,
) *api.SearchCollectionParams {
parameters := &api.SearchCollectionParams{}
parameters.Q = pointer.String(q)
if filterByString := formatFilterQuery(filterBy); filterByString != "" {
parameters.FilterBy = pointer.String(filterByString)
}
parameters.Page = pointer.Int(page)
parameters.PerPage = pointer.Int(perPage)
if sortBy != "" {
parameters.SortBy = pointer.String(sortBy)
}
return parameters
}
func formatFilterQuery(filterBy map[string]string) string {
if filterBy == nil {
return ""
}
filterByString := []string{}
for key, value := range filterBy {
filterByString = append(filterByString, key+":="+value)
}
return strings.Join(filterByString, "&&")
}
func (b *BaseAPI[indexDocument, returnType]) generateRevisionID() pkgtypesense.RevisionID {
return pkgtypesense.RevisionID(time.Now().Format("2006-01-02-15-04")) // "YYYY-MM-DD-HH-MM"
}
func getLatestRevisionID(revisions map[pkgtypesense.IndexID]pkgtypesense.RevisionID) pkgtypesense.RevisionID {
var latest pkgtypesense.RevisionID
for _, rev := range revisions {
if rev > latest {
latest = rev
}
}
return latest
}
func formatCollectionName(indexID pkgtypesense.IndexID, revisionID pkgtypesense.RevisionID) string {
return fmt.Sprintf("%s-%s", indexID, revisionID)
}
func extractRevisionID(collectionName, name string) pkgtypesense.RevisionID {
if !strings.HasPrefix(collectionName, name+"-") {
return ""
}
revisionID := strings.TrimPrefix(collectionName, name+"-")
// Validate that the extracted revision ID follows YYYY-MM-DD-HH-MM format (16 chars)
if len(revisionID) != 16 {
return ""
}
return pkgtypesense.RevisionID(revisionID)
}
// ensureAliasMapping ensures an alias correctly points to the specified collection.
func (b *BaseAPI[indexDocument, returnType]) ensureAliasMapping(ctx context.Context, indexID pkgtypesense.IndexID, collectionName string) error {
_, err := b.client.Aliases().Upsert(ctx, string(indexID), &api.CollectionAliasSchema{
CollectionName: collectionName,
})
if err != nil {
b.l.Error("Failed to upsert alias",
zap.String("alias", string(indexID)),
zap.String("collection", collectionName),
zap.Error(err),
)
}
return err
}
func (b *BaseAPI[indexDocument, returnType]) pruneOldCollections(ctx context.Context, alias, currentCollection string) error {
// Step 1: Retrieve all collections
collections, err := b.client.Collections().Retrieve(ctx)
if err != nil {
b.l.Error("Failed to retrieve collections", zap.Error(err))
return err
}
var oldCollections []string
for _, col := range collections {
if strings.HasPrefix(col.Name, alias+"-") && col.Name != currentCollection {
oldCollections = append(oldCollections, col.Name)
}
}
// Step 2: Sort collections by timestamp (latest first)
sort.Slice(oldCollections, func(i, j int) bool {
return oldCollections[i] > oldCollections[j] // Reverse order
})
// Step 3: Delete all but the latest two collections
if len(oldCollections) > 1 {
toDelete := oldCollections[1:] // Keep only the latest two
for _, col := range toDelete {
_, err := b.client.Collection(col).Delete(ctx)
if err != nil {
b.l.Error("Failed to delete collection", zap.String("collection", col), zap.Error(err))
} else {
b.l.Info("Deleted old collection", zap.String("collection", col))
}
}
}
return nil
}
// fetchExistingCollections retrieves all existing collections and stores them in a map for quick lookup.
func (b *BaseAPI[indexDocument, returnType]) fetchExistingCollections(ctx context.Context) (map[string]bool, error) {
collections, err := b.client.Collections().Retrieve(ctx)
if err != nil {
b.l.Error("Failed to retrieve collections", zap.Error(err))
return nil, err
}
existingCollections := make(map[string]bool)
for _, col := range collections {
existingCollections[col.Name] = true
}
return existingCollections, nil
}
// createCollectionIfNotExists ensures that a collection exists before trying to use it.
func (b *BaseAPI[indexDocument, returnType]) createCollectionIfNotExists(ctx context.Context, schema *api.CollectionSchema, collectionName string) error {
// Check if collection already exists
existingCollections, err := b.fetchExistingCollections(ctx)
if err != nil {
return err
}
if existingCollections[collectionName] {
b.l.Info("Collection already exists, skipping creation", zap.String("collection", collectionName))
return nil
}
// Set the collection name and create it
schema.Name = collectionName
_, err = b.client.Collections().Create(ctx, schema)
if err != nil {
b.l.Error("Failed to create collection", zap.String("collection", collectionName), zap.Error(err))
return err
}
b.l.Info("Created new collection", zap.String("collection", collectionName))
return nil
}