feat(PROJ-13,PROJ-42): REST API v1 + Gespeicherte Suchanfragen

PROJ-13: Externe REST API für CRM/ERP-Anbindung
- API-Key Middleware mit SHA-256-Hash-Lookup + Token-Bucket Rate-Limiter
- GET /api/v1/mails — Suche mit Paginierung (max 100/Seite)
- GET /api/v1/mails/{id} — Mail-Metadaten als JSON
- GET /api/v1/mails/{id}/raw — Original-EML Download
- Admin-Endpoints: POST/GET/DELETE /api/admin/apikeys
- Tenant-Isolation, Audit-Log, 405 für non-GET Methoden

PROJ-42: Gespeicherte Suchanfragen
- Tabelle saved_searches (user_id, tenant_id, name, query_json)
- GET/POST/DELETE /api/searches/saved mit Ownership-Check
- Frontend: "Suche speichern"-Button + Popover mit gespeicherten Suchen
- shadcn/ui Komponenten, Loading/Empty States

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
sysops
2026-04-06 10:54:26 +02:00
parent 9298216ce0
commit 3b05e949dd
15 changed files with 1400 additions and 251 deletions
+22
View File
@@ -89,6 +89,7 @@ type Server struct {
tokenStore *tokenstore.Store
fqdn string // from server.fqdn config (PROJ-28)
smtpOutStore *smtpoutconfig.Store
apiKeyMw *auth.APIKeyMiddleware // PROJ-13: external API auth
}
// SetSMTPDaemon wires the SMTP daemon into the API server after construction.
@@ -170,6 +171,7 @@ func New(
logger: logger,
mux: http.NewServeMux(),
startTime: time.Now(),
apiKeyMw: auth.NewAPIKeyMiddleware(store), // PROJ-13
}
s.routes()
return s
@@ -243,6 +245,11 @@ func (s *Server) routes() {
s.mux.HandleFunc("GET /api/admin/settings/imap-mode", s.authAdmin(s.handleGetIMAPMode))
s.mux.HandleFunc("PUT /api/admin/settings/imap-mode", s.authAdmin(s.handleSetIMAPMode))
// PROJ-42: Gespeicherte Suchanfragen
s.mux.HandleFunc("GET /api/searches/saved", s.auth(s.handleListSavedSearches))
s.mux.HandleFunc("POST /api/searches/saved", s.auth(s.handleCreateSavedSearch))
s.mux.HandleFunc("DELETE /api/searches/saved/{id}", s.auth(s.handleDeleteSavedSearch))
// Export routes
s.mux.HandleFunc("GET /api/export/pdf/{id}", s.auth(s.requireMailAccess(s.handleExportPDF)))
s.mux.HandleFunc("POST /api/export/zip", s.auth(s.requireMailAccess(s.handleExportZIP)))
@@ -285,6 +292,21 @@ func (s *Server) routes() {
s.mux.HandleFunc("POST /api/auth/totp", s.handleTOTPLogin) // no auth middleware — uses pending token
s.mux.HandleFunc("POST /api/admin/users/{id}/totp/reset", s.authAdmin(s.handleTOTPReset))
// PROJ-13: External REST API v1 (API-key auth)
s.mux.HandleFunc("/api/v1/mails", s.apiKeyMw.Wrap(s.handleV1SearchMails))
s.mux.HandleFunc("GET /api/v1/mails/{message_id}", s.apiKeyMw.Wrap(s.handleV1GetMail))
s.mux.HandleFunc("GET /api/v1/mails/{message_id}/raw", s.apiKeyMw.Wrap(s.handleV1GetMailRaw))
// PROJ-13: Catch-all for non-GET methods on v1 single-mail endpoints
s.mux.HandleFunc("POST /api/v1/mails/{message_id}", s.apiKeyMw.Wrap(s.handleV1MethodNotAllowed))
s.mux.HandleFunc("PUT /api/v1/mails/{message_id}", s.apiKeyMw.Wrap(s.handleV1MethodNotAllowed))
s.mux.HandleFunc("DELETE /api/v1/mails/{message_id}", s.apiKeyMw.Wrap(s.handleV1MethodNotAllowed))
s.mux.HandleFunc("PATCH /api/v1/mails/{message_id}", s.apiKeyMw.Wrap(s.handleV1MethodNotAllowed))
// PROJ-13: API key management (admin)
s.mux.HandleFunc("POST /api/admin/apikeys", s.authAdmin(s.handleCreateAPIKey))
s.mux.HandleFunc("GET /api/admin/apikeys", s.authAdmin(s.handleListAPIKeys))
s.mux.HandleFunc("DELETE /api/admin/apikeys/{id}", s.authAdmin(s.handleDeleteAPIKey))
// Certificate management routes (superadmin only)
s.mux.HandleFunc("GET /api/admin/cert/info", s.auth(s.requireRole(userstore.RoleSuperAdmin, s.handleCertInfo)))
s.mux.HandleFunc("POST /api/admin/cert/upload", s.auth(s.requireRole(userstore.RoleSuperAdmin, s.handleCertUpload)))