package sqlite import ( "context" "fmt" "strings" ) type Provider struct { ID int64 PackID int64 ProviderID string DisplayName string BaseURL string Platform string AccountType string DefaultModelsJSON string SmokeTestModel string GroupTemplateJSON string ChannelTemplateJSON string PlanTemplateJSON string ImportOptionsJSON string ManifestJSON string } type ProvidersRepo struct { db execQuerier } func newProvidersRepo(db execQuerier) *ProvidersRepo { return &ProvidersRepo{db: db} } func (r *ProvidersRepo) ListByPackID(ctx context.Context, packID int64) ([]Provider, error) { if packID <= 0 { return nil, fmt.Errorf("pack_id is required") } rows, err := r.db.QueryContext(ctx, `SELECT id, pack_id, provider_id, display_name, base_url, platform, account_type, default_models_json, smoke_test_model, group_template_json, channel_template_json, plan_template_json, import_options_json, manifest_json FROM providers WHERE pack_id = ? ORDER BY id`, packID) if err != nil { return nil, fmt.Errorf("query providers by pack_id %d: %w", packID, err) } defer rows.Close() providers := make([]Provider, 0) for rows.Next() { var provider Provider if err := rows.Scan( &provider.ID, &provider.PackID, &provider.ProviderID, &provider.DisplayName, &provider.BaseURL, &provider.Platform, &provider.AccountType, &provider.DefaultModelsJSON, &provider.SmokeTestModel, &provider.GroupTemplateJSON, &provider.ChannelTemplateJSON, &provider.PlanTemplateJSON, &provider.ImportOptionsJSON, &provider.ManifestJSON, ); err != nil { return nil, fmt.Errorf("scan provider: %w", err) } providers = append(providers, provider) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate providers: %w", err) } return providers, nil } func (r *ProvidersRepo) ListByProviderID(ctx context.Context, providerID string) ([]Provider, error) { providerID = strings.TrimSpace(providerID) if providerID == "" { return nil, fmt.Errorf("provider_id is required") } rows, err := r.db.QueryContext(ctx, `SELECT id, pack_id, provider_id, display_name, base_url, platform, account_type, default_models_json, smoke_test_model, group_template_json, channel_template_json, plan_template_json, import_options_json, manifest_json FROM providers WHERE provider_id = ? ORDER BY id`, providerID) if err != nil { return nil, fmt.Errorf("query providers by provider_id %q: %w", providerID, err) } defer rows.Close() providers := make([]Provider, 0) for rows.Next() { var provider Provider if err := rows.Scan( &provider.ID, &provider.PackID, &provider.ProviderID, &provider.DisplayName, &provider.BaseURL, &provider.Platform, &provider.AccountType, &provider.DefaultModelsJSON, &provider.SmokeTestModel, &provider.GroupTemplateJSON, &provider.ChannelTemplateJSON, &provider.PlanTemplateJSON, &provider.ImportOptionsJSON, &provider.ManifestJSON, ); err != nil { return nil, fmt.Errorf("scan provider by provider_id %q: %w", providerID, err) } providers = append(providers, provider) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate providers by provider_id %q: %w", providerID, err) } return providers, nil } func (r *ProvidersRepo) ListByBaseURL(ctx context.Context, baseURL string) ([]Provider, error) { baseURL = strings.TrimSpace(baseURL) if baseURL == "" { return nil, fmt.Errorf("base_url is required") } rows, err := r.db.QueryContext(ctx, `SELECT id, pack_id, provider_id, display_name, base_url, platform, account_type, default_models_json, smoke_test_model, group_template_json, channel_template_json, plan_template_json, import_options_json, manifest_json FROM providers WHERE base_url = ? ORDER BY id`, baseURL) if err != nil { return nil, fmt.Errorf("query providers by base_url %q: %w", baseURL, err) } defer rows.Close() providers := make([]Provider, 0) for rows.Next() { var provider Provider if err := rows.Scan( &provider.ID, &provider.PackID, &provider.ProviderID, &provider.DisplayName, &provider.BaseURL, &provider.Platform, &provider.AccountType, &provider.DefaultModelsJSON, &provider.SmokeTestModel, &provider.GroupTemplateJSON, &provider.ChannelTemplateJSON, &provider.PlanTemplateJSON, &provider.ImportOptionsJSON, &provider.ManifestJSON, ); err != nil { return nil, fmt.Errorf("scan provider by base_url %q: %w", baseURL, err) } providers = append(providers, provider) } if err := rows.Err(); err != nil { return nil, fmt.Errorf("iterate providers by base_url %q: %w", baseURL, err) } return providers, nil } func (r *ProvidersRepo) GetByPackIDAndProviderID(ctx context.Context, packID int64, providerID string) (Provider, error) { if packID <= 0 { return Provider{}, fmt.Errorf("pack_id is required") } providerID = strings.TrimSpace(providerID) if providerID == "" { return Provider{}, fmt.Errorf("provider_id is required") } var provider Provider if err := r.db.QueryRowContext(ctx, `SELECT id, pack_id, provider_id, display_name, base_url, platform, account_type, default_models_json, smoke_test_model, group_template_json, channel_template_json, plan_template_json, import_options_json, manifest_json FROM providers WHERE pack_id = ? AND provider_id = ?`, packID, providerID).Scan( &provider.ID, &provider.PackID, &provider.ProviderID, &provider.DisplayName, &provider.BaseURL, &provider.Platform, &provider.AccountType, &provider.DefaultModelsJSON, &provider.SmokeTestModel, &provider.GroupTemplateJSON, &provider.ChannelTemplateJSON, &provider.PlanTemplateJSON, &provider.ImportOptionsJSON, &provider.ManifestJSON, ); err != nil { return Provider{}, err } return provider, nil } func (r *ProvidersRepo) GetByID(ctx context.Context, id int64) (Provider, error) { if id <= 0 { return Provider{}, fmt.Errorf("id is required") } var provider Provider if err := r.db.QueryRowContext(ctx, `SELECT id, pack_id, provider_id, display_name, base_url, platform, account_type, default_models_json, smoke_test_model, group_template_json, channel_template_json, plan_template_json, import_options_json, manifest_json FROM providers WHERE id = ?`, id).Scan( &provider.ID, &provider.PackID, &provider.ProviderID, &provider.DisplayName, &provider.BaseURL, &provider.Platform, &provider.AccountType, &provider.DefaultModelsJSON, &provider.SmokeTestModel, &provider.GroupTemplateJSON, &provider.ChannelTemplateJSON, &provider.PlanTemplateJSON, &provider.ImportOptionsJSON, &provider.ManifestJSON, ); err != nil { return Provider{}, err } return provider, nil } func (r *ProvidersRepo) Create(ctx context.Context, provider Provider) (int64, error) { providerID := strings.TrimSpace(provider.ProviderID) displayName := strings.TrimSpace(provider.DisplayName) baseURL := strings.TrimSpace(provider.BaseURL) platform := strings.TrimSpace(provider.Platform) manifestJSON := strings.TrimSpace(provider.ManifestJSON) if manifestJSON == "" { manifestJSON = "{}" } switch { case provider.PackID <= 0: return 0, fmt.Errorf("pack_id is required") case providerID == "": return 0, fmt.Errorf("provider_id is required") case displayName == "": return 0, fmt.Errorf("display_name is required") case baseURL == "": return 0, fmt.Errorf("base_url is required") case platform == "": return 0, fmt.Errorf("platform is required") } result, err := r.db.ExecContext( ctx, `INSERT INTO providers (pack_id, provider_id, display_name, base_url, platform, account_type, default_models_json, smoke_test_model, group_template_json, channel_template_json, plan_template_json, import_options_json, manifest_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, provider.PackID, providerID, displayName, baseURL, platform, strings.TrimSpace(provider.AccountType), defaultJSONArray(provider.DefaultModelsJSON), strings.TrimSpace(provider.SmokeTestModel), defaultJSONObject(provider.GroupTemplateJSON), defaultJSONObject(provider.ChannelTemplateJSON), defaultJSONObject(provider.PlanTemplateJSON), defaultJSONObject(provider.ImportOptionsJSON), manifestJSON, ) if err != nil { return 0, fmt.Errorf("insert provider %q: %w", providerID, err) } id, err := result.LastInsertId() if err != nil { return 0, fmt.Errorf("read inserted provider id for %q: %w", providerID, err) } return id, nil } func (r *ProvidersRepo) Upsert(ctx context.Context, provider Provider) (int64, error) { providerID := strings.TrimSpace(provider.ProviderID) displayName := strings.TrimSpace(provider.DisplayName) baseURL := strings.TrimSpace(provider.BaseURL) platform := strings.TrimSpace(provider.Platform) manifestJSON := strings.TrimSpace(provider.ManifestJSON) if manifestJSON == "" { manifestJSON = "{}" } switch { case provider.PackID <= 0: return 0, fmt.Errorf("pack_id is required") case providerID == "": return 0, fmt.Errorf("provider_id is required") case displayName == "": return 0, fmt.Errorf("display_name is required") case baseURL == "": return 0, fmt.Errorf("base_url is required") case platform == "": return 0, fmt.Errorf("platform is required") } result, err := r.db.ExecContext( ctx, `INSERT INTO providers (pack_id, provider_id, display_name, base_url, platform, account_type, default_models_json, smoke_test_model, group_template_json, channel_template_json, plan_template_json, import_options_json, manifest_json) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) ON CONFLICT(pack_id, provider_id) DO UPDATE SET display_name = excluded.display_name, base_url = excluded.base_url, platform = excluded.platform, account_type = excluded.account_type, default_models_json = excluded.default_models_json, smoke_test_model = excluded.smoke_test_model, group_template_json = excluded.group_template_json, channel_template_json = excluded.channel_template_json, plan_template_json = excluded.plan_template_json, import_options_json = excluded.import_options_json, manifest_json = excluded.manifest_json`, provider.PackID, providerID, displayName, baseURL, platform, strings.TrimSpace(provider.AccountType), defaultJSONArray(provider.DefaultModelsJSON), strings.TrimSpace(provider.SmokeTestModel), defaultJSONObject(provider.GroupTemplateJSON), defaultJSONObject(provider.ChannelTemplateJSON), defaultJSONObject(provider.PlanTemplateJSON), defaultJSONObject(provider.ImportOptionsJSON), manifestJSON, ) if err != nil { return 0, fmt.Errorf("upsert provider %q: %w", providerID, err) } id, err := result.LastInsertId() if err == nil && id > 0 { return id, nil } persisted, getErr := r.GetByPackIDAndProviderID(ctx, provider.PackID, providerID) if getErr != nil { if err != nil { return 0, fmt.Errorf("read upserted provider %q: %w", providerID, getErr) } return 0, getErr } return persisted.ID, nil } func defaultJSONObject(value string) string { if strings.TrimSpace(value) == "" { return "{}" } return value } func defaultJSONArray(value string) string { if strings.TrimSpace(value) == "" { return "[]" } return value }