Compare commits
2
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7515b8db4d | ||
|
|
cd5ca8da53 |
No files matched your search
@@ -4,6 +4,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "ai/**"
|
||||
- "common/**"
|
||||
|
||||
jobs:
|
||||
pflichttest-gate:
|
||||
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "archive/**"
|
||||
- "common/**"
|
||||
|
||||
jobs:
|
||||
pflichttest-gate:
|
||||
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "mail/**"
|
||||
- "common/**"
|
||||
|
||||
jobs:
|
||||
pflichttest-gate:
|
||||
|
||||
@@ -4,6 +4,7 @@ on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "workflow/**"
|
||||
- "common/**"
|
||||
|
||||
jobs:
|
||||
pflichttest-gate:
|
||||
|
||||
@@ -64,6 +64,8 @@ Kein Modul kopiert die Daten eines anderen — stattdessen ein wiederkehrendes *
|
||||
|
||||
Gemeinsamer Nenner aller drei: **Referenz statt Kopie, Adapter statt Direktkopplung.**
|
||||
|
||||
Go-`internal/`-Pakete sind zwischen den Modulen nicht sichtbar; identische Querschnittspakete werden deshalb im geteilten Modul `common/` gehalten (per `replace` in den `go.mod` eingebunden, keine eigenen Abhängigkeiten außer der Standardbibliothek). Pilot: `common/httpclient`, `common/apierror`. Fachlich verschiedene Pakete (`audittrail`, `cutoverstage`, `crypto`) bleiben modul-lokal.
|
||||
|
||||
Dasselbe Prinzip gilt auch innerhalb eines Moduls, auf Code-Ebene: Hauptdateien/Entry-Points bleiben klein, Fachlogik wandert in eigene Module. Vor einer neuen Funktions-/Modul-Datei wird geprüft, ob dieselbe Funktionalität nicht schon woanders existiert (auch modulübergreifend, z. B. in Core) — keine doppelten Funktionsdateien. Das eigene Kanban-Planungswerkzeug wurde nach genau diesem Muster korrigiert (sieben `kanban.html` enthielten identisches JS/CSS als Volltext-Duplikat, damals in `_shared/kanban.js`/`kanban.css` ausgelagert) — die Boards selbst sind inzwischen komplett aus dem Repo entfernt (Ticket-Historie liegt in der Gitea-PR-Historie), das Prinzip "keine doppelten Funktionsdateien" gilt unverändert für den eigentlichen NEXARCH-Code.
|
||||
|
||||
## 6. Technische Architektur (Kurzfassung, Details in eigenen Dokumenten)
|
||||
|
||||
@@ -41,9 +41,9 @@ import (
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearchapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/summarize"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/summaryapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/tenantregistryclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/threadsummarize"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/vectorindex"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/tenantregistryclient"
|
||||
)
|
||||
|
||||
const workerPollInterval = 2 * time.Second
|
||||
|
||||
@@ -13,3 +13,7 @@ require (
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
)
|
||||
|
||||
require gitea.perlbach24.de/scripte/nexarch/common v0.0.0
|
||||
|
||||
replace gitea.perlbach24.de/scripte/nexarch/common => ../common
|
||||
@@ -1,72 +0,0 @@
|
||||
// Package auditclient ist ein schlanker HTTP-Client fuer Core AUD-08
|
||||
// (internal/audit, POST /audit/ingest). AI ist ein physisch getrenntes
|
||||
// Go-Modul und kann Cores internal/audit daher nicht direkt importieren -
|
||||
// nur ueber dessen HTTP-Schnittstelle. Gleiches Muster wie
|
||||
// connect/internal/auditclient (ENG-06) und archive/internal/auditclient.
|
||||
package auditclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/httpclient"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
ServiceToken string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func New(baseURL, serviceToken string) *Client {
|
||||
return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: httpclient.Default()}
|
||||
}
|
||||
|
||||
type ingestRequest struct {
|
||||
TenantSlug string `json:"tenant_slug"`
|
||||
Actor string `json:"actor"`
|
||||
Action string `json:"action"`
|
||||
Target string `json:"target"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
// Ingest meldet EIN Audit-Ereignis an Core. Absichtlich keine
|
||||
// Update-/Delete-Methode: der Ingest-Endpunkt (AUD-08) ruft denselben
|
||||
// alleinigen, anfuegenden Schreibpfad wie core-interne Aufrufer auf
|
||||
// (audit.Log.Record), gegen den Core selbst per Datenbank-Trigger jede
|
||||
// nachtraegliche Aenderung/Loeschung verweigert (AUD-02).
|
||||
func (c *Client) Ingest(ctx context.Context, tenantSlug, actor, action, target string, metadata map[string]any) error {
|
||||
body, err := json.Marshal(ingestRequest{
|
||||
TenantSlug: tenantSlug, Actor: actor, Action: action, Target: target, Metadata: metadata,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("auditclient: request kodieren: %w", err)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/audit/ingest", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("auditclient: request bauen: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Service-Token", c.ServiceToken)
|
||||
|
||||
resp, err := c.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("auditclient: aufruf fehlgeschlagen: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
return fmt.Errorf("auditclient: unerwarteter status %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
}
|
||||
return httpclient.Default()
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
package auditclient
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestIngest_SendsExpectedRequest(t *testing.T) {
|
||||
var gotToken string
|
||||
var gotBody ingestRequest
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
gotToken = r.Header.Get("X-Service-Token")
|
||||
_ = json.NewDecoder(r.Body).Decode(&gotBody)
|
||||
w.WriteHeader(http.StatusAccepted)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := New(srv.URL, "test-token")
|
||||
err := c.Ingest(context.Background(), "tenant_a", "svc:ai", "ai.decision.classify", "doc:1",
|
||||
map[string]any{"model": "llama3", "prompt_version": 2})
|
||||
if err != nil {
|
||||
t.Fatalf("Ingest: %v", err)
|
||||
}
|
||||
if gotToken != "test-token" {
|
||||
t.Errorf("token = %q, want test-token", gotToken)
|
||||
}
|
||||
if gotBody.Action != "ai.decision.classify" || gotBody.Target != "doc:1" {
|
||||
t.Errorf("unerwarteter request: %+v", gotBody)
|
||||
}
|
||||
if gotBody.Metadata["model"] != "llama3" {
|
||||
t.Errorf("metadata.model = %v, want llama3", gotBody.Metadata["model"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestIngest_UnexpectedStatusIsError(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Error(w, "zugriff verweigert", http.StatusForbidden)
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
c := New(srv.URL, "test-token")
|
||||
if err := c.Ingest(context.Background(), "tenant_a", "svc:ai", "ai.decision.classify", "doc:1", nil); err == nil {
|
||||
t.Fatal("erwartet fehler bei status 403, habe nil")
|
||||
}
|
||||
}
|
||||
@@ -9,11 +9,11 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/mock"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
)
|
||||
|
||||
var testSchema = Schema{Categories: []string{"rechnung", "vertrag", "kuendigung"}}
|
||||
|
||||
@@ -8,7 +8,7 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
)
|
||||
|
||||
// Decision ist eine einzelne, abgeschlossene KI-gestuetzte Entscheidung
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
)
|
||||
|
||||
type recordedRequest struct {
|
||||
|
||||
@@ -7,10 +7,10 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/loadtest"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider/ollama"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/loadtest"
|
||||
)
|
||||
|
||||
// QA-08 Pruefung 1 (AK1): echte parallele Chat-Anfragen gegen den realen,
|
||||
|
||||
@@ -12,8 +12,8 @@ import (
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/docchat"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/vectorindex"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractvalidate"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/invoiceextract"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/extractmapping"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/metadatapushclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Package httpclient liefert den HTTP-Client fuer ausgehende Dienstaufrufe
|
||||
// (Kopie des Musters aus connect/internal/httpclient, ENG-06): Zeitlimit
|
||||
// statt endlosem Warten, keine Weiterleitungen (ein Service-Token wuerde
|
||||
// sonst an das Weiterleitungsziel mitgesendet).
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultTimeout = 15 * time.Second
|
||||
|
||||
var shared = New(DefaultTimeout)
|
||||
|
||||
func Default() *http.Client { return shared }
|
||||
|
||||
func New(timeout time.Duration) *http.Client {
|
||||
return &http.Client{
|
||||
Timeout: timeout,
|
||||
CheckRedirect: func(*http.Request, []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,29 +0,0 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestNew_DoesNotFollowRedirects(t *testing.T) {
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Fatal("weiterleitungsziel haette nie erreicht werden duerfen")
|
||||
}))
|
||||
defer target.Close()
|
||||
|
||||
redirector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, target.URL, http.StatusFound)
|
||||
}))
|
||||
defer redirector.Close()
|
||||
|
||||
resp, err := Default().Get(redirector.URL)
|
||||
if err != nil {
|
||||
t.Fatalf("Get: %v", err)
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode != http.StatusFound {
|
||||
t.Fatalf("status = %d, want %d (keine weiterleitung gefolgt)", resp.StatusCode, http.StatusFound)
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
// Package loadtest implementiert die fuer QA-07/QA-08 gemeinsam genutzte
|
||||
// Last- und Leistungsmessung: parallele Sessions ausfuehren, Latenzen
|
||||
// aufzeichnen, Durchsatz bestimmen. k6/vegeta sind auf diesem Rechner nicht
|
||||
// installierbar (Projektregel: keine zusaetzlichen Toolchains/Dienste
|
||||
// installieren) — echter Ersatz ohne externe Abhaengigkeit: reale
|
||||
// nebenlaeufige Aufrufe ueber die Go-Standardbibliothek, gegen den echten,
|
||||
// in dieser Sitzung gebauten Code. Eigenstaendige Kopie aus
|
||||
// mail/internal/loadtest, da Go-Module keine internal/-Pakete teilen.
|
||||
package loadtest
|
||||
|
||||
import (
|
||||
"context"
|
||||
"os"
|
||||
"runtime"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Result fasst Latenz- und Durchsatzwerte eines Lasttest-Laufs zusammen.
|
||||
type Result struct {
|
||||
Protocol string
|
||||
Concurrency int
|
||||
Sessions int
|
||||
Errors int
|
||||
Duration time.Duration
|
||||
Latencies []time.Duration // aufsteigend sortiert
|
||||
}
|
||||
|
||||
// Throughput liefert Sessions pro Sekunde über die Gesamtlaufzeit.
|
||||
func (r Result) Throughput() float64 {
|
||||
if r.Duration <= 0 {
|
||||
return 0
|
||||
}
|
||||
return float64(r.Sessions) / r.Duration.Seconds()
|
||||
}
|
||||
|
||||
// Percentile liefert die Latenz am p-ten Perzentil (0.0–1.0).
|
||||
func (r Result) Percentile(p float64) time.Duration {
|
||||
if len(r.Latencies) == 0 {
|
||||
return 0
|
||||
}
|
||||
idx := int(p * float64(len(r.Latencies)-1))
|
||||
if idx < 0 {
|
||||
idx = 0
|
||||
}
|
||||
if idx >= len(r.Latencies) {
|
||||
idx = len(r.Latencies) - 1
|
||||
}
|
||||
return r.Latencies[idx]
|
||||
}
|
||||
|
||||
// Max liefert die höchste gemessene Latenz.
|
||||
func (r Result) Max() time.Duration {
|
||||
if len(r.Latencies) == 0 {
|
||||
return 0
|
||||
}
|
||||
return r.Latencies[len(r.Latencies)-1]
|
||||
}
|
||||
|
||||
// Run führt totalSessions Sessions über maximal concurrency parallele
|
||||
// Worker aus und misst je Session die Latenz von sessionFunc.
|
||||
func Run(ctx context.Context, protocol string, concurrency, totalSessions int, sessionFunc func(ctx context.Context) error) Result {
|
||||
var mu sync.Mutex
|
||||
latencies := make([]time.Duration, 0, totalSessions)
|
||||
errCount := 0
|
||||
|
||||
sem := make(chan struct{}, concurrency)
|
||||
var wg sync.WaitGroup
|
||||
start := time.Now()
|
||||
for i := 0; i < totalSessions; i++ {
|
||||
sem <- struct{}{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
defer func() { <-sem }()
|
||||
t0 := time.Now()
|
||||
err := sessionFunc(ctx)
|
||||
elapsed := time.Since(t0)
|
||||
mu.Lock()
|
||||
latencies = append(latencies, elapsed)
|
||||
if err != nil {
|
||||
errCount++
|
||||
}
|
||||
mu.Unlock()
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
duration := time.Since(start)
|
||||
|
||||
sort.Slice(latencies, func(i, j int) bool { return latencies[i] < latencies[j] })
|
||||
return Result{
|
||||
Protocol: protocol,
|
||||
Concurrency: concurrency,
|
||||
Sessions: totalSessions,
|
||||
Errors: errCount,
|
||||
Duration: duration,
|
||||
Latencies: latencies,
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceSample ist eine Momentaufnahme des Ressourcenverbrauchs DIESES
|
||||
// Prozesses: Heap über die Go-Runtime, kumulierte CPU-Zeit über
|
||||
// /proc/self/stat (Linux, kein externes Werkzeug nötig).
|
||||
type ResourceSample struct {
|
||||
HeapAllocBytes uint64
|
||||
CPUTimeSeconds float64
|
||||
}
|
||||
|
||||
func SampleResources() ResourceSample {
|
||||
var m runtime.MemStats
|
||||
runtime.ReadMemStats(&m)
|
||||
return ResourceSample{HeapAllocBytes: m.HeapAlloc, CPUTimeSeconds: readSelfCPUTimeSeconds()}
|
||||
}
|
||||
|
||||
// readSelfCPUTimeSeconds liefert die kumulierte User+System-CPU-Zeit dieses
|
||||
// Prozesses in Sekunden. Liefert 0, wenn /proc/self/stat nicht lesbar ist
|
||||
// (z. B. auf Nicht-Linux-Systemen) — Aufrufer behandeln das als "keine
|
||||
// CPU-Messung verfügbar", kein harter Fehler.
|
||||
func readSelfCPUTimeSeconds() float64 {
|
||||
data, err := os.ReadFile("/proc/self/stat")
|
||||
if err != nil {
|
||||
return 0
|
||||
}
|
||||
fields := strings.Fields(string(data))
|
||||
if len(fields) < 15 {
|
||||
return 0
|
||||
}
|
||||
utime, err1 := strconv.ParseFloat(fields[13], 64)
|
||||
stime, err2 := strconv.ParseFloat(fields[14], 64)
|
||||
if err1 != nil || err2 != nil {
|
||||
return 0
|
||||
}
|
||||
const clockTicksPerSecond = 100 // Linux-Standard-USER_HZ
|
||||
return (utime + stime) / clockTicksPerSecond
|
||||
}
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"net/url"
|
||||
"sort"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/moduletype"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -18,6 +18,7 @@ import (
|
||||
// TESTSTRATEGIE-AI.md Abschnitt 3.
|
||||
var sensitivePathPatterns = []*regexp.Regexp{
|
||||
regexp.MustCompile(`(^|/)ai/internal/svcauth/`),
|
||||
regexp.MustCompile(`(^|/)common/svcauth/`),
|
||||
regexp.MustCompile(`(^|/)ai/internal/tenantfeature/`),
|
||||
regexp.MustCompile(`(^|/)ai/internal/semanticsearch/`),
|
||||
regexp.MustCompile(`(^|/)ai/internal/semanticsearchapi/`),
|
||||
|
||||
@@ -16,7 +16,6 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/decisionaudit"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/migrate"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
|
||||
@@ -25,6 +24,7 @@ import (
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/residency"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/tenantfeature"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/testdb"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
)
|
||||
|
||||
// realPool liefert einen frischen, migrierten Pool auf der Wegwerf-DB aus
|
||||
|
||||
@@ -8,8 +8,8 @@ import (
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/limits"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/loadtest"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/provider"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/loadtest"
|
||||
)
|
||||
|
||||
// QA-08 Pruefung 2 (AK2): realistische parallele Klassifizierungs-/
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/residency"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/historysuggest"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/routingfeedback"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/semanticsearch"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/summarize"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/ai/internal/threadsummarize"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
type Server struct {
|
||||
|
||||
@@ -1,20 +0,0 @@
|
||||
// Package svcauth vergleicht Service-Token in konstanter Zeit (eigene Kopie
|
||||
// je Modul, siehe workflow/internal/svcauth — Go-Module teilen keine
|
||||
// internal/-Pakete über Modulgrenzen; Lehre aus RET-14/F-08: Schutz beim
|
||||
// ersten Entwurf einbauen statt nachzuruesten).
|
||||
package svcauth
|
||||
|
||||
import (
|
||||
"crypto/sha256"
|
||||
"crypto/subtle"
|
||||
)
|
||||
|
||||
// Equal vergleicht die SHA-256-Werte beider Eingaben in konstanter Zeit.
|
||||
// Ein leeres erwartetes Token stimmt nie.
|
||||
func Equal(got, want string) bool {
|
||||
if want == "" {
|
||||
return false
|
||||
}
|
||||
g, w := sha256.Sum256([]byte(got)), sha256.Sum256([]byte(want))
|
||||
return subtle.ConstantTimeCompare(g[:], w[:]) == 1
|
||||
}
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/complianceapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -10,10 +10,10 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/kennzahlenapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -13,10 +13,10 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/legalholdapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/moduleadapter"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/tenantregistryclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/restoretest"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/rbacclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/retentionapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/scrub"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
@@ -13,3 +13,7 @@ require (
|
||||
golang.org/x/sync v0.22.0 // indirect
|
||||
golang.org/x/text v0.41.0 // indirect
|
||||
)
|
||||
|
||||
require gitea.perlbach24.de/scripte/nexarch/common v0.0.0
|
||||
|
||||
replace gitea.perlbach24.de/scripte/nexarch/common => ../common
|
||||
@@ -33,7 +33,7 @@ func moduleRoot(t *testing.T) string {
|
||||
// Antwort bzw. der rohe ListenAndServe-Aufruf genau die erlaubte
|
||||
// Implementierung der Regel selbst ist (API-20/API-21), kein Verstoss.
|
||||
// Das Archive-Modul hat keine weiteren, physisch verschachtelten Go-Module.
|
||||
var exempt = []string{"internal/apierror", "internal/httpharden"}
|
||||
var exempt = []string{"internal/httpharden"}
|
||||
|
||||
// TestNoErrorTextLeaksIntoHTTPResponses ist Akzeptanzkriterium 1: kein
|
||||
// err.Error() direkt in einer HTTP-Antwort, ausser ueber apierror.
|
||||
|
||||
@@ -1,70 +0,0 @@
|
||||
// Package auditclient ist ein schlanker HTTP-Client für Core AUD-08
|
||||
// (internal/audit, POST /audit/ingest). Archive ist ein physisch
|
||||
// getrenntes Go-Modul und kann Cores internal/audit daher nicht direkt
|
||||
// importieren — nur über dessen HTTP-Schnittstelle. Gleiches Muster wie
|
||||
// archive/internal/notifyclient (CFG-05).
|
||||
package auditclient
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpclient"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
BaseURL string
|
||||
ServiceToken string
|
||||
HTTPClient *http.Client
|
||||
}
|
||||
|
||||
func New(baseURL, serviceToken string) *Client {
|
||||
return &Client{BaseURL: baseURL, ServiceToken: serviceToken, HTTPClient: httpclient.Default()}
|
||||
}
|
||||
|
||||
type ingestRequest struct {
|
||||
TenantSlug string `json:"tenant_slug"`
|
||||
Actor string `json:"actor"`
|
||||
Action string `json:"action"`
|
||||
Target string `json:"target"`
|
||||
Metadata map[string]any `json:"metadata"`
|
||||
}
|
||||
|
||||
// Ingest meldet EIN Audit-Ereignis an Core. Jeder Fehler (Transport,
|
||||
// Timeout, unerwarteter Status) wird als Fehler zurückgegeben — der
|
||||
// Aufrufer entscheidet, ob/wie er das nachbehandelt (siehe
|
||||
// archive/internal/audittrail für den outage-toleranten Wrapper).
|
||||
func (c *Client) Ingest(ctx context.Context, tenantSlug, actor, action, target string, metadata map[string]any) error {
|
||||
body, err := json.Marshal(ingestRequest{
|
||||
TenantSlug: tenantSlug, Actor: actor, Action: action, Target: target, Metadata: metadata,
|
||||
})
|
||||
if err != nil {
|
||||
return fmt.Errorf("auditclient: request kodieren: %w", err)
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.BaseURL+"/audit/ingest", bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return fmt.Errorf("auditclient: request bauen: %w", err)
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.Header.Set("X-Service-Token", c.ServiceToken)
|
||||
|
||||
resp, err := c.httpClient().Do(req)
|
||||
if err != nil {
|
||||
return fmt.Errorf("auditclient: aufruf fehlgeschlagen: %w", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
|
||||
if resp.StatusCode != http.StatusAccepted {
|
||||
return fmt.Errorf("auditclient: unerwarteter status %d", resp.StatusCode)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) httpClient() *http.Client {
|
||||
if c.HTTPClient != nil {
|
||||
return c.HTTPClient
|
||||
}
|
||||
return httpclient.Default()
|
||||
}
|
||||
@@ -20,7 +20,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
)
|
||||
|
||||
type Recorder struct {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
)
|
||||
|
||||
func setupOutboxTest(t *testing.T) *pgxpool.Pool {
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/audittrail"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/referenceset"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
)
|
||||
|
||||
@@ -14,7 +14,7 @@ import (
|
||||
"net/url"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/ediscovery"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
)
|
||||
|
||||
const ModuleTypeDMSDocument = "dms_document"
|
||||
|
||||
@@ -1,99 +0,0 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"regexp"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDefaultHasTimeout(t *testing.T) {
|
||||
if Default().Timeout != DefaultTimeout || DefaultTimeout <= 0 {
|
||||
t.Fatalf("timeout = %s", Default().Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientGivesUpOnHangingServer(t *testing.T) {
|
||||
release := make(chan struct{})
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-release }))
|
||||
defer srv.Close()
|
||||
defer close(release)
|
||||
|
||||
start := time.Now()
|
||||
resp, err := New(200 * time.Millisecond).Get(srv.URL)
|
||||
if err == nil {
|
||||
_ = resp.Body.Close()
|
||||
t.Fatal("kein fehler bei haengendem server")
|
||||
}
|
||||
if elapsed := time.Since(start); elapsed > 5*time.Second {
|
||||
t.Fatalf("aufruf dauerte %s", elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientDoesNotFollowRedirects(t *testing.T) {
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("weiterleitungsziel wurde aufgerufen (Service-Token-Leck)")
|
||||
}))
|
||||
defer target.Close()
|
||||
redirector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, target.URL, http.StatusTemporaryRedirect)
|
||||
}))
|
||||
defer redirector.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPost, redirector.URL, strings.NewReader("x"))
|
||||
req.Header.Set("X-Service-Token", "geheim")
|
||||
resp, err := Default().Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("do: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusTemporaryRedirect {
|
||||
t.Fatalf("status = %d, erwartet 307", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
|
||||
// Architekturtest: im Produktivcode dieses Moduls gibt es keine Aufrufe ueber
|
||||
// den Standard-Client ohne Zeitlimit.
|
||||
func TestNoUnboundedDefaultClientInProductionCode(t *testing.T) {
|
||||
root, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
forbidden := regexp.MustCompile(`http\.DefaultClient|http\.(Get|Post|Head|PostForm)\(`)
|
||||
err = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if d.IsDir() {
|
||||
if n := d.Name(); n == "node_modules" || n == "testdata" || n == ".git" {
|
||||
return filepath.SkipDir
|
||||
}
|
||||
return nil
|
||||
}
|
||||
if !strings.HasSuffix(path, ".go") || strings.HasSuffix(path, "_test.go") {
|
||||
return nil
|
||||
}
|
||||
data, err := os.ReadFile(path)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for i, line := range strings.Split(string(data), "\n") {
|
||||
if t := strings.TrimSpace(line); strings.HasPrefix(t, "//") {
|
||||
continue
|
||||
}
|
||||
if forbidden.MatchString(line) {
|
||||
rel, _ := filepath.Rel(root, path)
|
||||
t.Errorf("%s:%d nutzt den Standard-Client ohne Zeitlimit: %s", rel, i+1, strings.TrimSpace(line))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
@@ -1,116 +0,0 @@
|
||||
// Package httpharden liefert gehaertete http.Server-Grundeinstellungen
|
||||
// (Timeouts, Header-Limit) und ein Body-Limit fuer alle Dienste (QA-15 F-05).
|
||||
// Dieselbe Datei existiert in archive/internal/httpharden und
|
||||
// dms/internal/httpharden, weil die Module getrennte go.mod-Dateien haben.
|
||||
package httpharden
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/apierror"
|
||||
)
|
||||
|
||||
const (
|
||||
DefaultReadHeaderTimeout = 10 * time.Second
|
||||
DefaultReadTimeout = 30 * time.Second
|
||||
DefaultWriteTimeout = 60 * time.Second
|
||||
DefaultIdleTimeout = 120 * time.Second
|
||||
DefaultMaxHeaderBytes = 1 << 20
|
||||
DefaultMaxBodyBytes = 1 << 20
|
||||
|
||||
EnvReadHeaderTimeout = "NEXARCH_HTTP_READ_HEADER_TIMEOUT_SECONDS"
|
||||
EnvReadTimeout = "NEXARCH_HTTP_READ_TIMEOUT_SECONDS"
|
||||
EnvWriteTimeout = "NEXARCH_HTTP_WRITE_TIMEOUT_SECONDS"
|
||||
EnvIdleTimeout = "NEXARCH_HTTP_IDLE_TIMEOUT_SECONDS"
|
||||
EnvMaxBodyBytes = "NEXARCH_HTTP_MAX_BODY_BYTES"
|
||||
)
|
||||
|
||||
// Options weicht vom Standard ab. Nullwerte bedeuten Standard bzw. Env-Wert;
|
||||
// StreamingBodies/StreamingResponses setzen ReadTimeout/WriteTimeout auf 0
|
||||
// (unbegrenzt), damit lange Uploads und Downloads nicht abbrechen. Schutz
|
||||
// gegen langsame Header bleibt ueber ReadHeaderTimeout und IdleTimeout.
|
||||
type Options struct {
|
||||
StreamingBodies bool
|
||||
StreamingResponses bool
|
||||
}
|
||||
|
||||
func envSeconds(name string, def time.Duration) time.Duration {
|
||||
v := os.Getenv(name)
|
||||
if v == "" {
|
||||
return def
|
||||
}
|
||||
n, err := strconv.Atoi(v)
|
||||
if err != nil || n < 1 || n > 3600 {
|
||||
log.Fatalf("%s muss eine ganze Zahl zwischen 1 und 3600 sein, war %q", name, v)
|
||||
}
|
||||
return time.Duration(n) * time.Second
|
||||
}
|
||||
|
||||
// MaxBodyBytes liest die Body-Obergrenze (Standard 1 MiB, Env
|
||||
// NEXARCH_HTTP_MAX_BODY_BYTES, 1 KiB bis 1 GiB).
|
||||
func MaxBodyBytes() int64 {
|
||||
v := os.Getenv(EnvMaxBodyBytes)
|
||||
if v == "" {
|
||||
return DefaultMaxBodyBytes
|
||||
}
|
||||
n, err := strconv.ParseInt(v, 10, 64)
|
||||
if err != nil || n < 1<<10 || n > 1<<30 {
|
||||
log.Fatalf("%s muss zwischen 1024 und 1073741824 liegen, war %q", EnvMaxBodyBytes, v)
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
// NewServer baut einen http.Server mit gehaerteten Timeouts.
|
||||
func NewServer(addr string, h http.Handler, opt Options) *http.Server {
|
||||
s := &http.Server{
|
||||
Addr: addr,
|
||||
Handler: h,
|
||||
ReadHeaderTimeout: envSeconds(EnvReadHeaderTimeout, DefaultReadHeaderTimeout),
|
||||
ReadTimeout: envSeconds(EnvReadTimeout, DefaultReadTimeout),
|
||||
WriteTimeout: envSeconds(EnvWriteTimeout, DefaultWriteTimeout),
|
||||
IdleTimeout: envSeconds(EnvIdleTimeout, DefaultIdleTimeout),
|
||||
MaxHeaderBytes: DefaultMaxHeaderBytes,
|
||||
}
|
||||
if opt.StreamingBodies {
|
||||
s.ReadTimeout = 0
|
||||
}
|
||||
if opt.StreamingResponses {
|
||||
s.WriteTimeout = 0
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
// ListenAndServe ersetzt http.ListenAndServe: JSON-API mit Body-Limit und
|
||||
// (QA-15 F-14) apierror.Normalize aussen um jeden Handler — 5xx-Antworten
|
||||
// verlieren dadurch automatisch jeden Interna-Text (Datenbank-/Pfadfehler),
|
||||
// unabhaengig davon, was der einzelne Handler geschrieben hat.
|
||||
func ListenAndServe(addr string, h http.Handler) error {
|
||||
return NewServer(addr, apierror.Normalize(LimitBody(MaxBodyBytes(), h)), Options{}).ListenAndServe()
|
||||
}
|
||||
|
||||
// ListenAndServeStreaming fuer Dienste mit grossen Uploads/Downloads: keine
|
||||
// globale Body-Grenze und keine Lese-/Schreib-Gesamtzeit, nur Header-Timeout.
|
||||
func ListenAndServeStreaming(addr string, h http.Handler) error {
|
||||
return NewServer(addr, apierror.Normalize(h), Options{StreamingBodies: true, StreamingResponses: true}).ListenAndServe()
|
||||
}
|
||||
|
||||
// LimitBody begrenzt Request-Bodies. Ein Content-Length ueber dem Limit wird
|
||||
// vor dem Handler mit 413 abgewiesen, chunked Bodies ueber MaxBytesReader.
|
||||
func LimitBody(max int64, next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
if r.ContentLength > max {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusRequestEntityTooLarge)
|
||||
_, _ = w.Write([]byte(`{"error":{"code":"payload_too_large","message":"Anfrage zu gross"}}`))
|
||||
return
|
||||
}
|
||||
if r.Body != nil && r.Body != http.NoBody {
|
||||
r.Body = http.MaxBytesReader(w, r.Body, max)
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"encoding/base64"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/svcauth"
|
||||
)
|
||||
|
||||
const (
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
||||
@@ -8,7 +8,7 @@ package tenantregistryclient
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"gitea.perlbach24.de/scripte/nexarch/archive/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
"net/http"
|
||||
"net/url"
|
||||
)
|
||||
|
||||
@@ -17,10 +17,10 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/audit"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/auth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/authtoken"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/lockout"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/moduletoken"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/rbac"
|
||||
|
||||
@@ -18,10 +18,10 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/audit"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/auditadmin"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/db"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/svcauth"
|
||||
)
|
||||
|
||||
|
||||
+1
-1
@@ -5,10 +5,10 @@ import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/audit"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/config"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/db"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/moduledirectory"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/tenant"
|
||||
|
||||
+1
-1
@@ -13,8 +13,8 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/flag"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/kek"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/moduleregistry"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/tenant"
|
||||
|
||||
@@ -16,10 +16,10 @@ import (
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/adminapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/db"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/flag"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/license"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/usage"
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/db"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/metrics"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/notify"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/notifyapi"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/notifyprefs"
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/auth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/moduletrust"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/notifyprefs"
|
||||
)
|
||||
|
||||
@@ -15,8 +15,8 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/flag"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/policy"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/policyapi"
|
||||
)
|
||||
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/auth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/moduletrust"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/rbac"
|
||||
)
|
||||
|
||||
@@ -13,9 +13,9 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/audit"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/flag"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/moduleregistry"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/resync"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/usage"
|
||||
|
||||
@@ -10,7 +10,7 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/secheaders"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,8 +11,8 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/db"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/statuspage"
|
||||
)
|
||||
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
"net/http"
|
||||
"os"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/audit"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/db"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/httpharden"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/svcauth"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/tenant"
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/tenantadmin"
|
||||
|
||||
File renamed without changes.
File renamed without changes.
@@ -12,7 +12,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
File renamed without changes.
@@ -0,0 +1,3 @@
|
||||
module gitea.perlbach24.de/scripte/nexarch/common
|
||||
|
||||
go 1.25.0
|
||||
File renamed without changes.
@@ -57,15 +57,22 @@ func TestClientDoesNotFollowRedirects(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
// Architekturtest: im Produktivcode dieses Moduls gibt es keine Aufrufe ueber
|
||||
// den Standard-Client ohne Zeitlimit.
|
||||
// Architekturtest: im Produktivcode der Module, die dieses Paket nutzen, gibt
|
||||
// es keine Aufrufe ueber den Standard-Client ohne Zeitlimit.
|
||||
func TestNoUnboundedDefaultClientInProductionCode(t *testing.T) {
|
||||
root, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
repoRoot, err := filepath.Abs(filepath.Join("..", ".."))
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, mod := range []string{"common", "dms", "mail", "archive"} {
|
||||
scanNoDefaultClient(t, repoRoot, filepath.Join(repoRoot, mod))
|
||||
}
|
||||
}
|
||||
|
||||
func scanNoDefaultClient(t *testing.T, repoRoot, root string) {
|
||||
t.Helper()
|
||||
forbidden := regexp.MustCompile(`http\.DefaultClient|http\.(Get|Post|Head|PostForm)\(`)
|
||||
err = filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
err := filepath.WalkDir(root, func(path string, d fs.DirEntry, err error) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -87,7 +94,7 @@ func TestNoUnboundedDefaultClientInProductionCode(t *testing.T) {
|
||||
continue
|
||||
}
|
||||
if forbidden.MatchString(line) {
|
||||
rel, _ := filepath.Rel(root, path)
|
||||
rel, _ := filepath.Rel(repoRoot, path)
|
||||
t.Errorf("%s:%d nutzt den Standard-Client ohne Zeitlimit: %s", rel, i+1, strings.TrimSpace(line))
|
||||
}
|
||||
}
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/internal/apierror"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/apierror"
|
||||
)
|
||||
|
||||
const (
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -13,3 +13,7 @@ require (
|
||||
golang.org/x/sync v0.17.0 // indirect
|
||||
golang.org/x/text v0.29.0 // indirect
|
||||
)
|
||||
|
||||
require gitea.perlbach24.de/scripte/nexarch/common v0.0.0
|
||||
|
||||
replace gitea.perlbach24.de/scripte/nexarch/common => ../common
|
||||
@@ -12,7 +12,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
|
||||
@@ -19,7 +19,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
)
|
||||
|
||||
type Recorder struct {
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
)
|
||||
|
||||
func setupOutboxTest(t *testing.T) *pgxpool.Pool {
|
||||
|
||||
@@ -26,8 +26,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/crypto"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/registry"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/credential"
|
||||
connectcrypto "gitea.perlbach24.de/scripte/nexarch/connect/internal/crypto"
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/credential"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/registry"
|
||||
)
|
||||
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
)
|
||||
|
||||
// tenantKEKResponse mirrors Core internal/kek.tenantKEKResponse (JSON
|
||||
|
||||
@@ -14,8 +14,8 @@ import (
|
||||
"io"
|
||||
"net/http"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/credential"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/registry"
|
||||
)
|
||||
|
||||
|
||||
@@ -1,25 +0,0 @@
|
||||
// Package httpclient liefert den HTTP-Client fuer ausgehende Dienstaufrufe
|
||||
// (Muster aus archive/internal/httpclient, QA-15 F-11): Zeitlimit statt
|
||||
// endlosem Warten, keine Weiterleitungen (ein Service-Token wuerde sonst an
|
||||
// das Weiterleitungsziel mitgesendet).
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"time"
|
||||
)
|
||||
|
||||
const DefaultTimeout = 15 * time.Second
|
||||
|
||||
var shared = New(DefaultTimeout)
|
||||
|
||||
func Default() *http.Client { return shared }
|
||||
|
||||
func New(timeout time.Duration) *http.Client {
|
||||
return &http.Client{
|
||||
Timeout: timeout,
|
||||
CheckRedirect: func(*http.Request, []*http.Request) error {
|
||||
return http.ErrUseLastResponse
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -1,54 +0,0 @@
|
||||
package httpclient
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestDefaultHasTimeout(t *testing.T) {
|
||||
if Default().Timeout != DefaultTimeout || DefaultTimeout <= 0 {
|
||||
t.Fatalf("timeout = %s", Default().Timeout)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientGivesUpOnHangingServer(t *testing.T) {
|
||||
release := make(chan struct{})
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { <-release }))
|
||||
defer srv.Close()
|
||||
defer close(release)
|
||||
|
||||
start := time.Now()
|
||||
resp, err := New(200 * time.Millisecond).Get(srv.URL)
|
||||
if err == nil {
|
||||
_ = resp.Body.Close()
|
||||
t.Fatal("kein fehler bei haengendem server")
|
||||
}
|
||||
if elapsed := time.Since(start); elapsed > 5*time.Second {
|
||||
t.Fatalf("aufruf dauerte %s", elapsed)
|
||||
}
|
||||
}
|
||||
|
||||
func TestClientDoesNotFollowRedirects(t *testing.T) {
|
||||
target := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
t.Error("weiterleitungsziel wurde aufgerufen (Service-Token-Leck)")
|
||||
}))
|
||||
defer target.Close()
|
||||
redirector := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
http.Redirect(w, r, target.URL, http.StatusTemporaryRedirect)
|
||||
}))
|
||||
defer redirector.Close()
|
||||
|
||||
req, _ := http.NewRequest(http.MethodPost, redirector.URL, strings.NewReader("x"))
|
||||
req.Header.Set("X-Service-Token", "geheim")
|
||||
resp, err := Default().Do(req)
|
||||
if err != nil {
|
||||
t.Fatalf("do: %v", err)
|
||||
}
|
||||
defer func() { _ = resp.Body.Close() }()
|
||||
if resp.StatusCode != http.StatusTemporaryRedirect {
|
||||
t.Fatalf("status = %d, erwartet 307", resp.StatusCode)
|
||||
}
|
||||
}
|
||||
@@ -18,8 +18,8 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/credential"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/registry"
|
||||
)
|
||||
|
||||
|
||||
@@ -11,7 +11,7 @@ import (
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/policyclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/policyclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/syncjob"
|
||||
)
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/policyclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/policyclient"
|
||||
)
|
||||
|
||||
// newFakePolicyAPI steht fuer Core's echten POST /authorize-Vertrag (nimmt
|
||||
|
||||
@@ -9,7 +9,7 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/credential"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/erpconnector"
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/auditclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/audittrail"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/migrate"
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/testdb"
|
||||
|
||||
@@ -15,7 +15,7 @@ import (
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/connect/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
|
||||
+1
-1
@@ -15,6 +15,7 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/tenantregistryclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/audittrail"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/authz"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/batchsplit"
|
||||
@@ -34,7 +35,6 @@ import (
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/searchwiring"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/shared"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/storage"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/tenantregistryclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/watchfolder"
|
||||
)
|
||||
|
||||
|
||||
@@ -30,9 +30,9 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
dmscrypto "gitea.perlbach24.de/scripte/nexarch/dms/internal/crypto"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/folder"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/jobqueue"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/migrate"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/routeprefix"
|
||||
|
||||
@@ -23,8 +23,8 @@ import (
|
||||
|
||||
"github.com/jackc/pgx/v5/pgxpool"
|
||||
|
||||
"gitea.perlbach24.de/scripte/nexarch/common/httpclient"
|
||||
dmscrypto "gitea.perlbach24.de/scripte/nexarch/dms/internal/crypto"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/httpclient"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/jobqueue"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/migrate"
|
||||
"gitea.perlbach24.de/scripte/nexarch/dms/internal/routeprefix"
|
||||
|
||||
Loaded 100 of 135 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user