Files
looking-monorepo/backend/SCHEMA.md
2025-12-28 13:52:25 -03:00

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

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

["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 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

{
  "_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

{
  "_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

[
  {
    "_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

  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

{
  "_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:

  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

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 } });

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

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.



Need Help? Check the backend troubleshooting section for database-related issues.