- dms/internal/retentionclient: HTTP-Client fuer Archive RET-05/RET-09 - dms/internal/retentiondestroy: RegisterOrRequeue (FDN-04-Requeue bei Fehlschlag, kein Absturz/stilles Verwerfen), DestroyCallbackHandler (physische Loeschung aller Datei-Revisionen ueber storage.Service, markiert Dokument als vernichtet, 2xx erst danach) - dms/cmd/app: mountet destroy-callback, registriert beim Start - dms/cmd/worker: verarbeitet Requeue-Jobs (Retry) - nur dms_document registriert (DOC-01/FDN-02 kennen keinen separaten Anhang-Typ, kein Umbau angrenzender Bereiche) - real getestet: registrierung gegen echten fake-RET-05-Server, echte Loeschung inkl. Storage-Byte-Nachweis, echter Requeue-Eintrag bei Fehlschlag, erfolgreicher Retry - live auf 131 gegen echten RET-09-Dienst (Port 8095) verifiziert, echter curl-Vernichtungs-Rueckruf mit real geloeschter Datei - reale Core-API-06-Deploy-Luecke dokumentiert (nicht verschwiegen): /internal/resync/usage auf 131 aktuell nicht gemountet Pruefungen siehe dms/docs/DOC-16-PRUEFPROTOKOLL.md
95 lines
3.5 KiB
Go
95 lines
3.5 KiB
Go
// app ist der Anfrage-Dienst (Request-Path) des DMS — getrennt vom Worker,
|
|
// damit lange Hintergrundaufgaben nie eine HTTP-Anfrage blockieren
|
|
// (Akzeptanzkriterium/Produkt-DNA: paperless-ngx-Trennung Dienst/Worker).
|
|
package main
|
|
|
|
import (
|
|
"context"
|
|
"log"
|
|
"net/http"
|
|
"os"
|
|
"time"
|
|
|
|
"github.com/jackc/pgx/v5/pgxpool"
|
|
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/jobqueue"
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/retentionclient"
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/retentiondestroy"
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/shared"
|
|
"gitea.perlbach24.de/scripte/nexarch/dms/internal/storage"
|
|
)
|
|
|
|
func main() {
|
|
addr := os.Getenv("NEXARCH_DMS_APP_LISTEN_ADDR")
|
|
if addr == "" {
|
|
addr = ":8090"
|
|
}
|
|
|
|
mux := http.NewServeMux()
|
|
mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) {
|
|
w.Header().Set("Content-Type", "application/json")
|
|
_, _ = w.Write([]byte(`{"status":"ok","service":"dms-app","version":"` + shared.Version + `"}`))
|
|
})
|
|
|
|
// DOC-16: RET-05-Client (Registrierung + Vernichtungs-Rückruf-Empfang).
|
|
// Optional, um cmd/app auch ohne diese Umgebungsvariablen weiter
|
|
// startbar zu halten (z. B. isolierte Tests anderer Endpunkte).
|
|
if dsn := os.Getenv("NEXARCH_DMS_APP_TENANT_DSN"); dsn != "" {
|
|
setupRetention(mux, dsn)
|
|
} else {
|
|
log.Print("dms-app: NEXARCH_DMS_APP_TENANT_DSN nicht gesetzt, RET-05-Anbindung (DOC-16) deaktiviert")
|
|
}
|
|
|
|
log.Printf("dms-app hoert auf %s (version %s)", addr, shared.Version)
|
|
if err := http.ListenAndServe(addr, mux); err != nil {
|
|
log.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func setupRetention(mux *http.ServeMux, dsn string) {
|
|
ctx := context.Background()
|
|
pool, err := pgxpool.New(ctx, dsn)
|
|
if err != nil {
|
|
log.Fatalf("dms-app: datenbankverbindung: %v", err)
|
|
}
|
|
|
|
storageDir := os.Getenv("NEXARCH_DMS_APP_STORAGE_DIR")
|
|
signingSecret := os.Getenv("NEXARCH_DMS_APP_STORAGE_SIGNING_SECRET")
|
|
publicBaseURL := os.Getenv("NEXARCH_DMS_APP_STORAGE_PUBLIC_BASE_URL")
|
|
driver := storage.NewLocalDriver(storageDir, []byte(signingSecret), publicBaseURL)
|
|
|
|
usageEndpoint := os.Getenv("NEXARCH_DMS_APP_USAGE_ENDPOINT_URL")
|
|
usageClientID := os.Getenv("NEXARCH_DMS_APP_USAGE_CLIENT_ID")
|
|
usageClientSecret := os.Getenv("NEXARCH_DMS_APP_USAGE_CLIENT_SECRET")
|
|
tenantSlug := os.Getenv("NEXARCH_DMS_APP_TENANT_SLUG")
|
|
usageReporter := storage.NewHTTPUsageReporter(usageEndpoint, usageClientID, usageClientSecret, nil)
|
|
storageSvc := storage.NewService(driver, usageReporter, tenantSlug)
|
|
|
|
mux.HandleFunc("POST /retention/destroy-callback", retentiondestroy.DestroyCallbackHandler(pool, storageSvc))
|
|
|
|
retentionBaseURL := os.Getenv("NEXARCH_DMS_APP_RETENTION_BASE_URL")
|
|
retentionClass := os.Getenv("NEXARCH_DMS_APP_RETENTION_CLASS")
|
|
callbackURL := os.Getenv("NEXARCH_DMS_APP_RETENTION_CALLBACK_URL")
|
|
if retentionBaseURL == "" || retentionClass == "" || callbackURL == "" {
|
|
log.Print("dms-app: RET-05-Registrierung uebersprungen (RETENTION_BASE_URL/CLASS/CALLBACK_URL unvollstaendig)")
|
|
return
|
|
}
|
|
|
|
client := retentionclient.New(retentionBaseURL)
|
|
queue := jobqueue.NewQueue(pool, 5*time.Minute)
|
|
cfg := retentiondestroy.RegisterConfig{
|
|
ObjectType: retentiondestroy.ObjectTypeDocument,
|
|
RetentionClass: retentionClass,
|
|
CallbackURL: callbackURL,
|
|
}
|
|
|
|
regCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
|
|
defer cancel()
|
|
queued, err := retentiondestroy.RegisterOrRequeue(regCtx, client, queue, cfg)
|
|
if err != nil {
|
|
log.Printf("dms-app: RET-05-registrierung fehlgeschlagen (requeued=%t): %v", queued, err)
|
|
} else {
|
|
log.Print("dms-app: bei archive RET-05 registriert")
|
|
}
|
|
}
|