// 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") } }