Documentation

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

227
backend/.env.example Normal file
View File

@@ -0,0 +1,227 @@
# Looking Backend - Environment Variables
# Copy this file to .env and fill in your values
# NEVER commit .env files to version control
# ============================================
# SERVER CONFIGURATION
# ============================================
# Port for Express server to listen on
# Default: 3069
# Production: Use same port or configure via load balancer
PORT=3069
# Node environment
# Options: development, production, test
NODE_ENV=development
# ============================================
# DATABASE CONFIGURATION
# ============================================
# MongoDB connection string
# Development (DevContainer): mongodb://mongo:27017/urge
# Development (Local): mongodb://localhost:27017/urge
# Production: Use MongoDB Atlas or managed instance
MONGODB_URI=mongodb://mongo:27017/urge
# MongoDB Admin Credentials (for connection if auth enabled)
# Only needed if MongoDB requires authentication
# MONGO_USER=admin
# MONGO_PASS=password
# ============================================
# JWT AUTHENTICATION
# ============================================
# Secret key for JWT token signing
# CRITICAL: Use a strong random string (minimum 32 characters)
# Generate with: openssl rand -base64 32
# NEVER share or commit this value
JWT_SECRET=your-super-secret-jwt-key-change-this-to-random-32-chars
# JWT token expiration time
# Options: '15m', '1h', '24h', '7d'
# Default: 15m (15 minutes)
JWT_EXPIRES_IN=15m
# ============================================
# GOOGLE MAPS API
# ============================================
# Google Maps API key for geocoding features
# Get API key: https://console.cloud.google.com/apis/credentials
# Enable: Geocoding API, Places API (if used)
# Restrict: Set HTTP referrer or IP restrictions for security
GOOGLE_MAPS_API_KEY=AIzaSyXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
# ============================================
# EMAIL CONFIGURATION (SMTP)
# ============================================
# SMTP server hostname
# Examples:
# Gmail: smtp.gmail.com
# Outlook: smtp-mail.outlook.com
# SendGrid: smtp.sendgrid.net
# Custom: mail.yourdomain.com
MAIL_HOST=smtp.gmail.com
# SMTP server port
# Common ports:
# 587 - TLS/STARTTLS (recommended)
# 465 - SSL
# 25 - Unencrypted (not recommended)
MAIL_PORT=587
# SMTP username (usually your email address)
MAIL_USER=support@example.com
# SMTP password or app-specific password
# For Gmail: Use App Password (not your account password)
# 1. Go to Google Account → Security → 2-Step Verification
# 2. Scroll to "App passwords"
# 3. Generate password for "Mail"
# 4. Use that 16-character password here
MAIL_PASS=your-email-password-or-app-specific-password
# Email sender name (displayed in "From" field)
MAIL_FROM_NAME=Looking App Support
# Email sender address (must match MAIL_USER or authorized sender)
MAIL_FROM_ADDRESS=support@example.com
# ============================================
# CORS CONFIGURATION
# ============================================
# Allowed origins for CORS (comma-separated)
# Development: * (all origins)
# Production: Specific domains only
# Examples:
# Development: *
# Production: https://pfosi.mifi.dev,https://www.pfosi.mifi.dev
CORS_ORIGIN=*
# ============================================
# FILE UPLOAD CONFIGURATION
# ============================================
# Maximum file size for image uploads (in bytes)
# 5MB = 5242880 bytes
# 10MB = 10485760 bytes
MAX_FILE_SIZE=5242880
# Allowed image MIME types (comma-separated)
ALLOWED_IMAGE_TYPES=image/jpeg,image/png,image/gif,image/webp
# ============================================
# LOGGING CONFIGURATION
# ============================================
# Log level
# Options: error, warn, info, http, verbose, debug, silly
# Production: info or warn
# Development: debug or verbose
LOG_LEVEL=debug
# Log file location (if file logging enabled)
# Default: logs/combined.log
LOG_FILE=logs/combined.log
# Error log file location
LOG_ERROR_FILE=logs/error.log
# ============================================
# SECURITY CONFIGURATION
# ============================================
# Password hashing iterations (PBKDF2)
# Higher = more secure but slower
# Default: 233335
# Recommended: 100000+
PASSWORD_HASH_ITERATIONS=233335
# Password minimum length
PASSWORD_MIN_LENGTH=8
# Session secret for express-session (if using sessions)
# SESSION_SECRET=your-session-secret-change-this
# ============================================
# RATE LIMITING (if implemented)
# ============================================
# Maximum requests per window
# RATE_LIMIT_MAX=100
# Time window in milliseconds (15 minutes = 900000)
# RATE_LIMIT_WINDOW_MS=900000
# ============================================
# PRODUCTION DEPLOYMENT
# ============================================
# Domain/hostname for the application
# Used for email links, CORS, etc.
# APP_URL=https://pfosi.mifi.dev
# API_URL=https://api.pfosi.mifi.dev
# Traefik labels (if using docker-compose with Traefik)
# TRAEFIK_ENABLE=true
# TRAEFIK_DOMAIN=api.pfosi.mifi.dev
# ============================================
# MONITORING & ANALYTICS (optional)
# ============================================
# Sentry DSN for error tracking
# SENTRY_DSN=https://xxxxx@sentry.io/xxxxx
# Google Analytics tracking ID
# GA_TRACKING_ID=UA-XXXXXXXXX-X
# ============================================
# DATABASE SEEDING
# ============================================
# Path to seed data file
# Default: data/profiles.json
SEED_DATA_PATH=data/profiles.json
# Auto-seed database on startup (true/false)
# WARNING: This will wipe existing data
# Only use in development
AUTO_SEED=false
# ============================================
# FEATURE FLAGS (optional)
# ============================================
# Enable user story submissions via public endpoint
# ENABLE_SUBMISSIONS=true
# Require admin approval for submitted stories
# REQUIRE_APPROVAL=true
# Enable email notifications for new submissions
# NOTIFY_ON_SUBMISSION=true
# ============================================
# NOTES
# ============================================
# 1. NEVER commit this file with real values to version control
# 2. Add .env to .gitignore (already done)
# 3. Use different values for development and production
# 4. Rotate secrets regularly in production
# 5. Use environment-specific .env files:
# - .env.development
# - .env.production
# - .env.test
# 6. In production, use secret management tools:
# - Docker secrets
# - Kubernetes secrets
# - AWS Secrets Manager
# - Azure Key Vault
# - HashiCorp Vault

1029
backend/API.md Normal file

File diff suppressed because it is too large Load Diff

652
backend/README.md Normal file
View File

@@ -0,0 +1,652 @@
# Looking - Backend API
> **Express.js REST API with MongoDB for the Looking art project**
This is the backend REST API that powers the "Looking" application. Built with Express 4 and MongoDB 4.4, it provides endpoints for profile management, user authentication, messaging, and geolocation features.
---
## 📋 Table of Contents
- [Overview](#overview)
- [Tech Stack](#tech-stack)
- [Project Structure](#project-structure)
- [Getting Started](#getting-started)
- [Data Flow](#data-flow)
- [Environment Variables](#environment-variables)
- [Database Seeding](#database-seeding)
- [Development Workflow](#development-workflow)
- [Troubleshooting](#troubleshooting)
- [Related Documentation](#related-documentation)
---
## 🎯 Overview
The backend API provides:
- **Authentication**: JWT-based user sessions with PBKDF2 password hashing
- **Profile Management**: CRUD operations with approval workflow
- **Messaging**: Conversation threads with image support
- **Image Processing**: Automatic thumbnail and detail image generation
- **Geolocation**: Google Maps API integration for location features
- **Email**: Password reset and notification emails via Nodemailer
**Server Port**: 3069 (configurable via PORT environment variable)
**Database**: MongoDB 4.4 on port 27017 (database name: `urge`)
---
## 🛠 Tech Stack
| Technology | Version | Purpose |
| ---------------- | ------- | ---------------------- |
| **Express** | 4.14.0 | Web framework |
| **Node.js** | 14.x | Runtime |
| **MongoDB** | 4.4 | Database |
| **Mongoose** | 4.7.4 | MongoDB ODM |
| **jsonwebtoken** | 7.3.0 | JWT authentication |
| **multer** | 1.2.0 | File upload handling |
| **@google/maps** | 0.4.5 | Google Maps API client |
| **nodemailer** | 4.0.1 | Email sending |
| **winston** | 2.4.0 | Application logging |
| **morgan** | 1.7.0 | HTTP request logging |
| **moment** | 2.17.1 | Date/time manipulation |
| **shortid** | 2.2.8 | Short ID generation |
| **vcard-js** | 1.2.2 | VCard generation |
**Dev Tools:**
- **gulp** 3.9.1 - Task runner
- **nodemon** 1.11.0 - Auto-restart on file changes
- **mocha** 3.0.1 - Test framework
- **chai** 3.5.0 - Assertion library
---
## 📁 Project Structure
```
backend/
├── src/
│ ├── app.js # Express app configuration
│ │
│ ├── bin/
│ │ └── www # Server startup script (port 3069)
│ │
│ ├── routes/ # API route handlers
│ │ ├── auth.js # Authentication (login, logout, password reset)
│ │ ├── profiles.js # Profile CRUD + approval workflow
│ │ ├── users.js # User management
│ │ └── geocache.js # Geolocation/geocoding
│ │
│ ├── models/ # Mongoose schemas
│ │ ├── user.js # User schema (auth, permissions)
│ │ ├── profile.js # Profile schema (details, messages)
│ │ ├── message.js # Message schema (text, image, timestamp)
│ │ ├── reset.js # Password reset token schema
│ │ └── geocache.js # Location cache schema
│ │
│ ├── modules/ # Utility modules
│ │ ├── authentication.js # PBKDF2 password hashing
│ │ ├── token.js # JWT creation/verification
│ │ ├── images.js # Image upload/processing (multer)
│ │ ├── mailer.js # Email sending (nodemailer)
│ │ ├── geocoder.js # Google Maps geocoding
│ │ └── logger.js # Winston logging configuration
│ │
│ └── images/ # Uploaded image storage
│ ├── profile/ # Profile photos (thumb + detail)
│ ├── message/ # Message attachments
│ └── cruise/ # Additional images
├── data/
│ └── profiles.json # Seed data (source of truth)
├── package.json # Dependencies and scripts
├── gulpfile.js # Gulp tasks (dev, test)
└── Dockerfile # Docker build configuration
```
---
## 🚀 Getting Started
### Prerequisites
- Node.js 14.x (included in DevContainer)
- MongoDB 4.4 (included in DevContainer)
- Environment variables configured (see [Environment Variables](#environment-variables))
### Installation
```bash
# From project root
npm run install:all
# Or from backend directory
cd backend
npm install
```
### Start Development Server
```bash
# From project root (recommended)
npm run dev:backend
# Or from backend directory
cd backend
npm run dev
# Or using gulp directly
gulp
```
Server starts on **port 3069** with auto-restart via nodemon.
### Verify Server is Running
```bash
curl http://localhost:3069/profiles
```
Expected: JSON array of profiles or 404 if no data seeded.
---
## 🔄 Data Flow
### Request Lifecycle
```mermaid
graph TD
A[Client HTTP Request] --> B[Express App app.js]
B --> C[Morgan Logger logs request]
C --> D[Body Parser parses JSON]
D --> E[CORS Headers added]
E --> F{Route Match?}
F -->|Yes| G[Route Handler routes/*]
F -->|No| H[404 Error Handler]
G --> I{Auth Required?}
I -->|Yes| J[Token.verifyThen]
I -->|No| K[Execute Controller Logic]
J --> L{Valid Token?}
L -->|Yes| M{Has Permission?}
L -->|No| N[403 Forbidden]
M -->|Yes| K
M -->|No| N
K --> O[Mongoose Model models/*]
O --> P[MongoDB Query]
P --> Q[Pre-Save Hooks?]
Q -->|Image Processing| R[modules/images]
R --> S[Save to Filesystem]
S --> T[Update Document Path]
Q -->|No Hooks| U[Save to DB]
T --> U
U --> V[Return Response]
V --> W[Winston Logger logs result]
W --> X[Send JSON to Client]
```
### Example: Message Creation with Image
```mermaid
graph TD
A[POST /profiles/:id/message] --> B[Token Verification]
B --> C[Extract message data from request body]
C --> D[Message.save pre-hook triggered]
D --> E{message.image is object?}
E -->|Yes Base64 data| F[Images.saveMessageImage]
E -->|No string path| G[Skip processing]
F --> H[Decode Base64]
H --> I[Generate unique filename]
I --> J[Write to src/images/message/]
J --> K[Return filename path]
K --> L[Update message.image = filename]
L --> M[Save to MongoDB]
G --> M
M --> N[Emit event to route handler]
N --> O[Return 200 with message data]
```
---
## 🔐 Environment Variables
**Required environment variables** must be configured in [`.env.example`](.env.example).
### Create Environment File
```bash
# Copy example file
cp .env.example .env
# Edit with your values
nano .env
```
### Required Variables
| Variable | Description | Example |
| ----------------------- | --------------------------------- | ------------------------------------ |
| **PORT** | Server port | `3069` |
| **MONGODB_URI** | MongoDB connection string | `mongodb://mongo:27017/urge` |
| **JWT_SECRET** | Secret key for JWT signing | `your-super-secret-key-min-32-chars` |
| **GOOGLE_MAPS_API_KEY** | Google Maps API key for geocoding | `AIzaSy...` |
| **MAIL_HOST** | SMTP server hostname | `smtp.gmail.com` |
| **MAIL_PORT** | SMTP server port | `587` |
| **MAIL_USER** | SMTP username | `support@example.com` |
| **MAIL_PASS** | SMTP password | `your-password` |
### Security Notes
- **JWT_SECRET**: Use a random 32+ character string. Generate with:
```bash
openssl rand -base64 32
```
- **MAIL_PASS**: Use app-specific passwords for Gmail/G Suite
- **GOOGLE_MAPS_API_KEY**: Restrict API key to your backend IP/domain
- **Never commit** `.env` files to version control
📄 **See [.env.example](.env.example) for complete configuration with descriptions**
---
## 💾 Database Seeding
### ⚠️ Important: Data Entry Workflow
**This application does NOT support interactive data entry through the UI.** All profile and message data must be added to the seed file and database reseeded.
### Seed Data Location
**Source of Truth**: [`data/profiles.json`](data/profiles.json)
### How to Add/Edit Profiles
1. **Edit `data/profiles.json`**:
```json
[
{
"order": 1,
"details": {
"name": "John",
"age": 28,
"location": "San Francisco, CA",
"about": "User's story...",
"pic": {
"thumb": "profile/john_thumbnail.png",
"detail": "profile/john_detail.png"
},
"position": ["Top", "Versatile"],
"looking": ["Dates", "Friends"],
"tribes": ["Geek", "Jock"],
"ethnos": ["White", "Latino"]
},
"messages": [
{
"text": "What brought you to dating apps?",
"isUser": false,
"timestamp": "2024-01-15T10:30:00Z"
},
{
"text": "I moved to a new city and wanted to meet people...",
"isUser": true,
"timestamp": "2024-01-15T10:32:00Z"
}
],
"submitted": true,
"approved": true
}
]
```
2. **Add images** to `src/images/profile/` and `src/images/message/`
3. **Run seed script**:
```bash
# From project root
npm run seed
# Or from backend directory
cd backend
npm run seed
```
4. **Verify data loaded**:
```bash
curl http://localhost:3069/profiles
```
### Seed Script Behavior
- **Drops existing database** (all data wiped)
- **Creates fresh collections** from schema
- **Loads data** from `data/profiles.json`
- **Processes images** via pre-save hooks
- **No backup created** - commit changes to Git first
### Production Deployment
⚠️ **Database is wiped and reseeded on each deployment**. All content changes must be committed to `data/profiles.json` before deploying.
If the application becomes interactive with user-generated content, implement proper backup strategies (see [DEPLOYMENT.md](../DEPLOYMENT.md)).
---
## 💻 Development Workflow
### Gulp Tasks
The project uses **Gulp** for development automation.
**Default task** (auto-restart on changes):
```bash
gulp
```
This runs:
- **nodemon** - Restarts server on `.js` file changes
- **mocha** - Runs tests on changes
- Watches `src/**/*.js` for changes
**Test task**:
```bash
gulp test
```
Runs Mocha tests with "nyan" reporter (cat animation 🐱).
### Manual Development
Without Gulp:
```bash
# Start with nodemon
nodemon src/bin/www
# Or plain Node.js
node src/bin/www
```
### Testing
```bash
# Run tests
npm test
# Or with Gulp
gulp test
```
**Note**: Test coverage is limited. Most tests are in backend, none in frontend.
### Logging
**Winston logger** outputs to:
- **Console**: Colorized logs during development
- **Files**: `logs/error.log`, `logs/combined.log` (if configured)
**Morgan HTTP logger** logs all requests:
```
GET /profiles 200 45ms - 2.5kb
POST /auth/login 401 12ms - 87b
```
### Code Linting
```bash
# From project root
npm run lint:backend
# Or from backend directory
cd backend
npm run lint
```
**Note**: No ESLint config in backend currently. Consider adding:
```bash
npm install -D eslint
npx eslint --init
```
---
## 🔧 Troubleshooting
### MongoDB Connection Failed
**Error:**
```
MongooseServerSelectionError: connect ECONNREFUSED 127.0.0.1:27017
```
**Solutions:**
1. **Verify MongoDB is running**:
```bash
docker ps | grep mongo
```
2. **Check connection string**:
```bash
echo $MONGODB_URI
# Should be: mongodb://mongo:27017/urge (DevContainer)
# Or: mongodb://localhost:27017/urge (local)
```
3. **Test connection**:
```bash
docker exec -it <mongo-container> mongo --eval "db.adminCommand('ping')"
```
4. **Restart MongoDB**:
```bash
docker-compose -f .devcontainer/docker-compose.yml restart mongo
```
---
### JWT Token Invalid
**Error:**
```
JsonWebTokenError: invalid signature
```
**Solutions:**
1. **Verify JWT_SECRET is set**:
```bash
echo $JWT_SECRET
```
2. **Check .env file exists**:
```bash
ls -la .env
```
3. **Restart backend** after changing environment variables:
```bash
npm run dev:backend
```
4. **Generate new token** by logging in again
---
### Image Upload Fails
**Error:**
```
ENOENT: no such file or directory, open 'src/images/profile/...'
```
**Solutions:**
1. **Create image directories**:
```bash
mkdir -p src/images/profile src/images/message src/images/cruise
```
2. **Check permissions**:
```bash
chmod -R 755 src/images
```
3. **Verify volume mount** in DevContainer:
```bash
ls -la src/images
```
4. **Check multer configuration** in `modules/images.js`
---
### Port 3069 Already in Use
**Error:**
```
Error: listen EADDRINUSE: address already in use :::3069
```
**Solutions:**
```bash
# Find process using port 3069
lsof -i :3069
# Kill the process
kill -9 <PID>
# Or use different port
export PORT=3070
npm run dev
```
---
### Seed Script Fails
**Error:**
```
Error: Cannot find module 'data/profiles.json'
```
**Solutions:**
1. **Verify file exists**:
```bash
ls -la data/profiles.json
```
2. **Check JSON syntax**:
```bash
node -e "JSON.parse(require('fs').readFileSync('data/profiles.json'))"
```
3. **Run from correct directory**:
```bash
cd backend
npm run seed
```
---
### Google Maps API Errors
**Error:**
```
REQUEST_DENIED: The provided API key is invalid
```
**Solutions:**
1. **Set API key in environment**:
```bash
export GOOGLE_MAPS_API_KEY="your-key-here"
```
2. **Enable APIs** in Google Cloud Console:
- Geocoding API
- Places API (if used)
3. **Check API restrictions** (IP/domain whitelisting)
4. **Verify billing enabled** (Google requires it even for free tier)
---
### Email Sending Fails
**Error:**
```
Invalid login: 535-5.7.8 Username and Password not accepted
```
**Solutions:**
1. **Use app-specific password** (Gmail):
- Go to Google Account → Security → 2-Step Verification → App passwords
- Generate password for "Mail"
- Use that password in MAIL_PASS
2. **Verify SMTP settings**:
```bash
echo $MAIL_HOST $MAIL_PORT $MAIL_USER
```
3. **Test SMTP connection**:
```bash
telnet $MAIL_HOST $MAIL_PORT
```
4. **Enable "Less secure app access"** (not recommended, use app passwords instead)
---
## 📚 Related Documentation
- **[API Reference](API.md)** - Complete REST API endpoint documentation
- **[Database Schema](SCHEMA.md)** - MongoDB collection schemas and relationships
- **[Environment Variables](.env.example)** - Configuration template
- **[Root README](../README.md)** - Project overview and quick start
- **[Frontend README](../app/README.md)** - Ionic app documentation
- **[DevContainer Guide](../.devcontainer/README.md)** - Development environment
- **[Deployment Guide](../DEPLOYMENT.md)** - Production deployment
---
**Need Help?** Check the [root troubleshooting section](../README.md#troubleshooting) or API documentation for endpoint-specific issues.

619
backend/SCHEMA.md Normal file
View File

@@ -0,0 +1,619 @@
# Looking - Database Schema
> **MongoDB collection schemas and data models for the Looking API**
This document details all MongoDB collections, their schemas, relationships, indexes, and data processing hooks.
**Database Name**: `urge`
**MongoDB Version**: 4.4
**ODM**: Mongoose 4.7.4
---
## 📋 Table of Contents
- [Collections Overview](#collections-overview)
- [User Collection](#user-collection)
- [Profile Collection](#profile-collection)
- [Message Schema](#message-schema)
- [Reset Collection](#reset-collection)
- [Geocache Collection](#geocache-collection)
- [Entity Relationships](#entity-relationships)
- [Indexes](#indexes)
- [Image Processing Hooks](#image-processing-hooks)
---
## 📊 Collections Overview
| Collection | Purpose | Model File |
| ------------- | -------------------------------- | -------------------------------------------- |
| **users** | User accounts and authentication | [models/user.js](src/models/user.js) |
| **profiles** | Dating profile data and stories | [models/profile.js](src/models/profile.js) |
| **messages** | (Embedded in profiles) | [models/message.js](src/models/message.js) |
| **resets** | Password reset tokens | [models/reset.js](src/models/reset.js) |
| **geocaches** | Location geocoding cache | [models/geocache.js](src/models/geocache.js) |
---
## 👤 User Collection
**Collection Name**: `users`
**Purpose**: User authentication, authorization, and account management
### Schema
| Field | Type | Required | Unique | Default | Description |
| -------------- | -------- | -------- | ------ | ------------ | ------------------------------------------- |
| **username** | String | ✅ | ✅ | - | Unique username for login |
| **password** | String | ❌ | ❌ | - | PBKDF2 hashed password (hash + salt stored) |
| **name.first** | String | ✅ | ❌ | - | User's first name |
| **name.last** | String | ✅ | ❌ | - | User's last name |
| **email** | String | ✅ | ✅ | - | Unique email address |
| **can** | [String] | ❌ | ❌ | `['view']` | Permission array (enum values) |
| **forceReset** | Boolean | ❌ | ❌ | `true` | Force password change on next login |
| **updated_at** | Date | ❌ | ❌ | `Date.now()` | Last update timestamp |
### Permission Enum Values
```javascript
["add", "edit", "delete", "manage", "super", "update", "view"];
```
| Permission | Access Level |
| ---------- | --------------------------------------- |
| **view** | Read-only access to profiles |
| **add** | Create new profiles |
| **edit** | Modify existing profiles |
| **update** | Update profile details |
| **delete** | Remove profiles |
| **manage** | Approve submissions, content management |
| **super** | Full admin access, user management |
### Password Storage
Passwords are **never stored in plaintext**. The system uses:
**Algorithm**: PBKDF2
**Hash**: SHA-512
**Iterations**: 233,335
**Hash Length**: 32 bytes
**Salt Length**: 24 bytes
**Stored Format**:
```javascript
{
"password": "hash:salt" // 32-byte hash + 24-byte salt (hex encoded)
}
```
### Pre-Save Hooks
**Password Hashing** (`findOneAndUpdate` hook):
- Detects password changes in updates
- Validates `password` matches `confirmPassword`
- If `currentPassword` provided, validates before allowing change
- Hashes new password with `authentication.hashPassword()`
- Sets `forceReset: false` after successful password change
### Example Document
```json
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"username": "admin",
"password": "a3f4b2c1...e5d6c7b8:1a2b3c4d...5e6f7a8b",
"name": {
"first": "John",
"last": "Doe"
},
"email": "admin@example.com",
"can": ["add", "edit", "delete", "manage", "super", "update", "view"],
"forceReset": false,
"updated_at": ISODate("2024-01-15T10:30:00Z")
}
```
---
## 📋 Profile Collection
**Collection Name**: `profiles`
**Purpose**: User profile data, stories, and message threads
### Schema
| Field | Type | Required | Index | Default | Description |
| ------------- | --------- | -------- | ----- | ------- | ------------------------------- |
| **order** | Number | ❌ | ❌ | `0` | Display order for sorting |
| **details** | Object | ❌ | ❌ | `{}` | Profile information (see below) |
| **messages** | [Message] | ❌ | ❌ | `[]` | Array of embedded messages |
| **submitted** | Boolean | ❌ | ❌ | `false` | Story submitted by user |
| **approved** | Boolean | ❌ | ❌ | `false` | Approved by admin |
### Details Object Schema
| Field | Type | Index | Default | Description |
| ---------------------- | -------- | ----- | --------------------------------- | ----------------------------------------------------- |
| **details.about** | String | ❌ | - | User's story/bio text |
| **details.age** | Number | ✅ | `0` | User's age |
| **details.location** | String | ❌ | - | City, State format |
| **details.name** | String | ✅ | - | Display name |
| **details.pic.detail** | String | ❌ | `"profile/default_detail.png"` | Full-size profile photo path |
| **details.pic.thumb** | String | ❌ | `"profile/default_thumbnail.png"` | Thumbnail photo path |
| **details.position** | [String] | ❌ | - | Position preferences (Top/Bottom/Versatile) |
| **details.looking** | [String] | ❌ | - | What user is looking for (Dates/Friends/Relationship) |
| **details.tribes** | [String] | ❌ | - | Tribes/groups (Geek/Jock/Bear/Otter/etc.) |
| **details.ethnos** | [String] | ❌ | - | Ethnicity (White/Black/Latino/Asian/etc.) |
### Pre-Save Hooks
**Image Processing** (both `save` and `findOneAndUpdate` hooks):
1. **Detects Base64 Image Data**:
- Checks if `details.pic.detail` or `details.pic.thumb` is an object/base64 string
2. **Processes Images**:
- **Detail Image**: Calls `Images.saveProfileDetailImage()`
- Decodes base64
- Generates unique filename
- Saves to `src/images/profile/<filename>`
- Returns path string
- **Thumbnail**: Calls `Images.saveProfileThumbnailImage()`
- Same process for thumbnails
- May resize/compress image
3. **Updates Document**:
- Replaces base64 data with file path string
- Continues with save operation
### Example Document
```json
{
"_id": ObjectId("507f1f77bcf86cd799439011"),
"order": 1,
"details": {
"name": "John",
"age": 28,
"location": "San Francisco, CA",
"about": "I've been using dating apps for 5 years. My experience has been...",
"pic": {
"thumb": "profile/john_1234567890_thumbnail.png",
"detail": "profile/john_1234567890_detail.png"
},
"position": ["Top", "Versatile"],
"looking": ["Dates", "Friends"],
"tribes": ["Geek", "Jock"],
"ethnos": ["White", "Latino"]
},
"messages": [
{
"_id": ObjectId("507f191e810c19729de860ea"),
"text": "What brought you to dating apps?",
"isUser": false,
"timestamp": ISODate("2024-01-15T10:30:00Z")
},
{
"_id": ObjectId("507f191e810c19729de860eb"),
"text": "I moved to San Francisco for work and didn't know anyone...",
"image": "message/john_response_1234567890.png",
"isUser": true,
"timestamp": ISODate("2024-01-15T10:32:00Z")
}
],
"submitted": true,
"approved": true
}
```
---
## 💬 Message Schema
**Collection**: Embedded in `profiles.messages` array
**Purpose**: Q&A conversation threads between interviewer and profile subject
### Schema
| Field | Type | Required | Index | Default | Description |
| ------------- | ------- | -------- | ----- | ------------ | ------------------------------------------ |
| **text** | String | ❌ | ❌ | - | Message content |
| **image** | String | ❌ | ✅ | - | Image file path (if message has photo) |
| **isUser** | Boolean | ✅ | ✅ | `false` | `true` = user response, `false` = question |
| **timestamp** | Date | ❌ | ❌ | `Date.now()` | Message timestamp |
### Message Types
| `isUser` | Type | Purpose | Display |
| --------- | -------- | ---------------------- | --------------------- |
| **false** | Question | Interviewer's question | Left-aligned, bold |
| **true** | Response | User's answer | Right-aligned, normal |
### Pre-Save Hooks
**Image Processing** (both `save` and `findOneAndUpdate` hooks):
1. **Detects Image Data**:
- Checks if `message.image` is an object (base64)
2. **Processes Image**:
- Calls `Images.saveMessageImage()`
- Decodes base64 data
- Generates unique filename
- Saves to `src/images/message/<filename>`
- Returns path string
3. **Updates Document**:
- Replaces base64 with file path
- Continues with save
### Example Messages
```json
[
{
"_id": ObjectId("507f191e810c19729de860ea"),
"text": "What's your most memorable dating app experience?",
"isUser": false,
"timestamp": ISODate("2024-01-15T10:30:00Z")
},
{
"_id": ObjectId("507f191e810c19729de860eb"),
"text": "I matched with someone who shared my love of hiking...",
"image": "message/hiking_photo_1234567890.png",
"isUser": true,
"timestamp": ISODate("2024-01-15T10:35:00Z")
}
]
```
---
## 🔄 Reset Collection
**Collection Name**: `resets`
**Purpose**: Password reset token management
### Schema
| Field | Type | Required | Default | Description |
| -------------- | -------- | -------- | ------------ | -------------------------- |
| **user** | ObjectId | ✅ | - | Reference to `users._id` |
| **expires** | Date | ❌ | `Date.now()` | Token expiration timestamp |
| **used** | Boolean | ❌ | `false` | Token already consumed |
| **updated_at** | Date | ❌ | `Date.now()` | Last update timestamp |
### Token Generation
**HMAC-SHA1 Token**:
```javascript
const secret = "Creepily hooking the gays up since 2008!";
const token = crypto
.createHmac("sha1", secret)
.update(userId + "|" + expires)
.digest("hex");
```
**Expiration**: Typically 1-24 hours from creation
### Reset Workflow
1. **User requests reset**: `POST /auth/reset` with username
2. **System creates reset document**: Links user, generates token, sets expiration
3. **Email sent**: Password reset link with `/auth/reset/:id/:token`
4. **User clicks link**: `GET /auth/reset/:id/:token` validates token
5. **User sets new password**: `PUT /auth/reset/:id/:token` with new password
6. **Token marked as used**: `used: true` prevents reuse
### Example Document
```json
{
"_id": ObjectId("507f1f77bcf86cd799439020"),
"user": ObjectId("507f1f77bcf86cd799439011"),
"expires": ISODate("2024-01-16T10:00:00Z"),
"used": false,
"updated_at": ISODate("2024-01-15T10:00:00Z")
}
```
---
## 🗺️ Geocache Collection
**Collection Name**: `geocaches`
**Purpose**: Cache Google Maps API geocoding results to reduce API calls
### Schema
| Field | Type | Required | Unique | Default | Description |
| ------------------- | -------- | -------- | ------ | --------- | ---------------------------------------------------- |
| **key** | String | ✅ | ✅ | - | Original location string (e.g., "San Francisco, CA") |
| **formatted** | String | ✅ | ❌ | - | Google's formatted address |
| **loc.type** | String | ❌ | ❌ | `"Point"` | GeoJSON type |
| **loc.coordinates** | [Number] | ❌ | ❌ | `[0, 0]` | [longitude, latitude] array |
| **georesult** | Mixed | ❌ | ❌ | - | Full Google Maps API response |
### GeoJSON Structure
MongoDB's geospatial queries require **GeoJSON Point** format:
```json
{
"type": "Point",
"coordinates": [-122.4194, 37.7749] // [longitude, latitude]
}
```
⚠️ **Note**: Coordinates are `[lng, lat]`, not `[lat, lng]`
### Example Document
```json
{
"_id": ObjectId("507f1f77bcf86cd799439030"),
"key": "San Francisco, CA",
"formatted": "San Francisco, California, USA",
"loc": {
"type": "Point",
"coordinates": [-122.4194, 37.7749]
},
"georesult": {
"address_components": [ ... ],
"formatted_address": "San Francisco, California, USA",
"geometry": {
"location": {
"lat": 37.7749,
"lng": -122.4194
}
},
"place_id": "ChIJIQBpAG2ahYAR_6128GcTUEo"
}
}
```
### Usage
**Geocoding Workflow**:
1. Check if location exists in geocache by `key`
2. If cached, return stored result (no API call)
3. If not cached:
- Call Google Maps Geocoding API
- Store result in geocache
- Return result
4. Future requests for same location use cache
**Benefits**:
- Reduces API costs
- Faster response times
- Works offline for cached locations
---
## 🔗 Entity Relationships
```mermaid
erDiagram
USER ||--o{ RESET : "can have"
PROFILE ||--o{ MESSAGE : "contains"
PROFILE }o--|| GEOCACHE : "references location"
USER {
ObjectId _id PK
string username UK
string password
string email UK
array can
boolean forceReset
}
PROFILE {
ObjectId _id PK
number order
object details
array messages
boolean submitted
boolean approved
}
MESSAGE {
ObjectId _id PK
string text
string image
boolean isUser
date timestamp
}
RESET {
ObjectId _id PK
ObjectId user FK
date expires
boolean used
}
GEOCACHE {
ObjectId _id PK
string key UK
string formatted
object loc
object georesult
}
```
### Relationship Details
| Relationship | Type | Description |
| ---------------------- | ---------------------- | ---------------------------------------------- |
| **User → Reset** | One-to-Many | User can have multiple reset tokens (history) |
| **Profile → Message** | One-to-Many (Embedded) | Profile contains array of messages |
| **Profile → Geocache** | Many-to-One (Soft) | Profile location string may match geocache key |
**Note**: No foreign key constraints in MongoDB. Relationships are application-level only.
---
## 📇 Indexes
### Current Indexes
| Collection | Field | Type | Purpose |
| ------------- | -------------- | -------- | -------------------------------------- |
| **users** | `username` | Unique | Fast login lookups, prevent duplicates |
| **users** | `email` | Unique | Email validation, prevent duplicates |
| **profiles** | `details.age` | Standard | Age range filtering |
| **profiles** | `details.name` | Standard | Name search/sorting |
| **messages** | `image` | Standard | Find messages with images |
| **messages** | `isUser` | Standard | Filter by message type |
| **geocaches** | `key` | Unique | Location lookup cache |
### Index Usage Queries
```javascript
// Fast age range query
db.profiles
.find({
"details.age": { $gte: 25, $lte: 35 },
})
.sort({ order: 1 });
// User login
db.users.findOne({ username: "admin" });
// Geocache lookup
db.geocaches.findOne({ key: "San Francisco, CA" });
// Messages with images
db.profiles.find({ "messages.image": { $exists: true } });
```
### Recommended Additional Indexes
For production optimization:
```javascript
// Approved profiles (most common query)
db.profiles.createIndex({ approved: 1, order: 1 });
// Submitted profiles (admin review)
db.profiles.createIndex({ submitted: 1, approved: 1 });
// User permissions lookup
db.users.createIndex({ can: 1 });
// Location geospatial queries
db.geocaches.createIndex({ loc: "2dsphere" });
```
---
## 🖼️ Image Processing Hooks
### Overview
Images are processed **automatically** via Mongoose pre-save hooks before documents are saved to MongoDB.
**Supported Formats**:
- Base64 encoded strings (from frontend)
- Data URLs: `data:image/png;base64,iVBORw0KGg...`
- File paths (strings are passed through)
### Processing Flow
```mermaid
graph TD
A[Document Save Triggered] --> B{Field is Base64?}
B -->|Yes| C[Extract Base64 Data]
B -->|No string path| D[Skip Processing]
C --> E[Decode to Binary]
E --> F[Generate Unique Filename]
F --> G{Image Type?}
G -->|Profile Detail| H[Save to src/images/profile/]
G -->|Profile Thumb| I[Save to src/images/profile/]
G -->|Message Image| J[Save to src/images/message/]
H --> K[Return File Path]
I --> K
J --> K
K --> L[Update Document Field]
L --> M[Continue Save to MongoDB]
D --> M
```
### Image Modules
**Location**: `modules/images.js`
**Functions**:
- `saveProfileDetailImage(data, callback)` - Processes full-size profile photos
- `saveProfileThumbnailImage(data, callback)` - Processes profile thumbnails
- `saveMessageImage(data, callback)` - Processes message attachments
**Filename Format**:
```
<type>_<shortid>_<timestamp>.<ext>
Example: profile_aB3xF2_1705315200000.png
```
### Storage Locations
| Image Type | Directory | Example Path |
| --------------------- | --------------------- | ------------------------------------ |
| **Profile Detail** | `src/images/profile/` | `profile/john_detail_1234567890.png` |
| **Profile Thumbnail** | `src/images/profile/` | `profile/john_thumb_1234567890.png` |
| **Message Image** | `src/images/message/` | `message/response_1234567890.png` |
| **Additional** | `src/images/cruise/` | `cruise/event_1234567890.png` |
### Error Handling
If image processing fails:
- **Error logged** via Winston logger
- **Save continues** with original data
- **Client receives error** in response
**Example Error**:
```javascript
Logger.error(
"[MessageSchema.pre(save)] There was an error processing the message image.",
{
error: err,
}
);
```
### Volume Persistence
**Development**: Images stored in DevContainer volume
**Production**: Images stored in Docker volume `api_images`
**Volume Mount**:
```yaml
volumes:
- api_images:/app/src/images
```
This ensures images persist across container restarts.
---
## 📚 Related Documentation
- **[Backend README](README.md)** - Server setup and development
- **[API Reference](API.md)** - Complete REST API documentation
- **[Environment Variables](.env.example)** - Configuration
- **[Root README](../README.md)** - Project overview
- **[Deployment Guide](../DEPLOYMENT.md)** - Production deployment
---
**Need Help?** Check the backend [troubleshooting section](README.md#troubleshooting) for database-related issues.