package httpapi import ( "context" "github.com/dtoro/oikos/internal/httpapi/gen" ) func (s *Server) SearchKnowledge(ctx context.Context, request gen.SearchKnowledgeRequestObject) (gen.SearchKnowledgeResponseObject, error) { q := request.Params.Q limit := clampLimit(request.Params.Limit) rows, err := s.pool.Query(ctx, ` SELECT e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags, ts_rank(ke.search, plainto_tsquery('english', $1)) AS rank, ts_headline('english', ke.content, plainto_tsquery('english', $1), 'MaxWords=40, MinWords=15, ShortWord=3, MaxFragments=3, FragmentDelimiter=" ... "') AS snippet FROM knowledge_entities ke JOIN entities e ON e.id = ke.entity_id JOIN entity_types et ON et.name = e.type WHERE ke.search @@ plainto_tsquery('english', $1) ORDER BY rank DESC LIMIT $2`, q, limit) if err != nil { return nil, err } defer rows.Close() items := []gen.KnowledgeHit{} for rows.Next() { var slug, eType, title, source string var tags []string var rank float32 var snippet *string if err := rows.Scan(&slug, &eType, &title, &source, &tags, &rank, &snippet); err != nil { return nil, err } hitType := gen.Document switch eType { case "investigation": hitType = gen.Investigation case "runbook": hitType = gen.Runbook } items = append(items, gen.KnowledgeHit{ Slug: slug, Title: title, Type: hitType, Rank: &rank, Snippet: snippet, SourcePath: &source, }) } if rows.Err() != nil { return nil, rows.Err() } if items == nil { items = []gen.KnowledgeHit{} } return gen.SearchKnowledge200JSONResponse{Items: items}, nil } func (s *Server) GetEntityKnowledge(ctx context.Context, request gen.GetEntityKnowledgeRequestObject) (gen.GetEntityKnowledgeResponseObject, error) { entitySlug := request.EntityId rows, err := s.pool.Query(ctx, ` SELECT e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags FROM knowledge_entities ke JOIN entities e ON e.id = ke.entity_id JOIN entity_types et ON et.name = e.type JOIN relationships r ON r.source_id = ke.entity_id JOIN entities target ON target.id = r.target_id WHERE target.slug = $1 AND r.valid_to IS NULL AND r.type IN ('documents', 'about') UNION SELECT e.slug, COALESCE(et.name,''), ke.title, ke.source, ke.tags FROM knowledge_entities ke JOIN entities e ON e.id = ke.entity_id JOIN entity_types et ON et.name = e.type JOIN relationships r ON r.source_id = ke.entity_id JOIN entity_types target_type ON target_type.name = r.target_id::text JOIN entities ent ON ent.type = target_type.name AND ent.slug = $1 WHERE r.valid_to IS NULL AND r.type = 'procedure-for' ORDER BY 1`, entitySlug) if err != nil { return nil, err } defer rows.Close() items := []gen.KnowledgeHit{} for rows.Next() { var slug, eType, title, source string var tags []string if err := rows.Scan(&slug, &eType, &title, &source, &tags); err != nil { return nil, err } hitType := gen.Document switch eType { case "investigation": hitType = gen.Investigation case "runbook": hitType = gen.Runbook } items = append(items, gen.KnowledgeHit{ Slug: slug, Title: title, Type: hitType, SourcePath: &source, }) } if rows.Err() != nil { return nil, rows.Err() } if items == nil { items = []gen.KnowledgeHit{} } return gen.GetEntityKnowledge200JSONResponse{Items: items}, nil }