← back to Jill Website

CLAUDE.md

1116 lines

# CLAUDE.md - AI Development Documentation

## Server Access

- **URL**: http://45.61.58.125:8201
- **Username**: admin
- **Password**: (ADMIN_PASSWORD env — see project .env; no longer hardcoded)
- **PM2 Name**: jill-website

## Project Overview

**Project Name**: Nosara Beachfront Rentals (jill-website)
**Version**: 1.0.0
**Purpose**: Luxury beachfront vacation rental website for Nosara, Costa Rica
**Type**: Full-stack web application with REST API
**Primary User**: Property managers and vacation rental guests

This application provides a professional vacation rental platform with property listings, guest inquiry management, and automated email notifications. Built for reliability and scalability, it serves as the digital presence for luxury beachfront properties in Nosara, Costa Rica.

---

## Tech Stack & Dependencies

### Core Technologies
- **Runtime**: Node.js (v16+)
- **Framework**: Express.js 4.18.2
- **Language**: TypeScript 5.9.3 (strict mode enabled)
- **Database**: PostgreSQL 12+ with connection pooling
- **Template Engine**: EJS 3.1.10
- **Process Manager**: PM2 (recommended for production)

### Key Dependencies
- **pg** (8.16.3): PostgreSQL client with connection pooling
- **nodemailer** (7.0.12): SMTP email delivery with retry logic
- **dotenv** (17.2.3): Environment variable management
- **express**: HTTP server and routing

### Development & Testing
- **TypeScript**: Full type safety with strict compiler options
- **Jest** (30.2.0): Testing framework with 90% coverage threshold
- **Supertest** (7.2.2): HTTP endpoint testing
- **ts-jest** (29.4.6): TypeScript support for Jest
- **ts-node** (10.9.2): Direct TypeScript execution for development

---

## Project Structure & Architecture

```
jill-website/
├── src/
│   ├── config/
│   │   └── database.ts           # PostgreSQL connection pool & config
│   ├── migrations/
│   │   ├── 001_create_tables.sql # Database schema with indexes & triggers
│   │   └── migrate.ts            # Migration runner script
│   ├── models/
│   │   ├── Inquiry.ts            # Inquiry model with validation & CRUD
│   │   ├── Property.ts           # Property model with JSONB handling
│   │   └── __mocks__/            # Jest mocks for testing
│   ├── routes/
│   │   ├── inquiries.ts          # Inquiry API endpoints (POST, GET)
│   │   ├── properties.ts         # Property API endpoints (GET)
│   │   └── unsubscribe.ts        # Email unsubscribe handler
│   ├── services/
│   │   └── emailService.ts       # Email service with retry & templates
│   ├── __tests__/                # Test suites (unit + integration)
│   ├── index.ts                  # Application entry point
│   └── server.ts                 # Express server setup & middleware
├── public/                       # Static assets (CSS, JS, images)
├── views/                        # EJS templates for server-side rendering
├── dist/                         # Compiled JavaScript (generated by tsc)
├── coverage/                     # Test coverage reports (generated)
├── scripts/                      # Utility scripts
├── .env                          # Environment variables (not in git)
├── .env.example                  # Example environment configuration
├── package.json                  # Dependencies & scripts
├── tsconfig.json                 # TypeScript compiler configuration
├── jest.config.js                # Jest testing configuration
├── DATABASE.md                   # Database schema documentation
└── README.md                     # User-facing documentation
```

### Architectural Patterns

**MVC-Style Separation**:
- **Models** (`src/models/`): Data layer with validation, CRUD operations, database interaction
- **Routes** (`src/routes/`): Controller layer handling HTTP requests/responses, input parsing
- **Services** (`src/services/`): Business logic layer (email delivery, external integrations)

**Key Design Decisions**:
1. **Factory Pattern for Routes**: Routes use factory functions (`createInquiriesRouter()`) to enable dependency injection for testing
2. **Singleton Database Pool**: Single connection pool managed by `database.ts` prevents connection exhaustion
3. **Non-blocking Email**: Email sending is fire-and-forget to avoid blocking HTTP responses
4. **Comprehensive Validation**: All data validated in models before database operations
5. **Type Safety**: Full TypeScript coverage with strict mode for compile-time error detection

---

## Database Architecture

### Connection Management

**File**: `src/config/database.ts`

The database layer uses a singleton pattern with a PostgreSQL connection pool:

```typescript
// Pool configuration (environment-driven)
const pool = new Pool({
  host: process.env.DB_HOST || 'localhost',
  port: parseInt(process.env.DB_PORT || '5432'),
  database: process.env.DB_NAME || 'jill_website',
  user: process.env.DB_USER || 'postgres',
  password: process.env.DB_PASSWORD,
  max: 20,                    // Max 20 concurrent connections
  idleTimeoutMillis: 30000,   // Close idle connections after 30s
  connectionTimeoutMillis: 2000 // 2s timeout for new connections
});
```

**Key Functions**:
- `getPool()`: Returns singleton pool instance (creates on first call)
- `testConnection()`: Validates database connectivity
- `closePool()`: Gracefully closes all connections

**Error Handling**:
- Connection errors logged to console and cause process exit
- Pool events (`connect`, `acquire`, `error`) are monitored
- Automatic reconnection on transient failures

### Schema Design

**Tables**:

1. **inquiries** - Guest rental inquiries
   - Primary Key: `id` (SERIAL)
   - Required Fields: name, email, phone, check_in_date, check_out_date, guest_count, message
   - Constraints:
     - `guest_count > 0`
     - `check_out_date > check_in_date` (prevents invalid date ranges)
   - Indexes:
     - `idx_inquiries_email` - Fast email lookups
     - `idx_inquiries_check_in` - Availability queries
     - `idx_inquiries_created_at DESC` - Recent inquiries sorting
   - Triggers: Auto-update `updated_at` timestamp on row modification

2. **properties** - Luxury property listings
   - Primary Key: `id` (SERIAL)
   - Required Fields: name (UNIQUE), description, amenities (JSONB), images (JSONB), airbnb_url
   - Optional Fields: capacity, bedrooms, bathrooms, size_sqm, price_per_night
   - JSONB Fields:
     - `amenities`: Array of strings (["Pool", "WiFi", "Beach Access"])
     - `images`: Array of URLs (["https://...", "https://..."])
   - Constraints:
     - Name must be unique
     - Numeric fields cannot be negative
   - Indexes:
     - `idx_properties_name` - Fast name lookups
   - Triggers: Auto-update `updated_at` timestamp on row modification

**Schema Rationale**:
- **JSONB for flexible arrays**: Amenities and images stored as JSONB allow efficient querying and indexing without separate junction tables
- **Decimal types for precision**: `NUMERIC(10,2)` used for prices and measurements to avoid floating-point errors
- **Automatic timestamps**: Database triggers handle `updated_at` to ensure consistency
- **Constraint validation**: Database-level constraints (CHECK) provide defense-in-depth beyond application validation

### Database Relationships

**Current Schema**: No foreign keys between tables (properties are generic, inquiries don't reference specific properties)

**Rationale**: The initial design treats inquiries as general interest forms rather than property-specific bookings. This simplifies the schema and allows flexibility in the booking workflow.

**Future Considerations**:
- If property-specific inquiries are needed: Add `property_id` foreign key to inquiries table
- If booking calendar is needed: Create separate `bookings` table with date ranges and status
- If user accounts are needed: Create `users` table with authentication

---

## Models & Data Layer

### InquiryModel (`src/models/Inquiry.ts`)

**Purpose**: Manage guest rental inquiries with validation and database operations

**Interface**:
```typescript
interface Inquiry {
  id?: number;              // Auto-generated by database
  name: string;             // Guest full name
  email: string;            // Contact email (validated, lowercased)
  phone: string;            // Phone number (any format)
  check_in_date: Date;      // Arrival date
  check_out_date: Date;     // Departure date
  guest_count: number;      // Number of guests (min: 1)
  message: string;          // Guest inquiry message
  created_at?: Date;        // Auto-generated by database
  updated_at?: Date;        // Auto-updated by database trigger
}
```

**Validation Rules** (enforced in `validate()` method):
1. Name: Required, non-empty after trim
2. Email: Required, valid format (`/^[^\s@]+@[^\s@]+\.[^\s@]+$/`), automatically lowercased
3. Phone: Required, non-empty after trim (no format validation)
4. Check-in date: Required
5. Check-out date: Required, must be after check-in date
6. Guest count: Required, must be >= 1
7. Message: Required, non-empty after trim

**Methods**:
- `create(inquiry)`: Validates and inserts new inquiry, returns created record
- `findById(id)`: Retrieves single inquiry by ID, returns null if not found
- `findAll(limit=100, offset=0)`: Paginated list of all inquiries, ordered by created_at DESC
- `update(id, inquiry)`: Updates existing inquiry with validation
- `delete(id)`: Removes inquiry by ID, returns boolean success status

**Data Transformations**:
- Email normalized to lowercase before storage
- All string fields trimmed of whitespace
- Dates converted to PostgreSQL DATE format

**Error Handling**:
- Validation errors thrown as: `Error("Validation failed: field: message, ...")`
- Database errors caught, logged, and re-thrown with context
- All methods use try-catch for robust error handling

### PropertyModel (`src/models/Property.ts`)

**Purpose**: Manage luxury property listings with JSONB handling

**Interface**:
```typescript
interface Property {
  id?: number;                 // Auto-generated by database
  name: string;                // Unique property name
  description: string;         // Rich text description
  amenities: string[];         // Array of amenities (stored as JSONB)
  images: string[];            // Array of image URLs (stored as JSONB)
  airbnb_url: string;          // Link to Airbnb listing
  capacity?: number;           // Max guest capacity
  bedrooms?: number;           // Number of bedrooms
  bathrooms?: number;          // Number of bathrooms (can be decimal: 1.5)
  size_sqm?: number;           // Property size in square meters
  price_per_night?: number;    // Nightly rate in USD
  created_at?: Date;           // Auto-generated by database
  updated_at?: Date;           // Auto-updated by database trigger
}
```

**Validation Rules**:
1. Name: Required, non-empty, must be unique
2. Description: Required, non-empty
3. Amenities: Required, must have at least one item
4. Images: Required, must have at least one URL, all URLs must be valid
5. Airbnb URL: Required, must be valid URL format
6. Capacity: Optional, if provided must be >= 1
7. Bedrooms: Optional, if provided must be >= 0
8. Bathrooms: Optional, if provided must be >= 0
9. Size: Optional, if provided must be >= 0
10. Price: Optional, if provided must be >= 0

**Methods**:
- `create(property)`: Validates and inserts new property with JSONB serialization
- `findById(id)`: Retrieves property by ID with JSONB parsing
- `findAll(limit=100, offset=0)`: Paginated list, ordered by created_at DESC
- `update(id, property)`: Updates existing property with validation
- `delete(id)`: Removes property by ID

**JSONB Handling** (critical implementation detail):
- **Storage**: Arrays serialized to JSON strings via `JSON.stringify()` before INSERT/UPDATE
- **Retrieval**: JSON strings parsed back to arrays via `JSON.parse()` after SELECT
- **Type Safety**: TypeScript interface shows arrays, but database stores as JSONB
- **Decimal Conversion**: Numeric fields (`size_sqm`, `price_per_night`) converted to float with `parseFloat()`

Example JSONB transformation:
```typescript
// JavaScript → Database
amenities: ["Pool", "WiFi"]  →  amenities: '["Pool","WiFi"]'::JSONB

// Database → JavaScript
amenities: '["Pool","WiFi"]'::JSONB  →  amenities: ["Pool", "WiFi"]
```

---

## API Routes & Endpoints

### Inquiry Routes (`src/routes/inquiries.ts`)

**Architecture**: Uses factory pattern for testability:
```typescript
export function createInquiriesRouter(
  inquiryModel?: InquiryModel,
  emailSvc?: EmailService
): Router
```

**Endpoints**:

#### POST `/api/inquiries`
**Purpose**: Create new guest inquiry and send confirmation email

**Request Body**:
```json
{
  "name": "John Doe",
  "email": "john@example.com",
  "phone": "+1-555-0123",
  "check_in_date": "2025-02-01",
  "check_out_date": "2025-02-08",
  "guest_count": 4,
  "message": "Looking to book Casita del Sol for a week"
}
```

**Success Response** (201 Created):
```json
{
  "success": true,
  "data": {
    "id": 1,
    "inquiry": { /* full inquiry object */ }
  },
  "message": "Inquiry created successfully"
}
```

**Error Responses**:
- 400 Bad Request: Validation error (missing fields, invalid dates, etc.)
- 500 Internal Server Error: Database or system error

**Side Effects**:
- Sends confirmation email to guest (non-blocking, fire-and-forget)
- Email failures logged but don't affect HTTP response
- Email sending happens asynchronously after successful inquiry creation

**Implementation Notes**:
- Date strings converted to Date objects before validation
- guest_count parsed to integer
- Validation errors return specific field-level messages

#### GET `/api/inquiries`
**Purpose**: Retrieve all inquiries (for admin dashboard)

**Query Parameters**:
- `limit` (optional, default: 100): Number of results to return
- `offset` (optional, default: 0): Number of results to skip

**Success Response** (200 OK):
```json
{
  "success": true,
  "data": [ /* array of inquiry objects */ ],
  "count": 42
}
```

**Pagination Example**:
- Page 1: `/api/inquiries?limit=20&offset=0`
- Page 2: `/api/inquiries?limit=20&offset=20`
- Page 3: `/api/inquiries?limit=20&offset=40`

#### GET `/api/inquiries/:id`
**Purpose**: Retrieve specific inquiry by ID

**Success Response** (200 OK):
```json
{
  "success": true,
  "data": { /* inquiry object */ }
}
```

**Error Responses**:
- 400 Bad Request: Invalid ID format (e.g., "abc" or "3.14")
- 404 Not Found: Inquiry does not exist
- 500 Internal Server Error: Database error

**ID Validation**:
- Must be valid integer
- String representations of decimals rejected ("3.5" → 400 error)
- Array IDs handled (takes first element)

### Property Routes (`src/routes/properties.ts`)

Similar factory pattern for testability:
```typescript
export function createPropertiesRouter(propertyModel?: PropertyModel): Router
```

**Endpoints**:

#### GET `/api/properties`
**Purpose**: Retrieve all property listings

**Query Parameters**:
- `limit` (optional, default: 100): Number of results
- `offset` (optional, default: 0): Pagination offset

**Success Response** (200 OK):
```json
{
  "success": true,
  "data": [ /* array of property objects */ ],
  "count": 3
}
```

#### GET `/api/properties/:id`
**Purpose**: Retrieve specific property by ID

**Success Response** (200 OK):
```json
{
  "success": true,
  "data": {
    "id": 1,
    "name": "Casita del Sol",
    "description": "First floor oceanfront villa...",
    "amenities": ["Saltwater Pool", "Beach Access", "WiFi"],
    "images": ["https://example.com/image1.jpg"],
    "airbnb_url": "https://airbnb.com/rooms/12345",
    "capacity": 4,
    "bedrooms": 1,
    "bathrooms": 1.5,
    "size_sqm": 76,
    "price_per_night": 250
  }
}
```

**Error Responses**:
- 400 Bad Request: Invalid ID format
- 404 Not Found: Property does not exist
- 500 Internal Server Error: Database error

---

## Email Service (`src/services/emailService.ts`)

### Architecture

**Class**: `EmailService` (singleton exported as `emailService`)

**Purpose**: Send professional HTML email confirmations with retry logic and logging

**Configuration** (from environment variables):
```typescript
{
  host: SMTP_HOST || 'smtp.gmail.com',
  port: SMTP_PORT || 587,
  secure: SMTP_SECURE || false,  // Use TLS
  auth: {
    user: SMTP_USER,               // Gmail address
    pass: SMTP_PASS                // App Password (not regular password)
  },
  from: SMTP_FROM || 'noreply@nosarabeachfront.com'
}
```

### Features

**Retry Mechanism**:
- Max retries: 3 attempts
- Retry delay: 5 seconds between attempts
- Each attempt logged separately
- Final failure logged with error details

**Email Templates**:

1. **HTML Template** (`generateConfirmationEmail()`):
   - Responsive design (max-width: 600px)
   - Professional gradient header (#667eea to #764ba2)
   - Guest details in styled box with border accent
   - Formatted dates (e.g., "Monday, February 1, 2025")
   - Unsubscribe link in footer
   - Clean, modern styling with white/gray color scheme

2. **Plain Text Template** (`generatePlainTextEmail()`):
   - Fallback for email clients that don't support HTML
   - Same content, plain text formatting
   - Includes all guest details
   - Unsubscribe URL at bottom

**Email Workflow**:
1. Inquiry created successfully in database
2. `sendConfirmationEmail(inquiry)` called asynchronously (non-blocking)
3. Email service attempts delivery (up to 3 times)
4. Success/failure logged to console and in-memory log
5. HTTP response sent to client (email result doesn't affect this)

**Logging**:
- In-memory log stores last 1000 emails
- Each log entry includes: timestamp, recipient, subject, success status, error message, retry count
- Console logging with emoji status indicators (✅/❌)
- Logs accessible via `getEmailLogs(limit)` method

**SMTP Configuration for Gmail**:
1. Enable 2FA on Google account
2. Generate App Password at https://myaccount.google.com/apppasswords
3. Use App Password (not account password) as `SMTP_PASS`
4. Gmail automatically enables TLS on port 587

### Methods

```typescript
// Send confirmation email with retry
async sendConfirmationEmail(inquiry: Inquiry): Promise<EmailResult>

// Verify SMTP connection (useful for health checks)
async verifyConnection(): Promise<boolean>

// Get recent email logs for debugging
getEmailLogs(limit: number = 100): EmailLog[]
```

**EmailResult Interface**:
```typescript
{
  success: boolean;      // true if delivered, false if all retries failed
  messageId?: string;    // SMTP message ID on success
  error?: string;        // Error message on failure
}
```

---

## Business Logic & Workflows

### Inquiry Submission Workflow

1. **Client submits form** → POST `/api/inquiries`
2. **Request validation**:
   - Parse request body
   - Convert date strings to Date objects
   - Convert guest_count to integer
3. **Model validation** (`InquiryModel.validate()`):
   - Check required fields
   - Validate email format
   - Ensure check-out date > check-in date
   - Verify guest_count >= 1
4. **Database insertion**:
   - Trim and normalize data (lowercase email)
   - INSERT into inquiries table
   - Return created record with auto-generated ID
5. **Email confirmation** (non-blocking):
   - Generate HTML and plain text templates
   - Attempt SMTP delivery (up to 3 retries)
   - Log result (success or failure)
6. **HTTP response**:
   - Return 201 Created with inquiry data
   - Email status NOT included in response

**Critical Timing**: Email sending is fire-and-forget to avoid blocking the HTTP response. Even if email fails, the inquiry is saved and the user receives a success response.

### Property Listing Workflow

1. **Client requests properties** → GET `/api/properties`
2. **Database query**:
   - Fetch properties with pagination (limit/offset)
   - Order by created_at DESC (newest first)
3. **JSONB parsing**:
   - Convert JSONB amenities to JavaScript array
   - Convert JSONB images to JavaScript array
   - Convert decimal fields to floats
4. **HTTP response**:
   - Return 200 OK with property array
   - Include count of returned items

### Error Handling Philosophy

**Layered Validation**:
1. **TypeScript types**: Compile-time type safety
2. **Model validation**: Application-level validation with detailed error messages
3. **Database constraints**: Defense-in-depth (CHECK constraints, NOT NULL, UNIQUE)

**Error Response Format**:
```json
{
  "success": false,
  "error": "Error category",
  "message": "Detailed error message"
}
```

**Logging Strategy**:
- All errors logged to console with context
- Database errors include full error object
- Email errors logged separately from HTTP errors
- No sensitive data (passwords, tokens) in logs

---

## Configuration & Environment

### Environment Variables

**File**: `.env` (not committed to git, use `.env.example` as template)

**Required Variables**:

```bash
# Database Configuration
DB_HOST=localhost          # PostgreSQL host
DB_PORT=5432               # PostgreSQL port
DB_NAME=jill_website       # Database name
DB_USER=postgres           # Database user
DB_PASSWORD=secure_pass    # Database password (keep secret!)

# Server Configuration
PORT=8200                  # HTTP server port
NODE_ENV=development       # Environment (development/production)

# Email Configuration (SMTP)
SMTP_HOST=smtp.gmail.com   # SMTP server
SMTP_PORT=587              # SMTP port (587 for TLS)
SMTP_SECURE=false          # Use TLS (true for SSL on port 465)
SMTP_USER=email@gmail.com  # SMTP username (Gmail address)
SMTP_PASS=app_password     # App Password (NOT account password)
SMTP_FROM="Nosara Beachfront Rentals <noreply@nosarabeachfront.com>"
```

**Optional Variables**:
- `BASE_URL`: Base URL for unsubscribe links (defaults to `http://localhost:8200`)

**Security Best Practices**:
- Never commit `.env` to version control
- Use different credentials for development/production
- Rotate SMTP passwords regularly
- Use App Passwords (not account passwords) for Gmail
- Restrict database user permissions to only necessary tables

### TypeScript Configuration

**File**: `tsconfig.json`

**Key Settings**:
```json
{
  "compilerOptions": {
    "target": "ES2020",              // Modern JavaScript output
    "module": "commonjs",            // Node.js module system
    "outDir": "./dist",              // Compiled JS output directory
    "rootDir": "./src",              // TypeScript source directory
    "strict": true,                  // Enable all strict type checks
    "esModuleInterop": true,         // CommonJS interop
    "skipLibCheck": true,            // Skip .d.ts file checking
    "forceConsistentCasingInFileNames": true,
    "resolveJsonModule": true,       // Allow JSON imports
    "moduleResolution": "node",      // Use Node.js resolution
    "types": ["node", "jest"]        // Include type definitions
  },
  "include": ["src/**/*"],           // Compile all files in src/
  "exclude": ["node_modules", "dist", "src/**/*.test.ts"]
}
```

**Strict Mode Benefits**:
- `noImplicitAny`: Catch missing type annotations
- `strictNullChecks`: Prevent null/undefined errors
- `strictFunctionTypes`: Ensure parameter type safety
- `strictPropertyInitialization`: Catch uninitialized class properties

### Jest Testing Configuration

**File**: `jest.config.js`

**Key Settings**:
```javascript
{
  preset: 'ts-jest',               // TypeScript support
  testEnvironment: 'node',         // Node.js test environment
  testMatch: ['**/__tests__/**/*.test.ts'],
  collectCoverageFrom: ['src/**/*.ts', '!src/migrations/**'],
  coverageThreshold: {
    global: {
      branches: 90,                // 90% branch coverage required
      functions: 90,               // 90% function coverage required
      lines: 90,                   // 90% line coverage required
      statements: 90               // 90% statement coverage required
    }
  },
  setupFilesAfterEnv: ['<rootDir>/src/__tests__/setup.ts'],
  testTimeout: 10000,              // 10s timeout for async tests
  maxWorkers: 1                    // Serial test execution (avoid DB race conditions)
}
```

**Testing Philosophy**:
- High coverage threshold (90%) ensures code quality
- Serial execution prevents database race conditions
- Setup file initializes test database
- All async operations tested with proper timeouts

---

## External Integrations

### PostgreSQL Database

**Connection**: Direct connection via `pg` library with connection pooling

**Features Used**:
- Connection pooling (max 20 connections)
- Parameterized queries (SQL injection protection)
- JSONB data type for flexible arrays
- Triggers for automatic timestamp updates
- CHECK constraints for data validation
- Indexes for query optimization

**Integration Points**:
- `src/config/database.ts`: Connection management
- All models: Data persistence and retrieval
- `src/migrations/migrate.ts`: Schema management

### SMTP Email Service

**Provider**: Configurable (Gmail recommended for development)

**Protocol**: SMTP with TLS (port 587) or SSL (port 465)

**Features Used**:
- HTML and plain text multipart emails
- Automatic retry on transient failures
- Connection verification
- Error logging and tracking

**Integration Points**:
- `src/services/emailService.ts`: Email delivery
- `src/routes/inquiries.ts`: Trigger confirmation emails
- Environment variables: SMTP credentials

**Gmail-Specific Setup**:
1. Enable 2FA on Google account
2. Generate App Password (not regular password)
3. Use App Password in `SMTP_PASS` environment variable
4. Gmail automatically handles TLS encryption

### Future Integration Opportunities

**Potential Additions**:
- **Stripe/PayPal**: Payment processing for deposits/bookings
- **Twilio**: SMS notifications for inquiries
- **Google Calendar**: Availability calendar sync
- **Slack**: Real-time inquiry notifications
- **Analytics**: Google Analytics or Mixpanel for tracking
- **CDN**: Cloudflare or AWS CloudFront for image delivery
- **Monitoring**: Sentry for error tracking, Datadog for performance

---

## Code Organization Principles

### File Naming Conventions

- **Models**: PascalCase, singular (`Inquiry.ts`, `Property.ts`)
- **Routes**: camelCase, plural (`inquiries.ts`, `properties.ts`)
- **Services**: camelCase with "Service" suffix (`emailService.ts`)
- **Config**: camelCase, descriptive (`database.ts`)
- **Tests**: Same name as tested file + `.test.ts` suffix
- **Migrations**: Numbered with descriptive name (`001_create_tables.sql`)

### Import Organization

**Standard Order**:
1. External dependencies (express, pg, nodemailer)
2. Internal modules (models, services, config)
3. Type-only imports (interfaces, types)

Example:
```typescript
// External dependencies
import { Router, Request, Response } from 'express';
import { Pool } from 'pg';

// Internal modules
import { InquiryModel, Inquiry } from '../models/Inquiry';
import { emailService } from '../services/emailService';

// Config
import { getPool } from '../config/database';
```

### Module Exports

**Patterns**:
- Models: Export class and interface separately
- Routes: Export factory function + default instance
- Services: Export class and singleton instance
- Config: Export functions, not classes

Example (routes):
```typescript
// Factory function for testing
export function createInquiriesRouter(
  inquiryModel?: InquiryModel,
  emailSvc?: EmailService
): Router { /* ... */ }

// Default instance for production
export default createInquiriesRouter();
```

### Error Handling Pattern

**Consistent Structure**:
```typescript
try {
  // Operation that may fail
  const result = await someAsyncOperation();
  return result;
} catch (error) {
  // Log error with context
  console.error('Error performing operation:', error);

  // Re-throw with user-friendly message
  throw new Error(`Failed to perform operation: ${error instanceof Error ? error.message : 'Unknown error'}`);
}
```

**Benefits**:
- Original error preserved in logs
- User-friendly error messages exposed via API
- Consistent error format across codebase

### Validation Pattern

**Two-Stage Validation**:
1. **Model-level**: Business logic validation (email format, date ranges)
2. **Database-level**: Constraint enforcement (NOT NULL, CHECK, UNIQUE)

**Model Validation Template**:
```typescript
private validate(data: Partial<T>): ValidationError[] {
  const errors: ValidationError[] = [];

  // Check each field
  if (!data.field || data.field.trim().length === 0) {
    errors.push({ field: 'field', message: 'Field is required' });
  }

  return errors;
}

// Usage in create/update methods
const errors = this.validate(data);
if (errors.length > 0) {
  throw new Error(`Validation failed: ${errors.map(e => `${e.field}: ${e.message}`).join(', ')}`);
}
```

### Testing Patterns

**Factory Pattern for Testability**:
```typescript
// Route factory accepts mock dependencies
export function createInquiriesRouter(
  inquiryModel?: InquiryModel,    // Injectable for mocking
  emailSvc?: EmailService         // Injectable for mocking
): Router {
  const model = inquiryModel || new InquiryModel();  // Use real if not provided
  const email = emailSvc || emailService;
  // ... route implementation
}
```

**Test Structure**:
```typescript
describe('Feature', () => {
  describe('Method', () => {
    it('should handle success case', async () => {
      // Arrange
      const input = { /* test data */ };

      // Act
      const result = await method(input);

      // Assert
      expect(result).toBeDefined();
      expect(result.field).toBe('expected value');
    });

    it('should handle error case', async () => {
      // Arrange, Act, Assert error thrown
      await expect(method(invalidInput)).rejects.toThrow('Expected error');
    });
  });
});
```

---

## Development Workflow

### Local Development Setup

1. **Install PostgreSQL**:
   ```bash
   # Ubuntu/Debian
   sudo apt-get install postgresql postgresql-contrib

   # macOS
   brew install postgresql
   ```

2. **Create Database**:
   ```bash
   createdb jill_website
   ```

3. **Clone & Install**:
   ```bash
   git clone <repository-url>
   cd jill-website
   npm install
   ```

4. **Configure Environment**:
   ```bash
   cp .env.example .env
   # Edit .env with your credentials
   ```

5. **Run Migrations**:
   ```bash
   npm run migrate
   ```

6. **Start Development Server**:
   ```bash
   npm run dev    # Runs ts-node for hot reload
   ```

### Development Scripts

```bash
npm run dev           # Development server with ts-node (hot reload)
npm run build         # Compile TypeScript to JavaScript
npm start             # Run compiled JavaScript (production)
npm run typecheck     # Verify TypeScript types without compiling
npm test              # Run all tests
npm run test:watch    # Run tests in watch mode
npm run test:coverage # Generate coverage report
npm run migrate       # Run database migrations
```

---

## Troubleshooting Common Issues

### Database Connection Fails
- Verify PostgreSQL is running: `sudo systemctl status postgresql`
- Check database exists: `psql -l | grep jill_website`
- Verify `.env` credentials match PostgreSQL config
- Test connection manually: `psql -h localhost -U postgres -d jill_website`

### Email Not Sending
- Verify SMTP credentials in `.env`
- Check Gmail App Password is correctly generated
- Ensure "Less secure app access" is NOT enabled
- Review logs: Email errors are logged to console

### Port Already in Use
- Find process: `lsof -ti:8200`
- Kill process: `lsof -ti:8200 | xargs kill -9`
- Change port in `.env`

### TypeScript Errors
- Clear build: `rm -rf dist`
- Reinstall: `rm -rf node_modules package-lock.json && npm install`
- Run typecheck: `npm run typecheck`

---

## Architectural Decision Records (ADRs)

### ADR-001: Why TypeScript?

**Context**: Need type safety for data models and API contracts

**Decision**: Use TypeScript with strict mode

**Rationale**:
- Catch type errors at compile time (before runtime)
- Better IDE autocomplete and refactoring
- Self-documenting code (types are documentation)
- Growing ecosystem and community support

**Consequences**:
- Learning curve for developers unfamiliar with TypeScript
- Compilation step required (adds build time)
- Occasional type gymnastics with third-party libraries

### ADR-002: Why PostgreSQL over NoSQL?

**Context**: Need database for structured data with relationships

**Decision**: Use PostgreSQL with JSONB for flexible fields

**Rationale**:
- ACID transactions ensure data consistency
- Powerful query capabilities (joins, aggregations)
- JSONB provides flexibility without sacrificing SQL benefits
- Battle-tested reliability and performance
- Strong constraints (CHECK, FOREIGN KEY, UNIQUE)

**Consequences**:
- Schema migrations required for changes
- Steeper learning curve than simple NoSQL
- Need to manage connection pooling

### ADR-003: Why Non-Blocking Email Sending?

**Context**: Email delivery can be slow (1-5 seconds)

**Decision**: Send emails asynchronously (fire-and-forget)

**Rationale**:
- HTTP response time reduced from 5s to <100ms
- Better user experience (no waiting for email)
- Email failures don't affect inquiry creation
- Retry logic handles transient failures

**Consequences**:
- User doesn't know if email failed (need separate monitoring)
- Email logs stored in memory (not persisted)
- Need retry logic to handle failures

### ADR-004: Why Factory Pattern for Routes?

**Context**: Need to test route handlers in isolation

**Decision**: Export factory functions that accept injectable dependencies

**Rationale**:
- Enables dependency injection for testing
- Can mock models and services in tests
- Default exports provide production instances
- Follows SOLID principles (dependency inversion)

**Consequences**:
- Slightly more complex route setup
- Need to maintain default and factory exports
- Testing becomes much easier (benefit outweighs cost)

### ADR-005: Why 90% Test Coverage Threshold?

**Context**: Need balance between code quality and development speed

**Decision**: Require 90% coverage across all metrics

**Rationale**:
- High coverage catches most bugs
- 100% coverage has diminishing returns
- 90% allows pragmatic exclusions (error handling edge cases)
- Forces thinking about testability during development

**Consequences**:
- May require extra effort to reach threshold
- Some edge cases may be overtested
- Encourages better code design (testable = good design)

---

## Key Files Reference

### Critical Files (Don't Modify Without Understanding)

- `src/config/database.ts`: Connection pool management (affects all DB operations)
- `src/models/Inquiry.ts`: Inquiry validation logic (affects data integrity)
- `src/models/Property.ts`: JSONB handling (critical for correct data storage)
- `src/services/emailService.ts`: Email retry logic (affects deliverability)
- `src/migrations/001_create_tables.sql`: Database schema (migrations are immutable)

### Configuration Files

- `.env`: Environment variables (never commit)
- `.env.example`: Template for environment variables
- `tsconfig.json`: TypeScript compiler options
- `jest.config.js`: Test configuration
- `package.json`: Dependencies and scripts

### Documentation Files

- `README.md`: User-facing documentation
- `DATABASE.md`: Database schema reference
- `CLAUDE.md`: This file (AI assistant context)

### Entry Points

- `src/server.ts`: Express server setup
- `src/index.ts`: Application startup
- `dist/server.js`: Compiled production entry point

---

## Conclusion

This documentation provides comprehensive context for AI assistants working on the Nosara Beachfront Rentals project. It covers:

- Project architecture and design decisions
- Database schema and relationships
- API endpoints and business logic
- Configuration and deployment
- Testing and development workflows
- Security considerations
- Future enhancement opportunities

For questions not covered here, refer to README.md (user guide) or DATABASE.md (schema reference).

**Last Updated**: January 14, 2025
**Documentation Version**: 1.0.0
**Project Version**: 1.0.0