Changeset 0.9.0 (#50)
This commit is contained in:
194
server/store/postgres/provider.go
Normal file
194
server/store/postgres/provider.go
Normal file
@@ -0,0 +1,194 @@
|
||||
package postgres
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
"git.gobha.me/xcaliber/chat-switchboard/models"
|
||||
)
|
||||
|
||||
type ProviderStore struct{}
|
||||
|
||||
func NewProviderStore() *ProviderStore { return &ProviderStore{} }
|
||||
|
||||
const providerCols = `id, scope, owner_id, name, provider, endpoint, api_key_enc,
|
||||
model_default, config, headers, settings, is_active, is_private, created_at, updated_at`
|
||||
|
||||
func (s *ProviderStore) Create(ctx context.Context, cfg *models.ProviderConfig) error {
|
||||
return DB.QueryRowContext(ctx, `
|
||||
INSERT INTO provider_configs (scope, owner_id, name, provider, endpoint, api_key_enc,
|
||||
model_default, config, headers, settings, is_active, is_private)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
|
||||
RETURNING id, created_at, updated_at`,
|
||||
cfg.Scope, models.NullString(cfg.OwnerID), cfg.Name, cfg.Provider, cfg.Endpoint,
|
||||
cfg.APIKeyEnc, cfg.ModelDefault,
|
||||
ToJSON(cfg.Config), ToJSON(cfg.Headers), ToJSON(cfg.Settings),
|
||||
cfg.IsActive, cfg.IsPrivate,
|
||||
).Scan(&cfg.ID, &cfg.CreatedAt, &cfg.UpdatedAt)
|
||||
}
|
||||
|
||||
func (s *ProviderStore) GetByID(ctx context.Context, id string) (*models.ProviderConfig, error) {
|
||||
row := DB.QueryRowContext(ctx,
|
||||
fmt.Sprintf("SELECT %s FROM provider_configs WHERE id = $1", providerCols), id)
|
||||
return scanProvider(row)
|
||||
}
|
||||
|
||||
func (s *ProviderStore) Update(ctx context.Context, id string, patch models.ProviderConfigPatch) error {
|
||||
b := NewUpdate("provider_configs")
|
||||
if patch.Name != nil {
|
||||
b.Set("name", *patch.Name)
|
||||
}
|
||||
if patch.Endpoint != nil {
|
||||
b.Set("endpoint", *patch.Endpoint)
|
||||
}
|
||||
if patch.APIKeyEnc != nil {
|
||||
b.Set("api_key_enc", *patch.APIKeyEnc)
|
||||
}
|
||||
if patch.ModelDefault != nil {
|
||||
b.Set("model_default", *patch.ModelDefault)
|
||||
}
|
||||
if patch.Config != nil {
|
||||
b.SetJSON("config", patch.Config)
|
||||
}
|
||||
if patch.Headers != nil {
|
||||
b.SetJSON("headers", patch.Headers)
|
||||
}
|
||||
if patch.Settings != nil {
|
||||
b.SetJSON("settings", patch.Settings)
|
||||
}
|
||||
if patch.IsActive != nil {
|
||||
b.Set("is_active", *patch.IsActive)
|
||||
}
|
||||
if patch.IsPrivate != nil {
|
||||
b.Set("is_private", *patch.IsPrivate)
|
||||
}
|
||||
if !b.HasSets() {
|
||||
return nil
|
||||
}
|
||||
b.Where("id", id)
|
||||
_, err := b.Exec(DB)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ProviderStore) Delete(ctx context.Context, id string) error {
|
||||
_, err := DB.ExecContext(ctx, "DELETE FROM provider_configs WHERE id = $1", id)
|
||||
return err
|
||||
}
|
||||
|
||||
func (s *ProviderStore) ListGlobal(ctx context.Context) ([]models.ProviderConfig, error) {
|
||||
return s.listByScope(ctx, models.ScopeGlobal, "")
|
||||
}
|
||||
|
||||
func (s *ProviderStore) ListForTeam(ctx context.Context, teamID string) ([]models.ProviderConfig, error) {
|
||||
return s.listByScope(ctx, models.ScopeTeam, teamID)
|
||||
}
|
||||
|
||||
func (s *ProviderStore) ListForUser(ctx context.Context, userID string) ([]models.ProviderConfig, error) {
|
||||
return s.listByScope(ctx, models.ScopePersonal, userID)
|
||||
}
|
||||
|
||||
// ListAccessible returns all provider configs a user can access:
|
||||
// global + team (for teams they belong to) + personal.
|
||||
func (s *ProviderStore) ListAccessible(ctx context.Context, userID string) ([]models.ProviderConfig, error) {
|
||||
rows, err := DB.QueryContext(ctx, fmt.Sprintf(`
|
||||
SELECT %s FROM provider_configs
|
||||
WHERE is_active = true AND (
|
||||
scope = 'global'
|
||||
OR (scope = 'personal' AND owner_id = $1)
|
||||
OR (scope = 'team' AND owner_id IN (
|
||||
SELECT team_id FROM team_members WHERE user_id = $1
|
||||
))
|
||||
)
|
||||
ORDER BY scope, name`, providerCols), userID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanProviders(rows)
|
||||
}
|
||||
|
||||
// UserCanAccess checks if a user can use a specific provider config.
|
||||
func (s *ProviderStore) UserCanAccess(ctx context.Context, userID, configID string) (bool, error) {
|
||||
var exists bool
|
||||
err := DB.QueryRowContext(ctx, `
|
||||
SELECT EXISTS(
|
||||
SELECT 1 FROM provider_configs
|
||||
WHERE id = $2 AND is_active = true AND (
|
||||
scope = 'global'
|
||||
OR (scope = 'personal' AND owner_id = $1)
|
||||
OR (scope = 'team' AND owner_id IN (
|
||||
SELECT team_id FROM team_members WHERE user_id = $1
|
||||
))
|
||||
)
|
||||
)`, userID, configID).Scan(&exists)
|
||||
return exists, err
|
||||
}
|
||||
|
||||
// ── Internal helpers ────────────────────────
|
||||
|
||||
func (s *ProviderStore) listByScope(ctx context.Context, scope, ownerID string) ([]models.ProviderConfig, error) {
|
||||
var rows *sql.Rows
|
||||
var err error
|
||||
if scope == models.ScopeGlobal {
|
||||
rows, err = DB.QueryContext(ctx,
|
||||
fmt.Sprintf("SELECT %s FROM provider_configs WHERE scope = $1 AND is_active = true ORDER BY name", providerCols),
|
||||
scope)
|
||||
} else {
|
||||
rows, err = DB.QueryContext(ctx,
|
||||
fmt.Sprintf("SELECT %s FROM provider_configs WHERE scope = $1 AND owner_id = $2 AND is_active = true ORDER BY name", providerCols),
|
||||
scope, ownerID)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
return scanProviders(rows)
|
||||
}
|
||||
|
||||
func scanProvider(row *sql.Row) (*models.ProviderConfig, error) {
|
||||
var p models.ProviderConfig
|
||||
var ownerID, modelDefault sql.NullString
|
||||
var configJSON, headersJSON, settingsJSON []byte
|
||||
err := row.Scan(
|
||||
&p.ID, &p.Scope, &ownerID, &p.Name, &p.Provider, &p.Endpoint,
|
||||
&p.APIKeyEnc, &modelDefault,
|
||||
&configJSON, &headersJSON, &settingsJSON,
|
||||
&p.IsActive, &p.IsPrivate, &p.CreatedAt, &p.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.OwnerID = NullableStringPtr(ownerID)
|
||||
p.ModelDefault = modelDefault.String
|
||||
json.Unmarshal(configJSON, &p.Config)
|
||||
json.Unmarshal(headersJSON, &p.Headers)
|
||||
json.Unmarshal(settingsJSON, &p.Settings)
|
||||
return &p, nil
|
||||
}
|
||||
|
||||
func scanProviders(rows *sql.Rows) ([]models.ProviderConfig, error) {
|
||||
var result []models.ProviderConfig
|
||||
for rows.Next() {
|
||||
var p models.ProviderConfig
|
||||
var ownerID, modelDefault sql.NullString
|
||||
var configJSON, headersJSON, settingsJSON []byte
|
||||
err := rows.Scan(
|
||||
&p.ID, &p.Scope, &ownerID, &p.Name, &p.Provider, &p.Endpoint,
|
||||
&p.APIKeyEnc, &modelDefault,
|
||||
&configJSON, &headersJSON, &settingsJSON,
|
||||
&p.IsActive, &p.IsPrivate, &p.CreatedAt, &p.UpdatedAt,
|
||||
)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
p.OwnerID = NullableStringPtr(ownerID)
|
||||
p.ModelDefault = modelDefault.String
|
||||
json.Unmarshal(configJSON, &p.Config)
|
||||
json.Unmarshal(headersJSON, &p.Headers)
|
||||
json.Unmarshal(settingsJSON, &p.Settings)
|
||||
result = append(result, p)
|
||||
}
|
||||
return result, rows.Err()
|
||||
}
|
||||
Reference in New Issue
Block a user