# pounce — Domain Availability Monitoring

A professional full-stack application for monitoring domain name availability with TLD price tracking and intelligence.

---

## ⚠️ Important: After Fresh Clone / Database Reset

If you just cloned the repo or reset the database, run these commands to populate data:

```bash
cd backend
source venv/bin/activate

# 1. Initialize database (creates tables)
python scripts/init_db.py

# 2. Scrape TLD prices (886+ TLDs from Porkbun)
python scripts/seed_tld_prices.py

# 3. (Optional) Scrape auctions immediately
python3 -c "
import asyncio
from app.services.auction_scraper import AuctionScraperService
from app.database import AsyncSessionLocal

async def scrape():
    scraper = AuctionScraperService()
    async with AsyncSessionLocal() as db:
        await scraper.scrape_all_platforms(db)
    await scraper.close()

asyncio.run(scrape())
"
```

Without these steps:
- TLD Pricing page shows only 18 TLDs (instead of 886+)
- Auctions page shows 0 auctions
- The scheduler will auto-scrape TLDs daily at 03:00 UTC and auctions hourly

---

## ⚡ Quick Start (Local Development)

**Terminal 1 - Backend:**
```bash
cd backend
source venv/bin/activate
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```

**Terminal 2 - Frontend:**
```bash
cd frontend
npm run dev
```

**Access:**
- Frontend: http://localhost:3000
- Backend: http://localhost:8000
- API Docs: http://localhost:8000/docs

### ⚠️ Common Console Warnings (Can be ignored)

| Warning | Explanation |
|---------|-------------|
| `SES Removing unpermitted intrinsics` | MetaMask/browser extension - harmless |
| `PostHog CORS blocked` | Normal on localhost, works in production |
| `-webkit-text-size-adjust` error | Old CSS prefixes - harmless |
| `-moz-column-gap` unknown | Old Firefox prefix - harmless |
| `preloaded font not used` | Next.js optimization - harmless |

### ❌ Actual Errors to Fix

| Error | Solution |
|-------|----------|
| `CORS request did not succeed` to localhost:8000 | **Start the backend!** |
| `500 Internal Server Error` | Check backend logs, run `python scripts/init_db.py` |

---

## Features

### Core Functionality
- **Domain Availability Monitoring** — Track any domain and get notified when it becomes available
- **Domain Portfolio Management** — Track domains you own with purchase price, value, and ROI
- **Domain Valuation Engine** — 100% transparent algorithmic valuation with detailed breakdown
- **Smart Pounce Auctions** — Aggregated auctions from GoDaddy, Sedo, NameJet & more
- **TLD Price Intelligence** — Compare prices across 886+ TLDs from Porkbun API
- **Automated Price Scraping** — Daily cronjob scrapes real TLD prices from public APIs
- **Price Change Alerts** — Email notifications when TLD prices change >5%
- **Expiration Tracking** — Monitor domain expiration dates and plan ahead
- **Real-time Checks** — RDAP, WHOIS, and DNS-based availability verification
- **Swiss Domain Support** — Special RDAP integration for .ch/.li domains via nic.ch
- **Historical Data** — Track price trends and availability history over time (12 months)
- **Email Notifications** — Domain available alerts, price changes, weekly digests

### Smart Pounce Auction Aggregator
- **Multi-Platform Search** — GoDaddy, Sedo, NameJet, SnapNames, DropCatch
- **Ending Soon** — Auctions ending in < 1 hour for sniping opportunities
- **Hot Auctions** — Most-bid domains across all platforms
- **Smart Opportunities** — AI-powered detection of undervalued domains
- **Affiliate Integration** — Click through to bid (no payment handling = Swiss GwG compliant)
- **Filters** — Search by keyword, TLD, platform, price range

### Domain Valuation (Transparent)
- **Clear Formula** — `Value = Base × Length × TLD × Keyword × Brand`
- **Factor Breakdown** — Each multiplier explained (e.g., "4-letter domain ×5.0")
- **Confidence Levels** — High/Medium/Low based on score consistency
- **Real TLD Costs** — Registration cost context from database
- **Value-to-Cost Ratio** — Investment decision metric
- **Disclaimer** — Clear limitations of algorithmic valuation

### User Experience
- **Modern UI** — Clean, minimalist dark-mode design with smooth animations
- **Responsive** — Fully optimized for desktop, tablet, and mobile
- **Authentication** — Secure JWT-based auth with subscription tiers
- **Dashboard** — Personal watchlist with status indicators and actions

### Security Features (v1.1)
- **Password Reset** — Secure token-based password recovery via email
- **Email Verification** — Optional email confirmation for new accounts
- **OAuth Login** — Sign in with Google or GitHub
- **Rate Limiting** — Protection against brute-force attacks (slowapi)
- **Stripe Payments** — Secure subscription payments with Stripe Checkout
- **Stripe Customer Portal** — Manage billing, view invoices, cancel subscriptions
- **Contact Form** — With email confirmation and spam protection
- **Newsletter** — Subscribe/unsubscribe with double opt-in

### Blog System (v1.3)
- **Blog Posts** — Create and manage blog articles
- **Categories & Tags** — Organize content
- **Featured Posts** — Highlight posts on homepage
- **View Tracking** — Analytics for post views
- **SEO Metadata** — Custom meta titles and descriptions
- **Draft/Publish** — Content workflow management

### Admin Panel (v1.3)
- **User Management** — List, search, upgrade, and manage users
- **Bulk Operations** — Upgrade multiple users at once
- **User Export** — Export all users to CSV
- **Price Alerts** — View all active TLD price alerts
- **Domain Health** — Manually trigger domain checks
- **Scheduler Status** — Monitor background jobs and last runs
- **Email Test** — Verify SMTP configuration
- **Activity Log** — Track admin actions
- **Blog Management** — Full CRUD for blog posts

### CI/CD Pipeline (v1.2)
- **GitHub Actions** — Automated CI/CD on push to main
- **Frontend Lint** — ESLint + TypeScript type checking
- **Backend Lint** — Ruff linter for Python
- **Docker Build** — Multi-stage build verification
- **Security Scan** — Trivy vulnerability scanner
- **Automated Deploy** — SSH deployment to production server

### TLD Detail Page (Professional)
- **Price Hero** — Instant view of cheapest price with direct registration link
- **Price Alert System** — Subscribe to email notifications for price changes
- **Interactive Price Chart** — 1M/3M/1Y/ALL time periods with hover tooltips
- **Domain Search** — Check availability directly on the TLD page
- **Registrar Comparison** — Full table with Register/Renew/Transfer prices + external links
- **Savings Calculator** — Shows how much you save vs most expensive registrar
- **Renewal Warning** — ⚠️ indicator when renewal price is >1.5x registration price
- **Related TLDs** — Smart suggestions based on current TLD (e.g., .com → .net, .org, .io)
- **Quick Stats** — Average price, price range, 30-day change, registrar count

---

## Tech Stack

### Backend
| Technology | Purpose |
|------------|---------|
| **Python 3.12+** | Runtime |
| **FastAPI** | Async web framework |
| **SQLAlchemy 2.0** | Async ORM |
| **SQLite/PostgreSQL** | Database |
| **APScheduler** | Background job scheduling |
| **python-whois** | WHOIS domain lookups |
| **whodap** | RDAP domain lookups |
| **httpx** | Async HTTP client (for custom RDAP) |
| **BeautifulSoup4** | Web scraping (backup) |
| **Pydantic** | Data validation |
| **JWT (python-jose)** | Authentication |

### Frontend
| Technology | Purpose |
|------------|---------|
| **Next.js 14** | React framework (App Router) |
| **TypeScript** | Type safety |
| **Tailwind CSS** | Styling |
| **Zustand** | State management |
| **Lucide React** | Icons (outlined style) |
| **clsx** | Conditional classes |

---

## Project Structure

```
pounce/
├── backend/
│   ├── app/
│   │   ├── api/                 # API endpoints
│   │   │   ├── auth.py          # Authentication (register, login, me)
│   │   │   ├── check.py         # Domain availability check
│   │   │   ├── domains.py       # Domain watchlist CRUD
│   │   │   ├── subscription.py  # User subscription management
│   │   │   ├── tld_prices.py    # TLD pricing data
│   │   │   └── admin.py         # Admin endpoints
│   │   ├── models/              # SQLAlchemy models
│   │   │   ├── user.py          # User model
│   │   │   ├── domain.py        # Domain watchlist model
│   │   │   ├── subscription.py  # Subscription tiers
│   │   │   └── tld_price.py     # TLD pricing model
│   │   ├── schemas/             # Pydantic schemas
│   │   ├── services/            # Business logic
│   │   │   ├── auth.py          # Auth service (JWT, hashing)
│   │   │   ├── domain_checker.py # RDAP/WHOIS/DNS checks
│   │   │   ├── email_service.py  # Email notifications (SMTP)
│   │   │   ├── price_tracker.py  # Price change detection & alerts
│   │   │   └── tld_scraper/     # TLD price scraping
│   │   │       ├── __init__.py
│   │   │       ├── base.py      # Base scraper class
│   │   │       ├── porkbun.py   # Porkbun API scraper
│   │   │       ├── tld_list.py  # TLD-List scraper (placeholder)
│   │   │       └── aggregator.py # Combines sources, saves to DB
│   │   ├── config.py            # Environment settings
│   │   ├── database.py          # Database connection
│   │   ├── main.py              # FastAPI app entry
│   │   └── scheduler.py         # Background jobs
│   ├── scripts/                # Utility scripts
│   │   ├── seed_tld_prices.py  # Initial TLD data scrape
│   │   ├── export_tld_prices.py # Export DB to JSON
│   │   ├── import_tld_prices.py # Import JSON to DB
│   │   └── tld_prices_export.json # Price data backup
│   ├── requirements.txt
│   ├── Dockerfile
│   └── env.example
├── frontend/
│   ├── src/
│   │   ├── app/                 # Next.js App Router pages
│   │   │   ├── page.tsx         # Homepage with domain checker
│   │   │   ├── layout.tsx       # Root layout
│   │   │   ├── dashboard/       # User dashboard
│   │   │   ├── login/           # Login page
│   │   │   ├── register/        # Registration page
│   │   │   ├── pricing/         # Pricing plans
│   │   │   └── tld-pricing/     # TLD price intelligence
│   │   │       ├── page.tsx     # TLD overview
│   │   │       └── [tld]/       # TLD detail page
│   │   ├── components/          # React components
│   │   │   ├── Header.tsx       # Navigation header
│   │   │   ├── Footer.tsx       # Site footer
│   │   │   └── DomainChecker.tsx # Domain search component
│   │   └── lib/                 # Utilities
│   │       ├── api.ts           # API client
│   │       └── store.ts         # Zustand store
│   ├── tailwind.config.ts       # Tailwind configuration
│   ├── package.json
│   ├── Dockerfile
│   └── env.example
├── docker-compose.yml           # Docker deployment
├── DEPLOYMENT.md                # Deployment guide
└── README.md
```

---

## Pages Overview

| Route | Description | Auth Required |
|-------|-------------|---------------|
| `/` | Homepage with domain checker, features, pricing preview | No |
| `/login` | User login | No |
| `/register` | User registration | No |
| `/forgot-password` | Request password reset | No |
| `/reset-password` | Reset password with token | No |
| `/verify-email` | Verify email with token | No |
| `/dashboard` | Personal domain watchlist | Yes |
| `/pricing` | Subscription plans with Stripe checkout | No |
| `/tld-pricing` | TLD price overview with trends | No* |
| `/tld-pricing/[tld]` | TLD detail with registrar comparison | Yes |
| `/auctions` | Smart Pounce auction aggregator | No* |
| `/contact` | Contact form | No |
| `/about` | About us | No |
| `/blog` | Blog posts listing | No |
| `/blog/{slug}` | Blog post detail | No |
| `/admin` | Admin panel | Yes (Admin) |
| `/privacy` | Privacy policy | No |
| `/terms` | Terms of service | No |
| `/imprint` | Legal imprint | No |

*Unauthenticated users see limited data with shimmer effects

---

## Full Setup Guide

### Prerequisites
- Python 3.12+
- Node.js 18+
- npm or yarn

### 🚀 One-Command Setup (Fresh Install)

```bash
# Clone repository
git clone https://git.6bit.ch/yvg/pounce.git
cd pounce

# Run deployment script (installs everything + initializes database)
chmod +x deploy.sh
./deploy.sh
```

### 🏃 Starting the Application

**Option A: Two Terminals (Recommended for Development)**

```bash
# Terminal 1 - Backend
cd backend
source venv/bin/activate
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000

# Terminal 2 - Frontend
cd frontend
npm run dev
```

**Option B: Single Command (Quick)**

```bash
chmod +x start.sh
./start.sh
```

**Option C: PM2 (Production)**

```bash
# Backend
cd backend && source venv/bin/activate
pm2 start "uvicorn app.main:app --host 0.0.0.0 --port 8000" --name pounce-api

# Frontend
cd frontend
npm run build
pm2 start "npm start" --name pounce-web
```

### 🔧 Manual Setup (Step by Step)

#### 1. Clone Repository

```bash
git clone https://git.6bit.ch/yvg/pounce.git
cd pounce
```

#### 2. Backend Setup

```bash
cd backend

# Create virtual environment
python3 -m venv venv
source venv/bin/activate  # Linux/macOS
# .\venv\Scripts\activate  # Windows

# Install dependencies
pip install -r requirements.txt

# Create environment file
cp env.example .env

# Generate secret key
python -c "import secrets; print(secrets.token_hex(32))"
# Copy the output and paste into .env as SECRET_KEY=...

# Initialize database (creates tables + seeds basic data)
python scripts/init_db.py

# Scrape TLD prices from Porkbun API (886+ TLDs)
python scripts/seed_tld_prices.py

# (Optional) Scrape auctions - or let the scheduler do it hourly
python3 -c "
import asyncio
from app.services.auction_scraper import AuctionScraperService
from app.database import AsyncSessionLocal

async def scrape():
    scraper = AuctionScraperService()
    async with AsyncSessionLocal() as db:
        result = await scraper.scrape_all_platforms(db)
        print(f'Scraped auctions: {result}')
    await scraper.close()

asyncio.run(scrape())
"
```

Start backend:
```bash
uvicorn app.main:app --reload --host 0.0.0.0 --port 8000
```

#### 3. Frontend Setup

```bash
cd frontend

# Install dependencies
npm install

# Create environment file
echo "NEXT_PUBLIC_API_URL=http://localhost:8000" > .env.local

# Start development server
npm run dev
```

#### 4. Verify Everything Works

```bash
# Check backend health
curl http://localhost:8000/health
# Should return: {"status":"healthy","service":"DomainWatch","version":"1.0.0"}

# Open frontend
open http://localhost:3000
```

### 📍 Application URLs

| Service | URL | Description |
|---------|-----|-------------|
| Frontend | http://localhost:3000 | Main application |
| Backend API | http://localhost:8000 | REST API |
| API Docs (Swagger) | http://localhost:8000/docs | Interactive API docs |
| API Docs (ReDoc) | http://localhost:8000/redoc | Alternative docs |

---

## API Endpoints

### Authentication
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/auth/register` | Register new user (sends verification email) |
| POST | `/api/v1/auth/login` | Login (returns JWT) |
| GET | `/api/v1/auth/me` | Get current user |
| PUT | `/api/v1/auth/me` | Update current user |
| POST | `/api/v1/auth/forgot-password` | Request password reset |
| POST | `/api/v1/auth/reset-password` | Reset password with token |
| POST | `/api/v1/auth/verify-email` | Verify email with token |
| POST | `/api/v1/auth/resend-verification` | Resend verification email |

### Domain Check
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/check` | Check domain availability |

### Domain Watchlist
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/domains` | List watched domains |
| POST | `/api/v1/domains` | Add domain to watchlist |
| DELETE | `/api/v1/domains/{id}` | Remove domain |
| POST | `/api/v1/domains/{id}/refresh` | Refresh domain status |
| GET | `/api/v1/domains/{id}/history` | Get check history |

### TLD Prices
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/tld-prices/overview` | Get all TLDs overview |
| GET | `/api/v1/tld-prices/trending` | Get trending TLDs |
| GET | `/api/v1/tld-prices/{tld}` | Get TLD details |
| GET | `/api/v1/tld-prices/{tld}/history` | Get price history |
| GET | `/api/v1/tld-prices/{tld}/compare` | Compare registrar prices |

#### Price Data Consistency

All TLD price endpoints use a unified pricing strategy:

1. **Static TLDs (18 popular)**: Rich multi-registrar data (5+ registrars per TLD)
   - .com, .net, .org, .io, .ai, .dev, .app, .xyz, .tech, .online, .store, .me, .info, .biz, .ch, .de, .uk, .co
   
2. **Database TLDs (869+)**: Scraped from Porkbun API (single registrar)

**Price Calculation** (consistent across all endpoints):
```
avg_price = sum(registrar_prices) / count(registrars)
min_price = min(registrar_prices)
max_price = max(registrar_prices)
```

This ensures identical prices on:
- Overview table (`/overview`)
- Detail page (`/{tld}`)
- Compare page (`/{tld}/compare`)
- Trending cards (`/trending`)

### Subscription & Payments
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/subscription` | Get current subscription |
| GET | `/api/v1/subscription/tiers` | Get available tiers |
| GET | `/api/v1/subscription/features` | Get current features |
| POST | `/api/v1/subscription/checkout` | Create Stripe checkout session |
| POST | `/api/v1/subscription/portal` | Create Stripe customer portal |
| POST | `/api/v1/subscription/cancel` | Cancel subscription |

### Price Alerts
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/price-alerts` | List user's price alerts |
| POST | `/api/v1/price-alerts` | Create new price alert |
| GET | `/api/v1/price-alerts/status/{tld}` | Check alert status for TLD |
| GET | `/api/v1/price-alerts/{tld}` | Get alert for specific TLD |
| PUT | `/api/v1/price-alerts/{tld}` | Update alert settings |
| DELETE | `/api/v1/price-alerts/{tld}` | Delete alert |
| POST | `/api/v1/price-alerts/{tld}/toggle` | Toggle alert on/off |

### Contact & Newsletter
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/contact` | Submit contact form |
| POST | `/api/v1/contact/newsletter/subscribe` | Subscribe to newsletter |
| POST | `/api/v1/contact/newsletter/unsubscribe` | Unsubscribe from newsletter |
| GET | `/api/v1/contact/newsletter/status` | Check subscription status |

### Webhooks
| Method | Endpoint | Description |
|--------|----------|-------------|
| POST | `/api/v1/webhooks/stripe` | Stripe webhook handler |

### Smart Pounce (Auctions)
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/auctions` | Search auctions across platforms |
| GET | `/api/v1/auctions/ending-soon` | Auctions ending within X hours |
| GET | `/api/v1/auctions/hot` | Most-bid auctions |
| GET | `/api/v1/auctions/opportunities` | AI-powered opportunities (auth) |
| GET | `/api/v1/auctions/stats` | Platform statistics |

### OAuth
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/oauth/providers` | List enabled OAuth providers |
| GET | `/api/v1/oauth/google/login` | Initiate Google OAuth |
| GET | `/api/v1/oauth/google/callback` | Handle Google callback |
| GET | `/api/v1/oauth/github/login` | Initiate GitHub OAuth |
| GET | `/api/v1/oauth/github/callback` | Handle GitHub callback |

### Blog
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/blog/posts` | List published posts |
| GET | `/api/v1/blog/posts/featured` | Get featured posts |
| GET | `/api/v1/blog/posts/categories` | Get all categories |
| GET | `/api/v1/blog/posts/{slug}` | Get single post |
| GET | `/api/v1/blog/admin/posts` | Admin: List all posts |
| POST | `/api/v1/blog/admin/posts` | Admin: Create post |
| GET | `/api/v1/blog/admin/posts/{id}` | Admin: Get post |
| PATCH | `/api/v1/blog/admin/posts/{id}` | Admin: Update post |
| DELETE | `/api/v1/blog/admin/posts/{id}` | Admin: Delete post |
| POST | `/api/v1/blog/admin/posts/{id}/publish` | Admin: Publish post |
| POST | `/api/v1/blog/admin/posts/{id}/unpublish` | Admin: Unpublish post |

### Admin
| Method | Endpoint | Description |
|--------|----------|-------------|
| GET | `/api/v1/admin/stats` | Get platform statistics |
| GET | `/api/v1/admin/users` | List all users |
| GET | `/api/v1/admin/users/export` | Export users as CSV |
| POST | `/api/v1/admin/users/bulk-upgrade` | Bulk upgrade users |
| GET | `/api/v1/admin/users/{id}` | Get user details |
| PATCH | `/api/v1/admin/users/{id}` | Update user |
| DELETE | `/api/v1/admin/users/{id}` | Delete user |
| POST | `/api/v1/admin/users/{id}/upgrade` | Upgrade single user |
| GET | `/api/v1/admin/price-alerts` | List all price alerts |
| POST | `/api/v1/admin/domains/check-all` | Trigger domain checks |
| GET | `/api/v1/admin/newsletter` | List newsletter subs |
| GET | `/api/v1/admin/newsletter/export` | Export newsletter |
| POST | `/api/v1/admin/scrape-tld-prices` | Trigger TLD scrape |
| GET | `/api/v1/admin/tld-prices/stats` | Get TLD stats |
| GET | `/api/v1/admin/system/health` | System health check |
| GET | `/api/v1/admin/system/scheduler` | Scheduler status |
| POST | `/api/v1/admin/system/test-email` | Send test email |
| GET | `/api/v1/admin/activity-log` | Get activity log |

---

## Subscription Tiers

| Feature | Scout (Free) | Trader (€19/mo) | Tycoon (€49/mo) |
|---------|--------------|-----------------|------------------|
| Watchlist Domains | 5 | 50 | 500 |
| Portfolio Domains | — | 25 | Unlimited |
| Check Frequency | Daily | Hourly | 10 min |
| Notifications | Email | SMS/Telegram | + Webhooks |
| Domain Valuation | — | ✓ | ✓ |
| SEO Metrics | — | — | ✓ |
| Check History | — | 90 days | Unlimited |
| API Access | — | — | ✓ |
| Bulk Tools | — | — | ✓ |

---

## Environment Variables

### Backend (.env)

| Variable | Description | Default |
|----------|-------------|---------|
| `DATABASE_URL` | Database connection string | `sqlite+aiosqlite:///./domainwatch.db` |
| `SECRET_KEY` | JWT signing key (min 32 chars) | **Required** |
| `ALLOWED_ORIGINS` | Allowed origins (comma-separated) | `http://localhost:3000` |
| `ACCESS_TOKEN_EXPIRE_MINUTES` | JWT token lifetime | `10080` (7 days) |
| `SITE_URL` | Frontend URL (for email links) | `http://localhost:3000` |
| `REQUIRE_EMAIL_VERIFICATION` | Require email verification | `false` |
| `SCHEDULER_CHECK_INTERVAL_HOURS` | Domain check interval | `24` |
| **SMTP Settings (Zoho)** | | |
| `SMTP_HOST` | Email server host | `smtp.zoho.eu` |
| `SMTP_PORT` | Email server port | `465` |
| `SMTP_USER` | Email username | `hello@pounce.ch` |
| `SMTP_PASSWORD` | Email password | **Required** |
| `SMTP_FROM_EMAIL` | Sender email | `hello@pounce.ch` |
| `SMTP_USE_SSL` | Use SSL (port 465) | `true` |
| `SMTP_USE_TLS` | Use STARTTLS (port 587) | `false` |
| `CONTACT_EMAIL` | Contact form recipient | `hello@pounce.ch` |
| **Stripe Settings** | | |
| `STRIPE_SECRET_KEY` | Stripe API secret key | Optional |
| `STRIPE_WEBHOOK_SECRET` | Stripe webhook secret | Optional |
| `STRIPE_PRICE_TRADER` | Trader plan Price ID | Optional |
| `STRIPE_PRICE_TYCOON` | Tycoon plan Price ID | Optional |

### Frontend (.env.local)

| Variable | Description | Default |
|----------|-------------|---------|
| `NEXT_PUBLIC_API_URL` | Backend API URL | `http://localhost:8000` |

---

## Production Deployment

### Required Cron Jobs Summary

| Job | Frequency | Command |
|-----|-----------|---------|
| TLD Prices | Daily 03:00 UTC | `python scripts/seed_tld_prices.py` |
| Domain Check | Daily 06:00 UTC | Built-in scheduler |
| Auction Scrape | Hourly :30 | Built-in scheduler |
| Price Alerts | Daily 04:00 UTC | Built-in scheduler |

**Note:** All cron jobs run automatically via APScheduler when the backend is running. External cron is only needed if you disable the built-in scheduler.

### Docker (Recommended)

```bash
# Clone repository
git clone https://git.6bit.ch/yvg/pounce.git
cd pounce

# Set environment variables
export SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")

# Build and start
docker-compose up -d --build
```

### Manual Deployment

See [DEPLOYMENT.md](./DEPLOYMENT.md) for detailed instructions including:
- Systemd service setup
- PM2 for Node.js
- Nginx reverse proxy configuration
- SSL/HTTPS with Let's Encrypt
- PostgreSQL database setup

---

## Domain Checking Methods

pounce uses multiple methods to verify domain availability:

1. **Custom RDAP** — For TLDs with own RDAP servers (e.g., `.ch`, `.li` via `rdap.nic.ch`)
2. **RDAP via whodap** — Modern protocol with detailed data, supported by major registries
3. **WHOIS (Fallback)** — Traditional protocol for domains without RDAP support
4. **DNS (Quick check)** — Fast initial availability check via DNS queries

The system automatically falls back to the next method if one fails.

### Supported TLDs with Custom RDAP

| TLD | RDAP Server | Notes |
|-----|-------------|-------|
| `.ch` | `rdap.nic.ch` | Swiss domains - WHOIS blocked |
| `.li` | `rdap.nic.ch` | Liechtenstein - same registry |

### Standard RDAP TLDs

`.com`, `.net`, `.org`, `.info`, `.biz`, `.io`, `.co`, `.ai`, `.app`, `.dev`, `.xyz`, `.me`, and many more.

---

## TLD Price Scraping

pounce automatically scrapes TLD prices from public sources for price intelligence and trend tracking.

### Data Source

**Primary:** Porkbun Public API (no API key required!)

```
POST https://api.porkbun.com/api/json/v3/pricing/get
```

- ✅ **886+ TLDs** with pricing
- ✅ Registration, Renewal, Transfer prices
- ✅ No authentication needed
- ✅ Updated daily via scheduler

### Scheduler Jobs (Built-in APScheduler)

The backend includes a built-in scheduler that starts automatically with the application. **No external cron jobs needed!**

| Job | Schedule | Description |
|-----|----------|-------------|
| **Domain Check** | Daily at configured hour (default: 06:00 UTC) | Checks all watched domains for availability |
| **TLD Price Scrape** | Daily at 03:00 UTC | Scrapes 886+ TLD prices from Porkbun API |
| **Price Change Alerts** | Daily at 04:00 UTC | Sends email for price changes >5% |
| **Auction Scrape** | Every hour at :30 | Scrapes domain auctions from ExpiredDomains |

**Scheduler is enabled by default.** When you run `uvicorn app.main:app`, the scheduler starts automatically via the lifespan handler.

### External Cron Jobs (Production - Optional)

If you want more control, you can disable the built-in scheduler and use system cron:

```bash
# Disable built-in scheduler by setting env var
DISABLE_SCHEDULER=true

# Add to crontab (crontab -e)
# TLD prices daily at 03:00 UTC
0 3 * * * cd /path/to/pounce/backend && source venv/bin/activate && python scripts/seed_tld_prices.py >> /var/log/pounce/tld_scrape.log 2>&1

# Domain check daily at 06:00 UTC  
0 6 * * * cd /path/to/pounce/backend && source venv/bin/activate && python -c "import asyncio; from app.scheduler import check_all_domains; asyncio.run(check_all_domains())" >> /var/log/pounce/domain_check.log 2>&1

# Auction scrape hourly at :30
30 * * * * cd /path/to/pounce/backend && source venv/bin/activate && python -c "import asyncio; from app.scheduler import scrape_auctions; asyncio.run(scrape_auctions())" >> /var/log/pounce/auction_scrape.log 2>&1

# Price alerts daily at 04:00 UTC
0 4 * * * cd /path/to/pounce/backend && source venv/bin/activate && python -c "import asyncio; from app.scheduler import check_price_changes; asyncio.run(check_price_changes())" >> /var/log/pounce/price_alerts.log 2>&1
```

### PM2 with Scheduler (Recommended for Production)

The easiest production setup is PM2 which keeps the backend running with its built-in scheduler:

```bash
cd backend && source venv/bin/activate
pm2 start "uvicorn app.main:app --host 0.0.0.0 --port 8000" --name pounce-api
pm2 save
pm2 startup
```

This way, the APScheduler handles all cron jobs automatically.

### Manual Scrape

Trigger a manual TLD price scrape:

```bash
curl -X POST http://localhost:8000/api/v1/admin/scrape-tld-prices
```

Response:
```json
{
  "message": "TLD price scrape completed",
  "status": "success",
  "tlds_scraped": 886,
  "prices_saved": 886,
  "sources_succeeded": 1
}
```

### Database Stats

Get current TLD price data statistics:

```bash
curl http://localhost:8000/api/v1/admin/tld-prices/stats
```

Response:
```json
{
  "total_records": 886,
  "unique_tlds": 886,
  "unique_registrars": 1,
  "latest_record": "2025-12-08T08:04:54",
  "data_range_days": 0
}
```

### Price Data Model

```python
TLDPrice:
  - tld: str              # e.g., "com", "io", "ai"
  - registrar: str        # e.g., "porkbun"
  - registration_price: float
  - renewal_price: float
  - transfer_price: float
  - currency: str         # "USD"
  - recorded_at: datetime # For historical tracking
```

### Future Data Sources

The scraper architecture supports multiple sources:

1. **Porkbun API** ✅ (active)
2. **TLD-List.com** (blocked - placeholder)
3. **Namecheap Affiliate** (requires signup)
4. **Cloudflare** (requires account)

---

## Email Notifications

pounce supports automated email notifications for domain availability and price changes.

### Setup (Zoho Mail)

Configure SMTP settings in your `.env`:

```env
# Zoho Mail Configuration (Recommended)
SMTP_HOST=smtp.zoho.eu
SMTP_PORT=465
SMTP_USER=hello@pounce.ch
SMTP_PASSWORD=your-zoho-app-password
SMTP_FROM_EMAIL=hello@pounce.ch
SMTP_FROM_NAME=pounce
SMTP_USE_TLS=false
SMTP_USE_SSL=true
CONTACT_EMAIL=hello@pounce.ch
```

Alternative providers:
```env
# Gmail (port 587, STARTTLS)
SMTP_HOST=smtp.gmail.com
SMTP_PORT=587
SMTP_USE_SSL=false
SMTP_USE_TLS=true
```

### Notification Types

| Type | Trigger | Description |
|------|---------|-------------|
| **Domain Available** | Watched domain becomes available | Instant alert to domain owner |
| **Price Change Alert** | TLD price changes >5% | Daily batch after price scrape |
| **Weekly Digest** | Every Sunday | Summary of watched domains & price trends |

### Email Templates

All emails feature:
- Dark mode branded design matching the app
- Clear call-to-action buttons
- Direct links to register domains
- Unsubscribe options

### Test Email

```bash
# Via Python
cd backend && python3 -c "
import asyncio
from app.services.email_service import email_service

async def test():
    result = await email_service.send_domain_available_alert(
        'test@example.com',
        'example.com',
        'Test User'
    )
    print('Email sent:', result)

asyncio.run(test())
"
```

---

## Design System

### Colors
- **Background:** Dark (`#0a0a0a`)
- **Foreground:** Light (`#fafafa`)
- **Accent:** Teal (`#00d4aa`)
- **Warning:** Orange (`#f97316`)
- **Success:** Green (same as accent)

### Typography
- **Display:** Playfair Display (serif)
- **Body:** Inter (sans-serif)
- **Mono:** JetBrains Mono (code)

### Components
- All icons use outlined style (Lucide React)
- Minimalist, award-winning aesthetic
- Smooth animations and transitions
- Responsive design (mobile-first)

---

## Development

### Backend

```bash
cd backend
source venv/bin/activate

# Run with auto-reload
uvicorn app.main:app --reload

# API docs available at:
# http://localhost:8000/docs (Swagger)
# http://localhost:8000/redoc (ReDoc)
```

### Frontend

```bash
cd frontend

# Development server
npm run dev

# Lint
npm run lint

# Build for production
npm run build

# Start production server
npm start
```

---

## Troubleshooting

### Backend won't start

1. Check Python version: `python3 --version` (needs 3.12+)
2. Ensure virtual environment is activated
3. Check `.env` file exists and has valid `SECRET_KEY`
4. Check for syntax errors in config files

### Frontend can't connect to backend

1. Ensure backend is running on port 8000
2. Check `NEXT_PUBLIC_API_URL` in `.env.local`
3. Verify CORS origins in backend `.env`
4. Check browser console for CORS errors

### Domain checks failing

1. Some TLDs may have rate limits
2. Certain ccTLDs require special handling (see below)
3. Check backend logs for specific errors

**Swiss/Liechtenstein domains (.ch, .li):**
- WHOIS is blocked for automated requests
- pounce uses the official `rdap.nic.ch` API instead
- This is automatic - no configuration needed

**Other ccTLDs:**
- Some country code TLDs don't support RDAP
- pounce falls back to DNS checking
- Results may be less detailed (no registrar info)

### Database errors

1. Delete `domainwatch.db` to reset (dev only)
2. Check database URL format in `.env`
3. Ensure write permissions on database directory

---

## License

MIT License

---

## Support

For issues and feature requests, please open a GitHub issue or contact support@pounce.dev
