Documentation
This commit is contained in:
764
.devcontainer/README.md
Normal file
764
.devcontainer/README.md
Normal 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
1078
DEPLOYMENT.md
Normal file
File diff suppressed because it is too large
Load Diff
640
README.md
Normal file
640
README.md
Normal 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
713
app/README.md
Normal 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
227
backend/.env.example
Normal 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
1029
backend/API.md
Normal file
File diff suppressed because it is too large
Load Diff
652
backend/README.md
Normal file
652
backend/README.md
Normal 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
619
backend/SCHEMA.md
Normal 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.
|
||||||
Reference in New Issue
Block a user