Git-Repository für bestehenden archivdms-Code initialisiert, Branch-/Commit-Konvention (feature/<ticket>-<slug>-Branches, Ticket-Prefix in Commit-Nachricht) etabliert.
123 lines
4.6 KiB
Go
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
|
|
}
|