package storage import ( "context" "fmt" "time" ) // SavedSearch represents a user's saved search query (PROJ-42). type SavedSearch struct { ID int64 `json:"id"` UserID int64 `json:"user_id"` TenantID int64 `json:"tenant_id"` Name string `json:"name"` QueryJSON []byte `json:"query"` CreatedAt time.Time `json:"created_at"` } // ListSavedSearches returns all saved searches for the given user and tenant. func (s *Store) ListSavedSearches(ctx context.Context, userID, tenantID int64) ([]SavedSearch, error) { rows, err := s.db.Query(ctx, ` SELECT id, user_id, tenant_id, name, query_json, created_at FROM saved_searches WHERE user_id = $1 AND tenant_id = $2 ORDER BY created_at DESC `, userID, tenantID) if err != nil { return nil, fmt.Errorf("saved_searches: list: %w", err) } defer rows.Close() var result []SavedSearch for rows.Next() { var ss SavedSearch if err := rows.Scan(&ss.ID, &ss.UserID, &ss.TenantID, &ss.Name, &ss.QueryJSON, &ss.CreatedAt); err != nil { return nil, fmt.Errorf("saved_searches: scan: %w", err) } result = append(result, ss) } return result, rows.Err() } // CreateSavedSearch inserts a new saved search and returns it. func (s *Store) CreateSavedSearch(ctx context.Context, userID, tenantID int64, name string, queryJSON []byte) (*SavedSearch, error) { ss := &SavedSearch{ UserID: userID, TenantID: tenantID, Name: name, QueryJSON: queryJSON, } err := s.db.QueryRow(ctx, ` INSERT INTO saved_searches (user_id, tenant_id, name, query_json) VALUES ($1, $2, $3, $4) RETURNING id, created_at `, userID, tenantID, name, queryJSON).Scan(&ss.ID, &ss.CreatedAt) if err != nil { return nil, fmt.Errorf("saved_searches: create: %w", err) } return ss, nil } // DeleteSavedSearch deletes a saved search. Ownership is enforced by requiring // both userID and tenantID to match the row. func (s *Store) DeleteSavedSearch(ctx context.Context, id, userID, tenantID int64) error { tag, err := s.db.Exec(ctx, ` DELETE FROM saved_searches WHERE id = $1 AND user_id = $2 AND tenant_id = $3 `, id, userID, tenantID) if err != nil { return fmt.Errorf("saved_searches: delete: %w", err) } if tag.RowsAffected() == 0 { return fmt.Errorf("saved_searches: not found or not owned by user") } return nil }