@de-otio/trellis 0.10.11 → 0.12.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/env.d.ts +232 -0
- package/dist/env.d.ts.map +1 -1
- package/dist/env.js +221 -0
- package/dist/env.js.map +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -1
- package/dist/lambda/media-completion-worker.d.ts +175 -0
- package/dist/lambda/media-completion-worker.d.ts.map +1 -0
- package/dist/lambda/media-completion-worker.js +373 -0
- package/dist/lambda/media-completion-worker.js.map +1 -0
- package/dist/lambda/media-processing-worker.d.ts +172 -1
- package/dist/lambda/media-processing-worker.d.ts.map +1 -1
- package/dist/lambda/media-processing-worker.js +343 -49
- package/dist/lambda/media-processing-worker.js.map +1 -1
- package/dist/lib/app.d.ts.map +1 -1
- package/dist/lib/app.js +5 -0
- package/dist/lib/app.js.map +1 -1
- package/dist/lib/encrypted-settings/config.d.ts +13 -0
- package/dist/lib/encrypted-settings/config.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/config.js +19 -0
- package/dist/lib/encrypted-settings/config.js.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.d.ts +57 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.js +178 -0
- package/dist/lib/encrypted-settings/encrypted-settings-handler.js.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.d.ts +110 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.js +103 -0
- package/dist/lib/encrypted-settings/encrypted-settings-store.js.map +1 -0
- package/dist/lib/encrypted-settings/types.d.ts +26 -0
- package/dist/lib/encrypted-settings/types.d.ts.map +1 -0
- package/dist/lib/encrypted-settings/types.js +27 -0
- package/dist/lib/encrypted-settings/types.js.map +1 -0
- package/dist/lib/exif-stripper.d.ts +37 -22
- package/dist/lib/exif-stripper.d.ts.map +1 -1
- package/dist/lib/exif-stripper.js +101 -41
- package/dist/lib/exif-stripper.js.map +1 -1
- package/dist/lib/media/cas-keys.d.ts +63 -0
- package/dist/lib/media/cas-keys.d.ts.map +1 -0
- package/dist/lib/media/cas-keys.js +102 -0
- package/dist/lib/media/cas-keys.js.map +1 -0
- package/dist/lib/media/classify-worker-error.d.ts +48 -0
- package/dist/lib/media/classify-worker-error.d.ts.map +1 -0
- package/dist/lib/media/classify-worker-error.js +319 -0
- package/dist/lib/media/classify-worker-error.js.map +1 -0
- package/dist/lib/media/dedupe-key.d.ts +29 -0
- package/dist/lib/media/dedupe-key.d.ts.map +1 -0
- package/dist/lib/media/dedupe-key.js +49 -0
- package/dist/lib/media/dedupe-key.js.map +1 -0
- package/dist/lib/media/duration-cap.d.ts +30 -0
- package/dist/lib/media/duration-cap.d.ts.map +1 -0
- package/dist/lib/media/duration-cap.js +37 -0
- package/dist/lib/media/duration-cap.js.map +1 -0
- package/dist/lib/media/ffmpeg-args.d.ts +83 -0
- package/dist/lib/media/ffmpeg-args.d.ts.map +1 -0
- package/dist/lib/media/ffmpeg-args.js +119 -0
- package/dist/lib/media/ffmpeg-args.js.map +1 -0
- package/dist/lib/media/media-ports.d.ts +126 -0
- package/dist/lib/media/media-ports.d.ts.map +1 -0
- package/dist/lib/media/media-ports.js +129 -0
- package/dist/lib/media/media-ports.js.map +1 -0
- package/dist/lib/media/media-upsert.d.ts +55 -0
- package/dist/lib/media/media-upsert.d.ts.map +1 -0
- package/dist/lib/media/media-upsert.js +38 -0
- package/dist/lib/media/media-upsert.js.map +1 -0
- package/dist/lib/media/moderation-provider.d.ts +111 -0
- package/dist/lib/media/moderation-provider.d.ts.map +1 -0
- package/dist/lib/media/moderation-provider.js +130 -0
- package/dist/lib/media/moderation-provider.js.map +1 -0
- package/dist/lib/media/moderation-resolved-payload.d.ts +48 -0
- package/dist/lib/media/moderation-resolved-payload.d.ts.map +1 -0
- package/dist/lib/media/moderation-resolved-payload.js +37 -0
- package/dist/lib/media/moderation-resolved-payload.js.map +1 -0
- package/dist/lib/media/moderation-status.d.ts +98 -0
- package/dist/lib/media/moderation-status.d.ts.map +1 -0
- package/dist/lib/media/moderation-status.js +122 -0
- package/dist/lib/media/moderation-status.js.map +1 -0
- package/dist/lib/media/processing-types.d.ts +45 -0
- package/dist/lib/media/processing-types.d.ts.map +1 -0
- package/dist/lib/media/processing-types.js +9 -0
- package/dist/lib/media/processing-types.js.map +1 -0
- package/dist/lib/media/promote-decision.d.ts +64 -0
- package/dist/lib/media/promote-decision.d.ts.map +1 -0
- package/dist/lib/media/promote-decision.js +76 -0
- package/dist/lib/media/promote-decision.js.map +1 -0
- package/dist/lib/media/quota-check.d.ts +22 -0
- package/dist/lib/media/quota-check.d.ts.map +1 -0
- package/dist/lib/media/quota-check.js +42 -0
- package/dist/lib/media/quota-check.js.map +1 -0
- package/dist/lib/media/quota-types.d.ts +15 -0
- package/dist/lib/media/quota-types.d.ts.map +1 -0
- package/dist/lib/media/quota-types.js +9 -0
- package/dist/lib/media/quota-types.js.map +1 -0
- package/dist/lib/media/route-upload.d.ts +58 -0
- package/dist/lib/media/route-upload.d.ts.map +1 -0
- package/dist/lib/media/route-upload.js +80 -0
- package/dist/lib/media/route-upload.js.map +1 -0
- package/dist/lib/media/serve-gate.d.ts +51 -0
- package/dist/lib/media/serve-gate.d.ts.map +1 -0
- package/dist/lib/media/serve-gate.js +68 -0
- package/dist/lib/media/serve-gate.js.map +1 -0
- package/dist/lib/media/tenant-resolution.d.ts +42 -0
- package/dist/lib/media/tenant-resolution.d.ts.map +1 -0
- package/dist/lib/media/tenant-resolution.js +45 -0
- package/dist/lib/media/tenant-resolution.js.map +1 -0
- package/dist/lib/media/text-moderation.d.ts +28 -0
- package/dist/lib/media/text-moderation.d.ts.map +1 -0
- package/dist/lib/media/text-moderation.js +62 -0
- package/dist/lib/media/text-moderation.js.map +1 -0
- package/dist/lib/media/track-verdict.d.ts +45 -0
- package/dist/lib/media/track-verdict.d.ts.map +1 -0
- package/dist/lib/media/track-verdict.js +52 -0
- package/dist/lib/media/track-verdict.js.map +1 -0
- package/dist/lib/media/transcript-moderation.d.ts +47 -0
- package/dist/lib/media/transcript-moderation.d.ts.map +1 -0
- package/dist/lib/media/transcript-moderation.js +70 -0
- package/dist/lib/media/transcript-moderation.js.map +1 -0
- package/dist/lib/media-handler.d.ts.map +1 -1
- package/dist/lib/media-handler.js +15 -9
- package/dist/lib/media-handler.js.map +1 -1
- package/dist/lib/notification-handler.d.ts +11 -4
- package/dist/lib/notification-handler.d.ts.map +1 -1
- package/dist/lib/notification-handler.js +161 -29
- package/dist/lib/notification-handler.js.map +1 -1
- package/dist/lib/post-handler.d.ts.map +1 -1
- package/dist/lib/post-handler.js +4 -1
- package/dist/lib/post-handler.js.map +1 -1
- package/dist/lib/realtime/block-store.d.ts +61 -0
- package/dist/lib/realtime/block-store.d.ts.map +1 -0
- package/dist/lib/realtime/block-store.js +0 -0
- package/dist/lib/realtime/block-store.js.map +1 -0
- package/dist/lib/realtime/channel.d.ts +34 -0
- package/dist/lib/realtime/channel.d.ts.map +1 -0
- package/dist/lib/realtime/channel.js +100 -0
- package/dist/lib/realtime/channel.js.map +1 -0
- package/dist/lib/realtime/delivery-policy.d.ts +51 -0
- package/dist/lib/realtime/delivery-policy.d.ts.map +1 -0
- package/dist/lib/realtime/delivery-policy.js +98 -0
- package/dist/lib/realtime/delivery-policy.js.map +1 -0
- package/dist/lib/realtime/index.d.ts +21 -0
- package/dist/lib/realtime/index.d.ts.map +1 -0
- package/dist/lib/realtime/index.js +39 -0
- package/dist/lib/realtime/index.js.map +1 -0
- package/dist/lib/realtime/no-op-transport.d.ts +10 -0
- package/dist/lib/realtime/no-op-transport.d.ts.map +1 -0
- package/dist/lib/realtime/no-op-transport.js +44 -0
- package/dist/lib/realtime/no-op-transport.js.map +1 -0
- package/dist/lib/realtime/poll-transport.d.ts +11 -0
- package/dist/lib/realtime/poll-transport.d.ts.map +1 -0
- package/dist/lib/realtime/poll-transport.js +68 -0
- package/dist/lib/realtime/poll-transport.js.map +1 -0
- package/dist/lib/realtime/push-notifier.d.ts +39 -0
- package/dist/lib/realtime/push-notifier.d.ts.map +1 -0
- package/dist/lib/realtime/push-notifier.js +76 -0
- package/dist/lib/realtime/push-notifier.js.map +1 -0
- package/dist/lib/realtime/realtime-transport.d.ts +2 -0
- package/dist/lib/realtime/realtime-transport.d.ts.map +1 -0
- package/dist/lib/realtime/realtime-transport.js +23 -0
- package/dist/lib/realtime/realtime-transport.js.map +1 -0
- package/dist/lib/realtime/setting-store.d.ts +30 -0
- package/dist/lib/realtime/setting-store.d.ts.map +1 -0
- package/dist/lib/realtime/setting-store.js +0 -0
- package/dist/lib/realtime/setting-store.js.map +1 -0
- package/dist/lib/realtime/types.d.ts +200 -0
- package/dist/lib/realtime/types.d.ts.map +1 -0
- package/dist/lib/realtime/types.js +61 -0
- package/dist/lib/realtime/types.js.map +1 -0
- package/dist/lib/routes/index.d.ts.map +1 -1
- package/dist/lib/routes/index.js +3 -0
- package/dist/lib/routes/index.js.map +1 -1
- package/dist/lib/routes/media.d.ts +21 -0
- package/dist/lib/routes/media.d.ts.map +1 -1
- package/dist/lib/routes/media.js +584 -483
- package/dist/lib/routes/media.js.map +1 -1
- package/dist/lib/routes/settings.d.ts +17 -0
- package/dist/lib/routes/settings.d.ts.map +1 -0
- package/dist/lib/routes/settings.js +187 -0
- package/dist/lib/routes/settings.js.map +1 -0
- package/dist/lib/services/image-normalizer.d.ts +64 -6
- package/dist/lib/services/image-normalizer.d.ts.map +1 -1
- package/dist/lib/services/image-normalizer.js +88 -6
- package/dist/lib/services/image-normalizer.js.map +1 -1
- package/dist/lib/services/media-upload-service.d.ts +2 -2
- package/dist/lib/services/media-upload-service.d.ts.map +1 -1
- package/dist/lib/services/media-upload-service.js +22 -21
- package/dist/lib/services/media-upload-service.js.map +1 -1
- package/dist/lib/tenant-scope.d.ts.map +1 -1
- package/dist/lib/tenant-scope.js +18 -1
- package/dist/lib/tenant-scope.js.map +1 -1
- package/package.json +23 -22
- package/prisma/migrations/20260620051144_add_encrypted_user_settings/migration.sql +24 -0
- package/prisma/migrations/20260620120000_add_blocked_users/migration.sql +29 -0
- package/prisma/migrations/20260625000000_media_tenant_scope_and_moderation_status/migration.sql +49 -0
- package/prisma/migrations/20260625000001_p0b_moderation_jobs/migration.sql +73 -0
- package/prisma/schema.prisma +133 -15
- package/src/lambda/media-completion-worker.ts +567 -0
- package/src/lambda/media-processing-worker.ts +508 -59
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
-- P0b: moderation job tracking — schema additions.
|
|
2
|
+
--
|
|
3
|
+
-- Summary of changes:
|
|
4
|
+
-- ~ media_files.original_key: DROP NOT NULL (nullable for video pending transcode)
|
|
5
|
+
-- ~ media_files.content_hash: DROP NOT NULL (null until the worker hashes the
|
|
6
|
+
-- transcoded bytes; @@unique([tenant, content_hash]) tolerates many NULLs)
|
|
7
|
+
-- + media_files.upload_id (TEXT, UNIQUE, nullable) — client idempotency key
|
|
8
|
+
-- + CREATE TYPE moderation_track AS ENUM ('VISUAL', 'AUDIO')
|
|
9
|
+
-- + CREATE TABLE media_moderation_jobs — per-track job records with FK + indexes
|
|
10
|
+
-- + CREATE TABLE processed_moderation_messages — SQS exactly-once dedup table
|
|
11
|
+
--
|
|
12
|
+
-- Not deployed standalone: consumed by Trellis as an npm dependency; migration
|
|
13
|
+
-- is applied when Trellis bumps @de-otio/trellis and runs prisma migrate deploy.
|
|
14
|
+
|
|
15
|
+
-- AlterTable: make original_key nullable (video rows have no key until post-transcode)
|
|
16
|
+
ALTER TABLE "media_files"
|
|
17
|
+
ALTER COLUMN "original_key" DROP NOT NULL;
|
|
18
|
+
|
|
19
|
+
-- AlterTable: make content_hash nullable (video has no hash until the worker
|
|
20
|
+
-- hashes the transcoded bytes; the within-tenant unique tolerates many NULLs)
|
|
21
|
+
ALTER TABLE "media_files"
|
|
22
|
+
ALTER COLUMN "content_hash" DROP NOT NULL;
|
|
23
|
+
|
|
24
|
+
-- AlterTable: add upload_id for client-side idempotency
|
|
25
|
+
ALTER TABLE "media_files"
|
|
26
|
+
ADD COLUMN "upload_id" TEXT;
|
|
27
|
+
|
|
28
|
+
-- CreateIndex: unique constraint on upload_id (one row per upload session)
|
|
29
|
+
CREATE UNIQUE INDEX "media_files_upload_id_key" ON "media_files"("upload_id");
|
|
30
|
+
|
|
31
|
+
-- CreateEnum: moderation track discriminator
|
|
32
|
+
CREATE TYPE "ModerationTrack" AS ENUM ('VISUAL', 'AUDIO');
|
|
33
|
+
|
|
34
|
+
-- CreateTable: per-track moderation job records
|
|
35
|
+
CREATE TABLE "media_moderation_jobs" (
|
|
36
|
+
"id" TEXT NOT NULL,
|
|
37
|
+
"media_id" TEXT NOT NULL,
|
|
38
|
+
"track" "ModerationTrack" NOT NULL,
|
|
39
|
+
"job_id" TEXT NOT NULL,
|
|
40
|
+
"decision" TEXT,
|
|
41
|
+
"threshold_snapshot" JSONB NOT NULL,
|
|
42
|
+
"created_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
43
|
+
"updated_at" TIMESTAMP(3) NOT NULL,
|
|
44
|
+
|
|
45
|
+
CONSTRAINT "media_moderation_jobs_pkey" PRIMARY KEY ("id")
|
|
46
|
+
);
|
|
47
|
+
|
|
48
|
+
-- CreateIndex: unique constraint on job_id (one row per provider job)
|
|
49
|
+
CREATE UNIQUE INDEX "media_moderation_jobs_job_id_key" ON "media_moderation_jobs"("job_id");
|
|
50
|
+
|
|
51
|
+
-- CreateIndex: lookup jobs by media (used by the pipeline to find in-flight jobs)
|
|
52
|
+
CREATE INDEX "media_moderation_jobs_media_id_idx" ON "media_moderation_jobs"("media_id");
|
|
53
|
+
|
|
54
|
+
-- CreateIndex: lookup by job_id (used by the result-callback path)
|
|
55
|
+
CREATE INDEX "media_moderation_jobs_job_id_idx" ON "media_moderation_jobs"("job_id");
|
|
56
|
+
|
|
57
|
+
-- AddForeignKey: media_moderation_jobs -> media_files (cascade on delete)
|
|
58
|
+
ALTER TABLE "media_moderation_jobs"
|
|
59
|
+
ADD CONSTRAINT "media_moderation_jobs_media_id_fkey"
|
|
60
|
+
FOREIGN KEY ("media_id") REFERENCES "media_files"("id") ON DELETE CASCADE ON UPDATE CASCADE;
|
|
61
|
+
|
|
62
|
+
-- CreateTable: SQS exactly-once dedup for moderation result messages
|
|
63
|
+
CREATE TABLE "processed_moderation_messages" (
|
|
64
|
+
"id" TEXT NOT NULL,
|
|
65
|
+
"message_dedupe_key" TEXT NOT NULL,
|
|
66
|
+
"processed_at" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
|
67
|
+
|
|
68
|
+
CONSTRAINT "processed_moderation_messages_pkey" PRIMARY KEY ("id")
|
|
69
|
+
);
|
|
70
|
+
|
|
71
|
+
-- CreateIndex: unique constraint on dedupe key (collision = duplicate message)
|
|
72
|
+
CREATE UNIQUE INDEX "processed_moderation_messages_message_dedupe_key_key"
|
|
73
|
+
ON "processed_moderation_messages"("message_dedupe_key");
|
package/prisma/schema.prisma
CHANGED
|
@@ -66,6 +66,18 @@ enum EntityStatus {
|
|
|
66
66
|
TRANSFERRED
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
+
// Media moderation lifecycle state. Mirrors the hand-written ModerationStatus
|
|
70
|
+
// union in apps/api/src/lib/media/moderation-status.ts — spelling must match.
|
|
71
|
+
// Every new MediaFile is born PENDING (fail-closed); only the moderation
|
|
72
|
+
// pipeline can advance to APPROVED.
|
|
73
|
+
enum ModerationStatus {
|
|
74
|
+
PENDING
|
|
75
|
+
APPROVED
|
|
76
|
+
REVIEW
|
|
77
|
+
QUARANTINED
|
|
78
|
+
REJECTED
|
|
79
|
+
}
|
|
80
|
+
|
|
69
81
|
model PostGeoIndex {
|
|
70
82
|
postUri String @id @map("post_uri")
|
|
71
83
|
// Multi-tenancy — denormalized from the owning post; location data is
|
|
@@ -180,7 +192,7 @@ model User {
|
|
|
180
192
|
// migration. (WebFinger/AP still resolve on `username` until the federation
|
|
181
193
|
// repoint — that change is code-only, no data migration, since `handle` is the
|
|
182
194
|
// stable key.)
|
|
183
|
-
handle String
|
|
195
|
+
handle String @unique
|
|
184
196
|
createdAt DateTime @default(now()) @map("created_at")
|
|
185
197
|
|
|
186
198
|
// Cognito Auth integration
|
|
@@ -360,6 +372,11 @@ model User {
|
|
|
360
372
|
notifications Notification[]
|
|
361
373
|
notificationPreference NotificationPreference?
|
|
362
374
|
|
|
375
|
+
// Realtime / server-blind settings sync
|
|
376
|
+
encryptedSettings EncryptedUserSetting[]
|
|
377
|
+
blocksInitiated BlockedUser[] @relation("BlockedUserBlocker")
|
|
378
|
+
blockedBy BlockedUser[] @relation("BlockedUserBlocked")
|
|
379
|
+
|
|
363
380
|
// Connection codes (out-of-band invite mechanism)
|
|
364
381
|
createdConnectionCodes ConnectionCode[] @relation("ConnectionCodeCreator")
|
|
365
382
|
redeemedConnectionCodes ConnectionCodeRedemption[]
|
|
@@ -600,16 +617,32 @@ model Post {
|
|
|
600
617
|
@@map("posts")
|
|
601
618
|
}
|
|
602
619
|
|
|
603
|
-
// Media files (content-addressed storage)
|
|
620
|
+
// Media files (tenant-scoped content-addressed storage).
|
|
621
|
+
// tenantId is NOT NULL — every object belongs to exactly one tenant.
|
|
622
|
+
// Dedup is within-tenant: @@unique([tenantId, contentHash]).
|
|
623
|
+
// Every new object is born PENDING (fail-closed); the moderation pipeline
|
|
624
|
+
// advances objects to APPROVED before they can be served.
|
|
604
625
|
model MediaFile {
|
|
605
626
|
id String @id @default(cuid())
|
|
606
|
-
|
|
627
|
+
tenantId String @map("tenant_id") // Tenant that owns this object (NOT NULL)
|
|
628
|
+
contentHash String? @map("content_hash") // SHA-256 hash (lowercase hex); null until post-transcode for video (set by the P0b worker)
|
|
607
629
|
cid String? @unique // AT Protocol CID (optional, for compatibility)
|
|
608
630
|
mimeType String @map("mime_type")
|
|
609
631
|
size Int
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
632
|
+
// uploadId: client-side idempotency key for the upload session. Nullable
|
|
633
|
+
// because rows created before P0b (or via the reconciliation path) have no
|
|
634
|
+
// upload session. UNIQUE so the same upload cannot race into two rows.
|
|
635
|
+
uploadId String? @unique @map("upload_id")
|
|
636
|
+
// originalKey: S3 CAS key for the raw ingested object. Nullable for video —
|
|
637
|
+
// the CAS key is computed post-transcode by the pipeline worker; the row
|
|
638
|
+
// exists before transcoding completes.
|
|
639
|
+
originalKey String? @map("original_key") // S3 key: cas/{tenantId}/{hash} (null until transcode for video)
|
|
640
|
+
thumbnailKey String? @map("thumbnail_key") // S3 key: cas/{tenantId}/{hash}/thumb
|
|
641
|
+
optimizedKey String? @map("optimized_key") // S3 key: cas/{tenantId}/{hash}/opt
|
|
642
|
+
|
|
643
|
+
// Moderation state — the lifecycle gate (see ModerationStatus enum above).
|
|
644
|
+
// Default PENDING means every object is fail-closed until approved.
|
|
645
|
+
moderationStatus ModerationStatus @default(PENDING) @map("moderation_status")
|
|
613
646
|
|
|
614
647
|
// Media metadata (extracted from file headers)
|
|
615
648
|
width Int? // Image/video width in pixels
|
|
@@ -622,13 +655,13 @@ model MediaFile {
|
|
|
622
655
|
videoMetadata Json? @map("video_metadata")
|
|
623
656
|
|
|
624
657
|
// Denormalized/unified metadata fields
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
keywords String[] @default([]) @map("keywords")
|
|
658
|
+
// GPS coordinates are NOT stored — dropped at ingestion (data-minimization).
|
|
659
|
+
dateTaken DateTime? @map("date_taken")
|
|
660
|
+
keywords String[] @default([]) @map("keywords")
|
|
629
661
|
|
|
630
|
-
// Privacy flags
|
|
631
|
-
|
|
662
|
+
// Privacy flags. metadataVisible defaults false: metadata is private unless
|
|
663
|
+
// the owner explicitly shares it (data-minimization default).
|
|
664
|
+
metadataVisible Boolean @default(false) @map("metadata_visible")
|
|
632
665
|
locationVisible Boolean @default(false) @map("location_visible")
|
|
633
666
|
|
|
634
667
|
// Media visibility and deletion tracking
|
|
@@ -654,16 +687,18 @@ model MediaFile {
|
|
|
654
687
|
updatedAt DateTime @updatedAt @map("updated_at")
|
|
655
688
|
|
|
656
689
|
// Relations
|
|
657
|
-
posts
|
|
690
|
+
posts PostMedia[] // Many-to-many with posts
|
|
691
|
+
moderationJobs MediaModerationJob[] // P0b: per-track moderation job records
|
|
658
692
|
|
|
659
|
-
@@
|
|
693
|
+
@@unique([tenantId, contentHash]) // Within-tenant dedup (replaces bare contentHash @unique)
|
|
660
694
|
@@index([cid])
|
|
695
|
+
@@index([moderationStatus]) // Gate queries: find PENDING for the moderation queue
|
|
696
|
+
@@index([tenantId, contentHash]) // Serve path: tenant-scoped CAS lookup
|
|
661
697
|
@@index([hidden, deletedAt]) // For filtering visible media
|
|
662
698
|
@@index([createdAt]) // For chronological sorting
|
|
663
699
|
@@index([dateTaken])
|
|
664
700
|
@@index([metadataVisible])
|
|
665
701
|
@@index([locationVisible])
|
|
666
|
-
@@index([gpsLatitude, gpsLongitude])
|
|
667
702
|
@@index([uploadStatus])
|
|
668
703
|
@@index([uploadedBy])
|
|
669
704
|
@@index([uploadBatchId])
|
|
@@ -688,6 +723,54 @@ model UploadSession {
|
|
|
688
723
|
@@map("upload_sessions")
|
|
689
724
|
}
|
|
690
725
|
|
|
726
|
+
// ============================================================================
|
|
727
|
+
// P0b — Moderation job tracking
|
|
728
|
+
// ============================================================================
|
|
729
|
+
|
|
730
|
+
// Discriminator for the two parallel moderation tracks.
|
|
731
|
+
// VISUAL — frame/thumbnail-level visual content analysis.
|
|
732
|
+
// AUDIO — speech-to-text + audio content analysis.
|
|
733
|
+
enum ModerationTrack {
|
|
734
|
+
VISUAL
|
|
735
|
+
AUDIO
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
// One row per (media, track) moderation job submitted to an external provider.
|
|
739
|
+
// decision is String (not an enum) so the schema stays decoupled from
|
|
740
|
+
// ModerationDecision evolution; app code validates the value on read.
|
|
741
|
+
// thresholdSnapshot captures the operative thresholds at job-submission time
|
|
742
|
+
// so historical decisions remain auditable after a threshold change.
|
|
743
|
+
model MediaModerationJob {
|
|
744
|
+
id String @id @default(cuid())
|
|
745
|
+
mediaId String @map("media_id")
|
|
746
|
+
media MediaFile @relation(fields: [mediaId], references: [id], onDelete: Cascade)
|
|
747
|
+
track ModerationTrack
|
|
748
|
+
// jobId: the external provider's job identifier (unique per provider call).
|
|
749
|
+
jobId String @unique @map("job_id")
|
|
750
|
+
// decision: resolved ModerationDecision value, or null while the job is in flight.
|
|
751
|
+
decision String?
|
|
752
|
+
// thresholdSnapshot: a copy of env.media.thresholds at submission time (JSON).
|
|
753
|
+
thresholdSnapshot Json @map("threshold_snapshot")
|
|
754
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
755
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
756
|
+
|
|
757
|
+
@@index([mediaId])
|
|
758
|
+
@@index([jobId])
|
|
759
|
+
@@map("media_moderation_jobs")
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
// Deduplication table for SQS moderation-result messages (exactly-once delivery).
|
|
763
|
+
// A row is inserted (with ON CONFLICT DO NOTHING / unique-constraint) before
|
|
764
|
+
// processing; duplicate deliveries of the same message find the row present and
|
|
765
|
+
// are skipped. Rows may be pruned after a retention window by a cron worker.
|
|
766
|
+
model ProcessedModerationMessage {
|
|
767
|
+
id String @id @default(cuid())
|
|
768
|
+
messageDedupeKey String @unique @map("message_dedupe_key")
|
|
769
|
+
processedAt DateTime @default(now()) @map("processed_at")
|
|
770
|
+
|
|
771
|
+
@@map("processed_moderation_messages")
|
|
772
|
+
}
|
|
773
|
+
|
|
691
774
|
// Post media
|
|
692
775
|
model PostMedia {
|
|
693
776
|
id String @id @default(cuid())
|
|
@@ -1748,6 +1831,7 @@ model Tenant {
|
|
|
1748
1831
|
connectionCodes ConnectionCode[]
|
|
1749
1832
|
connectionCodeRedemptions ConnectionCodeRedemption[]
|
|
1750
1833
|
notifications Notification[]
|
|
1834
|
+
blockedUsers BlockedUser[]
|
|
1751
1835
|
|
|
1752
1836
|
// Denormalized tenant_id on by-relation children (P1 — for direct RLS).
|
|
1753
1837
|
postMedia PostMedia[]
|
|
@@ -2013,3 +2097,37 @@ enum EntityRelationshipStatus {
|
|
|
2013
2097
|
CONFIRMED
|
|
2014
2098
|
REJECTED
|
|
2015
2099
|
}
|
|
2100
|
+
|
|
2101
|
+
// Realtime / server-blind settings sync — opaque AEAD blob the server cannot read.
|
|
2102
|
+
model EncryptedUserSetting {
|
|
2103
|
+
id String @id @default(cuid())
|
|
2104
|
+
userId String @map("user_id")
|
|
2105
|
+
namespace String // allowlisted, e.g. "feed_filters"
|
|
2106
|
+
ciphertext String @db.Text // opaque AEAD blob; the server cannot read it
|
|
2107
|
+
version Int @default(1) // monotonic per (user, namespace)
|
|
2108
|
+
updatedAt DateTime @updatedAt @map("updated_at")
|
|
2109
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
2110
|
+
|
|
2111
|
+
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
|
|
2112
|
+
|
|
2113
|
+
@@unique([userId, namespace])
|
|
2114
|
+
@@index([userId])
|
|
2115
|
+
@@map("encrypted_user_settings")
|
|
2116
|
+
}
|
|
2117
|
+
|
|
2118
|
+
// Realtime delivery safety floor — blocked-sender relation (who blocks whom).
|
|
2119
|
+
model BlockedUser {
|
|
2120
|
+
id String @id @default(cuid())
|
|
2121
|
+
tenantId String @map("tenant_id")
|
|
2122
|
+
blockerId String @map("blocker_id")
|
|
2123
|
+
blockedId String @map("blocked_id")
|
|
2124
|
+
createdAt DateTime @default(now()) @map("created_at")
|
|
2125
|
+
|
|
2126
|
+
tenant Tenant @relation(fields: [tenantId], references: [id], onDelete: Cascade)
|
|
2127
|
+
blocker User @relation("BlockedUserBlocker", fields: [blockerId], references: [id], onDelete: Cascade)
|
|
2128
|
+
blocked User @relation("BlockedUserBlocked", fields: [blockedId], references: [id], onDelete: Cascade)
|
|
2129
|
+
|
|
2130
|
+
@@unique([tenantId, blockerId, blockedId])
|
|
2131
|
+
@@index([tenantId, blockerId])
|
|
2132
|
+
@@map("blocked_users")
|
|
2133
|
+
}
|