# 03 — Database Schema

**ORM:** Prisma  
**Database:** PostgreSQL  
**Schema file:** `prisma/schema.prisma`

---

## Entity Relationship Diagram

```
User
 ├──< Contract (one user → many contracts)
 │     ├──< ContractRagChunk (one contract → many RAG chunks)
 │     ├──< Notification (one contract → many notifications)
 │     └──< BlockchainTransaction (one contract → many blockchain txs)
 ├──< Notification (one user → many notifications)
 └──< BlockchainTransaction (one user → many blockchain txs)
```

---

## Models

### `User`

Stores platform users, synchronized from Clerk via webhook.

| Column | Type | Notes |
|--------|------|-------|
| `id` | `String` (cuid) | Primary key |
| `clerkId` | `String` | Unique Clerk user ID |
| `email` | `String` | Unique email address |
| `firstName` | `String?` | Optional first name |
| `lastName` | `String?` | Optional last name |
| `imageUrl` | `String?` | Clerk profile image URL |
| `createdAt` | `DateTime` | Record creation timestamp |
| `updatedAt` | `DateTime` | Auto-updated on changes |

**Indexes:** `clerkId`, `email`

**Sync:** Created/updated by `app/api/webhooks/clerk/` in response to Clerk `user.created` and `user.updated` events.

---

### `Contract`

The core entity — one record per uploaded document.

**File Information (from upload):**

| Column | Type | Notes |
|--------|------|-------|
| `id` | `String` (cuid) | Primary key |
| `userId` | `String` | FK → User.id (cascade delete) |
| `fileName` | `String` | Original file name |
| `fileUrl` | `String` | UploadThing CDN URL |
| `fileSize` | `Int` | File size in bytes |
| `mimeType` | `String` | `application/pdf` or `image/*` |

**AI-Extracted Fields (filled after analysis):**

| Column | Type | Notes |
|--------|------|-------|
| `title` | `String?` | Contract title |
| `type` | `ContractType?` | Enum (see below) |
| `provider` | `String?` | Insurance company / bank name |
| `policyNumber` | `String?` | Policy or contract reference number |
| `startDate` | `DateTime?` | Contract start date |
| `endDate` | `DateTime?` | Contract end / expiry date |
| `premium` | `Decimal(10,2)?` | Annual/monthly premium amount |
| `extractedText` | `String?` (Text) | Full text extracted from document |
| `summary` | `String?` (Text) | 4-6 sentence AI summary |
| `keyPoints` | `Json?` | Guarantees, exclusions, explainability |

**Processing:**

| Column | Type | Notes |
|--------|------|-------|
| `status` | `ContractStatus` | Default: `UPLOADED` |

**Blockchain Proof:**

| Column | Type | Notes |
|--------|------|-------|
| `documentHash` | `String?` | SHA-256 hash of the file (0x-prefixed) |
| `txHash` | `String?` | Ethereum transaction hash |
| `blockNumber` | `Int?` | Block number where tx was mined |
| `blockTimestamp` | `DateTime?` | Block timestamp = legal proof date |
| `blockchainNetwork` | `String?` | `"hardhat"` or `"sepolia"` |
| `contractAddress` | `String?` | DocumentRegistry contract address |

**Indexes:** `userId`, `status`, `type`, `endDate`  
**Relations:** `ContractRagChunk[]`, `Notification[]`, `BlockchainTransaction[]`

---

### `ContractRagChunk`

Stores text chunks and their vector embeddings for RAG (Retrieval-Augmented Generation).

| Column | Type | Notes |
|--------|------|-------|
| `id` | `String` (cuid) | Primary key |
| `contractId` | `String` | FK → Contract.id (cascade delete) |
| `chunkIndex` | `Int` | Position in the document |
| `content` | `String` | Raw text content of the chunk |
| `contentHash` | `String` | SHA-256 of content (deduplication) |
| `embedding` | `Float[]` | Gemini text-embedding-004 vector |
| `createdAt` | `DateTime` | — |
| `updatedAt` | `DateTime` | — |

**Unique constraint:** `(contractId, chunkIndex)` — no duplicate chunks per contract.  
**Indexes:** `contractId`, `contentHash`, `chunkIndex`

> **Note:** PostgreSQL stores `Float[]` as a native array column. Cosine similarity is computed in application code, not via pgvector. A future enhancement could migrate to pgvector for large-scale similarity search.

---

### `Notification`

In-app notifications for users.

| Column | Type | Notes |
|--------|------|-------|
| `id` | `String` (cuid) | Primary key |
| `userId` | `String` | FK → User.id (cascade delete) |
| `contractId` | `String?` | Optional FK → Contract.id (set null on delete) |
| `type` | `NotificationType` | Enum: SUCCESS, WARNING, ERROR, INFO, DEADLINE |
| `title` | `String` | Short notification title |
| `message` | `String` | Full notification message |
| `icon` | `String?` | Icon identifier for UI rendering |
| `actionType` | `String?` | e.g., `"RENEWAL_REMINDER"`, `"UPLOAD_SUCCESS"` |
| `actionData` | `Json?` | Additional action-specific payload |
| `read` | `Boolean` | Default: `false` |
| `createdAt` | `DateTime` | — |
| `expiresAt` | `DateTime?` | Optional expiration for auto-cleanup |

**Indexes:** `userId`, `contractId`, `type`, `read`, `createdAt`

---

### `BlockchainTransaction`

Audit log of every on-chain document registration.

| Column | Type | Notes |
|--------|------|-------|
| `id` | `String` (cuid) | Primary key |
| `userId` | `String` | FK → User.id (cascade delete) |
| `contractId` | `String` | FK → Contract.id (cascade delete) |
| `documentHash` | `String` | SHA-256 hash of the document |
| `txHash` | `String` (unique) | Ethereum transaction hash |
| `blockNumber` | `Int` | Block number where tx was mined |
| `blockTimestamp` | `DateTime` | Block timestamp |
| `network` | `String` | `"hardhat"` or `"sepolia"` |
| `contractAddress` | `String` | Smart contract address |
| `status` | `String` | Default: `"CONFIRMED"` (PENDING/CONFIRMED/FAILED) |
| `createdAt` | `DateTime` | — |

**Indexes:** `userId`, `contractId`, `txHash`, `network`

---

## Enums

### `ContractType`

| Value | Description |
|-------|-------------|
| `INSURANCE_AUTO` | Auto/vehicle insurance |
| `INSURANCE_HOME` | Home/property insurance |
| `INSURANCE_HEALTH` | Health insurance |
| `INSURANCE_LIFE` | Life insurance |
| `LOAN` | Bank loan / mortgage |
| `CREDIT_CARD` | Credit card agreement |
| `INVESTMENT` | Investment / fund agreement |
| `OTHER` | Any other contract type |

### `ContractStatus`

| Value | Description |
|-------|-------------|
| `UPLOADED` | File received, awaiting analysis |
| `PROCESSING` | AI analysis in progress |
| `COMPLETED` | Analysis done and blockchain registered |
| `FAILED` | Analysis or registration failed |

### `NotificationType`

| Value | Description |
|-------|-------------|
| `SUCCESS` | Successful operation (analysis complete, etc.) |
| `WARNING` | Non-critical issue or approaching deadline |
| `ERROR` | Failed operation |
| `INFO` | General informational message |
| `DEADLINE` | Contract expiry deadline approaching |

---

## Database Operations

### Migrations

```bash
# Create a new migration in development
npm run db:migrate:dev -- --name <migration_name>

# Apply pending migrations in production
npm run db:migrate

# Regenerate Prisma client after schema changes
npm run db:generate
```

### Prisma Studio (visual DB browser)

```bash
npm run db:studio
```

### Connection Pooling (Production)

For high-traffic production deployments, use **PgBouncer** or **Prisma Accelerate** to pool PostgreSQL connections. The `DATABASE_URL` format with connection pooling:

```
DATABASE_URL="postgresql://USER:PASS@HOST:PORT/DB?pgbouncer=true&connection_limit=1"
```

Set `connection_limit=1` when using PgBouncer in transaction mode.
