diff --git a/.devcontainer/README.md b/.devcontainer/README.md new file mode 100644 index 0000000..7cdd37d --- /dev/null +++ b/.devcontainer/README.md @@ -0,0 +1,764 @@ +# Looking - DevContainer Configuration + +> **VS Code Development Container setup for the Looking monorepo** + +This directory contains the complete Docker-based development environment configuration for the Looking project. The DevContainer provides a consistent, isolated development environment with all necessary tools and dependencies pre-installed. + +--- + +## πŸ“‹ Table of Contents + +- [Overview](#overview) +- [What's Included](#whats-included) +- [Architecture](#architecture) +- [Services](#services) +- [Port Mappings](#port-mappings) +- [Volume Mounts](#volume-mounts) +- [Installation Process](#installation-process) +- [VS Code Integration](#vs-code-integration) +- [Environment Variables](#environment-variables) +- [Troubleshooting](#troubleshooting) + +--- + +## 🎯 Overview + +The DevContainer setup provides: + +- **Consistent Environment**: Same Node 14, Python 2.7, and build tools across all developers +- **Isolated Dependencies**: No conflicts with your local system packages +- **Auto-Configuration**: Automatic dependency installation and VS Code extensions +- **Multi-Service Stack**: Frontend, backend, database, and admin UI all running together +- **Volume Optimization**: Fast performance with node_modules isolation + +**Technologies**: Docker Compose, VS Code Remote - Containers, Node 14, MongoDB 4.4 + +--- + +## πŸ“¦ What's Included + +### Base Container + +**Image**: Custom Dockerfile based on `node:14-bullseye` + +**System Packages**: + +- Git (version control) +- curl & wget (HTTP clients) +- vim (text editor) +- **Python 2.7** (required for legacy node-sass) +- build-essential (C++ compiler for native modules) + +**Global NPM Packages**: + +- `@ionic/cli` - Ionic framework CLI +- `@angular/cli` - Angular CLI (though project uses ionic-app-scripts) +- `nodemon` - Auto-restart development server +- `concurrently` - Run multiple commands in parallel +- `eslint` - JavaScript linting +- `prettier` - Code formatting + +### VS Code Extensions (Auto-Installed) + +| Extension | Purpose | +| -------------------------------------- | --------------------------- | +| **ms-vscode.vscode-json** | JSON language support | +| **bradlc.vscode-tailwindcss** | Tailwind CSS IntelliSense | +| **esbenp.prettier-vscode** | Code formatting | +| **dbaeumer.vscode-eslint** | JavaScript linting | +| **ms-vscode.vscode-typescript-next** | TypeScript support | +| **formulahendry.auto-rename-tag** | HTML/XML tag rename | +| **christian-kohler.path-intellisense** | Autocomplete file paths | +| **mongodb.mongodb-vscode** | MongoDB database management | + +### Editor Settings (Auto-Configured) + +- **Format on Save**: Enabled +- **Default Formatter**: Prettier +- **ESLint Auto-Fix**: Runs on save +- **Emmet**: Enabled for JavaScript (React/JSX) + +--- + +## πŸ—οΈ Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ DevContainer Stack β”‚ +β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€ +β”‚ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ App Container (node:14-bullseye) β”‚ β”‚ +β”‚ β”‚ - VS Code Remote Server β”‚ β”‚ +β”‚ β”‚ - Node.js 14.x + npm β”‚ β”‚ +β”‚ β”‚ - Python 2.7 (for node-sass) β”‚ β”‚ +β”‚ β”‚ - Ionic/Angular Dev Server (port 8100) β”‚ β”‚ +β”‚ β”‚ - Express API Server (port 3069) β”‚ β”‚ +β”‚ β”‚ - Development tools (nodemon, eslint, prettier) β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ ↓↑ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ MongoDB 4.4 (mongo:4.4) β”‚ β”‚ +β”‚ β”‚ - Database: urge β”‚ β”‚ +β”‚ β”‚ - Port: 27017 β”‚ β”‚ +β”‚ β”‚ - Credentials: admin/password β”‚ β”‚ +β”‚ β”‚ - Health Check: ping command every 30s β”‚ β”‚ +β”‚ β”‚ - Seed Data: /docker-entrypoint-initdb.d β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ ↓↑ β”‚ +β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”‚ +β”‚ β”‚ Mongo Express (mongo-express:latest) β”‚ β”‚ +β”‚ β”‚ - Web-based MongoDB admin UI β”‚ β”‚ +β”‚ β”‚ - Port: 8081 β”‚ β”‚ +β”‚ β”‚ - Login: admin/password β”‚ β”‚ +β”‚ β”‚ - Browse collections, run queries β”‚ β”‚ +β”‚ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β”‚ +β”‚ β”‚ +β”‚ Network: dev-network (bridge driver) β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + ↓ ↓ ↓ + Port 8100 Port 3069 Port 27017 + (Frontend Dev) (Backend API) (MongoDB) +``` + +--- + +## πŸ”Œ Services + +### 1. App Service (Development Container) + +**File**: [docker-compose.yml](docker-compose.yml) + +**Configuration**: + +```yaml +service: app +build: + context: .. + dockerfile: .devcontainer/Dockerfile +command: sleep infinity +volumes: + - ../..:/workspaces:cached + - /workspaces/PfosiLooking-monorepo/app/node_modules + - /workspaces/PfosiLooking-monorepo/backend/node_modules +depends_on: + mongo: + condition: service_healthy +``` + +**Purpose**: + +- Main development container where you write code +- Runs VS Code Remote Server +- Hosts Ionic dev server (port 8100) and Express API (port 3069) +- Waits for MongoDB health check before starting + +**Command**: `sleep infinity` keeps container running indefinitely + +**Node Modules Isolation**: + +- Anonymous volumes for `app/node_modules` and `backend/node_modules` +- Prevents host filesystem performance issues (especially on Mac/Windows) +- Each workspace has its own isolated dependencies + +--- + +### 2. MongoDB Service + +**Configuration**: + +```yaml +service: mongo +image: mongo:4.4 +environment: + - MONGO_INITDB_DATABASE=urge + - MONGO_INITDB_ROOT_USERNAME=admin + - MONGO_INITDB_ROOT_PASSWORD=password +ports: + - "27017:27017" +volumes: + - mongo_data:/data/db + - ../backend/data:/docker-entrypoint-initdb.d:ro +healthcheck: + test: echo 'db.runCommand("ping").ok' | mongo localhost:27017/urge --quiet + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s +``` + +**Purpose**: + +- NoSQL database for profiles, users, and messages +- Persistent storage via `mongo_data` volume +- Auto-loads seed data from `backend/data/` on first run + +**Credentials**: + +- **Admin User**: `admin` +- **Admin Password**: `password` +- **Database**: `urge` + +**Health Check**: + +- Runs every 30 seconds +- Pings database to verify it's responsive +- App container waits for healthy status before starting + +**Seed Data**: + +- Files in `backend/data/` are mounted to `/docker-entrypoint-initdb.d` +- MongoDB automatically executes `.js` and `.sh` files in that directory +- Used for initial database population (if scripts exist) + +--- + +### 3. Mongo Express Service + +**Configuration**: + +```yaml +service: mongo-express +image: mongo-express:latest +environment: + - ME_CONFIG_MONGODB_SERVER=mongo + - ME_CONFIG_MONGODB_PORT=27017 + - ME_CONFIG_MONGODB_ADMINUSERNAME=admin + - ME_CONFIG_MONGODB_ADMINPASSWORD=password + - ME_CONFIG_BASICAUTH_USERNAME=admin + - ME_CONFIG_BASICAUTH_PASSWORD=password +ports: + - "8081:8081" +depends_on: + - mongo +``` + +**Purpose**: + +- Web-based database administration tool +- View/edit collections without command-line mongo shell +- Useful for debugging data issues + +**Access**: http://localhost:8081 + +- **Login**: admin / password + +**Features**: + +- Browse collections visually +- Run MongoDB queries +- Create/edit/delete documents +- View indexes and stats + +--- + +## πŸ”Œ Port Mappings + +| Port | Service | Purpose | Access | +| --------- | ---------------- | -------------------- | ------------------------- | +| **8100** | Ionic Dev Server | Frontend application | http://localhost:8100 | +| **3069** | Express API | Backend REST API | http://localhost:3069 | +| **27017** | MongoDB | Database server | mongodb://localhost:27017 | +| **8081** | Mongo Express | Database admin UI | http://localhost:8081 | + +### Port Forwarding Configuration + +**File**: [devcontainer.json](devcontainer.json) + +```json +"forwardPorts": [8100, 3069, 27017, 8081], +"portsAttributes": { + "8100": { + "label": "Frontend (Ionic)", + "onAutoForward": "notify" + }, + "3069": { + "label": "Backend API", + "onAutoForward": "notify" + }, + "27017": { + "label": "MongoDB", + "onAutoForward": "silent" + }, + "8081": { + "label": "Mongo Express", + "onAutoForward": "notify" + } +} +``` + +**Notifications**: + +- **notify**: VS Code shows notification when port is forwarded +- **silent**: No notification (database port) + +--- + +## πŸ’Ύ Volume Mounts + +### Workspace Volume + +```yaml +volumes: + - ../..:/workspaces:cached +``` + +**Purpose**: + +- Mounts parent directory (entire monorepo) into container +- `:cached` flag optimizes for read-heavy operations +- Enables live code changes to reflect immediately + +**Path Inside Container**: `/workspaces/PfosiLooking-monorepo/` + +--- + +### Node Modules Isolation + +```yaml +volumes: + - /workspaces/PfosiLooking-monorepo/app/node_modules + - /workspaces/PfosiLooking-monorepo/backend/node_modules +``` + +**Purpose**: + +- Creates anonymous Docker volumes for node_modules directories +- Isolates dependencies from host filesystem +- **Solves performance issues** on Mac/Windows (Docker Desktop) + +**Why?**: + +- node_modules can have 100,000+ files +- Host filesystem sync is slow for that many files +- Volume isolation provides native container filesystem performance + +--- + +### MongoDB Data Volume + +```yaml +volumes: mongo_data:/data/db +``` + +**Purpose**: + +- Persistent storage for MongoDB data +- Survives container restarts +- Named volume managed by Docker + +**Location**: Docker volume `mongo_data` (not visible in project directory) + +--- + +### Seed Data Volume + +```yaml +volumes: + - ../backend/data:/docker-entrypoint-initdb.d:ro +``` + +**Purpose**: + +- Mounts seed data directory into MongoDB initialization location +- `:ro` = read-only (MongoDB can't modify host files) +- Scripts run automatically on first database creation + +--- + +## πŸ”„ Installation Process + +### Automatic Setup (postCreateCommand) + +**File**: [postCreateCommand.sh](postCreateCommand.sh) + +The script runs automatically after the container is created: + +```bash +#!/bin/bash +set -e # Exit on any error + +# 1. Install root dependencies +npm install + +# 2. Install backend dependencies +cd backend && npm install + +# 3. Install app dependencies with legacy support +cd app && npm install --legacy-peer-deps +``` + +**Execution Time**: 5-10 minutes (first time only) + +**What Happens**: + +1. **Root workspace** (`package.json`): + - Installs `concurrently`, `npm-run-all` + - Enables `npm run dev:all` script +2. **Backend** (`backend/package.json`): + - Installs Express, Mongoose, JWT, etc. + - Standard npm install (no special flags) +3. **Frontend** (`app/package.json`): + - Installs Ionic 3, Angular 5, TypeScript 2.4 + - **Uses `--legacy-peer-deps`** flag + - Required for old Ionic 3 packages with npm 7+ + - Falls back to `--force` if legacy deps fail + +**Progress Visibility**: + +- VS Code shows "Running postCreateCommand" notification +- View detailed output: Terminal β†’ "postCreateCommand" tab + +--- + +### Manual Installation + +If automatic setup fails: + +```bash +# 1. Open VS Code terminal inside DevContainer +# Terminal β†’ New Terminal (or Ctrl + `) + +# 2. Install dependencies manually +npm install +cd backend && npm install && cd .. +cd app && npm install --legacy-peer-deps && cd .. + +# 3. Verify installation +npm run dev:all +``` + +--- + +## 🎨 VS Code Integration + +### DevContainer Configuration + +**File**: [devcontainer.json](devcontainer.json) + +**Key Settings**: + +```json +{ + "name": "PfosiLooking Full Stack", + "dockerComposeFile": "docker-compose.yml", + "service": "app", + "workspaceFolder": "/workspaces/PfosiLooking-monorepo", + "shutdownAction": "stopCompose" +} +``` + +**Shutdown Behavior**: + +- `"stopCompose"` - Stops all services when closing VS Code +- Preserves data in volumes (MongoDB data, node_modules) +- Fast restart on next open (no rebuild needed) + +--- + +### Features + +**Auto-Installed**: + +```json +{ + "ghcr.io/devcontainers/features/node:1": { + "version": "14" + }, + "ghcr.io/devcontainers/features/git:1": {}, + "ghcr.io/devcontainers/features/github-cli:1": {} +} +``` + +**Provides**: + +- Node.js 14.x (managed version) +- Git (latest stable) +- GitHub CLI (`gh` command) + +--- + +### Post-Start Message + +```json +"postStartCommand": "echo 'DevContainer ready! Run npm run dev:all to start development.'" +``` + +Displays helpful message after container starts. + +--- + +## πŸ” Environment Variables + +### Development Environment File + +**File**: [dev.env](dev.env) + +**Contains** (example values): + +```bash +NODE_ENV=development +MONGODB_URI=mongodb://mongo:27017/urge +REACT_APP_API_URL=http://localhost:3069 +PORT=3069 +IONIC_PORT=8100 +JWT_SECRET=dev-secret-change-in-production +``` + +**Usage**: + +```yaml +env_file: + - dev.env +``` + +⚠️ **Security Note**: + +- `dev.env` may contain secrets +- DO NOT commit real credentials +- Use `dev.env.example` for templates +- Add `dev.env` to `.gitignore` + +--- + +### Inline Environment Variables + +**In docker-compose.yml**: + +```yaml +environment: + - NODE_ENV=development + - MONGODB_URI=mongodb://mongo:27017/urge + - PORT=3069 +``` + +These override values from `dev.env` if both are present. + +--- + +## πŸ”§ Troubleshooting + +### Container Won't Start + +**Problem**: DevContainer fails to start or build + +**Solutions**: + +1. **Rebuild without cache**: + + ``` + F1 β†’ "Dev Containers: Rebuild Container Without Cache" + ``` + +2. **Check Docker Desktop is running**: + + ```bash + docker ps + ``` + +3. **Free up disk space**: + + ```bash + docker system prune -a + ``` + +4. **View build logs**: + ``` + F1 β†’ "Dev Containers: Show Container Log" + ``` + +--- + +### Python 2.7 Errors + +**Problem**: `node-sass` or `node-gyp` errors about Python + +**Check Python symlinks**: + +```bash +# Inside container +which python +python --version # Should show Python 2.7.x +``` + +**Dockerfile installs**: + +```dockerfile +RUN apt-get install -y python2.7 python2.7-dev \ + && ln -sf /usr/bin/python2.7 /usr/bin/python +``` + +**Why?**: node-sass (used by Ionic 3) requires Python 2.7 to build native modules. + +--- + +### MongoDB Connection Failed + +**Problem**: Backend can't connect to MongoDB + +**1. Check MongoDB is healthy**: + +```bash +docker ps +# Look for "healthy" status on mongo container +``` + +**2. Test connection manually**: + +```bash +docker exec -it mongo --eval "db.runCommand('ping')" +``` + +**3. Verify connection string**: + +```bash +echo $MONGODB_URI +# Should be: mongodb://mongo:27017/urge +``` + +**4. Wait for health check**: + +- Health check runs every 30s +- Container may need 40s start period +- App service waits for `service_healthy` condition + +--- + +### Port Already in Use + +**Problem**: Port 8100, 3069, or 27017 already in use on host + +**Find conflicting process**: + +```bash +# Mac/Linux +lsof -i :8100 +lsof -i :3069 +lsof -i :27017 + +# Kill process +kill -9 +``` + +**Or change ports** in [docker-compose.yml](docker-compose.yml): + +```yaml +ports: + - "8101:8100" # Changed from 8100 +``` + +--- + +### Node Modules Issues + +**Problem**: Module not found, peer dependency warnings, or version conflicts + +**Solution 1 - Reinstall**: + +```bash +# Remove all node_modules +rm -rf node_modules app/node_modules backend/node_modules + +# Reinstall +npm run install:all +``` + +**Solution 2 - Clear volume**: + +```bash +# Exit container, then from host +docker-compose -f .devcontainer/docker-compose.yml down -v +# This deletes ALL volumes including node_modules and MongoDB data + +# Rebuild container +F1 β†’ "Dev Containers: Rebuild Container" +``` + +--- + +### Slow Performance + +**Problem**: File watching or compilation is slow + +**Causes**: + +- Docker Desktop on Mac/Windows has filesystem sync overhead +- Large node_modules directories + +**Solutions**: + +1. **Verify node_modules isolation**: + + ```yaml + # These volumes should exist in docker-compose.yml + - /workspaces/PfosiLooking-monorepo/app/node_modules + - /workspaces/PfosiLooking-monorepo/backend/node_modules + ``` + +2. **Increase Docker resources**: + + - Docker Desktop β†’ Settings β†’ Resources + - Increase CPU: 4+ cores + - Increase Memory: 8+ GB + +3. **Use `:cached` mount flag** (already configured): + ```yaml + - ../..:/workspaces:cached + ``` + +--- + +### Database Permissions + +**Problem**: Can't write to MongoDB or access Mongo Express + +**Check credentials**: + +```yaml +# MongoDB +MONGO_INITDB_ROOT_USERNAME=admin +MONGO_INITDB_ROOT_PASSWORD=password + +# Mongo Express +ME_CONFIG_MONGODB_ADMINUSERNAME=admin +ME_CONFIG_MONGODB_ADMINPASSWORD=password +ME_CONFIG_BASICAUTH_USERNAME=admin +ME_CONFIG_BASICAUTH_PASSWORD=password +``` + +**Access Mongo Express**: + +- URL: http://localhost:8081 +- Login: admin / password (HTTP Basic Auth) + +--- + +### VS Code Extensions Not Installing + +**Problem**: Extensions listed in devcontainer.json don't install + +**Manual install**: + +``` +F1 β†’ "Extensions: Install Extensions" +Search for extension ID (e.g., "esbenp.prettier-vscode") +``` + +**Rebuild to retry auto-install**: + +``` +F1 β†’ "Dev Containers: Rebuild Container" +``` + +--- + +## πŸ“š Related Documentation + +- **[Root README](../README.md)** - Project overview and quick start +- **[Backend README](../backend/README.md)** - API server setup +- **[Frontend README](../app/README.md)** - Ionic app setup +- **[Deployment Guide](../DEPLOYMENT.md)** - Production deployment + +--- + +**Need Help?** Check the [root troubleshooting section](../README.md#troubleshooting) for additional solutions. diff --git a/DEPLOYMENT.md b/DEPLOYMENT.md new file mode 100644 index 0000000..9b7d141 --- /dev/null +++ b/DEPLOYMENT.md @@ -0,0 +1,1078 @@ +# Looking - Production Deployment Guide + +> **Complete guide to deploying the Looking application to production** + +This document covers building Docker images, configuring Traefik reverse proxy, setting up SSL certificates with Let's Encrypt, managing environment variables, and backup/restore procedures. + +--- + +## πŸ“‹ Table of Contents + +- [Overview](#overview) +- [Prerequisites](#prerequisites) +- [Architecture](#architecture) +- [Pre-Deployment Checklist](#pre-deployment-checklist) +- [Building Docker Images](#building-docker-images) +- [Environment Variables](#environment-variables) +- [Deployment](#deployment) +- [Traefik Configuration](#traefik-configuration) +- [SSL/TLS Certificates](#ssltls-certificates) +- [Domain Configuration](#domain-configuration) +- [Database Management](#database-management) +- [Backup and Restore](#backup-and-restore) +- [Monitoring and Logs](#monitoring-and-logs) +- [Rollback Procedures](#rollback-procedures) +- [Troubleshooting](#troubleshooting) + +--- + +## 🎯 Overview + +The production deployment uses **Docker Compose** with: + +- **Traefik** - Reverse proxy and load balancer +- **Let's Encrypt** - Automatic SSL certificate management +- **Docker Volumes** - Persistent storage for database and images +- **Custom Networks** - Isolated backend and external traffic separation + +**Production Domains**: + +- **Frontend**: https://pfosi.mifi.dev +- **Backend API**: https://api.pfosi.mifi.dev +- **Database Admin**: https://mongo.pfosi.mifi.dev + +--- + +## βœ… Prerequisites + +### Server Requirements + +- **OS**: Linux (Ubuntu 20.04+ or Debian 11+ recommended) +- **CPU**: 2+ cores +- **RAM**: 4GB+ (8GB recommended) +- **Disk**: 20GB+ free space +- **Docker**: 20.10+ with Docker Compose v2 +- **Network**: Public IP address +- **Ports**: 80 (HTTP), 443 (HTTPS) open to internet + +### Domain Setup + +1. **Register domains**: + + - `pfosi.mifi.dev` (frontend) + - `api.pfosi.mifi.dev` (backend) + - `mongo.pfosi.mifi.dev` (admin panel) + +2. **DNS Configuration**: + + - Point all domains to your server's public IP + - Use A records: + ``` + pfosi.mifi.dev A + api.pfosi.mifi.dev A + mongo.pfosi.mifi.dev A + ``` + +3. **Wait for DNS propagation** (can take up to 24-48 hours, usually minutes) + ```bash + dig pfosi.mifi.dev + dig api.pfosi.mifi.dev + ``` + +### Software Requirements + +```bash +# Install Docker and Docker Compose +curl -fsSL https://get.docker.com -o get-docker.sh +sudo sh get-docker.sh +sudo usermod -aG docker $USER + +# Verify installation +docker --version +docker compose version +``` + +### Traefik Setup + +This deployment assumes you have **Traefik already running** with: + +- External network: `marina-net` +- Let's Encrypt configured +- Ports 80/443 exposed + +If you don't have Traefik, see [Traefik Configuration](#traefik-configuration) section. + +--- + +## πŸ—οΈ Architecture + +``` +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Internet β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + Port 80/443 + β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Traefik (Reverse Proxy) β”‚ +β”‚ - SSL Termination (Let's Encrypt) β”‚ +β”‚ - HTTP β†’ HTTPS Redirect β”‚ +β”‚ - Domain Routing β”‚ +β”‚ - CORS Headers β”‚ +β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ β”‚ + β”‚ marina-net β”‚ marina-net β”‚ marina-net + β”‚ (external) β”‚ (external) β”‚ (external) + β”‚ β”‚ β”‚ +β”Œβ”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β”Œβ”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” +β”‚ Frontend β”‚ β”‚ Backend β”‚ β”‚ Mongo Express β”‚ +β”‚ (Nginx) β”‚ β”‚ (Express) β”‚ β”‚ (Web UI) β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ Port: 80 β”‚ β”‚ Port: 3069 β”‚ β”‚ Port: 8081 β”‚ +β”‚ (internal) β”‚ β”‚ (internal) β”‚ β”‚ (internal) β”‚ +β”‚ β”‚ β”‚ β”‚ β”‚ β”‚ +β”‚ Domain: β”‚ β”‚ Domain: β”‚ β”‚ Domain: β”‚ +β”‚ pfosi. β”‚ β”‚ api.pfosi. β”‚ β”‚ mongo.pfosi. β”‚ +β”‚ mifi.dev β”‚ β”‚ mifi.dev β”‚ β”‚ mifi.dev β”‚ +β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ β”‚ + β”‚ backend-net β”‚ backend-net + β”‚ (internal) β”‚ (internal) + β”‚ β”‚ + β”Œβ”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β” + β”‚ MongoDB 4.4 β”‚ + β”‚ β”‚ + β”‚ Port: 27017 (internal) β”‚ + β”‚ Database: urge β”‚ + β”‚ β”‚ + β”‚ Volume: mongo_data β”‚ + β”‚ (persistent storage) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ + β”‚ + β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β–Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” + β”‚ api_images volume β”‚ + β”‚ (profile/message β”‚ + β”‚ image storage) β”‚ + β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ +``` + +### Network Topology + +| Network | Type | Purpose | +| --------------- | -------- | --------------------------------------------------------- | +| **marina-net** | External | Traefik-to-services communication, public internet access | +| **backend-net** | Internal | Services-to-MongoDB communication, isolated from internet | + +--- + +## πŸ“ Pre-Deployment Checklist + +### 1. Update Seed Data + +⚠️ **CRITICAL**: Database is wiped and reseeded on each deployment. + +**Edit profile data**: + +```bash +nano backend/data/profiles.json +``` + +**Add images**: + +```bash +cp new-profile.jpg backend/src/images/profile/ +``` + +**Commit changes**: + +```bash +git add backend/data/profiles.json backend/src/images/ +git commit -m "Update profile data for deployment" +git push +``` + +--- + +### 2. Set Environment Variables + +Create `.env` file in project root: + +```bash +cp backend/.env.example .env +nano .env +``` + +**Required variables** (see [Environment Variables](#environment-variables) section): + +- JWT_SECRET +- GOOGLE_MAPS_API_KEY +- MAIL_HOST, MAIL_PORT, MAIL_USER, MAIL_PASS +- MONGO_ROOT_USER, MONGO_ROOT_PASSWORD +- MONGO_EXPRESS_USER, MONGO_EXPRESS_PASSWORD + +--- + +### 3. Update Docker Compose + +**Edit [docker-compose.yml](docker-compose.yml)**: + +Update image registry URLs: + +```yaml +image: git.mifi.dev:12023/mifi/pfosi-looking-frontend:latest +# Change to your registry +``` + +Update domains in Traefik labels: + +```yaml +- "traefik.http.routers.looking-frontend.rule=Host(`pfosi.mifi.dev`)" +# Change to your domain +``` + +--- + +### 4. Test Locally + +```bash +# Build images +npm run docker:build + +# Test services +docker-compose up -d + +# Verify services +curl http://localhost:80 # Frontend +curl http://localhost:3069/profiles # Backend +``` + +--- + +## 🐳 Building Docker Images + +### Frontend Dockerfile + +**File**: `app/Dockerfile` (must be created) + +```dockerfile +# Stage 1: Build +FROM node:14-bullseye AS builder + +WORKDIR /app +COPY package*.json ./ +RUN npm install --legacy-peer-deps + +COPY . . +RUN npm run build + +# Stage 2: Production +FROM nginx:alpine + +COPY --from=builder /app/www /usr/share/nginx/html +COPY nginx.conf /etc/nginx/nginx.conf + +EXPOSE 80 +CMD ["nginx", "-g", "daemon off;"] +``` + +**Create `app/nginx.conf`**: + +```nginx +server { + listen 80; + server_name _; + root /usr/share/nginx/html; + index index.html; + + location / { + try_files $uri $uri/ /index.html; + } + + location /assets { + expires 1y; + add_header Cache-Control "public, immutable"; + } +} +``` + +--- + +### Backend Dockerfile + +**File**: `backend/Dockerfile` + +```dockerfile +FROM node:14-bullseye + +WORKDIR /app + +COPY package*.json ./ +RUN npm install --production + +COPY . . + +EXPOSE 3069 + +CMD ["node", "src/bin/www"] +``` + +--- + +### Build Commands + +**Build both images**: + +```bash +npm run docker:build +``` + +**Or build individually**: + +```bash +# Frontend +docker build -t pfosi-looking-frontend:latest ./app + +# Backend +docker build -t pfosi-looking-backend:latest ./backend +``` + +--- + +### Push to Registry + +If using a private registry: + +```bash +# Tag images +docker tag pfosi-looking-frontend:latest git.mifi.dev:12023/mifi/pfosi-looking-frontend:latest +docker tag pfosi-looking-backend:latest git.mifi.dev:12023/mifi/pfosi-looking-backend:latest + +# Login to registry +docker login git.mifi.dev:12023 + +# Push images +docker push git.mifi.dev:12023/mifi/pfosi-looking-frontend:latest +docker push git.mifi.dev:12023/mifi/pfosi-looking-backend:latest +``` + +--- + +## πŸ” Environment Variables + +### Production .env File + +**Location**: Project root `.env` (NOT committed to Git) + +```bash +# JWT Secret (generate with: openssl rand -base64 32) +JWT_SECRET=your-production-secret-min-32-chars-long + +# Google Maps API +GOOGLE_MAPS_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +# SMTP Configuration +MAIL_HOST=smtp.gmail.com +MAIL_PORT=587 +MAIL_USER=support@example.com +MAIL_PASS=app-specific-password-here + +# MongoDB Credentials +MONGO_ROOT_USER=admin +MONGO_ROOT_PASSWORD=strong-password-here + +# Mongo Express Credentials +MONGO_EXPRESS_USER=admin +MONGO_EXPRESS_PASSWORD=different-strong-password +``` + +### Using Docker Secrets (Recommended) + +For enhanced security, use Docker secrets: + +```bash +# Create secrets +echo "strong-password" | docker secret create mongo_root_password - +echo "jwt-secret" | docker secret create jwt_secret - + +# Update docker-compose.yml +secrets: + mongo_root_password: + external: true + jwt_secret: + external: true + +services: + looking-backend: + secrets: + - jwt_secret + environment: + - JWT_SECRET_FILE=/run/secrets/jwt_secret +``` + +--- + +## πŸš€ Deployment + +### Deploy Services + +```bash +# From project root +npm run deploy + +# Or manually +docker-compose up -d +``` + +--- + +### Verify Deployment + +```bash +# Check container status +docker-compose ps + +# Should show: +# - looking-frontend (healthy) +# - looking-backend (healthy) +# - looking-mongo (healthy) +# - looking-mongo-express (healthy) + +# View logs +docker-compose logs -f looking-backend +docker-compose logs -f looking-frontend + +# Test endpoints +curl https://pfosi.mifi.dev +curl https://api.pfosi.mifi.dev/profiles +``` + +--- + +### Initial Database Seed + +Database automatically seeds on first MongoDB startup: + +```bash +# View MongoDB logs to confirm seeding +docker logs looking-mongo + +# Or manually seed +docker exec -it looking-backend npm run seed +``` + +--- + +## πŸ”€ Traefik Configuration + +If you don't have Traefik running, here's a basic setup. + +### Create Traefik docker-compose.yml + +**File**: `~/traefik/docker-compose.yml` + +```yaml +version: "3.8" + +services: + traefik: + image: traefik:v2.10 + container_name: traefik + restart: unless-stopped + command: + - "--api.dashboard=true" + - "--providers.docker=true" + - "--providers.docker.exposedbydefault=false" + - "--entrypoints.web.address=:80" + - "--entrypoints.websecure.address=:443" + - "--certificatesresolvers.letsencrypt.acme.email=admin@yourdomain.com" + - "--certificatesresolvers.letsencrypt.acme.storage=/letsencrypt/acme.json" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge=true" + - "--certificatesresolvers.letsencrypt.acme.httpchallenge.entrypoint=web" + ports: + - "80:80" + - "443:443" + volumes: + - /var/run/docker.sock:/var/run/docker.sock:ro + - traefik-letsencrypt:/letsencrypt + networks: + - marina-net + labels: + - "traefik.enable=true" + - "traefik.http.routers.traefik.rule=Host(`traefik.yourdomain.com`)" + - "traefik.http.routers.traefik.service=api@internal" + - "traefik.http.routers.traefik.tls.certresolver=letsencrypt" + - "traefik.http.middlewares.redirect-https.redirectscheme.scheme=https" + - "traefik.http.middlewares.redirect-https.redirectscheme.permanent=true" + - "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)" + - "traefik.http.routers.http-catchall.entrypoints=web" + - "traefik.http.routers.http-catchall.middlewares=redirect-https" + +volumes: + traefik-letsencrypt: + +networks: + marina-net: + external: true +``` + +### Start Traefik + +```bash +cd ~/traefik +docker network create marina-net +docker-compose up -d +``` + +--- + +## πŸ”’ SSL/TLS Certificates + +### Let's Encrypt Configuration + +**In docker-compose.yml Traefik labels**: + +```yaml +labels: + - "traefik.http.routers.looking-frontend.tls=true" + - "traefik.http.routers.looking-frontend.tls.certresolver=letsencrypt" +``` + +**Certificate storage**: `/letsencrypt/acme.json` in Traefik container + +**Renewal**: Automatic (Traefik handles renewals) + +--- + +### Certificate Troubleshooting + +**View certificate info**: + +```bash +docker exec traefik cat /letsencrypt/acme.json | jq +``` + +**Force certificate renewal**: + +```bash +# Delete acme.json and restart Traefik +docker exec traefik rm /letsencrypt/acme.json +docker restart traefik +``` + +**Let's Encrypt rate limits**: + +- 50 certificates per domain per week +- Use staging server for testing: + ```yaml + - "--certificatesresolvers.letsencrypt.acme.caserver=https://acme-staging-v02.api.letsencrypt.org/directory" + ``` + +--- + +## 🌐 Domain Configuration + +### Traefik Labels + +**Frontend**: + +```yaml +labels: + - "traefik.http.routers.looking-frontend.rule=Host(`pfosi.mifi.dev`)" + - "traefik.http.services.looking-frontend.loadbalancer.server.port=80" +``` + +**Backend**: + +```yaml +labels: + - "traefik.http.routers.looking-backend.rule=Host(`api.pfosi.mifi.dev`)" + - "traefik.http.services.looking-backend.loadbalancer.server.port=3069" +``` + +**Mongo Express**: + +```yaml +labels: + - "traefik.http.routers.mongo-express.rule=Host(`mongo.pfosi.mifi.dev`)" + - "traefik.http.services.mongo-express.loadbalancer.server.port=8081" +``` + +--- + +### CORS Configuration + +**Backend CORS middleware** (already in docker-compose.yml): + +```yaml +labels: + - "traefik.http.routers.looking-backend.middlewares=api-cors" + - "traefik.http.middlewares.api-cors.headers.accesscontrolallowmethods=GET,OPTIONS,PUT,POST,DELETE,PATCH" + - "traefik.http.middlewares.api-cors.headers.accesscontrolalloworiginlist=https://pfosi.mifi.dev" + - "traefik.http.middlewares.api-cors.headers.accesscontrolallowcredentials=true" + - "traefik.http.middlewares.api-cors.headers.accesscontrolallowheaders=Origin,X-Requested-With,Content-Type,Accept,Authorization,Cache-Control" +``` + +**Key settings**: + +- **Origin**: `https://pfosi.mifi.dev` (frontend domain only) +- **Credentials**: Enabled (for JWT cookies if used) +- **Methods**: Standard REST verbs + OPTIONS for preflight + +--- + +## πŸ’Ύ Database Management + +### Current Workflow (Seed-Based) + +⚠️ **Database is wiped and reseeded on each deployment** + +**Steps**: + +1. Edit `backend/data/profiles.json` +2. Commit changes to Git +3. Deploy (database drops and reloads) + +**No user-generated content is preserved.** + +--- + +### Future Interactive Workflow + +**When the app becomes interactive** (user-generated content): + +1. **Disable auto-seeding**: + + - Remove seed data mount from docker-compose.yml + - Don't run `npm run seed` after initial deployment + +2. **Implement backups** (see [Backup and Restore](#backup-and-restore)) + +3. **Add backup cron job**: + + ```bash + # /etc/cron.d/mongodb-backup + 0 2 * * * /usr/local/bin/backup-mongodb.sh + ``` + +4. **Test restore procedure** regularly + +--- + +## πŸ”„ Backup and Restore + +### Current State: Not Required + +With seed-based workflow: + +- All data is in `backend/data/profiles.json` (version controlled) +- No user-generated content +- Redeployment = fresh database + +**No backups needed currently.** + +--- + +### Future: When Interactive + +**If app becomes interactive** with user-generated content, implement these procedures: + +--- + +### Backup Procedure + +**Create backup script**: `/usr/local/bin/backup-mongodb.sh` + +```bash +#!/bin/bash +set -e + +BACKUP_DIR="/backups/mongodb" +DATE=$(date +%Y%m%d_%H%M%S) +BACKUP_FILE="$BACKUP_DIR/urge_$DATE.gz" + +# Create backup directory +mkdir -p "$BACKUP_DIR" + +# Dump database +docker exec looking-mongo mongodump \ + --db=urge \ + --archive | gzip > "$BACKUP_FILE" + +# Keep last 30 days of backups +find "$BACKUP_DIR" -name "urge_*.gz" -mtime +30 -delete + +echo "Backup completed: $BACKUP_FILE" +``` + +**Make executable**: + +```bash +chmod +x /usr/local/bin/backup-mongodb.sh +``` + +--- + +### Restore Procedure + +**Restore from backup**: + +```bash +# List backups +ls -lh /backups/mongodb/ + +# Restore specific backup +gunzip < /backups/mongodb/urge_20240115_020000.gz | \ + docker exec -i looking-mongo mongorestore --archive --db=urge +``` + +--- + +### Volume Snapshots + +**Backup Docker volume directly**: + +```bash +# Stop services (optional, for consistency) +docker-compose stop looking-backend + +# Create volume backup +docker run --rm \ + -v pfosi-looking-monorepo_mongo_data:/data \ + -v /backups:/backup \ + alpine tar czf /backup/mongo_data_$(date +%Y%m%d).tar.gz /data + +# Restart services +docker-compose start looking-backend +``` + +**Restore volume**: + +```bash +# Stop all services +docker-compose down + +# Restore volume +docker run --rm \ + -v pfosi-looking-monorepo_mongo_data:/data \ + -v /backups:/backup \ + alpine sh -c "rm -rf /data/* && tar xzf /backup/mongo_data_20240115.tar.gz -C /" + +# Start services +docker-compose up -d +``` + +--- + +### Backup Image Uploads + +**Backup `api_images` volume**: + +```bash +docker run --rm \ + -v pfosi-looking-monorepo_api_images:/data \ + -v /backups:/backup \ + alpine tar czf /backup/api_images_$(date +%Y%m%d).tar.gz /data +``` + +**Restore**: + +```bash +docker run --rm \ + -v pfosi-looking-monorepo_api_images:/data \ + -v /backups:/backup \ + alpine sh -c "rm -rf /data/* && tar xzf /backup/api_images_20240115.tar.gz -C /" +``` + +--- + +## πŸ“Š Monitoring and Logs + +### View Logs + +**All services**: + +```bash +docker-compose logs -f +``` + +**Specific service**: + +```bash +docker-compose logs -f looking-backend +docker-compose logs -f looking-frontend +docker-compose logs -f looking-mongo +``` + +**With timestamps**: + +```bash +docker-compose logs -f --timestamps looking-backend +``` + +--- + +### Container Status + +```bash +# Check running containers +docker-compose ps + +# Resource usage +docker stats +``` + +--- + +### MongoDB Logs + +```bash +# View MongoDB logs +docker logs looking-mongo + +# Follow logs +docker logs -f looking-mongo + +# Search logs +docker logs looking-mongo 2>&1 | grep ERROR +``` + +--- + +### Application Logs + +**Backend** (Winston logger): + +- Console output via `docker-compose logs` +- File logs: `backend/logs/` (if configured) + +**Nginx** (Frontend): + +```bash +docker exec looking-frontend cat /var/log/nginx/access.log +docker exec looking-frontend cat /var/log/nginx/error.log +``` + +--- + +### Health Checks + +**MongoDB**: + +```bash +docker exec looking-mongo mongo --eval "db.adminCommand('ping')" +``` + +**Backend API**: + +```bash +curl https://api.pfosi.mifi.dev/profiles +``` + +**Frontend**: + +```bash +curl https://pfosi.mifi.dev +``` + +--- + +## βͺ Rollback Procedures + +### Quick Rollback + +**1. Identify previous image version**: + +```bash +docker images | grep pfosi-looking +``` + +**2. Update docker-compose.yml**: + +```yaml +image: git.mifi.dev:12023/mifi/pfosi-looking-backend:previous-tag +``` + +**3. Redeploy**: + +```bash +docker-compose up -d looking-backend +``` + +--- + +### Full Rollback with Database + +**1. Stop services**: + +```bash +docker-compose down +``` + +**2. Restore database backup** (if needed): + +```bash +gunzip < /backups/mongodb/urge_previous.gz | \ + docker exec -i looking-mongo mongorestore --archive --db=urge +``` + +**3. Deploy previous version**: + +```bash +git checkout +npm run deploy +``` + +--- + +### Emergency Rollback + +**Restore from Git**: + +```bash +# Clone fresh copy +git clone PfosiLooking-monorepo-backup +cd PfosiLooking-monorepo-backup +git checkout + +# Deploy +npm run docker:build +npm run deploy +``` + +--- + +## πŸ”§ Troubleshooting + +### Services Won't Start + +**Check logs**: + +```bash +docker-compose logs +``` + +**Verify network exists**: + +```bash +docker network ls | grep marina-net + +# Create if missing +docker network create marina-net +``` + +**Check disk space**: + +```bash +df -h +docker system df +``` + +--- + +### SSL Certificate Issues + +**Domain not resolving**: + +```bash +dig pfosi.mifi.dev +nslookup pfosi.mifi.dev +``` + +**Certificate not issued**: + +```bash +# Check Traefik logs +docker logs traefik | grep -i acme + +# Verify domain is reachable +curl -I http://pfosi.mifi.dev +``` + +**Rate limit errors**: + +- Use Let's Encrypt staging server for testing +- Wait 1 week if hit production limit + +--- + +### Database Connection Errors + +**Backend can't reach MongoDB**: + +```bash +# Verify MongoDB is on backend-net +docker network inspect pfosi-looking-monorepo_backend-net + +# Test connection from backend +docker exec looking-backend nc -zv mongo 27017 +``` + +**Wrong credentials**: + +```bash +# Check environment variables +docker exec looking-backend env | grep MONGO +``` + +--- + +### CORS Errors + +**Browser shows CORS error**: + +1. **Verify Traefik middleware**: + + ```bash + docker exec traefik cat /etc/traefik/dynamic.conf + ``` + +2. **Check allowed origin**: + + ```yaml + accesscontrolalloworiginlist=https://pfosi.mifi.dev + # Must match frontend domain exactly + ``` + +3. **Test with curl**: + ```bash + curl -H "Origin: https://pfosi.mifi.dev" \ + -H "Access-Control-Request-Method: POST" \ + -H "Access-Control-Request-Headers: Content-Type" \ + -X OPTIONS https://api.pfosi.mifi.dev/profiles + ``` + +--- + +### Image Upload Failures + +**Volume permissions**: + +```bash +# Check volume exists +docker volume ls | grep api_images + +# Inspect volume +docker volume inspect pfosi-looking-monorepo_api_images +``` + +**Directory writable**: + +```bash +docker exec looking-backend ls -la /app/src/images +docker exec looking-backend touch /app/src/images/test +``` + +--- + +## πŸ“š Related Documentation + +- **[Root README](README.md)** - Project overview +- **[Backend README](backend/README.md)** - API server setup +- **[Frontend README](app/README.md)** - Ionic app setup +- **[DevContainer Guide](.devcontainer/README.md)** - Development environment +- **[API Reference](backend/API.md)** - REST API endpoints +- **[Database Schema](backend/SCHEMA.md)** - MongoDB collections + +--- + +**Need Help?** Check individual component documentation or [open an issue](your-repository-issues-url) for deployment-specific problems. diff --git a/README.md b/README.md new file mode 100644 index 0000000..82df1f1 --- /dev/null +++ b/README.md @@ -0,0 +1,640 @@ +# Looking - PfosiLooking Monorepo + +> **An art project exploring modern gay dating culture through interactive storytelling** + +"Looking" is a documentary photography and art project by Nick Pfosi that examines the cultural landscape of gay dating apps like Grindr and modern LGBTQ+ meeting spaces. The project captures user stories, experiences, and narratives about contemporary gay dating culture through an interactive mobile application. + +--- + +## πŸ“‹ Table of Contents + +- [Project Overview](#project-overview) +- [Tech Stack](#tech-stack) +- [Prerequisites](#prerequisites) +- [Quick Start](#quick-start) +- [Project Structure](#project-structure) +- [Available Scripts](#available-scripts) +- [Data Management](#data-management) +- [Documentation Links](#documentation-links) +- [Troubleshooting](#troubleshooting) +- [Technical Debt & Security](#technical-debt--security) +- [Future Modernization Path](#future-modernization-path) + +--- + +## 🎨 Project Overview + +This monorepo contains the full-stack "Looking" application: + +- **Frontend**: Ionic 3 / Angular 5 mobile application for browsing profiles and stories +- **Backend**: Express.js REST API with MongoDB for data management +- **DevContainer**: Complete Docker-based development environment + +The name "Urnings" (seen in code) references a historical term for homosexual men, adding a scholarly dimension to the project's exploration of queer identity and dating culture. + +--- + +## πŸ›  Tech Stack + +### Frontend ([app/](app/)) + +- **Framework**: Ionic 3.9.2 with Angular 5.0.3 +- **Language**: TypeScript 2.4.2 +- **Build Tool**: `@ionic/app-scripts` 3.2.4 +- **UI Components**: Ionic Angular, Ionicons +- **HTTP**: Angular HTTP module with RxJS 5.5.2 +- **Dev Server**: Port 8100 + +### Backend ([backend/](backend/)) + +- **Framework**: Express 4.14.0 +- **Runtime**: Node.js 14.x +- **Database**: MongoDB 4.4 +- **ODM**: Mongoose 4.7.4 +- **Authentication**: JWT (jsonwebtoken 7.3.0) +- **Security**: PBKDF2 password hashing (233,335 iterations) +- **Image Processing**: Multer 1.2.0 +- **Logging**: Winston 2.4.0, Morgan 1.7.0 +- **Email**: Nodemailer 4.0.1 +- **API Server**: Port 3069 + +### DevOps + +- **DevContainer**: Node 14 (Debian Bullseye) +- **Database**: MongoDB 4.4 +- **Database Admin**: Mongo Express (port 8081) +- **Production**: Docker Compose + Traefik + Let's Encrypt SSL + +--- + +## βœ… Prerequisites + +Before you begin, ensure you have: + +- **Docker Desktop** (latest version) - [Download here](https://www.docker.com/products/docker-desktop) +- **Visual Studio Code** (latest version) - [Download here](https://code.visualstudio.com/) +- **VS Code Remote - Containers extension** - [Install from marketplace](https://marketplace.visualstudio.com/items?itemName=ms-vscode-remote.remote-containers) +- **Git** (for cloning the repository) + +> **Note**: You do NOT need Node.js, npm, or MongoDB installed locally. The DevContainer provides everything. + +--- + +## πŸš€ Quick Start + +### Using DevContainer (Recommended) + +1. **Clone the repository**: + + ```bash + git clone + cd PfosiLooking-monorepo + ``` + +2. **Open in VS Code**: + + ```bash + code . + ``` + +3. **Reopen in Container**: + + - VS Code should prompt: "Reopen in Container" + - Or manually: Press `F1` β†’ "Dev Containers: Reopen in Container" + +4. **Wait for setup** (first time only - 5-10 minutes): + + - Container builds with Node 14 + Python 2.7 + - Dependencies install automatically via `postCreateCommand.sh` + - MongoDB starts and initializes + +5. **Start development servers**: + + ```bash + npm run dev:all + ``` + +6. **Access the application**: + - **Frontend**: http://localhost:8100 + - **Backend API**: http://localhost:3069 + - **MongoDB**: localhost:27017 + - **Mongo Express**: http://localhost:8081 (admin/password) + +--- + +## πŸ“ Project Structure + +``` +PfosiLooking-monorepo/ +β”œβ”€β”€ app/ # Ionic 3 / Angular 5 frontend +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ app/ # App module and root component +β”‚ β”‚ β”œβ”€β”€ pages/ # Page components (grid, chat, profile, etc.) +β”‚ β”‚ β”œβ”€β”€ services/ # Data services (ProfileService) +β”‚ β”‚ └── assets/ # Static assets, images, data +β”‚ └── package.json # Frontend dependencies +β”‚ +β”œβ”€β”€ backend/ # Express.js API +β”‚ β”œβ”€β”€ src/ +β”‚ β”‚ β”œβ”€β”€ routes/ # API route handlers +β”‚ β”‚ β”œβ”€β”€ models/ # Mongoose schemas +β”‚ β”‚ β”œβ”€β”€ modules/ # Utilities (auth, images, mailer, etc.) +β”‚ β”‚ └── bin/www # Server startup script +β”‚ β”œβ”€β”€ data/ # Seed data (profiles.json) +β”‚ └── package.json # Backend dependencies +β”‚ +β”œβ”€β”€ .devcontainer/ # Development container configuration +β”‚ β”œβ”€β”€ devcontainer.json # VS Code DevContainer config +β”‚ β”œβ”€β”€ docker-compose.yml # Dev services (app, mongo, mongo-express) +β”‚ β”œβ”€β”€ Dockerfile # Custom dev image (Node 14 + Python 2.7) +β”‚ └── postCreateCommand.sh # Auto-install dependencies +β”‚ +β”œβ”€β”€ docker-compose.yml # Production deployment configuration +β”œβ”€β”€ package.json # Root workspace configuration +└── README.md # This file +``` + +--- + +## πŸ“œ Available Scripts + +### Development + +```bash +# Start both frontend and backend concurrently +npm run dev:all + +# Start only backend (port 3069) +npm run dev:backend + +# Start only frontend (port 8100) +npm run dev:app +``` + +### Installation + +```bash +# Install all dependencies (root + workspaces) +npm run install:all + +# Install workspace dependencies only +npm run install:workspaces +``` + +### Database + +```bash +# Seed MongoDB with initial data from backend/data/profiles.json +npm run seed + +# Access MongoDB shell +npm run mongo:shell +``` + +### Build & Deploy + +```bash +# Build both frontend and backend +npm run build + +# Build Docker images for production +npm run docker:build + +# Deploy to production (docker-compose up) +npm run deploy +``` + +### Testing & Quality + +```bash +# Run all tests (backend + frontend) +npm test + +# Run backend tests only (Mocha/Chai) +npm run test:backend + +# Run frontend tests only +npm run test:app + +# Lint all code +npm run lint +``` + +--- + +## πŸ’Ύ Data Management + +### ⚠️ Important: No Interactive Data Entry + +**This application currently does NOT support adding or editing data through the UI.** All profile and message data is seeded from static JSON files. + +### How to Add/Edit Content + +1. **Edit the seed data file**: [`backend/data/profiles.json`](backend/data/profiles.json) + +2. **Re-seed the database**: + + ```bash + npm run seed + ``` + +3. **For production deployments**: The database is wiped and reseeded on each deployment, so all changes must be committed to `profiles.json` before deploying. + +### Data Structure + +Profiles are stored in `backend/data/profiles.json` with this structure: + +```json +{ + "order": 1, + "details": { + "name": "John", + "age": 28, + "location": "San Francisco, CA", + "about": "User's story...", + "pic": { + "thumb": "profile/john_thumbnail.png", + "detail": "profile/john_detail.png" + } + }, + "messages": [ + { + "text": "What's your story?", + "isUser": false + }, + { + "text": "I've been using dating apps for...", + "isUser": true + } + ], + "submitted": true, + "approved": true +} +``` + +--- + +## πŸ“š Documentation Links + +- **[App Documentation](app/README.md)** - Frontend architecture, pages, and services +- **[Backend Documentation](backend/README.md)** - API server, routes, and models +- **[API Reference](backend/API.md)** - Complete REST API endpoint documentation +- **[Database Schema](backend/SCHEMA.md)** - MongoDB collection schemas and relationships +- **[DevContainer Guide](.devcontainer/README.md)** - Development environment setup +- **[Deployment Guide](DEPLOYMENT.md)** - Production deployment with Docker and Traefik + +--- + +## πŸ”§ Troubleshooting + +### DevContainer Issues + +**Problem**: Container fails to start or build + +```bash +# Solution: Rebuild container without cache +F1 β†’ "Dev Containers: Rebuild Container Without Cache" +``` + +**Problem**: Port conflicts (8100, 3069, 27017, 8081 already in use) + +```bash +# Solution: Stop conflicting services +docker ps # Find conflicting containers +docker stop + +# Or change ports in .devcontainer/docker-compose.yml +``` + +**Problem**: `node_modules` issues or permission errors + +```bash +# Solution: Remove node_modules and reinstall +rm -rf app/node_modules backend/node_modules node_modules +npm run install:all +``` + +### Frontend Issues + +**Problem**: `node-sass` build failed + +```bash +# Solution: Python 2.7 is required (included in DevContainer) +# If running locally without DevContainer: +npm install --legacy-peer-deps +``` + +**Problem**: Peer dependency warnings during `npm install` + +```bash +# Solution: Use legacy peer deps flag (included in postCreateCommand.sh) +cd app +npm install --legacy-peer-deps +``` + +**Problem**: Module not found errors + +```bash +# Solution: Clear cache and reinstall +cd app +rm -rf node_modules package-lock.json +npm install --legacy-peer-deps +``` + +**Problem**: JavaScript heap out of memory + +```bash +# Solution: Increase Node.js memory limit +export NODE_OPTIONS="--max-old-space-size=4096" +npm run dev:app +``` + +### Backend Issues + +**Problem**: MongoDB connection failed + +```bash +# Solution: Verify MongoDB container is running +docker ps | grep mongo + +# Check MongoDB health +docker exec -it mongo --eval "db.adminCommand('ping')" + +# Restart MongoDB service +docker-compose -f .devcontainer/docker-compose.yml restart mongo +``` + +**Problem**: JWT token invalid errors + +```bash +# Solution: Verify JWT_SECRET is set in .devcontainer/dev.env +# Check backend/.env.example for required environment variables +``` + +**Problem**: Image upload fails + +```bash +# Solution: Check volume permissions and multer configuration +# Verify backend/src/images directory exists and is writable +ls -la backend/src/images +``` + +**Problem**: Cannot connect to API from frontend + +```bash +# Solution: Verify backend is running on port 3069 +curl http://localhost:3069/profiles + +# Check CORS settings in backend/src/app.js +``` + +### Database Issues + +**Problem**: Seed data not loading + +```bash +# Solution: Manually run seed script +cd backend +npm run seed + +# Or check data file exists +ls -la backend/data/profiles.json +``` + +**Problem**: Mongo Express not accessible + +```bash +# Solution: Verify service is running and credentials are correct +# Default: http://localhost:8081 (admin/password) +docker logs +``` + +--- + +## ⚠️ Technical Debt & Security + +### Outdated Dependencies + +This project uses **legacy technologies** (circa 2017-2018) that are significantly outdated: + +| Component | Current Version | Latest Stable | Status | +| ------------ | --------------- | ------------- | --------------------------- | +| **Node.js** | 14.x | 20.x LTS | β›” EOL April 2023 | +| **Angular** | 5.0.3 | 17.x | β›” 12 major versions behind | +| **Ionic** | 3.9.2 | 7.x | β›” 4 major versions behind | +| **Express** | 4.14.0 | 4.18.x | ⚠️ Missing security patches | +| **MongoDB** | 4.4 | 7.0 | ⚠️ 3 major versions behind | +| **Mongoose** | 4.7.4 | 8.x | β›” 4 major versions behind | + +### Security Concerns + +1. **CORS Configuration**: Wide open (`Access-Control-Allow-Origin: *`) + + - **Risk**: Any domain can make requests to API + - **Fix**: Restrict to specific origins in production (already configured in docker-compose.yml) + +2. **No Rate Limiting**: API has no request throttling + + - **Risk**: Vulnerable to brute force and DoS attacks + - **Fix**: Add `express-rate-limit` middleware + +3. **Missing Input Validation**: No validation library + + - **Risk**: Potential injection attacks + - **Fix**: Add Joi or express-validator + +4. **JWT Secret Management**: Secret must be set via environment variable + + - **Risk**: If leaked, tokens can be forged + - **Fix**: Use strong secrets, rotate regularly, consider refresh tokens + +5. **Image Upload Security**: No file type/size restrictions visible + + - **Risk**: Malicious file uploads + - **Fix**: Validate MIME types, set size limits, sanitize filenames + +6. **Node.js 14 EOL**: No longer receives security updates + - **Risk**: Unpatched vulnerabilities + - **Fix**: Upgrade to Node 20 LTS (see modernization path below) + +### Code Quality Issues + +- No TypeScript in backend (JavaScript only) +- Limited test coverage (backend has Mocha tests, frontend has none) +- No automated CI/CD pipeline +- `ionic-app-scripts` is deprecated +- Gulp is dated for task running + +--- + +## πŸš€ Future Modernization Path + +For developers tasked with modernizing this codebase, follow this **incremental upgrade strategy**: + +### Phase 1: Critical Security Updates (2-4 weeks) + +1. **Upgrade Node.js**: + + ```bash + # Update Dockerfile to use Node 20 LTS + FROM node:20-bullseye + + # Remove Python 2.7 symlinks (modern node-sass doesn't need it) + ``` + +2. **Update Backend Dependencies**: + + ```bash + cd backend + npm update express mongoose jsonwebtoken + npm install express-rate-limit express-validator helmet + ``` + +3. **Add Security Middleware**: + + ```javascript + // backend/src/app.js + const rateLimit = require("express-rate-limit"); + const helmet = require("helmet"); + const { body, validationResult } = require("express-validator"); + + app.use(helmet()); + app.use(rateLimit({ windowMs: 15 * 60 * 1000, max: 100 })); + ``` + +4. **Fix CORS** (already done in production docker-compose.yml): + ```javascript + // Restrict to specific origin + res.header("Access-Control-Allow-Origin", "https://pfosi.mifi.dev"); + ``` + +### Phase 2: Backend Modernization (4-6 weeks) + +1. **Convert Backend to TypeScript**: + + ```bash + npm install -D typescript @types/express @types/node @types/mongoose + npx tsc --init + # Incrementally convert .js files to .ts + ``` + +2. **Upgrade MongoDB & Mongoose**: + + ```bash + # Update docker-compose.yml + mongo: + image: mongo:7.0 + + # Update Mongoose + npm install mongoose@latest + ``` + +3. **Modern Testing Setup**: + + ```bash + npm install -D jest ts-jest @types/jest supertest + # Migrate from Mocha/Chai to Jest + ``` + +4. **Add API Documentation**: + ```bash + npm install swagger-jsdoc swagger-ui-express + # Generate OpenAPI/Swagger docs from code + ``` + +### Phase 3: Frontend Modernization (8-12 weeks) + +This is the **most complex** phase due to breaking changes. + +**Option A: Incremental Angular Upgrade** (Slower but safer) + +```bash +# Upgrade through each major version +ng update @angular/cli@6 @angular/core@6 +ng update @angular/cli@7 @angular/core@7 +# ... continue through each version to 17 +``` + +**Option B: Fresh Ionic 7 Rewrite** (Faster, cleaner) + +```bash +# Create new Ionic 7 app with Angular 17 +ionic start looking-v2 tabs --type=angular +# Port components one by one +# Benefits: Standalone components, Signals, better performance +``` + +**Recommendation**: For novice developers, **Option B (rewrite)** is recommended because: + +- Angular 5 β†’ 17 migration is complex with many breaking changes +- Ionic 3 β†’ 7 requires complete UI component rewrites +- Fresh start allows modern architecture (standalone components, no NgModules) +- Cleaner codebase without legacy workarounds + +### Phase 4: Modern Build Pipeline (2-3 weeks) + +1. **Replace deprecated build tools**: + + ```bash + # Remove ionic-app-scripts, use Angular CLI + # Remove Gulp, use npm scripts + ``` + +2. **Add CI/CD**: + + ```yaml + # .github/workflows/ci.yml or .gitlab-ci.yml + - Run tests on every commit + - Automated Docker builds + - Deployment to staging/production + ``` + +3. **Code Quality Tools**: + ```bash + npm install -D eslint prettier husky lint-staged + # Pre-commit hooks for linting + ``` + +### Estimated Total Effort + +- **Phase 1 (Security)**: 2-4 weeks - **START HERE** +- **Phase 2 (Backend)**: 4-6 weeks +- **Phase 3 (Frontend)**: 8-12 weeks - **Most complex** +- **Phase 4 (DevOps)**: 2-3 weeks + +**Total**: 16-25 weeks (4-6 months) for full modernization + +### Resources for Learning + +- [Angular Update Guide](https://update.angular.io/) - Step-by-step migration instructions +- [Ionic Migration Guide](https://ionicframework.com/docs/intro/upgrading-to-ionic-7) +- [Node.js Best Practices](https://github.com/goldbergyoni/nodebestpractices) +- [Express Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html) + +--- + +## 🀝 Contributing + +This is an art project with specific artistic intent. Before making changes: + +1. Understand the artistic and documentary purpose +2. Preserve the original narrative and user stories +3. Consult with the artist (Nick Pfosi) for content changes +4. Follow the modernization path for technical improvements + +--- + +## πŸ“„ License + +See project repository for license information. + +--- + +## πŸ‘€ Author + +**Nick Pfosi** - Artist & Creator +**Mike Fitzpatrick** - Technical Implementation (badmf@mifi.dev) + +--- + +**Questions or Issues?** Check the [troubleshooting section](#troubleshooting) or refer to component-specific documentation linked above. diff --git a/app/README.md b/app/README.md new file mode 100644 index 0000000..7f46129 --- /dev/null +++ b/app/README.md @@ -0,0 +1,713 @@ +# Looking - Frontend Application + +> **Ionic 3 / Angular 5 mobile application for browsing dating app profiles and stories** + +This is the frontend mobile application for the "Looking" art project. Built with Ionic 3 and Angular 5, it provides an interactive interface for exploring user profiles, stories, and messages about modern gay dating culture. + +--- + +## πŸ“‹ Table of Contents + +- [Overview](#overview) +- [Tech Stack](#tech-stack) +- [Project Structure](#project-structure) +- [Pages & Components](#pages--components) +- [Services](#services) +- [Data Flow](#data-flow) +- [Development](#development) +- [Building](#building) +- [Troubleshooting](#troubleshooting) + +--- + +## 🎯 Overview + +The frontend is a **hybrid mobile application** that can run as: + +- Web application (development mode on port 8100) +- iOS native app (requires Xcode) +- Android native app (requires Android Studio) + +**Key Features:** + +- Grid-based profile browsing (Tinder-like swipe interface) +- Detailed profile views with images and user stories +- Message threads with conversation history +- Story submission form ("Tell Your Story") +- Tab-based navigation +- Offline support with local JSON fallback + +--- + +## πŸ›  Tech Stack + +| Technology | Version | Purpose | +| ------------------- | ------- | ---------------------------------------- | +| **Ionic Framework** | 3.9.2 | Hybrid mobile framework | +| **Angular** | 5.0.3 | Component framework | +| **TypeScript** | 2.4.2 | Type-safe JavaScript | +| **RxJS** | 5.5.2 | Reactive programming | +| **@angular/http** | 5.0.3 | HTTP client (deprecated, pre-HttpClient) | +| **ionic-swipe-all** | 1.2.0 | Swipe gesture library | +| **moment** | 2.21.0 | Date/time formatting | +| **@ionic/storage** | 2.1.3 | Local storage abstraction | +| **ionicons** | 3.0.0 | Icon library | + +**Build Tool**: `@ionic/app-scripts` 3.2.4 (Ionic's custom webpack wrapper) + +--- + +## πŸ“ Project Structure + +``` +app/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ app/ # Root application module +β”‚ β”‚ β”œβ”€β”€ app.component.ts # Root component (title: "Urnings") +β”‚ β”‚ β”œβ”€β”€ app.module.ts # Main NgModule with declarations +β”‚ β”‚ β”œβ”€β”€ app.html # Root template +β”‚ β”‚ β”œβ”€β”€ app.scss # Global styles +β”‚ β”‚ └── main.ts # Bootstrap entry point +β”‚ β”‚ +β”‚ β”œβ”€β”€ pages/ # Page components (routable views) +β”‚ β”‚ β”œβ”€β”€ tabs/ # Tab navigation container +β”‚ β”‚ β”‚ β”œβ”€β”€ tabs.ts # TabsPage component +β”‚ β”‚ β”‚ └── tabs.html # Tab layout (3 tabs) +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ grid/ # Profile grid view (main browse page) +β”‚ β”‚ β”‚ β”œβ”€β”€ grid.ts # Grid logic with swipe handling +β”‚ β”‚ β”‚ β”œβ”€β”€ grid.html # Grid layout with profile cards +β”‚ β”‚ β”‚ └── grid.scss # Grid styles +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ profile/ # Detailed profile view +β”‚ β”‚ β”‚ β”œβ”€β”€ profile.ts # Profile display logic +β”‚ β”‚ β”‚ β”œβ”€β”€ profile.html # Profile template (name, age, about, pics) +β”‚ β”‚ β”‚ └── profile.scss # Profile styles +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ chat/ # Individual conversation view +β”‚ β”‚ β”‚ β”œβ”€β”€ chat.ts # Chat message display +β”‚ β”‚ β”‚ β”œβ”€β”€ chat.html # Message thread UI +β”‚ β”‚ β”‚ └── chat.scss # Chat bubble styles +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ messages/ # Message list/inbox view +β”‚ β”‚ β”‚ β”œβ”€β”€ messages.ts # Message list logic +β”‚ β”‚ β”‚ β”œβ”€β”€ messages.html # List of conversations +β”‚ β”‚ β”‚ └── messages.scss # Message list styles +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ users/ # User list view +β”‚ β”‚ β”‚ β”œβ”€β”€ users.ts # User list logic +β”‚ β”‚ β”‚ β”œβ”€β”€ users.html # User grid/list +β”‚ β”‚ β”‚ └── users.scss # User list styles +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ lightbox/ # Full-screen image viewer +β”‚ β”‚ β”‚ β”œβ”€β”€ lightbox.ts # Image zoom/pan logic +β”‚ β”‚ β”‚ β”œβ”€β”€ lightbox.html # Fullscreen image display +β”‚ β”‚ β”‚ └── lightbox.scss # Lightbox styles +β”‚ β”‚ β”‚ +β”‚ β”‚ β”œβ”€β”€ information/ # Info/about page +β”‚ β”‚ β”‚ β”œβ”€β”€ information.ts # Static info page +β”‚ β”‚ β”‚ β”œβ”€β”€ information.html # Project information +β”‚ β”‚ β”‚ └── information.scss # Info page styles +β”‚ β”‚ β”‚ +β”‚ β”‚ └── tell/ # Story submission form +β”‚ β”‚ β”œβ”€β”€ tell.ts # Form submission logic +β”‚ β”‚ β”œβ”€β”€ tell.html # Story submission form +β”‚ β”‚ └── tell.scss # Form styles +β”‚ β”‚ +β”‚ β”œβ”€β”€ services/ # Shared services +β”‚ β”‚ └── profiles.ts # ProfileService for data management +β”‚ β”‚ +β”‚ β”œβ”€β”€ theme/ # Global theme configuration +β”‚ β”‚ └── variables.scss # Ionic theme variables +β”‚ β”‚ +β”‚ β”œβ”€β”€ assets/ # Static assets +β”‚ β”‚ β”œβ”€β”€ data/ # Local JSON data files (fallback) +β”‚ β”‚ β”‚ β”œβ”€β”€ profiles.json # All profiles +β”‚ β”‚ β”‚ β”œβ”€β”€ profiles initial.json # Initial seed data +β”‚ β”‚ β”‚ └── cruises.json # Additional data +β”‚ β”‚ β”œβ”€β”€ icon/ # App icons +β”‚ β”‚ β”œβ”€β”€ images/ # Profile, message images +β”‚ β”‚ β”‚ β”œβ”€β”€ profile/ # Profile photos +β”‚ β”‚ β”‚ β”œβ”€β”€ message/ # Message attachments +β”‚ β”‚ β”‚ └── cruise/ # Cruise-related images +β”‚ β”‚ └── imgs/ # UI images and icons +β”‚ β”‚ +β”‚ β”œβ”€β”€ index.html # Main HTML entry +β”‚ β”œβ”€β”€ manifest.json # PWA manifest +β”‚ └── service-worker.js # Service worker for offline support +β”‚ +β”œβ”€β”€ www/ # Build output directory +β”‚ β”œβ”€β”€ build/ # Compiled JS/CSS bundles +β”‚ β”‚ β”œβ”€β”€ main.js # Application code +β”‚ β”‚ β”œβ”€β”€ vendor.js # Third-party libraries +β”‚ β”‚ β”œβ”€β”€ polyfills.js # Browser polyfills +β”‚ β”‚ └── main.css # Compiled styles +β”‚ └── assets/ # Copied static assets +β”‚ +β”œβ”€β”€ package.json # Dependencies and scripts +β”œβ”€β”€ ionic.config.json # Ionic CLI configuration +β”œβ”€β”€ tsconfig.json # TypeScript compiler config +β”œβ”€β”€ tslint.json # TSLint rules (deprecated) +└── .editorconfig # Editor formatting rules +``` + +--- + +## πŸ“„ Pages & Components + +### TabsPage (`pages/tabs/`) + +**Root navigation container** with three tabs: + +```typescript +// tabs.ts - Tab configuration +tabs = [ + { root: GridPage, tabTitle: "Grid", tabIcon: "grid" }, + { root: UsersPage, tabTitle: "Users", tabIcon: "people" }, + { root: MessagesPage, tabTitle: "Messages", tabIcon: "chatboxes" }, +]; +``` + +### GridPage (`pages/grid/`) + +**Main profile browsing interface** - Tinder-like grid: + +- Displays profile thumbnails in grid layout +- Supports swipe gestures (using `ionic-swipe-all`) +- Taps open detailed profile view +- Loads profiles from ProfileService +- Falls back to local JSON if API unavailable + +**Key Features:** + +- Swipe left/right for navigation +- Pull-to-refresh +- Infinite scroll +- Click thumbnail β†’ ProfilePage + +### ProfilePage (`pages/profile/`) + +**Detailed profile view** with: + +- Profile photo (large detail image) +- Name, age, location +- "About" section with user story +- Multiple photos in gallery +- Message thread (if profile has messages) +- Navigation to previous/next profile + +**Navigation:** + +- Receives profile ID via NavParams +- "Next" and "Previous" buttons +- Photo gallery opens LightboxPage + +### ChatPage (`pages/chat/`) + +**Message thread view**: + +- Displays conversation history +- Question/answer format +- User responses vs. interviewer questions +- Image attachments supported +- Timestamps with moment.js + +**Message Types:** + +- `isUser: false` β†’ Questions (left-aligned) +- `isUser: true` β†’ User responses (right-aligned) + +### MessagesPage (`pages/messages/`) + +**Inbox/conversation list**: + +- Shows all profiles with messages +- Displays message preview +- Unread indicators +- Tap to open ChatPage with full thread + +### UsersPage (`pages/users/`) + +**User list view**: + +- Alternative view to grid +- List format with thumbnails +- Quick access to profiles +- Filter/search options (if implemented) + +### LightboxPage (`pages/lightbox/`) + +**Full-screen image viewer**: + +- Zoom and pan gestures +- Swipe between photos +- Close button returns to profile +- Supports pinch-to-zoom + +### InformationPage (`pages/information/`) + +**Static information page**: + +- About the art project +- Artist statement +- Project goals +- Credits and acknowledgments + +### TellPage (`pages/tell/`) + +**Story submission form**: + +- Users can submit their own stories +- Form fields: name, age, location, story +- Photo upload +- Submission sends to API (if connected) + +--- + +## πŸ”Œ Services + +### ProfileService (`services/profiles.ts`) + +**Central data management service** for profiles. + +**Endpoints:** + +```typescript +endpoint: "http://localhost:27017/urnings/profiles"; // Primary API +fallback: "assets/data/profiles.json"; // Local fallback +epSubmitted: "/submitted"; // Submitted profiles +epVerified: "/verified"; // Verified profiles +``` + +**Methods:** + +```typescript +load(); // Load all profiles +loadSubmitted(); // Load submitted (pending) profiles +loadVerified(); // Load verified (approved) profiles +getProfiles(); // Get cached profiles +getProfileById(id); // Get specific profile +getNextProfile(id); // Navigate to next profile +getPreviousProfile(id); // Navigate to previous +``` + +**Data Flow:** + +1. Tries to fetch from API endpoint +2. On error, falls back to local `assets/data/profiles.json` +3. Caches data in memory +4. Builds IDβ†’index map for navigation + +**Fallback Behavior:** + +```typescript +doGetRequest(endpoint, resolve, type) { + this.http.get(endpoint).subscribe( + (data) => { /* Use API data */ }, + (error) => { + // API failed β†’ use local JSON + this.doGetRequest(this.fallback, resolve, type); + } + ); +} +``` + +--- + +## πŸ”„ Data Flow + +### Profile Loading Flow + +```mermaid +graph TD + A[GridPage loads] --> B{ProfileService.load} + B --> C[Try API: http://localhost:27017/urnings/profiles] + C --> D{API Success?} + D -->|Yes| E[Store profiles in memory] + D -->|No| F[Fallback to assets/data/profiles.json] + F --> E + E --> G[Build ID-to-index map] + G --> H[Return profiles to GridPage] + H --> I[Display grid of thumbnails] + I --> J[User taps profile] + J --> K[Navigate to ProfilePage with ID] + K --> L[ProfilePage fetches profile from service] + L --> M[Display full profile details] +``` + +### Message Loading Flow + +```mermaid +graph TD + A[ProfilePage loads] --> B[Get profile by ID] + B --> C{Profile has messages?} + C -->|Yes| D[Extract messages array] + C -->|No| E[Show 'No messages'] + D --> F[Display in ChatPage] + F --> G[Format with moment.js] + G --> H[Render question/answer pairs] +``` + +--- + +## πŸ’» Development + +### Start Dev Server + +```bash +# From project root +npm run dev:app + +# Or from app directory +cd app +ionic serve --host=0.0.0.0 --port=8100 +``` + +**Access**: http://localhost:8100 + +### Live Reload + +Ionic's dev server watches for file changes and automatically: + +- Recompiles TypeScript +- Rebuilds SCSS +- Reloads browser +- Preserves dev tools open + +### Chrome DevTools + +Open Chrome DevTools to: + +- Inspect components +- Debug TypeScript (source maps enabled) +- Test responsive layouts +- Simulate mobile devices +- Monitor network requests + +### API Configuration + +To switch between local and remote API: + +**Edit `services/profiles.ts`:** + +```typescript +// Local API (backend running on port 3069) +endpoint: "http://localhost:3069/profiles"; + +// Remote API (production) +endpoint: "https://api.pfosi.mifi.dev/profiles"; + +// Fallback (always local) +fallback: "assets/data/profiles.json"; +``` + +--- + +## πŸ—οΈ Building + +### Development Build + +```bash +npm run build +# or +ionic build +``` + +Output: `www/` directory + +### Production Build + +```bash +ionic build --prod +``` + +**Optimizations:** + +- Minification +- Tree shaking +- Ahead-of-time (AOT) compilation +- CSS purging +- Source map generation (optional) + +### Build for Mobile Platforms + +**iOS**: + +```bash +ionic cordova build ios --prod --release +# Requires Xcode and iOS development certificates +``` + +**Android**: + +```bash +ionic cordova build android --prod --release +# Requires Android Studio and signing keys +``` + +**Note**: Mobile builds require additional Cordova setup not covered here. + +--- + +## πŸ”§ Troubleshooting + +### `node-sass` Build Failed + +**Error:** + +``` +Error: `gyp` failed with exit code: 1 +Module 'node-sass' not found +``` + +**Solution:** + +```bash +# node-sass requires Python 2.7 (included in DevContainer) +# If running locally: +npm rebuild node-sass --force + +# Or use legacy peer deps +npm install --legacy-peer-deps +``` + +**Why?** `node-sass` is a native module that requires Python 2.7 and build tools. The DevContainer includes these dependencies via symlinks. + +--- + +### Peer Dependency Warnings + +**Error:** + +``` +npm WARN ERESOLVE overriding peer dependency +``` + +**Solution:** + +```bash +# Use legacy peer dependency resolution (npm 7+) +npm install --legacy-peer-deps +``` + +**Why?** Ionic 3 and Angular 5 were built before npm 7's strict peer dependency checking. The `--legacy-peer-deps` flag uses npm 6 behavior. + +--- + +### Module Not Found Errors + +**Error:** + +``` +Error: Cannot find module '@angular/core' +``` + +**Solution:** + +```bash +# Remove node_modules and reinstall +rm -rf node_modules package-lock.json +npm install --legacy-peer-deps +``` + +--- + +### JavaScript Heap Out of Memory + +**Error:** + +``` +FATAL ERROR: Ineffective mark-compacts near heap limit Allocation failed +``` + +**Solution:** + +```bash +# Increase Node.js memory limit +export NODE_OPTIONS="--max-old-space-size=4096" +npm run ionic:serve +``` + +--- + +### Ionic Serve Port Already in Use + +**Error:** + +``` +Error: Port 8100 is already in use +``` + +**Solution:** + +```bash +# Find process using port 8100 +lsof -i :8100 + +# Kill the process +kill -9 + +# Or use different port +ionic serve --port=8101 +``` + +--- + +### TypeScript Compilation Errors + +**Error:** + +``` +Error: TS2304: Cannot find name 'Promise' +``` + +**Solution:** + +```bash +# Verify TypeScript version matches project +npm install typescript@2.4.2 --save-dev --save-exact +``` + +**Check `tsconfig.json`:** + +```json +{ + "compilerOptions": { + "lib": ["es2015", "dom"], + "target": "es5" + } +} +``` + +--- + +### CORS Errors in Browser + +**Error:** + +``` +Access to XMLHttpRequest at 'http://localhost:3069/profiles' blocked by CORS policy +``` + +**Solution:** + +**Option 1**: Backend already allows all origins: + +```javascript +// backend/src/app.js +res.header("Access-Control-Allow-Origin", "*"); +``` + +**Option 2**: Use Ionic's proxy (for dev server): + +**Edit `ionic.config.json`:** + +```json +{ + "proxies": [ + { + "path": "/api", + "proxyUrl": "http://localhost:3069" + } + ] +} +``` + +**Update service:** + +```typescript +endpoint: "/api/profiles"; // Proxied through dev server +``` + +--- + +### Images Not Loading + +**Error:** + +``` +404 Not Found: http://localhost:8100/assets/images/profile/john.png +``` + +**Solutions:** + +1. **Verify image exists:** + + ```bash + ls -la src/assets/images/profile/ + ``` + +2. **Check path in data:** + + ```json + // Should be relative to assets/images/ + "pic": { + "thumb": "profile/john_thumbnail.png", // βœ… Correct + "detail": "/profile/john_detail.png" // ❌ Leading slash breaks it + } + ``` + +3. **Rebuild to copy assets:** + ```bash + ionic build + ``` + +--- + +### Service Worker Issues (Offline Mode) + +**Problem**: Stale data cached, changes not appearing + +**Solution:** + +```bash +# Clear service worker cache in Chrome DevTools +Application β†’ Storage β†’ Clear site data + +# Or disable service worker during development +# Comment out service worker registration in index.html +``` + +--- + +### Ionic CLI Not Found + +**Error:** + +``` +ionic: command not found +``` + +**Solution:** + +```bash +# Install Ionic CLI globally (included in DevContainer) +npm install -g @ionic/cli + +# Verify installation +ionic --version +``` + +--- + +## πŸ“š Additional Resources + +- **Ionic 3 Documentation**: https://ionicframework.com/docs/v3/ +- **Angular 5 Documentation**: https://v5.angular.io/docs +- **TypeScript Documentation**: https://www.typescriptlang.org/docs/handbook/basic-types.html +- **RxJS 5 Documentation**: https://rxjs-dev.firebaseapp.com/api + +--- + +## πŸ”— Related Documentation + +- [Root README](../README.md) - Project overview and quick start +- [Backend README](../backend/README.md) - API server documentation +- [API Reference](../backend/API.md) - REST API endpoints +- [Database Schema](../backend/SCHEMA.md) - MongoDB collections +- [DevContainer Guide](../.devcontainer/README.md) - Development environment +- [Deployment Guide](../DEPLOYMENT.md) - Production deployment + +--- + +**Need Help?** Check the [root troubleshooting section](../README.md#troubleshooting) or backend documentation for API-related issues. diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..726bc53 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,227 @@ +# Looking Backend - Environment Variables +# Copy this file to .env and fill in your values +# NEVER commit .env files to version control + +# ============================================ +# SERVER CONFIGURATION +# ============================================ + +# Port for Express server to listen on +# Default: 3069 +# Production: Use same port or configure via load balancer +PORT=3069 + +# Node environment +# Options: development, production, test +NODE_ENV=development + +# ============================================ +# DATABASE CONFIGURATION +# ============================================ + +# MongoDB connection string +# Development (DevContainer): mongodb://mongo:27017/urge +# Development (Local): mongodb://localhost:27017/urge +# Production: Use MongoDB Atlas or managed instance +MONGODB_URI=mongodb://mongo:27017/urge + +# MongoDB Admin Credentials (for connection if auth enabled) +# Only needed if MongoDB requires authentication +# MONGO_USER=admin +# MONGO_PASS=password + +# ============================================ +# JWT AUTHENTICATION +# ============================================ + +# Secret key for JWT token signing +# CRITICAL: Use a strong random string (minimum 32 characters) +# Generate with: openssl rand -base64 32 +# NEVER share or commit this value +JWT_SECRET=your-super-secret-jwt-key-change-this-to-random-32-chars + +# JWT token expiration time +# Options: '15m', '1h', '24h', '7d' +# Default: 15m (15 minutes) +JWT_EXPIRES_IN=15m + +# ============================================ +# GOOGLE MAPS API +# ============================================ + +# Google Maps API key for geocoding features +# Get API key: https://console.cloud.google.com/apis/credentials +# Enable: Geocoding API, Places API (if used) +# Restrict: Set HTTP referrer or IP restrictions for security +GOOGLE_MAPS_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX + +# ============================================ +# EMAIL CONFIGURATION (SMTP) +# ============================================ + +# SMTP server hostname +# Examples: +# Gmail: smtp.gmail.com +# Outlook: smtp-mail.outlook.com +# SendGrid: smtp.sendgrid.net +# Custom: mail.yourdomain.com +MAIL_HOST=smtp.gmail.com + +# SMTP server port +# Common ports: +# 587 - TLS/STARTTLS (recommended) +# 465 - SSL +# 25 - Unencrypted (not recommended) +MAIL_PORT=587 + +# SMTP username (usually your email address) +MAIL_USER=support@example.com + +# SMTP password or app-specific password +# For Gmail: Use App Password (not your account password) +# 1. Go to Google Account β†’ Security β†’ 2-Step Verification +# 2. Scroll to "App passwords" +# 3. Generate password for "Mail" +# 4. Use that 16-character password here +MAIL_PASS=your-email-password-or-app-specific-password + +# Email sender name (displayed in "From" field) +MAIL_FROM_NAME=Looking App Support + +# Email sender address (must match MAIL_USER or authorized sender) +MAIL_FROM_ADDRESS=support@example.com + +# ============================================ +# CORS CONFIGURATION +# ============================================ + +# Allowed origins for CORS (comma-separated) +# Development: * (all origins) +# Production: Specific domains only +# Examples: +# Development: * +# Production: https://pfosi.mifi.dev,https://www.pfosi.mifi.dev +CORS_ORIGIN=* + +# ============================================ +# FILE UPLOAD CONFIGURATION +# ============================================ + +# Maximum file size for image uploads (in bytes) +# 5MB = 5242880 bytes +# 10MB = 10485760 bytes +MAX_FILE_SIZE=5242880 + +# Allowed image MIME types (comma-separated) +ALLOWED_IMAGE_TYPES=image/jpeg,image/png,image/gif,image/webp + +# ============================================ +# LOGGING CONFIGURATION +# ============================================ + +# Log level +# Options: error, warn, info, http, verbose, debug, silly +# Production: info or warn +# Development: debug or verbose +LOG_LEVEL=debug + +# Log file location (if file logging enabled) +# Default: logs/combined.log +LOG_FILE=logs/combined.log + +# Error log file location +LOG_ERROR_FILE=logs/error.log + +# ============================================ +# SECURITY CONFIGURATION +# ============================================ + +# Password hashing iterations (PBKDF2) +# Higher = more secure but slower +# Default: 233335 +# Recommended: 100000+ +PASSWORD_HASH_ITERATIONS=233335 + +# Password minimum length +PASSWORD_MIN_LENGTH=8 + +# Session secret for express-session (if using sessions) +# SESSION_SECRET=your-session-secret-change-this + +# ============================================ +# RATE LIMITING (if implemented) +# ============================================ + +# Maximum requests per window +# RATE_LIMIT_MAX=100 + +# Time window in milliseconds (15 minutes = 900000) +# RATE_LIMIT_WINDOW_MS=900000 + +# ============================================ +# PRODUCTION DEPLOYMENT +# ============================================ + +# Domain/hostname for the application +# Used for email links, CORS, etc. +# APP_URL=https://pfosi.mifi.dev +# API_URL=https://api.pfosi.mifi.dev + +# Traefik labels (if using docker-compose with Traefik) +# TRAEFIK_ENABLE=true +# TRAEFIK_DOMAIN=api.pfosi.mifi.dev + +# ============================================ +# MONITORING & ANALYTICS (optional) +# ============================================ + +# Sentry DSN for error tracking +# SENTRY_DSN=https://xxxxx@sentry.io/xxxxx + +# Google Analytics tracking ID +# GA_TRACKING_ID=UA-XXXXXXXXX-X + +# ============================================ +# DATABASE SEEDING +# ============================================ + +# Path to seed data file +# Default: data/profiles.json +SEED_DATA_PATH=data/profiles.json + +# Auto-seed database on startup (true/false) +# WARNING: This will wipe existing data +# Only use in development +AUTO_SEED=false + +# ============================================ +# FEATURE FLAGS (optional) +# ============================================ + +# Enable user story submissions via public endpoint +# ENABLE_SUBMISSIONS=true + +# Require admin approval for submitted stories +# REQUIRE_APPROVAL=true + +# Enable email notifications for new submissions +# NOTIFY_ON_SUBMISSION=true + +# ============================================ +# NOTES +# ============================================ + +# 1. NEVER commit this file with real values to version control +# 2. Add .env to .gitignore (already done) +# 3. Use different values for development and production +# 4. Rotate secrets regularly in production +# 5. Use environment-specific .env files: +# - .env.development +# - .env.production +# - .env.test +# 6. In production, use secret management tools: +# - Docker secrets +# - Kubernetes secrets +# - AWS Secrets Manager +# - Azure Key Vault +# - HashiCorp Vault diff --git a/backend/API.md b/backend/API.md new file mode 100644 index 0000000..5b87bb4 --- /dev/null +++ b/backend/API.md @@ -0,0 +1,1029 @@ +# Looking API Reference + +> **Complete REST API documentation for the Looking backend** + +This document provides detailed information about all API endpoints, including request/response formats, authentication requirements, and error codes. + +--- + +## πŸ“‹ Table of Contents + +- [Base URL](#base-url) +- [Authentication](#authentication) +- [Common Response Codes](#common-response-codes) +- [Authentication Endpoints](#authentication-endpoints) +- [Profile Endpoints](#profile-endpoints) +- [User Endpoints](#user-endpoints) +- [Geocache Endpoints](#geocache-endpoints) +- [Error Handling](#error-handling) + +--- + +## 🌐 Base URL + +**Development**: `http://localhost:3069` +**Production**: `https://api.pfosi.mifi.dev` + +All endpoints are prefixed with the base URL. + +--- + +## πŸ” Authentication + +Most endpoints require JWT authentication via the `Authorization` header. + +### Header Format + +``` +Authorization: Bearer +``` + +### Permission Levels + +| Permission | Description | +| ---------- | ----------------------------------- | +| **view** | Read-only access to profiles | +| **add** | Create new profiles | +| **edit** | Modify existing profiles | +| **update** | Update profile details | +| **delete** | Remove profiles | +| **manage** | Approve submissions, manage content | +| **super** | Full admin access, user management | + +### Obtaining a Token + +Use the `/auth/login` endpoint (see [Authentication Endpoints](#authentication-endpoints)). + +--- + +## πŸ“Š Common Response Codes + +| Code | Status | Description | +| ------- | --------------------- | ----------------------------- | +| **200** | OK | Request successful | +| **201** | Created | Resource created successfully | +| **400** | Bad Request | Invalid request data | +| **401** | Unauthorized | Missing or invalid token | +| **403** | Forbidden | Insufficient permissions | +| **404** | Not Found | Resource not found | +| **500** | Internal Server Error | Server error | + +--- + +## πŸ”‘ Authentication Endpoints + +### POST `/auth/login` + +Authenticate user and receive JWT token. + +**Request:** + +```json +POST /auth/login +Content-Type: application/json + +{ + "username": "admin", + "password": "password123" +} +``` + +**Success Response (200):** + +```json +{ + "status": 200, + "authorized": true, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "user": { + "id": "507f1f77bcf86cd799439011", + "username": "admin", + "name": { + "first": "John", + "last": "Doe" + }, + "email": "admin@example.com", + "can": ["add", "edit", "delete", "manage", "super", "update", "view"] + } +} +``` + +**Failure Response (401):** + +```json +{ + "status": 401, + "authorized": false, + "message": "Invalid username or password" +} +``` + +--- + +### POST `/auth/logout` + +Logout user (client-side token removal, no server-side action needed). + +**Request:** + +```json +POST /auth/logout +Authorization: Bearer +``` + +**Response (200):** + +```json +{ + "message": "Logged out successfully" +} +``` + +--- + +### GET `/auth/session` + +Validate existing JWT token and get user info. + +**Request:** + +``` +GET /auth/session +Authorization: Bearer +``` + +**Success Response (200):** + +```json +{ + "valid": true, + "decoded": { + "uid": "507f1f77bcf86cd799439011", + "username": "admin", + "can": ["add", "edit", "delete", "manage", "super", "update", "view"], + "iat": 1640000000, + "exp": 1640003600 + } +} +``` + +**Failure Response (401):** + +```json +{ + "valid": false, + "message": "Token expired or invalid" +} +``` + +--- + +### POST `/auth/session` + +Create anonymous session token (limited permissions). + +**Request:** + +```json +POST /auth/session +``` + +**Response (200):** + +```json +{ + "status": 200, + "authorized": false, + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..." +} +``` + +--- + +### PUT `/auth/session` + +Refresh existing JWT token (extend expiration). + +**Request:** + +``` +PUT /auth/session +Authorization: Bearer +``` + +**Response (200):** + +```json +{ + "token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", + "expiresIn": "15m" +} +``` + +--- + +### POST `/auth/reset` + +Request password reset email. + +**Request:** + +```json +POST /auth/reset +Content-Type: application/json + +{ + "username": "admin" +} +``` + +**Response (200):** + +```json +{ + "message": "Password reset email sent", + "email": "ad***@example.com" +} +``` + +--- + +### GET `/auth/reset/:id/:token` + +Verify password reset token validity. + +**Request:** + +``` +GET /auth/reset/507f1f77bcf86cd799439011/abc123def456 +``` + +**Response (200):** + +```json +{ + "valid": true, + "userId": "507f1f77bcf86cd799439011", + "expires": "2024-01-15T12:00:00Z" +} +``` + +--- + +### PUT `/auth/reset/:id/:token` + +Reset password with valid token. + +**Request:** + +```json +PUT /auth/reset/507f1f77bcf86cd799439011/abc123def456 +Content-Type: application/json + +{ + "password": "newPassword123", + "confirmPassword": "newPassword123" +} +``` + +**Response (200):** + +```json +{ + "message": "Password updated successfully", + "success": true +} +``` + +--- + +## πŸ‘€ Profile Endpoints + +### GET `/profiles` + +Get all approved profiles. + +**Request:** + +``` +GET /profiles +``` + +**Response (200):** + +```json +[ + { + "_id": "507f1f77bcf86cd799439011", + "order": 1, + "details": { + "name": "John", + "age": 28, + "location": "San Francisco, CA", + "about": "User's story about dating...", + "pic": { + "thumb": "profile/john_thumbnail.png", + "detail": "profile/john_detail.png" + }, + "position": ["Top", "Versatile"], + "looking": ["Dates", "Friends"], + "tribes": ["Geek", "Jock"], + "ethnos": ["White", "Latino"] + }, + "messages": [ + { + "_id": "507f191e810c19729de860ea", + "text": "What brought you to dating apps?", + "isUser": false, + "timestamp": "2024-01-15T10:30:00Z" + }, + { + "_id": "507f191e810c19729de860eb", + "text": "I moved to a new city...", + "isUser": true, + "timestamp": "2024-01-15T10:32:00Z" + } + ], + "submitted": true, + "approved": true + } +] +``` + +--- + +### GET `/profiles/submitted` + +Get all submitted (pending approval) profiles. + +**Authentication**: Required (`view` permission) + +**Request:** + +``` +GET /profiles/submitted +Authorization: Bearer +``` + +**Response (200):** + +```json +[ + { + "_id": "507f1f77bcf86cd799439012", + "details": { ... }, + "submitted": true, + "approved": false + } +] +``` + +--- + +### GET `/profiles/verified` + +Alias for approved profiles. + +**Request:** + +``` +GET /profiles/verified +``` + +**Response**: Same as `GET /profiles` + +--- + +### GET `/profiles/:id` + +Get single profile by ID. + +**Request:** + +``` +GET /profiles/507f1f77bcf86cd799439011 +``` + +**Response (200):** + +```json +{ + "_id": "507f1f77bcf86cd799439011", + "order": 1, + "details": { ... }, + "messages": [ ... ], + "submitted": true, + "approved": true +} +``` + +**Error (404):** + +```json +{ + "message": "Profile not found" +} +``` + +--- + +### POST `/profiles` + +Create new profile. + +**Authentication**: Required (`add` permission) + +**Request:** + +```json +POST /profiles +Authorization: Bearer +Content-Type: application/json + +{ + "order": 10, + "details": { + "name": "Alex", + "age": 25, + "location": "New York, NY", + "about": "My story...", + "pic": { + "thumb": "data:image/png;base64,iVBORw0KG...", // Base64 or path + "detail": "data:image/png;base64,iVBORw0KG..." + } + }, + "messages": [ + { + "text": "Question text", + "isUser": false + } + ], + "submitted": true, + "approved": false +} +``` + +**Response (201):** + +```json +{ + "_id": "507f1f77bcf86cd799439013", + "message": "Profile created successfully", + "profile": { ... } +} +``` + +--- + +### PUT `/profiles/:id` + +Update existing profile. + +**Authentication**: Required (`update` permission) + +**Request:** + +```json +PUT /profiles/507f1f77bcf86cd799439011 +Authorization: Bearer +Content-Type: application/json + +{ + "details": { + "about": "Updated story..." + } +} +``` + +**Response (200):** + +```json +{ + "message": "Profile updated successfully", + "profile": { ... } +} +``` + +--- + +### DELETE `/profiles/:id` + +Delete profile by ID. + +**Authentication**: Required (`delete` permission) + +**Request:** + +``` +DELETE /profiles/507f1f77bcf86cd799439011 +Authorization: Bearer +``` + +**Response (200):** + +```json +{ + "message": "Profile deleted successfully", + "id": "507f1f77bcf86cd799439011" +} +``` + +--- + +### GET `/profiles/approve/:id` + +Approve submitted profile. + +**Authentication**: Required (`manage` permission) + +**Request:** + +``` +GET /profiles/approve/507f1f77bcf86cd799439012 +Authorization: Bearer +``` + +**Response (200):** + +```json +{ + "message": "Profile approved", + "profile": { + "_id": "507f1f77bcf86cd799439012", + "approved": true + } +} +``` + +--- + +### POST `/profiles/submission` + +Submit new profile (public endpoint, no auth required). + +**Request:** + +```json +POST /profiles/submission +Content-Type: application/json + +{ + "details": { + "name": "Anonymous", + "age": 30, + "location": "Los Angeles, CA", + "about": "My dating story...", + "pic": { + "thumb": "data:image/png;base64,...", + "detail": "data:image/png;base64,..." + } + }, + "messages": [ + { + "text": "Tell us your story", + "isUser": false + }, + { + "text": "I've been using apps for 3 years...", + "isUser": true + } + ] +} +``` + +**Response (200):** + +```json +{ + "message": "Story submitted successfully. It will be reviewed before publishing.", + "profile": { + "_id": "507f1f77bcf86cd799439014", + "submitted": true, + "approved": false + } +} +``` + +--- + +### GET `/profiles/find/:limit?/:skip?/:min?/:max?/:pos?/:lkng?/:tribes?/:ethnos?` + +Advanced profile search with filters. + +**Authentication**: Required (`view` permission) + +**Parameters:** + +- `limit` - Number of results (default: all) +- `skip` - Number to skip for pagination +- `min` - Minimum age +- `max` - Maximum age +- `pos` - Position filter (pipe-separated: "Top|Bottom|Versatile") +- `lkng` - Looking for filter (pipe-separated: "Dates|Friends|Relationship") +- `tribes` - Tribes filter (pipe-separated: "Geek|Jock|Bear|Otter") +- `ethnos` - Ethnicity filter (pipe-separated: "White|Black|Latino|Asian") + +**Request:** + +``` +GET /profiles/find/10/0/25/35/Top|Versatile/Dates|Friends/Geek/null +Authorization: Bearer +``` + +**Response (200):** + +```json +[ + { + "_id": "507f1f77bcf86cd799439011", + "details": { + "age": 28, + "position": ["Top", "Versatile"], + "looking": ["Dates", "Friends"], + "tribes": ["Geek"] + } + } +] +``` + +--- + +### GET `/profiles/list/:limit?/:skip?/:min?/:max?/:pos?/:lkng?/:tribes?/:ethnos?` + +List profiles with minimal data (name, thumbnail only). + +**Authentication**: Required (`view` permission) + +**Response (200):** + +```json +[ + { + "_id": "507f1f77bcf86cd799439011", + "order": 1, + "details": { + "name": "John", + "pic": { + "thumb": "profile/john_thumbnail.png" + } + } + } +] +``` + +--- + +### PUT `/profiles/:profileId/message/:messageId` + +Update specific message in profile thread. + +**Authentication**: Required (`update` permission) + +**Request:** + +```json +PUT /profiles/507f1f77bcf86cd799439011/message/507f191e810c19729de860ea +Authorization: Bearer +Content-Type: application/json + +{ + "text": "Updated question text", + "image": "data:image/png;base64,..." +} +``` + +**Response (200):** + +```json +{ + "message": "Message updated successfully", + "profile": { ... } +} +``` + +--- + +## πŸ‘₯ User Endpoints + +### GET `/users` + +Get all users. + +**Authentication**: Required (`super` permission) + +**Request:** + +``` +GET /users +Authorization: Bearer +``` + +**Response (200):** + +```json +[ + { + "_id": "507f1f77bcf86cd799439011", + "username": "admin", + "name": { + "first": "John", + "last": "Doe" + }, + "email": "admin@example.com", + "can": ["add", "edit", "delete", "manage", "super", "update", "view"], + "forceReset": false + } +] +``` + +--- + +### GET `/users/:id` + +Get user by ID. + +**Authentication**: Required (`super` permission) + +**Response (200):** + +```json +{ + "_id": "507f1f77bcf86cd799439011", + "username": "admin", + "name": { "first": "John", "last": "Doe" }, + "email": "admin@example.com", + "can": ["super"] +} +``` + +--- + +### POST `/users` + +Create new user. + +**Authentication**: Required (`super` permission) + +**Request:** + +```json +POST /users +Authorization: Bearer +Content-Type: application/json + +{ + "username": "newuser", + "password": "password123", + "confirmPassword": "password123", + "name": { + "first": "Jane", + "last": "Smith" + }, + "email": "jane@example.com", + "can": ["view", "add"] +} +``` + +**Response (201):** + +```json +{ + "message": "User created successfully", + "user": { + "_id": "507f1f77bcf86cd799439015", + "username": "newuser" + } +} +``` + +--- + +### PUT `/users/:id` + +Update user information. + +**Authentication**: Required (`super` permission or own user) + +**Request:** + +```json +PUT /users/507f1f77bcf86cd799439011 +Authorization: Bearer +Content-Type: application/json + +{ + "name": { + "first": "John", + "last": "Updated" + }, + "email": "newemail@example.com", + "password": "newPassword123", + "confirmPassword": "newPassword123", + "currentPassword": "oldPassword123" +} +``` + +**Response (200):** + +```json +{ + "message": "User updated successfully", + "user": { ... } +} +``` + +--- + +### DELETE `/users/:id` + +Delete user. + +**Authentication**: Required (`super` permission) + +**Request:** + +``` +DELETE /users/507f1f77bcf86cd799439011 +Authorization: Bearer +``` + +**Response (200):** + +```json +{ + "message": "User deleted successfully" +} +``` + +--- + +## πŸ—ΊοΈ Geocache Endpoints + +### GET `/geocache/populate/:field` + +Populate geocache for a specific field (e.g., location). + +**Authentication**: Required (`manageAppPreferences` permission) + +**Request:** + +``` +GET /geocache/populate/location +Authorization: Bearer +``` + +**Response (200):** + +```json +{ + "message": "Geocache populated", + "count": 42, + "results": [ + { + "key": "San Francisco, CA", + "formatted": "San Francisco, California, USA", + "loc": { + "type": "Point", + "coordinates": [-122.4194, 37.7749] + } + } + ] +} +``` + +--- + +### PUT `/geocache/:id` + +Update geocache entry. + +**Authentication**: Required (`manageAppPreferences` permission) + +**Request:** + +```json +PUT /geocache/507f1f77bcf86cd799439011 +Authorization: Bearer +Content-Type: application/json + +{ + "key": "New York, NY", + "formatted": "New York, New York, USA", + "loc": { + "type": "Point", + "coordinates": [-74.0060, 40.7128] + } +} +``` + +**Response (200):** + +```json +{ + "message": "Geocache updated", + "geocache": { ... } +} +``` + +--- + +## ⚠️ Error Handling + +### Error Response Format + +All errors follow this structure: + +```json +{ + "message": "Human-readable error message", + "err": { + "code": "ERROR_CODE", + "details": "Additional error information" + } +} +``` + +### Common Error Messages + +| Error | Code | Cause | +| ---------------------------------------------- | ---- | ------------------------------ | +| **User not authorized to perform this action** | 403 | Missing required permission | +| **Token expired or invalid** | 401 | JWT validation failed | +| **Profile not found** | 404 | Invalid profile ID | +| **Invalid username or password** | 401 | Login failed | +| **Could not update profile** | 500 | Database error during update | +| **There was an error processing the image** | 500 | Image upload/processing failed | +| **No profile id or data specified** | 500 | Missing required request data | + +### Authentication Errors + +**Missing Token:** + +```json +{ + "message": "No authorization token provided", + "err": null +} +``` + +**Invalid Token:** + +```json +{ + "message": "User not authorized to perform this action", + "err": { + "name": "JsonWebTokenError", + "message": "invalid signature" + } +} +``` + +**Expired Token:** + +```json +{ + "message": "User not authorized to perform this action", + "err": { + "name": "TokenExpiredError", + "message": "jwt expired", + "expiredAt": "2024-01-15T12:00:00Z" + } +} +``` + +### Validation Errors + +**Password Mismatch:** + +```json +{ + "success": false, + "message": "There was an error saving the updated password." +} +``` + +**Duplicate Username:** + +```json +{ + "message": "There was a duplicate key error", + "err": { + "code": 11000, + "keyPattern": { "username": 1 } + } +} +``` + +--- + +## πŸ“š Related Documentation + +- **[Backend README](README.md)** - Server setup and development +- **[Database Schema](SCHEMA.md)** - MongoDB collection schemas +- **[Environment Variables](.env.example)** - Configuration +- **[Root README](../README.md)** - Project overview +- **[Deployment Guide](../DEPLOYMENT.md)** - Production deployment + +--- + +**Need Help?** Check the backend [troubleshooting section](README.md#troubleshooting) for common API issues. diff --git a/backend/README.md b/backend/README.md new file mode 100644 index 0000000..ceacc23 --- /dev/null +++ b/backend/README.md @@ -0,0 +1,652 @@ +# Looking - Backend API + +> **Express.js REST API with MongoDB for the Looking art project** + +This is the backend REST API that powers the "Looking" application. Built with Express 4 and MongoDB 4.4, it provides endpoints for profile management, user authentication, messaging, and geolocation features. + +--- + +## πŸ“‹ Table of Contents + +- [Overview](#overview) +- [Tech Stack](#tech-stack) +- [Project Structure](#project-structure) +- [Getting Started](#getting-started) +- [Data Flow](#data-flow) +- [Environment Variables](#environment-variables) +- [Database Seeding](#database-seeding) +- [Development Workflow](#development-workflow) +- [Troubleshooting](#troubleshooting) +- [Related Documentation](#related-documentation) + +--- + +## 🎯 Overview + +The backend API provides: + +- **Authentication**: JWT-based user sessions with PBKDF2 password hashing +- **Profile Management**: CRUD operations with approval workflow +- **Messaging**: Conversation threads with image support +- **Image Processing**: Automatic thumbnail and detail image generation +- **Geolocation**: Google Maps API integration for location features +- **Email**: Password reset and notification emails via Nodemailer + +**Server Port**: 3069 (configurable via PORT environment variable) +**Database**: MongoDB 4.4 on port 27017 (database name: `urge`) + +--- + +## πŸ›  Tech Stack + +| Technology | Version | Purpose | +| ---------------- | ------- | ---------------------- | +| **Express** | 4.14.0 | Web framework | +| **Node.js** | 14.x | Runtime | +| **MongoDB** | 4.4 | Database | +| **Mongoose** | 4.7.4 | MongoDB ODM | +| **jsonwebtoken** | 7.3.0 | JWT authentication | +| **multer** | 1.2.0 | File upload handling | +| **@google/maps** | 0.4.5 | Google Maps API client | +| **nodemailer** | 4.0.1 | Email sending | +| **winston** | 2.4.0 | Application logging | +| **morgan** | 1.7.0 | HTTP request logging | +| **moment** | 2.17.1 | Date/time manipulation | +| **shortid** | 2.2.8 | Short ID generation | +| **vcard-js** | 1.2.2 | VCard generation | + +**Dev Tools:** + +- **gulp** 3.9.1 - Task runner +- **nodemon** 1.11.0 - Auto-restart on file changes +- **mocha** 3.0.1 - Test framework +- **chai** 3.5.0 - Assertion library + +--- + +## πŸ“ Project Structure + +``` +backend/ +β”œβ”€β”€ src/ +β”‚ β”œβ”€β”€ app.js # Express app configuration +β”‚ β”‚ +β”‚ β”œβ”€β”€ bin/ +β”‚ β”‚ └── www # Server startup script (port 3069) +β”‚ β”‚ +β”‚ β”œβ”€β”€ routes/ # API route handlers +β”‚ β”‚ β”œβ”€β”€ auth.js # Authentication (login, logout, password reset) +β”‚ β”‚ β”œβ”€β”€ profiles.js # Profile CRUD + approval workflow +β”‚ β”‚ β”œβ”€β”€ users.js # User management +β”‚ β”‚ └── geocache.js # Geolocation/geocoding +β”‚ β”‚ +β”‚ β”œβ”€β”€ models/ # Mongoose schemas +β”‚ β”‚ β”œβ”€β”€ user.js # User schema (auth, permissions) +β”‚ β”‚ β”œβ”€β”€ profile.js # Profile schema (details, messages) +β”‚ β”‚ β”œβ”€β”€ message.js # Message schema (text, image, timestamp) +β”‚ β”‚ β”œβ”€β”€ reset.js # Password reset token schema +β”‚ β”‚ └── geocache.js # Location cache schema +β”‚ β”‚ +β”‚ β”œβ”€β”€ modules/ # Utility modules +β”‚ β”‚ β”œβ”€β”€ authentication.js # PBKDF2 password hashing +β”‚ β”‚ β”œβ”€β”€ token.js # JWT creation/verification +β”‚ β”‚ β”œβ”€β”€ images.js # Image upload/processing (multer) +β”‚ β”‚ β”œβ”€β”€ mailer.js # Email sending (nodemailer) +β”‚ β”‚ β”œβ”€β”€ geocoder.js # Google Maps geocoding +β”‚ β”‚ └── logger.js # Winston logging configuration +β”‚ β”‚ +β”‚ └── images/ # Uploaded image storage +β”‚ β”œβ”€β”€ profile/ # Profile photos (thumb + detail) +β”‚ β”œβ”€β”€ message/ # Message attachments +β”‚ └── cruise/ # Additional images +β”‚ +β”œβ”€β”€ data/ +β”‚ └── profiles.json # Seed data (source of truth) +β”‚ +β”œβ”€β”€ package.json # Dependencies and scripts +β”œβ”€β”€ gulpfile.js # Gulp tasks (dev, test) +└── Dockerfile # Docker build configuration +``` + +--- + +## πŸš€ Getting Started + +### Prerequisites + +- Node.js 14.x (included in DevContainer) +- MongoDB 4.4 (included in DevContainer) +- Environment variables configured (see [Environment Variables](#environment-variables)) + +### Installation + +```bash +# From project root +npm run install:all + +# Or from backend directory +cd backend +npm install +``` + +### Start Development Server + +```bash +# From project root (recommended) +npm run dev:backend + +# Or from backend directory +cd backend +npm run dev + +# Or using gulp directly +gulp +``` + +Server starts on **port 3069** with auto-restart via nodemon. + +### Verify Server is Running + +```bash +curl http://localhost:3069/profiles +``` + +Expected: JSON array of profiles or 404 if no data seeded. + +--- + +## πŸ”„ Data Flow + +### Request Lifecycle + +```mermaid +graph TD + A[Client HTTP Request] --> B[Express App app.js] + B --> C[Morgan Logger logs request] + C --> D[Body Parser parses JSON] + D --> E[CORS Headers added] + E --> F{Route Match?} + F -->|Yes| G[Route Handler routes/*] + F -->|No| H[404 Error Handler] + G --> I{Auth Required?} + I -->|Yes| J[Token.verifyThen] + I -->|No| K[Execute Controller Logic] + J --> L{Valid Token?} + L -->|Yes| M{Has Permission?} + L -->|No| N[403 Forbidden] + M -->|Yes| K + M -->|No| N + K --> O[Mongoose Model models/*] + O --> P[MongoDB Query] + P --> Q[Pre-Save Hooks?] + Q -->|Image Processing| R[modules/images] + R --> S[Save to Filesystem] + S --> T[Update Document Path] + Q -->|No Hooks| U[Save to DB] + T --> U + U --> V[Return Response] + V --> W[Winston Logger logs result] + W --> X[Send JSON to Client] +``` + +### Example: Message Creation with Image + +```mermaid +graph TD + A[POST /profiles/:id/message] --> B[Token Verification] + B --> C[Extract message data from request body] + C --> D[Message.save pre-hook triggered] + D --> E{message.image is object?} + E -->|Yes Base64 data| F[Images.saveMessageImage] + E -->|No string path| G[Skip processing] + F --> H[Decode Base64] + H --> I[Generate unique filename] + I --> J[Write to src/images/message/] + J --> K[Return filename path] + K --> L[Update message.image = filename] + L --> M[Save to MongoDB] + G --> M + M --> N[Emit event to route handler] + N --> O[Return 200 with message data] +``` + +--- + +## πŸ” Environment Variables + +**Required environment variables** must be configured in [`.env.example`](.env.example). + +### Create Environment File + +```bash +# Copy example file +cp .env.example .env + +# Edit with your values +nano .env +``` + +### Required Variables + +| Variable | Description | Example | +| ----------------------- | --------------------------------- | ------------------------------------ | +| **PORT** | Server port | `3069` | +| **MONGODB_URI** | MongoDB connection string | `mongodb://mongo:27017/urge` | +| **JWT_SECRET** | Secret key for JWT signing | `your-super-secret-key-min-32-chars` | +| **GOOGLE_MAPS_API_KEY** | Google Maps API key for geocoding | `AIzaSy...` | +| **MAIL_HOST** | SMTP server hostname | `smtp.gmail.com` | +| **MAIL_PORT** | SMTP server port | `587` | +| **MAIL_USER** | SMTP username | `support@example.com` | +| **MAIL_PASS** | SMTP password | `your-password` | + +### Security Notes + +- **JWT_SECRET**: Use a random 32+ character string. Generate with: + ```bash + openssl rand -base64 32 + ``` +- **MAIL_PASS**: Use app-specific passwords for Gmail/G Suite +- **GOOGLE_MAPS_API_KEY**: Restrict API key to your backend IP/domain +- **Never commit** `.env` files to version control + +πŸ“„ **See [.env.example](.env.example) for complete configuration with descriptions** + +--- + +## πŸ’Ύ Database Seeding + +### ⚠️ Important: Data Entry Workflow + +**This application does NOT support interactive data entry through the UI.** All profile and message data must be added to the seed file and database reseeded. + +### Seed Data Location + +**Source of Truth**: [`data/profiles.json`](data/profiles.json) + +### How to Add/Edit Profiles + +1. **Edit `data/profiles.json`**: + + ```json + [ + { + "order": 1, + "details": { + "name": "John", + "age": 28, + "location": "San Francisco, CA", + "about": "User's story...", + "pic": { + "thumb": "profile/john_thumbnail.png", + "detail": "profile/john_detail.png" + }, + "position": ["Top", "Versatile"], + "looking": ["Dates", "Friends"], + "tribes": ["Geek", "Jock"], + "ethnos": ["White", "Latino"] + }, + "messages": [ + { + "text": "What brought you to dating apps?", + "isUser": false, + "timestamp": "2024-01-15T10:30:00Z" + }, + { + "text": "I moved to a new city and wanted to meet people...", + "isUser": true, + "timestamp": "2024-01-15T10:32:00Z" + } + ], + "submitted": true, + "approved": true + } + ] + ``` + +2. **Add images** to `src/images/profile/` and `src/images/message/` + +3. **Run seed script**: + + ```bash + # From project root + npm run seed + + # Or from backend directory + cd backend + npm run seed + ``` + +4. **Verify data loaded**: + ```bash + curl http://localhost:3069/profiles + ``` + +### Seed Script Behavior + +- **Drops existing database** (all data wiped) +- **Creates fresh collections** from schema +- **Loads data** from `data/profiles.json` +- **Processes images** via pre-save hooks +- **No backup created** - commit changes to Git first + +### Production Deployment + +⚠️ **Database is wiped and reseeded on each deployment**. All content changes must be committed to `data/profiles.json` before deploying. + +If the application becomes interactive with user-generated content, implement proper backup strategies (see [DEPLOYMENT.md](../DEPLOYMENT.md)). + +--- + +## πŸ’» Development Workflow + +### Gulp Tasks + +The project uses **Gulp** for development automation. + +**Default task** (auto-restart on changes): + +```bash +gulp +``` + +This runs: + +- **nodemon** - Restarts server on `.js` file changes +- **mocha** - Runs tests on changes +- Watches `src/**/*.js` for changes + +**Test task**: + +```bash +gulp test +``` + +Runs Mocha tests with "nyan" reporter (cat animation 🐱). + +### Manual Development + +Without Gulp: + +```bash +# Start with nodemon +nodemon src/bin/www + +# Or plain Node.js +node src/bin/www +``` + +### Testing + +```bash +# Run tests +npm test + +# Or with Gulp +gulp test +``` + +**Note**: Test coverage is limited. Most tests are in backend, none in frontend. + +### Logging + +**Winston logger** outputs to: + +- **Console**: Colorized logs during development +- **Files**: `logs/error.log`, `logs/combined.log` (if configured) + +**Morgan HTTP logger** logs all requests: + +``` +GET /profiles 200 45ms - 2.5kb +POST /auth/login 401 12ms - 87b +``` + +### Code Linting + +```bash +# From project root +npm run lint:backend + +# Or from backend directory +cd backend +npm run lint +``` + +**Note**: No ESLint config in backend currently. Consider adding: + +```bash +npm install -D eslint +npx eslint --init +``` + +--- + +## πŸ”§ Troubleshooting + +### MongoDB Connection Failed + +**Error:** + +``` +MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017 +``` + +**Solutions:** + +1. **Verify MongoDB is running**: + + ```bash + docker ps | grep mongo + ``` + +2. **Check connection string**: + + ```bash + echo $MONGODB_URI + # Should be: mongodb://mongo:27017/urge (DevContainer) + # Or: mongodb://localhost:27017/urge (local) + ``` + +3. **Test connection**: + + ```bash + docker exec -it mongo --eval "db.adminCommand('ping')" + ``` + +4. **Restart MongoDB**: + ```bash + docker-compose -f .devcontainer/docker-compose.yml restart mongo + ``` + +--- + +### JWT Token Invalid + +**Error:** + +``` +JsonWebTokenError: invalid signature +``` + +**Solutions:** + +1. **Verify JWT_SECRET is set**: + + ```bash + echo $JWT_SECRET + ``` + +2. **Check .env file exists**: + + ```bash + ls -la .env + ``` + +3. **Restart backend** after changing environment variables: + + ```bash + npm run dev:backend + ``` + +4. **Generate new token** by logging in again + +--- + +### Image Upload Fails + +**Error:** + +``` +ENOENT: no such file or directory, open 'src/images/profile/...' +``` + +**Solutions:** + +1. **Create image directories**: + + ```bash + mkdir -p src/images/profile src/images/message src/images/cruise + ``` + +2. **Check permissions**: + + ```bash + chmod -R 755 src/images + ``` + +3. **Verify volume mount** in DevContainer: + + ```bash + ls -la src/images + ``` + +4. **Check multer configuration** in `modules/images.js` + +--- + +### Port 3069 Already in Use + +**Error:** + +``` +Error: listen EADDRINUSE: address already in use :::3069 +``` + +**Solutions:** + +```bash +# Find process using port 3069 +lsof -i :3069 + +# Kill the process +kill -9 + +# Or use different port +export PORT=3070 +npm run dev +``` + +--- + +### Seed Script Fails + +**Error:** + +``` +Error: Cannot find module 'data/profiles.json' +``` + +**Solutions:** + +1. **Verify file exists**: + + ```bash + ls -la data/profiles.json + ``` + +2. **Check JSON syntax**: + + ```bash + node -e "JSON.parse(require('fs').readFileSync('data/profiles.json'))" + ``` + +3. **Run from correct directory**: + ```bash + cd backend + npm run seed + ``` + +--- + +### Google Maps API Errors + +**Error:** + +``` +REQUEST_DENIED: The provided API key is invalid +``` + +**Solutions:** + +1. **Set API key in environment**: + + ```bash + export GOOGLE_MAPS_API_KEY="your-key-here" + ``` + +2. **Enable APIs** in Google Cloud Console: + + - Geocoding API + - Places API (if used) + +3. **Check API restrictions** (IP/domain whitelisting) + +4. **Verify billing enabled** (Google requires it even for free tier) + +--- + +### Email Sending Fails + +**Error:** + +``` +Invalid login: 535-5.7.8 Username and Password not accepted +``` + +**Solutions:** + +1. **Use app-specific password** (Gmail): + + - Go to Google Account β†’ Security β†’ 2-Step Verification β†’ App passwords + - Generate password for "Mail" + - Use that password in MAIL_PASS + +2. **Verify SMTP settings**: + + ```bash + echo $MAIL_HOST $MAIL_PORT $MAIL_USER + ``` + +3. **Test SMTP connection**: + + ```bash + telnet $MAIL_HOST $MAIL_PORT + ``` + +4. **Enable "Less secure app access"** (not recommended, use app passwords instead) + +--- + +## πŸ“š Related Documentation + +- **[API Reference](API.md)** - Complete REST API endpoint documentation +- **[Database Schema](SCHEMA.md)** - MongoDB collection schemas and relationships +- **[Environment Variables](.env.example)** - Configuration template +- **[Root README](../README.md)** - Project overview and quick start +- **[Frontend README](../app/README.md)** - Ionic app documentation +- **[DevContainer Guide](../.devcontainer/README.md)** - Development environment +- **[Deployment Guide](../DEPLOYMENT.md)** - Production deployment + +--- + +**Need Help?** Check the [root troubleshooting section](../README.md#troubleshooting) or API documentation for endpoint-specific issues. diff --git a/backend/SCHEMA.md b/backend/SCHEMA.md new file mode 100644 index 0000000..c59206e --- /dev/null +++ b/backend/SCHEMA.md @@ -0,0 +1,619 @@ +# Looking - Database Schema + +> **MongoDB collection schemas and data models for the Looking API** + +This document details all MongoDB collections, their schemas, relationships, indexes, and data processing hooks. + +**Database Name**: `urge` +**MongoDB Version**: 4.4 +**ODM**: Mongoose 4.7.4 + +--- + +## πŸ“‹ Table of Contents + +- [Collections Overview](#collections-overview) +- [User Collection](#user-collection) +- [Profile Collection](#profile-collection) +- [Message Schema](#message-schema) +- [Reset Collection](#reset-collection) +- [Geocache Collection](#geocache-collection) +- [Entity Relationships](#entity-relationships) +- [Indexes](#indexes) +- [Image Processing Hooks](#image-processing-hooks) + +--- + +## πŸ“Š Collections Overview + +| Collection | Purpose | Model File | +| ------------- | -------------------------------- | -------------------------------------------- | +| **users** | User accounts and authentication | [models/user.js](src/models/user.js) | +| **profiles** | Dating profile data and stories | [models/profile.js](src/models/profile.js) | +| **messages** | (Embedded in profiles) | [models/message.js](src/models/message.js) | +| **resets** | Password reset tokens | [models/reset.js](src/models/reset.js) | +| **geocaches** | Location geocoding cache | [models/geocache.js](src/models/geocache.js) | + +--- + +## πŸ‘€ User Collection + +**Collection Name**: `users` +**Purpose**: User authentication, authorization, and account management + +### Schema + +| Field | Type | Required | Unique | Default | Description | +| -------------- | -------- | -------- | ------ | ------------ | ------------------------------------------- | +| **username** | String | βœ… | βœ… | - | Unique username for login | +| **password** | String | ❌ | ❌ | - | PBKDF2 hashed password (hash + salt stored) | +| **name.first** | String | βœ… | ❌ | - | User's first name | +| **name.last** | String | βœ… | ❌ | - | User's last name | +| **email** | String | βœ… | βœ… | - | Unique email address | +| **can** | [String] | ❌ | ❌ | `['view']` | Permission array (enum values) | +| **forceReset** | Boolean | ❌ | ❌ | `true` | Force password change on next login | +| **updated_at** | Date | ❌ | ❌ | `Date.now()` | Last update timestamp | + +### Permission Enum Values + +```javascript +["add", "edit", "delete", "manage", "super", "update", "view"]; +``` + +| Permission | Access Level | +| ---------- | --------------------------------------- | +| **view** | Read-only access to profiles | +| **add** | Create new profiles | +| **edit** | Modify existing profiles | +| **update** | Update profile details | +| **delete** | Remove profiles | +| **manage** | Approve submissions, content management | +| **super** | Full admin access, user management | + +### Password Storage + +Passwords are **never stored in plaintext**. The system uses: + +**Algorithm**: PBKDF2 +**Hash**: SHA-512 +**Iterations**: 233,335 +**Hash Length**: 32 bytes +**Salt Length**: 24 bytes + +**Stored Format**: + +```javascript +{ + "password": "hash:salt" // 32-byte hash + 24-byte salt (hex encoded) +} +``` + +### Pre-Save Hooks + +**Password Hashing** (`findOneAndUpdate` hook): + +- Detects password changes in updates +- Validates `password` matches `confirmPassword` +- If `currentPassword` provided, validates before allowing change +- Hashes new password with `authentication.hashPassword()` +- Sets `forceReset: false` after successful password change + +### Example Document + +```json +{ + "_id": ObjectId("507f1f77bcf86cd799439011"), + "username": "admin", + "password": "a3f4b2c1...e5d6c7b8:1a2b3c4d...5e6f7a8b", + "name": { + "first": "John", + "last": "Doe" + }, + "email": "admin@example.com", + "can": ["add", "edit", "delete", "manage", "super", "update", "view"], + "forceReset": false, + "updated_at": ISODate("2024-01-15T10:30:00Z") +} +``` + +--- + +## πŸ“‹ Profile Collection + +**Collection Name**: `profiles` +**Purpose**: User profile data, stories, and message threads + +### Schema + +| Field | Type | Required | Index | Default | Description | +| ------------- | --------- | -------- | ----- | ------- | ------------------------------- | +| **order** | Number | ❌ | ❌ | `0` | Display order for sorting | +| **details** | Object | ❌ | ❌ | `{}` | Profile information (see below) | +| **messages** | [Message] | ❌ | ❌ | `[]` | Array of embedded messages | +| **submitted** | Boolean | ❌ | ❌ | `false` | Story submitted by user | +| **approved** | Boolean | ❌ | ❌ | `false` | Approved by admin | + +### Details Object Schema + +| Field | Type | Index | Default | Description | +| ---------------------- | -------- | ----- | --------------------------------- | ----------------------------------------------------- | +| **details.about** | String | ❌ | - | User's story/bio text | +| **details.age** | Number | βœ… | `0` | User's age | +| **details.location** | String | ❌ | - | City, State format | +| **details.name** | String | βœ… | - | Display name | +| **details.pic.detail** | String | ❌ | `"profile/default_detail.png"` | Full-size profile photo path | +| **details.pic.thumb** | String | ❌ | `"profile/default_thumbnail.png"` | Thumbnail photo path | +| **details.position** | [String] | ❌ | - | Position preferences (Top/Bottom/Versatile) | +| **details.looking** | [String] | ❌ | - | What user is looking for (Dates/Friends/Relationship) | +| **details.tribes** | [String] | ❌ | - | Tribes/groups (Geek/Jock/Bear/Otter/etc.) | +| **details.ethnos** | [String] | ❌ | - | Ethnicity (White/Black/Latino/Asian/etc.) | + +### Pre-Save Hooks + +**Image Processing** (both `save` and `findOneAndUpdate` hooks): + +1. **Detects Base64 Image Data**: + - Checks if `details.pic.detail` or `details.pic.thumb` is an object/base64 string +2. **Processes Images**: + + - **Detail Image**: Calls `Images.saveProfileDetailImage()` + + - Decodes base64 + - Generates unique filename + - Saves to `src/images/profile/` + - Returns path string + + - **Thumbnail**: Calls `Images.saveProfileThumbnailImage()` + - Same process for thumbnails + - May resize/compress image + +3. **Updates Document**: + - Replaces base64 data with file path string + - Continues with save operation + +### Example Document + +```json +{ + "_id": ObjectId("507f1f77bcf86cd799439011"), + "order": 1, + "details": { + "name": "John", + "age": 28, + "location": "San Francisco, CA", + "about": "I've been using dating apps for 5 years. My experience has been...", + "pic": { + "thumb": "profile/john_1234567890_thumbnail.png", + "detail": "profile/john_1234567890_detail.png" + }, + "position": ["Top", "Versatile"], + "looking": ["Dates", "Friends"], + "tribes": ["Geek", "Jock"], + "ethnos": ["White", "Latino"] + }, + "messages": [ + { + "_id": ObjectId("507f191e810c19729de860ea"), + "text": "What brought you to dating apps?", + "isUser": false, + "timestamp": ISODate("2024-01-15T10:30:00Z") + }, + { + "_id": ObjectId("507f191e810c19729de860eb"), + "text": "I moved to San Francisco for work and didn't know anyone...", + "image": "message/john_response_1234567890.png", + "isUser": true, + "timestamp": ISODate("2024-01-15T10:32:00Z") + } + ], + "submitted": true, + "approved": true +} +``` + +--- + +## πŸ’¬ Message Schema + +**Collection**: Embedded in `profiles.messages` array +**Purpose**: Q&A conversation threads between interviewer and profile subject + +### Schema + +| Field | Type | Required | Index | Default | Description | +| ------------- | ------- | -------- | ----- | ------------ | ------------------------------------------ | +| **text** | String | ❌ | ❌ | - | Message content | +| **image** | String | ❌ | βœ… | - | Image file path (if message has photo) | +| **isUser** | Boolean | βœ… | βœ… | `false` | `true` = user response, `false` = question | +| **timestamp** | Date | ❌ | ❌ | `Date.now()` | Message timestamp | + +### Message Types + +| `isUser` | Type | Purpose | Display | +| --------- | -------- | ---------------------- | --------------------- | +| **false** | Question | Interviewer's question | Left-aligned, bold | +| **true** | Response | User's answer | Right-aligned, normal | + +### Pre-Save Hooks + +**Image Processing** (both `save` and `findOneAndUpdate` hooks): + +1. **Detects Image Data**: + - Checks if `message.image` is an object (base64) +2. **Processes Image**: + - Calls `Images.saveMessageImage()` + - Decodes base64 data + - Generates unique filename + - Saves to `src/images/message/` + - Returns path string +3. **Updates Document**: + - Replaces base64 with file path + - Continues with save + +### Example Messages + +```json +[ + { + "_id": ObjectId("507f191e810c19729de860ea"), + "text": "What's your most memorable dating app experience?", + "isUser": false, + "timestamp": ISODate("2024-01-15T10:30:00Z") + }, + { + "_id": ObjectId("507f191e810c19729de860eb"), + "text": "I matched with someone who shared my love of hiking...", + "image": "message/hiking_photo_1234567890.png", + "isUser": true, + "timestamp": ISODate("2024-01-15T10:35:00Z") + } +] +``` + +--- + +## πŸ”„ Reset Collection + +**Collection Name**: `resets` +**Purpose**: Password reset token management + +### Schema + +| Field | Type | Required | Default | Description | +| -------------- | -------- | -------- | ------------ | -------------------------- | +| **user** | ObjectId | βœ… | - | Reference to `users._id` | +| **expires** | Date | ❌ | `Date.now()` | Token expiration timestamp | +| **used** | Boolean | ❌ | `false` | Token already consumed | +| **updated_at** | Date | ❌ | `Date.now()` | Last update timestamp | + +### Token Generation + +**HMAC-SHA1 Token**: + +```javascript +const secret = "Creepily hooking the gays up since 2008!"; +const token = crypto + .createHmac("sha1", secret) + .update(userId + "|" + expires) + .digest("hex"); +``` + +**Expiration**: Typically 1-24 hours from creation + +### Reset Workflow + +1. **User requests reset**: `POST /auth/reset` with username +2. **System creates reset document**: Links user, generates token, sets expiration +3. **Email sent**: Password reset link with `/auth/reset/:id/:token` +4. **User clicks link**: `GET /auth/reset/:id/:token` validates token +5. **User sets new password**: `PUT /auth/reset/:id/:token` with new password +6. **Token marked as used**: `used: true` prevents reuse + +### Example Document + +```json +{ + "_id": ObjectId("507f1f77bcf86cd799439020"), + "user": ObjectId("507f1f77bcf86cd799439011"), + "expires": ISODate("2024-01-16T10:00:00Z"), + "used": false, + "updated_at": ISODate("2024-01-15T10:00:00Z") +} +``` + +--- + +## πŸ—ΊοΈ Geocache Collection + +**Collection Name**: `geocaches` +**Purpose**: Cache Google Maps API geocoding results to reduce API calls + +### Schema + +| Field | Type | Required | Unique | Default | Description | +| ------------------- | -------- | -------- | ------ | --------- | ---------------------------------------------------- | +| **key** | String | βœ… | βœ… | - | Original location string (e.g., "San Francisco, CA") | +| **formatted** | String | βœ… | ❌ | - | Google's formatted address | +| **loc.type** | String | ❌ | ❌ | `"Point"` | GeoJSON type | +| **loc.coordinates** | [Number] | ❌ | ❌ | `[0, 0]` | [longitude, latitude] array | +| **georesult** | Mixed | ❌ | ❌ | - | Full Google Maps API response | + +### GeoJSON Structure + +MongoDB's geospatial queries require **GeoJSON Point** format: + +```json +{ + "type": "Point", + "coordinates": [-122.4194, 37.7749] // [longitude, latitude] +} +``` + +⚠️ **Note**: Coordinates are `[lng, lat]`, not `[lat, lng]` + +### Example Document + +```json +{ + "_id": ObjectId("507f1f77bcf86cd799439030"), + "key": "San Francisco, CA", + "formatted": "San Francisco, California, USA", + "loc": { + "type": "Point", + "coordinates": [-122.4194, 37.7749] + }, + "georesult": { + "address_components": [ ... ], + "formatted_address": "San Francisco, California, USA", + "geometry": { + "location": { + "lat": 37.7749, + "lng": -122.4194 + } + }, + "place_id": "ChIJIQBpAG2ahYAR_6128GcTUEo" + } +} +``` + +### Usage + +**Geocoding Workflow**: + +1. Check if location exists in geocache by `key` +2. If cached, return stored result (no API call) +3. If not cached: + - Call Google Maps Geocoding API + - Store result in geocache + - Return result +4. Future requests for same location use cache + +**Benefits**: + +- Reduces API costs +- Faster response times +- Works offline for cached locations + +--- + +## πŸ”— Entity Relationships + +```mermaid +erDiagram + USER ||--o{ RESET : "can have" + PROFILE ||--o{ MESSAGE : "contains" + PROFILE }o--|| GEOCACHE : "references location" + + USER { + ObjectId _id PK + string username UK + string password + string email UK + array can + boolean forceReset + } + + PROFILE { + ObjectId _id PK + number order + object details + array messages + boolean submitted + boolean approved + } + + MESSAGE { + ObjectId _id PK + string text + string image + boolean isUser + date timestamp + } + + RESET { + ObjectId _id PK + ObjectId user FK + date expires + boolean used + } + + GEOCACHE { + ObjectId _id PK + string key UK + string formatted + object loc + object georesult + } +``` + +### Relationship Details + +| Relationship | Type | Description | +| ---------------------- | ---------------------- | ---------------------------------------------- | +| **User β†’ Reset** | One-to-Many | User can have multiple reset tokens (history) | +| **Profile β†’ Message** | One-to-Many (Embedded) | Profile contains array of messages | +| **Profile β†’ Geocache** | Many-to-One (Soft) | Profile location string may match geocache key | + +**Note**: No foreign key constraints in MongoDB. Relationships are application-level only. + +--- + +## πŸ“‡ Indexes + +### Current Indexes + +| Collection | Field | Type | Purpose | +| ------------- | -------------- | -------- | -------------------------------------- | +| **users** | `username` | Unique | Fast login lookups, prevent duplicates | +| **users** | `email` | Unique | Email validation, prevent duplicates | +| **profiles** | `details.age` | Standard | Age range filtering | +| **profiles** | `details.name` | Standard | Name search/sorting | +| **messages** | `image` | Standard | Find messages with images | +| **messages** | `isUser` | Standard | Filter by message type | +| **geocaches** | `key` | Unique | Location lookup cache | + +### Index Usage Queries + +```javascript +// Fast age range query +db.profiles + .find({ + "details.age": { $gte: 25, $lte: 35 }, + }) + .sort({ order: 1 }); + +// User login +db.users.findOne({ username: "admin" }); + +// Geocache lookup +db.geocaches.findOne({ key: "San Francisco, CA" }); + +// Messages with images +db.profiles.find({ "messages.image": { $exists: true } }); +``` + +### Recommended Additional Indexes + +For production optimization: + +```javascript +// Approved profiles (most common query) +db.profiles.createIndex({ approved: 1, order: 1 }); + +// Submitted profiles (admin review) +db.profiles.createIndex({ submitted: 1, approved: 1 }); + +// User permissions lookup +db.users.createIndex({ can: 1 }); + +// Location geospatial queries +db.geocaches.createIndex({ loc: "2dsphere" }); +``` + +--- + +## πŸ–ΌοΈ Image Processing Hooks + +### Overview + +Images are processed **automatically** via Mongoose pre-save hooks before documents are saved to MongoDB. + +**Supported Formats**: + +- Base64 encoded strings (from frontend) +- Data URLs: `data:image/png;base64,iVBORw0KGg...` +- File paths (strings are passed through) + +### Processing Flow + +```mermaid +graph TD + A[Document Save Triggered] --> B{Field is Base64?} + B -->|Yes| C[Extract Base64 Data] + B -->|No string path| D[Skip Processing] + C --> E[Decode to Binary] + E --> F[Generate Unique Filename] + F --> G{Image Type?} + G -->|Profile Detail| H[Save to src/images/profile/] + G -->|Profile Thumb| I[Save to src/images/profile/] + G -->|Message Image| J[Save to src/images/message/] + H --> K[Return File Path] + I --> K + J --> K + K --> L[Update Document Field] + L --> M[Continue Save to MongoDB] + D --> M +``` + +### Image Modules + +**Location**: `modules/images.js` + +**Functions**: + +- `saveProfileDetailImage(data, callback)` - Processes full-size profile photos +- `saveProfileThumbnailImage(data, callback)` - Processes profile thumbnails +- `saveMessageImage(data, callback)` - Processes message attachments + +**Filename Format**: + +``` +__. +Example: profile_aB3xF2_1705315200000.png +``` + +### Storage Locations + +| Image Type | Directory | Example Path | +| --------------------- | --------------------- | ------------------------------------ | +| **Profile Detail** | `src/images/profile/` | `profile/john_detail_1234567890.png` | +| **Profile Thumbnail** | `src/images/profile/` | `profile/john_thumb_1234567890.png` | +| **Message Image** | `src/images/message/` | `message/response_1234567890.png` | +| **Additional** | `src/images/cruise/` | `cruise/event_1234567890.png` | + +### Error Handling + +If image processing fails: + +- **Error logged** via Winston logger +- **Save continues** with original data +- **Client receives error** in response + +**Example Error**: + +```javascript +Logger.error( + "[MessageSchema.pre(save)] There was an error processing the message image.", + { + error: err, + } +); +``` + +### Volume Persistence + +**Development**: Images stored in DevContainer volume +**Production**: Images stored in Docker volume `api_images` + +**Volume Mount**: + +```yaml +volumes: + - api_images:/app/src/images +``` + +This ensures images persist across container restarts. + +--- + +## πŸ“š Related Documentation + +- **[Backend README](README.md)** - Server setup and development +- **[API Reference](API.md)** - Complete REST API documentation +- **[Environment Variables](.env.example)** - Configuration +- **[Root README](../README.md)** - Project overview +- **[Deployment Guide](../DEPLOYMENT.md)** - Production deployment + +--- + +**Need Help?** Check the backend [troubleshooting section](README.md#troubleshooting) for database-related issues.