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.
This commit is contained in:
@@ -0,0 +1,122 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user