// File Path: monorepo/cloud/maplepress-backend/pkg/search/index.go package search import ( "fmt" "github.com/meilisearch/meilisearch-go" ) // PageDocument represents a document in the search index type PageDocument struct { ID string `json:"id"` // page_id SiteID string `json:"site_id"` // for filtering (though each site has its own index) TenantID string `json:"tenant_id"` // for additional isolation Title string `json:"title"` Content string `json:"content"` // HTML stripped Excerpt string `json:"excerpt"` URL string `json:"url"` Status string `json:"status"` // publish, draft, trash PostType string `json:"post_type"` // page, post Author string `json:"author"` PublishedAt int64 `json:"published_at"` // Unix timestamp for sorting ModifiedAt int64 `json:"modified_at"` // Unix timestamp for sorting } // CreateIndex creates a new index for a site func (c *Client) CreateIndex(siteID string) error { indexName := c.GetIndexName(siteID) // Create index with site_id as primary key _, err := c.client.CreateIndex(&meilisearch.IndexConfig{ Uid: indexName, PrimaryKey: "id", // page_id is the primary key }) if err != nil { return fmt.Errorf("failed to create index %s: %w", indexName, err) } // Configure index settings return c.ConfigureIndex(siteID) } // ConfigureIndex configures the index settings func (c *Client) ConfigureIndex(siteID string) error { indexName := c.GetIndexName(siteID) index := c.client.Index(indexName) // Set searchable attributes (in order of priority) searchableAttributes := []string{ "title", "excerpt", "content", } _, err := index.UpdateSearchableAttributes(&searchableAttributes) if err != nil { return fmt.Errorf("failed to set searchable attributes: %w", err) } // Set filterable attributes filterableAttributes := []interface{}{ "status", "post_type", "author", "published_at", } _, err = index.UpdateFilterableAttributes(&filterableAttributes) if err != nil { return fmt.Errorf("failed to set filterable attributes: %w", err) } // Set ranking rules rankingRules := []string{ "words", "typo", "proximity", "attribute", "sort", "exactness", } _, err = index.UpdateRankingRules(&rankingRules) if err != nil { return fmt.Errorf("failed to set ranking rules: %w", err) } // Set displayed attributes (don't return full content in search results) displayedAttributes := []string{ "id", "title", "excerpt", "url", "status", "post_type", "author", "published_at", "modified_at", } _, err = index.UpdateDisplayedAttributes(&displayedAttributes) if err != nil { return fmt.Errorf("failed to set displayed attributes: %w", err) } return nil } // IndexExists checks if an index exists func (c *Client) IndexExists(siteID string) (bool, error) { indexName := c.GetIndexName(siteID) _, err := c.client.GetIndex(indexName) if err != nil { // Check if error is "index not found" (status code 404) if meiliErr, ok := err.(*meilisearch.Error); ok { if meiliErr.StatusCode == 404 { return false, nil } } return false, fmt.Errorf("failed to check index existence: %w", err) } return true, nil } // DeleteIndex deletes an index for a site func (c *Client) DeleteIndex(siteID string) error { indexName := c.GetIndexName(siteID) _, err := c.client.DeleteIndex(indexName) if err != nil { return fmt.Errorf("failed to delete index %s: %w", indexName, err) } return nil } // AddDocuments adds or updates documents in the index func (c *Client) AddDocuments(siteID string, documents []PageDocument) (*meilisearch.TaskInfo, error) { indexName := c.GetIndexName(siteID) index := c.client.Index(indexName) taskInfo, err := index.AddDocuments(documents, nil) if err != nil { return nil, fmt.Errorf("failed to add documents to index %s: %w", indexName, err) } return taskInfo, nil } // UpdateDocuments updates documents in the index func (c *Client) UpdateDocuments(siteID string, documents []PageDocument) (*meilisearch.TaskInfo, error) { indexName := c.GetIndexName(siteID) index := c.client.Index(indexName) taskInfo, err := index.UpdateDocuments(documents, nil) if err != nil { return nil, fmt.Errorf("failed to update documents in index %s: %w", indexName, err) } return taskInfo, nil } // DeleteDocument deletes a single document from the index func (c *Client) DeleteDocument(siteID string, documentID string) (*meilisearch.TaskInfo, error) { indexName := c.GetIndexName(siteID) index := c.client.Index(indexName) taskInfo, err := index.DeleteDocument(documentID) if err != nil { return nil, fmt.Errorf("failed to delete document %s from index %s: %w", documentID, indexName, err) } return taskInfo, nil } // DeleteDocuments deletes multiple documents from the index func (c *Client) DeleteDocuments(siteID string, documentIDs []string) (*meilisearch.TaskInfo, error) { indexName := c.GetIndexName(siteID) index := c.client.Index(indexName) taskInfo, err := index.DeleteDocuments(documentIDs) if err != nil { return nil, fmt.Errorf("failed to delete documents from index %s: %w", indexName, err) } return taskInfo, nil } // DeleteAllDocuments deletes all documents from the index func (c *Client) DeleteAllDocuments(siteID string) (*meilisearch.TaskInfo, error) { indexName := c.GetIndexName(siteID) index := c.client.Index(indexName) taskInfo, err := index.DeleteAllDocuments() if err != nil { return nil, fmt.Errorf("failed to delete all documents from index %s: %w", indexName, err) } return taskInfo, nil } // GetStats returns statistics about an index func (c *Client) GetStats(siteID string) (*meilisearch.StatsIndex, error) { indexName := c.GetIndexName(siteID) index := c.client.Index(indexName) stats, err := index.GetStats() if err != nil { return nil, fmt.Errorf("failed to get stats for index %s: %w", indexName, err) } return stats, nil }