cursedbelt-server 1.0.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/LICENSE +15 -0
- package/dist/server/activity/index.d.ts +36 -0
- package/dist/server/activity/index.js +36 -0
- package/dist/server/activity/migrations.d.ts +21 -0
- package/dist/server/activity/migrations.js +55 -0
- package/dist/server/activity/shareSource.d.ts +11 -0
- package/dist/server/activity/shareSource.js +45 -0
- package/dist/server/activity/store.d.ts +123 -0
- package/dist/server/activity/store.js +178 -0
- package/dist/server/app.d.ts +56 -0
- package/dist/server/app.js +45 -0
- package/dist/server/audit/index.d.ts +2 -0
- package/dist/server/audit/index.js +2 -0
- package/dist/server/audit/migrations.d.ts +2 -0
- package/dist/server/audit/migrations.js +79 -0
- package/dist/server/audit/sqliteSink.d.ts +36 -0
- package/dist/server/audit/sqliteSink.js +95 -0
- package/dist/server/audit/sqliteSink.test.d.ts +1 -0
- package/dist/server/audit/sqliteSink.test.js +180 -0
- package/dist/server/auth/cookie.d.ts +22 -0
- package/dist/server/auth/cookie.js +35 -0
- package/dist/server/auth/csrf.d.ts +42 -0
- package/dist/server/auth/csrf.js +36 -0
- package/dist/server/auth/fileTokenSigner.d.ts +10 -0
- package/dist/server/auth/fileTokenSigner.js +22 -0
- package/dist/server/auth/getOnly.d.ts +20 -0
- package/dist/server/auth/getOnly.js +27 -0
- package/dist/server/auth/index.d.ts +12 -0
- package/dist/server/auth/index.js +15 -0
- package/dist/server/auth/jwt.d.ts +18 -0
- package/dist/server/auth/jwt.js +56 -0
- package/dist/server/auth/loginThrottle.d.ts +118 -0
- package/dist/server/auth/loginThrottle.js +204 -0
- package/dist/server/auth/middleware.d.ts +14 -0
- package/dist/server/auth/middleware.js +47 -0
- package/dist/server/auth/password.d.ts +14 -0
- package/dist/server/auth/password.js +65 -0
- package/dist/server/auth/sessionStore.d.ts +19 -0
- package/dist/server/auth/sessionStore.js +221 -0
- package/dist/server/auth/totp.d.ts +34 -0
- package/dist/server/auth/totp.js +81 -0
- package/dist/server/auth/types.d.ts +133 -0
- package/dist/server/auth/types.js +1 -0
- package/dist/server/auth/vault.d.ts +27 -0
- package/dist/server/auth/vault.js +57 -0
- package/dist/server/context.d.ts +35 -0
- package/dist/server/context.js +1 -0
- package/dist/server/ctgr.d.ts +32 -0
- package/dist/server/ctgr.js +69 -0
- package/dist/server/db/kysely.d.ts +14 -0
- package/dist/server/db/kysely.js +16 -0
- package/dist/server/db/plumbingTypes.d.ts +116 -0
- package/dist/server/db/plumbingTypes.js +12 -0
- package/dist/server/dropzone/duplicates.d.ts +82 -0
- package/dist/server/dropzone/duplicates.js +90 -0
- package/dist/server/dropzone/index.d.ts +13 -0
- package/dist/server/dropzone/index.js +13 -0
- package/dist/server/dropzone/scan.d.ts +166 -0
- package/dist/server/dropzone/scan.js +272 -0
- package/dist/server/errors.d.ts +7 -0
- package/dist/server/errors.js +7 -0
- package/dist/server/guard/argon2Binding.d.ts +97 -0
- package/dist/server/guard/argon2Binding.js +138 -0
- package/dist/server/guard/index.d.ts +4 -0
- package/dist/server/guard/index.js +6 -0
- package/dist/server/guard/kdfWorker.d.ts +101 -0
- package/dist/server/guard/kdfWorker.js +301 -0
- package/dist/server/guard/kdfWorkerEntry.d.ts +1 -0
- package/dist/server/guard/kdfWorkerEntry.js +15 -0
- package/dist/server/guard/passwordVerifier.d.ts +65 -0
- package/dist/server/guard/passwordVerifier.js +122 -0
- package/dist/server/guard/revocationStore.d.ts +132 -0
- package/dist/server/guard/revocationStore.js +211 -0
- package/dist/server/guard/sessionSigner.d.ts +132 -0
- package/dist/server/guard/sessionSigner.js +303 -0
- package/dist/server/guard/verifier.d.ts +21 -0
- package/dist/server/guard/verifier.js +1 -0
- package/dist/server/incidents/channels.d.ts +161 -0
- package/dist/server/incidents/channels.js +357 -0
- package/dist/server/incidents/checks.d.ts +61 -0
- package/dist/server/incidents/checks.js +55 -0
- package/dist/server/incidents/index.d.ts +51 -0
- package/dist/server/incidents/index.js +51 -0
- package/dist/server/incidents/migrations.d.ts +20 -0
- package/dist/server/incidents/migrations.js +58 -0
- package/dist/server/incidents/notifier.d.ts +79 -0
- package/dist/server/incidents/notifier.js +141 -0
- package/dist/server/incidents/reporter.d.ts +60 -0
- package/dist/server/incidents/reporter.js +49 -0
- package/dist/server/incidents/routes.d.ts +91 -0
- package/dist/server/incidents/routes.js +202 -0
- package/dist/server/incidents/store.d.ts +75 -0
- package/dist/server/incidents/store.js +176 -0
- package/dist/server/incidents/types.d.ts +107 -0
- package/dist/server/incidents/types.js +32 -0
- package/dist/server/index.d.ts +25 -0
- package/dist/server/index.js +53 -0
- package/dist/server/jobs/fireQueue.d.ts +12 -0
- package/dist/server/jobs/fireQueue.js +37 -0
- package/dist/server/jobs/index.d.ts +6 -0
- package/dist/server/jobs/index.js +10 -0
- package/dist/server/jobs/logger.d.ts +10 -0
- package/dist/server/jobs/logger.js +14 -0
- package/dist/server/jobs/plainjobAdapter.d.ts +19 -0
- package/dist/server/jobs/plainjobAdapter.js +48 -0
- package/dist/server/jobs/polling.d.ts +17 -0
- package/dist/server/jobs/polling.js +79 -0
- package/dist/server/jobs/runner.d.ts +24 -0
- package/dist/server/jobs/runner.js +186 -0
- package/dist/server/jobs/timeout.d.ts +28 -0
- package/dist/server/jobs/timeout.js +47 -0
- package/dist/server/jobs/types.d.ts +167 -0
- package/dist/server/jobs/types.js +12 -0
- package/dist/server/master-lock/guard.d.ts +53 -0
- package/dist/server/master-lock/guard.js +205 -0
- package/dist/server/master-lock/index.d.ts +21 -0
- package/dist/server/master-lock/index.js +21 -0
- package/dist/server/master-lock/lockPage.d.ts +34 -0
- package/dist/server/master-lock/lockPage.js +281 -0
- package/dist/server/master-lock/masterLock.d.ts +294 -0
- package/dist/server/master-lock/masterLock.js +523 -0
- package/dist/server/master-lock/principals.d.ts +76 -0
- package/dist/server/master-lock/principals.js +157 -0
- package/dist/server/master-lock/seed.d.ts +47 -0
- package/dist/server/master-lock/seed.js +93 -0
- package/dist/server/master-lock/store.d.ts +19 -0
- package/dist/server/master-lock/store.js +20 -0
- package/dist/server/media-bun/image.d.ts +7 -0
- package/dist/server/media-bun/image.js +96 -0
- package/dist/server/media-bun/index.d.ts +4 -0
- package/dist/server/media-bun/index.js +10 -0
- package/dist/server/media-bun/streaming.d.ts +86 -0
- package/dist/server/media-bun/streaming.js +237 -0
- package/dist/server/media-bun/video.d.ts +35 -0
- package/dist/server/media-bun/video.js +181 -0
- package/dist/server/metrics/metricsBuffer.d.ts +39 -0
- package/dist/server/metrics/metricsBuffer.js +36 -0
- package/dist/server/metrics/normalizeRoute.d.ts +5 -0
- package/dist/server/metrics/normalizeRoute.js +58 -0
- package/dist/server/middleware/correlationId.d.ts +9 -0
- package/dist/server/middleware/correlationId.js +20 -0
- package/dist/server/middleware/corsAllowlist.d.ts +38 -0
- package/dist/server/middleware/corsAllowlist.js +37 -0
- package/dist/server/middleware/errorEnvelope.d.ts +17 -0
- package/dist/server/middleware/errorEnvelope.js +32 -0
- package/dist/server/middleware/index.d.ts +9 -0
- package/dist/server/middleware/index.js +10 -0
- package/dist/server/middleware/localOrigin.d.ts +71 -0
- package/dist/server/middleware/localOrigin.js +119 -0
- package/dist/server/middleware/rateLimit.d.ts +56 -0
- package/dist/server/middleware/rateLimit.js +86 -0
- package/dist/server/middleware/requestLogger.d.ts +29 -0
- package/dist/server/middleware/requestLogger.js +47 -0
- package/dist/server/middleware/securityHeaders.d.ts +29 -0
- package/dist/server/middleware/securityHeaders.js +44 -0
- package/dist/server/middleware/zValidator.d.ts +29 -0
- package/dist/server/middleware/zValidator.js +57 -0
- package/dist/server/migration/runner.d.ts +42 -0
- package/dist/server/migration/runner.js +70 -0
- package/dist/server/migration/types.d.ts +16 -0
- package/dist/server/migration/types.js +1 -0
- package/dist/server/net/outboundGuard.d.ts +30 -0
- package/dist/server/net/outboundGuard.js +199 -0
- package/dist/server/notifications/hub.d.ts +67 -0
- package/dist/server/notifications/hub.js +187 -0
- package/dist/server/notifications/index.d.ts +47 -0
- package/dist/server/notifications/index.js +46 -0
- package/dist/server/notifications/migrations.d.ts +13 -0
- package/dist/server/notifications/migrations.js +20 -0
- package/dist/server/notifications/routes.d.ts +58 -0
- package/dist/server/notifications/routes.js +113 -0
- package/dist/server/notifications/service.d.ts +58 -0
- package/dist/server/notifications/service.js +41 -0
- package/dist/server/paths/appDataHome.d.ts +19 -0
- package/dist/server/paths/appDataHome.js +68 -0
- package/dist/server/private-media/crypto.d.ts +37 -0
- package/dist/server/private-media/crypto.js +111 -0
- package/dist/server/private-media/index-store.d.ts +31 -0
- package/dist/server/private-media/index-store.js +36 -0
- package/dist/server/private-media/mountPrivateRoutes.d.ts +26 -0
- package/dist/server/private-media/mountPrivateRoutes.js +219 -0
- package/dist/server/private-media/pipeline.d.ts +37 -0
- package/dist/server/private-media/pipeline.js +97 -0
- package/dist/server/private-media/unlock.d.ts +52 -0
- package/dist/server/private-media/unlock.js +55 -0
- package/dist/server/resume/index.d.ts +2 -0
- package/dist/server/resume/index.js +2 -0
- package/dist/server/resume/routes.d.ts +41 -0
- package/dist/server/resume/routes.js +50 -0
- package/dist/server/resume/store.d.ts +37 -0
- package/dist/server/resume/store.js +38 -0
- package/dist/server/retention/archive.d.ts +78 -0
- package/dist/server/retention/archive.js +284 -0
- package/dist/server/retention/fleet.d.ts +50 -0
- package/dist/server/retention/fleet.js +66 -0
- package/dist/server/retention/index.d.ts +38 -0
- package/dist/server/retention/index.js +38 -0
- package/dist/server/retention/policy.d.ts +263 -0
- package/dist/server/retention/policy.js +264 -0
- package/dist/server/retention/store.d.ts +101 -0
- package/dist/server/retention/store.js +251 -0
- package/dist/server/retention/sweeper.d.ts +69 -0
- package/dist/server/retention/sweeper.js +289 -0
- package/dist/server/retention/target.d.ts +143 -0
- package/dist/server/retention/target.js +438 -0
- package/dist/server/search/index.d.ts +2 -0
- package/dist/server/search/index.js +6 -0
- package/dist/server/search/pgSearchProvider.d.ts +91 -0
- package/dist/server/search/pgSearchProvider.js +330 -0
- package/dist/server/search/pgSearchProvider.pg.test.d.ts +1 -0
- package/dist/server/search/pgSearchProvider.pg.test.js +338 -0
- package/dist/server/search/pgSearchSchema.d.ts +81 -0
- package/dist/server/search/pgSearchSchema.js +89 -0
- package/dist/server/sharing/index.d.ts +18 -0
- package/dist/server/sharing/index.js +16 -0
- package/dist/server/sharing/migrations.d.ts +6 -0
- package/dist/server/sharing/migrations.js +79 -0
- package/dist/server/sharing/router.d.ts +111 -0
- package/dist/server/sharing/router.js +93 -0
- package/dist/server/sharing/sql.d.ts +67 -0
- package/dist/server/sharing/sql.js +88 -0
- package/dist/server/sharing/store.d.ts +173 -0
- package/dist/server/sharing/store.js +484 -0
- package/dist/server/sqlite/backup.d.ts +48 -0
- package/dist/server/sqlite/backup.js +97 -0
- package/dist/server/sqlite/browse.d.ts +42 -0
- package/dist/server/sqlite/browse.js +84 -0
- package/dist/server/sqlite/export.d.ts +32 -0
- package/dist/server/sqlite/export.js +42 -0
- package/dist/server/sqlite/fts5.d.ts +106 -0
- package/dist/server/sqlite/fts5.js +154 -0
- package/dist/server/sqlite/health.d.ts +44 -0
- package/dist/server/sqlite/health.js +52 -0
- package/dist/server/sqlite/import.d.ts +36 -0
- package/dist/server/sqlite/import.js +108 -0
- package/dist/server/sqlite/index.d.ts +9 -0
- package/dist/server/sqlite/index.js +15 -0
- package/dist/server/sqlite/readonlySql.d.ts +15 -0
- package/dist/server/sqlite/readonlySql.js +65 -0
- package/dist/server/sqlite/stats.d.ts +22 -0
- package/dist/server/sqlite/stats.js +32 -0
- package/dist/server/sqlite/viewer.d.ts +17 -0
- package/dist/server/sqlite/viewer.js +56 -0
- package/dist/server/storage/binaryServerProcessing.d.ts +23 -0
- package/dist/server/storage/binaryServerProcessing.js +81 -0
- package/dist/server/storage/binaryServerStore.d.ts +3 -0
- package/dist/server/storage/binaryServerStore.js +95 -0
- package/dist/server/storage/diskStore.d.ts +2 -0
- package/dist/server/storage/diskStore.js +88 -0
- package/dist/server/storage/files/adapter.d.ts +33 -0
- package/dist/server/storage/files/adapter.js +203 -0
- package/dist/server/storage/files/catalogue.d.ts +72 -0
- package/dist/server/storage/files/catalogue.js +1033 -0
- package/dist/server/storage/files/chunkedUpload.d.ts +56 -0
- package/dist/server/storage/files/chunkedUpload.js +70 -0
- package/dist/server/storage/files/fileRoutes.d.ts +51 -0
- package/dist/server/storage/files/fileRoutes.js +405 -0
- package/dist/server/storage/files/index.d.ts +9 -0
- package/dist/server/storage/files/index.js +11 -0
- package/dist/server/storage/files/migrations.d.ts +2 -0
- package/dist/server/storage/files/migrations.js +163 -0
- package/dist/server/storage/files/mimeByExt.d.ts +1 -0
- package/dist/server/storage/files/mimeByExt.js +13 -0
- package/dist/server/storage/files/stack.d.ts +136 -0
- package/dist/server/storage/files/stack.js +132 -0
- package/dist/server/storage/files/storageOps.d.ts +54 -0
- package/dist/server/storage/files/storageOps.js +110 -0
- package/dist/server/storage/files/types.d.ts +261 -0
- package/dist/server/storage/files/types.js +22 -0
- package/dist/server/storage/imageThumbnail.d.ts +3 -0
- package/dist/server/storage/imageThumbnail.js +50 -0
- package/dist/server/storage/index.d.ts +15 -0
- package/dist/server/storage/index.js +27 -0
- package/dist/server/storage/mountMediaRoutes.d.ts +28 -0
- package/dist/server/storage/mountMediaRoutes.js +141 -0
- package/dist/server/storage/objectStoreMediaStore.d.ts +4 -0
- package/dist/server/storage/objectStoreMediaStore.js +57 -0
- package/dist/server/storage/s3ObjectStore.d.ts +63 -0
- package/dist/server/storage/s3ObjectStore.js +187 -0
- package/dist/server/storage/sources.d.ts +47 -0
- package/dist/server/storage/sources.js +148 -0
- package/dist/server/storage/streamRange.d.ts +19 -0
- package/dist/server/storage/streamRange.js +69 -0
- package/dist/server/storage/streamToken.d.ts +16 -0
- package/dist/server/storage/streamToken.js +54 -0
- package/dist/server/storage/transcodeJobs.d.ts +65 -0
- package/dist/server/storage/transcodeJobs.js +104 -0
- package/dist/server/storage/types.d.ts +193 -0
- package/dist/server/storage/types.js +41 -0
- package/dist/server/storage/videoDerivativeKeys.d.ts +127 -0
- package/dist/server/storage/videoDerivativeKeys.js +149 -0
- package/dist/server/storage/videoProcessor.d.ts +30 -0
- package/dist/server/storage/videoProcessor.js +148 -0
- package/dist/server/sync/alarm.d.ts +34 -0
- package/dist/server/sync/alarm.js +91 -0
- package/dist/server/sync/commands.d.ts +87 -0
- package/dist/server/sync/commands.js +241 -0
- package/dist/server/sync/engine.d.ts +62 -0
- package/dist/server/sync/engine.js +184 -0
- package/dist/server/sync/http.d.ts +106 -0
- package/dist/server/sync/http.js +243 -0
- package/dist/server/sync/index.d.ts +41 -0
- package/dist/server/sync/index.js +41 -0
- package/dist/server/sync/opLog.d.ts +61 -0
- package/dist/server/sync/opLog.js +96 -0
- package/dist/server/sync/planner.d.ts +31 -0
- package/dist/server/sync/planner.js +30 -0
- package/dist/server/sync/status.d.ts +63 -0
- package/dist/server/sync/status.js +49 -0
- package/dist/server/sync/timer.d.ts +52 -0
- package/dist/server/sync/timer.js +170 -0
- package/dist/server/sync/tokens.d.ts +25 -0
- package/dist/server/sync/tokens.js +51 -0
- package/dist/server/sync/types.d.ts +101 -0
- package/dist/server/sync/types.js +29 -0
- package/dist/server/ws/eventBus.d.ts +14 -0
- package/dist/server/ws/eventBus.js +42 -0
- package/dist/server/ws/hub.d.ts +20 -0
- package/dist/server/ws/hub.js +154 -0
- package/dist/server/ws/index.d.ts +5 -0
- package/dist/server/ws/index.js +7 -0
- package/dist/server/ws/presence.d.ts +11 -0
- package/dist/server/ws/presence.js +20 -0
- package/dist/server/ws/room.d.ts +17 -0
- package/dist/server/ws/room.js +23 -0
- package/dist/server/ws/types.d.ts +122 -0
- package/dist/server/ws/types.js +1 -0
- package/package.json +234 -0
- package/src/declaredDepsAreImported.spec.ts +87 -0
- package/src/leafSubpathsImportNothing.spec.ts +358 -0
- package/src/lockfileIsTracked.spec.ts +50 -0
- package/src/namedSubpathsResolve.spec.ts +122 -0
- package/src/noPathDeps.spec.ts +24 -0
- package/src/publishShape.spec.ts +89 -0
- package/src/server/activity/index.ts +59 -0
- package/src/server/activity/migrations.ts +67 -0
- package/src/server/activity/shareSource.ts +57 -0
- package/src/server/activity/store.spec.ts +255 -0
- package/src/server/activity/store.ts +368 -0
- package/src/server/app.spec.ts +243 -0
- package/src/server/app.ts +89 -0
- package/src/server/audit/index.ts +2 -0
- package/src/server/audit/migrations.ts +88 -0
- package/src/server/audit/sqliteSink.test.ts +200 -0
- package/src/server/audit/sqliteSink.ts +162 -0
- package/src/server/auth/cookie.spec.ts +46 -0
- package/src/server/auth/cookie.ts +42 -0
- package/src/server/auth/csrf.spec.ts +81 -0
- package/src/server/auth/csrf.ts +83 -0
- package/src/server/auth/fileTokenSigner.ts +40 -0
- package/src/server/auth/getOnly.spec.ts +53 -0
- package/src/server/auth/getOnly.ts +32 -0
- package/src/server/auth/index.ts +51 -0
- package/src/server/auth/jwt.spec.ts +106 -0
- package/src/server/auth/jwt.ts +75 -0
- package/src/server/auth/loginThrottle.spec.ts +213 -0
- package/src/server/auth/loginThrottle.ts +261 -0
- package/src/server/auth/middleware.spec.ts +148 -0
- package/src/server/auth/middleware.ts +56 -0
- package/src/server/auth/password.spec.ts +48 -0
- package/src/server/auth/password.ts +71 -0
- package/src/server/auth/sessionStore.spec.ts +189 -0
- package/src/server/auth/sessionStore.ts +261 -0
- package/src/server/auth/totp.spec.ts +73 -0
- package/src/server/auth/totp.ts +92 -0
- package/src/server/auth/types.ts +146 -0
- package/src/server/auth/vault.spec.ts +51 -0
- package/src/server/auth/vault.ts +71 -0
- package/src/server/context.ts +37 -0
- package/src/server/ctgr.spec.ts +138 -0
- package/src/server/ctgr.ts +108 -0
- package/src/server/db/kysely.spec.ts +62 -0
- package/src/server/db/kysely.ts +21 -0
- package/src/server/db/plumbingTypes.ts +126 -0
- package/src/server/dropzone/duplicates.ts +121 -0
- package/src/server/dropzone/index.ts +30 -0
- package/src/server/dropzone/scan.spec.ts +255 -0
- package/src/server/dropzone/scan.ts +342 -0
- package/src/server/errors.spec.ts +76 -0
- package/src/server/errors.ts +22 -0
- package/src/server/guard/argon2Binding.spec.ts +117 -0
- package/src/server/guard/argon2Binding.ts +195 -0
- package/src/server/guard/argon2Kat.spec.ts +250 -0
- package/src/server/guard/guard.spec.ts +390 -0
- package/src/server/guard/index.ts +6 -0
- package/src/server/guard/kdfWorker.spec.ts +283 -0
- package/src/server/guard/kdfWorker.ts +367 -0
- package/src/server/guard/kdfWorkerEntry.ts +16 -0
- package/src/server/guard/passwordVerifier.spec.ts +71 -0
- package/src/server/guard/passwordVerifier.ts +175 -0
- package/src/server/guard/revocationStore.spec.ts +135 -0
- package/src/server/guard/revocationStore.ts +291 -0
- package/src/server/guard/sessionSigner.ts +425 -0
- package/src/server/guard/verifier.ts +27 -0
- package/src/server/incidents/channels.ts +487 -0
- package/src/server/incidents/checks.ts +120 -0
- package/src/server/incidents/incidents.spec.ts +463 -0
- package/src/server/incidents/index.ts +96 -0
- package/src/server/incidents/migrations.ts +66 -0
- package/src/server/incidents/notifier.ts +215 -0
- package/src/server/incidents/reporter.ts +110 -0
- package/src/server/incidents/routes.ts +314 -0
- package/src/server/incidents/store.ts +329 -0
- package/src/server/incidents/types.ts +116 -0
- package/src/server/index.ts +248 -0
- package/src/server/jobs/fireQueue.spec.ts +85 -0
- package/src/server/jobs/fireQueue.ts +37 -0
- package/src/server/jobs/index.ts +26 -0
- package/src/server/jobs/logger.ts +16 -0
- package/src/server/jobs/plainjobAdapter.spec.ts +34 -0
- package/src/server/jobs/plainjobAdapter.ts +54 -0
- package/src/server/jobs/polling.spec.ts +219 -0
- package/src/server/jobs/polling.ts +81 -0
- package/src/server/jobs/runner.spec.ts +213 -0
- package/src/server/jobs/runner.ts +213 -0
- package/src/server/jobs/timeout.spec.ts +35 -0
- package/src/server/jobs/timeout.ts +53 -0
- package/src/server/jobs/types.ts +188 -0
- package/src/server/master-lock/guard.spec.ts +347 -0
- package/src/server/master-lock/guard.ts +290 -0
- package/src/server/master-lock/index.ts +44 -0
- package/src/server/master-lock/lockPage.spec.ts +88 -0
- package/src/server/master-lock/lockPage.ts +313 -0
- package/src/server/master-lock/masterLock.spec.ts +338 -0
- package/src/server/master-lock/masterLock.ts +627 -0
- package/src/server/master-lock/principals.spec.ts +378 -0
- package/src/server/master-lock/principals.ts +175 -0
- package/src/server/master-lock/seed.spec.ts +69 -0
- package/src/server/master-lock/seed.ts +112 -0
- package/src/server/master-lock/store.ts +35 -0
- package/src/server/media-bun/image.spec.ts +115 -0
- package/src/server/media-bun/image.ts +134 -0
- package/src/server/media-bun/index.ts +52 -0
- package/src/server/media-bun/streaming.spec.ts +134 -0
- package/src/server/media-bun/streaming.ts +347 -0
- package/src/server/media-bun/video.spec.ts +115 -0
- package/src/server/media-bun/video.ts +232 -0
- package/src/server/metrics/metricsBuffer.ts +92 -0
- package/src/server/metrics/normalizeRoute.spec.ts +51 -0
- package/src/server/metrics/normalizeRoute.ts +63 -0
- package/src/server/middleware/correlationId.ts +24 -0
- package/src/server/middleware/corsAllowlist.spec.ts +118 -0
- package/src/server/middleware/corsAllowlist.ts +69 -0
- package/src/server/middleware/errorEnvelope.ts +38 -0
- package/src/server/middleware/index.ts +19 -0
- package/src/server/middleware/localOrigin.spec.ts +168 -0
- package/src/server/middleware/localOrigin.ts +139 -0
- package/src/server/middleware/rateLimit.spec.ts +72 -0
- package/src/server/middleware/rateLimit.ts +125 -0
- package/src/server/middleware/requestLogger.ts +95 -0
- package/src/server/middleware/securityHeaders.ts +78 -0
- package/src/server/middleware/zValidator.spec.ts +251 -0
- package/src/server/middleware/zValidator.ts +114 -0
- package/src/server/migration/runner.spec.ts +88 -0
- package/src/server/migration/runner.ts +87 -0
- package/src/server/migration/types.ts +17 -0
- package/src/server/net/outboundGuard.spec.ts +90 -0
- package/src/server/net/outboundGuard.ts +205 -0
- package/src/server/notifications/hub.ts +245 -0
- package/src/server/notifications/index.ts +77 -0
- package/src/server/notifications/migrations.ts +39 -0
- package/src/server/notifications/routes.ts +177 -0
- package/src/server/notifications/service.spec.ts +305 -0
- package/src/server/notifications/service.ts +112 -0
- package/src/server/paths/appDataHome.spec.ts +87 -0
- package/src/server/paths/appDataHome.ts +84 -0
- package/src/server/private-media/blobRoute.spec.ts +165 -0
- package/src/server/private-media/crypto.spec.ts +81 -0
- package/src/server/private-media/crypto.ts +155 -0
- package/src/server/private-media/gatePolicy.spec.ts +98 -0
- package/src/server/private-media/index-store.ts +83 -0
- package/src/server/private-media/mountPrivateRoutes.ts +265 -0
- package/src/server/private-media/pipeline.spec.ts +111 -0
- package/src/server/private-media/pipeline.ts +170 -0
- package/src/server/private-media/routes.spec.ts +265 -0
- package/src/server/private-media/unlock.ts +112 -0
- package/src/server/resume/index.ts +8 -0
- package/src/server/resume/resume.spec.ts +118 -0
- package/src/server/resume/routes.ts +92 -0
- package/src/server/resume/store.ts +82 -0
- package/src/server/retention/archive.spec.ts +181 -0
- package/src/server/retention/archive.ts +353 -0
- package/src/server/retention/fleet.ts +104 -0
- package/src/server/retention/index.ts +101 -0
- package/src/server/retention/policy.spec.ts +132 -0
- package/src/server/retention/policy.ts +392 -0
- package/src/server/retention/store.spec.ts +236 -0
- package/src/server/retention/store.ts +338 -0
- package/src/server/retention/sweeper.spec.ts +321 -0
- package/src/server/retention/sweeper.ts +367 -0
- package/src/server/retention/target.spec.ts +592 -0
- package/src/server/retention/target.ts +613 -0
- package/src/server/search/index.ts +12 -0
- package/src/server/search/pgSearchProvider.pg.test.ts +392 -0
- package/src/server/search/pgSearchProvider.ts +421 -0
- package/src/server/search/pgSearchSchema.ts +147 -0
- package/src/server/sharing/index.ts +65 -0
- package/src/server/sharing/migrations.ts +128 -0
- package/src/server/sharing/router.spec.ts +167 -0
- package/src/server/sharing/router.ts +194 -0
- package/src/server/sharing/sql.ts +109 -0
- package/src/server/sharing/store.spec.ts +408 -0
- package/src/server/sharing/store.ts +737 -0
- package/src/server/sqlite/backup.spec.ts +114 -0
- package/src/server/sqlite/backup.ts +154 -0
- package/src/server/sqlite/browse.spec.ts +70 -0
- package/src/server/sqlite/browse.ts +120 -0
- package/src/server/sqlite/export.spec.ts +43 -0
- package/src/server/sqlite/export.ts +74 -0
- package/src/server/sqlite/fts5.spec.ts +175 -0
- package/src/server/sqlite/fts5.ts +255 -0
- package/src/server/sqlite/health.spec.ts +79 -0
- package/src/server/sqlite/health.ts +105 -0
- package/src/server/sqlite/import.spec.ts +75 -0
- package/src/server/sqlite/import.ts +141 -0
- package/src/server/sqlite/index.ts +64 -0
- package/src/server/sqlite/readonlySql.spec.ts +58 -0
- package/src/server/sqlite/readonlySql.ts +67 -0
- package/src/server/sqlite/stats.spec.ts +21 -0
- package/src/server/sqlite/stats.ts +61 -0
- package/src/server/sqlite/viewer.spec.ts +127 -0
- package/src/server/sqlite/viewer.ts +72 -0
- package/src/server/storage/binaryServerProcessing.integration.spec.ts +222 -0
- package/src/server/storage/binaryServerProcessing.spec.ts +150 -0
- package/src/server/storage/binaryServerProcessing.ts +127 -0
- package/src/server/storage/binaryServerStore.ts +102 -0
- package/src/server/storage/chunked.integration.spec.ts +185 -0
- package/src/server/storage/diskStore.ts +83 -0
- package/src/server/storage/files/adapter.photo.display.spec.ts +169 -0
- package/src/server/storage/files/adapter.photo.spec.ts +128 -0
- package/src/server/storage/files/adapter.spec.ts +121 -0
- package/src/server/storage/files/adapter.ts +255 -0
- package/src/server/storage/files/catalogue.media-host.spec.ts +184 -0
- package/src/server/storage/files/catalogue.orderings.spec.ts +157 -0
- package/src/server/storage/files/catalogue.private.spec.ts +106 -0
- package/src/server/storage/files/catalogue.recover.spec.ts +131 -0
- package/src/server/storage/files/catalogue.sort.spec.ts +154 -0
- package/src/server/storage/files/catalogue.spec.ts +133 -0
- package/src/server/storage/files/catalogue.trash-import.spec.ts +125 -0
- package/src/server/storage/files/catalogue.ts +1326 -0
- package/src/server/storage/files/chunkedUpload.spec.ts +111 -0
- package/src/server/storage/files/chunkedUpload.ts +146 -0
- package/src/server/storage/files/collections.spec.ts +294 -0
- package/src/server/storage/files/fileRoutes.replace.spec.ts +88 -0
- package/src/server/storage/files/fileRoutes.spec.ts +327 -0
- package/src/server/storage/files/fileRoutes.ts +462 -0
- package/src/server/storage/files/index.ts +66 -0
- package/src/server/storage/files/migrations.ts +187 -0
- package/src/server/storage/files/mimeByExt.ts +14 -0
- package/src/server/storage/files/stack.spec.ts +258 -0
- package/src/server/storage/files/stack.ts +284 -0
- package/src/server/storage/files/storageOps.spec.ts +126 -0
- package/src/server/storage/files/storageOps.ts +213 -0
- package/src/server/storage/files/types.ts +285 -0
- package/src/server/storage/imageThumbnail.spec.ts +81 -0
- package/src/server/storage/imageThumbnail.ts +60 -0
- package/src/server/storage/index.ts +122 -0
- package/src/server/storage/mountMediaRoutes.ts +231 -0
- package/src/server/storage/objectStore.spec.ts +272 -0
- package/src/server/storage/objectStoreMediaStore.ts +63 -0
- package/src/server/storage/s3ObjectStore.ts +239 -0
- package/src/server/storage/sources.ts +245 -0
- package/src/server/storage/storage.spec.ts +219 -0
- package/src/server/storage/streamRange.ts +92 -0
- package/src/server/storage/streamToken.ts +76 -0
- package/src/server/storage/transcode.integration.spec.ts +134 -0
- package/src/server/storage/transcodeJobs.spec.ts +152 -0
- package/src/server/storage/transcodeJobs.ts +171 -0
- package/src/server/storage/types.ts +230 -0
- package/src/server/storage/videoDerivativeKeys.spec.ts +112 -0
- package/src/server/storage/videoDerivativeKeys.ts +172 -0
- package/src/server/storage/videoProcessor.posterOnly.spec.ts +106 -0
- package/src/server/storage/videoProcessor.ts +229 -0
- package/src/server/sync/alarm.spec.ts +149 -0
- package/src/server/sync/alarm.ts +137 -0
- package/src/server/sync/commands.spec.ts +145 -0
- package/src/server/sync/commands.ts +361 -0
- package/src/server/sync/engine.spec.ts +496 -0
- package/src/server/sync/engine.ts +255 -0
- package/src/server/sync/http.ts +316 -0
- package/src/server/sync/httpRemote.spec.ts +158 -0
- package/src/server/sync/index.ts +90 -0
- package/src/server/sync/opLog.spec.ts +110 -0
- package/src/server/sync/opLog.ts +189 -0
- package/src/server/sync/planner.spec.ts +53 -0
- package/src/server/sync/planner.ts +62 -0
- package/src/server/sync/status.spec.ts +92 -0
- package/src/server/sync/status.ts +108 -0
- package/src/server/sync/timer.spec.ts +150 -0
- package/src/server/sync/timer.ts +207 -0
- package/src/server/sync/tokens.spec.ts +38 -0
- package/src/server/sync/tokens.ts +94 -0
- package/src/server/sync/types.ts +108 -0
- package/src/server/ws/eventBus.spec.ts +67 -0
- package/src/server/ws/eventBus.ts +46 -0
- package/src/server/ws/hub.integration.spec.ts +93 -0
- package/src/server/ws/hub.spec.ts +282 -0
- package/src/server/ws/hub.ts +168 -0
- package/src/server/ws/index.ts +20 -0
- package/src/server/ws/presence.spec.ts +27 -0
- package/src/server/ws/presence.ts +26 -0
- package/src/server/ws/room.spec.ts +36 -0
- package/src/server/ws/room.ts +29 -0
- package/src/server/ws/types.ts +134 -0
- package/src/skippedTestsAreAnswered.spec.ts +215 -0
- package/src/testTmpRoot.spec.ts +102 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
import type { GalleryMedia, GalleryPhoto } from 'cursedbelt-core/media';
|
|
2
|
+
import { isBrowserRenderableImage } from '../imageThumbnail';
|
|
3
|
+
import type { MediaUrls } from '../sources';
|
|
4
|
+
import type { MediaCtx, MediaGallerySource, MediaSource, PhotoSource } from '../types';
|
|
5
|
+
import type { FileUrls } from './fileRoutes';
|
|
6
|
+
import type { FileCatalogue, FileRecord, FileToPlayable } from './types';
|
|
7
|
+
|
|
8
|
+
/** Build the image→GalleryPhoto mapper shared by the photo source and the mixed media source.
|
|
9
|
+
* The primary image is served by the file byte route (fileUrls); the grid thumbnail + HEIC
|
|
10
|
+
* display derivative by the type-agnostic poster route (mediaUrls) when ingest produced one. */
|
|
11
|
+
function createFileToPhoto(cfg: {
|
|
12
|
+
id: string;
|
|
13
|
+
fileUrls: FileUrls;
|
|
14
|
+
mediaUrls: MediaUrls;
|
|
15
|
+
}): (ctx: MediaCtx, file: FileRecord) => Promise<GalleryPhoto | null> {
|
|
16
|
+
return async (ctx, file) => {
|
|
17
|
+
if (file.fileType !== 'image') return null;
|
|
18
|
+
const { url } = await cfg.fileUrls.build(ctx, file.id, file.mime);
|
|
19
|
+
const media = file.hasThumbnail ? await cfg.mediaUrls.build(ctx, cfg.id, file.id) : null;
|
|
20
|
+
const thumbnailUrl = media?.posterUrl;
|
|
21
|
+
const displayUrl = media && !isBrowserRenderableImage(file.mime) ? media.displayUrl : undefined;
|
|
22
|
+
return {
|
|
23
|
+
id: file.id,
|
|
24
|
+
type: 'image',
|
|
25
|
+
url,
|
|
26
|
+
thumbnailUrl,
|
|
27
|
+
displayUrl,
|
|
28
|
+
name: file.name,
|
|
29
|
+
sizeBytes: file.size,
|
|
30
|
+
mimeType: file.mime,
|
|
31
|
+
tags: file.tags,
|
|
32
|
+
createdAt: file.createdAt,
|
|
33
|
+
favorite: file.favorite,
|
|
34
|
+
};
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// ── FileCatalogue → media seam ────────────────────────────────────────────────────
|
|
39
|
+
// The catalogue is the ONE source of truth for a user's binaries. The video gallery is not a
|
|
40
|
+
// separate store — it is a video-filtered VIEW over the catalogue. These two adapters are the
|
|
41
|
+
// seam: `createFileToPlayable` turns a video FileRecord into the player's PlayableVideo (URLs
|
|
42
|
+
// baked server-side via the shared token/serve infra), and `createCatalogueVideoSource` exposes
|
|
43
|
+
// the catalogue as a `MediaSource` so the existing `mountMediaRoutes` + gallery serve it
|
|
44
|
+
// unchanged. Because the serve routes address bytes by (owner, videoId) through the shared
|
|
45
|
+
// keyspace, a catalogue video is served by the same routes regardless of `sourceId`.
|
|
46
|
+
|
|
47
|
+
/** Resolve a video FileRecord to a PlayableVideo with tokenised URLs. Non-video ⇒ null. */
|
|
48
|
+
export function createFileToPlayable(urls: MediaUrls, sourceId: string): FileToPlayable {
|
|
49
|
+
return async (ctx, file) => {
|
|
50
|
+
if (file.fileType !== 'video') return null;
|
|
51
|
+
const u = await urls.build(ctx, sourceId, file.id);
|
|
52
|
+
return {
|
|
53
|
+
id: file.id,
|
|
54
|
+
name: file.name,
|
|
55
|
+
streamUrl: u.streamUrl,
|
|
56
|
+
hlsUrl: file.hasHls ? u.hlsUrl : undefined,
|
|
57
|
+
posterUrl: file.hasThumbnail ? u.posterUrl : undefined,
|
|
58
|
+
vttUrl: file.hasSprites ? u.vttUrl : undefined,
|
|
59
|
+
spriteUrl: file.hasSprites ? u.spriteUrl : undefined,
|
|
60
|
+
durationSeconds: file.durationSeconds,
|
|
61
|
+
tags: file.tags,
|
|
62
|
+
processingStatus: file.processingStatus,
|
|
63
|
+
processingProgress: file.processingProgress,
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Expose a FileCatalogue as a video-only `MediaSource` — the gallery is a view, not a store. */
|
|
69
|
+
export function createCatalogueVideoSource(cfg: {
|
|
70
|
+
id: string;
|
|
71
|
+
title: string;
|
|
72
|
+
catalogue: FileCatalogue;
|
|
73
|
+
urls: MediaUrls;
|
|
74
|
+
}): MediaSource {
|
|
75
|
+
const toPlayable = createFileToPlayable(cfg.urls, cfg.id);
|
|
76
|
+
return {
|
|
77
|
+
id: cfg.id,
|
|
78
|
+
title: cfg.title,
|
|
79
|
+
|
|
80
|
+
async list(ctx, opts) {
|
|
81
|
+
const page = await cfg.catalogue.list(ctx, {
|
|
82
|
+
type: 'video',
|
|
83
|
+
tag: opts.tag,
|
|
84
|
+
query: opts.query,
|
|
85
|
+
cursor: opts.cursor,
|
|
86
|
+
limit: opts.limit,
|
|
87
|
+
sort: opts.sort,
|
|
88
|
+
});
|
|
89
|
+
const items = await Promise.all(page.items.map((f) => toPlayable(ctx, f)));
|
|
90
|
+
return { items: items.filter((v) => v !== null), nextCursor: page.nextCursor };
|
|
91
|
+
},
|
|
92
|
+
|
|
93
|
+
async resolve(ctx, videoId) {
|
|
94
|
+
const file = await cfg.catalogue.get(ctx, videoId);
|
|
95
|
+
return file ? toPlayable(ctx, file) : null;
|
|
96
|
+
},
|
|
97
|
+
|
|
98
|
+
async tags(ctx) {
|
|
99
|
+
return (await cfg.catalogue.tagFacets(ctx)).map((f) => f.tag);
|
|
100
|
+
},
|
|
101
|
+
|
|
102
|
+
async ingest(ctx, upload) {
|
|
103
|
+
const file = await cfg.catalogue.ingest(ctx, upload);
|
|
104
|
+
const playable = await toPlayable(ctx, file);
|
|
105
|
+
if (!playable) throw new Error('gallery ingest expects a video file');
|
|
106
|
+
return playable;
|
|
107
|
+
},
|
|
108
|
+
|
|
109
|
+
async remove(ctx, videoId) {
|
|
110
|
+
await cfg.catalogue.trash(ctx, videoId);
|
|
111
|
+
},
|
|
112
|
+
|
|
113
|
+
async recordView(ctx, videoId) {
|
|
114
|
+
await cfg.catalogue.recordView(ctx, videoId);
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ── Photo view ────────────────────────────────────────────────────────────────────
|
|
120
|
+
// The photo gallery is the image-filtered sibling of the video view: same catalogue, same
|
|
121
|
+
// token/serve infra. The primary image is served by the file byte route (fileUrls) and the grid
|
|
122
|
+
// thumbnail by the type-agnostic poster route (mediaUrls) when the ingest thumbnailer produced
|
|
123
|
+
// one — so a PhotoSource needs BOTH URL builders.
|
|
124
|
+
|
|
125
|
+
/** Expose a FileCatalogue as an image-only `PhotoSource` — a view, not a store. */
|
|
126
|
+
export function createCataloguePhotoSource(cfg: {
|
|
127
|
+
id: string;
|
|
128
|
+
title: string;
|
|
129
|
+
catalogue: FileCatalogue;
|
|
130
|
+
fileUrls: FileUrls;
|
|
131
|
+
mediaUrls: MediaUrls;
|
|
132
|
+
}): PhotoSource {
|
|
133
|
+
const toPhoto = createFileToPhoto(cfg);
|
|
134
|
+
|
|
135
|
+
return {
|
|
136
|
+
id: cfg.id,
|
|
137
|
+
title: cfg.title,
|
|
138
|
+
|
|
139
|
+
async list(ctx, opts) {
|
|
140
|
+
const page = await cfg.catalogue.list(ctx, {
|
|
141
|
+
type: 'image',
|
|
142
|
+
tag: opts.tag,
|
|
143
|
+
query: opts.query,
|
|
144
|
+
cursor: opts.cursor,
|
|
145
|
+
limit: opts.limit,
|
|
146
|
+
sort: opts.sort,
|
|
147
|
+
});
|
|
148
|
+
const items = await Promise.all(page.items.map((f) => toPhoto(ctx, f)));
|
|
149
|
+
return { items: items.filter((p) => p !== null), nextCursor: page.nextCursor };
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
async resolve(ctx, photoId) {
|
|
153
|
+
const file = await cfg.catalogue.get(ctx, photoId);
|
|
154
|
+
return file ? toPhoto(ctx, file) : null;
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
async tags(ctx) {
|
|
158
|
+
return (await cfg.catalogue.tagFacets(ctx)).map((f) => f.tag);
|
|
159
|
+
},
|
|
160
|
+
|
|
161
|
+
async ingest(ctx, upload) {
|
|
162
|
+
if (!upload.mime.startsWith('image/')) {
|
|
163
|
+
throw new Error('photo ingest expects an image file');
|
|
164
|
+
}
|
|
165
|
+
const file = await cfg.catalogue.ingest(ctx, upload);
|
|
166
|
+
const photo = await toPhoto(ctx, file);
|
|
167
|
+
if (!photo) throw new Error('photo ingest expects an image file');
|
|
168
|
+
return photo;
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
async remove(ctx, photoId) {
|
|
172
|
+
await cfg.catalogue.trash(ctx, photoId);
|
|
173
|
+
},
|
|
174
|
+
|
|
175
|
+
async recordView(ctx, photoId) {
|
|
176
|
+
await cfg.catalogue.recordView(ctx, photoId);
|
|
177
|
+
},
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
// ── Mixed media view ───────────────────────────────────────────────────────────────
|
|
182
|
+
// A combined gallery lists BOTH photos and videos from the one catalogue. This adapter
|
|
183
|
+
// reuses the same two mappers (`createFileToPlayable`, `createFileToPhoto`) so a mixed
|
|
184
|
+
// item renders identically to its single-type sibling — it just tags each with
|
|
185
|
+
// `mediaKind` for the UI to pick a tile/stage. It needs BOTH URL builders (video streams
|
|
186
|
+
// + image bytes/poster). A `kind` filter maps to the catalogue's `type`; `all` lists
|
|
187
|
+
// untyped and keeps only image|video (dropping docs/audio the mixed gallery can't show).
|
|
188
|
+
|
|
189
|
+
/** Expose a FileCatalogue as a `MediaGallerySource` — photos + videos in one feed.
|
|
190
|
+
* Videos are tokenised under `videoSourceId` (must match the registered video source so
|
|
191
|
+
* `mountMediaRoutes` serves their streams); photo posters/thumbnails under this source's `id`. */
|
|
192
|
+
export function createCatalogueMediaSource(cfg: {
|
|
193
|
+
id: string;
|
|
194
|
+
title: string;
|
|
195
|
+
catalogue: FileCatalogue;
|
|
196
|
+
urls: MediaUrls;
|
|
197
|
+
fileUrls: FileUrls;
|
|
198
|
+
/** SourceId the video stream tokens are minted under. Default `id`. */
|
|
199
|
+
videoSourceId?: string;
|
|
200
|
+
}): MediaGallerySource {
|
|
201
|
+
const toPlayable = createFileToPlayable(cfg.urls, cfg.videoSourceId ?? cfg.id);
|
|
202
|
+
const toPhoto = createFileToPhoto({ id: cfg.id, fileUrls: cfg.fileUrls, mediaUrls: cfg.urls });
|
|
203
|
+
|
|
204
|
+
const toMedia = async (ctx: MediaCtx, file: FileRecord): Promise<GalleryMedia | null> => {
|
|
205
|
+
if (file.fileType === 'video') {
|
|
206
|
+
const v = await toPlayable(ctx, file);
|
|
207
|
+
return v ? { ...v, mediaKind: 'video' } : null;
|
|
208
|
+
}
|
|
209
|
+
if (file.fileType === 'image') {
|
|
210
|
+
const p = await toPhoto(ctx, file);
|
|
211
|
+
return p ? { ...p, mediaKind: 'photo' } : null;
|
|
212
|
+
}
|
|
213
|
+
return null;
|
|
214
|
+
};
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
id: cfg.id,
|
|
218
|
+
title: cfg.title,
|
|
219
|
+
|
|
220
|
+
async list(ctx, opts) {
|
|
221
|
+
// `all` ⇒ no catalogue type filter, then drop non-image/video below.
|
|
222
|
+
const type = opts.kind === 'image' ? 'image' : opts.kind === 'video' ? 'video' : undefined;
|
|
223
|
+
const page = await cfg.catalogue.list(ctx, {
|
|
224
|
+
type,
|
|
225
|
+
tag: opts.tag,
|
|
226
|
+
query: opts.query,
|
|
227
|
+
cursor: opts.cursor,
|
|
228
|
+
limit: opts.limit,
|
|
229
|
+
sort: opts.sort,
|
|
230
|
+
});
|
|
231
|
+
const items = await Promise.all(page.items.map((f) => toMedia(ctx, f)));
|
|
232
|
+
return {
|
|
233
|
+
items: items.filter((m): m is GalleryMedia => m !== null),
|
|
234
|
+
nextCursor: page.nextCursor,
|
|
235
|
+
};
|
|
236
|
+
},
|
|
237
|
+
|
|
238
|
+
async resolve(ctx, itemId) {
|
|
239
|
+
const file = await cfg.catalogue.get(ctx, itemId);
|
|
240
|
+
return file ? toMedia(ctx, file) : null;
|
|
241
|
+
},
|
|
242
|
+
|
|
243
|
+
async tags(ctx) {
|
|
244
|
+
return (await cfg.catalogue.tagFacets(ctx)).map((f) => f.tag);
|
|
245
|
+
},
|
|
246
|
+
|
|
247
|
+
async remove(ctx, itemId) {
|
|
248
|
+
await cfg.catalogue.trash(ctx, itemId);
|
|
249
|
+
},
|
|
250
|
+
|
|
251
|
+
async recordView(ctx, itemId) {
|
|
252
|
+
await cfg.catalogue.recordView(ctx, itemId);
|
|
253
|
+
},
|
|
254
|
+
};
|
|
255
|
+
}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { afterAll, beforeAll, describe, expect, test } from 'bun:test';
|
|
3
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { runMigrations } from '../../migration/runner';
|
|
7
|
+
import {
|
|
8
|
+
cryptBlobStream,
|
|
9
|
+
derivePasswordSetKey,
|
|
10
|
+
generateUserSalt,
|
|
11
|
+
openMeta,
|
|
12
|
+
unwrapContentKey,
|
|
13
|
+
} from '../../private-media/crypto';
|
|
14
|
+
import { createDiskStore } from '../diskStore';
|
|
15
|
+
import { fsPosterKey, type MediaCtx, type MediaStore, privateSourceKey } from '../types';
|
|
16
|
+
import { createSqliteFileCatalogue, fileBinaryKey } from './catalogue';
|
|
17
|
+
import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
|
|
18
|
+
import type { FileCatalogue, PrivateFileCatalogue } from './types';
|
|
19
|
+
import { bytesStream } from 'cursedbelt-core/testing';
|
|
20
|
+
|
|
21
|
+
// New media-host capabilities: private NON-video blobs (photos), in-place byte replacement
|
|
22
|
+
// (the editor's "overwrite original"), and ingest-time image thumbnails.
|
|
23
|
+
|
|
24
|
+
const CTX: MediaCtx = { userId: 'u1', tenantId: 't1' };
|
|
25
|
+
const OWNER = CTX.userId;
|
|
26
|
+
const stream = bytesStream;
|
|
27
|
+
const drain = async (s: ReadableStream<Uint8Array>): Promise<Uint8Array> =>
|
|
28
|
+
new Uint8Array(await new Response(s).arrayBuffer());
|
|
29
|
+
|
|
30
|
+
let db: Database;
|
|
31
|
+
let root: string;
|
|
32
|
+
let store: MediaStore;
|
|
33
|
+
let cat: FileCatalogue & PrivateFileCatalogue;
|
|
34
|
+
|
|
35
|
+
beforeAll(async () => {
|
|
36
|
+
db = new Database(':memory:');
|
|
37
|
+
runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
|
|
38
|
+
root = await mkdtemp(path.join(tmpdir(), 'fc-host-'));
|
|
39
|
+
store = createDiskStore(root);
|
|
40
|
+
let clock = 1;
|
|
41
|
+
cat = createSqliteFileCatalogue({
|
|
42
|
+
db,
|
|
43
|
+
store,
|
|
44
|
+
workDir: path.join(root, 'w'),
|
|
45
|
+
owner: (c) => c.userId,
|
|
46
|
+
now: () => ++clock,
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
afterAll(async () => {
|
|
50
|
+
db.close();
|
|
51
|
+
await rm(root, { recursive: true, force: true });
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
describe('private blob ingest (non-video)', () => {
|
|
55
|
+
const salt = generateUserSalt();
|
|
56
|
+
const setKey = derivePasswordSetKey('photo-pass', salt);
|
|
57
|
+
const plaintext = new TextEncoder().encode('these are secret photo bytes — not for the feed');
|
|
58
|
+
let id: string;
|
|
59
|
+
|
|
60
|
+
test('ingests an image as a ready, encrypted blob (no ffmpeg involved)', async () => {
|
|
61
|
+
const item = await cat.ingestPrivate(
|
|
62
|
+
CTX,
|
|
63
|
+
{ name: 'secret.jpg', mime: 'image/jpeg', body: stream(plaintext), tags: ['hidden'] },
|
|
64
|
+
{ setKey, keyMode: 'deniable' },
|
|
65
|
+
);
|
|
66
|
+
id = item.id;
|
|
67
|
+
expect(item.processingStatus).toBe('ready');
|
|
68
|
+
expect(item.size).toBe(plaintext.byteLength);
|
|
69
|
+
expect(item.hasHls).toBe(false);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test('stored bytes are ciphertext; the wrapped content key decrypts them', async () => {
|
|
73
|
+
const stored = await store.get(privateSourceKey(OWNER, id));
|
|
74
|
+
expect(stored).not.toBeNull();
|
|
75
|
+
expect(stored?.byteLength).toBe(plaintext.byteLength); // CTR is length-preserving
|
|
76
|
+
expect(Buffer.from(stored as Uint8Array).equals(Buffer.from(plaintext))).toBe(false);
|
|
77
|
+
|
|
78
|
+
const item = await cat.asPrivateIndex().get(OWNER, id);
|
|
79
|
+
const content = unwrapContentKey(setKey, item?.wrappedContentKey ?? '');
|
|
80
|
+
expect(content).not.toBeNull();
|
|
81
|
+
const decrypted = await drain(
|
|
82
|
+
cryptBlobStream(content as NonNullable<typeof content>, stream(stored as Uint8Array)),
|
|
83
|
+
);
|
|
84
|
+
expect(Buffer.from(decrypted).equals(Buffer.from(plaintext))).toBe(true);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test('the sealed descriptor reveals name/mime/tags to its set key only', async () => {
|
|
88
|
+
const item = await cat.asPrivateIndex().get(OWNER, id);
|
|
89
|
+
const meta = openMeta<{ name: string; mime: string; tags: string[] }>(
|
|
90
|
+
setKey,
|
|
91
|
+
item?.sealedMeta ?? '',
|
|
92
|
+
);
|
|
93
|
+
expect(meta).toEqual({ name: 'secret.jpg', mime: 'image/jpeg', tags: ['hidden'] });
|
|
94
|
+
expect(openMeta(derivePasswordSetKey('wrong', salt), item?.sealedMeta ?? '')).toBeNull();
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test('index.remove wipes the ciphertext blob too', async () => {
|
|
98
|
+
await cat.asPrivateIndex().remove(OWNER, id);
|
|
99
|
+
expect(await store.get(privateSourceKey(OWNER, id))).toBeNull();
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe('replaceContent — overwrite bytes, keep identity', () => {
|
|
104
|
+
let id: string;
|
|
105
|
+
let collectionId: string;
|
|
106
|
+
|
|
107
|
+
test('setup: ingest a tagged, collected, favorited doc', async () => {
|
|
108
|
+
const rec = await cat.ingest(CTX, {
|
|
109
|
+
name: 'notes.txt',
|
|
110
|
+
mime: 'text/plain',
|
|
111
|
+
body: stream('v1 bytes'),
|
|
112
|
+
tags: ['keep-me'],
|
|
113
|
+
});
|
|
114
|
+
id = rec.id;
|
|
115
|
+
const coll = await cat.createCollection(CTX, { name: 'Stuff' });
|
|
116
|
+
collectionId = coll.id;
|
|
117
|
+
await cat.addToCollection(CTX, collectionId, [id]);
|
|
118
|
+
await cat.setFavorite(CTX, id, true);
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test('replaces bytes + re-derives mime/size/type while identity survives', async () => {
|
|
122
|
+
const updated = await cat.replaceContent(CTX, id, {
|
|
123
|
+
mime: 'application/pdf',
|
|
124
|
+
body: stream('v2 much longer replacement bytes'),
|
|
125
|
+
name: 'notes.pdf',
|
|
126
|
+
});
|
|
127
|
+
expect(updated?.id).toBe(id);
|
|
128
|
+
expect(updated?.name).toBe('notes.pdf');
|
|
129
|
+
expect(updated?.mime).toBe('application/pdf');
|
|
130
|
+
expect(updated?.fileType).toBe('doc');
|
|
131
|
+
expect(updated?.size).toBe('v2 much longer replacement bytes'.length);
|
|
132
|
+
expect(updated?.tags).toEqual(['keep-me']);
|
|
133
|
+
expect(updated?.favorite).toBe(true);
|
|
134
|
+
expect(updated?.collectionIds).toContain(collectionId);
|
|
135
|
+
|
|
136
|
+
const bytes = await store.get(fileBinaryKey(OWNER, id));
|
|
137
|
+
expect(new TextDecoder().decode(bytes as Uint8Array)).toBe('v2 much longer replacement bytes');
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
test('unknown id → null; private rows refuse replacement', async () => {
|
|
141
|
+
expect(await cat.replaceContent(CTX, 'nope', { mime: 'text/plain', body: stream('x') })).toBe(
|
|
142
|
+
null,
|
|
143
|
+
);
|
|
144
|
+
const salt = generateUserSalt();
|
|
145
|
+
const priv = await cat.ingestPrivate(
|
|
146
|
+
CTX,
|
|
147
|
+
{ name: 'p.jpg', mime: 'image/jpeg', body: stream('p') },
|
|
148
|
+
{ setKey: derivePasswordSetKey('pw', salt), keyMode: 'deniable' },
|
|
149
|
+
);
|
|
150
|
+
expect(
|
|
151
|
+
cat.replaceContent(CTX, priv.id, { mime: 'image/jpeg', body: stream('q') }),
|
|
152
|
+
).rejects.toThrow(/private/);
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
describe('image ingest thumbnails (sharp)', () => {
|
|
157
|
+
test('an ingested image grows a poster-key thumbnail + has_thumbnail flips', async () => {
|
|
158
|
+
// A real encoded image, generated by the same optional peer the thumbnailer uses.
|
|
159
|
+
const sharp = (await import('sharp')).default;
|
|
160
|
+
const png = await sharp({
|
|
161
|
+
create: { width: 64, height: 48, channels: 3, background: { r: 200, g: 40, b: 40 } },
|
|
162
|
+
})
|
|
163
|
+
.png()
|
|
164
|
+
.toBuffer();
|
|
165
|
+
|
|
166
|
+
const rec = await cat.ingest(CTX, {
|
|
167
|
+
name: 'photo.png',
|
|
168
|
+
mime: 'image/png',
|
|
169
|
+
body: stream(new Uint8Array(png)),
|
|
170
|
+
});
|
|
171
|
+
expect(rec.processingStatus).toBe('ready'); // images are ready immediately; thumb is async
|
|
172
|
+
|
|
173
|
+
// The thumbnailer is fire-and-forget — poll briefly.
|
|
174
|
+
let thumbed = false;
|
|
175
|
+
for (let i = 0; i < 40 && !thumbed; i++) {
|
|
176
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
177
|
+
thumbed = (await cat.get(CTX, rec.id))?.hasThumbnail ?? false;
|
|
178
|
+
}
|
|
179
|
+
expect(thumbed).toBe(true);
|
|
180
|
+
const poster = await store.get(fsPosterKey(OWNER, rec.id));
|
|
181
|
+
expect(poster).not.toBeNull();
|
|
182
|
+
expect((poster as Uint8Array).byteLength).toBeGreaterThan(0);
|
|
183
|
+
});
|
|
184
|
+
});
|
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
import { Database } from 'bun:sqlite';
|
|
2
|
+
import { afterEach, beforeEach, describe, expect, test } from 'bun:test';
|
|
3
|
+
import { mkdtemp, rm } from 'node:fs/promises';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { runMigrations } from '../../migration/runner';
|
|
7
|
+
import { createDiskStore } from '../diskStore';
|
|
8
|
+
import type { MediaCtx } from '../types';
|
|
9
|
+
import { createSqliteFileCatalogue, type SqliteFileCatalogueOptions } from './catalogue';
|
|
10
|
+
import { FILE_CATALOGUE_MIGRATIONS } from './migrations';
|
|
11
|
+
import type { FileCatalogue } from './types';
|
|
12
|
+
|
|
13
|
+
// Task #475 — the seeded `random` order and the view-tracked `lastViewed` order. Both are
|
|
14
|
+
// cursor-paginated like every other sort; `random` folds its shuffle seed into the cursor so a
|
|
15
|
+
// visit reshuffles but paginates stably, and `lastViewed` orders by recorded watch-opens.
|
|
16
|
+
|
|
17
|
+
const CTX: MediaCtx = { userId: 'u1', tenantId: 'orderings' };
|
|
18
|
+
const NAMES = ['a.png', 'b.png', 'c.png', 'd.png', 'e.png', 'f.png', 'g.png', 'h.png'];
|
|
19
|
+
|
|
20
|
+
describe('FileCatalogue — random + lastViewed orderings', () => {
|
|
21
|
+
let db: Database;
|
|
22
|
+
let root: string;
|
|
23
|
+
let clock: number;
|
|
24
|
+
|
|
25
|
+
const mkCatalogue = async (
|
|
26
|
+
extra?: Partial<SqliteFileCatalogueOptions>,
|
|
27
|
+
): Promise<FileCatalogue> => {
|
|
28
|
+
const cat = createSqliteFileCatalogue({
|
|
29
|
+
db,
|
|
30
|
+
store: createDiskStore(root),
|
|
31
|
+
workDir: path.join(root, 'work'),
|
|
32
|
+
now: () => ++clock,
|
|
33
|
+
...extra,
|
|
34
|
+
});
|
|
35
|
+
for (const name of NAMES) {
|
|
36
|
+
const body = new Response('x').body as ReadableStream<Uint8Array>;
|
|
37
|
+
await cat.ingest(CTX, { name, mime: 'image/png', body });
|
|
38
|
+
}
|
|
39
|
+
return cat;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Walk the whole pool page-by-page under a sort; assert no gaps, no repeats.
|
|
43
|
+
const paginate = async (cat: FileCatalogue, sort: 'random' | 'lastViewed', limit: number) => {
|
|
44
|
+
const order: string[] = [];
|
|
45
|
+
const seen = new Set<string>();
|
|
46
|
+
let cursor: string | undefined;
|
|
47
|
+
let guard = 0;
|
|
48
|
+
do {
|
|
49
|
+
const page = await cat.list(CTX, { sort, limit, cursor });
|
|
50
|
+
for (const f of page.items) {
|
|
51
|
+
expect(seen.has(f.id)).toBe(false);
|
|
52
|
+
seen.add(f.id);
|
|
53
|
+
order.push(f.id);
|
|
54
|
+
}
|
|
55
|
+
cursor = page.nextCursor;
|
|
56
|
+
guard++;
|
|
57
|
+
} while (cursor && guard < 20);
|
|
58
|
+
return order;
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
beforeEach(async () => {
|
|
62
|
+
db = new Database(':memory:');
|
|
63
|
+
runMigrations(db, FILE_CATALOGUE_MIGRATIONS);
|
|
64
|
+
root = await mkdtemp(path.join(tmpdir(), 'fc-ord-'));
|
|
65
|
+
clock = 5000;
|
|
66
|
+
});
|
|
67
|
+
afterEach(async () => {
|
|
68
|
+
db.close();
|
|
69
|
+
await rm(root, { recursive: true, force: true });
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
test("'random' is a full permutation of the pool (no gaps, no repeats)", async () => {
|
|
73
|
+
const cat = await mkCatalogue({ randomSeed: () => 12345 });
|
|
74
|
+
const oneShot = (await cat.list(CTX, { sort: 'random' })).items.map((f) => f.id);
|
|
75
|
+
expect(new Set(oneShot).size).toBe(NAMES.length);
|
|
76
|
+
// A fixed seed makes the shuffle deterministic, so paginated == one-shot.
|
|
77
|
+
const paged = await paginate(cat, 'random', 3);
|
|
78
|
+
expect(paged).toEqual(oneShot);
|
|
79
|
+
expect(paged.length).toBe(NAMES.length);
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
test("'random' pagination is stable across pages under one seed (cursor carries it)", async () => {
|
|
83
|
+
// The default (random) seed differs per first-page call, so we must thread the cursor: page 2
|
|
84
|
+
// recovers the seed from page 1's cursor and continues the SAME shuffle.
|
|
85
|
+
const cat = await mkCatalogue();
|
|
86
|
+
const p1 = await cat.list(CTX, { sort: 'random', limit: 3 });
|
|
87
|
+
expect(p1.nextCursor).toBeDefined();
|
|
88
|
+
const p2 = await cat.list(CTX, { sort: 'random', limit: 3, cursor: p1.nextCursor });
|
|
89
|
+
const p3 = await cat.list(CTX, { sort: 'random', limit: 3, cursor: p2.nextCursor });
|
|
90
|
+
const walked = [...p1.items, ...p2.items, ...p3.items].map((f) => f.id);
|
|
91
|
+
expect(new Set(walked).size).toBe(NAMES.length); // no repeats across the whole walk
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
test("'random' reshuffles between visits (different seed ⇒ different order)", async () => {
|
|
95
|
+
let seed = 1;
|
|
96
|
+
const cat = await mkCatalogue({ randomSeed: () => seed });
|
|
97
|
+
const first = (await cat.list(CTX, { sort: 'random' })).items.map((f) => f.id);
|
|
98
|
+
seed = 999999;
|
|
99
|
+
const second = (await cat.list(CTX, { sort: 'random' })).items.map((f) => f.id);
|
|
100
|
+
// Same set of files, but the order should differ under a different seed.
|
|
101
|
+
expect(new Set(first)).toEqual(new Set(second));
|
|
102
|
+
expect(second).not.toEqual(first);
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
test("'lastViewed' orders most-recently-viewed first, never-viewed last", async () => {
|
|
106
|
+
const cat = await mkCatalogue();
|
|
107
|
+
const all = (await cat.list(CTX)).items; // recent order
|
|
108
|
+
// View three files in a deliberate sequence; leave the rest unviewed.
|
|
109
|
+
const [x, y, z] = [all[5], all[2], all[7]];
|
|
110
|
+
await cat.recordView(CTX, x.id); // oldest view
|
|
111
|
+
await cat.recordView(CTX, y.id);
|
|
112
|
+
await cat.recordView(CTX, z.id); // newest view
|
|
113
|
+
const ordered = (await cat.list(CTX, { sort: 'lastViewed' })).items;
|
|
114
|
+
// Viewed files come first, newest-view first.
|
|
115
|
+
expect(ordered.slice(0, 3).map((f) => f.id)).toEqual([z.id, y.id, x.id]);
|
|
116
|
+
// The remaining five (never viewed) fill the tail.
|
|
117
|
+
expect(ordered.length).toBe(NAMES.length);
|
|
118
|
+
const tail = new Set(ordered.slice(3).map((f) => f.id));
|
|
119
|
+
expect(tail.has(x.id)).toBe(false);
|
|
120
|
+
expect(tail.size).toBe(NAMES.length - 3);
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
test("re-viewing a file moves it to the front of 'lastViewed' and paginates without gaps", async () => {
|
|
124
|
+
const cat = await mkCatalogue();
|
|
125
|
+
const all = (await cat.list(CTX)).items;
|
|
126
|
+
await cat.recordView(CTX, all[0].id);
|
|
127
|
+
await cat.recordView(CTX, all[1].id);
|
|
128
|
+
await cat.recordView(CTX, all[0].id); // bump all[0] back to the front
|
|
129
|
+
const ordered = (await cat.list(CTX, { sort: 'lastViewed' })).items;
|
|
130
|
+
expect(ordered[0].id).toBe(all[0].id);
|
|
131
|
+
// Full-pool pagination under lastViewed has no gaps/repeats and equals the one-shot order.
|
|
132
|
+
const paged = await paginate(cat, 'lastViewed', 3);
|
|
133
|
+
expect(paged).toEqual(ordered.map((f) => f.id));
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test('recordView is owner-scoped — a foreign id records nothing', async () => {
|
|
137
|
+
const cat = await mkCatalogue();
|
|
138
|
+
const other: MediaCtx = { userId: 'u2', tenantId: 'someone-else' };
|
|
139
|
+
// Recording against an id that isn't the caller's must not create a view row.
|
|
140
|
+
await cat.recordView(CTX, 'no-such-file');
|
|
141
|
+
await cat.recordView(other, (await cat.list(CTX)).items[0].id);
|
|
142
|
+
// Nothing viewed for CTX ⇒ lastViewed degenerates to the id tie-break, still a full pool.
|
|
143
|
+
const ordered = (await cat.list(CTX, { sort: 'lastViewed' })).items;
|
|
144
|
+
expect(ordered.length).toBe(NAMES.length);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("a deleted file drops its view row (no orphan skews 'lastViewed')", async () => {
|
|
148
|
+
const cat = await mkCatalogue();
|
|
149
|
+
const all = (await cat.list(CTX)).items;
|
|
150
|
+
await cat.recordView(CTX, all[0].id);
|
|
151
|
+
await cat.remove(CTX, all[0].id);
|
|
152
|
+
const rows = db
|
|
153
|
+
.query<{ n: number }, [string]>(`SELECT COUNT(*) AS n FROM file_views WHERE file_id = ?`)
|
|
154
|
+
.get(all[0].id);
|
|
155
|
+
expect(rows?.n).toBe(0);
|
|
156
|
+
});
|
|
157
|
+
});
|