package bot import ( "time" "queryorchestration/internal/database/repository" "github.com/google/uuid" ) // sessionListRow is a small in-package shape that lets ListSessionsForOwner // and ListSessionsForSuperAdmin share decorateSessionList without a generic // or interface assertion at every site. The two underlying sqlc rows have // the same columns but distinct Go types (per sqlc's row-per-query rule), // so we copy the field values once at the boundary. type sessionListRow struct { id uuid.UUID clientID string createdBy string title string state []byte createdAt time.Time updatedAt time.Time lastTerminal int32 isDeleted bool } // rowsOwner adapts ListBotSessionsForOwnerRow rows to the shared shape. func rowsOwner(rows []*repository.ListBotSessionsForOwnerRow) []sessionListRow { out := make([]sessionListRow, 0, len(rows)) for _, r := range rows { out = append(out, sessionListRow{ id: r.ID, clientID: r.ClientID, createdBy: r.CreatedBy, title: r.Title, state: r.State, createdAt: r.CreatedAt.Time, updatedAt: r.UpdatedAt.Time, lastTerminal: r.LastTerminalOrdinal, isDeleted: r.IsDeleted, }) } return out } // rowsSuperAdmin adapts ListBotSessionsForSuperAdminRow rows to the shared // shape. Identical to rowsOwner but on a different generated type. func rowsSuperAdmin(rows []*repository.ListBotSessionsForSuperAdminRow) []sessionListRow { out := make([]sessionListRow, 0, len(rows)) for _, r := range rows { out = append(out, sessionListRow{ id: r.ID, clientID: r.ClientID, createdBy: r.CreatedBy, title: r.Title, state: r.State, createdAt: r.CreatedAt.Time, updatedAt: r.UpdatedAt.Time, lastTerminal: r.LastTerminalOrdinal, isDeleted: r.IsDeleted, }) } return out } // dedupUUIDs returns the input slice with duplicates removed, preserving // first-seen order. Returns a non-nil empty slice for nil input so the // caller can pass the result directly to set-comparison helpers. func dedupUUIDs(in []uuid.UUID) []uuid.UUID { if len(in) == 0 { return []uuid.UUID{} } seen := make(map[uuid.UUID]struct{}, len(in)) out := make([]uuid.UUID, 0, len(in)) for _, id := range in { if _, ok := seen[id]; ok { continue } seen[id] = struct{}{} out = append(out, id) } return out } // hasIntersection returns true when sets a and b share at least one // element. Both inputs are expected to be deduplicated already. func hasIntersection(a, b []uuid.UUID) bool { if len(a) == 0 || len(b) == 0 { return false } set := make(map[uuid.UUID]struct{}, len(a)) for _, id := range a { set[id] = struct{}{} } for _, id := range b { if _, ok := set[id]; ok { return true } } return false } // computeUnion returns (existing - remove) ∪ add as a deduplicated // slice in deterministic order: first the surviving existing ids in // their original order, then any new add ids that are not already in // the surviving set. The bot_session_documents / bot_session_folders // PRIMARY KEY guarantees `existing` is already deduplicated when read // from SQL, so the loop can rely on that invariant. func computeUnion(existing, add, remove []uuid.UUID) []uuid.UUID { removeSet := make(map[uuid.UUID]struct{}, len(remove)) for _, id := range remove { removeSet[id] = struct{}{} } out := make([]uuid.UUID, 0, len(existing)+len(add)) seen := make(map[uuid.UUID]struct{}, len(existing)+len(add)) for _, id := range existing { if _, dropped := removeSet[id]; dropped { continue } seen[id] = struct{}{} out = append(out, id) } for _, id := range add { if _, dup := seen[id]; dup { continue } seen[id] = struct{}{} out = append(out, id) } return out }