Files
CC-Manager/server/prisma/migrations/20250101000000_init/migration.sql
T
Harlekin c242e73c38 GExtension-Abgleich mit Handpruefung fuer unklare Pakete
Die Profilseiten sind ohne Login abrufbar und betten die Kaeufe als JSON ins
HTML ein. Gelesen wird dieses JSON statt der gerenderten Tabelle: das ist
unempfindlich gegen Layout-Aenderungen und liefert zusaetzlich Kaufdatum,
Betrag und den Storno-Status. Ein Headless-Browser ist nicht noetig.

Der Abgleich bucht nichts von sich aus. Eindeutige Treffer landen als
regulaerer Import in der bestehenden Vorschau, die per package_bought_id
dedupliziert. Titel, die nach CC klingen, sich aber keiner Guthaben-Art
sicher zuordnen lassen, gehen in eine Handpruefung statt geraten zu werden -
sichtbar als Hinweis auf der Uebersicht und als Box beim Spieler.

Beachtet werden dabei: stornierte Kaeufe werden uebersprungen, der Web-
Bereich enthaelt nie CC-Pakete, und ein Redesign-Titel zaehlt als Redesign,
auch wenn Custom Character darin vorkommt. Letzteres war auch in den
Standard-Mappings des Datei-Imports falsch herum einsortiert.

Der Lauf geht bewusst nur ueber bereits erfasste Spieler und haelt eine
Sekunde Abstand zwischen den Anfragen, da die Seite fremde Infrastruktur ist.

Voraussetzung dafuer ist, dass SteamIDs auf eine Schreibweise gebracht
werden: die Legacy-Form, die Steam3-Form und die 64-Bit-Zahl bezeichnen
dieselbe Person, wurden bisher aber als verschiedene Spieler gefuehrt.
Gespeichert wird jetzt immer die 64-Bit-Form, Eingabe und Suche akzeptieren
alle drei.
2026-09-18 15:42:31 +02:00

321 lines
10 KiB
SQL

-- CreateSchema
CREATE SCHEMA IF NOT EXISTS "public";
-- CreateEnum
CREATE TYPE "Role" AS ENUM ('ADMIN', 'EDITOR', 'VIEWER');
-- CreateEnum
CREATE TYPE "CreditKind" AS ENUM ('CC', 'REDESIGN_PAID', 'REDESIGN_KULANZ');
-- CreateEnum
CREATE TYPE "CreditSource" AS ENUM ('CC_PACKAGE', 'REDESIGN_PACKAGE', 'KULANZ', 'ADJUSTMENT', 'CONSUME', 'REVERSAL');
-- CreateEnum
CREATE TYPE "CharacterStatus" AS ENUM ('ACTIVE', 'INACTIVE');
-- CreateEnum
CREATE TYPE "InactiveReason" AS ENUM ('PLAYER_INACTIVITY', 'REWORKED', 'OTHER');
-- CreateEnum
CREATE TYPE "ProductTarget" AS ENUM ('CC_PACKAGE', 'REDESIGN_PACKAGE', 'IGNORE');
-- CreateEnum
CREATE TYPE "ImportStatus" AS ENUM ('PENDING', 'COMMITTED', 'DISCARDED');
-- CreateTable
CREATE TABLE "User" (
"id" SERIAL NOT NULL,
"username" TEXT NOT NULL,
"passwordHash" TEXT NOT NULL,
"role" "Role" NOT NULL DEFAULT 'EDITOR',
"totpSecret" TEXT,
"totpEnabled" BOOLEAN NOT NULL DEFAULT false,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"mustChangePassword" BOOLEAN NOT NULL DEFAULT false,
"lastLoginAt" TIMESTAMP(3),
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Session" (
"id" TEXT NOT NULL,
"tokenHash" TEXT NOT NULL,
"userId" INTEGER NOT NULL,
"expiresAt" TIMESTAMP(3) NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"lastSeenAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"ip" TEXT,
"userAgent" TEXT,
CONSTRAINT "Session_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Player" (
"id" SERIAL NOT NULL,
"displayName" TEXT NOT NULL,
"steamId" TEXT,
"forumName" TEXT,
"notes" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"gextensionUsage" INTEGER,
"gextensionSyncedAt" TIMESTAMP(3),
CONSTRAINT "Player_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "UnmatchedPackage" (
"id" SERIAL NOT NULL,
"externalId" TEXT NOT NULL,
"playerId" INTEGER,
"steamId" TEXT NOT NULL,
"rawTitle" TEXT NOT NULL,
"rawTag" TEXT NOT NULL,
"amount" TEXT,
"purchasedAt" TIMESTAMP(3),
"scrapedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"resolved" BOOLEAN NOT NULL DEFAULT false,
"resolvedAs" TEXT,
"resolvedById" INTEGER,
"resolvedAt" TIMESTAMP(3),
CONSTRAINT "UnmatchedPackage_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "CreditLedger" (
"id" SERIAL NOT NULL,
"playerId" INTEGER NOT NULL,
"kind" "CreditKind" NOT NULL,
"amount" INTEGER NOT NULL,
"source" "CreditSource" NOT NULL,
"groupId" TEXT NOT NULL,
"characterId" INTEGER,
"importId" INTEGER,
"note" TEXT,
"reversalOfId" INTEGER,
"createdById" INTEGER,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "CreditLedger_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Character" (
"id" SERIAL NOT NULL,
"playerId" INTEGER NOT NULL,
"name" TEXT NOT NULL,
"loreText" TEXT,
"status" "CharacterStatus" NOT NULL DEFAULT 'ACTIVE',
"inactiveReason" "InactiveReason",
"inactiveNote" TEXT,
"lineageId" TEXT NOT NULL,
"predecessorId" INTEGER,
"createdById" INTEGER,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "Character_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Import" (
"id" SERIAL NOT NULL,
"filename" TEXT NOT NULL,
"status" "ImportStatus" NOT NULL DEFAULT 'PENDING',
"rowCount" INTEGER NOT NULL DEFAULT 0,
"bookedCount" INTEGER NOT NULL DEFAULT 0,
"mapping" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"committedAt" TIMESTAMP(3),
"importedById" INTEGER,
CONSTRAINT "Import_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ImportRow" (
"id" SERIAL NOT NULL,
"importId" INTEGER NOT NULL,
"lineNo" INTEGER NOT NULL,
"externalId" TEXT,
"playerKey" TEXT,
"playerName" TEXT,
"productName" TEXT,
"purchasedAt" TIMESTAMP(3),
"target" "ProductTarget",
"resolvedPlayerId" INTEGER,
"plan" TEXT NOT NULL,
"raw" JSONB NOT NULL,
CONSTRAINT "ImportRow_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ImportedOrder" (
"id" SERIAL NOT NULL,
"externalId" TEXT NOT NULL,
"importId" INTEGER NOT NULL,
"playerId" INTEGER NOT NULL,
"target" "ProductTarget" NOT NULL,
"groupId" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "ImportedOrder_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ProductMapping" (
"id" SERIAL NOT NULL,
"matchPattern" TEXT NOT NULL,
"target" "ProductTarget" NOT NULL,
"priority" INTEGER NOT NULL DEFAULT 100,
"isActive" BOOLEAN NOT NULL DEFAULT true,
"note" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
CONSTRAINT "ProductMapping_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "AuditLog" (
"id" SERIAL NOT NULL,
"actorUserId" INTEGER,
"actorName" TEXT,
"action" TEXT NOT NULL,
"targetType" TEXT NOT NULL,
"targetId" TEXT,
"details" JSONB,
"ip" TEXT,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_username_key" ON "User"("username");
-- CreateIndex
CREATE UNIQUE INDEX "Session_tokenHash_key" ON "Session"("tokenHash");
-- CreateIndex
CREATE INDEX "Session_userId_idx" ON "Session"("userId");
-- CreateIndex
CREATE INDEX "Session_expiresAt_idx" ON "Session"("expiresAt");
-- CreateIndex
CREATE UNIQUE INDEX "Player_steamId_key" ON "Player"("steamId");
-- CreateIndex
CREATE INDEX "Player_displayName_idx" ON "Player"("displayName");
-- CreateIndex
CREATE UNIQUE INDEX "UnmatchedPackage_externalId_key" ON "UnmatchedPackage"("externalId");
-- CreateIndex
CREATE INDEX "UnmatchedPackage_resolved_idx" ON "UnmatchedPackage"("resolved");
-- CreateIndex
CREATE INDEX "UnmatchedPackage_playerId_idx" ON "UnmatchedPackage"("playerId");
-- CreateIndex
CREATE UNIQUE INDEX "CreditLedger_reversalOfId_key" ON "CreditLedger"("reversalOfId");
-- CreateIndex
CREATE INDEX "CreditLedger_playerId_idx" ON "CreditLedger"("playerId");
-- CreateIndex
CREATE INDEX "CreditLedger_playerId_kind_idx" ON "CreditLedger"("playerId", "kind");
-- CreateIndex
CREATE INDEX "CreditLedger_groupId_idx" ON "CreditLedger"("groupId");
-- CreateIndex
CREATE INDEX "CreditLedger_createdAt_idx" ON "CreditLedger"("createdAt");
-- CreateIndex
CREATE INDEX "Character_playerId_idx" ON "Character"("playerId");
-- CreateIndex
CREATE INDEX "Character_playerId_status_idx" ON "Character"("playerId", "status");
-- CreateIndex
CREATE INDEX "Character_lineageId_idx" ON "Character"("lineageId");
-- CreateIndex
CREATE INDEX "Import_status_idx" ON "Import"("status");
-- CreateIndex
CREATE INDEX "ImportRow_importId_idx" ON "ImportRow"("importId");
-- CreateIndex
CREATE UNIQUE INDEX "ImportedOrder_externalId_key" ON "ImportedOrder"("externalId");
-- CreateIndex
CREATE INDEX "ImportedOrder_playerId_idx" ON "ImportedOrder"("playerId");
-- CreateIndex
CREATE INDEX "ProductMapping_priority_idx" ON "ProductMapping"("priority");
-- CreateIndex
CREATE INDEX "AuditLog_createdAt_idx" ON "AuditLog"("createdAt");
-- CreateIndex
CREATE INDEX "AuditLog_targetType_targetId_idx" ON "AuditLog"("targetType", "targetId");
-- CreateIndex
CREATE INDEX "AuditLog_actorUserId_idx" ON "AuditLog"("actorUserId");
-- AddForeignKey
ALTER TABLE "Session" ADD CONSTRAINT "Session_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "UnmatchedPackage" ADD CONSTRAINT "UnmatchedPackage_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "Player"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CreditLedger" ADD CONSTRAINT "CreditLedger_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "Player"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CreditLedger" ADD CONSTRAINT "CreditLedger_characterId_fkey" FOREIGN KEY ("characterId") REFERENCES "Character"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CreditLedger" ADD CONSTRAINT "CreditLedger_importId_fkey" FOREIGN KEY ("importId") REFERENCES "Import"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "CreditLedger" ADD CONSTRAINT "CreditLedger_reversalOfId_fkey" FOREIGN KEY ("reversalOfId") REFERENCES "CreditLedger"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "CreditLedger" ADD CONSTRAINT "CreditLedger_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Character" ADD CONSTRAINT "Character_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "Player"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Character" ADD CONSTRAINT "Character_predecessorId_fkey" FOREIGN KEY ("predecessorId") REFERENCES "Character"("id") ON DELETE NO ACTION ON UPDATE NO ACTION;
-- AddForeignKey
ALTER TABLE "Character" ADD CONSTRAINT "Character_createdById_fkey" FOREIGN KEY ("createdById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Import" ADD CONSTRAINT "Import_importedById_fkey" FOREIGN KEY ("importedById") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ImportRow" ADD CONSTRAINT "ImportRow_importId_fkey" FOREIGN KEY ("importId") REFERENCES "Import"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ImportedOrder" ADD CONSTRAINT "ImportedOrder_importId_fkey" FOREIGN KEY ("importId") REFERENCES "Import"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "ImportedOrder" ADD CONSTRAINT "ImportedOrder_playerId_fkey" FOREIGN KEY ("playerId") REFERENCES "Player"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "AuditLog" ADD CONSTRAINT "AuditLog_actorUserId_fkey" FOREIGN KEY ("actorUserId") REFERENCES "User"("id") ON DELETE SET NULL ON UPDATE CASCADE;