feat: Datenmodell Sprachnotizen (T7 L2 Task 4)

VoiceCapture und VoiceCaptureAudio nach Spec §3 mit CHECKs
(audioBytes 1..2 MiB, durationSec 1..240, CONFIRMED genau mit
inspectionId, audioMime nur bei AUDIO), Recht voice-captures:use für
ADMIN, BEEKEEPER und STAFF, Sperrreihenfolge mit VoiceCapture am Ende,
Ausschluss aus dem Backup mit Begründung, Wächter gegen Suche und
Export.

Refs #27

Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
This commit is contained in:
Nick SzaunigandClaude Opus 5.5 committed 2026-10-16 09:20:04 +02:00
1 parent 257286fbef
commit af9df0afe4
10 files changed
+412

No files matched your search

@@ -0,0 +1,96 @@
-- CreateEnum
CREATE TYPE "VoiceCaptureSource" AS ENUM ('AUDIO', 'TEXT');
-- CreateEnum
CREATE TYPE "VoiceCaptureStatus" AS ENUM ('QUEUED', 'TRANSCRIBING', 'TRANSCRIBED', 'EXTRACTING', 'READY', 'CONFIRMED', 'DISCARDED', 'FAILED', 'EXPIRED');
-- CreateTable
CREATE TABLE "VoiceCapture" (
"id" TEXT NOT NULL,
"ownerUserId" TEXT NOT NULL,
"colonyId" TEXT NOT NULL,
"inspectionDate" DATE NOT NULL,
"source" "VoiceCaptureSource" NOT NULL,
"status" "VoiceCaptureStatus" NOT NULL DEFAULT 'QUEUED',
"audioMime" TEXT,
"audioBytes" INTEGER,
"durationSec" INTEGER,
"transcriptEnc" TEXT,
"suggestionEnc" TEXT,
"attempts" INTEGER NOT NULL DEFAULT 0,
"availableAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"claimedAt" TIMESTAMP(3),
"claimToken" TEXT,
"lastErrorCode" TEXT,
"model" TEXT,
"inputTokens" INTEGER,
"outputTokens" INTEGER,
"inspectionId" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"readyAt" TIMESTAMP(3),
"contentExpiresAt" TIMESTAMP(3) NOT NULL,
"contentDeletedAt" TIMESTAMP(3),
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "VoiceCapture_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "VoiceCaptureAudio" (
"captureId" TEXT NOT NULL,
"dataEnc" BYTEA NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "VoiceCaptureAudio_pkey" PRIMARY KEY ("captureId")
);
-- CreateIndex
CREATE INDEX "VoiceCapture_status_availableAt_idx" ON "VoiceCapture"("status", "availableAt");
-- CreateIndex
CREATE INDEX "VoiceCapture_ownerUserId_status_idx" ON "VoiceCapture"("ownerUserId", "status");
-- CreateIndex
CREATE INDEX "VoiceCapture_contentExpiresAt_idx" ON "VoiceCapture"("contentExpiresAt");
-- AddForeignKey
ALTER TABLE "VoiceCapture" ADD CONSTRAINT "VoiceCapture_ownerUserId_fkey" FOREIGN KEY ("ownerUserId") REFERENCES "User"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VoiceCapture" ADD CONSTRAINT "VoiceCapture_colonyId_fkey" FOREIGN KEY ("colonyId") REFERENCES "Colony"("id") ON DELETE RESTRICT ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "VoiceCaptureAudio" ADD CONSTRAINT "VoiceCaptureAudio_captureId_fkey" FOREIGN KEY ("captureId") REFERENCES "VoiceCapture"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- Constraints Prisma cannot express.
ALTER TABLE "VoiceCapture"
ADD CONSTRAINT "VoiceCapture_audioBytes_range"
CHECK ("audioBytes" IS NULL OR "audioBytes" BETWEEN 1 AND 2097152);
ALTER TABLE "VoiceCapture"
ADD CONSTRAINT "VoiceCapture_durationSec_range"
CHECK ("durationSec" IS NULL OR "durationSec" BETWEEN 1 AND 240);
ALTER TABLE "VoiceCapture"
ADD CONSTRAINT "VoiceCapture_confirmed_matches_inspection"
CHECK (("status" = 'CONFIRMED') = ("inspectionId" IS NOT NULL));
ALTER TABLE "VoiceCapture"
ADD CONSTRAINT "VoiceCapture_audioMime_only_for_audio"
CHECK ("source" = 'AUDIO' OR "audioMime" IS NULL);
-- Permission: the boot-time catalog sync only grants new permissions to
-- system roles (ADMIN), so the grants are made here (same as T6). READONLY
-- and API_CLIENT get nothing: voice notes are for people who inspect.
INSERT INTO "Permission" (id, key, resource, action, "labelDe", "groupDe", "sortOrder")
VALUES
(gen_random_uuid()::text, 'voice-captures:use', 'voice-captures', 'use', 'Benutzen', 'Sprache', 4000)
ON CONFLICT (key) DO NOTHING;
INSERT INTO "RolePermission" ("roleId", "permissionId", "grantedAt")
SELECT r.id, p.id, now()
FROM "Role" r, "Permission" p
WHERE p.key = 'voice-captures:use'
AND r.key IN ('ADMIN', 'BEEKEEPER', 'STAFF')
ON CONFLICT DO NOTHING;