README.md
December 28, 2025 · View on GitHub
# PATH: README.md
<!--
NOTE:
This file must NOT be wrapped in an outer Markdown code fence.
It should start directly with a Markdown heading.
-->
<!-- // --- REPLACE START: README Ops links + fix broken code fence + align headings/TOC with anchor-smoke slugs (keep content intact) --- -->
# Loventia.app
A full-stack dating application with both **server** (Node.js/Express, MongoDB) and **client** (Vite + React, Tailwind CSS). Features include user registration, authentication, profile management, image uploads, subscription payments (Stripe & PayPal), and admin tools.
---
## Table of Contents
1. [Features](#features)
2. [Prerequisites](#prerequisites)
3. [Installation](#installation)
4. [Quick start for new developers](#quick-start-for-new-developers)
5. [Environment Variables](#environment-variables)
6. [Running the App](#running-the-app)
* [Server](#server)
* [Client](#client)
7. [Docker Setup (Optional)](#docker-setup-optional)
8. [Docker Desktop Auto-Start](#docker-desktop-auto-start)
9. [API Documentation](#api-documentation)
10. [Authentication (IMPORTANT)](#authentication-important)
11. [Documentation and Infrastructure](#documentation-and-infrastructure)
* [Version Control Workflow](#version-control-workflow)
* [CI/CD Documentation](#cicd-documentation)
12. [Image Upload API](#image-upload-api)
13. [Client API Abstraction](#client-api-abstraction)
14. [Testing and CI/CD](#testing-and-cicd)
15. [Commit Convention and Code Style](#commit-convention-and-code-style)
16. [ER Diagram](#er-diagram)
---
## Features
* **User Authentication**: JWT-based login & registration
* **Profile Management**: Update profile fields, upload avatar & extra images
* **Subscriptions**: Free vs. Premium plans (image limits)
* **Payments**: Stripe Checkout & PayPal integration
* **Image Uploads**: Multer-powered file handling
* **Admin Tools**: Hide/show users, delete accounts
* **Real-time Webhooks**: Stripe & PayPal event handling
* **API Documentation**: OpenAPI/Swagger spec at `/api/docs` (after setup)
* **CI/CD Ready**: GitHub Actions example workflows
---
## Prerequisites
* **Node.js** >= v16
* **npm** or **Yarn**
* **MongoDB** instance (Atlas or local)
* **Stripe** account & API keys
* **PayPal** Developer account & credentials
---
## Installation
1. **Clone the repo**
```bash
git clone https://github.com/your-username/date-app.git
cd date-app
-
Install dependencies
# Server cd server && npm install # Client cd ../client && npm install
Quick start for new developers
This section is a short, practical path to get Loventia running locally and verify that the core API is healthy.
-
Prepare environment files
From the project root:
# Server cp server/.env.example server/.env # Client cp client/.env.example client/.envThen edit:
-
server/.env-> set at least:MONGO_URIJWT_SECRETREFRESH_TOKEN_SECRETSTRIPE_SECRET_KEYSTRIPE_WEBHOOK_SECRETSTRIPE_PREMIUM_PRICE_IDCLIENT_URL=http://localhost:5174
-
client/.env-> set:VITE_API_BASE_URL=http://localhost:5000/api
-
-
Start MongoDB (local or Docker)
Option A – Local MongoDB service (if installed):
# On Windows, Mongo usually runs as a service automatically. # On Linux / macOS you might need: sudo systemctl start mongodOption B – Mongo in Docker:
From the project root:
docker compose up -d mongoMake sure
MONGO_URIinserver/.envpoints to that instance, e.g.:MONGO_URI=mongodb://127.0.0.1:27017/loventia -
Start the backend API
cd server npm run devThe API should now be available at:
http://localhost:5000 -
Start the frontend client
In a second terminal:
cd client npm run devBy default Vite serves the client at:
http://localhost:5174 -
Smoke-test the API (health + login + /api/auth/me)
From PowerShell (Windows), after you have a test user created:
$ErrorActionPreference = 'Stop' $BaseUrl = "http://127.0.0.1:5000" Write-Host "==> Health-check..." -ForegroundColor Cyan $health = Invoke-RestMethod -Uri "$BaseUrl/health" -Method GET Write-Host "Health OK" -ForegroundColor Green # Replace with a real test user (Free or Premium) $email = "testuser1@example.com" $password = "Test1234!" Write-Host "`n==> Login..." -ForegroundColor Cyan $bodyJson = @{ email = $email; password = $password } | ConvertTo-Json $login = Invoke-RestMethod -Uri "$BaseUrl/api/auth/login" -Method POST -Body $bodyJson -ContentType "application/json" $token = $login.accessToken Write-Host "Login OK, token prefix: $($token.Substring(0,24))..." -ForegroundColor Green Write-Host "`n==> /api/auth/me..." -ForegroundColor Cyan $me = Invoke-RestMethod -Uri "$BaseUrl/api/auth/me" -Method GET -Headers @{ Authorization = "Bearer $token" } $me | Format-List email,premium,isPremium,@{Name="tier";Expression={$_.entitlements.tier}}If you see:
Health OKLogin OK/api/auth/mereturnsemailandpremium/isPremium/entitlements,
then your local environment is basically healthy.
-
Optional: API docs & Stripe smoke tests
-
To lint and bundle the OpenAPI spec:
cd server powershell -ExecutionPolicy Bypass -File .\openapi\openapi.ps1 -
To validate Stripe webhooks in dev, see the section "Stripe CLI smoke (dev)" under Testing and CI/CD.
-
-
Quick reference: core backend endpoints & OpenAPI commands
This table is a cheat sheet for the most important backend endpoints and dev commands you will need as a new contributor.
Category Endpoint / Command Description Login POST /api/auth/loginPrimary login endpoint. Returns access & refresh tokens and normalized user payload. Me (primary) GET /api/auth/meMain meendpoint. Returns email, premium / isPremium, entitlements, stripeCustomerId, etc.Me (alias) GET /api/meConvenience alias to the authenticated user info ( /api/auth/me).Me (legacy) GET /api/users/meLegacy users route, still normalized. Kept for backward compatibility with older clients/tools. Health GET /healthLiveness check. No auth required. Ready GET /readyReadiness check + Mongo status. No auth required. Metrics (root) GET /metricsPrometheus-style app metrics. No auth in dev. Metrics (API) GET /api/metricsExtended HTTP metrics (text). Dev: open. Prod: admin-only via guard. Metrics (API JSON) GET /api/metrics/jsonSame metrics as above but JSON-formatted. OpenAPI lint+bundle powershell -ExecutionPolicy Bypass -File .\openapi\openapi.ps1(run inserver/)Runs Spectral lint and bundles OpenAPI spec with Redocly into openapi/openapi.bundle.yaml.
Environment Variables
Copy example files and populate with your credentials:
# Server
cp server/.env.example server/.env
# Client
cp client/.env.example client/.env
Staging / Production env templates
These templates are provided so deployment environments stay consistent:
# Server templates (documentation)
cp server/.env.staging.example server/.env.staging
cp server/.env.prod.example server/.env.prod
# Client templates (documentation)
cp client/.env.staging.example client/.env.staging
cp client/.env.prod.example client/.env.prod
In CI/CD (GitHub Actions), environment variables are usually injected via secrets. These template files are still useful as documentation and for local simulation.
Server .env
PORT=5000
MONGO_URI=<your-mongo-uri>
# Access token secret (short-lived)
JWT_SECRET=dev_access_secret
# Refresh token secret (long-lived)
REFRESH_TOKEN_SECRET=dev_refresh_secret
STRIPE_SECRET_KEY=<sk_test_xxx>
STRIPE_WEBHOOK_SECRET=<whsec_xxx>
STRIPE_PREMIUM_PRICE_ID=<price_xxx>
PAYPAL_CLIENT_ID=<your-paypal-client-id>
PAYPAL_SECRET=<your-paypal-secret>
PAYPAL_PREMIUM_PRICE=<12.00>
PAYPAL_WEBHOOK_ID=<your-paypal-webhook-id>
CLIENT_URL=http://localhost:5174
NODE_ENV=development
Client .env
VITE_API_BASE_URL=http://localhost:5000/api
Running the App
Server
cd server
npm run dev
- Runs on:
http://localhost:5000 - Swagger UI (after setup):
http://localhost:5000/api/docs(preferred) orhttp://localhost:5000/api-docs(legacy)
Client
cd client
npm run dev
- Runs on:
http://localhost:5174
Docker Setup (Optional)
Containerize services using Docker Compose:
version: '3.8'
services:
mongo:
image: mongo:6.0
restart: unless-stopped
environment:
MONGO_INITDB_ROOT_USERNAME: admin
MONGO_INITDB_ROOT_PASSWORD: secret
volumes:
- mongo_data:/data/db
ports:
- '27017:27017'
server:
build:
context: ./server
dockerfile: Dockerfile
restart: unless-stopped
env_file:
- ./server/.env
ports:
- '5000:5000'
volumes:
- ./server/uploads:/usr/src/app/uploads
- ./server:/usr/src/app
- /usr/src/app/node_modules
depends_on:
- mongo
command: npm run dev
client:
build:
context: ./client
dockerfile: Dockerfile
restart: unless-stopped
env_file:
- ./client/.env
ports:
- '5174:5174'
volumes:
- ./client:/app
- /app/node_modules
depends_on:
- server
command: npm run dev
volumes:
mongo_data: {}
Docker Desktop Auto-Start
To ensure Docker Desktop is always running when you log in:
- Open Docker Desktop from the Start Menu.
- Click the Settings (gear) icon in the top-right corner.
- In General settings, enable Start Docker Desktop when you log in.
- Click Apply & Restart if prompted.
Once enabled, Docker will automatically launch on system boot, and you can immediately run:
# From project root
docker compose up -d
API Documentation
OpenAPI spec (recommended): server/openapi/openapi.yaml
Legacy: server/openapi.yaml or server/openapi.json
Integrate Swagger UI:
cd server
npm install swagger-ui-express yamljs
In Express app (server/src/app.js or server/src/index.js):
// Swagger integration
import path from 'path';
import { fileURLToPath } from 'url';
import swaggerUi from 'swagger-ui-express';
import YAML from 'yamljs';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
// Try to load the main OpenAPI YAML from /server/openapi/openapi.yaml first
const swaggerDocument = YAML.load(
path.join(__dirname, '../openapi/openapi.yaml')
);
// Mount under /api/docs so it matches the rest of the API
app.use('/api/docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));
Note: If you use a separate
server/src/routes/index.jsthat is mounted withapp.use('/api', routes);, Swagger should still be mounted directly on the app as above, not inside the routes index.
Authentication (IMPORTANT)
This is the part that has been validated via PowerShell and should stay documented here to avoid future “why does /api/users/login sometimes work and sometimes not” questions.
1. Primary endpoint (new ESM-based, full)
POST /api/auth/login
Content-Type: application/json
Body:
{
"email": "testuser1@example.com",
"password": "Test1234!"
}
Response (shortened):
{
"message": "Login successful.",
"user": {
"id": "690767bbdc9075ce9f0034c6",
"email": "testuser1@example.com",
"role": "user",
"isPremium": false,
"entitlements": { "tier": "free" }
},
"accessToken": "<jwt>",
"refreshToken": "<jwt>",
"expiresIn": 7200
}
Why this is primary
Because it comes from the new ESM router server/routes/auth.js which exposes:
/api/auth/login/api/auth/refresh/api/auth/logout/api/auth/forgot-password/api/auth/reset-password/api/auth/me(premium data, entitlements, stripeCustomerId, etc.)
2. Secondary / legacy endpoint (fallback only)
POST /api/users/login
This is kept only so older clients or old frontend code or Postman collections do not break. The new frontend logic is:
try /api/auth/login → if 404/405 → try /api/users/login
3. Me endpoint
GET /api/auth/me
Authorization: Bearer <accessToken>
This is the canonical me endpoint, because:
- it returns the same premium data as
/api/users/me - it uses the same normalizer as
userRoutes - it never leaks
password - it constructs
entitlementscorrectly even if older DB documents are missing fields
4. Private router (does not shadow me)
GET /api/auth/private/ping
GET /api/auth/private/no-me
These are only for diagnostics. There is no separate /api/auth/private/me.
This ensures there is only one real “user info” endpoint: /api/auth/me.
5. Refresh
Supports two ways (both tested via PowerShell):
-
Refresh token in the body:
POST /api/auth/refresh Content-Type: application/json { "refreshToken": "<refresh_jwt>" } -
Refresh token in the Authorization header (legacy):
POST /api/auth/refresh Authorization: Bearer <refresh_jwt>
Both yield a new access token (and optionally a rotated refresh token, depending on configuration).
6. Forgot / reset
POST /api/auth/forgot-password
{
"email": "testuser1@example.com"
}
→ Creates a token, stores passwordResetToken and passwordResetExpires in DB, and attempts to send an email (logging activity even when email cannot be sent in dev).
POST /api/auth/reset-password
{
"id": "690767bbdc9075ce9f0034c6",
"token": "<raw OR sha256(token)>",
"password": "NewStrongPassword!1"
}
→ Accepts either the raw token or its sha256 hash, fixing the prior “hash vs raw” discrepancy.
Documentation and Infrastructure
This section points to the canonical operational documentation.
Ops docs (canonical)
- Ops Runbook:
docs/ops/runbook.md - Rollback Playbook:
docs/ops/rollback-playbook.md - Security Hardening:
docs/security-hardening.md - Test Plan:
docs/test-plan.md
Version Control Workflow
Document your branching strategy (e.g. Git-flow or trunk-based) and pull request conventions in docs/version-control-workflow.md.
CI/CD Documentation
See: docs/ci-cd.md
Image Upload API
Upload Profile Avatar
POST /api/images/:userId/upload-avatar
Authorization: Bearer <token>
Content-Type: multipart/form-data
- Form Data:
avatar(single file)
curl -X POST http://localhost:5000/api/images/USER_ID/upload-avatar \
-H "Authorization: Bearer $TOKEN" \
-F "avatar=@/path/to/avatar.jpg"
Upload Extra Photos
POST /api/images/:userId/upload-photos
Authorization: Bearer <token>
Content-Type: multipart/form-data
- Form Data:
photos(multiple files)
curl -X POST http://localhost:5000/api/images/USER_ID/upload-photos \
-H "Authorization: Bearer $TOKEN" \
-F "photos=@/path/to/photo1.png" \
-F "photos=@/path/to/photo2.jpg"
Client API Abstraction
Location: client/src/api/images.js
import axios from 'axios';
export const uploadAvatar = (userId, file) => {
const form = new FormData();
form.append('avatar', file);
return axios.post(`/api/images/${userId}/upload-avatar`, form, {
headers: { 'Content-Type': 'multipart/form-data' },
});
};
export const uploadPhotos = (userId, files) => {
const form = new FormData();
files.forEach((f) => form.append('photos', f));
return axios.post(`/api/images/${userId}/upload-photos`, form, {
headers: { 'Content-Type': 'multipart/form-data' },
});
};
Testing and CI/CD
- Server Tests: Jest & Supertest (
server/tests) - Client Tests: React Testing Library (
client/src/__tests__) - CI Workflow: see
.github/workflows/ci.yml,staging-e2e.yml,production-deploy.yml
Example CI workflow (.github/workflows/ci.yml):
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
with:
node-version: '16'
- run: npm ci
- run: npm test
- run: npm run lint
Stripe CLI smoke (dev)
Quick way to validate webhook-only flow locally (Test mode):
# 1) Forward events from Stripe → local webhook endpoint
stripe listen --forward-to http://localhost:5000/webhooks/stripe
# 2) Simulate subscription activation and cancellation/update
stripe trigger checkout.session.completed
stripe trigger customer.subscription.updated
stripe trigger customer.subscription.deleted
Commit Convention and Code Style
-
Linting: ESLint
-
Formatting: Prettier
-
Commit Messages: Conventional Commits
feat: add new featurefix: bug fixdocs: documentation only changeschore: build process or auxiliary tool changes
ER Diagram
erDiagram
USER ||--o{ SUBSCRIPTION : has
USER ||--o{ IMAGE : owns
USER ||--o{ MESSAGE : sends
USER ||--o{ MESSAGE : receives
SUBSCRIPTION {
ObjectId _id
ObjectId user
String plan
Date createdAt
}
IMAGE {
ObjectId _id
String url
ObjectId owner
Date uploaded
}
USER {
ObjectId _id
String username
String email
String password
Boolean isPremium
String profilePicture
[String] extraImages
[ObjectId] likes
[ObjectId] superLikes
[Date] superLikeTimestamps
[ObjectId] blockedUsers
}
MESSAGE {
ObjectId _id
ObjectId sender
ObjectId receiver
String content
Date createdAt
}
::contentReference[oaicite:0]{index=0}