@de-otio/trellis 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/db.d.ts +36 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +39 -0
- package/dist/db.js.map +1 -0
- package/dist/env.d.ts +107 -0
- package/dist/env.d.ts.map +1 -0
- package/dist/env.js +268 -0
- package/dist/env.js.map +1 -0
- package/dist/extensions.d.ts +15 -0
- package/dist/extensions.d.ts.map +1 -0
- package/dist/extensions.js +35 -0
- package/dist/extensions.js.map +1 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +15 -0
- package/dist/index.js.map +1 -0
- package/dist/lambda/cleanup-cron.d.ts +2 -0
- package/dist/lambda/cleanup-cron.d.ts.map +1 -0
- package/dist/lambda/cleanup-cron.js +38 -0
- package/dist/lambda/cleanup-cron.js.map +1 -0
- package/dist/lambda/create-auth-challenge.d.ts +2 -0
- package/dist/lambda/create-auth-challenge.d.ts.map +1 -0
- package/dist/lambda/create-auth-challenge.js +108 -0
- package/dist/lambda/create-auth-challenge.js.map +1 -0
- package/dist/lambda/custom-message.d.ts +3 -0
- package/dist/lambda/custom-message.d.ts.map +1 -0
- package/dist/lambda/custom-message.js +29 -0
- package/dist/lambda/custom-message.js.map +1 -0
- package/dist/lambda/define-auth-challenge.d.ts +2 -0
- package/dist/lambda/define-auth-challenge.d.ts.map +1 -0
- package/dist/lambda/define-auth-challenge.js +27 -0
- package/dist/lambda/define-auth-challenge.js.map +1 -0
- package/dist/lambda/delete-account-worker.d.ts +3 -0
- package/dist/lambda/delete-account-worker.d.ts.map +1 -0
- package/dist/lambda/delete-account-worker.js +122 -0
- package/dist/lambda/delete-account-worker.js.map +1 -0
- package/dist/lambda/diagnostics-proxy.d.ts +6 -0
- package/dist/lambda/diagnostics-proxy.d.ts.map +1 -0
- package/dist/lambda/diagnostics-proxy.js +154 -0
- package/dist/lambda/diagnostics-proxy.js.map +1 -0
- package/dist/lambda/e2e-sweeper.d.ts +18 -0
- package/dist/lambda/e2e-sweeper.d.ts.map +1 -0
- package/dist/lambda/e2e-sweeper.js +127 -0
- package/dist/lambda/e2e-sweeper.js.map +1 -0
- package/dist/lambda/federation-outbox-worker.d.ts +3 -0
- package/dist/lambda/federation-outbox-worker.d.ts.map +1 -0
- package/dist/lambda/federation-outbox-worker.js +11 -0
- package/dist/lambda/federation-outbox-worker.js.map +1 -0
- package/dist/lambda/followers-events-worker.d.ts +3 -0
- package/dist/lambda/followers-events-worker.d.ts.map +1 -0
- package/dist/lambda/followers-events-worker.js +11 -0
- package/dist/lambda/followers-events-worker.js.map +1 -0
- package/dist/lambda/hourly-cron.d.ts +2 -0
- package/dist/lambda/hourly-cron.d.ts.map +1 -0
- package/dist/lambda/hourly-cron.js +101 -0
- package/dist/lambda/hourly-cron.js.map +1 -0
- package/dist/lambda/link-check-worker.d.ts +3 -0
- package/dist/lambda/link-check-worker.d.ts.map +1 -0
- package/dist/lambda/link-check-worker.js +11 -0
- package/dist/lambda/link-check-worker.js.map +1 -0
- package/dist/lambda/maintenance-cron.d.ts +2 -0
- package/dist/lambda/maintenance-cron.d.ts.map +1 -0
- package/dist/lambda/maintenance-cron.js +127 -0
- package/dist/lambda/maintenance-cron.js.map +1 -0
- package/dist/lambda/media-processing-worker.d.ts +3 -0
- package/dist/lambda/media-processing-worker.d.ts.map +1 -0
- package/dist/lambda/media-processing-worker.js +95 -0
- package/dist/lambda/media-processing-worker.js.map +1 -0
- package/dist/lambda/media-reconciliation-worker.d.ts +3 -0
- package/dist/lambda/media-reconciliation-worker.d.ts.map +1 -0
- package/dist/lambda/media-reconciliation-worker.js +11 -0
- package/dist/lambda/media-reconciliation-worker.js.map +1 -0
- package/dist/lambda/nightly-cron.d.ts +2 -0
- package/dist/lambda/nightly-cron.d.ts.map +1 -0
- package/dist/lambda/nightly-cron.js +348 -0
- package/dist/lambda/nightly-cron.js.map +1 -0
- package/dist/lambda/post-confirmation.d.ts +3 -0
- package/dist/lambda/post-confirmation.d.ts.map +1 -0
- package/dist/lambda/post-confirmation.js +79 -0
- package/dist/lambda/post-confirmation.js.map +1 -0
- package/dist/lambda/pre-signup.d.ts +3 -0
- package/dist/lambda/pre-signup.d.ts.map +1 -0
- package/dist/lambda/pre-signup.js +35 -0
- package/dist/lambda/pre-signup.js.map +1 -0
- package/dist/lambda/pre-token-generation.d.ts +3 -0
- package/dist/lambda/pre-token-generation.d.ts.map +1 -0
- package/dist/lambda/pre-token-generation.js +79 -0
- package/dist/lambda/pre-token-generation.js.map +1 -0
- package/dist/lambda/tools/check-health.d.ts +6 -0
- package/dist/lambda/tools/check-health.d.ts.map +1 -0
- package/dist/lambda/tools/check-health.js +24 -0
- package/dist/lambda/tools/check-health.js.map +1 -0
- package/dist/lambda/tools/describe-services.d.ts +20 -0
- package/dist/lambda/tools/describe-services.d.ts.map +1 -0
- package/dist/lambda/tools/describe-services.js +41 -0
- package/dist/lambda/tools/describe-services.js.map +1 -0
- package/dist/lambda/tools/get-cost-report.d.ts +16 -0
- package/dist/lambda/tools/get-cost-report.d.ts.map +1 -0
- package/dist/lambda/tools/get-cost-report.js +54 -0
- package/dist/lambda/tools/get-cost-report.js.map +1 -0
- package/dist/lambda/tools/get-errors.d.ts +11 -0
- package/dist/lambda/tools/get-errors.d.ts.map +1 -0
- package/dist/lambda/tools/get-errors.js +62 -0
- package/dist/lambda/tools/get-errors.js.map +1 -0
- package/dist/lambda/tools/get-feature-flags.d.ts +8 -0
- package/dist/lambda/tools/get-feature-flags.d.ts.map +1 -0
- package/dist/lambda/tools/get-feature-flags.js +23 -0
- package/dist/lambda/tools/get-feature-flags.js.map +1 -0
- package/dist/lambda/tools/get-queue-status.d.ts +9 -0
- package/dist/lambda/tools/get-queue-status.d.ts.map +1 -0
- package/dist/lambda/tools/get-queue-status.js +46 -0
- package/dist/lambda/tools/get-queue-status.js.map +1 -0
- package/dist/lambda/tools/search-logs.d.ts +19 -0
- package/dist/lambda/tools/search-logs.d.ts.map +1 -0
- package/dist/lambda/tools/search-logs.js +55 -0
- package/dist/lambda/tools/search-logs.js.map +1 -0
- package/dist/lambda/tools/send-alert.d.ts +8 -0
- package/dist/lambda/tools/send-alert.d.ts.map +1 -0
- package/dist/lambda/tools/send-alert.js +29 -0
- package/dist/lambda/tools/send-alert.js.map +1 -0
- package/dist/lambda/verify-auth-challenge.d.ts +2 -0
- package/dist/lambda/verify-auth-challenge.d.ts.map +1 -0
- package/dist/lambda/verify-auth-challenge.js +35 -0
- package/dist/lambda/verify-auth-challenge.js.map +1 -0
- package/dist/lib/abuse-metrics.d.ts +44 -0
- package/dist/lib/abuse-metrics.d.ts.map +1 -0
- package/dist/lib/abuse-metrics.js +322 -0
- package/dist/lib/abuse-metrics.js.map +1 -0
- package/dist/lib/activitypub/activity-processor.d.ts +89 -0
- package/dist/lib/activitypub/activity-processor.d.ts.map +1 -0
- package/dist/lib/activitypub/activity-processor.js +709 -0
- package/dist/lib/activitypub/activity-processor.js.map +1 -0
- package/dist/lib/activitypub/activity-service.d.ts +47 -0
- package/dist/lib/activitypub/activity-service.d.ts.map +1 -0
- package/dist/lib/activitypub/activity-service.js +165 -0
- package/dist/lib/activitypub/activity-service.js.map +1 -0
- package/dist/lib/activitypub/actor.d.ts +44 -0
- package/dist/lib/activitypub/actor.d.ts.map +1 -0
- package/dist/lib/activitypub/actor.js +116 -0
- package/dist/lib/activitypub/actor.js.map +1 -0
- package/dist/lib/activitypub/audience-service.d.ts +52 -0
- package/dist/lib/activitypub/audience-service.d.ts.map +1 -0
- package/dist/lib/activitypub/audience-service.js +245 -0
- package/dist/lib/activitypub/audience-service.js.map +1 -0
- package/dist/lib/activitypub/crypto.d.ts +42 -0
- package/dist/lib/activitypub/crypto.d.ts.map +1 -0
- package/dist/lib/activitypub/crypto.js +129 -0
- package/dist/lib/activitypub/crypto.js.map +1 -0
- package/dist/lib/activitypub/delivery-service.d.ts +44 -0
- package/dist/lib/activitypub/delivery-service.d.ts.map +1 -0
- package/dist/lib/activitypub/delivery-service.js +259 -0
- package/dist/lib/activitypub/delivery-service.js.map +1 -0
- package/dist/lib/activitypub/dispatchers/entity-actor.d.ts +50 -0
- package/dist/lib/activitypub/dispatchers/entity-actor.d.ts.map +1 -0
- package/dist/lib/activitypub/dispatchers/entity-actor.js +247 -0
- package/dist/lib/activitypub/dispatchers/entity-actor.js.map +1 -0
- package/dist/lib/activitypub/dispatchers/group-actor.d.ts +47 -0
- package/dist/lib/activitypub/dispatchers/group-actor.d.ts.map +1 -0
- package/dist/lib/activitypub/dispatchers/group-actor.js +202 -0
- package/dist/lib/activitypub/dispatchers/group-actor.js.map +1 -0
- package/dist/lib/activitypub/dispatchers/user-actor.d.ts +47 -0
- package/dist/lib/activitypub/dispatchers/user-actor.d.ts.map +1 -0
- package/dist/lib/activitypub/dispatchers/user-actor.js +207 -0
- package/dist/lib/activitypub/dispatchers/user-actor.js.map +1 -0
- package/dist/lib/activitypub/dm-service.d.ts +35 -0
- package/dist/lib/activitypub/dm-service.d.ts.map +1 -0
- package/dist/lib/activitypub/dm-service.js +73 -0
- package/dist/lib/activitypub/dm-service.js.map +1 -0
- package/dist/lib/activitypub/entity-profile-service.d.ts +43 -0
- package/dist/lib/activitypub/entity-profile-service.d.ts.map +1 -0
- package/dist/lib/activitypub/entity-profile-service.js +80 -0
- package/dist/lib/activitypub/entity-profile-service.js.map +1 -0
- package/dist/lib/activitypub/fedify/config.d.ts +15 -0
- package/dist/lib/activitypub/fedify/config.d.ts.map +1 -0
- package/dist/lib/activitypub/fedify/config.js +38 -0
- package/dist/lib/activitypub/fedify/config.js.map +1 -0
- package/dist/lib/activitypub/fedify/context.d.ts +21 -0
- package/dist/lib/activitypub/fedify/context.d.ts.map +1 -0
- package/dist/lib/activitypub/fedify/context.js +67 -0
- package/dist/lib/activitypub/fedify/context.js.map +1 -0
- package/dist/lib/activitypub/fedify/runtime.d.ts +18 -0
- package/dist/lib/activitypub/fedify/runtime.d.ts.map +1 -0
- package/dist/lib/activitypub/fedify/runtime.js +26 -0
- package/dist/lib/activitypub/fedify/runtime.js.map +1 -0
- package/dist/lib/activitypub/friendship-service.d.ts +15 -0
- package/dist/lib/activitypub/friendship-service.d.ts.map +1 -0
- package/dist/lib/activitypub/friendship-service.js +26 -0
- package/dist/lib/activitypub/friendship-service.js.map +1 -0
- package/dist/lib/activitypub/group-service.d.ts +83 -0
- package/dist/lib/activitypub/group-service.d.ts.map +1 -0
- package/dist/lib/activitypub/group-service.js +301 -0
- package/dist/lib/activitypub/group-service.js.map +1 -0
- package/dist/lib/activitypub/http-signatures.d.ts +41 -0
- package/dist/lib/activitypub/http-signatures.d.ts.map +1 -0
- package/dist/lib/activitypub/http-signatures.js +284 -0
- package/dist/lib/activitypub/http-signatures.js.map +1 -0
- package/dist/lib/activitypub/jsonld.d.ts +39 -0
- package/dist/lib/activitypub/jsonld.d.ts.map +1 -0
- package/dist/lib/activitypub/jsonld.js +97 -0
- package/dist/lib/activitypub/jsonld.js.map +1 -0
- package/dist/lib/activitypub/listeners/friends-collection.d.ts +20 -0
- package/dist/lib/activitypub/listeners/friends-collection.d.ts.map +1 -0
- package/dist/lib/activitypub/listeners/friends-collection.js +105 -0
- package/dist/lib/activitypub/listeners/friends-collection.js.map +1 -0
- package/dist/lib/activitypub/listeners/http-signatures.d.ts +29 -0
- package/dist/lib/activitypub/listeners/http-signatures.d.ts.map +1 -0
- package/dist/lib/activitypub/listeners/http-signatures.js +208 -0
- package/dist/lib/activitypub/listeners/http-signatures.js.map +1 -0
- package/dist/lib/activitypub/listeners/inbox.d.ts +34 -0
- package/dist/lib/activitypub/listeners/inbox.d.ts.map +1 -0
- package/dist/lib/activitypub/listeners/inbox.js +226 -0
- package/dist/lib/activitypub/listeners/inbox.js.map +1 -0
- package/dist/lib/activitypub/listeners/outbox.d.ts +20 -0
- package/dist/lib/activitypub/listeners/outbox.d.ts.map +1 -0
- package/dist/lib/activitypub/listeners/outbox.js +117 -0
- package/dist/lib/activitypub/listeners/outbox.js.map +1 -0
- package/dist/lib/activitypub/remote-fetch-service.d.ts +108 -0
- package/dist/lib/activitypub/remote-fetch-service.d.ts.map +1 -0
- package/dist/lib/activitypub/remote-fetch-service.js +364 -0
- package/dist/lib/activitypub/remote-fetch-service.js.map +1 -0
- package/dist/lib/activitypub/services/abuse-prevention.d.ts +52 -0
- package/dist/lib/activitypub/services/abuse-prevention.d.ts.map +1 -0
- package/dist/lib/activitypub/services/abuse-prevention.js +118 -0
- package/dist/lib/activitypub/services/abuse-prevention.js.map +1 -0
- package/dist/lib/activitypub/services/dm-service-fedify.d.ts +46 -0
- package/dist/lib/activitypub/services/dm-service-fedify.d.ts.map +1 -0
- package/dist/lib/activitypub/services/dm-service-fedify.js +168 -0
- package/dist/lib/activitypub/services/dm-service-fedify.js.map +1 -0
- package/dist/lib/activitypub/services/fedify-converters.d.ts +51 -0
- package/dist/lib/activitypub/services/fedify-converters.d.ts.map +1 -0
- package/dist/lib/activitypub/services/fedify-converters.js +109 -0
- package/dist/lib/activitypub/services/fedify-converters.js.map +1 -0
- package/dist/lib/activitypub/services/fedify-delivery.d.ts +41 -0
- package/dist/lib/activitypub/services/fedify-delivery.d.ts.map +1 -0
- package/dist/lib/activitypub/services/fedify-delivery.js +186 -0
- package/dist/lib/activitypub/services/fedify-delivery.js.map +1 -0
- package/dist/lib/activitypub/services/follow-activity-service.d.ts +34 -0
- package/dist/lib/activitypub/services/follow-activity-service.d.ts.map +1 -0
- package/dist/lib/activitypub/services/follow-activity-service.js +64 -0
- package/dist/lib/activitypub/services/follow-activity-service.js.map +1 -0
- package/dist/lib/activitypub/services/post-service-fedify.d.ts +49 -0
- package/dist/lib/activitypub/services/post-service-fedify.d.ts.map +1 -0
- package/dist/lib/activitypub/services/post-service-fedify.js +281 -0
- package/dist/lib/activitypub/services/post-service-fedify.js.map +1 -0
- package/dist/lib/activitypub/services/remote-activity-handler.d.ts +22 -0
- package/dist/lib/activitypub/services/remote-activity-handler.d.ts.map +1 -0
- package/dist/lib/activitypub/services/remote-activity-handler.js +136 -0
- package/dist/lib/activitypub/services/remote-activity-handler.js.map +1 -0
- package/dist/lib/activitypub/standalone-mode.d.ts +34 -0
- package/dist/lib/activitypub/standalone-mode.d.ts.map +1 -0
- package/dist/lib/activitypub/standalone-mode.js +127 -0
- package/dist/lib/activitypub/standalone-mode.js.map +1 -0
- package/dist/lib/activitypub/webfinger/server.d.ts +16 -0
- package/dist/lib/activitypub/webfinger/server.d.ts.map +1 -0
- package/dist/lib/activitypub/webfinger/server.js +221 -0
- package/dist/lib/activitypub/webfinger/server.js.map +1 -0
- package/dist/lib/age-gate-middleware.d.ts +19 -0
- package/dist/lib/age-gate-middleware.d.ts.map +1 -0
- package/dist/lib/age-gate-middleware.js +26 -0
- package/dist/lib/age-gate-middleware.js.map +1 -0
- package/dist/lib/age-gate.d.ts +37 -0
- package/dist/lib/age-gate.d.ts.map +1 -0
- package/dist/lib/age-gate.js +96 -0
- package/dist/lib/age-gate.js.map +1 -0
- package/dist/lib/age-tier-transition.d.ts +21 -0
- package/dist/lib/age-tier-transition.d.ts.map +1 -0
- package/dist/lib/age-tier-transition.js +190 -0
- package/dist/lib/age-tier-transition.js.map +1 -0
- package/dist/lib/audit-logger.d.ts +142 -0
- package/dist/lib/audit-logger.d.ts.map +1 -0
- package/dist/lib/audit-logger.js +326 -0
- package/dist/lib/audit-logger.js.map +1 -0
- package/dist/lib/auth/cognito-jwt.d.ts +20 -0
- package/dist/lib/auth/cognito-jwt.d.ts.map +1 -0
- package/dist/lib/auth/cognito-jwt.js +56 -0
- package/dist/lib/auth/cognito-jwt.js.map +1 -0
- package/dist/lib/auth-context-manager.d.ts +116 -0
- package/dist/lib/auth-context-manager.d.ts.map +1 -0
- package/dist/lib/auth-context-manager.js +130 -0
- package/dist/lib/auth-context-manager.js.map +1 -0
- package/dist/lib/auth-handler.d.ts +19 -0
- package/dist/lib/auth-handler.d.ts.map +1 -0
- package/dist/lib/auth-handler.js +76 -0
- package/dist/lib/auth-handler.js.map +1 -0
- package/dist/lib/badge-handler.d.ts +20 -0
- package/dist/lib/badge-handler.d.ts.map +1 -0
- package/dist/lib/badge-handler.js +142 -0
- package/dist/lib/badge-handler.js.map +1 -0
- package/dist/lib/circle-handler.d.ts +22 -0
- package/dist/lib/circle-handler.d.ts.map +1 -0
- package/dist/lib/circle-handler.js +224 -0
- package/dist/lib/circle-handler.js.map +1 -0
- package/dist/lib/circuit-breaker.d.ts +27 -0
- package/dist/lib/circuit-breaker.d.ts.map +1 -0
- package/dist/lib/circuit-breaker.js +63 -0
- package/dist/lib/circuit-breaker.js.map +1 -0
- package/dist/lib/comment-handler.d.ts +77 -0
- package/dist/lib/comment-handler.d.ts.map +1 -0
- package/dist/lib/comment-handler.js +953 -0
- package/dist/lib/comment-handler.js.map +1 -0
- package/dist/lib/connection-code-handler.d.ts +17 -0
- package/dist/lib/connection-code-handler.d.ts.map +1 -0
- package/dist/lib/connection-code-handler.js +293 -0
- package/dist/lib/connection-code-handler.js.map +1 -0
- package/dist/lib/content-discovery.d.ts +113 -0
- package/dist/lib/content-discovery.d.ts.map +1 -0
- package/dist/lib/content-discovery.js +519 -0
- package/dist/lib/content-discovery.js.map +1 -0
- package/dist/lib/context-aware-data-access.d.ts +89 -0
- package/dist/lib/context-aware-data-access.d.ts.map +1 -0
- package/dist/lib/context-aware-data-access.js +97 -0
- package/dist/lib/context-aware-data-access.js.map +1 -0
- package/dist/lib/cors-handler.d.ts +29 -0
- package/dist/lib/cors-handler.d.ts.map +1 -0
- package/dist/lib/cors-handler.js +225 -0
- package/dist/lib/cors-handler.js.map +1 -0
- package/dist/lib/cost-accumulator.d.ts +58 -0
- package/dist/lib/cost-accumulator.d.ts.map +1 -0
- package/dist/lib/cost-accumulator.js +173 -0
- package/dist/lib/cost-accumulator.js.map +1 -0
- package/dist/lib/crypto/encryption-service.d.ts +100 -0
- package/dist/lib/crypto/encryption-service.d.ts.map +1 -0
- package/dist/lib/crypto/encryption-service.js +293 -0
- package/dist/lib/crypto/encryption-service.js.map +1 -0
- package/dist/lib/crypto/index.d.ts +22 -0
- package/dist/lib/crypto/index.d.ts.map +1 -0
- package/dist/lib/crypto/index.js +28 -0
- package/dist/lib/crypto/index.js.map +1 -0
- package/dist/lib/crypto/types.d.ts +71 -0
- package/dist/lib/crypto/types.d.ts.map +1 -0
- package/dist/lib/crypto/types.js +3 -0
- package/dist/lib/crypto/types.js.map +1 -0
- package/dist/lib/crypto/versioning.d.ts +112 -0
- package/dist/lib/crypto/versioning.d.ts.map +1 -0
- package/dist/lib/crypto/versioning.js +148 -0
- package/dist/lib/crypto/versioning.js.map +1 -0
- package/dist/lib/crypto/voting/elgamal-encryption.d.ts +156 -0
- package/dist/lib/crypto/voting/elgamal-encryption.d.ts.map +1 -0
- package/dist/lib/crypto/voting/elgamal-encryption.js +172 -0
- package/dist/lib/crypto/voting/elgamal-encryption.js.map +1 -0
- package/dist/lib/crypto/voting/encryption-scheme.d.ts +138 -0
- package/dist/lib/crypto/voting/encryption-scheme.d.ts.map +1 -0
- package/dist/lib/crypto/voting/encryption-scheme.js +13 -0
- package/dist/lib/crypto/voting/encryption-scheme.js.map +1 -0
- package/dist/lib/crypto/voting/hash-utils.d.ts +58 -0
- package/dist/lib/crypto/voting/hash-utils.d.ts.map +1 -0
- package/dist/lib/crypto/voting/hash-utils.js +73 -0
- package/dist/lib/crypto/voting/hash-utils.js.map +1 -0
- package/dist/lib/crypto/voting/hybrid-encryption.d.ts +109 -0
- package/dist/lib/crypto/voting/hybrid-encryption.d.ts.map +1 -0
- package/dist/lib/crypto/voting/hybrid-encryption.js +134 -0
- package/dist/lib/crypto/voting/hybrid-encryption.js.map +1 -0
- package/dist/lib/crypto/voting/index.d.ts +18 -0
- package/dist/lib/crypto/voting/index.d.ts.map +1 -0
- package/dist/lib/crypto/voting/index.js +27 -0
- package/dist/lib/crypto/voting/index.js.map +1 -0
- package/dist/lib/crypto/voting/post-quantum-encryption.d.ts +107 -0
- package/dist/lib/crypto/voting/post-quantum-encryption.d.ts.map +1 -0
- package/dist/lib/crypto/voting/post-quantum-encryption.js +123 -0
- package/dist/lib/crypto/voting/post-quantum-encryption.js.map +1 -0
- package/dist/lib/csrf.d.ts +95 -0
- package/dist/lib/csrf.d.ts.map +1 -0
- package/dist/lib/csrf.js +174 -0
- package/dist/lib/csrf.js.map +1 -0
- package/dist/lib/data-router.d.ts +209 -0
- package/dist/lib/data-router.d.ts.map +1 -0
- package/dist/lib/data-router.js +792 -0
- package/dist/lib/data-router.js.map +1 -0
- package/dist/lib/database-circuit-breaker.d.ts +75 -0
- package/dist/lib/database-circuit-breaker.d.ts.map +1 -0
- package/dist/lib/database-circuit-breaker.js +155 -0
- package/dist/lib/database-circuit-breaker.js.map +1 -0
- package/dist/lib/database-config.d.ts +20 -0
- package/dist/lib/database-config.d.ts.map +1 -0
- package/dist/lib/database-config.js +46 -0
- package/dist/lib/database-config.js.map +1 -0
- package/dist/lib/database-connection-manager.d.ts +99 -0
- package/dist/lib/database-connection-manager.d.ts.map +1 -0
- package/dist/lib/database-connection-manager.js +495 -0
- package/dist/lib/database-connection-manager.js.map +1 -0
- package/dist/lib/database-monitor.d.ts +89 -0
- package/dist/lib/database-monitor.d.ts.map +1 -0
- package/dist/lib/database-monitor.js +199 -0
- package/dist/lib/database-monitor.js.map +1 -0
- package/dist/lib/database-rate-limiter.d.ts +41 -0
- package/dist/lib/database-rate-limiter.d.ts.map +1 -0
- package/dist/lib/database-rate-limiter.js +90 -0
- package/dist/lib/database-rate-limiter.js.map +1 -0
- package/dist/lib/database-wrapper-helper.d.ts +44 -0
- package/dist/lib/database-wrapper-helper.d.ts.map +1 -0
- package/dist/lib/database-wrapper-helper.js +104 -0
- package/dist/lib/database-wrapper-helper.js.map +1 -0
- package/dist/lib/database-wrapper.d.ts +51 -0
- package/dist/lib/database-wrapper.d.ts.map +1 -0
- package/dist/lib/database-wrapper.js +109 -0
- package/dist/lib/database-wrapper.js.map +1 -0
- package/dist/lib/db-query-helper.d.ts +130 -0
- package/dist/lib/db-query-helper.d.ts.map +1 -0
- package/dist/lib/db-query-helper.js +105 -0
- package/dist/lib/db-query-helper.js.map +1 -0
- package/dist/lib/discovery-handler.d.ts +19 -0
- package/dist/lib/discovery-handler.d.ts.map +1 -0
- package/dist/lib/discovery-handler.js +195 -0
- package/dist/lib/discovery-handler.js.map +1 -0
- package/dist/lib/domain-reputation-service.d.ts +112 -0
- package/dist/lib/domain-reputation-service.d.ts.map +1 -0
- package/dist/lib/domain-reputation-service.js +344 -0
- package/dist/lib/domain-reputation-service.js.map +1 -0
- package/dist/lib/email-privacy.d.ts +54 -0
- package/dist/lib/email-privacy.d.ts.map +1 -0
- package/dist/lib/email-privacy.js +72 -0
- package/dist/lib/email-privacy.js.map +1 -0
- package/dist/lib/email-provider.d.ts +133 -0
- package/dist/lib/email-provider.d.ts.map +1 -0
- package/dist/lib/email-provider.js +391 -0
- package/dist/lib/email-provider.js.map +1 -0
- package/dist/lib/encryption-key-service.d.ts +115 -0
- package/dist/lib/encryption-key-service.d.ts.map +1 -0
- package/dist/lib/encryption-key-service.js +272 -0
- package/dist/lib/encryption-key-service.js.map +1 -0
- package/dist/lib/entity-handler.d.ts +59 -0
- package/dist/lib/entity-handler.d.ts.map +1 -0
- package/dist/lib/entity-handler.js +866 -0
- package/dist/lib/entity-handler.js.map +1 -0
- package/dist/lib/entity-relationship-handler.d.ts +19 -0
- package/dist/lib/entity-relationship-handler.d.ts.map +1 -0
- package/dist/lib/entity-relationship-handler.js +242 -0
- package/dist/lib/entity-relationship-handler.js.map +1 -0
- package/dist/lib/entity-tagging-errors.d.ts +32 -0
- package/dist/lib/entity-tagging-errors.d.ts.map +1 -0
- package/dist/lib/entity-tagging-errors.js +53 -0
- package/dist/lib/entity-tagging-errors.js.map +1 -0
- package/dist/lib/entity-tagging-validator.d.ts +47 -0
- package/dist/lib/entity-tagging-validator.d.ts.map +1 -0
- package/dist/lib/entity-tagging-validator.js +84 -0
- package/dist/lib/entity-tagging-validator.js.map +1 -0
- package/dist/lib/exif-stripper.d.ts +37 -0
- package/dist/lib/exif-stripper.d.ts.map +1 -0
- package/dist/lib/exif-stripper.js +62 -0
- package/dist/lib/exif-stripper.js.map +1 -0
- package/dist/lib/extension-context.d.ts +19 -0
- package/dist/lib/extension-context.d.ts.map +1 -0
- package/dist/lib/extension-context.js +78 -0
- package/dist/lib/extension-context.js.map +1 -0
- package/dist/lib/extension-route-wrapper.d.ts +21 -0
- package/dist/lib/extension-route-wrapper.d.ts.map +1 -0
- package/dist/lib/extension-route-wrapper.js +113 -0
- package/dist/lib/extension-route-wrapper.js.map +1 -0
- package/dist/lib/extension-validator.d.ts +12 -0
- package/dist/lib/extension-validator.d.ts.map +1 -0
- package/dist/lib/extension-validator.js +60 -0
- package/dist/lib/extension-validator.js.map +1 -0
- package/dist/lib/feature-flags.d.ts +56 -0
- package/dist/lib/feature-flags.d.ts.map +1 -0
- package/dist/lib/feature-flags.js +140 -0
- package/dist/lib/feature-flags.js.map +1 -0
- package/dist/lib/feature-toggle-service.d.ts +48 -0
- package/dist/lib/feature-toggle-service.d.ts.map +1 -0
- package/dist/lib/feature-toggle-service.js +167 -0
- package/dist/lib/feature-toggle-service.js.map +1 -0
- package/dist/lib/feed-handler.d.ts +152 -0
- package/dist/lib/feed-handler.d.ts.map +1 -0
- package/dist/lib/feed-handler.js +784 -0
- package/dist/lib/feed-handler.js.map +1 -0
- package/dist/lib/feed-pagination.d.ts +42 -0
- package/dist/lib/feed-pagination.d.ts.map +1 -0
- package/dist/lib/feed-pagination.js +54 -0
- package/dist/lib/feed-pagination.js.map +1 -0
- package/dist/lib/feed-personalization.d.ts +52 -0
- package/dist/lib/feed-personalization.d.ts.map +1 -0
- package/dist/lib/feed-personalization.js +105 -0
- package/dist/lib/feed-personalization.js.map +1 -0
- package/dist/lib/followers-events.d.ts +37 -0
- package/dist/lib/followers-events.d.ts.map +1 -0
- package/dist/lib/followers-events.js +149 -0
- package/dist/lib/followers-events.js.map +1 -0
- package/dist/lib/followers-handler.d.ts +21 -0
- package/dist/lib/followers-handler.d.ts.map +1 -0
- package/dist/lib/followers-handler.js +35 -0
- package/dist/lib/followers-handler.js.map +1 -0
- package/dist/lib/friends-handler.d.ts +78 -0
- package/dist/lib/friends-handler.d.ts.map +1 -0
- package/dist/lib/friends-handler.js +390 -0
- package/dist/lib/friends-handler.js.map +1 -0
- package/dist/lib/graph/dual-write-service.d.ts +116 -0
- package/dist/lib/graph/dual-write-service.d.ts.map +1 -0
- package/dist/lib/graph/dual-write-service.js +332 -0
- package/dist/lib/graph/dual-write-service.js.map +1 -0
- package/dist/lib/graph/dual-write.d.ts +396 -0
- package/dist/lib/graph/dual-write.d.ts.map +1 -0
- package/dist/lib/graph/dual-write.js +53 -0
- package/dist/lib/graph/dual-write.js.map +1 -0
- package/dist/lib/graph/errors.d.ts +90 -0
- package/dist/lib/graph/errors.d.ts.map +1 -0
- package/dist/lib/graph/errors.js +131 -0
- package/dist/lib/graph/errors.js.map +1 -0
- package/dist/lib/graph/graph-factory.d.ts +64 -0
- package/dist/lib/graph/graph-factory.d.ts.map +1 -0
- package/dist/lib/graph/graph-factory.js +190 -0
- package/dist/lib/graph/graph-factory.js.map +1 -0
- package/dist/lib/graph/graph-schema-init.d.ts +31 -0
- package/dist/lib/graph/graph-schema-init.d.ts.map +1 -0
- package/dist/lib/graph/graph-schema-init.js +105 -0
- package/dist/lib/graph/graph-schema-init.js.map +1 -0
- package/dist/lib/graph/graph-service.d.ts +479 -0
- package/dist/lib/graph/graph-service.d.ts.map +1 -0
- package/dist/lib/graph/graph-service.js +21 -0
- package/dist/lib/graph/graph-service.js.map +1 -0
- package/dist/lib/graph/index.d.ts +40 -0
- package/dist/lib/graph/index.d.ts.map +1 -0
- package/dist/lib/graph/index.js +74 -0
- package/dist/lib/graph/index.js.map +1 -0
- package/dist/lib/graph/neo4j-graph-service.d.ts +186 -0
- package/dist/lib/graph/neo4j-graph-service.d.ts.map +1 -0
- package/dist/lib/graph/neo4j-graph-service.js +1625 -0
- package/dist/lib/graph/neo4j-graph-service.js.map +1 -0
- package/dist/lib/graph/reconciliation-service.d.ts +113 -0
- package/dist/lib/graph/reconciliation-service.d.ts.map +1 -0
- package/dist/lib/graph/reconciliation-service.js +533 -0
- package/dist/lib/graph/reconciliation-service.js.map +1 -0
- package/dist/lib/graph/scoring-engine.d.ts +154 -0
- package/dist/lib/graph/scoring-engine.d.ts.map +1 -0
- package/dist/lib/graph/scoring-engine.js +286 -0
- package/dist/lib/graph/scoring-engine.js.map +1 -0
- package/dist/lib/graph/types.d.ts +480 -0
- package/dist/lib/graph/types.d.ts.map +1 -0
- package/dist/lib/graph/types.js +10 -0
- package/dist/lib/graph/types.js.map +1 -0
- package/dist/lib/hook-dispatcher.d.ts +21 -0
- package/dist/lib/hook-dispatcher.d.ts.map +1 -0
- package/dist/lib/hook-dispatcher.js +62 -0
- package/dist/lib/hook-dispatcher.js.map +1 -0
- package/dist/lib/id-generator.d.ts +29 -0
- package/dist/lib/id-generator.d.ts.map +1 -0
- package/dist/lib/id-generator.js +51 -0
- package/dist/lib/id-generator.js.map +1 -0
- package/dist/lib/input-sanitizer.d.ts +55 -0
- package/dist/lib/input-sanitizer.d.ts.map +1 -0
- package/dist/lib/input-sanitizer.js +167 -0
- package/dist/lib/input-sanitizer.js.map +1 -0
- package/dist/lib/internal-docs-dashboard.json +112 -0
- package/dist/lib/internal-docs-handler.d.ts +60 -0
- package/dist/lib/internal-docs-handler.d.ts.map +1 -0
- package/dist/lib/internal-docs-handler.js +570 -0
- package/dist/lib/internal-docs-handler.js.map +1 -0
- package/dist/lib/internal-docs-navigation.d.ts +42 -0
- package/dist/lib/internal-docs-navigation.d.ts.map +1 -0
- package/dist/lib/internal-docs-navigation.js +73 -0
- package/dist/lib/internal-docs-navigation.js.map +1 -0
- package/dist/lib/internal-docs-navigation.json +169 -0
- package/dist/lib/invitation-handler.d.ts +133 -0
- package/dist/lib/invitation-handler.d.ts.map +1 -0
- package/dist/lib/invitation-handler.js +1335 -0
- package/dist/lib/invitation-handler.js.map +1 -0
- package/dist/lib/ip-scrubber.d.ts +59 -0
- package/dist/lib/ip-scrubber.d.ts.map +1 -0
- package/dist/lib/ip-scrubber.js +101 -0
- package/dist/lib/ip-scrubber.js.map +1 -0
- package/dist/lib/kv/dynamodb-kv.d.ts +39 -0
- package/dist/lib/kv/dynamodb-kv.d.ts.map +1 -0
- package/dist/lib/kv/dynamodb-kv.js +239 -0
- package/dist/lib/kv/dynamodb-kv.js.map +1 -0
- package/dist/lib/link-security-handler.d.ts +140 -0
- package/dist/lib/link-security-handler.d.ts.map +1 -0
- package/dist/lib/link-security-handler.js +460 -0
- package/dist/lib/link-security-handler.js.map +1 -0
- package/dist/lib/logger.d.ts +100 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +201 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/media-cleanup-handler.d.ts +46 -0
- package/dist/lib/media-cleanup-handler.d.ts.map +1 -0
- package/dist/lib/media-cleanup-handler.js +190 -0
- package/dist/lib/media-cleanup-handler.js.map +1 -0
- package/dist/lib/media-handler.d.ts +150 -0
- package/dist/lib/media-handler.d.ts.map +1 -0
- package/dist/lib/media-handler.js +1544 -0
- package/dist/lib/media-handler.js.map +1 -0
- package/dist/lib/media-metadata-extractor.d.ts +71 -0
- package/dist/lib/media-metadata-extractor.d.ts.map +1 -0
- package/dist/lib/media-metadata-extractor.js +278 -0
- package/dist/lib/media-metadata-extractor.js.map +1 -0
- package/dist/lib/media-metrics.d.ts +57 -0
- package/dist/lib/media-metrics.d.ts.map +1 -0
- package/dist/lib/media-metrics.js +202 -0
- package/dist/lib/media-metrics.js.map +1 -0
- package/dist/lib/metadata/index.d.ts +6 -0
- package/dist/lib/metadata/index.d.ts.map +1 -0
- package/dist/lib/metadata/index.js +22 -0
- package/dist/lib/metadata/index.js.map +1 -0
- package/dist/lib/metadata/metadata-config.d.ts +17 -0
- package/dist/lib/metadata/metadata-config.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-config.js +23 -0
- package/dist/lib/metadata/metadata-config.js.map +1 -0
- package/dist/lib/metadata/metadata-errors.d.ts +18 -0
- package/dist/lib/metadata/metadata-errors.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-errors.js +26 -0
- package/dist/lib/metadata/metadata-errors.js.map +1 -0
- package/dist/lib/metadata/metadata-extractor.d.ts +17 -0
- package/dist/lib/metadata/metadata-extractor.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-extractor.js +264 -0
- package/dist/lib/metadata/metadata-extractor.js.map +1 -0
- package/dist/lib/metadata/metadata-sanitizer.d.ts +9 -0
- package/dist/lib/metadata/metadata-sanitizer.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-sanitizer.js +100 -0
- package/dist/lib/metadata/metadata-sanitizer.js.map +1 -0
- package/dist/lib/metadata/metadata-schemas.d.ts +131 -0
- package/dist/lib/metadata/metadata-schemas.d.ts.map +1 -0
- package/dist/lib/metadata/metadata-schemas.js +71 -0
- package/dist/lib/metadata/metadata-schemas.js.map +1 -0
- package/dist/lib/mfa/mfa-handler.d.ts +60 -0
- package/dist/lib/mfa/mfa-handler.d.ts.map +1 -0
- package/dist/lib/mfa/mfa-handler.js +150 -0
- package/dist/lib/mfa/mfa-handler.js.map +1 -0
- package/dist/lib/mfa/totp-service.d.ts +41 -0
- package/dist/lib/mfa/totp-service.d.ts.map +1 -0
- package/dist/lib/mfa/totp-service.js +183 -0
- package/dist/lib/mfa/totp-service.js.map +1 -0
- package/dist/lib/middleware/comment-rate-limit.d.ts +19 -0
- package/dist/lib/middleware/comment-rate-limit.d.ts.map +1 -0
- package/dist/lib/middleware/comment-rate-limit.js +113 -0
- package/dist/lib/middleware/comment-rate-limit.js.map +1 -0
- package/dist/lib/middleware/feature-toggle-rate-limit.d.ts +53 -0
- package/dist/lib/middleware/feature-toggle-rate-limit.d.ts.map +1 -0
- package/dist/lib/middleware/feature-toggle-rate-limit.js +164 -0
- package/dist/lib/middleware/feature-toggle-rate-limit.js.map +1 -0
- package/dist/lib/middleware.d.ts +70 -0
- package/dist/lib/middleware.d.ts.map +1 -0
- package/dist/lib/middleware.js +375 -0
- package/dist/lib/middleware.js.map +1 -0
- package/dist/lib/moderation-handler.d.ts +55 -0
- package/dist/lib/moderation-handler.d.ts.map +1 -0
- package/dist/lib/moderation-handler.js +230 -0
- package/dist/lib/moderation-handler.js.map +1 -0
- package/dist/lib/notification-handler.d.ts +65 -0
- package/dist/lib/notification-handler.d.ts.map +1 -0
- package/dist/lib/notification-handler.js +236 -0
- package/dist/lib/notification-handler.js.map +1 -0
- package/dist/lib/notification-preferences-handler.d.ts +27 -0
- package/dist/lib/notification-preferences-handler.d.ts.map +1 -0
- package/dist/lib/notification-preferences-handler.js +119 -0
- package/dist/lib/notification-preferences-handler.js.map +1 -0
- package/dist/lib/openai-budget.d.ts +45 -0
- package/dist/lib/openai-budget.d.ts.map +1 -0
- package/dist/lib/openai-budget.js +175 -0
- package/dist/lib/openai-budget.js.map +1 -0
- package/dist/lib/orphaned-media-handler.d.ts +60 -0
- package/dist/lib/orphaned-media-handler.d.ts.map +1 -0
- package/dist/lib/orphaned-media-handler.js +487 -0
- package/dist/lib/orphaned-media-handler.js.map +1 -0
- package/dist/lib/parental-control-handler.d.ts +42 -0
- package/dist/lib/parental-control-handler.d.ts.map +1 -0
- package/dist/lib/parental-control-handler.js +327 -0
- package/dist/lib/parental-control-handler.js.map +1 -0
- package/dist/lib/parental-link-handler.d.ts +38 -0
- package/dist/lib/parental-link-handler.d.ts.map +1 -0
- package/dist/lib/parental-link-handler.js +217 -0
- package/dist/lib/parental-link-handler.js.map +1 -0
- package/dist/lib/performance-metrics.d.ts +125 -0
- package/dist/lib/performance-metrics.d.ts.map +1 -0
- package/dist/lib/performance-metrics.js +277 -0
- package/dist/lib/performance-metrics.js.map +1 -0
- package/dist/lib/post-handler.d.ts +94 -0
- package/dist/lib/post-handler.d.ts.map +1 -0
- package/dist/lib/post-handler.js +1346 -0
- package/dist/lib/post-handler.js.map +1 -0
- package/dist/lib/privacy-defaults.d.ts +32 -0
- package/dist/lib/privacy-defaults.d.ts.map +1 -0
- package/dist/lib/privacy-defaults.js +85 -0
- package/dist/lib/privacy-defaults.js.map +1 -0
- package/dist/lib/privacy-handler.d.ts +43 -0
- package/dist/lib/privacy-handler.d.ts.map +1 -0
- package/dist/lib/privacy-handler.js +124 -0
- package/dist/lib/privacy-handler.js.map +1 -0
- package/dist/lib/queue/sqs-queue.d.ts +16 -0
- package/dist/lib/queue/sqs-queue.d.ts.map +1 -0
- package/dist/lib/queue/sqs-queue.js +39 -0
- package/dist/lib/queue/sqs-queue.js.map +1 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.d.ts +9 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.d.ts.map +1 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.js +37 -0
- package/dist/lib/queue-consumers/media-reconciliation-consumer.js.map +1 -0
- package/dist/lib/quiet-hours.d.ts +33 -0
- package/dist/lib/quiet-hours.d.ts.map +1 -0
- package/dist/lib/quiet-hours.js +51 -0
- package/dist/lib/quiet-hours.js.map +1 -0
- package/dist/lib/rate-limit.d.ts +95 -0
- package/dist/lib/rate-limit.d.ts.map +1 -0
- package/dist/lib/rate-limit.js +247 -0
- package/dist/lib/rate-limit.js.map +1 -0
- package/dist/lib/reaction-handler.d.ts +68 -0
- package/dist/lib/reaction-handler.d.ts.map +1 -0
- package/dist/lib/reaction-handler.js +858 -0
- package/dist/lib/reaction-handler.js.map +1 -0
- package/dist/lib/recaptcha.d.ts +16 -0
- package/dist/lib/recaptcha.d.ts.map +1 -0
- package/dist/lib/recaptcha.js +71 -0
- package/dist/lib/recaptcha.js.map +1 -0
- package/dist/lib/redirect-resolver.d.ts +78 -0
- package/dist/lib/redirect-resolver.d.ts.map +1 -0
- package/dist/lib/redirect-resolver.js +276 -0
- package/dist/lib/redirect-resolver.js.map +1 -0
- package/dist/lib/region-config.d.ts +179 -0
- package/dist/lib/region-config.d.ts.map +1 -0
- package/dist/lib/region-config.js +474 -0
- package/dist/lib/region-config.js.map +1 -0
- package/dist/lib/region-detection.d.ts +110 -0
- package/dist/lib/region-detection.d.ts.map +1 -0
- package/dist/lib/region-detection.js +408 -0
- package/dist/lib/region-detection.js.map +1 -0
- package/dist/lib/relationship-handler.d.ts +19 -0
- package/dist/lib/relationship-handler.d.ts.map +1 -0
- package/dist/lib/relationship-handler.js +233 -0
- package/dist/lib/relationship-handler.js.map +1 -0
- package/dist/lib/request-context.d.ts +103 -0
- package/dist/lib/request-context.d.ts.map +1 -0
- package/dist/lib/request-context.js +179 -0
- package/dist/lib/request-context.js.map +1 -0
- package/dist/lib/route-helpers.d.ts +74 -0
- package/dist/lib/route-helpers.d.ts.map +1 -0
- package/dist/lib/route-helpers.js +190 -0
- package/dist/lib/route-helpers.js.map +1 -0
- package/dist/lib/route-matcher.d.ts +24 -0
- package/dist/lib/route-matcher.d.ts.map +1 -0
- package/dist/lib/route-matcher.js +96 -0
- package/dist/lib/route-matcher.js.map +1 -0
- package/dist/lib/router.d.ts +26 -0
- package/dist/lib/router.d.ts.map +1 -0
- package/dist/lib/router.js +90 -0
- package/dist/lib/router.js.map +1 -0
- package/dist/lib/routes/activitypub/actor.d.ts +12 -0
- package/dist/lib/routes/activitypub/actor.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/actor.js +141 -0
- package/dist/lib/routes/activitypub/actor.js.map +1 -0
- package/dist/lib/routes/activitypub/audiences.d.ts +12 -0
- package/dist/lib/routes/activitypub/audiences.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/audiences.js +325 -0
- package/dist/lib/routes/activitypub/audiences.js.map +1 -0
- package/dist/lib/routes/activitypub/collections.d.ts +12 -0
- package/dist/lib/routes/activitypub/collections.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/collections.js +127 -0
- package/dist/lib/routes/activitypub/collections.js.map +1 -0
- package/dist/lib/routes/activitypub/entity-profile.d.ts +9 -0
- package/dist/lib/routes/activitypub/entity-profile.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/entity-profile.js +136 -0
- package/dist/lib/routes/activitypub/entity-profile.js.map +1 -0
- package/dist/lib/routes/activitypub/friends.d.ts +15 -0
- package/dist/lib/routes/activitypub/friends.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/friends.js +33 -0
- package/dist/lib/routes/activitypub/friends.js.map +1 -0
- package/dist/lib/routes/activitypub/group.d.ts +8 -0
- package/dist/lib/routes/activitypub/group.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/group.js +436 -0
- package/dist/lib/routes/activitypub/group.js.map +1 -0
- package/dist/lib/routes/activitypub/inbox.d.ts +15 -0
- package/dist/lib/routes/activitypub/inbox.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/inbox.js +125 -0
- package/dist/lib/routes/activitypub/inbox.js.map +1 -0
- package/dist/lib/routes/activitypub/messages.d.ts +12 -0
- package/dist/lib/routes/activitypub/messages.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/messages.js +374 -0
- package/dist/lib/routes/activitypub/messages.js.map +1 -0
- package/dist/lib/routes/activitypub/outbox.d.ts +15 -0
- package/dist/lib/routes/activitypub/outbox.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/outbox.js +33 -0
- package/dist/lib/routes/activitypub/outbox.js.map +1 -0
- package/dist/lib/routes/activitypub/post.d.ts +12 -0
- package/dist/lib/routes/activitypub/post.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/post.js +213 -0
- package/dist/lib/routes/activitypub/post.js.map +1 -0
- package/dist/lib/routes/activitypub/webfinger.d.ts +11 -0
- package/dist/lib/routes/activitypub/webfinger.d.ts.map +1 -0
- package/dist/lib/routes/activitypub/webfinger.js +27 -0
- package/dist/lib/routes/activitypub/webfinger.js.map +1 -0
- package/dist/lib/routes/admin-costs.d.ts +8 -0
- package/dist/lib/routes/admin-costs.d.ts.map +1 -0
- package/dist/lib/routes/admin-costs.js +89 -0
- package/dist/lib/routes/admin-costs.js.map +1 -0
- package/dist/lib/routes/admin.d.ts +6 -0
- package/dist/lib/routes/admin.d.ts.map +1 -0
- package/dist/lib/routes/admin.js +1450 -0
- package/dist/lib/routes/admin.js.map +1 -0
- package/dist/lib/routes/auth.d.ts +6 -0
- package/dist/lib/routes/auth.d.ts.map +1 -0
- package/dist/lib/routes/auth.js +49 -0
- package/dist/lib/routes/auth.js.map +1 -0
- package/dist/lib/routes/badges.d.ts +6 -0
- package/dist/lib/routes/badges.d.ts.map +1 -0
- package/dist/lib/routes/badges.js +66 -0
- package/dist/lib/routes/badges.js.map +1 -0
- package/dist/lib/routes/circles.d.ts +8 -0
- package/dist/lib/routes/circles.d.ts.map +1 -0
- package/dist/lib/routes/circles.js +135 -0
- package/dist/lib/routes/circles.js.map +1 -0
- package/dist/lib/routes/comments.d.ts +6 -0
- package/dist/lib/routes/comments.d.ts.map +1 -0
- package/dist/lib/routes/comments.js +228 -0
- package/dist/lib/routes/comments.js.map +1 -0
- package/dist/lib/routes/connection-codes.d.ts +8 -0
- package/dist/lib/routes/connection-codes.d.ts.map +1 -0
- package/dist/lib/routes/connection-codes.js +84 -0
- package/dist/lib/routes/connection-codes.js.map +1 -0
- package/dist/lib/routes/content-discovery.d.ts +11 -0
- package/dist/lib/routes/content-discovery.d.ts.map +1 -0
- package/dist/lib/routes/content-discovery.js +211 -0
- package/dist/lib/routes/content-discovery.js.map +1 -0
- package/dist/lib/routes/dashboard.d.ts +8 -0
- package/dist/lib/routes/dashboard.d.ts.map +1 -0
- package/dist/lib/routes/dashboard.js +1139 -0
- package/dist/lib/routes/dashboard.js.map +1 -0
- package/dist/lib/routes/deletion.d.ts +6 -0
- package/dist/lib/routes/deletion.d.ts.map +1 -0
- package/dist/lib/routes/deletion.js +213 -0
- package/dist/lib/routes/deletion.js.map +1 -0
- package/dist/lib/routes/discovery.d.ts +8 -0
- package/dist/lib/routes/discovery.d.ts.map +1 -0
- package/dist/lib/routes/discovery.js +67 -0
- package/dist/lib/routes/discovery.js.map +1 -0
- package/dist/lib/routes/employees.d.ts +6 -0
- package/dist/lib/routes/employees.d.ts.map +1 -0
- package/dist/lib/routes/employees.js +82 -0
- package/dist/lib/routes/employees.js.map +1 -0
- package/dist/lib/routes/entities.d.ts +8 -0
- package/dist/lib/routes/entities.d.ts.map +1 -0
- package/dist/lib/routes/entities.js +467 -0
- package/dist/lib/routes/entities.js.map +1 -0
- package/dist/lib/routes/entity-relationships.d.ts +8 -0
- package/dist/lib/routes/entity-relationships.d.ts.map +1 -0
- package/dist/lib/routes/entity-relationships.js +118 -0
- package/dist/lib/routes/entity-relationships.js.map +1 -0
- package/dist/lib/routes/export.d.ts +6 -0
- package/dist/lib/routes/export.d.ts.map +1 -0
- package/dist/lib/routes/export.js +118 -0
- package/dist/lib/routes/export.js.map +1 -0
- package/dist/lib/routes/feature-flags.d.ts +8 -0
- package/dist/lib/routes/feature-flags.d.ts.map +1 -0
- package/dist/lib/routes/feature-flags.js +75 -0
- package/dist/lib/routes/feature-flags.js.map +1 -0
- package/dist/lib/routes/feeds.d.ts +6 -0
- package/dist/lib/routes/feeds.d.ts.map +1 -0
- package/dist/lib/routes/feeds.js +131 -0
- package/dist/lib/routes/feeds.js.map +1 -0
- package/dist/lib/routes/followers.d.ts +6 -0
- package/dist/lib/routes/followers.d.ts.map +1 -0
- package/dist/lib/routes/followers.js +405 -0
- package/dist/lib/routes/followers.js.map +1 -0
- package/dist/lib/routes/friends.d.ts +6 -0
- package/dist/lib/routes/friends.d.ts.map +1 -0
- package/dist/lib/routes/friends.js +116 -0
- package/dist/lib/routes/friends.js.map +1 -0
- package/dist/lib/routes/health.d.ts +6 -0
- package/dist/lib/routes/health.d.ts.map +1 -0
- package/dist/lib/routes/health.js +129 -0
- package/dist/lib/routes/health.js.map +1 -0
- package/dist/lib/routes/index.d.ts +21 -0
- package/dist/lib/routes/index.d.ts.map +1 -0
- package/dist/lib/routes/index.js +212 -0
- package/dist/lib/routes/index.js.map +1 -0
- package/dist/lib/routes/internal-docs.d.ts +6 -0
- package/dist/lib/routes/internal-docs.d.ts.map +1 -0
- package/dist/lib/routes/internal-docs.js +44 -0
- package/dist/lib/routes/internal-docs.js.map +1 -0
- package/dist/lib/routes/invitations.d.ts +6 -0
- package/dist/lib/routes/invitations.d.ts.map +1 -0
- package/dist/lib/routes/invitations.js +67 -0
- package/dist/lib/routes/invitations.js.map +1 -0
- package/dist/lib/routes/link-reports.d.ts +11 -0
- package/dist/lib/routes/link-reports.d.ts.map +1 -0
- package/dist/lib/routes/link-reports.js +287 -0
- package/dist/lib/routes/link-reports.js.map +1 -0
- package/dist/lib/routes/map.d.ts +6 -0
- package/dist/lib/routes/map.d.ts.map +1 -0
- package/dist/lib/routes/map.js +21 -0
- package/dist/lib/routes/map.js.map +1 -0
- package/dist/lib/routes/media-metadata-visibility.d.ts +3 -0
- package/dist/lib/routes/media-metadata-visibility.d.ts.map +1 -0
- package/dist/lib/routes/media-metadata-visibility.js +184 -0
- package/dist/lib/routes/media-metadata-visibility.js.map +1 -0
- package/dist/lib/routes/media.d.ts +9 -0
- package/dist/lib/routes/media.d.ts.map +1 -0
- package/dist/lib/routes/media.js +1910 -0
- package/dist/lib/routes/media.js.map +1 -0
- package/dist/lib/routes/mfa.d.ts +8 -0
- package/dist/lib/routes/mfa.d.ts.map +1 -0
- package/dist/lib/routes/mfa.js +193 -0
- package/dist/lib/routes/mfa.js.map +1 -0
- package/dist/lib/routes/notifications.d.ts +9 -0
- package/dist/lib/routes/notifications.d.ts.map +1 -0
- package/dist/lib/routes/notifications.js +230 -0
- package/dist/lib/routes/notifications.js.map +1 -0
- package/dist/lib/routes/orphaned-media-health.d.ts +9 -0
- package/dist/lib/routes/orphaned-media-health.d.ts.map +1 -0
- package/dist/lib/routes/orphaned-media-health.js +121 -0
- package/dist/lib/routes/orphaned-media-health.js.map +1 -0
- package/dist/lib/routes/orphaned-media.d.ts +8 -0
- package/dist/lib/routes/orphaned-media.d.ts.map +1 -0
- package/dist/lib/routes/orphaned-media.js +111 -0
- package/dist/lib/routes/orphaned-media.js.map +1 -0
- package/dist/lib/routes/out.d.ts +17 -0
- package/dist/lib/routes/out.d.ts.map +1 -0
- package/dist/lib/routes/out.js +339 -0
- package/dist/lib/routes/out.js.map +1 -0
- package/dist/lib/routes/parental-controls.d.ts +8 -0
- package/dist/lib/routes/parental-controls.d.ts.map +1 -0
- package/dist/lib/routes/parental-controls.js +282 -0
- package/dist/lib/routes/parental-controls.js.map +1 -0
- package/dist/lib/routes/posts.d.ts +6 -0
- package/dist/lib/routes/posts.d.ts.map +1 -0
- package/dist/lib/routes/posts.js +518 -0
- package/dist/lib/routes/posts.js.map +1 -0
- package/dist/lib/routes/privacy.d.ts +6 -0
- package/dist/lib/routes/privacy.d.ts.map +1 -0
- package/dist/lib/routes/privacy.js +66 -0
- package/dist/lib/routes/privacy.js.map +1 -0
- package/dist/lib/routes/products.d.ts +9 -0
- package/dist/lib/routes/products.d.ts.map +1 -0
- package/dist/lib/routes/products.js +224 -0
- package/dist/lib/routes/products.js.map +1 -0
- package/dist/lib/routes/relationships.d.ts +8 -0
- package/dist/lib/routes/relationships.d.ts.map +1 -0
- package/dist/lib/routes/relationships.js +118 -0
- package/dist/lib/routes/relationships.js.map +1 -0
- package/dist/lib/routes/sentiments.d.ts +6 -0
- package/dist/lib/routes/sentiments.d.ts.map +1 -0
- package/dist/lib/routes/sentiments.js +285 -0
- package/dist/lib/routes/sentiments.js.map +1 -0
- package/dist/lib/routes/taxonomy-analytics.d.ts +8 -0
- package/dist/lib/routes/taxonomy-analytics.d.ts.map +1 -0
- package/dist/lib/routes/taxonomy-analytics.js +151 -0
- package/dist/lib/routes/taxonomy-analytics.js.map +1 -0
- package/dist/lib/routes/taxonomy.d.ts +15 -0
- package/dist/lib/routes/taxonomy.d.ts.map +1 -0
- package/dist/lib/routes/taxonomy.js +370 -0
- package/dist/lib/routes/taxonomy.js.map +1 -0
- package/dist/lib/routes/types.d.ts +46 -0
- package/dist/lib/routes/types.d.ts.map +1 -0
- package/dist/lib/routes/types.js +8 -0
- package/dist/lib/routes/types.js.map +1 -0
- package/dist/lib/routes/upload-sessions.d.ts +9 -0
- package/dist/lib/routes/upload-sessions.d.ts.map +1 -0
- package/dist/lib/routes/upload-sessions.js +268 -0
- package/dist/lib/routes/upload-sessions.js.map +1 -0
- package/dist/lib/routes/user.d.ts +8 -0
- package/dist/lib/routes/user.d.ts.map +1 -0
- package/dist/lib/routes/user.js +287 -0
- package/dist/lib/routes/user.js.map +1 -0
- package/dist/lib/routes-all.d.ts +9 -0
- package/dist/lib/routes-all.d.ts.map +1 -0
- package/dist/lib/routes-all.js +170 -0
- package/dist/lib/routes-all.js.map +1 -0
- package/dist/lib/routes.d.ts +10 -0
- package/dist/lib/routes.d.ts.map +1 -0
- package/dist/lib/routes.js +16 -0
- package/dist/lib/routes.js.map +1 -0
- package/dist/lib/scaling-health.d.ts +48 -0
- package/dist/lib/scaling-health.d.ts.map +1 -0
- package/dist/lib/scaling-health.js +363 -0
- package/dist/lib/scaling-health.js.map +1 -0
- package/dist/lib/scheduled/media-stale-cleanup.d.ts +11 -0
- package/dist/lib/scheduled/media-stale-cleanup.d.ts.map +1 -0
- package/dist/lib/scheduled/media-stale-cleanup.js +79 -0
- package/dist/lib/scheduled/media-stale-cleanup.js.map +1 -0
- package/dist/lib/scheduled/orphaned-media-monitor.d.ts +97 -0
- package/dist/lib/scheduled/orphaned-media-monitor.d.ts.map +1 -0
- package/dist/lib/scheduled/orphaned-media-monitor.js +399 -0
- package/dist/lib/scheduled/orphaned-media-monitor.js.map +1 -0
- package/dist/lib/schemas.d.ts +314 -0
- package/dist/lib/schemas.d.ts.map +1 -0
- package/dist/lib/schemas.js +235 -0
- package/dist/lib/schemas.js.map +1 -0
- package/dist/lib/secret-resolver.d.ts +88 -0
- package/dist/lib/secret-resolver.d.ts.map +1 -0
- package/dist/lib/secret-resolver.js +183 -0
- package/dist/lib/secret-resolver.js.map +1 -0
- package/dist/lib/security-event-cleaner.d.ts +61 -0
- package/dist/lib/security-event-cleaner.d.ts.map +1 -0
- package/dist/lib/security-event-cleaner.js +74 -0
- package/dist/lib/security-event-cleaner.js.map +1 -0
- package/dist/lib/security-headers.d.ts +36 -0
- package/dist/lib/security-headers.d.ts.map +1 -0
- package/dist/lib/security-headers.js +87 -0
- package/dist/lib/security-headers.js.map +1 -0
- package/dist/lib/security-monitor.d.ts +92 -0
- package/dist/lib/security-monitor.d.ts.map +1 -0
- package/dist/lib/security-monitor.js +287 -0
- package/dist/lib/security-monitor.js.map +1 -0
- package/dist/lib/sentiment-digest.d.ts +19 -0
- package/dist/lib/sentiment-digest.d.ts.map +1 -0
- package/dist/lib/sentiment-digest.js +99 -0
- package/dist/lib/sentiment-digest.js.map +1 -0
- package/dist/lib/sentiment-display.d.ts +20 -0
- package/dist/lib/sentiment-display.d.ts.map +1 -0
- package/dist/lib/sentiment-display.js +38 -0
- package/dist/lib/sentiment-display.js.map +1 -0
- package/dist/lib/services/image-normalizer.d.ts +15 -0
- package/dist/lib/services/image-normalizer.d.ts.map +1 -0
- package/dist/lib/services/image-normalizer.js +22 -0
- package/dist/lib/services/image-normalizer.js.map +1 -0
- package/dist/lib/services/media-reconciliation-service.d.ts +25 -0
- package/dist/lib/services/media-reconciliation-service.d.ts.map +1 -0
- package/dist/lib/services/media-reconciliation-service.js +191 -0
- package/dist/lib/services/media-reconciliation-service.js.map +1 -0
- package/dist/lib/services/media-upload-service.d.ts +25 -0
- package/dist/lib/services/media-upload-service.d.ts.map +1 -0
- package/dist/lib/services/media-upload-service.js +240 -0
- package/dist/lib/services/media-upload-service.js.map +1 -0
- package/dist/lib/services/user-data-deletion.d.ts +30 -0
- package/dist/lib/services/user-data-deletion.d.ts.map +1 -0
- package/dist/lib/services/user-data-deletion.js +118 -0
- package/dist/lib/services/user-data-deletion.js.map +1 -0
- package/dist/lib/session-awareness.d.ts +35 -0
- package/dist/lib/session-awareness.d.ts.map +1 -0
- package/dist/lib/session-awareness.js +79 -0
- package/dist/lib/session-awareness.js.map +1 -0
- package/dist/lib/session-config.d.ts +87 -0
- package/dist/lib/session-config.d.ts.map +1 -0
- package/dist/lib/session-config.js +165 -0
- package/dist/lib/session-config.js.map +1 -0
- package/dist/lib/session-manager.d.ts +103 -0
- package/dist/lib/session-manager.d.ts.map +1 -0
- package/dist/lib/session-manager.js +492 -0
- package/dist/lib/session-manager.js.map +1 -0
- package/dist/lib/sso-auth-handler.d.ts +12 -0
- package/dist/lib/sso-auth-handler.d.ts.map +1 -0
- package/dist/lib/sso-auth-handler.js +24 -0
- package/dist/lib/sso-auth-handler.js.map +1 -0
- package/dist/lib/storage/s3-storage.d.ts +29 -0
- package/dist/lib/storage/s3-storage.d.ts.map +1 -0
- package/dist/lib/storage/s3-storage.js +135 -0
- package/dist/lib/storage/s3-storage.js.map +1 -0
- package/dist/lib/tag-suggestions-handler.d.ts +52 -0
- package/dist/lib/tag-suggestions-handler.d.ts.map +1 -0
- package/dist/lib/tag-suggestions-handler.js +195 -0
- package/dist/lib/tag-suggestions-handler.js.map +1 -0
- package/dist/lib/taxonomy-handler-factory.d.ts +18 -0
- package/dist/lib/taxonomy-handler-factory.d.ts.map +1 -0
- package/dist/lib/taxonomy-handler-factory.js +29 -0
- package/dist/lib/taxonomy-handler-factory.js.map +1 -0
- package/dist/lib/taxonomy-handler.d.ts +142 -0
- package/dist/lib/taxonomy-handler.d.ts.map +1 -0
- package/dist/lib/taxonomy-handler.js +636 -0
- package/dist/lib/taxonomy-handler.js.map +1 -0
- package/dist/lib/taxonomy-metrics.d.ts +76 -0
- package/dist/lib/taxonomy-metrics.d.ts.map +1 -0
- package/dist/lib/taxonomy-metrics.js +201 -0
- package/dist/lib/taxonomy-metrics.js.map +1 -0
- package/dist/lib/taxonomy-search-metrics.d.ts +45 -0
- package/dist/lib/taxonomy-search-metrics.d.ts.map +1 -0
- package/dist/lib/taxonomy-search-metrics.js +75 -0
- package/dist/lib/taxonomy-search-metrics.js.map +1 -0
- package/dist/lib/tenant-context.d.ts +35 -0
- package/dist/lib/tenant-context.d.ts.map +1 -0
- package/dist/lib/tenant-context.js +54 -0
- package/dist/lib/tenant-context.js.map +1 -0
- package/dist/lib/terminology.d.ts +25 -0
- package/dist/lib/terminology.d.ts.map +1 -0
- package/dist/lib/terminology.js +44 -0
- package/dist/lib/terminology.js.map +1 -0
- package/dist/lib/theme.d.ts +29 -0
- package/dist/lib/theme.d.ts.map +1 -0
- package/dist/lib/theme.js +38 -0
- package/dist/lib/theme.js.map +1 -0
- package/dist/lib/threat-intel-service.d.ts +67 -0
- package/dist/lib/threat-intel-service.d.ts.map +1 -0
- package/dist/lib/threat-intel-service.js +193 -0
- package/dist/lib/threat-intel-service.js.map +1 -0
- package/dist/lib/types/media-reconciliation.d.ts +64 -0
- package/dist/lib/types/media-reconciliation.d.ts.map +1 -0
- package/dist/lib/types/media-reconciliation.js +8 -0
- package/dist/lib/types/media-reconciliation.js.map +1 -0
- package/dist/lib/upload-session-handler.d.ts +56 -0
- package/dist/lib/upload-session-handler.d.ts.map +1 -0
- package/dist/lib/upload-session-handler.js +312 -0
- package/dist/lib/upload-session-handler.js.map +1 -0
- package/dist/lib/user-badge.d.ts +56 -0
- package/dist/lib/user-badge.d.ts.map +1 -0
- package/dist/lib/user-badge.js +92 -0
- package/dist/lib/user-badge.js.map +1 -0
- package/dist/lib/user-deletion-handler-enhanced.d.ts +96 -0
- package/dist/lib/user-deletion-handler-enhanced.d.ts.map +1 -0
- package/dist/lib/user-deletion-handler-enhanced.js +401 -0
- package/dist/lib/user-deletion-handler-enhanced.js.map +1 -0
- package/dist/lib/user-deprovisioning.d.ts +43 -0
- package/dist/lib/user-deprovisioning.d.ts.map +1 -0
- package/dist/lib/user-deprovisioning.js +138 -0
- package/dist/lib/user-deprovisioning.js.map +1 -0
- package/dist/lib/user-export-handler.d.ts +172 -0
- package/dist/lib/user-export-handler.d.ts.map +1 -0
- package/dist/lib/user-export-handler.js +435 -0
- package/dist/lib/user-export-handler.js.map +1 -0
- package/dist/lib/validate-request.d.ts +46 -0
- package/dist/lib/validate-request.d.ts.map +1 -0
- package/dist/lib/validate-request.js +127 -0
- package/dist/lib/validate-request.js.map +1 -0
- package/dist/lib/validation/feature-toggle-schemas.d.ts +410 -0
- package/dist/lib/validation/feature-toggle-schemas.d.ts.map +1 -0
- package/dist/lib/validation/feature-toggle-schemas.js +274 -0
- package/dist/lib/validation/feature-toggle-schemas.js.map +1 -0
- package/dist/lib/validation/validate-request.d.ts +75 -0
- package/dist/lib/validation/validate-request.d.ts.map +1 -0
- package/dist/lib/validation/validate-request.js +183 -0
- package/dist/lib/validation/validate-request.js.map +1 -0
- package/dist/lib/validation.d.ts +50 -0
- package/dist/lib/validation.d.ts.map +1 -0
- package/dist/lib/validation.js +122 -0
- package/dist/lib/validation.js.map +1 -0
- package/dist/lib/version.d.ts +36 -0
- package/dist/lib/version.d.ts.map +1 -0
- package/dist/lib/version.js +44 -0
- package/dist/lib/version.js.map +1 -0
- package/dist/server.d.ts +13 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +254 -0
- package/dist/server.js.map +1 -0
- package/dist/types/cloudflare-compat.d.ts +134 -0
- package/dist/types/cloudflare-compat.d.ts.map +1 -0
- package/dist/types/cloudflare-compat.js +7 -0
- package/dist/types/cloudflare-compat.js.map +1 -0
- package/dist/worker.d.ts +16 -0
- package/dist/worker.d.ts.map +1 -0
- package/dist/worker.js +21 -0
- package/dist/worker.js.map +1 -0
- package/package.json +91 -0
- package/src/lambda/cleanup-cron.ts +37 -0
- package/src/lambda/create-auth-challenge.ts +112 -0
- package/src/lambda/custom-message.ts +30 -0
- package/src/lambda/define-auth-challenge.ts +24 -0
- package/src/lambda/delete-account-worker.ts +96 -0
- package/src/lambda/diagnostics-proxy.ts +139 -0
- package/src/lambda/e2e-sweeper.ts +140 -0
- package/src/lambda/federation-outbox-worker.ts +8 -0
- package/src/lambda/followers-events-worker.ts +8 -0
- package/src/lambda/hourly-cron.ts +103 -0
- package/src/lambda/link-check-worker.ts +8 -0
- package/src/lambda/maintenance-cron.ts +95 -0
- package/src/lambda/media-processing-worker.ts +68 -0
- package/src/lambda/media-reconciliation-worker.ts +8 -0
- package/src/lambda/nightly-cron.ts +338 -0
- package/src/lambda/post-confirmation.ts +80 -0
- package/src/lambda/pre-signup.ts +39 -0
- package/src/lambda/pre-token-generation.ts +93 -0
- package/src/lambda/tools/check-health.ts +22 -0
- package/src/lambda/tools/describe-services.ts +45 -0
- package/src/lambda/tools/get-cost-report.ts +64 -0
- package/src/lambda/tools/get-errors.ts +78 -0
- package/src/lambda/tools/get-feature-flags.ts +27 -0
- package/src/lambda/tools/get-queue-status.ts +60 -0
- package/src/lambda/tools/search-logs.ts +75 -0
- package/src/lambda/tools/send-alert.ts +37 -0
- package/src/lambda/verify-auth-challenge.ts +37 -0
|
@@ -0,0 +1,1910 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Media Routes
|
|
4
|
+
*
|
|
5
|
+
* Handles media file uploads (images, videos) for posts and profiles.
|
|
6
|
+
* Implements content-addressed storage (CAS) with SHA-256 hashing for deduplication.
|
|
7
|
+
*/
|
|
8
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
9
|
+
if (k2 === undefined) k2 = k;
|
|
10
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
11
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
12
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
13
|
+
}
|
|
14
|
+
Object.defineProperty(o, k2, desc);
|
|
15
|
+
}) : (function(o, m, k, k2) {
|
|
16
|
+
if (k2 === undefined) k2 = k;
|
|
17
|
+
o[k2] = m[k];
|
|
18
|
+
}));
|
|
19
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
20
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
21
|
+
}) : function(o, v) {
|
|
22
|
+
o["default"] = v;
|
|
23
|
+
});
|
|
24
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
25
|
+
var ownKeys = function(o) {
|
|
26
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
27
|
+
var ar = [];
|
|
28
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
29
|
+
return ar;
|
|
30
|
+
};
|
|
31
|
+
return ownKeys(o);
|
|
32
|
+
};
|
|
33
|
+
return function (mod) {
|
|
34
|
+
if (mod && mod.__esModule) return mod;
|
|
35
|
+
var result = {};
|
|
36
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
37
|
+
__setModuleDefault(result, mod);
|
|
38
|
+
return result;
|
|
39
|
+
};
|
|
40
|
+
})();
|
|
41
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
42
|
+
exports.mediaRoutes = void 0;
|
|
43
|
+
const cors_handler_1 = require("../cors-handler");
|
|
44
|
+
const database_connection_manager_1 = require("../database-connection-manager");
|
|
45
|
+
const db_query_helper_1 = require("../db-query-helper");
|
|
46
|
+
const logger_1 = require("../logger");
|
|
47
|
+
const media_handler_1 = require("../media-handler");
|
|
48
|
+
const middleware_1 = require("../middleware");
|
|
49
|
+
const rate_limit_1 = require("../rate-limit");
|
|
50
|
+
const secret_resolver_1 = require("../secret-resolver");
|
|
51
|
+
const security_headers_1 = require("../security-headers");
|
|
52
|
+
const session_manager_1 = require("../session-manager");
|
|
53
|
+
const image_normalizer_1 = require("../services/image-normalizer");
|
|
54
|
+
const media_upload_service_1 = require("../services/media-upload-service");
|
|
55
|
+
/**
|
|
56
|
+
* Generate SHA-256 content hash for content-addressed storage
|
|
57
|
+
*/
|
|
58
|
+
async function generateContentHash(file) {
|
|
59
|
+
const hashBuffer = await crypto.subtle.digest("SHA-256", file);
|
|
60
|
+
const hashArray = Array.from(new Uint8Array(hashBuffer));
|
|
61
|
+
return hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* Get file extension from MIME type
|
|
65
|
+
*/
|
|
66
|
+
function getExtensionFromMimeType(mimeType) {
|
|
67
|
+
const mimeMap = {
|
|
68
|
+
"image/jpeg": "jpg",
|
|
69
|
+
"image/jpg": "jpg",
|
|
70
|
+
"image/png": "png",
|
|
71
|
+
"image/gif": "gif",
|
|
72
|
+
"image/webp": "webp",
|
|
73
|
+
"video/mp4": "mp4",
|
|
74
|
+
"video/webm": "webm",
|
|
75
|
+
"video/quicktime": "mov",
|
|
76
|
+
};
|
|
77
|
+
return mimeMap[mimeType] || "bin";
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* Validate magic numbers (file signatures) to ensure file type matches declared MIME type
|
|
81
|
+
*/
|
|
82
|
+
function validateMagicNumbers(bytes, declaredMimeType) {
|
|
83
|
+
// JPEG: FF D8 FF
|
|
84
|
+
if (declaredMimeType === "image/jpeg" || declaredMimeType === "image/jpg") {
|
|
85
|
+
if (bytes[0] === 0xff && bytes[1] === 0xd8 && bytes[2] === 0xff) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
// PNG: 89 50 4E 47 0D 0A 1A 0A
|
|
90
|
+
if (declaredMimeType === "image/png") {
|
|
91
|
+
if (bytes[0] === 0x89 &&
|
|
92
|
+
bytes[1] === 0x50 &&
|
|
93
|
+
bytes[2] === 0x4e &&
|
|
94
|
+
bytes[3] === 0x47 &&
|
|
95
|
+
bytes[4] === 0x0d &&
|
|
96
|
+
bytes[5] === 0x0a &&
|
|
97
|
+
bytes[6] === 0x1a &&
|
|
98
|
+
bytes[7] === 0x0a) {
|
|
99
|
+
return true;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
// GIF: 47 49 46 38 (GIF8)
|
|
103
|
+
if (declaredMimeType === "image/gif") {
|
|
104
|
+
if (bytes[0] === 0x47 &&
|
|
105
|
+
bytes[1] === 0x49 &&
|
|
106
|
+
bytes[2] === 0x46 &&
|
|
107
|
+
bytes[3] === 0x38) {
|
|
108
|
+
return true;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
// WebP: RIFF ... WEBP
|
|
112
|
+
if (declaredMimeType === "image/webp") {
|
|
113
|
+
if (bytes[0] === 0x52 &&
|
|
114
|
+
bytes[1] === 0x49 &&
|
|
115
|
+
bytes[2] === 0x46 &&
|
|
116
|
+
bytes[3] === 0x46 &&
|
|
117
|
+
bytes.length >= 12 &&
|
|
118
|
+
bytes[8] === 0x57 &&
|
|
119
|
+
bytes[9] === 0x45 &&
|
|
120
|
+
bytes[10] === 0x42 &&
|
|
121
|
+
bytes[11] === 0x50) {
|
|
122
|
+
return true;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
// MP4: ftyp box at offset 4
|
|
126
|
+
if (declaredMimeType === "video/mp4") {
|
|
127
|
+
// MP4 files start with ftyp box: bytes 4-7 should be "ftyp"
|
|
128
|
+
if (bytes.length >= 8 &&
|
|
129
|
+
bytes[4] === 0x66 &&
|
|
130
|
+
bytes[5] === 0x74 &&
|
|
131
|
+
bytes[6] === 0x79 &&
|
|
132
|
+
bytes[7] === 0x70) {
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
// WebM: starts with 1A 45 DF A3
|
|
137
|
+
if (declaredMimeType === "video/webm") {
|
|
138
|
+
if (bytes[0] === 0x1a &&
|
|
139
|
+
bytes[1] === 0x45 &&
|
|
140
|
+
bytes[2] === 0xdf &&
|
|
141
|
+
bytes[3] === 0xa3) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// QuickTime: ftyp box (similar to MP4)
|
|
146
|
+
if (declaredMimeType === "video/quicktime") {
|
|
147
|
+
if (bytes.length >= 8 &&
|
|
148
|
+
bytes[4] === 0x66 &&
|
|
149
|
+
bytes[5] === 0x74 &&
|
|
150
|
+
bytes[6] === 0x79 &&
|
|
151
|
+
bytes[7] === 0x70) {
|
|
152
|
+
return true;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
// HEIC/HEIF: ISO Base Media File Format with ftyp box
|
|
156
|
+
if (declaredMimeType === "image/heic" || declaredMimeType === "image/heif") {
|
|
157
|
+
if (bytes.length >= 8 &&
|
|
158
|
+
bytes[0] === 0x00 &&
|
|
159
|
+
bytes[1] === 0x00 &&
|
|
160
|
+
bytes[2] === 0x00 &&
|
|
161
|
+
bytes[4] === 0x66 &&
|
|
162
|
+
bytes[5] === 0x74 &&
|
|
163
|
+
bytes[6] === 0x79 &&
|
|
164
|
+
bytes[7] === 0x70) {
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
return false;
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Serve media file by content hash with variant support
|
|
172
|
+
* Shared function used by both /api/media/:mediaId and /api/media/:hash routes
|
|
173
|
+
*/
|
|
174
|
+
async function serveMediaByHash(contentHash, variant, request, env, session) {
|
|
175
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
176
|
+
logger.debug("SERVE MEDIA BY HASH: Starting", {
|
|
177
|
+
contentHash,
|
|
178
|
+
variant,
|
|
179
|
+
userId: session.userId,
|
|
180
|
+
});
|
|
181
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
182
|
+
try {
|
|
183
|
+
// Wrap entire function in try-catch to catch any unexpected errors
|
|
184
|
+
logger.debug("SERVE MEDIA: Inside try block");
|
|
185
|
+
// Find media file in database - using retry logic
|
|
186
|
+
let mediaFile = null;
|
|
187
|
+
try {
|
|
188
|
+
const region = "US"; // TODO: Get from session or request
|
|
189
|
+
// Using retry logic with exponential backoff for connection resilience
|
|
190
|
+
mediaFile = await (0, db_query_helper_1.withQueryTimeoutAndRetry)(database_connection_manager_1.sharedDatabaseConnectionManager, region, env, async (db) => {
|
|
191
|
+
const dbAny = db;
|
|
192
|
+
if (dbAny.mediaFile) {
|
|
193
|
+
return await dbAny.mediaFile.findUnique({
|
|
194
|
+
where: { contentHash },
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
return null;
|
|
198
|
+
}, {
|
|
199
|
+
...db_query_helper_1.QueryTimeoutPresets.USER_FACING,
|
|
200
|
+
maxRetries: 3,
|
|
201
|
+
baseDelayMs: 100,
|
|
202
|
+
context: {
|
|
203
|
+
operation: "media_get",
|
|
204
|
+
contentHash,
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
if (!mediaFile) {
|
|
208
|
+
logger.debug("MediaFile not found in database, using fallback key lookup", {
|
|
209
|
+
contentHash,
|
|
210
|
+
variant,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
logger.debug("MediaFile found in database", {
|
|
215
|
+
contentHash,
|
|
216
|
+
variant,
|
|
217
|
+
originalKey: mediaFile.originalKey,
|
|
218
|
+
optimizedKey: mediaFile.optimizedKey,
|
|
219
|
+
thumbnailKey: mediaFile.thumbnailKey,
|
|
220
|
+
});
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
catch (error) {
|
|
224
|
+
logger.warn("Failed to query MediaFile, using fallback key lookup", {
|
|
225
|
+
error: error.message,
|
|
226
|
+
contentHash,
|
|
227
|
+
variant,
|
|
228
|
+
});
|
|
229
|
+
}
|
|
230
|
+
// Fetch from R2
|
|
231
|
+
const r2Bucket = env.MEDIA_BUCKET_R2 || env.R2_BUCKET;
|
|
232
|
+
if (!r2Bucket) {
|
|
233
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Media storage not configured" }), { status: 503, headers: { "content-type": "application/json" } });
|
|
234
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
235
|
+
}
|
|
236
|
+
// Determine which R2 key to serve
|
|
237
|
+
let r2Key = null;
|
|
238
|
+
let contentType = "application/octet-stream";
|
|
239
|
+
if (mediaFile) {
|
|
240
|
+
logger.debug("SERVE MEDIA: Found database record", {
|
|
241
|
+
contentHash,
|
|
242
|
+
variant,
|
|
243
|
+
hasOriginalKey: !!mediaFile.originalKey,
|
|
244
|
+
hasOptimizedKey: !!mediaFile.optimizedKey,
|
|
245
|
+
hasThumbnailKey: !!mediaFile.thumbnailKey,
|
|
246
|
+
originalKey: mediaFile.originalKey,
|
|
247
|
+
optimizedKey: mediaFile.optimizedKey,
|
|
248
|
+
thumbnailKey: mediaFile.thumbnailKey,
|
|
249
|
+
});
|
|
250
|
+
switch (variant) {
|
|
251
|
+
case "thumbnail":
|
|
252
|
+
r2Key =
|
|
253
|
+
mediaFile.thumbnailKey ||
|
|
254
|
+
mediaFile.optimizedKey ||
|
|
255
|
+
mediaFile.originalKey;
|
|
256
|
+
contentType =
|
|
257
|
+
mediaFile.thumbnailKey || mediaFile.optimizedKey
|
|
258
|
+
? "image/webp"
|
|
259
|
+
: mediaFile.mimeType || "application/octet-stream";
|
|
260
|
+
break;
|
|
261
|
+
case "optimized":
|
|
262
|
+
// For optimized variant, prefer optimized but ALWAYS fall back to original
|
|
263
|
+
r2Key = mediaFile.optimizedKey || mediaFile.originalKey;
|
|
264
|
+
// If still no key, this is a data integrity issue - log and continue to fallback
|
|
265
|
+
if (!r2Key) {
|
|
266
|
+
logger.error("SERVE MEDIA: Database record has no keys!", {
|
|
267
|
+
contentHash,
|
|
268
|
+
mediaFileId: mediaFile.id,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
contentType = mediaFile.optimizedKey
|
|
272
|
+
? "image/webp"
|
|
273
|
+
: mediaFile.mimeType || "application/octet-stream";
|
|
274
|
+
break;
|
|
275
|
+
case "original":
|
|
276
|
+
r2Key = mediaFile.originalKey;
|
|
277
|
+
contentType = mediaFile.mimeType || "application/octet-stream";
|
|
278
|
+
break;
|
|
279
|
+
default:
|
|
280
|
+
// Default to optimized with fallback to original
|
|
281
|
+
r2Key = mediaFile.optimizedKey || mediaFile.originalKey;
|
|
282
|
+
contentType = mediaFile.optimizedKey
|
|
283
|
+
? "image/webp"
|
|
284
|
+
: mediaFile.mimeType || "application/octet-stream";
|
|
285
|
+
}
|
|
286
|
+
logger.debug("SERVE MEDIA: Using database record", {
|
|
287
|
+
contentHash,
|
|
288
|
+
variant,
|
|
289
|
+
r2Key,
|
|
290
|
+
contentType,
|
|
291
|
+
hasOptimized: !!mediaFile.optimizedKey,
|
|
292
|
+
hasOriginal: !!mediaFile.originalKey,
|
|
293
|
+
});
|
|
294
|
+
// CRITICAL FIX: If r2Key is still null after using database record,
|
|
295
|
+
// fall back to R2 key lookup
|
|
296
|
+
if (!r2Key) {
|
|
297
|
+
logger.debug("SERVE MEDIA: Database record has no keys, falling back to R2 lookup", {
|
|
298
|
+
contentHash,
|
|
299
|
+
variant,
|
|
300
|
+
});
|
|
301
|
+
// Set mediaFile to null to trigger fallback logic
|
|
302
|
+
mediaFile = null;
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
if (!mediaFile) {
|
|
306
|
+
// Fallback: construct key from hash and variant
|
|
307
|
+
logger.debug("SERVE MEDIA: Using R2 fallback key lookup", {
|
|
308
|
+
contentHash,
|
|
309
|
+
variant,
|
|
310
|
+
});
|
|
311
|
+
const commonExtensions = ["jpg", "jpeg", "png", "webp", "gif"];
|
|
312
|
+
// First, try to find the original file with any extension
|
|
313
|
+
let foundOriginalKey = null;
|
|
314
|
+
let foundContentType = "image/jpeg";
|
|
315
|
+
for (const ext of commonExtensions) {
|
|
316
|
+
const testKey = `media/${contentHash}.${ext}`;
|
|
317
|
+
logger.debug("SERVE MEDIA: Trying R2 key", { testKey });
|
|
318
|
+
try {
|
|
319
|
+
const testObject = await r2Bucket.head(testKey);
|
|
320
|
+
if (testObject) {
|
|
321
|
+
foundOriginalKey = testKey;
|
|
322
|
+
foundContentType = `image/${ext === "jpg" ? "jpeg" : ext}`;
|
|
323
|
+
logger.debug("SERVE MEDIA: Found media file in R2 fallback", {
|
|
324
|
+
contentHash,
|
|
325
|
+
key: testKey,
|
|
326
|
+
contentType: foundContentType,
|
|
327
|
+
});
|
|
328
|
+
break;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
catch (error) {
|
|
332
|
+
// head() can throw errors, continue trying other extensions
|
|
333
|
+
logger.debug("SERVE MEDIA: R2 head() failed for key", {
|
|
334
|
+
key: testKey,
|
|
335
|
+
error: error.message,
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
logger.debug("SERVE MEDIA: Original file search complete", {
|
|
340
|
+
foundOriginalKey,
|
|
341
|
+
foundContentType,
|
|
342
|
+
});
|
|
343
|
+
// Now determine which key to use based on variant
|
|
344
|
+
if (variant === "thumbnail") {
|
|
345
|
+
// Try thumbnail first
|
|
346
|
+
let foundThumb = false;
|
|
347
|
+
for (const ext of ["webp", ...commonExtensions]) {
|
|
348
|
+
const testKey = `media/${contentHash}_thumb.${ext}`;
|
|
349
|
+
try {
|
|
350
|
+
const testObject = await r2Bucket.head(testKey);
|
|
351
|
+
if (testObject) {
|
|
352
|
+
r2Key = testKey;
|
|
353
|
+
contentType = "image/webp";
|
|
354
|
+
foundThumb = true;
|
|
355
|
+
break;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
catch (error) {
|
|
359
|
+
// head() can throw errors, continue trying other extensions
|
|
360
|
+
logger.debug("R2 head() failed for thumbnail key", {
|
|
361
|
+
key: testKey,
|
|
362
|
+
error: error.message,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
// Fall back to optimized if no thumbnail
|
|
367
|
+
if (!foundThumb) {
|
|
368
|
+
for (const ext of ["webp", ...commonExtensions]) {
|
|
369
|
+
const testKey = `media/${contentHash}_opt.${ext}`;
|
|
370
|
+
try {
|
|
371
|
+
const testObject = await r2Bucket.head(testKey);
|
|
372
|
+
if (testObject) {
|
|
373
|
+
r2Key = testKey;
|
|
374
|
+
contentType = "image/webp";
|
|
375
|
+
foundThumb = true;
|
|
376
|
+
break;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
catch (error) {
|
|
380
|
+
// head() can throw errors, continue trying other extensions
|
|
381
|
+
logger.debug("R2 head() failed for optimized key", {
|
|
382
|
+
key: testKey,
|
|
383
|
+
error: error.message,
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Fall back to original if no thumbnail or optimized
|
|
389
|
+
if (!foundThumb && foundOriginalKey) {
|
|
390
|
+
r2Key = foundOriginalKey;
|
|
391
|
+
contentType = foundContentType;
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
else if (variant === "optimized") {
|
|
395
|
+
// Try optimized first
|
|
396
|
+
logger.debug("SERVE MEDIA: Looking for optimized variant", {
|
|
397
|
+
contentHash,
|
|
398
|
+
});
|
|
399
|
+
let foundOpt = false;
|
|
400
|
+
for (const ext of ["webp", ...commonExtensions]) {
|
|
401
|
+
const testKey = `media/${contentHash}_opt.${ext}`;
|
|
402
|
+
logger.debug("SERVE MEDIA: Trying optimized key", { testKey });
|
|
403
|
+
try {
|
|
404
|
+
const testObject = await r2Bucket.head(testKey);
|
|
405
|
+
if (testObject) {
|
|
406
|
+
r2Key = testKey;
|
|
407
|
+
contentType = "image/webp";
|
|
408
|
+
foundOpt = true;
|
|
409
|
+
logger.debug("SERVE MEDIA: Found optimized variant", { testKey });
|
|
410
|
+
break;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
catch (error) {
|
|
414
|
+
// head() can throw errors, continue trying other extensions
|
|
415
|
+
logger.debug("SERVE MEDIA: R2 head() failed for optimized key", {
|
|
416
|
+
key: testKey,
|
|
417
|
+
error: error.message,
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
// Fall back to original if no optimized version
|
|
422
|
+
// ALWAYS fall back to original if we didn't find an optimized version
|
|
423
|
+
if (!foundOpt) {
|
|
424
|
+
if (foundOriginalKey) {
|
|
425
|
+
r2Key = foundOriginalKey;
|
|
426
|
+
contentType = foundContentType;
|
|
427
|
+
logger.debug("SERVE MEDIA: Falling back to original for optimized variant", {
|
|
428
|
+
r2Key,
|
|
429
|
+
contentType,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
else {
|
|
433
|
+
// If we still haven't found the original, log it for debugging
|
|
434
|
+
logger.debug("SERVE MEDIA: No optimized version found and foundOriginalKey is null", {
|
|
435
|
+
contentHash,
|
|
436
|
+
variant,
|
|
437
|
+
});
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
else {
|
|
442
|
+
// Original variant or any other variant: use the found original key
|
|
443
|
+
if (foundOriginalKey) {
|
|
444
|
+
r2Key = foundOriginalKey;
|
|
445
|
+
contentType = foundContentType;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
logger.debug("SERVE MEDIA: Final R2 key selection", {
|
|
450
|
+
r2Key,
|
|
451
|
+
contentType,
|
|
452
|
+
variant,
|
|
453
|
+
contentHash,
|
|
454
|
+
hasMediaFile: !!mediaFile,
|
|
455
|
+
});
|
|
456
|
+
// CRITICAL FIX: If r2Key is still null at this point, try one more fallback
|
|
457
|
+
// This handles edge cases where the database record exists but has null keys,
|
|
458
|
+
// or the fallback R2 lookup failed for some reason
|
|
459
|
+
if (!r2Key) {
|
|
460
|
+
logger.debug("SERVE MEDIA: r2Key is null, attempting final fallback", {
|
|
461
|
+
contentHash,
|
|
462
|
+
variant,
|
|
463
|
+
});
|
|
464
|
+
// Try to find the file with common extensions
|
|
465
|
+
const commonExtensions = ["png", "jpg", "jpeg", "webp", "gif"];
|
|
466
|
+
for (const ext of commonExtensions) {
|
|
467
|
+
const testKey = `media/${contentHash}.${ext}`;
|
|
468
|
+
logger.debug("SERVE MEDIA: Final fallback trying key", { testKey });
|
|
469
|
+
try {
|
|
470
|
+
const testObject = await r2Bucket.head(testKey);
|
|
471
|
+
if (testObject) {
|
|
472
|
+
r2Key = testKey;
|
|
473
|
+
contentType = `image/${ext === "jpg" ? "jpeg" : ext}`;
|
|
474
|
+
logger.debug("SERVE MEDIA: Final fallback found file", {
|
|
475
|
+
r2Key,
|
|
476
|
+
contentType,
|
|
477
|
+
});
|
|
478
|
+
break;
|
|
479
|
+
}
|
|
480
|
+
}
|
|
481
|
+
catch (error) {
|
|
482
|
+
logger.debug("SERVE MEDIA: Final fallback head() failed", {
|
|
483
|
+
key: testKey,
|
|
484
|
+
error: error.message,
|
|
485
|
+
});
|
|
486
|
+
}
|
|
487
|
+
}
|
|
488
|
+
}
|
|
489
|
+
if (!r2Key) {
|
|
490
|
+
logger.debug("SERVE MEDIA: No R2 key found, returning 404", {
|
|
491
|
+
contentHash,
|
|
492
|
+
variant,
|
|
493
|
+
hasMediaFile: !!mediaFile,
|
|
494
|
+
});
|
|
495
|
+
// In dev, return detailed debug info
|
|
496
|
+
const debugInfo = env.ENVIRONMENT === "dev"
|
|
497
|
+
? {
|
|
498
|
+
contentHash,
|
|
499
|
+
variant,
|
|
500
|
+
hasMediaFile: !!mediaFile,
|
|
501
|
+
mediaFileKeys: mediaFile
|
|
502
|
+
? {
|
|
503
|
+
original: mediaFile.originalKey,
|
|
504
|
+
optimized: mediaFile.optimizedKey,
|
|
505
|
+
thumbnail: mediaFile.thumbnailKey,
|
|
506
|
+
}
|
|
507
|
+
: null,
|
|
508
|
+
codeVersion: "v2-with-debug",
|
|
509
|
+
}
|
|
510
|
+
: undefined;
|
|
511
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
512
|
+
error: "Media not found",
|
|
513
|
+
source: "serveMediaByHash-noKey",
|
|
514
|
+
...(debugInfo && { debug: debugInfo }),
|
|
515
|
+
}), { status: 404, headers: { "content-type": "application/json" } });
|
|
516
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
517
|
+
}
|
|
518
|
+
const object = await r2Bucket.get(r2Key);
|
|
519
|
+
if (!object) {
|
|
520
|
+
logger.debug("SERVE MEDIA: R2 object not found", {
|
|
521
|
+
r2Key,
|
|
522
|
+
contentHash,
|
|
523
|
+
variant,
|
|
524
|
+
});
|
|
525
|
+
// In dev, return detailed debug info
|
|
526
|
+
const debugInfo = env.ENVIRONMENT === "dev"
|
|
527
|
+
? {
|
|
528
|
+
r2Key,
|
|
529
|
+
contentHash,
|
|
530
|
+
variant,
|
|
531
|
+
message: "R2 object not found at key",
|
|
532
|
+
codeVersion: "v2-with-debug",
|
|
533
|
+
}
|
|
534
|
+
: undefined;
|
|
535
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
536
|
+
error: "Media not found",
|
|
537
|
+
source: "serveMediaByHash",
|
|
538
|
+
...(debugInfo && { debug: debugInfo }),
|
|
539
|
+
}), { status: 404, headers: { "content-type": "application/json" } });
|
|
540
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
541
|
+
}
|
|
542
|
+
// Get content type from object metadata or use determined type
|
|
543
|
+
const objectContentType = object.httpMetadata?.contentType || contentType;
|
|
544
|
+
// Return file with appropriate cache headers
|
|
545
|
+
const response = new Response(object.body, {
|
|
546
|
+
headers: {
|
|
547
|
+
"Content-Type": objectContentType,
|
|
548
|
+
"Cache-Control": `no-cache, no-store, must-revalidate`,
|
|
549
|
+
Pragma: "no-cache",
|
|
550
|
+
Expires: "0",
|
|
551
|
+
"Cache-Key": `media:${session.userId}:${contentHash}:${variant}`,
|
|
552
|
+
"X-Content-Type-Options": "nosniff",
|
|
553
|
+
"X-Debug-Variant": variant, // Simple debug header
|
|
554
|
+
"X-Debug-Timestamp": Date.now().toString(), // Unique per request
|
|
555
|
+
},
|
|
556
|
+
});
|
|
557
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
558
|
+
}
|
|
559
|
+
catch (unexpectedError) {
|
|
560
|
+
// Catch any unexpected errors in serveMediaByHash
|
|
561
|
+
logger.error("SERVE MEDIA BY HASH: Unexpected error", {
|
|
562
|
+
error: unexpectedError.message,
|
|
563
|
+
stack: unexpectedError.stack,
|
|
564
|
+
contentHash,
|
|
565
|
+
variant,
|
|
566
|
+
});
|
|
567
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
568
|
+
error: "Internal server error",
|
|
569
|
+
message: unexpectedError.message || "An unexpected error occurred",
|
|
570
|
+
source: "serveMediaByHash-unexpected",
|
|
571
|
+
contentHash,
|
|
572
|
+
variant,
|
|
573
|
+
}), { status: 500, headers: { "content-type": "application/json" } });
|
|
574
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
/**
|
|
578
|
+
* Check for suspicious patterns in file content
|
|
579
|
+
*/
|
|
580
|
+
function checkSuspiciousContent(bytes, mimeType) {
|
|
581
|
+
const suspicious = [];
|
|
582
|
+
// Check for executable patterns (MZ header = Windows executable)
|
|
583
|
+
if (bytes[0] === 0x4d && bytes[1] === 0x5a) {
|
|
584
|
+
suspicious.push("Executable header detected");
|
|
585
|
+
}
|
|
586
|
+
// Check for script patterns in first 1KB
|
|
587
|
+
if (bytes.length > 1024) {
|
|
588
|
+
const text = new TextDecoder("utf-8", {
|
|
589
|
+
fatal: false,
|
|
590
|
+
ignoreBOM: true,
|
|
591
|
+
}).decode(bytes.slice(0, 1024));
|
|
592
|
+
if (text.includes("<?php") || text.includes("<script")) {
|
|
593
|
+
suspicious.push("Script content detected");
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
// Check for unusually large metadata sections in JPEG
|
|
597
|
+
if (mimeType === "image/jpeg" || mimeType === "image/jpg") {
|
|
598
|
+
let metadataSize = 0;
|
|
599
|
+
let offset = 2; // Skip FF D8
|
|
600
|
+
while (offset < bytes.length && bytes[offset] === 0xff) {
|
|
601
|
+
const marker = bytes[offset + 1];
|
|
602
|
+
if (marker >= 0xe0 && marker <= 0xef) {
|
|
603
|
+
// APP segment
|
|
604
|
+
if (offset + 3 < bytes.length) {
|
|
605
|
+
const segmentLength = (bytes[offset + 2] << 8) | bytes[offset + 3];
|
|
606
|
+
metadataSize += segmentLength;
|
|
607
|
+
offset += segmentLength + 2;
|
|
608
|
+
}
|
|
609
|
+
else {
|
|
610
|
+
break;
|
|
611
|
+
}
|
|
612
|
+
}
|
|
613
|
+
else {
|
|
614
|
+
break;
|
|
615
|
+
}
|
|
616
|
+
}
|
|
617
|
+
if (metadataSize > 64 * 1024) {
|
|
618
|
+
suspicious.push("Excessive metadata detected");
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return suspicious;
|
|
622
|
+
}
|
|
623
|
+
exports.mediaRoutes = [
|
|
624
|
+
{
|
|
625
|
+
path: "/api/media/upload",
|
|
626
|
+
method: "POST",
|
|
627
|
+
handler: async (request, env) => {
|
|
628
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
629
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
630
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
631
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
632
|
+
// Check authentication
|
|
633
|
+
const authHeader = request.headers.get("Authorization");
|
|
634
|
+
logger.debug("[Media Upload] Attempting to get session", {
|
|
635
|
+
hasCookie: !!request.headers.get("Cookie"),
|
|
636
|
+
hasAuthHeader: !!authHeader,
|
|
637
|
+
authHeaderPreview: authHeader?.substring(0, 50) || "none",
|
|
638
|
+
});
|
|
639
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
640
|
+
if (!session) {
|
|
641
|
+
logger.warn("[Media Upload] Unauthorized - no valid session", {
|
|
642
|
+
hasCookie: !!request.headers.get("Cookie"),
|
|
643
|
+
hasAuthHeader: !!authHeader,
|
|
644
|
+
});
|
|
645
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
646
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
647
|
+
}
|
|
648
|
+
// Apply rate limiting: 10 uploads per 60s per user
|
|
649
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media/upload", 10, 60, session.userId);
|
|
650
|
+
if (rateLimitResponse) {
|
|
651
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
// Parse multipart form data
|
|
655
|
+
let formData;
|
|
656
|
+
try {
|
|
657
|
+
formData = await request.formData();
|
|
658
|
+
}
|
|
659
|
+
catch (error) {
|
|
660
|
+
logger.error("Media upload failed: Error parsing form data", {
|
|
661
|
+
userId: session.userId,
|
|
662
|
+
error: error.message,
|
|
663
|
+
});
|
|
664
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
665
|
+
error: "Invalid request format",
|
|
666
|
+
message: "Failed to parse multipart form data",
|
|
667
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
668
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
669
|
+
}
|
|
670
|
+
const file = formData.get("file");
|
|
671
|
+
if (!file) {
|
|
672
|
+
logger.warn("Media upload failed: No file provided", {
|
|
673
|
+
userId: session.userId,
|
|
674
|
+
formDataKeys: Array.from(formData.keys()),
|
|
675
|
+
});
|
|
676
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
677
|
+
error: "No file provided",
|
|
678
|
+
message: "File field is required in multipart form data",
|
|
679
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
680
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
681
|
+
}
|
|
682
|
+
// Check if file is empty
|
|
683
|
+
if (file.size === 0) {
|
|
684
|
+
logger.warn("Media upload failed: Empty file", {
|
|
685
|
+
userId: session.userId,
|
|
686
|
+
fileName: file.name,
|
|
687
|
+
});
|
|
688
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
689
|
+
error: "Empty file",
|
|
690
|
+
message: "File cannot be empty",
|
|
691
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
692
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
693
|
+
}
|
|
694
|
+
// Validate file type
|
|
695
|
+
const allowedImageTypes = [
|
|
696
|
+
"image/jpeg",
|
|
697
|
+
"image/jpg",
|
|
698
|
+
"image/png",
|
|
699
|
+
"image/gif",
|
|
700
|
+
"image/webp",
|
|
701
|
+
"image/heic",
|
|
702
|
+
"image/heif",
|
|
703
|
+
];
|
|
704
|
+
const allowedVideoTypes = [
|
|
705
|
+
"video/mp4",
|
|
706
|
+
"video/webm",
|
|
707
|
+
"video/quicktime",
|
|
708
|
+
];
|
|
709
|
+
// Read file bytes first to detect MIME type if not provided
|
|
710
|
+
let fileBuffer;
|
|
711
|
+
try {
|
|
712
|
+
fileBuffer = await file.arrayBuffer();
|
|
713
|
+
}
|
|
714
|
+
catch (error) {
|
|
715
|
+
logger.error("Media upload failed: Error reading file", {
|
|
716
|
+
userId: session.userId,
|
|
717
|
+
fileName: file.name,
|
|
718
|
+
fileSize: file.size,
|
|
719
|
+
error: error.message,
|
|
720
|
+
});
|
|
721
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
722
|
+
error: "File read error",
|
|
723
|
+
message: "Failed to read file data",
|
|
724
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
725
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
726
|
+
}
|
|
727
|
+
if (fileBuffer.byteLength === 0) {
|
|
728
|
+
logger.warn("Media upload failed: File buffer is empty", {
|
|
729
|
+
userId: session.userId,
|
|
730
|
+
fileName: file.name,
|
|
731
|
+
fileSize: file.size,
|
|
732
|
+
});
|
|
733
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
734
|
+
error: "Empty file",
|
|
735
|
+
message: "File data is empty",
|
|
736
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
737
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
738
|
+
}
|
|
739
|
+
const bytes = new Uint8Array(fileBuffer);
|
|
740
|
+
// Always detect MIME type from magic numbers (trust the file, not the declared type)
|
|
741
|
+
// This handles cases where the frontend compresses/converts images
|
|
742
|
+
let detectedMimeType = "application/octet-stream";
|
|
743
|
+
if (bytes.length >= 3 &&
|
|
744
|
+
bytes[0] === 0xff &&
|
|
745
|
+
bytes[1] === 0xd8 &&
|
|
746
|
+
bytes[2] === 0xff) {
|
|
747
|
+
detectedMimeType = "image/jpeg";
|
|
748
|
+
}
|
|
749
|
+
else if (bytes.length >= 8 &&
|
|
750
|
+
bytes[0] === 0x89 &&
|
|
751
|
+
bytes[1] === 0x50 &&
|
|
752
|
+
bytes[2] === 0x4e &&
|
|
753
|
+
bytes[3] === 0x47) {
|
|
754
|
+
detectedMimeType = "image/png";
|
|
755
|
+
}
|
|
756
|
+
else if (bytes.length >= 4 &&
|
|
757
|
+
bytes[0] === 0x47 &&
|
|
758
|
+
bytes[1] === 0x49 &&
|
|
759
|
+
bytes[2] === 0x46 &&
|
|
760
|
+
bytes[3] === 0x38) {
|
|
761
|
+
detectedMimeType = "image/gif";
|
|
762
|
+
}
|
|
763
|
+
else if (bytes.length >= 12 &&
|
|
764
|
+
bytes[0] === 0x52 &&
|
|
765
|
+
bytes[1] === 0x49 &&
|
|
766
|
+
bytes[2] === 0x46 &&
|
|
767
|
+
bytes[3] === 0x46 &&
|
|
768
|
+
bytes[8] === 0x57 &&
|
|
769
|
+
bytes[9] === 0x45 &&
|
|
770
|
+
bytes[10] === 0x42 &&
|
|
771
|
+
bytes[11] === 0x50) {
|
|
772
|
+
detectedMimeType = "image/webp";
|
|
773
|
+
}
|
|
774
|
+
else if (bytes.length >= 8 &&
|
|
775
|
+
bytes[0] === 0x00 &&
|
|
776
|
+
bytes[1] === 0x00 &&
|
|
777
|
+
bytes[2] === 0x00 &&
|
|
778
|
+
bytes[4] === 0x66 &&
|
|
779
|
+
bytes[5] === 0x74 &&
|
|
780
|
+
bytes[6] === 0x79 &&
|
|
781
|
+
bytes[7] === 0x70) {
|
|
782
|
+
// HEIC/HEIF: ISO Base Media File Format with ftyp box
|
|
783
|
+
detectedMimeType = "image/heic";
|
|
784
|
+
}
|
|
785
|
+
// Use detected type if we found one, otherwise fall back to declared type
|
|
786
|
+
const declaredMimeType = file.type || "application/octet-stream";
|
|
787
|
+
let mimeType = detectedMimeType !== "application/octet-stream"
|
|
788
|
+
? detectedMimeType
|
|
789
|
+
: declaredMimeType;
|
|
790
|
+
// Log if declared type doesn't match detected type (frontend may have converted the image)
|
|
791
|
+
if (declaredMimeType !== "application/octet-stream" &&
|
|
792
|
+
detectedMimeType !== "application/octet-stream" &&
|
|
793
|
+
declaredMimeType !== detectedMimeType) {
|
|
794
|
+
logger.info("Media upload: Declared MIME type differs from detected type", {
|
|
795
|
+
userId: session.userId,
|
|
796
|
+
fileName: file.name,
|
|
797
|
+
declaredMimeType,
|
|
798
|
+
detectedMimeType,
|
|
799
|
+
usingDetectedType: true,
|
|
800
|
+
});
|
|
801
|
+
}
|
|
802
|
+
const isImage = allowedImageTypes.includes(mimeType);
|
|
803
|
+
const isVideo = allowedVideoTypes.includes(mimeType);
|
|
804
|
+
if (!isImage && !isVideo) {
|
|
805
|
+
logger.warn("Media upload failed: Invalid file type", {
|
|
806
|
+
userId: session.userId,
|
|
807
|
+
fileName: file.name,
|
|
808
|
+
declaredMimeType: file.type,
|
|
809
|
+
detectedMimeType: mimeType,
|
|
810
|
+
fileSize: file.size,
|
|
811
|
+
firstBytes: bytes.length > 0
|
|
812
|
+
? Array.from(bytes.slice(0, 8))
|
|
813
|
+
.map((b) => `0x${b.toString(16).padStart(2, "0")}`)
|
|
814
|
+
.join(" ")
|
|
815
|
+
: "empty",
|
|
816
|
+
});
|
|
817
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
818
|
+
error: "Invalid file type",
|
|
819
|
+
message: "Only JPEG, PNG, GIF, WebP, HEIC images and MP4, WebM, QuickTime videos are supported",
|
|
820
|
+
details: {
|
|
821
|
+
declaredType: file.type || "not provided",
|
|
822
|
+
detectedType: mimeType,
|
|
823
|
+
fileName: file.name,
|
|
824
|
+
},
|
|
825
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
826
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
827
|
+
}
|
|
828
|
+
// Validate file size (per documentation: 10MB for images, 100MB for videos)
|
|
829
|
+
const maxImageSize = 10 * 1024 * 1024; // 10MB
|
|
830
|
+
const maxVideoSize = 100 * 1024 * 1024; // 100MB
|
|
831
|
+
const maxSize = isImage ? maxImageSize : maxVideoSize;
|
|
832
|
+
if (file.size > maxSize) {
|
|
833
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
834
|
+
error: "File too large",
|
|
835
|
+
message: isImage
|
|
836
|
+
? "Image must be 10MB or less"
|
|
837
|
+
: "Video must be 100MB or less",
|
|
838
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
839
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
840
|
+
}
|
|
841
|
+
// File buffer already read above for MIME type detection
|
|
842
|
+
// Reuse the bytes array
|
|
843
|
+
// Validate magic numbers (file signatures)
|
|
844
|
+
// Since we always detect from magic numbers and use the detected type,
|
|
845
|
+
// we should always validate to ensure the file is what we think it is
|
|
846
|
+
if (mimeType !== "application/octet-stream" &&
|
|
847
|
+
!validateMagicNumbers(bytes, mimeType)) {
|
|
848
|
+
logger.warn("Media upload failed: Invalid file signature", {
|
|
849
|
+
userId: session.userId,
|
|
850
|
+
fileName: file.name,
|
|
851
|
+
declaredMimeType: declaredMimeType,
|
|
852
|
+
detectedMimeType: detectedMimeType,
|
|
853
|
+
usingMimeType: mimeType,
|
|
854
|
+
fileSize: file.size,
|
|
855
|
+
firstBytes: bytes.length > 0
|
|
856
|
+
? Array.from(bytes.slice(0, 12))
|
|
857
|
+
.map((b) => `0x${b.toString(16).padStart(2, "0")}`)
|
|
858
|
+
.join(" ")
|
|
859
|
+
: "empty",
|
|
860
|
+
});
|
|
861
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
862
|
+
error: "Invalid file signature",
|
|
863
|
+
message: "File signature does not match detected file type. File may be corrupted or malicious.",
|
|
864
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
865
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
866
|
+
}
|
|
867
|
+
// Check for suspicious patterns
|
|
868
|
+
const suspicious = checkSuspiciousContent(bytes, mimeType);
|
|
869
|
+
if (suspicious.length > 0) {
|
|
870
|
+
logger.warn("Suspicious file detected", {
|
|
871
|
+
userId: session.userId,
|
|
872
|
+
fileName: file.name,
|
|
873
|
+
mimeType,
|
|
874
|
+
suspicious,
|
|
875
|
+
});
|
|
876
|
+
// For now, log but don't reject (can be made stricter later)
|
|
877
|
+
// In production, you might want to reject or quarantine
|
|
878
|
+
}
|
|
879
|
+
// Get extension from MIME type
|
|
880
|
+
const ext = getExtensionFromMimeType(mimeType);
|
|
881
|
+
// Extract metadata (best effort, non-fatal)
|
|
882
|
+
let extracted = {};
|
|
883
|
+
try {
|
|
884
|
+
const { MetadataExtractor } = await Promise.resolve().then(() => __importStar(require("../metadata/metadata-extractor")));
|
|
885
|
+
const extractor = new MetadataExtractor(env);
|
|
886
|
+
extracted = await extractor.extractAll(fileBuffer, mimeType);
|
|
887
|
+
}
|
|
888
|
+
catch (metaError) {
|
|
889
|
+
logger.warn("[Media Upload] Metadata extraction failed", {
|
|
890
|
+
userId: session.userId,
|
|
891
|
+
mimeType,
|
|
892
|
+
errorType: metaError?.name,
|
|
893
|
+
code: metaError?.code,
|
|
894
|
+
error: metaError?.message,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
// Prepare metadata for upload service
|
|
898
|
+
const metadata = {
|
|
899
|
+
width: extracted?.exifData?.width || extracted?.videoMetadata?.width,
|
|
900
|
+
height: extracted?.exifData?.height || extracted?.videoMetadata?.height,
|
|
901
|
+
duration: extracted?.videoMetadata?.duration,
|
|
902
|
+
};
|
|
903
|
+
// Use MediaUploadService for eventual consistency upload
|
|
904
|
+
// Pass the already-read fileBuffer to avoid a second file.arrayBuffer() call.
|
|
905
|
+
// On Cloudflare Workers, File objects from FormData may not support
|
|
906
|
+
// reliable re-reads of arrayBuffer(), causing a different contentHash.
|
|
907
|
+
const uploadService = new media_upload_service_1.MediaUploadService(env);
|
|
908
|
+
const result = await uploadService.uploadSingle(file, session.userId, metadata, fileBuffer);
|
|
909
|
+
// Normalize images to sRGB (non-blocking, best effort)
|
|
910
|
+
let optimizedKey = null;
|
|
911
|
+
if (mimeType.startsWith("image/")) {
|
|
912
|
+
if (env.IMAGES && env.MEDIA_BUCKET_R2) {
|
|
913
|
+
const normalizer = new image_normalizer_1.ImageNormalizer(env.IMAGES, env.MEDIA_BUCKET_R2);
|
|
914
|
+
const startTime = Date.now();
|
|
915
|
+
logger.info("image_normalization.started", {
|
|
916
|
+
contentHash: result.contentHash,
|
|
917
|
+
mimeType,
|
|
918
|
+
});
|
|
919
|
+
optimizedKey = await normalizer.normalize(`media/${result.contentHash}.${getExtensionFromMimeType(mimeType)}`, result.contentHash);
|
|
920
|
+
const durationMs = Date.now() - startTime;
|
|
921
|
+
if (optimizedKey) {
|
|
922
|
+
logger.info("image_normalization.completed", {
|
|
923
|
+
contentHash: result.contentHash,
|
|
924
|
+
optimizedKey,
|
|
925
|
+
durationMs,
|
|
926
|
+
});
|
|
927
|
+
}
|
|
928
|
+
else {
|
|
929
|
+
logger.warn("image_normalization.failed", {
|
|
930
|
+
contentHash: result.contentHash,
|
|
931
|
+
durationMs,
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
else {
|
|
936
|
+
logger.info("image_normalization.skipped", {
|
|
937
|
+
contentHash: result.contentHash,
|
|
938
|
+
reason: "images_binding_not_available",
|
|
939
|
+
});
|
|
940
|
+
}
|
|
941
|
+
}
|
|
942
|
+
else {
|
|
943
|
+
logger.info("image_normalization.skipped", {
|
|
944
|
+
contentHash: result.contentHash,
|
|
945
|
+
reason: "not_image",
|
|
946
|
+
});
|
|
947
|
+
}
|
|
948
|
+
// Create MediaFile DB record synchronously so post creation can
|
|
949
|
+
// reference it immediately (reconciliation will enrich it later)
|
|
950
|
+
const uploadOriginalKey = `media/${result.contentHash}.${getExtensionFromMimeType(mimeType)}`;
|
|
951
|
+
const uploadRegion = "US"; // TODO: Get from session or request
|
|
952
|
+
try {
|
|
953
|
+
await (0, db_query_helper_1.withQueryTimeoutAndRetry)(database_connection_manager_1.sharedDatabaseConnectionManager, uploadRegion, env, async (db) => {
|
|
954
|
+
return await db.mediaFile.upsert({
|
|
955
|
+
where: { contentHash: result.contentHash },
|
|
956
|
+
create: {
|
|
957
|
+
contentHash: result.contentHash,
|
|
958
|
+
mimeType: mimeType,
|
|
959
|
+
size: file.size,
|
|
960
|
+
originalKey: uploadOriginalKey,
|
|
961
|
+
optimizedKey: optimizedKey ?? undefined,
|
|
962
|
+
uploadStatus: "COMPLETE",
|
|
963
|
+
uploadedBy: session.userId,
|
|
964
|
+
width: metadata?.width,
|
|
965
|
+
height: metadata?.height,
|
|
966
|
+
duration: metadata?.duration,
|
|
967
|
+
},
|
|
968
|
+
update: {
|
|
969
|
+
// If record already exists (e.g. re-upload), update status and ownership
|
|
970
|
+
// Clear deletedAt so previously-deleted media can be reused
|
|
971
|
+
uploadStatus: "COMPLETE",
|
|
972
|
+
uploadedBy: session.userId,
|
|
973
|
+
optimizedKey: optimizedKey ?? undefined,
|
|
974
|
+
deletedAt: null,
|
|
975
|
+
},
|
|
976
|
+
});
|
|
977
|
+
}, {
|
|
978
|
+
...db_query_helper_1.QueryTimeoutPresets.USER_FACING,
|
|
979
|
+
maxRetries: 1,
|
|
980
|
+
context: {
|
|
981
|
+
operation: "mediaUpload_createRecord",
|
|
982
|
+
userId: session.userId,
|
|
983
|
+
},
|
|
984
|
+
});
|
|
985
|
+
}
|
|
986
|
+
catch (dbError) {
|
|
987
|
+
// DB record is required for post creation to validate media ownership.
|
|
988
|
+
// If this fails, the upload must fail so the frontend can retry.
|
|
989
|
+
logger.error("[Media Upload] Synchronous DB record creation failed", {
|
|
990
|
+
contentHash: result.contentHash,
|
|
991
|
+
error: dbError.message,
|
|
992
|
+
});
|
|
993
|
+
const dbErrorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
994
|
+
error: "Database error",
|
|
995
|
+
message: "Failed to register uploaded media. Please try again.",
|
|
996
|
+
}), {
|
|
997
|
+
status: 500,
|
|
998
|
+
headers: { "content-type": "application/json" },
|
|
999
|
+
});
|
|
1000
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(dbErrorResponse, request, env);
|
|
1001
|
+
}
|
|
1002
|
+
logger.debug("[Media Upload] DB record created successfully", {
|
|
1003
|
+
contentHash: result.contentHash,
|
|
1004
|
+
uploadedBy: session.userId,
|
|
1005
|
+
originalKey: uploadOriginalKey,
|
|
1006
|
+
uploadRegion,
|
|
1007
|
+
});
|
|
1008
|
+
logger.info("Media upload successful", {
|
|
1009
|
+
userId: session.userId,
|
|
1010
|
+
fileName: file.name,
|
|
1011
|
+
fileSize: file.size,
|
|
1012
|
+
mimeType,
|
|
1013
|
+
contentHash: result.contentHash,
|
|
1014
|
+
status: result.status,
|
|
1015
|
+
});
|
|
1016
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1017
|
+
url: result.url,
|
|
1018
|
+
mediaKey: result.contentHash,
|
|
1019
|
+
contentHash: result.contentHash,
|
|
1020
|
+
status: result.status,
|
|
1021
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
1022
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1023
|
+
}
|
|
1024
|
+
catch (error) {
|
|
1025
|
+
logger.error("Error handling media upload:", error);
|
|
1026
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1027
|
+
error: "Failed to upload media",
|
|
1028
|
+
message: error.message || "An unexpected error occurred",
|
|
1029
|
+
}), { status: 500, headers: { "content-type": "application/json" } });
|
|
1030
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1031
|
+
}
|
|
1032
|
+
},
|
|
1033
|
+
middleware: [(0, middleware_1.corsMiddleware)(), (0, middleware_1.csrfMiddleware)()],
|
|
1034
|
+
description: "Upload media file (image or video) with content-addressed storage",
|
|
1035
|
+
},
|
|
1036
|
+
{
|
|
1037
|
+
path: "/api/media/upload/batch",
|
|
1038
|
+
method: "POST",
|
|
1039
|
+
handler: async (request, env) => {
|
|
1040
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1041
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1042
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1043
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
1044
|
+
// Check authentication
|
|
1045
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1046
|
+
if (!session) {
|
|
1047
|
+
logger.warn("[Media Batch Upload] Unauthorized");
|
|
1048
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1049
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1050
|
+
}
|
|
1051
|
+
// Apply rate limiting: 5 batch uploads per 60s per user (stricter than single)
|
|
1052
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media/upload/batch", 5, 60, session.userId);
|
|
1053
|
+
if (rateLimitResponse) {
|
|
1054
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
1055
|
+
}
|
|
1056
|
+
try {
|
|
1057
|
+
// Parse multipart form data
|
|
1058
|
+
const formData = await request.formData();
|
|
1059
|
+
const files = [];
|
|
1060
|
+
// Collect all files from form data
|
|
1061
|
+
for (const [key, value] of formData.entries()) {
|
|
1062
|
+
if (key.startsWith("files[") &&
|
|
1063
|
+
value &&
|
|
1064
|
+
typeof value === "object" &&
|
|
1065
|
+
"name" in value) {
|
|
1066
|
+
files.push(value);
|
|
1067
|
+
}
|
|
1068
|
+
else if (key === "file" &&
|
|
1069
|
+
value &&
|
|
1070
|
+
typeof value === "object" &&
|
|
1071
|
+
"name" in value) {
|
|
1072
|
+
// Also support single 'file' field for compatibility
|
|
1073
|
+
files.push(value);
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
if (files.length === 0) {
|
|
1077
|
+
logger.warn("[Media Batch Upload] No files provided", {
|
|
1078
|
+
userId: session.userId,
|
|
1079
|
+
formDataKeys: Array.from(formData.keys()),
|
|
1080
|
+
});
|
|
1081
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1082
|
+
error: "No files provided",
|
|
1083
|
+
message: "At least one file is required",
|
|
1084
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1085
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1086
|
+
}
|
|
1087
|
+
if (files.length > 20) {
|
|
1088
|
+
logger.warn("[Media Batch Upload] Too many files", {
|
|
1089
|
+
userId: session.userId,
|
|
1090
|
+
fileCount: files.length,
|
|
1091
|
+
});
|
|
1092
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1093
|
+
error: "Too many files",
|
|
1094
|
+
message: "Maximum 20 files per batch",
|
|
1095
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1096
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1097
|
+
}
|
|
1098
|
+
logger.info("[Media Batch Upload] Processing batch", {
|
|
1099
|
+
userId: session.userId,
|
|
1100
|
+
fileCount: files.length,
|
|
1101
|
+
});
|
|
1102
|
+
// Use MediaUploadService for batch upload
|
|
1103
|
+
const uploadService = new media_upload_service_1.MediaUploadService(env);
|
|
1104
|
+
const results = await uploadService.uploadBatch(files, session.userId);
|
|
1105
|
+
const successCount = results.filter((r) => r.success).length;
|
|
1106
|
+
const failureCount = results.filter((r) => !r.success).length;
|
|
1107
|
+
logger.info("[Media Batch Upload] Batch complete", {
|
|
1108
|
+
userId: session.userId,
|
|
1109
|
+
total: files.length,
|
|
1110
|
+
successful: successCount,
|
|
1111
|
+
failed: failureCount,
|
|
1112
|
+
});
|
|
1113
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1114
|
+
results,
|
|
1115
|
+
summary: {
|
|
1116
|
+
total: files.length,
|
|
1117
|
+
successful: successCount,
|
|
1118
|
+
failed: failureCount,
|
|
1119
|
+
},
|
|
1120
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
1121
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1122
|
+
}
|
|
1123
|
+
catch (error) {
|
|
1124
|
+
logger.error("[Media Batch Upload] Error:", error);
|
|
1125
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1126
|
+
error: "Failed to upload media batch",
|
|
1127
|
+
message: error.message || "An unexpected error occurred",
|
|
1128
|
+
}), { status: 500, headers: { "content-type": "application/json" } });
|
|
1129
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1130
|
+
}
|
|
1131
|
+
},
|
|
1132
|
+
middleware: [(0, middleware_1.corsMiddleware)(), (0, middleware_1.csrfMiddleware)()],
|
|
1133
|
+
description: "Upload multiple media files in a single batch request",
|
|
1134
|
+
},
|
|
1135
|
+
{
|
|
1136
|
+
path: "/api/media/grouped",
|
|
1137
|
+
method: "GET",
|
|
1138
|
+
handler: async (request, env) => {
|
|
1139
|
+
const startTime = Date.now();
|
|
1140
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1141
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1142
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1143
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
1144
|
+
const mediaHandler = media_handler_1.MediaHandler.create(env);
|
|
1145
|
+
const { MediaMetrics } = await Promise.resolve().then(() => __importStar(require("../media-metrics")));
|
|
1146
|
+
const { RegionDetector } = await Promise.resolve().then(() => __importStar(require("../region-detection")));
|
|
1147
|
+
const metrics = new MediaMetrics(env);
|
|
1148
|
+
// Check authentication
|
|
1149
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1150
|
+
if (!session) {
|
|
1151
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1152
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1153
|
+
}
|
|
1154
|
+
// Apply rate limiting: 60 requests per minute per user
|
|
1155
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media/grouped", 60, 60, session.userId);
|
|
1156
|
+
if (rateLimitResponse) {
|
|
1157
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
1158
|
+
}
|
|
1159
|
+
try {
|
|
1160
|
+
const url = new URL(request.url);
|
|
1161
|
+
const groupBy = (url.searchParams.get("groupBy") ||
|
|
1162
|
+
url.searchParams.get("groupby") ||
|
|
1163
|
+
"month");
|
|
1164
|
+
if (!["month", "year"].includes(groupBy)) {
|
|
1165
|
+
const duration = Date.now() - startTime;
|
|
1166
|
+
const regionDetector = new RegionDetector(env);
|
|
1167
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1168
|
+
metrics.trackOperation({
|
|
1169
|
+
operation: "grouped",
|
|
1170
|
+
endpoint: "/api/media/grouped",
|
|
1171
|
+
duration,
|
|
1172
|
+
statusCode: 400,
|
|
1173
|
+
errorType: "ValidationError",
|
|
1174
|
+
userId: session.userId,
|
|
1175
|
+
region,
|
|
1176
|
+
});
|
|
1177
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1178
|
+
error: "Invalid groupBy",
|
|
1179
|
+
message: 'groupBy must be "month" or "year"',
|
|
1180
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1181
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1182
|
+
}
|
|
1183
|
+
const includeHidden = url.searchParams.get("includeHidden") === "true";
|
|
1184
|
+
const type = url.searchParams.get("type") || "all";
|
|
1185
|
+
const limit = url.searchParams.get("limit")
|
|
1186
|
+
? parseInt(url.searchParams.get("limit"), 10)
|
|
1187
|
+
: undefined;
|
|
1188
|
+
// Validate limit if provided
|
|
1189
|
+
if (limit !== undefined && (limit < 1 || limit > 10000)) {
|
|
1190
|
+
const duration = Date.now() - startTime;
|
|
1191
|
+
const regionDetector = new RegionDetector(env);
|
|
1192
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1193
|
+
metrics.trackOperation({
|
|
1194
|
+
operation: "grouped",
|
|
1195
|
+
endpoint: "/api/media/grouped",
|
|
1196
|
+
duration,
|
|
1197
|
+
statusCode: 400,
|
|
1198
|
+
errorType: "ValidationError",
|
|
1199
|
+
userId: session.userId,
|
|
1200
|
+
region,
|
|
1201
|
+
});
|
|
1202
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1203
|
+
error: "Invalid limit",
|
|
1204
|
+
message: "Limit must be between 1 and 10000",
|
|
1205
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1206
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1207
|
+
}
|
|
1208
|
+
const result = await mediaHandler.listUserMediaGrouped(session.userId, groupBy, {
|
|
1209
|
+
includeHidden,
|
|
1210
|
+
type,
|
|
1211
|
+
limit,
|
|
1212
|
+
}, env, request);
|
|
1213
|
+
const duration = Date.now() - startTime;
|
|
1214
|
+
const regionDetector = new RegionDetector(env);
|
|
1215
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1216
|
+
// Calculate total items across all groups
|
|
1217
|
+
const totalItems = result.groups.reduce((sum, group) => sum + group.media.length, 0);
|
|
1218
|
+
// Track metrics
|
|
1219
|
+
metrics.trackGrouped("/api/media/grouped", duration, result.groups.length, totalItems, 200, region, session.userId);
|
|
1220
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify(result), { status: 200, headers: { "content-type": "application/json" } });
|
|
1221
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1222
|
+
}
|
|
1223
|
+
catch (error) {
|
|
1224
|
+
const duration = Date.now() - startTime;
|
|
1225
|
+
const regionDetector = new RegionDetector(env);
|
|
1226
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1227
|
+
const statusCode = error.message === "Media not found" ? 404 : 500;
|
|
1228
|
+
// Track error metrics
|
|
1229
|
+
metrics.trackOperation({
|
|
1230
|
+
operation: "grouped",
|
|
1231
|
+
endpoint: "/api/media/grouped",
|
|
1232
|
+
duration,
|
|
1233
|
+
statusCode,
|
|
1234
|
+
errorType: error?.name || "UnknownError",
|
|
1235
|
+
userId: session?.userId,
|
|
1236
|
+
region,
|
|
1237
|
+
});
|
|
1238
|
+
logger.error("Error grouping user media:", error);
|
|
1239
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1240
|
+
error: "Failed to group media",
|
|
1241
|
+
message: error.message || "An unexpected error occurred",
|
|
1242
|
+
}), {
|
|
1243
|
+
status: statusCode,
|
|
1244
|
+
headers: { "content-type": "application/json" },
|
|
1245
|
+
});
|
|
1246
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1247
|
+
}
|
|
1248
|
+
},
|
|
1249
|
+
middleware: [(0, middleware_1.corsMiddleware)()],
|
|
1250
|
+
description: "List user media grouped by month or year",
|
|
1251
|
+
},
|
|
1252
|
+
{
|
|
1253
|
+
path: "/api/media/stats",
|
|
1254
|
+
method: "GET",
|
|
1255
|
+
handler: async (request, env) => {
|
|
1256
|
+
const startTime = Date.now();
|
|
1257
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1258
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1259
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1260
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
1261
|
+
const mediaHandler = media_handler_1.MediaHandler.create(env);
|
|
1262
|
+
const { MediaMetrics } = await Promise.resolve().then(() => __importStar(require("../media-metrics")));
|
|
1263
|
+
const { RegionDetector } = await Promise.resolve().then(() => __importStar(require("../region-detection")));
|
|
1264
|
+
const metrics = new MediaMetrics(env);
|
|
1265
|
+
// Check authentication
|
|
1266
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1267
|
+
if (!session) {
|
|
1268
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1269
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1270
|
+
}
|
|
1271
|
+
// Apply rate limiting: 60 requests per minute per user
|
|
1272
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media/stats", 60, 60, session.userId);
|
|
1273
|
+
if (rateLimitResponse) {
|
|
1274
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
1275
|
+
}
|
|
1276
|
+
try {
|
|
1277
|
+
const url = new URL(request.url);
|
|
1278
|
+
const includeHidden = url.searchParams.get("includeHidden") === "true";
|
|
1279
|
+
const type = url.searchParams.get("type") || "all";
|
|
1280
|
+
const result = await mediaHandler.getUserMediaStats(session.userId, { includeHidden, type }, env, request);
|
|
1281
|
+
const duration = Date.now() - startTime;
|
|
1282
|
+
const regionDetector = new RegionDetector(env);
|
|
1283
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1284
|
+
// Track metrics
|
|
1285
|
+
metrics.trackStats("/api/media/stats", duration, result.totalCount, 200, region, session.userId);
|
|
1286
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify(result), { status: 200, headers: { "content-type": "application/json" } });
|
|
1287
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1288
|
+
}
|
|
1289
|
+
catch (error) {
|
|
1290
|
+
const duration = Date.now() - startTime;
|
|
1291
|
+
const regionDetector = new RegionDetector(env);
|
|
1292
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1293
|
+
const statusCode = error.message === "Media not found" ? 404 : 500;
|
|
1294
|
+
// Track error metrics
|
|
1295
|
+
metrics.trackOperation({
|
|
1296
|
+
operation: "stats",
|
|
1297
|
+
endpoint: "/api/media/stats",
|
|
1298
|
+
duration,
|
|
1299
|
+
statusCode,
|
|
1300
|
+
errorType: error?.name || "UnknownError",
|
|
1301
|
+
userId: session?.userId,
|
|
1302
|
+
region,
|
|
1303
|
+
});
|
|
1304
|
+
logger.error("Error getting media stats:", error);
|
|
1305
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1306
|
+
error: "Failed to get media stats",
|
|
1307
|
+
message: error.message || "An unexpected error occurred",
|
|
1308
|
+
}), {
|
|
1309
|
+
status: statusCode,
|
|
1310
|
+
headers: { "content-type": "application/json" },
|
|
1311
|
+
});
|
|
1312
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1313
|
+
}
|
|
1314
|
+
},
|
|
1315
|
+
middleware: [(0, middleware_1.corsMiddleware)()],
|
|
1316
|
+
description: "Get media statistics",
|
|
1317
|
+
},
|
|
1318
|
+
{
|
|
1319
|
+
path: "/api/media/:mediaId",
|
|
1320
|
+
method: "GET",
|
|
1321
|
+
handler: async (request, env, context) => {
|
|
1322
|
+
const startTime = Date.now();
|
|
1323
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1324
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1325
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1326
|
+
logger.debug("MEDIA ROUTE: /api/media/:mediaId GET handler called", {
|
|
1327
|
+
url: request.url,
|
|
1328
|
+
mediaId: context.params?.mediaId,
|
|
1329
|
+
});
|
|
1330
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
1331
|
+
const mediaHandler = media_handler_1.MediaHandler.create(env);
|
|
1332
|
+
const { MediaMetrics } = await Promise.resolve().then(() => __importStar(require("../media-metrics")));
|
|
1333
|
+
const { RegionDetector } = await Promise.resolve().then(() => __importStar(require("../region-detection")));
|
|
1334
|
+
const metrics = new MediaMetrics(env);
|
|
1335
|
+
// Check authentication
|
|
1336
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1337
|
+
if (!session) {
|
|
1338
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1339
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1340
|
+
}
|
|
1341
|
+
// Apply rate limiting: 120 requests per minute per user
|
|
1342
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media/:mediaId", 120, 60, session.userId);
|
|
1343
|
+
if (rateLimitResponse) {
|
|
1344
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
1345
|
+
}
|
|
1346
|
+
try {
|
|
1347
|
+
const mediaId = context.params?.mediaId;
|
|
1348
|
+
if (!mediaId) {
|
|
1349
|
+
const duration = Date.now() - startTime;
|
|
1350
|
+
const regionDetector = new RegionDetector(env);
|
|
1351
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1352
|
+
metrics.trackOperation({
|
|
1353
|
+
operation: "details",
|
|
1354
|
+
endpoint: "/api/media/:mediaId",
|
|
1355
|
+
duration,
|
|
1356
|
+
statusCode: 400,
|
|
1357
|
+
errorType: "ValidationError",
|
|
1358
|
+
userId: session.userId,
|
|
1359
|
+
region,
|
|
1360
|
+
});
|
|
1361
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1362
|
+
error: "Invalid request",
|
|
1363
|
+
message: "Media ID is required",
|
|
1364
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1365
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1366
|
+
}
|
|
1367
|
+
// Check if this is actually a content hash request (64-char hex)
|
|
1368
|
+
// Content hashes are hex strings and typically 64 characters
|
|
1369
|
+
// If it looks like a content hash, delegate to shared hash handler
|
|
1370
|
+
const url = new URL(request.url);
|
|
1371
|
+
const variant = url.searchParams.get("variant");
|
|
1372
|
+
logger.debug("MEDIA DEBUG: Checking content hash", {
|
|
1373
|
+
mediaId,
|
|
1374
|
+
mediaIdLength: mediaId.length,
|
|
1375
|
+
isHex: /^[0-9a-f]+$/i.test(mediaId),
|
|
1376
|
+
variant,
|
|
1377
|
+
url: request.url,
|
|
1378
|
+
});
|
|
1379
|
+
if (mediaId.length === 64 && /^[0-9a-f]+$/i.test(mediaId)) {
|
|
1380
|
+
logger.debug("MEDIA DEBUG: Detected content hash, delegating to serveMediaByHash", {
|
|
1381
|
+
mediaId,
|
|
1382
|
+
variant: variant || "original", // Changed default from 'optimized' to 'original'
|
|
1383
|
+
});
|
|
1384
|
+
// This is a content hash request - use shared hash serving function
|
|
1385
|
+
const duration = Date.now() - startTime;
|
|
1386
|
+
const regionDetector = new RegionDetector(env);
|
|
1387
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1388
|
+
metrics.trackOperation({
|
|
1389
|
+
operation: "details",
|
|
1390
|
+
endpoint: "/api/media/:mediaId",
|
|
1391
|
+
duration,
|
|
1392
|
+
statusCode: 200,
|
|
1393
|
+
errorType: undefined,
|
|
1394
|
+
userId: session.userId,
|
|
1395
|
+
region,
|
|
1396
|
+
});
|
|
1397
|
+
// CRITICAL FIX: Default to 'original' variant instead of 'optimized'
|
|
1398
|
+
// This ensures media is always accessible even if optimized versions don't exist yet
|
|
1399
|
+
return serveMediaByHash(mediaId, variant || "original", request, env, session);
|
|
1400
|
+
}
|
|
1401
|
+
logger.debug("MEDIA DEBUG: Not a content hash, proceeding with regular media details", {
|
|
1402
|
+
mediaId,
|
|
1403
|
+
});
|
|
1404
|
+
// Get media details
|
|
1405
|
+
const result = await mediaHandler.getMediaDetails(mediaId, session.userId, env, request);
|
|
1406
|
+
const duration = Date.now() - startTime;
|
|
1407
|
+
const regionDetector = new RegionDetector(env);
|
|
1408
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1409
|
+
// Track metrics
|
|
1410
|
+
metrics.trackMediaAction("details", "/api/media/:mediaId", duration, 200, mediaId, region, session.userId);
|
|
1411
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify(result), {
|
|
1412
|
+
status: 200,
|
|
1413
|
+
headers: {
|
|
1414
|
+
"content-type": "application/json",
|
|
1415
|
+
"cache-control": "private, max-age=300", // Cache for 5 minutes, private to this user
|
|
1416
|
+
pragma: "no-cache",
|
|
1417
|
+
expires: new Date(Date.now() + 5 * 60 * 1000).toUTCString(),
|
|
1418
|
+
},
|
|
1419
|
+
});
|
|
1420
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1421
|
+
}
|
|
1422
|
+
catch (error) {
|
|
1423
|
+
const duration = Date.now() - startTime;
|
|
1424
|
+
const regionDetector = new RegionDetector(env);
|
|
1425
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1426
|
+
let status = 500;
|
|
1427
|
+
let message = "Failed to get media details";
|
|
1428
|
+
let errorType = error?.name || "UnknownError";
|
|
1429
|
+
if (error.message === "Media not found") {
|
|
1430
|
+
status = 404;
|
|
1431
|
+
message = "Media not found";
|
|
1432
|
+
errorType = "NotFoundError";
|
|
1433
|
+
}
|
|
1434
|
+
else if (error.message.includes("permission")) {
|
|
1435
|
+
status = 403;
|
|
1436
|
+
message = "Forbidden";
|
|
1437
|
+
errorType = "ForbiddenError";
|
|
1438
|
+
}
|
|
1439
|
+
// Track error metrics
|
|
1440
|
+
metrics.trackMediaAction("details", "/api/media/:mediaId", duration, status, context.params?.mediaId || "unknown", region, session?.userId, errorType);
|
|
1441
|
+
logger.error("Error getting media details:", error);
|
|
1442
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1443
|
+
error: message,
|
|
1444
|
+
message: error.message || "An unexpected error occurred",
|
|
1445
|
+
source: "mediaId-route-catch",
|
|
1446
|
+
}), { status, headers: { "content-type": "application/json" } });
|
|
1447
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1448
|
+
}
|
|
1449
|
+
},
|
|
1450
|
+
middleware: [(0, middleware_1.corsMiddleware)()],
|
|
1451
|
+
description: "Get detailed information about a media file",
|
|
1452
|
+
},
|
|
1453
|
+
{
|
|
1454
|
+
path: "/api/media/:hash",
|
|
1455
|
+
method: "GET",
|
|
1456
|
+
handler: async (request, env, { params }) => {
|
|
1457
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1458
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1459
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1460
|
+
// Check authentication
|
|
1461
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1462
|
+
if (!session) {
|
|
1463
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1464
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1465
|
+
}
|
|
1466
|
+
try {
|
|
1467
|
+
const contentHash = params.hash;
|
|
1468
|
+
if (!contentHash) {
|
|
1469
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1470
|
+
error: "Missing content hash",
|
|
1471
|
+
source: "hash-route",
|
|
1472
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1473
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1474
|
+
}
|
|
1475
|
+
// Parse variant from query params (thumbnail, optimized, original)
|
|
1476
|
+
const url = new URL(request.url);
|
|
1477
|
+
const variant = url.searchParams.get("variant") || "original"; // Changed default from 'optimized' to 'original'
|
|
1478
|
+
logger.debug("HASH ROUTE: Serving media", {
|
|
1479
|
+
contentHash,
|
|
1480
|
+
variant,
|
|
1481
|
+
hasVariantParam: url.searchParams.has("variant"),
|
|
1482
|
+
});
|
|
1483
|
+
// Use shared function to serve media by hash
|
|
1484
|
+
return serveMediaByHash(contentHash, variant, request, env, session);
|
|
1485
|
+
}
|
|
1486
|
+
catch (error) {
|
|
1487
|
+
logger.error("Error serving media:", error);
|
|
1488
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1489
|
+
error: "Failed to serve media",
|
|
1490
|
+
message: error.message || "An unexpected error occurred",
|
|
1491
|
+
source: "hash-route-catch",
|
|
1492
|
+
}), { status: 500, headers: { "content-type": "application/json" } });
|
|
1493
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1494
|
+
}
|
|
1495
|
+
},
|
|
1496
|
+
middleware: [(0, middleware_1.corsMiddleware)()],
|
|
1497
|
+
description: "Serve media file by content hash with variant support",
|
|
1498
|
+
},
|
|
1499
|
+
{
|
|
1500
|
+
path: "/api/media",
|
|
1501
|
+
method: "GET",
|
|
1502
|
+
handler: async (request, env) => {
|
|
1503
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1504
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1505
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1506
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
1507
|
+
const mediaHandler = media_handler_1.MediaHandler.create(env);
|
|
1508
|
+
const { MediaMetrics } = await Promise.resolve().then(() => __importStar(require("../media-metrics")));
|
|
1509
|
+
const { RegionDetector } = await Promise.resolve().then(() => __importStar(require("../region-detection")));
|
|
1510
|
+
const metrics = new MediaMetrics(env);
|
|
1511
|
+
const startTime = Date.now();
|
|
1512
|
+
// Check authentication
|
|
1513
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1514
|
+
if (!session) {
|
|
1515
|
+
const duration = Date.now() - startTime;
|
|
1516
|
+
metrics.trackOperation({
|
|
1517
|
+
operation: "list",
|
|
1518
|
+
endpoint: "/api/media",
|
|
1519
|
+
duration,
|
|
1520
|
+
statusCode: 401,
|
|
1521
|
+
errorType: "unauthorized",
|
|
1522
|
+
});
|
|
1523
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1524
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1525
|
+
}
|
|
1526
|
+
// Apply rate limiting: 60 requests per minute per user
|
|
1527
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media", 60, 60, session.userId);
|
|
1528
|
+
if (rateLimitResponse) {
|
|
1529
|
+
const duration = Date.now() - startTime;
|
|
1530
|
+
metrics.trackRateLimit("/api/media", session.userId, undefined, 60, 60);
|
|
1531
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
1532
|
+
}
|
|
1533
|
+
try {
|
|
1534
|
+
// Parse query parameters
|
|
1535
|
+
const url = new URL(request.url);
|
|
1536
|
+
const limit = url.searchParams.get("limit")
|
|
1537
|
+
? parseInt(url.searchParams.get("limit"), 10)
|
|
1538
|
+
: 50;
|
|
1539
|
+
const cursor = url.searchParams.get("cursor") || undefined;
|
|
1540
|
+
const sort = url.searchParams.get("sort") || "newest";
|
|
1541
|
+
const includeHidden = url.searchParams.get("includeHidden") === "true";
|
|
1542
|
+
const type = url.searchParams.get("type") || "all";
|
|
1543
|
+
const includeTotalCount = url.searchParams.get("includeTotalCount") === "true";
|
|
1544
|
+
// Validate limit
|
|
1545
|
+
if (limit < 1 || limit > 100) {
|
|
1546
|
+
const duration = Date.now() - startTime;
|
|
1547
|
+
metrics.trackOperation({
|
|
1548
|
+
operation: "list",
|
|
1549
|
+
endpoint: "/api/media",
|
|
1550
|
+
duration,
|
|
1551
|
+
statusCode: 400,
|
|
1552
|
+
errorType: "invalid_request",
|
|
1553
|
+
userId: session.userId,
|
|
1554
|
+
});
|
|
1555
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1556
|
+
error: "Invalid limit",
|
|
1557
|
+
message: "Limit must be between 1 and 100",
|
|
1558
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1559
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1560
|
+
}
|
|
1561
|
+
// Get user media
|
|
1562
|
+
const result = await mediaHandler.listUserMedia(session.userId, {
|
|
1563
|
+
limit,
|
|
1564
|
+
cursor,
|
|
1565
|
+
sort,
|
|
1566
|
+
includeHidden,
|
|
1567
|
+
type,
|
|
1568
|
+
includeTotalCount,
|
|
1569
|
+
}, env, request);
|
|
1570
|
+
const duration = Date.now() - startTime;
|
|
1571
|
+
const regionDetector = new RegionDetector(env);
|
|
1572
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1573
|
+
// Track metrics
|
|
1574
|
+
metrics.trackList("/api/media", duration, result.media.length, 200, region, session.userId);
|
|
1575
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify(result), { status: 200, headers: { "content-type": "application/json" } });
|
|
1576
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1577
|
+
}
|
|
1578
|
+
catch (error) {
|
|
1579
|
+
const duration = Date.now() - startTime;
|
|
1580
|
+
const regionDetector = new RegionDetector(env);
|
|
1581
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1582
|
+
const statusCode = error.message === "Media not found" ? 404 : 500;
|
|
1583
|
+
// Track error metrics
|
|
1584
|
+
metrics.trackOperation({
|
|
1585
|
+
operation: "list",
|
|
1586
|
+
endpoint: "/api/media",
|
|
1587
|
+
duration,
|
|
1588
|
+
statusCode,
|
|
1589
|
+
errorType: error?.name || "UnknownError",
|
|
1590
|
+
userId: session?.userId,
|
|
1591
|
+
region,
|
|
1592
|
+
});
|
|
1593
|
+
logger.error("Error listing user media:", error);
|
|
1594
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1595
|
+
error: "Failed to list media",
|
|
1596
|
+
message: error.message || "An unexpected error occurred",
|
|
1597
|
+
}), {
|
|
1598
|
+
status: statusCode,
|
|
1599
|
+
headers: { "content-type": "application/json" },
|
|
1600
|
+
});
|
|
1601
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1602
|
+
}
|
|
1603
|
+
},
|
|
1604
|
+
middleware: [(0, middleware_1.corsMiddleware)()],
|
|
1605
|
+
description: "List user media with pagination, sorting, and filtering",
|
|
1606
|
+
},
|
|
1607
|
+
{
|
|
1608
|
+
path: "/api/media/:mediaId/hide",
|
|
1609
|
+
method: "POST",
|
|
1610
|
+
handler: async (request, env, context) => {
|
|
1611
|
+
const startTime = Date.now();
|
|
1612
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1613
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1614
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1615
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
1616
|
+
const mediaHandler = media_handler_1.MediaHandler.create(env);
|
|
1617
|
+
const { MediaMetrics } = await Promise.resolve().then(() => __importStar(require("../media-metrics")));
|
|
1618
|
+
const { RegionDetector } = await Promise.resolve().then(() => __importStar(require("../region-detection")));
|
|
1619
|
+
const metrics = new MediaMetrics(env);
|
|
1620
|
+
// Check authentication
|
|
1621
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1622
|
+
if (!session) {
|
|
1623
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1624
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1625
|
+
}
|
|
1626
|
+
// Apply rate limiting: 10 requests per minute per user
|
|
1627
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media/:mediaId/hide", 10, 60, session.userId);
|
|
1628
|
+
if (rateLimitResponse) {
|
|
1629
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
1630
|
+
}
|
|
1631
|
+
try {
|
|
1632
|
+
const mediaId = context.params?.mediaId;
|
|
1633
|
+
if (!mediaId) {
|
|
1634
|
+
const duration = Date.now() - startTime;
|
|
1635
|
+
const regionDetector = new RegionDetector(env);
|
|
1636
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1637
|
+
metrics.trackOperation({
|
|
1638
|
+
operation: "hide",
|
|
1639
|
+
endpoint: "/api/media/:mediaId/hide",
|
|
1640
|
+
duration,
|
|
1641
|
+
statusCode: 400,
|
|
1642
|
+
errorType: "ValidationError",
|
|
1643
|
+
userId: session.userId,
|
|
1644
|
+
region,
|
|
1645
|
+
});
|
|
1646
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1647
|
+
error: "Invalid request",
|
|
1648
|
+
message: "Media ID is required",
|
|
1649
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1650
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1651
|
+
}
|
|
1652
|
+
// Hide media
|
|
1653
|
+
const result = await mediaHandler.hideMedia(mediaId, session.userId, env, request);
|
|
1654
|
+
const duration = Date.now() - startTime;
|
|
1655
|
+
const regionDetector = new RegionDetector(env);
|
|
1656
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1657
|
+
// Track metrics
|
|
1658
|
+
metrics.trackMediaAction("hide", "/api/media/:mediaId/hide", duration, 200, mediaId, region, session.userId);
|
|
1659
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1660
|
+
success: true,
|
|
1661
|
+
media: result,
|
|
1662
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
1663
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1664
|
+
}
|
|
1665
|
+
catch (error) {
|
|
1666
|
+
const duration = Date.now() - startTime;
|
|
1667
|
+
const regionDetector = new RegionDetector(env);
|
|
1668
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1669
|
+
let status = 500;
|
|
1670
|
+
let message = "Failed to hide media";
|
|
1671
|
+
let errorType = error?.name || "UnknownError";
|
|
1672
|
+
if (error.message === "Media not found") {
|
|
1673
|
+
status = 404;
|
|
1674
|
+
message = "Media not found";
|
|
1675
|
+
errorType = "NotFoundError";
|
|
1676
|
+
}
|
|
1677
|
+
else if (error.message.includes("permission")) {
|
|
1678
|
+
status = 403;
|
|
1679
|
+
message = "Forbidden";
|
|
1680
|
+
errorType = "ForbiddenError";
|
|
1681
|
+
}
|
|
1682
|
+
else if (error.message.includes("already hidden") ||
|
|
1683
|
+
error.message.includes("deleted")) {
|
|
1684
|
+
status = 400;
|
|
1685
|
+
message = error.message;
|
|
1686
|
+
errorType = "ValidationError";
|
|
1687
|
+
}
|
|
1688
|
+
// Track error metrics
|
|
1689
|
+
metrics.trackMediaAction("hide", "/api/media/:mediaId/hide", duration, status, context.params?.mediaId || "unknown", region, session?.userId, errorType);
|
|
1690
|
+
logger.error("Error hiding media:", error);
|
|
1691
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1692
|
+
error: message,
|
|
1693
|
+
message: error.message || "An unexpected error occurred",
|
|
1694
|
+
}), { status, headers: { "content-type": "application/json" } });
|
|
1695
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1696
|
+
}
|
|
1697
|
+
},
|
|
1698
|
+
middleware: [(0, middleware_1.corsMiddleware)(), (0, middleware_1.csrfMiddleware)()],
|
|
1699
|
+
description: "Hide a media file from all posts",
|
|
1700
|
+
},
|
|
1701
|
+
{
|
|
1702
|
+
path: "/api/media/:mediaId/unhide",
|
|
1703
|
+
method: "POST",
|
|
1704
|
+
handler: async (request, env, context) => {
|
|
1705
|
+
const startTime = Date.now();
|
|
1706
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1707
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1708
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1709
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
1710
|
+
const mediaHandler = media_handler_1.MediaHandler.create(env);
|
|
1711
|
+
const { MediaMetrics } = await Promise.resolve().then(() => __importStar(require("../media-metrics")));
|
|
1712
|
+
const { RegionDetector } = await Promise.resolve().then(() => __importStar(require("../region-detection")));
|
|
1713
|
+
const metrics = new MediaMetrics(env);
|
|
1714
|
+
// Check authentication
|
|
1715
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1716
|
+
if (!session) {
|
|
1717
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1718
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1719
|
+
}
|
|
1720
|
+
// Apply rate limiting: 10 requests per minute per user
|
|
1721
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media/:mediaId/unhide", 10, 60, session.userId);
|
|
1722
|
+
if (rateLimitResponse) {
|
|
1723
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
1724
|
+
}
|
|
1725
|
+
try {
|
|
1726
|
+
const mediaId = context.params?.mediaId;
|
|
1727
|
+
if (!mediaId) {
|
|
1728
|
+
const duration = Date.now() - startTime;
|
|
1729
|
+
const regionDetector = new RegionDetector(env);
|
|
1730
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1731
|
+
metrics.trackOperation({
|
|
1732
|
+
operation: "unhide",
|
|
1733
|
+
endpoint: "/api/media/:mediaId/unhide",
|
|
1734
|
+
duration,
|
|
1735
|
+
statusCode: 400,
|
|
1736
|
+
errorType: "ValidationError",
|
|
1737
|
+
userId: session.userId,
|
|
1738
|
+
region,
|
|
1739
|
+
});
|
|
1740
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1741
|
+
error: "Invalid request",
|
|
1742
|
+
message: "Media ID is required",
|
|
1743
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1744
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1745
|
+
}
|
|
1746
|
+
// Unhide media
|
|
1747
|
+
const result = await mediaHandler.unhideMedia(mediaId, session.userId, env, request);
|
|
1748
|
+
const duration = Date.now() - startTime;
|
|
1749
|
+
const regionDetector = new RegionDetector(env);
|
|
1750
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1751
|
+
// Track metrics
|
|
1752
|
+
metrics.trackMediaAction("unhide", "/api/media/:mediaId/unhide", duration, 200, mediaId, region, session.userId);
|
|
1753
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1754
|
+
success: true,
|
|
1755
|
+
media: result,
|
|
1756
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
1757
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1758
|
+
}
|
|
1759
|
+
catch (error) {
|
|
1760
|
+
const duration = Date.now() - startTime;
|
|
1761
|
+
const regionDetector = new RegionDetector(env);
|
|
1762
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1763
|
+
let status = 500;
|
|
1764
|
+
let message = "Failed to unhide media";
|
|
1765
|
+
let errorType = error?.name || "UnknownError";
|
|
1766
|
+
if (error.message === "Media not found") {
|
|
1767
|
+
status = 404;
|
|
1768
|
+
message = "Media not found";
|
|
1769
|
+
errorType = "NotFoundError";
|
|
1770
|
+
}
|
|
1771
|
+
else if (error.message.includes("permission")) {
|
|
1772
|
+
status = 403;
|
|
1773
|
+
message = "Forbidden";
|
|
1774
|
+
errorType = "ForbiddenError";
|
|
1775
|
+
}
|
|
1776
|
+
else if (error.message.includes("not hidden") ||
|
|
1777
|
+
error.message.includes("deleted")) {
|
|
1778
|
+
status = 400;
|
|
1779
|
+
message = error.message;
|
|
1780
|
+
errorType = "ValidationError";
|
|
1781
|
+
}
|
|
1782
|
+
// Track error metrics
|
|
1783
|
+
metrics.trackMediaAction("unhide", "/api/media/:mediaId/unhide", duration, status, context.params?.mediaId || "unknown", region, session?.userId, errorType);
|
|
1784
|
+
logger.error("Error unhiding media:", error);
|
|
1785
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1786
|
+
error: message,
|
|
1787
|
+
message: error.message || "An unexpected error occurred",
|
|
1788
|
+
}), { status, headers: { "content-type": "application/json" } });
|
|
1789
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1790
|
+
}
|
|
1791
|
+
},
|
|
1792
|
+
middleware: [(0, middleware_1.corsMiddleware)(), (0, middleware_1.csrfMiddleware)()],
|
|
1793
|
+
description: "Unhide a media file (make it visible again)",
|
|
1794
|
+
},
|
|
1795
|
+
{
|
|
1796
|
+
path: "/api/media/:mediaId",
|
|
1797
|
+
method: "DELETE",
|
|
1798
|
+
handler: async (request, env, context) => {
|
|
1799
|
+
const startTime = Date.now();
|
|
1800
|
+
const sessionManager = new session_manager_1.SessionManager();
|
|
1801
|
+
const securityHeaders = new security_headers_1.SecurityHeaders(env);
|
|
1802
|
+
const logger = logger_1.Logger.getInstance(env);
|
|
1803
|
+
const rateLimiter = new rate_limit_1.RateLimiter();
|
|
1804
|
+
const mediaHandler = media_handler_1.MediaHandler.create(env);
|
|
1805
|
+
const { MediaMetrics } = await Promise.resolve().then(() => __importStar(require("../media-metrics")));
|
|
1806
|
+
const { RegionDetector } = await Promise.resolve().then(() => __importStar(require("../region-detection")));
|
|
1807
|
+
const metrics = new MediaMetrics(env);
|
|
1808
|
+
// Check authentication
|
|
1809
|
+
const session = await sessionManager.getSession(request, secret_resolver_1.Secrets.getSessionSecret(env), env);
|
|
1810
|
+
if (!session) {
|
|
1811
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({ error: "Unauthorized" }), { status: 401, headers: { "content-type": "application/json" } });
|
|
1812
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1813
|
+
}
|
|
1814
|
+
// Apply rate limiting: 10 requests per minute per user
|
|
1815
|
+
const rateLimitResponse = await rateLimiter.applyRateLimitKV(env, request, "/api/media/:mediaId", 10, 60, session.userId);
|
|
1816
|
+
if (rateLimitResponse) {
|
|
1817
|
+
return securityHeaders.addSecurityHeaders(rateLimitResponse);
|
|
1818
|
+
}
|
|
1819
|
+
try {
|
|
1820
|
+
const mediaId = context.params?.mediaId;
|
|
1821
|
+
if (!mediaId) {
|
|
1822
|
+
const duration = Date.now() - startTime;
|
|
1823
|
+
const regionDetector = new RegionDetector(env);
|
|
1824
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1825
|
+
metrics.trackOperation({
|
|
1826
|
+
operation: "delete",
|
|
1827
|
+
endpoint: "/api/media/:mediaId",
|
|
1828
|
+
duration,
|
|
1829
|
+
statusCode: 400,
|
|
1830
|
+
errorType: "ValidationError",
|
|
1831
|
+
userId: session.userId,
|
|
1832
|
+
region,
|
|
1833
|
+
});
|
|
1834
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1835
|
+
error: "Invalid request",
|
|
1836
|
+
message: "Media ID is required",
|
|
1837
|
+
}), { status: 400, headers: { "content-type": "application/json" } });
|
|
1838
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1839
|
+
}
|
|
1840
|
+
// Delete media
|
|
1841
|
+
try {
|
|
1842
|
+
await mediaHandler.deleteMedia(mediaId, session.userId, env, request);
|
|
1843
|
+
const duration = Date.now() - startTime;
|
|
1844
|
+
const regionDetector = new RegionDetector(env);
|
|
1845
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1846
|
+
// Track metrics
|
|
1847
|
+
metrics.trackMediaAction("delete", "/api/media/:mediaId", duration, 200, mediaId, region, session.userId);
|
|
1848
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1849
|
+
success: true,
|
|
1850
|
+
message: "Media deleted successfully",
|
|
1851
|
+
}), { status: 200, headers: { "content-type": "application/json" } });
|
|
1852
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1853
|
+
}
|
|
1854
|
+
catch (deleteError) {
|
|
1855
|
+
const duration = Date.now() - startTime;
|
|
1856
|
+
const regionDetector = new RegionDetector(env);
|
|
1857
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1858
|
+
// Check if it's a "shared media" error (should hide instead)
|
|
1859
|
+
if (deleteError.message.includes("used by other users") ||
|
|
1860
|
+
deleteError.message.includes("hidden instead")) {
|
|
1861
|
+
// Track as conflict (media was hidden instead)
|
|
1862
|
+
metrics.trackMediaAction("delete", "/api/media/:mediaId", duration, 409, mediaId, region, session.userId, "ConflictError");
|
|
1863
|
+
const response = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1864
|
+
success: false,
|
|
1865
|
+
error: "Media is shared with other users",
|
|
1866
|
+
message: deleteError.message,
|
|
1867
|
+
action: "hidden", // Media was hidden instead of deleted
|
|
1868
|
+
}), { status: 409, headers: { "content-type": "application/json" } });
|
|
1869
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(response, request, env);
|
|
1870
|
+
}
|
|
1871
|
+
throw deleteError;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
catch (error) {
|
|
1875
|
+
const duration = Date.now() - startTime;
|
|
1876
|
+
const regionDetector = new RegionDetector(env);
|
|
1877
|
+
const region = await regionDetector.detectRegion(request, undefined, undefined);
|
|
1878
|
+
let status = 500;
|
|
1879
|
+
let message = "Failed to delete media";
|
|
1880
|
+
let errorType = error?.name || "UnknownError";
|
|
1881
|
+
if (error.message === "Media not found") {
|
|
1882
|
+
status = 404;
|
|
1883
|
+
message = "Media not found";
|
|
1884
|
+
errorType = "NotFoundError";
|
|
1885
|
+
}
|
|
1886
|
+
else if (error.message.includes("permission")) {
|
|
1887
|
+
status = 403;
|
|
1888
|
+
message = "Forbidden";
|
|
1889
|
+
errorType = "ForbiddenError";
|
|
1890
|
+
}
|
|
1891
|
+
else if (error.message.includes("already deleted")) {
|
|
1892
|
+
status = 400;
|
|
1893
|
+
message = error.message;
|
|
1894
|
+
errorType = "ValidationError";
|
|
1895
|
+
}
|
|
1896
|
+
// Track error metrics
|
|
1897
|
+
metrics.trackMediaAction("delete", "/api/media/:mediaId", duration, status, context.params?.mediaId || "unknown", region, session?.userId, errorType);
|
|
1898
|
+
logger.error("Error deleting media:", error);
|
|
1899
|
+
const errorResponse = securityHeaders.createSecureResponse(JSON.stringify({
|
|
1900
|
+
error: message,
|
|
1901
|
+
message: error.message || "An unexpected error occurred",
|
|
1902
|
+
}), { status, headers: { "content-type": "application/json" } });
|
|
1903
|
+
return cors_handler_1.CorsHandler.addCorsHeaders(errorResponse, request, env);
|
|
1904
|
+
}
|
|
1905
|
+
},
|
|
1906
|
+
middleware: [(0, middleware_1.corsMiddleware)(), (0, middleware_1.csrfMiddleware)()],
|
|
1907
|
+
description: "Delete a media file (soft delete)",
|
|
1908
|
+
},
|
|
1909
|
+
];
|
|
1910
|
+
//# sourceMappingURL=media.js.map
|