# AGENTS.md — StorySign Backend

Context for AI agents and developers working on this repo. Read this before making changes.

## What this is

**StorySign Backend** — NestJS API for a mobile app with three roles:

| Role | Purpose |
|------|---------|
| **Reader** | Browse authors, upload books, request autographs, pay via Stripe |
| **Author** | Subscribe, accept/reject signing requests, burn signature into PDFs |
| **Admin** | Dashboard, user management, CMS, fees, subscriptions, push notifications |

- **API prefix:** `api/v1`
- **Swagger:** `/docs` (not under `/api/v1`)
- **Static uploads:** `/uploads/*` (served from disk, not S3)
- **Postman:** `postman/StorySign-API.postman_collection.json` (~108 requests, 3 flows)

---

## Stack

| Layer | Technology |
|-------|------------|
| Framework | NestJS 11 |
| Database | MongoDB (Mongoose) — Atlas in production |
| Cache / OTP | Redis (forgot-password flow) |
| Payments | Stripe Payment Intents + webhook |
| PDF signing | `pdf-lib` |
| Push | Firebase Cloud Messaging (optional) |
| Auth | JWT access + refresh tokens |

---

## Project layout

```
src/
  main.ts                    # Bootstrap, CORS, static /uploads, Swagger, rawBody for Stripe
  config/configuration.ts    # Env mapping (APP_URL, JWT, Stripe, upload dir, etc.)
  common/
    enums/                   # UserRole, AutographRequestStatus, PaymentType, ...
    utils/files.ts           # fileUrl(), mapProfilePicture(), uploadPath()
  modules/
    auth/                    # Signup, login, forgot-password OTP, change password
    readers/                 # Reader controller
    authors/                 # Author controller
    admin/                   # Admin controller + admin.service aggregations
    users/                   # User CRUD (no file URL mapping — do it in controllers/services)
    books/                   # Reader library + admin ebook management
    autograph-requests/      # Request workflow + pdf-signing.service.ts
    subscriptions/           # Author plans + Stripe checkout
    payments/                # Autograph fee, verify-success, Stripe service
    webhooks/                # POST /webhooks/stripe (payment_intent.succeeded)
    uploads/                 # Local disk storage (UploadService)
    notifications/           # In-app notifications + FCM
    platform/                # FAQs, privacy, settings, contact queries
    redis/                   # Redis client
    mail/                    # SMTP (OTP logged to console in dev)
scripts/seed-admin.ts        # npm run seed:admin
ecosystem.config.cjs         # PM2 production config
postman/                     # Collection + local environment
```

---

## Commands

```bash
npm install
npm run start:dev          # Local development (watch mode)
npm run build              # Compile to dist/
npm run start:prod         # node dist/src/main.js
npm run seed:admin         # Create default admin user
```

**Important:** Nest builds to `dist/src/main.js`, not `dist/main.js`. `start:prod` and PM2 both use `dist/src/main.js`.

Default admin (after seed):

```
Email: admin@storysign.com
Password: Admin@12345
```

---

## Environment variables

Copy `.env.example` → `.env`. Critical vars:

| Variable | Purpose |
|----------|---------|
| `MONGODB_URI` | MongoDB connection (Atlas in prod) |
| `REDIS_URL` | Redis for OTP codes |
| `JWT_ACCESS_SECRET` / `JWT_REFRESH_SECRET` | Auth tokens |
| `STRIPE_*` | Stripe keys + webhook secret |
| `APP_URL` | **Public base URL for file links** (see below) |
| `PORT` | Default `3000` — must match reverse proxy |
| `UPLOAD_DIR` | Default `uploads` (root data folder) |

### `APP_URL` (critical for production)

All public file URLs are built in `src/common/utils/files.ts`:

```typescript
fileUrl(config, 'profiles/abc.jpg')
// → {APP_URL}/uploads/profiles/abc.jpg
```

- DB stores **relative paths** only (e.g. `profiles/abc.jpg`).
- If `APP_URL` is missing, URLs default to `http://localhost:3000`.
- Production value:
  ```
  APP_URL=https://mockup.testdevlink.com/story-sign-backend
  ```
- No trailing slash (configuration trims it).

When adding endpoints that return images/PDFs, always use `fileUrl()` or `mapProfilePicture()` — never return raw DB paths.

---

## Production deployment

**Live base URL:** `https://mockup.testdevlink.com/story-sign-backend`

| Endpoint | URL |
|----------|-----|
| Health | `.../story-sign-backend/api/v1/health` |
| Swagger | `.../story-sign-backend/docs` |
| Uploads | `.../story-sign-backend/uploads/...` |

Architecture: **Apache/LiteSpeed** reverse proxy → **Node on port 3000**.

### PM2 (recommended)

After server reboot, an empty `pm2 list` causes **503** — the proxy is up but Node is not.

```bash
cd /path/to/story-sign-backend
npm install
npm run build
pm2 start ecosystem.config.cjs
pm2 save
pm2 startup    # run the command it prints, then pm2 save again
```

Verify:

```bash
pm2 list
pm2 logs story-sign-backend --lines 50
curl http://127.0.0.1:3000/api/v1/health   # expect 200
```

### Common production issues

| Symptom | Likely cause | Fix |
|---------|--------------|-----|
| **503** on all APIs | Node not running after reboot | `pm2 start` / restart app |
| **503** locally OK, 503 public | Wrong proxy port or `.htaccess` | Match `PORT` in `.env` to proxy target |
| **404** on public API | Proxy not stripping `/story-sign-backend` prefix | Fix `.htaccess` rewrite to `http://127.0.0.1:3000` |
| **localhost:3000** in image URLs | `APP_URL` not set on server | Set `APP_URL` in server `.env`, restart PM2 |
| **404** on upload images | Proxy not forwarding `/uploads` | Proxy `/story-sign-backend/uploads/*` to Node |
| App crashes on start | Missing env, Mongo/Redis down, no build | Check `pm2 logs`, run `npm run build` |

Do **not** run both cPanel Node.js App and PM2 on the same port.

---

## `.gitignore` gotcha

Root upload **data** is ignored:

```
/uploads          # only the root uploads/ folder (user files)
```

`src/modules/uploads/` (the Nest module) **must be tracked**. A previous bug had `uploads` in `.gitignore` which also ignored the source module — fixed to `/uploads`.

---

## API conventions

- **Global prefix:** `api/v1` (set in `main.ts` via `API_PREFIX`)
- **Auth:** `Authorization: Bearer <accessToken>`
- **Roles:** `@Roles(UserRole.READER | AUTHOR | ADMIN)` + `RolesGuard`
- **Multipart:** signup, book upload, autograph request, approve & send, profile edit
- **Stripe webhook:** `POST /api/v1/webhooks/stripe` — requires `rawBody: true` in `main.ts`
- **Validation:** global `ValidationPipe` with `whitelist` + `forbidNonWhitelisted`

### Autograph workflow

1. Reader creates request → Stripe PaymentIntent → `confirm-payment` or webhook
2. Author **accepts** → status `in_progress`
3. Author **approve & send** with signature image + placement → PDF signed → status `delivered`

### Signature placement (Flutter contract)

Store **normalized ratios (0–1)** relative to PDF page size:

```json
{
  "pageIndex": 0,
  "xRatio": 0.15,
  "yRatio": 0.82,
  "widthRatio": 0.25,
  "heightRatio": 0.08,
  "pageWidthPts": 612,
  "pageHeightPts": 792,
  "signatureImagePath": "signatures/abc.png"
}
```

Backend burns signature in `pdf-signing.service.ts` using `pdf-lib`. Y-axis is flipped for PDF coordinates.

---

## Coding guidelines for agents

1. **Minimize scope** — match existing patterns; don't refactor unrelated code.
2. **File URLs** — use `fileUrl()` / `mapProfilePicture()` from `common/utils/files.ts` in any response that exposes upload paths.
3. **DB storage** — always store relative paths (`profiles/x.jpg`), never full URLs.
4. **No commits** unless the user explicitly asks.
5. **No `.env` commits** — secrets stay local / on server only.
6. **Build output** — always `dist/src/main.js`; verify with `npm run build` after changes.
7. **Tests** — only add when requested or for meaningful behavior coverage.

---

## Module status (as of last session)

Phases 1–4 implemented:

- Auth (signup multipart, login, OTP forgot-password, change password)
- Reader (authors, library, autograph + payment, profile, stats, notifications, contact, FAQs)
- Author (subscriptions, pending requests, accept/reject/approve-send, profile)
- Admin (dashboard, users, ebooks, subscriptions, fees, queries, reports, push, CMS)
- Stripe webhooks (idempotent `payment_intent.succeeded`)
- FCM token registration
- Postman collection with auto-saved tokens/IDs

`README.md` API map section is **outdated** (still says "planned") — trust this file and the Postman collection for current API surface.

---

## Quick local smoke test

```bash
npm run start:dev
curl http://localhost:3000/api/v1/health
open http://localhost:3000/docs
```

Import Postman collection, run **Admin Flow → Login**, then reader/author flows.

---

## Related files

| File | Notes |
|------|-------|
| `.env.example` | Template for all env vars |
| `ecosystem.config.cjs` | PM2 app name: `story-sign-backend` |
| `postman/README.md` | Test order and multipart notes |
| `src/common/utils/files.ts` | Single source of truth for public file URLs |
