This commit is contained in:
Felix Faerber committed 2026-05-20 15:08:45 +03:00
commit c54e888458
67 files changed
+5933

No files matched your search

@@ -0,0 +1,201 @@
-- CreateEnum
CREATE TYPE "UserRole" AS ENUM ('USER', 'ADMIN');
-- CreateEnum
CREATE TYPE "AuthProvider" AS ENUM ('google', 'email');
-- CreateEnum
CREATE TYPE "AccountStatus" AS ENUM ('active', 'disabled');
-- CreateEnum
CREATE TYPE "LedgerKind" AS ENUM ('welcomeGrant', 'settle', 'refund', 'topup', 'adjustment');
-- CreateEnum
CREATE TYPE "GatewayJobStatus" AS ENUM ('posted', 'acked', 'delivered', 'claimed', 'cancelled', 'timed_out', 'failed');
-- CreateEnum
CREATE TYPE "TopupStatus" AS ENUM ('requires_payment', 'processing', 'succeeded', 'credited', 'failed', 'refunded');
-- CreateTable
CREATE TABLE "User" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"email" TEXT NOT NULL,
"emailVerified" BOOLEAN NOT NULL DEFAULT false,
"verificationToken" TEXT,
"authProvider" "AuthProvider" NOT NULL,
"googleSub" TEXT,
"passwordHash" TEXT,
"role" "UserRole" NOT NULL DEFAULT 'USER',
"stripeCustomerId" TEXT,
"country" TEXT,
CONSTRAINT "User_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "ApiKey" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"key" TEXT NOT NULL,
"name" TEXT NOT NULL DEFAULT '',
"lastUsedAt" TIMESTAMP(3),
"scopeMaxXbzzPerDay" DECIMAL(78,0),
"modelAllowlist" TEXT[] DEFAULT ARRAY[]::TEXT[],
"userId" TEXT NOT NULL,
CONSTRAINT "ApiKey_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Account" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"status" "AccountStatus" NOT NULL DEFAULT 'active',
"balanceXbzzWei" DECIMAL(78,0) NOT NULL DEFAULT 0,
"reservedXbzzWei" DECIMAL(78,0) NOT NULL DEFAULT 0,
"userId" TEXT NOT NULL,
CONSTRAINT "Account_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "LedgerEntry" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"kind" "LedgerKind" NOT NULL,
"deltaXbzzWei" DECIMAL(78,0) NOT NULL,
"idempotencyKey" TEXT NOT NULL,
"note" TEXT,
"accountId" TEXT NOT NULL,
"gatewayJobId" TEXT,
"topupId" TEXT,
CONSTRAINT "LedgerEntry_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "GatewayJob" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"routingId" TEXT NOT NULL,
"onChainJobId" TEXT,
"requestHash" TEXT,
"responseHash" TEXT,
"provider" TEXT NOT NULL,
"modelId" TEXT NOT NULL,
"estimatedMaxXbzzWei" DECIMAL(78,0) NOT NULL,
"actualXbzzWei" DECIMAL(78,0),
"status" "GatewayJobStatus" NOT NULL DEFAULT 'posted',
"postedAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"ackedAt" TIMESTAMP(3),
"deliveredAt" TIMESTAMP(3),
"claimedAt" TIMESTAMP(3),
"ackDeadline" TIMESTAMP(3),
"deliveryDeadline" TIMESTAMP(3),
"promptTokens" INTEGER,
"completionTokens" INTEGER,
"promptRedacted" TEXT,
"responseRedacted" TEXT,
"errorMessage" TEXT,
"userId" TEXT NOT NULL,
CONSTRAINT "GatewayJob_pkey" PRIMARY KEY ("id")
);
-- CreateTable
CREATE TABLE "Topup" (
"id" TEXT NOT NULL,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,
"updatedAt" TIMESTAMP(3) NOT NULL,
"status" "TopupStatus" NOT NULL DEFAULT 'requires_payment',
"stripePaymentIntent" TEXT NOT NULL,
"amountUsdCents" INTEGER NOT NULL,
"creditedXbzzWei" DECIMAL(78,0),
"usdPerXbzzAtCredit" DECIMAL(18,8),
"error" TEXT,
"userId" TEXT NOT NULL,
CONSTRAINT "Topup_pkey" PRIMARY KEY ("id")
);
-- CreateIndex
CREATE UNIQUE INDEX "User_email_key" ON "User"("email");
-- CreateIndex
CREATE UNIQUE INDEX "User_verificationToken_key" ON "User"("verificationToken");
-- CreateIndex
CREATE UNIQUE INDEX "User_googleSub_key" ON "User"("googleSub");
-- CreateIndex
CREATE UNIQUE INDEX "User_stripeCustomerId_key" ON "User"("stripeCustomerId");
-- CreateIndex
CREATE INDEX "User_authProvider_googleSub_idx" ON "User"("authProvider", "googleSub");
-- CreateIndex
CREATE UNIQUE INDEX "ApiKey_key_key" ON "ApiKey"("key");
-- CreateIndex
CREATE INDEX "ApiKey_userId_idx" ON "ApiKey"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "Account_userId_key" ON "Account"("userId");
-- CreateIndex
CREATE UNIQUE INDEX "LedgerEntry_idempotencyKey_key" ON "LedgerEntry"("idempotencyKey");
-- CreateIndex
CREATE INDEX "LedgerEntry_accountId_createdAt_idx" ON "LedgerEntry"("accountId", "createdAt" DESC);
-- CreateIndex
CREATE INDEX "LedgerEntry_kind_idx" ON "LedgerEntry"("kind");
-- CreateIndex
CREATE UNIQUE INDEX "GatewayJob_routingId_key" ON "GatewayJob"("routingId");
-- CreateIndex
CREATE UNIQUE INDEX "GatewayJob_onChainJobId_key" ON "GatewayJob"("onChainJobId");
-- CreateIndex
CREATE INDEX "GatewayJob_userId_createdAt_idx" ON "GatewayJob"("userId", "createdAt" DESC);
-- CreateIndex
CREATE INDEX "GatewayJob_status_idx" ON "GatewayJob"("status");
-- CreateIndex
CREATE INDEX "GatewayJob_onChainJobId_idx" ON "GatewayJob"("onChainJobId");
-- CreateIndex
CREATE UNIQUE INDEX "Topup_stripePaymentIntent_key" ON "Topup"("stripePaymentIntent");
-- CreateIndex
CREATE INDEX "Topup_userId_createdAt_idx" ON "Topup"("userId", "createdAt" DESC);
-- CreateIndex
CREATE INDEX "Topup_status_idx" ON "Topup"("status");
-- AddForeignKey
ALTER TABLE "ApiKey" ADD CONSTRAINT "ApiKey_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Account" ADD CONSTRAINT "Account_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_accountId_fkey" FOREIGN KEY ("accountId") REFERENCES "Account"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_gatewayJobId_fkey" FOREIGN KEY ("gatewayJobId") REFERENCES "GatewayJob"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "LedgerEntry" ADD CONSTRAINT "LedgerEntry_topupId_fkey" FOREIGN KEY ("topupId") REFERENCES "Topup"("id") ON DELETE SET NULL ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "GatewayJob" ADD CONSTRAINT "GatewayJob_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
-- AddForeignKey
ALTER TABLE "Topup" ADD CONSTRAINT "Topup_userId_fkey" FOREIGN KEY ("userId") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
+3
View File
@@ -0,0 +1,3 @@
# Please do not edit this file manually
# It should be added in your version-control system (e.g., Git)
provider = "postgresql"
+223
View File
@@ -0,0 +1,223 @@
// Hosted T4T Gateway — Prisma schema.
//
// One operator wallet handles all on-chain transactions. Tenants are off-chain
// ledger entries denominated in xBZZ wei (Decimal(78, 0) holds any uint256).
// USD is never in the billing path — only in `Topup` rows for Phase 2 Stripe.
generator client {
provider = "prisma-client-js"
}
// Connection URL + seed config live in prisma.config.ts (Prisma 7).
datasource db {
provider = "postgresql"
}
// -----------------------------------------------------------------------------
// Enums
// -----------------------------------------------------------------------------
enum UserRole {
USER
ADMIN
}
enum AuthProvider {
google
email
}
enum AccountStatus {
active
disabled
}
enum LedgerKind {
welcomeGrant
settle
refund
topup
adjustment
}
enum GatewayJobStatus {
posted
acked
delivered
claimed
cancelled
timed_out
failed
}
enum TopupStatus {
requires_payment
processing
succeeded
credited
failed
refunded
}
// -----------------------------------------------------------------------------
// User + auth
// -----------------------------------------------------------------------------
model User {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
email String @unique
emailVerified Boolean @default(false)
// Single-use email verification token (NULL once consumed). For Phase 1 we
// surface this in the signup response in dev/test mode; Phase 1.5 wires up
// a real outbound mailer and stops echoing the token back.
verificationToken String? @unique
authProvider AuthProvider
googleSub String? @unique
passwordHash String?
role UserRole @default(USER)
// Phase 2 — Stripe handles billing address + sanctions screening
stripeCustomerId String? @unique
country String? // ISO 3166-1 alpha-2, mirrored from Stripe
account Account?
apiKeys ApiKey[]
gatewayJobs GatewayJob[]
topups Topup[]
@@index([authProvider, googleSub])
}
model ApiKey {
id String @id @default(uuid())
createdAt DateTime @default(now())
key String @unique @default(uuid())
name String @default("")
lastUsedAt DateTime?
scopeMaxXbzzPerDay Decimal? @db.Decimal(78, 0)
modelAllowlist String[] @default([])
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
@@index([userId])
}
// -----------------------------------------------------------------------------
// Account + ledger
// -----------------------------------------------------------------------------
model Account {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
status AccountStatus @default(active)
balanceXbzzWei Decimal @default(0) @db.Decimal(78, 0)
reservedXbzzWei Decimal @default(0) @db.Decimal(78, 0)
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String @unique
ledgerEntries LedgerEntry[]
}
model LedgerEntry {
id String @id @default(uuid())
createdAt DateTime @default(now())
kind LedgerKind
deltaXbzzWei Decimal @db.Decimal(78, 0)
idempotencyKey String @unique
note String?
account Account @relation(fields: [accountId], references: [id], onDelete: Cascade)
accountId String
gatewayJob GatewayJob? @relation(fields: [gatewayJobId], references: [id], onDelete: SetNull)
gatewayJobId String?
topup Topup? @relation(fields: [topupId], references: [id], onDelete: SetNull)
topupId String?
@@index([accountId, createdAt(sort: Desc)])
@@index([kind])
}
// -----------------------------------------------------------------------------
// Gateway jobs (per-chat-completion, settled on JobClaimed)
// -----------------------------------------------------------------------------
model GatewayJob {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
// Routing
routingId String @unique // local routing id (keccak of request hash etc.)
onChainJobId String? @unique // bytes32 from JobEscrow.JobPosted
requestHash String? // Swarm chunk ref of encrypted request
responseHash String? // Swarm chunk ref of encrypted response
// Counterparties / model
provider String // 0x address selected by the gateway
modelId String
// Billing (xBZZ-denominated; markup baked in)
estimatedMaxXbzzWei Decimal @db.Decimal(78, 0)
actualXbzzWei Decimal? @db.Decimal(78, 0)
// Lifecycle
status GatewayJobStatus @default(posted)
postedAt DateTime @default(now())
ackedAt DateTime?
deliveredAt DateTime?
claimedAt DateTime?
ackDeadline DateTime?
deliveryDeadline DateTime?
// Token accounting (from JobClaimed actualPayment)
promptTokens Int?
completionTokens Int?
// Optional payload retention (off by default; T4T_PERSIST_PAYLOADS-style)
promptRedacted String?
responseRedacted String?
errorMessage String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
ledgerEntries LedgerEntry[]
@@index([userId, createdAt(sort: Desc)])
@@index([status])
@@index([onChainJobId])
}
// -----------------------------------------------------------------------------
// Stripe top-ups (Phase 2)
// -----------------------------------------------------------------------------
model Topup {
id String @id @default(uuid())
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
status TopupStatus @default(requires_payment)
stripePaymentIntent String @unique
amountUsdCents Int
creditedXbzzWei Decimal? @db.Decimal(78, 0)
usdPerXbzzAtCredit Decimal? @db.Decimal(18, 8)
error String?
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
ledgerEntries LedgerEntry[]
@@index([userId, createdAt(sort: Desc)])
@@index([status])
}
+18
View File
@@ -0,0 +1,18 @@
// Phase 1 has no seed data — accounts are created on signup.
// Placeholder for future Config table (e.g. operator metadata) or test fixtures.
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
async function main() {
console.log('No seed data to insert in v1.');
}
main()
.catch((err) => {
console.error(err);
process.exit(1);
})
.finally(async () => {
await prisma.$disconnect();
});