# FaveCelebs — Product Specification

> **Platform policy:** FaveCelebs is a **safe, non-explicit creator platform** for entertainment, education, fitness, music, lifestyle, behind-the-scenes content, and fan engagement. Adult, sexually explicit, or otherwise unsafe content is **prohibited** and is removed on detection. All creators must complete identity verification (KYC). Payments are restricted to users aged 18+.

---

## Table of contents

1. [User stories & use cases](#1-user-stories--use-cases)
2. [Database design](#2-database-design)
3. [REST API design](#3-rest-api-design)
4. [NestJS backend architecture](#4-nestjs-backend-architecture)
5. [Security & compliance](#5-security--compliance)

---

## 1. User stories & use cases

Roles: **Viewer** (unauthenticated or browsing), **Subscriber** (authenticated fan), **Creator** (KYC-verified content publisher), **Admin** (staff: moderator, finance, super-admin).

### 1.1 Viewer

- **Account onboarding**
  - *As a viewer*, I can browse public creator profiles and free posts without an account.
  - *As a viewer*, I can sign up with email or Google to become a subscriber.
- **Discovery**
  - *As a viewer*, I can search creators by name, category, or tag.
  - *As a viewer*, I can preview a creator's bio, free posts, subscription price, and rating.
- **Safety**
  - *As a viewer*, I can report a profile or public post without signing in (rate-limited).

### 1.2 Subscriber

- **Onboarding**
  - *As a subscriber*, I confirm I am 18+ during signup before any payment action.
  - *As a subscriber*, I verify my email and can enable 2FA.
- **Payments**
  - *As a subscriber*, I can add a payment method (mobile money / card via Lipila or Stripe-compatible processor).
  - *As a subscriber*, I can see all charges, refunds, and receipts in a billing history.
- **Subscription access**
  - *As a subscriber*, I can subscribe to a creator on a monthly plan and auto-renew until I cancel.
  - *As a subscriber*, I can cancel; access continues until the period ends.
- **Paid content access**
  - *As a subscriber*, I can unlock individual premium posts (PPV) with a one-time purchase.
  - *As a subscriber*, purchased content remains accessible even if I unsubscribe.
- **Tips/donations**
  - *As a subscriber*, I can tip a creator from a post or DM with an optional message.
- **Messaging**
  - *As a subscriber*, I can DM creators I'm subscribed to (creators control inbound DM rules).
  - *As a subscriber*, I can block, mute, or report another user.
- **Reporting**
  - *As a subscriber*, I can report posts, messages, or profiles with a reason and optional evidence.

### 1.3 Creator

- **Creator onboarding (KYC)**
  - *As a creator*, I submit government ID + selfie liveness check; account remains in `pending_kyc` until approved.
  - *As a creator*, I accept the Creator Agreement (no explicit content, tax responsibility, payout terms).
  - *As a creator*, I set my display name, handle, category, bio, banner, and avatar.
- **Monetization setup**
  - *As a creator*, I configure my monthly subscription price and optional tiered plans.
  - *As a creator*, I add my payout method (bank or mobile money) after KYC approval.
- **Content publishing**
  - *As a creator*, I upload photos, videos, audio, or text posts and mark them free, subscribers-only, or PPV.
  - *As a creator*, I can schedule posts and edit captions/pricing post-publish.
- **Dashboard / analytics**
  - *As a creator*, I see subscribers, churn, revenue (subs + PPV + tips), top posts, and traffic sources.
- **Messaging**
  - *As a creator*, I can mass-message my subscribers, send PPV attachments, and restrict who can DM me.
- **Payouts**
  - *As a creator*, I see pending earnings, payout schedule, fees, and tax forms.
- **Safety**
  - *As a creator*, I can block users, report abuse, and appeal moderation decisions.

### 1.4 Admin

- **Moderation queue**
  - *As a moderator*, I review reported content with severity priority and take action (warn, hide, remove, suspend, ban).
- **KYC review**
  - *As a moderator*, I approve or reject creator KYC submissions and request resubmission.
- **Fraud / payments**
  - *As a finance admin*, I review chargebacks, refund requests, and suspicious payout patterns.
- **Platform analytics**
  - *As an admin*, I see DAU/MAU, GMV, take-rate, refund rate, report volume, and moderation SLAs.
- **Audit**
  - *As a super-admin*, I view immutable audit logs of all admin actions.

### 1.5 Key use cases (flows)

- **UC-01 Subscribe to creator** — Subscriber → checkout → payment provider → webhook → `subscriptions.status=active` → access granted.
- **UC-02 Purchase PPV post** — Subscriber → unlock → payment → webhook → `purchases` row → media signed URL issued.
- **UC-03 Send tip** — Subscriber → tip dialog → payment → webhook → `tips` row → notification to creator.
- **UC-04 Report → moderation** — Reporter → `reports` row (pending) → moderator queue → `moderation_actions` → content hidden / user notified → appeal window.
- **UC-05 Creator KYC** — Creator submits docs → identity provider returns verdict → admin final approval → `creator_profiles.kyc_status=approved` → payouts unlocked.
- **UC-06 Payout cycle** — Cron rolls eligible earnings → `payouts` row (pending) → provider transfer → webhook → `paid` / `failed`.

---

## 2. Database design

> Conventions: `id UUID PK`, `created_at`, `updated_at` on every table. Money stored as `NUMERIC(12,2)` in a single platform currency (multi-currency = separate `currency` column). All timestamps `TIMESTAMPTZ`. Soft deletes via `deleted_at` where noted.

### 2.1 Tables

#### `users`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| email | CITEXT UNIQUE NOT NULL | |
| password_hash | TEXT | nullable for OAuth-only |
| username | CITEXT UNIQUE NOT NULL | handle |
| display_name | TEXT | |
| avatar_url | TEXT | |
| role | ENUM(`viewer`,`subscriber`,`creator`,`moderator`,`admin`) | default `subscriber` |
| dob | DATE | required before any payment |
| age_verified | BOOLEAN | derived from dob ≥ 18 |
| email_verified_at | TIMESTAMPTZ | |
| two_factor_enabled | BOOLEAN | |
| status | ENUM(`active`,`suspended`,`banned`,`deleted`) | |
| last_login_at | TIMESTAMPTZ | |

Indexes: `(email)`, `(username)`, `(role,status)`.

#### `creator_profiles`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| user_id | UUID FK → users.id UNIQUE | |
| bio | TEXT | |
| category | TEXT | fitness, music, education… |
| banner_url | TEXT | |
| kyc_status | ENUM(`not_started`,`pending`,`approved`,`rejected`) | |
| kyc_provider_ref | TEXT | external verification id |
| payout_method_id | UUID FK → payout_methods.id | |
| default_monthly_price | NUMERIC(12,2) | |
| is_listed | BOOLEAN | hidden until KYC approved |
| rating_avg | NUMERIC(3,2) | |

Indexes: `(user_id)`, `(kyc_status)`, `(category)`.

#### `subscription_plans`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| creator_id | UUID FK → creator_profiles.id | |
| name | TEXT | e.g. `Monthly`, `VIP` |
| price | NUMERIC(12,2) | |
| interval | ENUM(`month`,`quarter`,`year`) | |
| benefits | JSONB | bullet list |
| is_active | BOOLEAN | |

Index: `(creator_id, is_active)`.

#### `subscriptions`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| subscriber_id | UUID FK → users.id | |
| creator_id | UUID FK → creator_profiles.id | |
| plan_id | UUID FK → subscription_plans.id | |
| status | ENUM(`active`,`past_due`,`cancelled`,`expired`) | |
| current_period_start | TIMESTAMPTZ | |
| current_period_end | TIMESTAMPTZ | |
| cancel_at_period_end | BOOLEAN | |
| provider_subscription_id | TEXT | |

Indexes: `(subscriber_id, status)`, `(creator_id, status)`, UNIQUE `(subscriber_id, creator_id, status) WHERE status='active'`.

#### `content_posts`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| creator_id | UUID FK → creator_profiles.id | |
| caption | TEXT | |
| visibility | ENUM(`public`,`subscribers`,`ppv`) | |
| price | NUMERIC(12,2) | when visibility=ppv |
| status | ENUM(`draft`,`scheduled`,`published`,`hidden`,`removed`) | |
| scheduled_at | TIMESTAMPTZ | |
| published_at | TIMESTAMPTZ | |
| like_count | INT | |
| comment_count | INT | |

Indexes: `(creator_id, status, published_at DESC)`, `(visibility, status)`.

#### `media_assets`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| post_id | UUID FK → content_posts.id | nullable (DM attachments) |
| message_id | UUID FK → messages.id | nullable |
| owner_id | UUID FK → users.id | |
| kind | ENUM(`image`,`video`,`audio`) | |
| storage_key | TEXT | object-storage path |
| width | INT | |
| height | INT | |
| duration_sec | INT | |
| mime | TEXT | |
| moderation_status | ENUM(`pending`,`approved`,`flagged`,`rejected`) | |
| nsfw_score | NUMERIC(4,3) | from auto-moderation |

Indexes: `(post_id)`, `(message_id)`, `(moderation_status)`.

#### `purchases` (one-time PPV unlocks)
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| user_id | UUID FK → users.id | |
| post_id | UUID FK → content_posts.id | |
| amount | NUMERIC(12,2) | |
| platform_fee | NUMERIC(12,2) | |
| payment_id | UUID FK → payments.id | |
| status | ENUM(`pending`,`paid`,`refunded`,`failed`) | |

Index: UNIQUE `(user_id, post_id) WHERE status='paid'`.

#### `tips`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| from_user_id | UUID FK → users.id | |
| to_creator_id | UUID FK → creator_profiles.id | |
| post_id | UUID FK → content_posts.id NULL | |
| amount | NUMERIC(12,2) | |
| message | TEXT(280) | |
| payment_id | UUID FK → payments.id | |
| status | ENUM(`pending`,`paid`,`failed`,`refunded`) | |

Indexes: `(to_creator_id, created_at DESC)`, `(from_user_id)`.

#### `messages`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| conversation_id | UUID | |
| sender_id | UUID FK → users.id | |
| recipient_id | UUID FK → users.id | |
| body | TEXT | |
| ppv_price | NUMERIC(12,2) | nullable |
| ppv_unlocked_by | UUID[] | unlocked recipients |
| read_at | TIMESTAMPTZ | |
| deleted_for | UUID[] | per-user soft delete |

Indexes: `(conversation_id, created_at)`, `(recipient_id, read_at)`.

#### `user_blocks`
| column | type | notes |
|---|---|---|
| blocker_id | UUID FK → users.id | PK part |
| blocked_id | UUID FK → users.id | PK part |

#### `reports`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| reporter_id | UUID FK → users.id NULL | nullable for anonymous |
| target_type | ENUM(`post`,`profile`,`message`,`comment`) | |
| target_id | UUID | |
| reason | ENUM(`spam`,`harassment`,`explicit`,`fraud`,`copyright`,`other`) | |
| details | TEXT | |
| evidence_urls | TEXT[] | |
| status | ENUM(`pending`,`triaged`,`resolved`,`dismissed`) | |
| severity | ENUM(`low`,`medium`,`high`,`critical`) | |
| assigned_to | UUID FK → users.id | moderator |

Indexes: `(status, severity)`, `(target_type, target_id)`.

#### `moderation_actions`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| report_id | UUID FK → reports.id NULL | |
| moderator_id | UUID FK → users.id | |
| target_type | ENUM(`post`,`profile`,`message`,`user`) | |
| target_id | UUID | |
| action | ENUM(`warn`,`hide`,`remove`,`suspend`,`ban`,`restore`) | |
| reason | TEXT | |
| duration_hours | INT | for suspend |
| appeal_status | ENUM(`none`,`pending`,`upheld`,`overturned`) | |

Index: `(target_type, target_id)`.

#### `payments` (provider transactions, parent of purchases/tips/subscriptions)
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| user_id | UUID FK → users.id | |
| provider | ENUM(`lipila`,`stripe`,`paddle`) | |
| provider_payment_id | TEXT UNIQUE | |
| amount | NUMERIC(12,2) | |
| currency | TEXT(3) | |
| status | ENUM(`pending`,`succeeded`,`failed`,`refunded`,`chargeback`) | |
| kind | ENUM(`subscription`,`purchase`,`tip`) | |
| metadata | JSONB | |

Indexes: `(user_id)`, `(provider_payment_id)`.

#### `payouts`
| column | type | notes |
|---|---|---|
| id | UUID PK | |
| creator_id | UUID FK → creator_profiles.id | |
| amount | NUMERIC(12,2) | |
| period_start | DATE | |
| period_end | DATE | |
| status | ENUM(`pending`,`processing`,`paid`,`failed`) | |
| provider_transfer_id | TEXT | |
| failure_reason | TEXT | |

Index: `(creator_id, status)`.

#### `analytics_events`
| column | type | notes |
|---|---|---|
| id | BIGSERIAL PK | |
| user_id | UUID NULL | |
| session_id | UUID | |
| event_name | TEXT | e.g. `post_view`, `subscribe_click` |
| properties | JSONB | |
| occurred_at | TIMESTAMPTZ | |

Indexes: `(event_name, occurred_at)`, `(user_id, occurred_at)`. Partitioned monthly.

### 2.2 Relationship diagram

```text
users 1──1 creator_profiles 1──* subscription_plans
  │              │ 1                    │
  │              │                      │
  │              * content_posts *──* media_assets
  │              │
  │              * payouts
  │
  *──* subscriptions *──1 subscription_plans
  *──* purchases *──1 content_posts
  *──* tips *──1 creator_profiles
  *──* messages (sender/recipient)
  *──* reports *──1 moderation_actions
  *──* payments (parent of subscriptions/purchases/tips)
  *──* analytics_events
```

---

## 3. REST API design

> Base URL: `https://api.favecelebs.com/v1`. Auth: `Authorization: Bearer <jwt>`. All responses JSON. Standard error: `{ "error": { "code": "STRING", "message": "...", "details": {} } }`.

### 3.1 Auth

**POST `/auth/signup`**
```json
{ "email": "jane@x.com", "password": "******", "username": "janedoe",
  "display_name": "Jane", "dob": "1995-04-12", "accepts_tos": true }
```
→ `201` `{ "user": { "id": "...", "email": "...", "username": "...", "role": "subscriber" }, "access_token": "...", "refresh_token": "..." }`

**POST `/auth/login`**
```json
{ "email": "jane@x.com", "password": "******" }
```
→ `200` `{ "user": {...}, "access_token": "...", "refresh_token": "..." }`

**POST `/auth/refresh`** · **POST `/auth/logout`** · **POST `/auth/forgot-password`** · **POST `/auth/reset-password`** · **POST `/auth/verify-email`** · **POST `/auth/2fa/enable`**

### 3.2 User profile

**GET `/me`** → current user + settings.
**PATCH `/me`** `{ "display_name": "...", "bio": "...", "avatar_url": "..." }`.
**GET `/users/:username`** → public profile.
**POST `/me/block`** `{ "user_id": "..." }`.

### 3.3 Creator onboarding

**POST `/creators/apply`** → returns KYC session URL.
```json
{ "category": "fitness", "bio": "..." }
```
→ `201` `{ "kyc_status": "pending", "kyc_url": "https://verify..." }`

**POST `/creators/kyc/webhook`** (provider → server) — verifies signature, updates `kyc_status`.

**POST `/creators/payout-methods`**
```json
{ "type": "mobile_money", "msisdn": "+260...", "currency": "ZMW" }
```

### 3.4 Creator dashboard

**GET `/creators/me/dashboard?range=30d`**
```json
{ "subscribers": 1240, "active_ppv_buyers": 318, "revenue": {
    "subscriptions": 12400.00, "ppv": 2150.00, "tips": 980.00 },
  "churn_rate": 0.043, "top_posts": [ { "id": "...", "views": 9821, "revenue": 410.00 } ] }
```

**GET `/creators/me/posts`** · **POST `/creators/me/posts`** · **PATCH `/creators/me/posts/:id`** · **DELETE `/creators/me/posts/:id`**.

Create post:
```json
{ "caption": "...", "visibility": "ppv", "price": 9.99,
  "media": [ { "asset_id": "..." } ], "scheduled_at": null }
```

### 3.5 Subscription plans

**GET `/creators/:id/plans`** → list of plans.
**POST `/creators/me/plans`** `{ "name": "VIP", "price": 19.99, "interval": "month", "benefits": ["DMs","weekly Q&A"] }`.

### 3.6 Subscribe to a creator

**POST `/subscriptions`**
```json
{ "creator_id": "...", "plan_id": "...", "payment_method_id": "..." }
```
→ `201` `{ "subscription_id": "...", "status": "pending", "checkout_url": "https://pay..." }`

**DELETE `/subscriptions/:id`** → `{ "status": "cancelled", "access_until": "2026-07-28T00:00:00Z" }`.

### 3.7 Subscriber-only content

**GET `/feed?cursor=...`** → posts from active subscriptions; signed media URLs included.
**GET `/posts/:id`** → returns `locked: true/false`; locked posts omit `media_url`.

### 3.8 Premium (PPV) purchase

**POST `/posts/:id/purchase`**
```json
{ "payment_method_id": "..." }
```
→ `201` `{ "purchase_id": "...", "status": "pending", "checkout_url": "..." }`

After webhook: **GET `/posts/:id`** returns unlocked media.

### 3.9 Tips

**POST `/creators/:id/tip`**
```json
{ "amount": 25.00, "message": "Loved the workout 💪", "post_id": null,
  "payment_method_id": "..." }
```

### 3.10 Messaging

**GET `/conversations`** · **GET `/conversations/:id/messages?cursor=`** · **POST `/conversations/:id/messages`**
```json
{ "body": "Hey!", "media_asset_ids": [], "ppv_price": null }
```
**POST `/messages/:id/unlock`** (for PPV DM).
**POST `/conversations/:id/mute`** · **POST `/users/:id/block`** · **POST `/messages/:id/report`**.

### 3.11 Reporting

**POST `/reports`**
```json
{ "target_type": "post", "target_id": "...", "reason": "explicit",
  "details": "...", "evidence_urls": [] }
```
→ `201` `{ "report_id": "...", "status": "pending" }`

### 3.12 Admin moderation

**GET `/admin/reports?status=pending&severity=high`**
**POST `/admin/moderation`**
```json
{ "target_type": "post", "target_id": "...", "action": "remove",
  "reason": "policy violation", "report_id": "..." }
```
**GET `/admin/kyc?status=pending`** · **POST `/admin/kyc/:id/decide`** `{ "decision": "approve" }`.
**GET `/admin/users/:id`** · **POST `/admin/users/:id/suspend`** `{ "duration_hours": 168 }`.

### 3.13 Analytics

**GET `/analytics/platform?range=30d`** (admin) — DAU/MAU, GMV, refunds, reports.
**GET `/analytics/creator/:id?range=30d`** (creator self / admin) — see dashboard schema.
**POST `/analytics/events`** (client) — batch ingest of UI events.

### 3.14 Payment webhooks (server-to-server)

**POST `/webhooks/lipila`** · **POST `/webhooks/stripe`** — signature header required; idempotent on `provider_payment_id`.

---

## 4. NestJS backend architecture

```text
src/
  main.ts
  app.module.ts
  common/
    guards/ (JwtAuthGuard, RolesGuard, KycVerifiedGuard, Age18Guard)
    interceptors/ (LoggingInterceptor, AuditInterceptor)
    pipes/ (ZodValidationPipe)
    decorators/ (@Roles, @CurrentUser)
    filters/ (AllExceptionsFilter)
    middleware/ (RequestIdMiddleware, RateLimitMiddleware)
  modules/
    auth/  users/  creators/  content/  subscriptions/
    payments/  tips/  messaging/  reports/  moderation/
    analytics/  admin/
  infrastructure/
    db/ (TypeORM data source, migrations)
    storage/ (S3-compatible client, signed URLs)
    queue/ (BullMQ: payouts, emails, moderation jobs)
    cache/ (Redis)
    providers/ (LipilaClient, StripeClient, KycProvider, ModerationProvider)
```

Per-module pattern: `xxx.module.ts`, `xxx.controller.ts`, `xxx.service.ts`, `dto/`, `entities/`.

### 4.1 AuthModule
- **Controllers:** `AuthController` (signup, login, refresh, logout, forgot/reset, verify-email, 2FA).
- **Services:** `AuthService` (password hashing argon2id, JWT issue/verify), `TokenService` (rotating refresh tokens), `OAuthService` (Google).
- **DTOs:** `SignupDto`, `LoginDto`, `RefreshDto`, `ResetPasswordDto`.
- **Guards:** `JwtAuthGuard`, `RefreshGuard`, `LocalAuthGuard`.
- **Middleware:** rate limiter on `/auth/*`.
- **Business logic:** age check (dob ≥ 18) before issuing payment-capable JWT claim; lockout after N failed logins; email verification token TTL.

### 4.2 UsersModule
- **Controllers:** `UsersController` (GET /me, PATCH /me, GET /users/:username, block).
- **Services:** `UsersService`, `BlockService`.
- **Entities:** `User`, `UserBlock`.
- **Guards:** `JwtAuthGuard`.
- **Logic:** username uniqueness, profile field validation, GDPR export/delete jobs.

### 4.3 CreatorsModule
- **Controllers:** `CreatorsController` (apply, payout methods, dashboard, posts CRUD).
- **Services:** `CreatorsService`, `KycService` (wraps `KycProvider`), `PayoutMethodService`, `DashboardService`.
- **DTOs:** `ApplyCreatorDto`, `UpdateCreatorDto`, `CreatePayoutMethodDto`.
- **Guards:** `JwtAuthGuard`, `RolesGuard('creator')`, `KycVerifiedGuard` (for payout actions).
- **Logic:** state machine `not_started → pending → approved/rejected`; auto-listing on approval.

### 4.4 ContentModule
- **Controllers:** `PostsController`, `MediaController` (upload init, finalize), `FeedController`, `LikesController`, `CommentsController`.
- **Services:** `PostsService`, `MediaService` (S3 signed PUT/GET, transcoding job), `FeedService` (Redis fan-out / pull-model), `AutoModerationService` (image/video classifier).
- **Entities:** `ContentPost`, `MediaAsset`, `PostLike`, `PostComment`.
- **Guards:** `JwtAuthGuard`, `AccessPolicyGuard` (checks subscription/purchase before serving signed URL).
- **Logic:** scheduling cron, hide post when `nsfw_score > threshold` and queue for human review, signed URL TTL 5 min.

### 4.5 SubscriptionsModule
- **Controllers:** `PlansController`, `SubscriptionsController`.
- **Services:** `PlansService`, `SubscriptionsService`, `BillingCycleService` (renews via cron, marks `past_due`, dunning).
- **DTOs:** `CreatePlanDto`, `SubscribeDto`, `CancelSubscriptionDto`.
- **Guards:** `JwtAuthGuard`, `Age18Guard`.
- **Logic:** prevent self-subscription, prorate on plan change, grace period.

### 4.6 PaymentsModule
- **Controllers:** `PaymentsController` (methods CRUD, history), `WebhooksController` (`/webhooks/lipila`, `/webhooks/stripe`).
- **Services:** `PaymentsService`, `LipilaService`, `StripeService`, `RefundService`, `PayoutService`.
- **Guards:** `JwtAuthGuard`, `Age18Guard`; webhooks use `WebhookSignatureGuard` instead of JWT.
- **Middleware:** raw-body parser for signature verification.
- **Logic:** idempotency keys, retries with exponential backoff, double-entry ledger entries, fraud heuristics (velocity, geo).

### 4.7 TipsModule
- **Controllers:** `TipsController`.
- **Services:** `TipsService` (delegates payment to `PaymentsService`).
- **DTOs:** `SendTipDto` (min/max amount, 280-char message).
- **Guards:** `JwtAuthGuard`, `Age18Guard`, `NotBlockedGuard`.

### 4.8 MessagingModule
- **Controllers:** `ConversationsController`, `MessagesController`.
- **Gateway:** `MessagingGateway` (WebSocket) for realtime delivery + typing/read receipts.
- **Services:** `ConversationsService`, `MessagesService`, `MessageModerationService` (keyword + ML pre-filter).
- **DTOs:** `SendMessageDto`, `UnlockMessageDto`.
- **Guards:** `JwtAuthGuard`, `CanDmGuard` (checks subscription, creator DM rules, block list).
- **Logic:** PPV DM unlock through payment; rate-limit per-conversation; mass-message queue.

### 4.9 ReportsModule
- **Controllers:** `ReportsController` (create), nested admin endpoints under AdminModule.
- **Services:** `ReportsService`, `SeverityClassifier` (auto-tags severity from reason + signals).
- **DTOs:** `CreateReportDto`.
- **Guards:** `OptionalJwtGuard` (allow anonymous), `RateLimitGuard`.

### 4.10 ModerationModule
- **Controllers:** `ModerationController` (admin/moderator).
- **Services:** `ModerationService` (apply action + persist `moderation_actions`), `AppealService`, `BanService`.
- **Guards:** `JwtAuthGuard`, `RolesGuard('moderator','admin')`.
- **Logic:** action effects (hide/remove/suspend/ban), notification fan-out, appeal SLA timers, audit log entry per action.

### 4.11 AnalyticsModule
- **Controllers:** `AnalyticsController` (events ingest, creator dashboard read), `AdminAnalyticsController`.
- **Services:** `EventIngestService` (batches → queue → ClickHouse/Postgres partitions), `CreatorMetricsService`, `PlatformMetricsService`.
- **Guards:** `JwtAuthGuard`, role checks for admin endpoints.

### 4.12 AdminModule
- **Controllers:** `AdminUsersController`, `AdminKycController`, `AdminReportsController`, `AdminFinanceController`, `AdminAuditController`.
- **Services:** thin orchestrators delegating to domain modules; emits `AuditEvent` for every mutation.
- **Guards:** `JwtAuthGuard`, `RolesGuard('admin','moderator')`, `MfaRequiredGuard`.

### 4.13 Cross-cutting

- **Validation:** Zod or `class-validator` via global `ValidationPipe`.
- **Config:** `@nestjs/config` with schema validation; secrets from env / vault.
- **Logging:** Pino + request-id; redacts PII and tokens.
- **Queue jobs:** BullMQ workers for transcoding, payouts, email, moderation scoring, analytics rollups.

---

## 5. Security & compliance

### 5.1 Authentication
- Argon2id password hashing; minimum length 6, non-blocking warning on weak passwords.
- JWT access tokens (15 min) + rotating refresh tokens (30 days, single-use).
- TOTP 2FA optional for subscribers, required for admins/moderators.
- OAuth (Google) with PKCE; OAuth-linked accounts still require dob + 18+ confirmation.

### 5.2 Authorization (RBAC)
- Roles: `viewer`, `subscriber`, `creator`, `moderator`, `admin`.
- `@Roles()` decorator + `RolesGuard`; `KycVerifiedGuard` for payout-sensitive routes; `Age18Guard` for any payment-initiating route.
- Resource-level checks: `AccessPolicyGuard` verifies an active subscription OR paid purchase before issuing signed media URLs.

### 5.3 Payment webhook verification
- Raw body retained; signature header (`x-lipila-signature` / `Stripe-Signature`) verified with `timingSafeEqual`.
- Idempotency keyed on `provider_payment_id`; webhook handlers are pure functions of the verified payload.
- Reject events older than 5 minutes (replay protection).
- All state transitions logged with provider event id.

### 5.4 Rate limiting
- Global: 100 req/min per IP.
- Auth endpoints: 10 req/min per IP + per email.
- Reports & tips: per-user quotas to deter abuse.
- Messaging: per-conversation token bucket.
- Implemented via Redis (sliding window) at the middleware layer.

### 5.5 Audit logging
- Append-only `audit_log` table (`actor_id`, `action`, `target`, `before`, `after`, `ip`, `user_agent`, `request_id`).
- Every admin/moderator/financial action emits an audit event via `AuditInterceptor`.
- Logs shipped to immutable storage (object lock / WORM) with 7-year retention.

### 5.6 Content moderation workflow
1. **Pre-publish auto-moderation** — every uploaded media asset runs through an image/video safety classifier; explicit content is **blocked at upload time** with user-facing message. Score ≥ threshold but < block → hold for human review.
2. **Post-publish detection** — periodic re-scan + community reports populate the queue.
3. **Triage** — reports auto-classified by severity; `critical` (CSAM, threats) escalated immediately and reported to authorities where required.
4. **Action** — moderator picks from `warn / hide / remove / suspend / ban / restore`; action recorded in `moderation_actions` and audit log.
5. **Notify & appeal** — affected user notified with reason; can appeal once within 14 days; appeals reviewed by a different moderator.

### 5.7 KYC / identity verification (creators)
- Third-party provider (e.g. Onfido, Veriff, Smile ID) for document + selfie liveness.
- Creator cannot publish paid content, receive tips, or be listed publicly until `kyc_status='approved'`.
- PEP / sanctions screening on approval; periodic re-screening.
- KYC artifacts stored encrypted (AES-256) in a restricted bucket; access requires admin role + audit reason.

### 5.8 Data privacy controls
- **Lawful basis & consent** captured at signup (TOS + privacy policy version stored on `users`).
- **Data export** — `GET /me/export` enqueues a job that emails a signed ZIP within 30 days.
- **Right to erasure** — `DELETE /me` triggers anonymization: PII scrubbed, content options (transfer/delete), payments retained for legal/tax (7 years) with user link severed.
- **Encryption** — TLS 1.2+ in transit; AES-256 at rest; field-level encryption for KYC docs and payout details.
- **Minimization** — DOB stored, exact age derived; payment card data never touches our servers (tokenized via provider).
- **Cookies** — strictly necessary + analytics opt-in banner.
- **Sub-processors** registry maintained and disclosed publicly.

### 5.9 Platform safety guardrails (no-explicit-content enforcement)
- Terms of Service explicitly prohibits adult, sexually explicit, or unsafe content.
- Automated nudity / explicit-content classifiers block uploads above threshold.
- Repeat violations escalate: warn → 24h suspend → 7d suspend → permanent ban + payout freeze pending review.
- Public reporting endpoint accessible without an account to lower friction for abuse reports.
- Underage user detection (signal-based) triggers account hold and identity check.

---

*End of specification.*
