Documentation

This commit is contained in:
2025-12-28 13:52:25 -03:00
parent f17c0d08a1
commit 7fa78e870b
8 changed files with 5722 additions and 0 deletions

764
.devcontainer/README.md Normal file
View File

@@ -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-container-id> 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 <PID>
```
**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.

1078
DEPLOYMENT.md Normal file

File diff suppressed because it is too large Load Diff

640
README.md Normal file
View File

@@ -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 <repository-url>
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 <container-id>
# 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-container-id> 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 <mongo-express-container-id>
```
---
## ⚠️ 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.

713
app/README.md Normal file
View File

@@ -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 <PID>
# 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.

227
backend/.env.example Normal file
View File

@@ -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

1029
backend/API.md Normal file

File diff suppressed because it is too large Load Diff

652
backend/README.md Normal file
View File

@@ -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-container> 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 <PID>
# 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.

619
backend/SCHEMA.md Normal file
View File

@@ -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/<filename>`
- 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/<filename>`
- 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**:
```
<type>_<shortid>_<timestamp>.<ext>
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.