package ai import ( "context" "encoding/json" "fmt" "io" "path" "strings" "time" "github.com/jackc/pgx/v5/pgxpool" "github.com/ultisuite/ulti-backend/internal/nextcloud" ) type ChatStore struct { nc *nextcloud.Client db *pgxpool.Pool } func NewChatStore(nc *nextcloud.Client, db *pgxpool.Pool) *ChatStore { return &ChatStore{nc: nc, db: db} } func (s *ChatStore) Sync(ctx context.Context, userEmail string, record ChatRecord) error { if s == nil || s.nc == nil { return fmt.Errorf("nextcloud unavailable") } policy, _ := LoadAssistantPolicy(ctx, s.db) basePath := policy.ChatNCPath if strings.TrimSpace(basePath) == "" { basePath = nextcloud.DefaultChatNCBasePath } if strings.TrimSpace(record.ID) == "" { return fmt.Errorf("chat id required") } if record.UpdatedAt.IsZero() { record.UpdatedAt = time.Now().UTC() } if record.CreatedAt.IsZero() { record.CreatedAt = record.UpdatedAt } if strings.TrimSpace(record.Source) == "" { record.Source = "openwebui" } userID := nextcloud.UserIDFromClaims(userEmail, "") sidecarPath := nextcloud.ChatSidecarPath(basePath, record.ID) dir := path.Dir(sidecarPath) if dir != "/" && dir != "." { _ = s.nc.CreateFolder(ctx, userID, dir) } payload, err := json.MarshalIndent(record, "", " ") if err != nil { return err } return s.nc.Upload(ctx, userID, sidecarPath, strings.NewReader(string(payload)), "application/json") } func (s *ChatStore) Get(ctx context.Context, userEmail, chatID string) (ChatRecord, error) { if s == nil || s.nc == nil { return ChatRecord{}, fmt.Errorf("nextcloud unavailable") } policy, _ := LoadAssistantPolicy(ctx, s.db) basePath := policy.ChatNCPath if strings.TrimSpace(basePath) == "" { basePath = nextcloud.DefaultChatNCBasePath } userID := nextcloud.UserIDFromClaims(userEmail, "") sidecarPath := nextcloud.ChatSidecarPath(basePath, chatID) body, _, err := s.nc.Download(ctx, userID, sidecarPath) if err != nil { return ChatRecord{}, err } defer body.Close() raw, err := io.ReadAll(io.LimitReader(body, 8<<20)) if err != nil { return ChatRecord{}, err } var record ChatRecord if err := json.Unmarshal(raw, &record); err != nil { return ChatRecord{}, err } return record, nil } func (s *ChatStore) Delete(ctx context.Context, userEmail, chatID string) error { if s == nil || s.nc == nil { return fmt.Errorf("nextcloud unavailable") } policy, _ := LoadAssistantPolicy(ctx, s.db) basePath := policy.ChatNCPath if strings.TrimSpace(basePath) == "" { basePath = nextcloud.DefaultChatNCBasePath } userID := nextcloud.UserIDFromClaims(userEmail, "") sidecarPath := nextcloud.ChatSidecarPath(basePath, chatID) return s.nc.Delete(ctx, userID, sidecarPath) }