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:
@@ -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)))
|
||||
|
||||
Reference in New Issue
Block a user