20 KiB
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
- User Collection
- Profile Collection
- Message Schema
- Reset Collection
- Geocache Collection
- Entity Relationships
- Indexes
- Image Processing Hooks
📊 Collections Overview
| Collection | Purpose | Model File |
|---|---|---|
| users | User accounts and authentication | models/user.js |
| profiles | Dating profile data and stories | models/profile.js |
| messages | (Embedded in profiles) | models/message.js |
| resets | Password reset tokens | models/reset.js |
| geocaches | Location geocoding cache | 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 |
| 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
["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:
{
"password": "hash:salt" // 32-byte hash + 24-byte salt (hex encoded)
}
Pre-Save Hooks
Password Hashing (findOneAndUpdate hook):
- Detects password changes in updates
- Validates
passwordmatchesconfirmPassword - If
currentPasswordprovided, validates before allowing change - Hashes new password with
authentication.hashPassword() - Sets
forceReset: falseafter successful password change
Example Document
{
"_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):
-
Detects Base64 Image Data:
- Checks if
details.pic.detailordetails.pic.thumbis an object/base64 string
- Checks if
-
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
-
-
Updates Document:
- Replaces base64 data with file path string
- Continues with save operation
Example Document
{
"_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):
- Detects Image Data:
- Checks if
message.imageis an object (base64)
- Checks if
- Processes Image:
- Calls
Images.saveMessageImage() - Decodes base64 data
- Generates unique filename
- Saves to
src/images/message/<filename> - Returns path string
- Calls
- Updates Document:
- Replaces base64 with file path
- Continues with save
Example Messages
[
{
"_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:
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
- User requests reset:
POST /auth/resetwith username - System creates reset document: Links user, generates token, sets expiration
- Email sent: Password reset link with
/auth/reset/:id/:token - User clicks link:
GET /auth/reset/:id/:tokenvalidates token - User sets new password:
PUT /auth/reset/:id/:tokenwith new password - Token marked as used:
used: trueprevents reuse
Example Document
{
"_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:
{
"type": "Point",
"coordinates": [-122.4194, 37.7749] // [longitude, latitude]
}
⚠️ Note: Coordinates are [lng, lat], not [lat, lng]
Example Document
{
"_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:
- Check if location exists in geocache by
key - If cached, return stored result (no API call)
- If not cached:
- Call Google Maps Geocoding API
- Store result in geocache
- Return result
- Future requests for same location use cache
Benefits:
- Reduces API costs
- Faster response times
- Works offline for cached locations
🔗 Entity Relationships
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
// 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:
// 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
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 photossaveProfileThumbnailImage(data, callback)- Processes profile thumbnailssaveMessageImage(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:
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:
volumes:
- api_images:/app/src/images
This ensures images persist across container restarts.
📚 Related Documentation
- Backend README - Server setup and development
- API Reference - Complete REST API documentation
- Environment Variables - Configuration
- Root README - Project overview
- Deployment Guide - Production deployment
Need Help? Check the backend troubleshooting section for database-related issues.