Files
archivdms/internal/storage/ollama_config.go
T
patrick 9a24ea29e1 FDN-01: repository & projektgerüst
Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/<ticket>-<slug>-Branches, Ticket-Prefix in Commit-Nachricht) etabliert.
2026-08-11 21:27:53 +02:00

123 lines
4.6 KiB
Go

package storage
import (
"context"
"errors"
"fmt"
"strings"
"time"
"github.com/jackc/pgx/v5"
)
// OllamaConfig is the per-tenant connection configuration for an EXTERNAL,
// already-running Ollama server (never installed on the archivdms host — the
// URL/port is provided by the tenant admin). It gates the optional 'ollama'
// metadata-suggestion provider. BaseURL is an internal network URL, not a
// secret, so it is returned to the API as-is (unlike the LDAP bind password).
type OllamaConfig struct {
TenantID int64 `json:"tenant_id"`
Enabled bool `json:"enabled"`
BaseURL string `json:"base_url"`
Model string `json:"model"`
TimeoutSeconds int `json:"timeout_seconds"`
UpdatedAt time.Time `json:"updated_at"`
}
// defaultOllamaTimeoutSeconds is used for rows that predate the column default
// or were never configured, so GetOllamaConfig never returns a zero timeout.
const defaultOllamaTimeoutSeconds = 30
// initOllamaConfigSchema creates the tenant_ollama_config table. Idempotent,
// called from (*Store).initSchema. Documented (not executed) in
// migrations/017_tenant_ollama_config.sql.
//
// No FK enforcement beyond the documented REFERENCES: consistent with the rest
// of the schema (tenant_id is a plain BIGINT elsewhere); the PRIMARY KEY gives
// the one-row-per-tenant upsert target.
func (s *Store) initOllamaConfigSchema(ctx context.Context) error {
_, err := s.db.Exec(ctx, `
CREATE TABLE IF NOT EXISTS tenant_ollama_config (
tenant_id BIGINT PRIMARY KEY,
enabled BOOLEAN NOT NULL DEFAULT false,
base_url TEXT NOT NULL DEFAULT '',
model TEXT NOT NULL DEFAULT '',
timeout_seconds INT NOT NULL DEFAULT 30,
updated_at TIMESTAMPTZ NOT NULL DEFAULT now()
);
`)
if err != nil {
return fmt.Errorf("storage: create tenant_ollama_config table: %w", err)
}
return nil
}
// GetOllamaConfig returns the tenant's Ollama connection config. When no row
// exists it returns a zero/default (disabled, empty URL/model, default
// timeout) config and NO error — callers treat "not configured" as "disabled".
func (s *Store) GetOllamaConfig(ctx context.Context, tenantID int64) (*OllamaConfig, error) {
row := s.db.QueryRow(ctx, `
SELECT tenant_id, enabled, base_url, model, timeout_seconds, updated_at
FROM tenant_ollama_config WHERE tenant_id = $1`, tenantID)
var c OllamaConfig
err := row.Scan(&c.TenantID, &c.Enabled, &c.BaseURL, &c.Model, &c.TimeoutSeconds, &c.UpdatedAt)
if err != nil {
if errors.Is(err, pgx.ErrNoRows) {
return &OllamaConfig{
TenantID: tenantID,
Enabled: false,
TimeoutSeconds: defaultOllamaTimeoutSeconds,
}, nil
}
return nil, fmt.Errorf("storage: get ollama config: %w", err)
}
if c.TimeoutSeconds <= 0 {
c.TimeoutSeconds = defaultOllamaTimeoutSeconds
}
return &c, nil
}
// UpsertOllamaConfig creates or updates the tenant's Ollama connection config.
// When enabled is true, base_url (http/https prefixed) and model must be set
// and timeout_seconds must be in [5,120]; when disabled the fields may be empty
// (the switch can be flipped off without wiping the stored URL/model, but the
// values are still range-checked when present).
func (s *Store) UpsertOllamaConfig(ctx context.Context, tenantID int64, enabled bool, baseURL, model string, timeoutSeconds int) error {
baseURL = strings.TrimSpace(baseURL)
model = strings.TrimSpace(model)
if timeoutSeconds == 0 {
timeoutSeconds = defaultOllamaTimeoutSeconds
}
if timeoutSeconds < 5 || timeoutSeconds > 120 {
return fmt.Errorf("Timeout muss zwischen 5 und 120 Sekunden liegen")
}
if enabled {
if baseURL == "" || model == "" {
return fmt.Errorf("Server-URL und Modell müssen ausgefüllt sein, um Ollama zu aktivieren")
}
if !strings.HasPrefix(baseURL, "http://") && !strings.HasPrefix(baseURL, "https://") {
return fmt.Errorf("Server-URL muss mit http:// oder https:// beginnen")
}
}
// Normalise the base URL by stripping a trailing slash so the client can
// always append "/api/generate" without producing a double slash.
baseURL = strings.TrimRight(baseURL, "/")
_, err := s.db.Exec(ctx, `
INSERT INTO tenant_ollama_config (tenant_id, enabled, base_url, model, timeout_seconds, updated_at)
VALUES ($1, $2, $3, $4, $5, now())
ON CONFLICT (tenant_id) DO UPDATE SET
enabled = EXCLUDED.enabled,
base_url = EXCLUDED.base_url,
model = EXCLUDED.model,
timeout_seconds = EXCLUDED.timeout_seconds,
updated_at = now()`,
tenantID, enabled, baseURL, model, timeoutSeconds)
if err != nil {
return fmt.Errorf("storage: upsert ollama config: %w", err)
}
return nil
}