package httpapi import ( "log/slog" "net/http" "time" "data-terminal-cloud/internal/alerts" "data-terminal-cloud/internal/billing" "data-terminal-cloud/internal/buildinfo" "data-terminal-cloud/internal/contentmodel" "data-terminal-cloud/internal/device" "data-terminal-cloud/internal/diagnostics" "data-terminal-cloud/internal/health" "data-terminal-cloud/internal/identity" "data-terminal-cloud/internal/media" "data-terminal-cloud/internal/memorytool" "data-terminal-cloud/internal/opsaccess" "data-terminal-cloud/internal/opsconfig" "data-terminal-cloud/internal/opsdevice" "data-terminal-cloud/internal/saas" "data-terminal-cloud/internal/sharing" "data-terminal-cloud/internal/webevents" ) type RouterOptions struct { ServiceName string Logger *slog.Logger RateLimiter RateLimiter Readiness *health.Checker TokenVerifier *identity.OIDCVerifier IdentityRepository *identity.Repository SaaSService *saas.Service DeviceVerifier DeviceRequestVerifier DeviceService *device.Repository DeviceTelemetrySubmitter DeviceTelemetrySubmitter MediaService *media.Service WebEventService *webevents.Service SharingService *sharing.Service OpsConfigService *opsconfig.Service BillingService *billing.Service AlertService *alerts.Service DiagnosticService *diagnostics.Service OpsAccessService *opsaccess.Service OpsDeviceService *opsdevice.Service ContentModelService *contentmodel.Service ConversationCreator ConversationCreator ConversationActivator ConversationActivator DeviceConversationStateReader DeviceConversationStateReader ConversationHeartbeater ConversationHeartbeater ConversationStopper ConversationStopper ConversationTranscriptSubmitter ConversationTranscriptSubmitter ProviderCallbackSecret []byte ProviderCallbackSkew time.Duration ProviderCallbackProcessor ProviderCallbackProcessor PlatformHealthCallbackSecret []byte PlatformHealthCallbackSkew time.Duration PlatformHealthProcessor PlatformHealthProcessor MemoryToolService *memorytool.Service MemoryToolSigner *memorytool.TokenSigner } func NewRouter(options RouterOptions) http.Handler { logger := options.Logger if logger == nil { logger = slog.Default() } mux := http.NewServeMux() mux.Handle("GET /metrics", MetricsHandler()) mux.HandleFunc("GET /health/live", func(writer http.ResponseWriter, request *http.Request) { WriteJSON(writer, request, http.StatusOK, map[string]string{ "status": "alive", "service": options.ServiceName, }) }) mux.HandleFunc("GET /health/ready", func(writer http.ResponseWriter, request *http.Request) { result := options.Readiness.Ready(request.Context()) status := http.StatusOK if result.Status != "ready" { status = http.StatusServiceUnavailable } WriteJSON(writer, request, status, result) }) mux.HandleFunc("GET /api/v1/version", func(writer http.ResponseWriter, request *http.Request) { WriteJSON(writer, request, http.StatusOK, buildinfo.Current()) }) if options.ServiceName == "api" && options.MemoryToolService != nil && options.MemoryToolSigner != nil { mux.Handle("POST /internal/v1/tools/memory-search", MemoryToolSearchHandler(options.MemoryToolService, options.MemoryToolSigner, options.RateLimiter, logger)) } if options.ServiceName == "api" && options.TokenVerifier != nil && options.IdentityRepository != nil { mux.Handle("GET /api/v1/me", BearerAuth(options.TokenVerifier, UserRateLimit(logger, options.RateLimiter, CurrentUserHandler(options.IdentityRepository)))) } if options.ServiceName == "api" && options.TokenVerifier != nil && options.SaaSService != nil { authenticated := func(handler http.Handler) http.Handler { return BearerAuth(options.TokenVerifier, UserRateLimit(logger, options.RateLimiter, handler)) } if options.WebEventService != nil { mux.Handle("GET /api/v1/events", authenticated(StreamWebEventsHandler(options.WebEventService))) } if options.SharingService != nil { mux.Handle("GET /api/v1/sharing-grants", authenticated(ListSharingGrantsHandler(options.SharingService))) mux.Handle("POST /api/v1/sharing-grants", authenticated(CreateSharingGrantHandler(options.SharingService))) mux.Handle("DELETE /api/v1/sharing-grants/{id}", authenticated(RevokeSharingGrantHandler(options.SharingService))) mux.Handle("GET /api/v1/sharing-grants/{id}/resource", authenticated(AccessSharedResourceHandler(options.SharingService))) mux.Handle("PATCH /api/v1/sharing-grants/{id}/resource", authenticated(EditSharedStoryHandler(options.SharingService))) } if options.OpsConfigService != nil { mux.Handle("GET /api/v1/ops/content-quality", authenticated(GetOpsContentQualityHandler(options.OpsConfigService))) mux.Handle("GET /api/v1/ops/resource-references", authenticated(ListOpsResourceReferencesHandler(options.OpsConfigService))) mux.Handle("POST /api/v1/ops/resource-references", authenticated(CreateOpsResourceReferenceHandler(options.OpsConfigService))) mux.Handle("PATCH /api/v1/ops/resource-references/{id}/revoke", authenticated(RevokeOpsResourceReferenceHandler(options.OpsConfigService))) mux.Handle("GET /api/v1/ops/device-groups", authenticated(ListOpsDeviceGroupsHandler(options.OpsConfigService))) mux.Handle("POST /api/v1/ops/device-groups", authenticated(CreateOpsDeviceGroupHandler(options.OpsConfigService))) mux.Handle("GET /api/v1/ops/config-rollouts", authenticated(ListOpsConfigRolloutsHandler(options.OpsConfigService))) mux.Handle("PATCH /api/v1/ops/config-rollouts/{id}/pause", authenticated(PauseOpsConfigRolloutHandler(options.OpsConfigService))) mux.Handle("PATCH /api/v1/ops/config-rollouts/{id}/resume", authenticated(ResumeOpsConfigRolloutHandler(options.OpsConfigService))) mux.Handle("GET /api/v1/ops/config-versions", authenticated(ListOpsConfigVersionsHandler(options.OpsConfigService))) mux.Handle("POST /api/v1/ops/config-versions", authenticated(CreateOpsConfigVersionHandler(options.OpsConfigService))) mux.Handle("GET /api/v1/ops/config-versions/{id}/evaluations", authenticated(ListOpsConfigEvaluationsHandler(options.OpsConfigService))) mux.Handle("POST /api/v1/ops/config-versions/{id}/evaluations", authenticated(CreateOpsConfigEvaluationHandler(options.OpsConfigService))) mux.Handle("PATCH /api/v1/ops/config-versions/{id}/submit", authenticated(SubmitOpsConfigVersionHandler(options.OpsConfigService))) mux.Handle("PATCH /api/v1/ops/config-versions/{id}/publish", authenticated(PublishOpsConfigVersionHandler(options.OpsConfigService))) mux.Handle("POST /api/v1/ops/config-versions/{id}/rollback", authenticated(RollbackOpsConfigVersionHandler(options.OpsConfigService))) } if options.BillingService != nil { mux.Handle("GET /api/v1/households/{id}/usage-overview", authenticated(GetFamilyUsageOverviewHandler(options.BillingService))) mux.Handle("GET /api/v1/ops/price-books", authenticated(ListOpsPriceBooksHandler(options.BillingService))) mux.Handle("POST /api/v1/ops/price-books", authenticated(CreateOpsPriceBookHandler(options.BillingService))) mux.Handle("PATCH /api/v1/ops/price-books/{id}/publish", authenticated(PublishOpsPriceBookHandler(options.BillingService))) mux.Handle("GET /api/v1/ops/quota-policies", authenticated(ListOpsQuotaPoliciesHandler(options.BillingService))) mux.Handle("POST /api/v1/ops/quota-policies", authenticated(CreateOpsQuotaPolicyHandler(options.BillingService))) mux.Handle("GET /api/v1/ops/cost-summary", authenticated(GetOpsCostSummaryHandler(options.BillingService))) mux.Handle("GET /api/v1/ops/usage-overview", authenticated(GetOpsUsageOverviewHandler(options.BillingService))) } if options.DiagnosticService != nil { mux.Handle("GET /api/v1/ops/diagnostics/platform-health", authenticated(GetOpsPlatformHealthHandler(options.DiagnosticService))) mux.Handle("GET /api/v1/ops/integration-issues", authenticated(ListOpsIntegrationIssuesHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/integration-issues", authenticated(CreateOpsIntegrationIssueHandler(options.DiagnosticService))) mux.Handle("PATCH /api/v1/ops/integration-issues/{id}", authenticated(UpdateOpsIntegrationIssueHandler(options.DiagnosticService))) mux.Handle("GET /api/v1/ops/diagnostics/search", authenticated(SearchOpsDiagnosticsHandler(options.DiagnosticService))) mux.Handle("GET /api/v1/ops/diagnostics/external-dependencies", authenticated(ListOpsExternalDependenciesHandler(options.DiagnosticService))) mux.Handle("GET /api/v1/ops/diagnostics/jobs", authenticated(ListOpsDiagnosticJobsHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/jobs/{id}/retry", authenticated(RetryOpsDiagnosticJobHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/bundles", authenticated(ExportOpsDiagnosticBundleHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/acceptance-snapshots", authenticated(ExportOpsAcceptanceSnapshotHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/pilot-daily-snapshots", authenticated(ExportOpsPilotDailySnapshotHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/pilot-weekly-snapshots", authenticated(ExportOpsPilotWeeklySnapshotHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/conversations/{id}/stop", authenticated(StopOpsDiagnosticConversationHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/conversations/{id}/reconcile", authenticated(ReconcileOpsDiagnosticConversationHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/devices/{id}/config-refresh", authenticated(RefreshOpsDiagnosticDeviceConfigHandler(options.DiagnosticService))) mux.Handle("POST /api/v1/ops/diagnostics/devices/{id}/revoke", authenticated(RevokeOpsDiagnosticDeviceHandler(options.DiagnosticService))) } if options.OpsAccessService != nil { mux.Handle("GET /api/v1/ops/sensitive-access-requests", authenticated(ListOpsSensitiveAccessRequestsHandler(options.OpsAccessService))) mux.Handle("POST /api/v1/ops/sensitive-access-requests", authenticated(CreateOpsSensitiveAccessRequestHandler(options.OpsAccessService))) mux.Handle("POST /api/v1/ops/sensitive-access-requests/{id}/review", authenticated(ReviewOpsSensitiveAccessRequestHandler(options.OpsAccessService))) mux.Handle("GET /api/v1/ops/sensitive-conversations/{id}", authenticated(GetOpsSensitiveConversationHandler(options.OpsAccessService))) mux.Handle("POST /api/v1/ops/sensitive-media/{id}/play", authenticated(CreateOpsSensitiveRecordingAccessHandler(options.OpsAccessService))) } if options.OpsDeviceService != nil { mux.Handle("GET /api/v1/ops/devices", authenticated(ListOpsDevicesHandler(options.OpsDeviceService))) mux.Handle("POST /api/v1/ops/devices", authenticated(CreateOpsDeviceHandler(options.OpsDeviceService))) mux.Handle("GET /api/v1/ops/device-assignment-options", authenticated(ListOpsDeviceOptionsHandler(options.OpsDeviceService))) mux.Handle("PATCH /api/v1/ops/devices/{id}", authenticated(UpdateOpsDeviceHandler(options.OpsDeviceService))) mux.Handle("DELETE /api/v1/ops/devices/{id}", authenticated(DeleteOpsDeviceHandler(options.OpsDeviceService))) mux.Handle("POST /api/v1/ops/devices/{id}/credentials/rotate", authenticated(RotateOpsDeviceCredentialHandler(options.OpsDeviceService))) mux.Handle("POST /api/v1/ops/devices/{id}/claims", authenticated(IssueOpsDeviceClaimHandler(options.OpsDeviceService))) } if options.ContentModelService != nil { mux.Handle("GET /api/v1/content-models", authenticated(ListContentModelsHandler(options.ContentModelService))) mux.Handle("POST /api/v1/content-models", authenticated(CreateContentModelHandler(options.ContentModelService))) mux.Handle("POST /api/v1/content-models/{id}/test", authenticated(TestContentModelHandler(options.ContentModelService))) mux.Handle("POST /api/v1/content-models/{id}/activate", authenticated(ActivateContentModelHandler(options.ContentModelService))) mux.Handle("DELETE /api/v1/content-models/{id}", authenticated(RevokeContentModelHandler(options.ContentModelService))) } mux.Handle("GET /api/v1/households", authenticated(ListHouseholdsHandler(options.SaaSService))) mux.Handle("POST /api/v1/households", authenticated(CreateHouseholdHandler(options.SaaSService))) mux.Handle("GET /api/v1/households/{id}", authenticated(GetHouseholdHandler(options.SaaSService))) mux.Handle("PATCH /api/v1/households/{id}", authenticated(PatchHouseholdHandler(options.SaaSService))) mux.Handle("GET /api/v1/households/{id}/members", authenticated(ListHouseholdMembersHandler(options.SaaSService))) mux.Handle("PATCH /api/v1/households/{id}/members/{member_id}", authenticated(PatchHouseholdMemberHandler(options.SaaSService))) mux.Handle("DELETE /api/v1/households/{id}/members/{member_id}", authenticated(RemoveHouseholdMemberHandler(options.SaaSService))) mux.Handle("GET /api/v1/households/{id}/invitations", authenticated(ListMemberInvitationsHandler(options.SaaSService))) mux.Handle("POST /api/v1/households/{id}/invitations", authenticated(CreateMemberInvitationHandler(options.SaaSService))) mux.Handle("POST /api/v1/households/{id}/invitations/{invitation_id}/revoke", authenticated(RevokeMemberInvitationHandler(options.SaaSService))) mux.Handle("POST /api/v1/member-invitations/preview", authenticated(PreviewMemberInvitationHandler(options.SaaSService))) mux.Handle("POST /api/v1/member-invitations/accept", authenticated(AcceptMemberInvitationHandler(options.SaaSService))) mux.Handle("POST /api/v1/households/{id}/ownership", authenticated(TransferHouseholdOwnershipHandler(options.SaaSService))) mux.Handle("GET /api/v1/households/{id}/subjects", authenticated(ListSubjectsHandler(options.SaaSService))) mux.Handle("POST /api/v1/households/{id}/subjects", authenticated(CreateSubjectHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}", authenticated(GetSubjectHandler(options.SaaSService))) mux.Handle("PATCH /api/v1/subjects/{id}", authenticated(PatchSubjectHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}/conversation-readiness", authenticated(GetConversationReadinessHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}/facts", authenticated(ListSubjectFactsHandler(options.SaaSService))) mux.Handle("POST /api/v1/subjects/{id}/facts", authenticated(CreateSubjectFactHandler(options.SaaSService))) mux.Handle("PATCH /api/v1/subjects/{id}/facts/{fact_id}", authenticated(PatchSubjectFactHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}/consents", authenticated(ListConsentsHandler(options.SaaSService))) mux.Handle("POST /api/v1/subjects/{id}/consents", authenticated(GrantConsentHandler(options.SaaSService))) mux.Handle("POST /api/v1/subjects/{id}/consents/{consent_id}/withdraw", authenticated(WithdrawConsentHandler(options.SaaSService))) mux.Handle("POST /api/v1/device-claims", authenticated(ClaimDeviceHandler(options.SaaSService))) mux.Handle("GET /api/v1/devices", authenticated(ListManagedDevicesHandler(options.SaaSService))) mux.Handle("GET /api/v1/devices/{id}", authenticated(GetManagedDeviceHandler(options.SaaSService))) mux.Handle("GET /api/v1/devices/{id}/binding-impact", authenticated(GetDeviceBindingImpactHandler(options.SaaSService))) mux.Handle("POST /api/v1/devices/{id}/binding-change", authenticated(ChangeDeviceBindingHandler(options.SaaSService))) mux.Handle("GET /api/v1/devices/{id}/presence", authenticated(GetDevicePresenceHandler(options.SaaSService))) mux.Handle("GET /api/v1/devices/{id}/twin", authenticated(GetDeviceTwinHandler(options.SaaSService))) mux.Handle("PATCH /api/v1/devices/{id}/twin", authenticated(PatchDeviceTwinHandler(options.SaaSService))) mux.Handle("GET /api/v1/devices/{id}/commands", authenticated(ListDeviceCommandsHandler(options.SaaSService))) mux.Handle("POST /api/v1/devices/{id}/commands", authenticated(CreateDeviceCommandHandler(options.SaaSService))) mux.Handle("POST /api/v1/households/{id}/firmware-releases", authenticated(PrepareFirmwareReleaseHandler(options.SaaSService))) mux.Handle("GET /api/v1/households/{id}/firmware-releases", authenticated(ListFirmwareReleasesHandler(options.SaaSService))) mux.Handle("POST /api/v1/households/{id}/firmware-signing-keys", authenticated(RegisterFirmwareSigningKeyHandler(options.SaaSService))) mux.Handle("GET /api/v1/households/{id}/firmware-signing-keys", authenticated(ListFirmwareSigningKeysHandler(options.SaaSService))) mux.Handle("POST /api/v1/households/{id}/firmware-signing-keys/revoke", authenticated(RevokeFirmwareSigningKeyHandler(options.SaaSService))) mux.Handle("GET /api/v1/firmware-releases/{id}/impact", authenticated(GetFirmwareReleaseImpactHandler(options.SaaSService))) mux.Handle("POST /api/v1/firmware-releases/{id}/finalize", authenticated(FinalizeFirmwareReleaseHandler(options.SaaSService))) mux.Handle("POST /api/v1/firmware-releases/{id}/upload-session", authenticated(RenewFirmwareUploadHandler(options.SaaSService))) mux.Handle("POST /api/v1/firmware-releases/{id}/pause", authenticated(PauseFirmwareReleaseHandler(options.SaaSService))) mux.Handle("POST /api/v1/firmware-releases/{id}/resume", authenticated(ResumeFirmwareReleaseHandler(options.SaaSService))) mux.Handle("POST /api/v1/firmware-releases/{id}/retire", authenticated(RetireFirmwareReleaseHandler(options.SaaSService))) mux.Handle("POST /api/v1/households/{id}/firmware-rollouts", authenticated(StartFirmwareRolloutHandler(options.SaaSService))) mux.Handle("GET /api/v1/households/{id}/firmware-rollouts", authenticated(ListFirmwareRolloutsHandler(options.SaaSService))) mux.Handle("GET /api/v1/firmware-rollouts/{id}", authenticated(GetFirmwareRolloutHandler(options.SaaSService))) mux.Handle("POST /api/v1/firmware-rollouts/{id}/pause", authenticated(PauseFirmwareRolloutHandler(options.SaaSService))) mux.Handle("POST /api/v1/firmware-rollouts/{id}/rollback", authenticated(RollbackFirmwareRolloutHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}/conversations", authenticated(ListSubjectConversationsHandler(options.SaaSService))) mux.Handle("GET /api/v1/conversations/{id}", authenticated(GetConversationHandler(options.SaaSService))) mux.Handle("GET /api/v1/conversations/{id}/usage", authenticated(GetConversationUsageHandler(options.SaaSService))) if options.MediaService != nil { mux.Handle("GET /api/v1/subjects/{id}/recording-access-grants", authenticated(ListRecordingAccessGrantsHandler(options.MediaService))) mux.Handle("POST /api/v1/subjects/{id}/recording-access-grants", authenticated(GrantRecordingAccessHandler(options.MediaService))) mux.Handle("POST /api/v1/recording-access-grants/{id}/revoke", authenticated(RevokeRecordingAccessHandler(options.MediaService))) mux.Handle("GET /api/v1/conversations/{id}/media", authenticated(GetConversationMediaHandler(options.MediaService))) mux.Handle("POST /api/v1/media-segments/{id}/access", authenticated(CreateMediaAccessHandler(options.MediaService))) } mux.Handle("GET /api/v1/conversations/{id}/turns", authenticated(ListConversationTurnsHandler(options.SaaSService))) mux.Handle("GET /api/v1/conversations/{id}/summaries", authenticated(ListConversationSummariesHandler(options.SaaSService))) mux.Handle("POST /api/v1/conversations/{id}/summaries:regenerate", authenticated(RegenerateConversationSummaryHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}/stories", authenticated(ListSubjectStoriesHandler(options.SaaSService))) mux.Handle("GET /api/v1/stories/{id}", authenticated(GetStoryHandler(options.SaaSService))) mux.Handle("PATCH /api/v1/stories/{id}", authenticated(PatchStoryHandler(options.SaaSService))) mux.Handle("PATCH /api/v1/stories/{id}/facts/{fact_id}", authenticated(UpdateFactCandidateHandler(options.SaaSService))) mux.Handle("POST /api/v1/stories/{id}/confirm", authenticated(ConfirmStoryHandler(options.SaaSService))) mux.Handle("POST /api/v1/stories/{id}/reject", authenticated(RejectStoryHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}/memories", authenticated(ListSubjectMemoriesHandler(options.SaaSService))) mux.Handle("GET /api/v1/memories/{id}", authenticated(GetMemoryHandler(options.SaaSService))) mux.Handle("PATCH /api/v1/memories/{id}", authenticated(PatchMemoryHandler(options.SaaSService))) mux.Handle("DELETE /api/v1/memories/{id}", authenticated(DeleteMemoryHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}/privacy-requests", authenticated(ListPrivacyRequestsHandler(options.SaaSService))) mux.Handle("POST /api/v1/subjects/{id}/privacy-requests", authenticated(CreatePrivacyRequestHandler(options.SaaSService))) mux.Handle("GET /api/v1/privacy-requests/{id}", authenticated(GetPrivacyRequestHandler(options.SaaSService))) mux.Handle("GET /api/v1/subjects/{id}/exports", authenticated(ListExportTasksHandler(options.SaaSService))) mux.Handle("POST /api/v1/subjects/{id}/exports", authenticated(CreateExportTaskHandler(options.SaaSService))) mux.Handle("GET /api/v1/exports/{id}", authenticated(GetExportTaskHandler(options.SaaSService))) } if options.ServiceName == "api" && options.TokenVerifier != nil && options.AlertService != nil { authenticated := func(handler http.Handler) http.Handler { return BearerAuth(options.TokenVerifier, UserRateLimit(logger, options.RateLimiter, handler)) } mux.Handle("GET /api/v1/ops/alert-routing-policies", authenticated(ListOpsAlertRoutingPoliciesHandler(options.AlertService))) mux.Handle("POST /api/v1/ops/alert-routing-policies", authenticated(CreateOpsAlertRoutingPolicyHandler(options.AlertService))) mux.Handle("GET /api/v1/ops/alert-delivery-tests", authenticated(ListOpsAlertDeliveryTestsHandler(options.AlertService))) mux.Handle("POST /api/v1/ops/alert-routing-policies/{id}/delivery-tests", authenticated(CreateOpsAlertDeliveryTestHandler(options.AlertService))) mux.Handle("GET /api/v1/ops/alerts", authenticated(ListOpsAlertsHandler(options.AlertService))) mux.Handle("GET /api/v1/ops/alerts/{id}", authenticated(GetOpsAlertHandler(options.AlertService))) mux.Handle("POST /api/v1/ops/alerts/{id}/acknowledge", authenticated(TransitionOpsAlertHandler(options.AlertService, "ACKNOWLEDGE"))) mux.Handle("POST /api/v1/ops/alerts/{id}/mitigate", authenticated(TransitionOpsAlertHandler(options.AlertService, "MITIGATE"))) mux.Handle("POST /api/v1/ops/alerts/{id}/resolve", authenticated(TransitionOpsAlertHandler(options.AlertService, "RESOLVE"))) mux.Handle("GET /api/v1/ops/alert-silences", authenticated(ListOpsAlertSilencesHandler(options.AlertService))) mux.Handle("POST /api/v1/ops/alert-silences", authenticated(CreateOpsAlertSilenceHandler(options.AlertService))) mux.Handle("POST /api/v1/ops/alert-silences/{id}/revoke", authenticated(RevokeOpsAlertSilenceHandler(options.AlertService))) } if options.ServiceName == "api" && options.SharingService != nil { mux.Handle("GET /api/v1/public/shares/{id}", AccessPublicSharedResourceHandler(options.SharingService)) } if options.ServiceName == "api" && options.DeviceVerifier != nil && options.DeviceService != nil { deviceAuthenticated := func(handler http.Handler) http.Handler { return DeviceAuth(options.DeviceVerifier, DeviceRateLimit(logger, options.RateLimiter, handler)) } mux.Handle("GET /device/v2/bootstrap", deviceAuthenticated(DeviceBootstrapHandler(options.DeviceService))) mux.Handle("GET /device/v2/config", deviceAuthenticated(DeviceConfigHandler(options.DeviceService))) mux.Handle("GET /device/v2/commands", deviceAuthenticated(PullDeviceCommandsHandler(options.DeviceService))) mux.Handle("POST /device/v2/commands/{id}/ack", deviceAuthenticated(AcknowledgeDeviceCommandHandler(options.DeviceService))) if options.DeviceTelemetrySubmitter != nil { mux.Handle("POST /device/v2/telemetry:batch", deviceAuthenticated(SubmitDeviceTelemetryHandler(options.DeviceTelemetrySubmitter))) } mux.Handle("GET /device/v2/firmware-releases/{id}/download", deviceAuthenticated(DeviceFirmwareDownloadHandler(options.SaaSService))) if options.MediaService != nil { mux.Handle("POST /device/v2/conversations/{id}/upload-batches", deviceAuthenticated(PrepareMediaUploadBatchHandler(options.MediaService))) mux.Handle("POST /device/v2/upload-batches/{id}/complete", deviceAuthenticated(CompleteMediaUploadBatchHandler(options.MediaService))) mux.Handle("GET /device/v2/upload-batches/{id}", deviceAuthenticated(GetDeviceMediaUploadBatchHandler(options.MediaService))) mux.Handle("POST /device/v2/media-segments/{id}/local-delete-ack", deviceAuthenticated(AcknowledgeLocalMediaDeleteHandler(options.MediaService))) } if options.ConversationCreator != nil { mux.Handle("POST /device/v2/conversations", deviceAuthenticated(CreateConversationHandler(options.ConversationCreator))) } if options.ConversationActivator != nil { mux.Handle("POST /device/v2/conversations/{id}/activate", deviceAuthenticated(ActivateConversationHandler(options.ConversationActivator))) } if options.DeviceConversationStateReader != nil { mux.Handle("GET /device/v2/conversations/{id}", deviceAuthenticated(GetDeviceConversationStateHandler(options.DeviceConversationStateReader))) } if options.ConversationHeartbeater != nil { mux.Handle("POST /device/v2/conversations/{id}/heartbeat", deviceAuthenticated(HeartbeatConversationHandler(options.ConversationHeartbeater))) } if options.ConversationStopper != nil { mux.Handle("POST /device/v2/conversations/{id}/stop", deviceAuthenticated(StopConversationHandler(options.ConversationStopper))) } if options.ConversationTranscriptSubmitter != nil { mux.Handle("POST /device/v2/conversations/{id}/turns:batch", deviceAuthenticated(SubmitConversationTurnsHandler(options.ConversationTranscriptSubmitter))) } } if options.ServiceName == "callbacks" && len(options.ProviderCallbackSecret) > 0 && options.ProviderCallbackProcessor != nil { mux.Handle("POST /callbacks/v1/vertc/voice-chat", VoiceProviderCallbackHandler( options.ProviderCallbackSecret, options.ProviderCallbackSkew, options.ProviderCallbackProcessor, )) } if options.ServiceName == "callbacks" && len(options.PlatformHealthCallbackSecret) > 0 && options.PlatformHealthProcessor != nil { mux.Handle("POST /callbacks/v1/platform-health", PlatformHealthCallbackHandler( options.PlatformHealthCallbackSecret, options.PlatformHealthCallbackSkew, options.PlatformHealthProcessor, )) } mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { WriteError(writer, request, http.StatusNotFound, "RESOURCE_NOT_FOUND", "请求的资源不存在") }) return mux }