# Contact Card — Technical Specification

## Overview

A web app where authenticated users create, manage, and optionally share contact cards. Each contact holds a name (required), email, phone number, and picture (thumbnail).

**Core features:**

- Email/password authentication (better-auth)
- CRUD operations on contacts
- Image upload with automatic thumbnail conversion
- Public sharing via unique link (per-contact toggle)

## Architecture

**Frontend** — Next.js App Router, TypeScript, TailwindCSS
**Backend** — Next.js Route Handlers (API routes)
**Data** — SQLite via Bun's built-in `bun:sqlite`, raw SQL
**Auth** — better-auth (email/password)
**Image processing** — sharp (resize to 200×200 WebP thumbnail, stored as BLOB in SQLite)

## Functional Requirements

### Authentication

- User can sign up with email and password
- User can log in / log out
- All contact operations require authentication

### Contacts

- User can create a contact (name required; email, phone, image optional)
- User can view a list of all their contacts
- User can view a single contact's details
- User can update any field on a contact
- User can delete a contact
- User can upload an image that is automatically resized to a 200×200 WebP thumbnail

### Sharing

- User can share a contact publicly (generates a unique token and URL)
- User can stop sharing (revokes the token; link stops working)
- Anyone with the share link can view the contact without authentication

## Data Model

better-auth auto-creates its own tables (`user`, `session`, `account`, `verification`) — do not manually define them.

### `contact`

| Column | Type | Description |
|---|---|---|
| id | TEXT PK | Unique ID (nanoid) |
| user_id | TEXT NOT NULL FK → user(id) | Owning user |
| name | TEXT NOT NULL | Contact name |
| email | TEXT | Contact email |
| phone | TEXT | Contact phone |
| image | BLOB | WebP thumbnail binary |
| image_type | TEXT | MIME type (e.g. `image/webp`) |
| is_public | INTEGER NOT NULL DEFAULT 0 | 1 = publicly shared |
| share_token | TEXT UNIQUE | URL token for public access |
| created_at | TEXT NOT NULL | ISO 8601 timestamp |
| updated_at | TEXT NOT NULL | ISO 8601 timestamp |

```sql
CREATE TABLE IF NOT EXISTS contact (
  id          TEXT PRIMARY KEY,
  user_id     TEXT NOT NULL REFERENCES user(id) ON DELETE CASCADE,
  name        TEXT NOT NULL,
  email       TEXT,
  phone       TEXT,
  image       BLOB,
  image_type  TEXT,
  is_public   INTEGER NOT NULL DEFAULT 0,
  share_token TEXT UNIQUE,
  created_at  TEXT NOT NULL DEFAULT (datetime('now')),
  updated_at  TEXT NOT NULL DEFAULT (datetime('now'))
);

CREATE INDEX idx_contact_user_id ON contact(user_id);
CREATE INDEX idx_contact_share_token ON contact(share_token);
```

## API Design

All `/api/contacts/*` routes require authentication. Auth routes (`/api/auth/**`) are handled by better-auth.

| Method | Path | Description | Request Body | Response |
|---|---|---|---|---|
| `*` | `/api/auth/**` | better-auth handlers | — | — |
| `GET` | `/api/contacts` | List user's contacts | — | `Contact[]` |
| `POST` | `/api/contacts` | Create contact | `{ name, email?, phone?, image?: File }` (multipart) | `Contact` |
| `GET` | `/api/contacts/:id` | Get contact detail | — | `Contact` |
| `PUT` | `/api/contacts/:id` | Update contact | `{ name?, email?, phone?, image?: File }` (multipart) | `Contact` |
| `DELETE` | `/api/contacts/:id` | Delete contact | — | `204` |
| `POST` | `/api/contacts/:id/share` | Enable public sharing | — | `{ shareUrl }` |
| `DELETE` | `/api/contacts/:id/share` | Disable public sharing | — | `204` |
| `GET` | `/api/contacts/:id/image` | Serve contact thumbnail | — | `image/webp` binary |
| `GET` | `/api/share/:token` | View public contact (no auth) | — | `Contact` |
| `GET` | `/api/share/:token/image` | Serve public contact image (no auth) | — | `image/webp` binary |

**`Contact` response shape:**

```json
{
  "id": "string",
  "name": "string",
  "email": "string | null",
  "phone": "string | null",
  "imageUrl": "string | null",
  "isPublic": false,
  "shareUrl": "string | null",
  "createdAt": "string",
  "updatedAt": "string"
}
```

## Frontend — Pages & Components

### Routes (App Router)

| Route | Purpose |
|---|---|
| `/` | Landing page (redirects to dashboard if logged in) |
| `/login` | Login form |
| `/signup` | Sign-up form |
| `/dashboard` | Lists all user's contacts |
| `/contacts/new` | Create contact form |
| `/contacts/:id` | View contact detail |
| `/contacts/:id/edit` | Edit contact form |
| `/share/:token` | Public contact view (no auth) |

### Key Components

| Component | Purpose |
|---|---|
| `Navbar` | Top nav with logo, auth state, logout |
| `ContactCard` | Summary card for dashboard grid |
| `ContactForm` | Reusable create/edit form with image upload |
| `ContactDetail` | Full contact view with share toggle |
| `ImageUpload` | File picker with client-side preview |
| `ShareToggle` | Button to enable/disable public sharing; shows copyable link |

## Non-Functional Requirements

**Performance**
- Thumbnail resizing happens server-side on upload (not on read)
- Contact list pagination or virtual scrolling if > 50 contacts

**Security**
- All CRUD endpoints verify `user_id` ownership
- Validate and sanitize inputs (name length, email format, phone format)
- Validate image MIME type (accept only JPEG, PNG, WebP, GIF) before processing
- Cap upload size (e.g. 5 MB)
- Share tokens are cryptographically random (nanoid, 21 chars)

**Reliability**
- Enable SQLite WAL mode for concurrent read performance
- Foreign key enforcement (`PRAGMA foreign_keys = ON`)
