# Prisma JSON Field Schemas

This document describes the expected structure of all JSON fields in the Prisma schema. JSON fields store flexible data structures that may vary based on feature evolution.

## Entity Relationship Overview

```mermaid
erDiagram
    Dealer ||--o{ Page : "has many"
    Dealer ||--o{ DealerNote : "has many"
    Dealer ||--o{ AdminAction : "referenced in"
    User ||--o{ AdminAction : "performed by"

    Dealer {
        string id PK
        json contactData "Business hours, alt phones"
        json socialLinks "Social media URLs"
        json pageContent "Custom content sections"
        json settings "Lead form settings"
        json heroSlider "Slider configuration"
        json migrationData "Legacy migration data"
    }

    Page {
        string id PK
        string dealerId FK
        json puckData "Puck editor component tree"
    }

    AdminAction {
        string id PK
        string adminUserId FK
        string targetType "Entity type"
        string targetId "Entity ID"
        string action "Action type"
        json details "Action-specific context"
    }

    DealerNote {
        string id PK
        string dealerId FK
        string noteType "manual, support_call, etc"
        json metadata "Before/after values"
    }
```

---

## Table of Contents

- [Dealer Model](#dealer-model)
  - [contactData](#contactdata)
  - [socialLinks](#sociallinks)
  - [pageContent](#pagecontent)
  - [settings](#settings)
  - [heroSlider](#heroslider)
  - [migrationData](#migrationdata)
- [Page Model](#page-model)
  - [puckData](#puckdata)
- [AdminAction Model](#adminaction-model)
  - [details](#details)
- [DealerNote Model](#dealernote-model)
  - [metadata](#metadata)

---

## Dealer Model

### contactData

**Purpose:** Store additional contact information beyond the primary fields (phone, address, email).

**Type Definition:** Not yet formally typed. Currently reserved for future use.

**Expected Structure:**

```typescript
interface ContactData {
  businessHours?: {
    monday?: string;
    tuesday?: string;
    wednesday?: string;
    thursday?: string;
    friday?: string;
    saturday?: string;
    sunday?: string;
  };
  alternatePhones?: string[];
  secondaryEmails?: string[];
}
```

**Example:**

```json
{
  "businessHours": {
    "monday": "9:00 AM - 5:00 PM",
    "tuesday": "9:00 AM - 5:00 PM",
    "wednesday": "9:00 AM - 5:00 PM",
    "thursday": "9:00 AM - 5:00 PM",
    "friday": "9:00 AM - 5:00 PM",
    "saturday": "Closed",
    "sunday": "Closed"
  },
  "alternatePhones": ["+1-555-123-4567"],
  "secondaryEmails": ["support@dealer.com"]
}
```

**Current Status:** Reserved for future implementation. Field exists but is not actively used.

---

### socialLinks

**Purpose:** Store dealer's social media profile URLs.

**Type Definition:** Not yet formally typed.

**Expected Structure:**

```typescript
interface SocialLinks {
  facebook?: string;
  instagram?: string;
  twitter?: string;
  linkedin?: string;
  youtube?: string;
}
```

**Example:**

```json
{
  "facebook": "https://facebook.com/myamsoildealer",
  "instagram": "https://instagram.com/myamsoildealer",
  "youtube": "https://youtube.com/@myamsoildealer"
}
```

**Current Status:** Reserved for future implementation.

---

### pageContent

**Purpose:** Store custom content sections for the dealer's landing page.

**Type Definition:** Not yet formally typed.

**Expected Structure:**

```typescript
interface PageContent {
  heroText?: string;
  aboutSection?: string;
  testimonials?: Array<{
    author: string;
    content: string;
    rating?: number;
  }>;
}
```

**Example:**

```json
{
  "heroText": "Your Local AMSOIL Expert",
  "aboutSection": "<p>Welcome to my AMSOIL dealership...</p>",
  "testimonials": [
    {
      "author": "John D.",
      "content": "Great service and product knowledge!",
      "rating": 5
    }
  ]
}
```

**Current Status:** Reserved for future implementation.

---

### settings

**Purpose:** Store dealer-specific configuration settings, including lead form settings.

**Type Definition:** See `types/lead-form.ts`

**Expected Structure:**

```typescript
interface DealerSettings {
  leadFormSettings?: LeadFormSettings;
  // Future settings can be added here
}

// From types/lead-form.ts
interface LeadFormSettings {
  leadFormEnabled: boolean;
  autoResponseEnabled: boolean;
  autoResponseContent: string; // HTML content from Lexical editor
  emailNotificationEnabled: boolean;
  notificationEmail?: string; // Defaults to Dealer.contactEmail if not set
}
```

**Default Values:**

```typescript
const DEFAULT_LEAD_FORM_SETTINGS: LeadFormSettings = {
  leadFormEnabled: true,
  autoResponseEnabled: false,
  autoResponseContent: '<p>Thank you for contacting us! We will get back to you shortly.</p>',
  emailNotificationEnabled: true,
  notificationEmail: undefined,
};
```

**Example:**

```json
{
  "leadFormSettings": {
    "leadFormEnabled": true,
    "autoResponseEnabled": true,
    "autoResponseContent": "<p>Thanks for reaching out! I will respond within 24 hours.</p>",
    "emailNotificationEnabled": true,
    "notificationEmail": "leads@mydealer.com"
  }
}
```

**Where Used:**

- `app/api/dealer/settings/route.ts` - GET/PUT endpoints for managing settings
- `app/api/contact/route.ts` - Lead form submission processing

**Validation:**

- `leadFormSettings.autoResponseContent` max length: 50KB
- `notificationEmail` must be valid email format if provided

---

### heroSlider

**Purpose:** Store the hero slider configuration for the dealer's landing page.

**Type Definition:** See `types/slider.ts`

**Expected Structure:**

```typescript
// From types/slider.ts
interface SliderContent {
  id: string; // Unique ID per dealer
  slides: Slide[];
  publishedAt?: Date;
  updatedAt: Date;
}

interface Slide {
  id: string; // UUID
  order: number;
  image: SlideImage;
  headline: string;
  headlineFormatting: TextFormatting;
  subheading: string;
  subheadingFormatting: TextFormatting;
  button: {
    text: string;
    url: string;
  };
  alignment?: 'left' | 'right';
}

interface SlideImage {
  url: string;
  source: 'upload' | 'library';
}

interface TextFormatting {
  color?: string; // Hex color from brand palette
  bold?: boolean;
  italic?: boolean;
}
```

**Brand Colors:**

```typescript
const BRAND_COLORS = {
  primary: '#003366', // Dark blue
  secondary: '#FF6B35', // Orange
  accent: '#FFD700', // Gold
  light: '#FFFFFF', // White
  dark: '#1A1A1A', // Dark gray
};
```

**Example:**

```json
{
  "id": "dealer-slider-abc123",
  "slides": [
    {
      "id": "slide-1",
      "order": 0,
      "image": {
        "url": "/images/slider-defaults/hero-1.jpg",
        "source": "library"
      },
      "headline": "EUROPEAN PRECISION",
      "headlineFormatting": { "color": "#FFFFFF", "bold": true },
      "subheading": "Engineered for the vehicles that demand the best",
      "subheadingFormatting": { "color": "#FFFFFF" },
      "button": {
        "text": "SHOP EUROPEAN OILS",
        "url": "https://www.amsoil.com/c/european-motor-oil/4/?zo=12345"
      },
      "alignment": "left"
    }
  ],
  "updatedAt": "2024-12-15T10:30:00.000Z"
}
```

**Where Used:**

- `app/actions/slider.ts` - saveHeroSlider(), getHeroSlider()
- `components/HeroSlider.tsx` - Rendering the slider
- `app/dashboard/slider-editor/page.tsx` - Slider editing UI
- `types/dealer.ts` - DealerInfo.heroSlider

**Validation:**

- Must have at least one slide
- Each slide must have a valid image URL

---

### migrationData

**Purpose:** Store scraped data and metadata from dealer migration from the legacy system.

**Type Definition:** Not formally typed.

**Expected Structure:**

```typescript
interface MigrationData {
  sourceUrl?: string; // Original myamsoil.com URL
  scrapedAt?: string; // ISO date when data was scraped
  rawData?: Record<string, unknown>; // Raw scraped content
  // Additional migration-specific fields
}
```

**Example:**

```json
{
  "sourceUrl": "https://autolife.myamsoil.com",
  "scrapedAt": "2024-11-15T14:30:00.000Z",
  "rawData": {
    "dealerName": "Don & LaVel Rude",
    "originalHtml": "..."
  }
}
```

**Current Status:** Used during dealer migration from legacy system. Read-only after migration.

---

## Page Model

### puckData

**Purpose:** Store the Puck visual editor's component tree for CMS pages.

**Type Definition:** Uses Puck library's native `Data` type from `@measured/puck`

**Expected Structure:**

```typescript
import type { Data as PuckData } from '@measured/puck';

// PuckData is the native Puck editor format:
interface PuckData {
  root: {
    props?: Record<string, unknown>;
  };
  content: Array<{
    type: string;
    props: Record<string, unknown>;
  }>;
  zones?: Record<
    string,
    Array<{
      type: string;
      props: Record<string, unknown>;
    }>
  >;
}
```

**Example:**

```json
{
  "root": {
    "props": {
      "title": "My Page"
    }
  },
  "content": [
    {
      "type": "Hero",
      "props": {
        "title": "Welcome",
        "subtitle": "This is my page",
        "imageUrl": "/images/hero.jpg"
      }
    },
    {
      "type": "TextBlock",
      "props": {
        "content": "<p>Page content here...</p>"
      }
    }
  ]
}
```

**Where Used:**

- `lib/cms/types.ts` - Page interface
- `app/api/cms/pages/[id]/route.ts` - Page CRUD operations
- `app/api/cms/publish/route.ts` - Page publishing
- `lib/cms/publisher.ts` - Rendering puckData to HTML
- `app/dashboard/cms/pages/[id]/edit/page.tsx` - Puck editor

**Validation:**

- Validated by Puck editor
- See `lib/cms/validation.ts` for additional server-side validation

---

## AdminAction Model

### details

**Purpose:** Store context-specific details for admin audit log entries. Structure varies based on the `action` type.

**Type Definition:** `Record<string, unknown>` - varies by action type

**Action-Specific Structures:**

#### start_impersonation

```typescript
interface StartImpersonationDetails {
  targetEmail: string;
  targetRole: string;
  dealerName: string | null;
  expiresAt: string; // ISO date
}
```

#### end_impersonation

```typescript
interface EndImpersonationDetails {
  targetEmail: string;
  startedAt: string; // ISO date
  endedAt: string; // ISO date
}
```

#### change_status

```typescript
interface ChangeStatusDetails {
  previousStatus: string;
  newStatus: string;
  stripeAction: 'pause' | 'resume' | 'cancel' | null;
  stripeResult: {
    success: boolean;
    status?: string;
    refundId?: string;
    refundAmount?: number;
  } | null;
  cancellationMode?: 'immediate' | 'end_of_period' | 'immediate_refund';
  dnsCreated: boolean;
  notes: string | null;
}
```

#### update_subdomain

```typescript
interface UpdateSubdomainDetails {
  oldSubdomain: string | null;
  newSubdomain: string;
  oldDomain: string;
  newDomain: string;
  oldDomainPrefix: string;
  newDomainPrefix: string;
  hadDnsRecord: boolean;
}
```

#### note_added

```typescript
interface NoteAddedDetails {
  noteId: string;
  noteType: 'manual' | 'support_call' | 'email_sent' | 'internal';
}
```

#### support\_\* (impersonation session actions)

```typescript
interface SupportActionDetails {
  impersonatedUserId: string;
  impersonatedUserEmail: string;
  // Plus action-specific fields
  changedSettings?: string[];
  previousValues?: Record<string, unknown>;
  newValues?: Record<string, unknown>;
}
```

#### toggle_grandfathered_custom_domain

```typescript
interface ToggleGrandfatheredCustomDomainDetails {
  previousValue: boolean;
  newValue: boolean;
}
```

#### revalidate_pages

```typescript
interface RevalidatePagesDetails {
  subdomain: string;
  totalPages: number;
  successCount: number;
  failedPaths: string[];
}
```

#### note_deleted

```typescript
interface NoteDeletedDetails {
  noteId: string;
  noteType: 'manual' | 'support_call' | 'email_sent' | 'internal';
}
```

#### assign_custom_domain

```typescript
interface AssignCustomDomainDetails {
  domain: string;
  dealerSubdomain: string | null;
  businessName: string | null;
}
```

#### modify_custom_domain

```typescript
interface ModifyCustomDomainDetails {
  domain: string;
  previousDealerId: string;
  action: 'transfer' | 'update';
  // For transfers:
  newDealerId?: string;
  // For status updates:
  previousStatus?: string;
  newStatus?: string;
  clearedError?: boolean;
}
```

#### remove_custom_domain

```typescript
interface RemoveCustomDomainDetails {
  domain: string;
  dealerSubdomain: string | null;
  businessName: string | null;
  cleanupMethod: string; // "certbot" (current) | "cloudflare_saas" | "cloudflare_zone" (both legacy) | "none"
}
```

#### regenerate_certificate

```typescript
interface RegenerateCertificateDetails {
  domain: string;
  dealerSubdomain: string | null;
  businessName: string | null;
  newExpiresAt: string | null; // ISO date
  certPath: string | null;
  skippedRevoke: boolean;
}
```

#### regenerate_certificate_failed

```typescript
interface RegenerateCertificateFailedDetails {
  domain: string;
  error: string | null;
  skippedRevoke: boolean;
}
```

**Example (change_status):**

```json
{
  "previousStatus": "active",
  "newStatus": "suspended",
  "stripeAction": "pause",
  "stripeResult": {
    "success": true,
    "status": "paused"
  },
  "cancellationMode": null,
  "dnsCreated": false,
  "notes": "Customer requested temporary suspension"
}
```

**Where Used:**

- `app/api/admin/impersonate/start/route.ts`, `app/api/admin/impersonate/end/route.ts` - Impersonation start/end logging
- `lib/admin-auth.ts` (`logImpersonationAction`) - `support_*` action logging during a session
- `app/api/admin/dealers/[id]/status/route.ts` - Status change logging
- `app/api/admin/dealers/[id]/subdomain/route.ts` - Subdomain change logging
- `app/api/admin/dealers/[id]/notes/route.ts` - Note addition logging
- `app/api/admin/dealers/[id]/notes/[noteId]/route.ts` - Note deletion logging
- `app/api/admin/dealers/[id]/grandfathered-custom-domain/route.ts` - Grandfathered toggle logging
- `app/api/admin/dealers/[id]/revalidate/route.ts` - Page revalidation logging
- `app/api/admin/custom-domains/route.ts` - Custom domain assignment logging
- `app/api/admin/custom-domains/[domain]/route.ts` - Domain modify/remove logging
- `app/api/admin/custom-domains/[domain]/regenerate/route.ts` - Certificate regeneration logging
- `lib/admin-auth.ts` - logImpersonationAction() for support session actions
- `app/api/admin/dealers/[id]/activity/route.ts` - Reading activity timeline

---

## DealerNote Model

### metadata

**Purpose:** Store additional structured data for auto-generated notes, such as before/after values for system events.

**Type Definition:** Not formally typed. Used primarily for automatic notes.

**Expected Structure:**

```typescript
interface NoteMetadata {
  // For status change notes
  previousStatus?: string;
  newStatus?: string;

  // For before/after tracking
  before?: Record<string, unknown>;
  after?: Record<string, unknown>;

  // For action references
  relatedActionId?: string;
}
```

**Example:**

```json
{
  "previousStatus": "pending",
  "newStatus": "active",
  "before": {
    "status": "pending"
  },
  "after": {
    "status": "active"
  }
}
```

**Current Usage:**

- Currently reserved for automatic notes (`isAutomatic: true`)
- Manual notes (`isAutomatic: false`) typically have `metadata: null`

**Where Used:**

- `prisma/schema.prisma` - Model definition with comment
- `app/api/admin/dealers/[id]/notes/route.ts` - Note creation (currently sets null for manual notes)

---

## Type Reference Files

| JSON Field                | Type Definition Location                        |
| ------------------------- | ----------------------------------------------- |
| heroSlider                | `types/slider.ts`                               |
| settings.leadFormSettings | `types/lead-form.ts`                            |
| puckData                  | `@measured/puck` (external), `lib/cms/types.ts` |
| Others                    | Not yet formally typed                          |

---

## Best Practices

1. **Type Safety:** When reading JSON fields, always cast to the expected type:

   ```typescript
   const settings = dealer.settings as DealerSettings | null;
   const heroSlider = dealer.heroSlider as SliderContent | null;
   ```

2. **Default Values:** Always provide defaults when accessing optional nested properties:

   ```typescript
   const leadFormSettings = {
     ...DEFAULT_LEAD_FORM_SETTINGS,
     ...(settings?.leadFormSettings || {}),
   };
   ```

3. **Validation:** Validate JSON data before storing, especially for user-submitted content.

4. **Backwards Compatibility:** When adding new fields to JSON structures, ensure existing data without the new fields continues to work.

5. **Documentation:** Update this document when modifying JSON field structures.
