removed IDToken encrption
This commit is contained in:
@@ -1,442 +0,0 @@
|
||||
# Grateful Journal — Migration Guide
|
||||
|
||||
**Version:** 2.0 → 2.1 (Database Refactoring)
|
||||
**Date:** 2026-03-05
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This guide walks you through migrating your MongoDB database from the old schema (with duplicate users and string userId references) to the new refactored schema.
|
||||
|
||||
⚠️ **IMPORTANT:** Backup your database before starting. This process modifies your data.
|
||||
|
||||
---
|
||||
|
||||
## Pre-Migration Checklist
|
||||
|
||||
- [ ] No active users using the application
|
||||
- [ ] Database backup created
|
||||
- [ ] Python dependencies installed
|
||||
- [ ] FastAPI backend stopped
|
||||
- [ ] MongoDB running and accessible
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Backup Your Database
|
||||
|
||||
**Critical:** Always backup before running migrations.
|
||||
|
||||
```bash
|
||||
# Create timestamped backup
|
||||
mongodump --db grateful_journal --out ./backup-$(date +%Y%m%d-%H%M%S)
|
||||
|
||||
# Verify backup
|
||||
ls -lh backup-*/
|
||||
```
|
||||
|
||||
This creates a directory like `backup-2026-03-05-120000` with all your data.
|
||||
|
||||
**Alternative: Cloud Backup (MongoDB Atlas)**
|
||||
|
||||
If using MongoDB Atlas, create a snapshot in the dashboard before proceeding.
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Verify Current Database State
|
||||
|
||||
Before migration, inspect your current data:
|
||||
|
||||
```bash
|
||||
# Check duplicate users by email
|
||||
mongosh --db grateful_journal << 'EOF'
|
||||
db.users.aggregate([
|
||||
{ $group: { _id: "$email", count: { $sum: 1 }, ids: { $push: "$_id" } } },
|
||||
{ $match: { count: { $gt: 1 } } }
|
||||
])
|
||||
EOF
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
If you see results, you have duplicates. The migration script will consolidate them.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Ensure Dependencies
|
||||
|
||||
The migration script uses PyMongo, which should already be installed:
|
||||
|
||||
```bash
|
||||
cd /Users/jeet/Desktop/Jio/grateful-journal
|
||||
|
||||
# Check if pymongo is installed
|
||||
python -c "import pymongo; print(pymongo.__version__)"
|
||||
|
||||
# If not installed:
|
||||
pip install pymongo
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Run the Migration Script
|
||||
|
||||
Navigate to the backend directory and run the migration:
|
||||
|
||||
```bash
|
||||
cd /Users/jeet/Desktop/Jio/grateful-journal/backend
|
||||
|
||||
# Run the migration
|
||||
python scripts/migrate_data.py
|
||||
```
|
||||
|
||||
**Script Output:**
|
||||
|
||||
The script will:
|
||||
|
||||
1. Report duplicate users found
|
||||
2. Map old duplicate user IDs to the canonical (oldest) user
|
||||
3. Update all entries to reference the canonical user
|
||||
4. Convert `userId` from string to ObjectId
|
||||
5. Add `entryDate` field to entries
|
||||
6. Add `encryption` metadata to entries
|
||||
7. Verify data integrity
|
||||
|
||||
**Example Output:**
|
||||
|
||||
```
|
||||
✓ Connected to MongoDB: grateful_journal
|
||||
|
||||
======================================================================
|
||||
STEP 1: Deduplicating Users (keeping oldest)
|
||||
======================================================================
|
||||
|
||||
📧 Email: jeet.debnath2004@gmail.com
|
||||
Found 12 duplicate users
|
||||
Keeping (earliest): ObjectId('69a7d6749a69142259e40394')
|
||||
Deleting (later): ObjectId('69a7db0f8fbb489ac05ab945')
|
||||
Deleting (later): ObjectId('69a7db178fbb489ac05ab946')
|
||||
...
|
||||
|
||||
✓ Removed 11 duplicate users
|
||||
|
||||
======================================================================
|
||||
STEP 2: Migrating Entries (userId string → ObjectId, add entryDate)
|
||||
======================================================================
|
||||
|
||||
Total entries to process: 42
|
||||
|
||||
✓ Processed 100/150 entries
|
||||
✓ Updated 150/150 entries
|
||||
|
||||
✓ Updated 150 entries
|
||||
|
||||
======================================================================
|
||||
STEP 3: Verifying Data Integrity
|
||||
======================================================================
|
||||
|
||||
Users collection: 1
|
||||
Entries collection: 150
|
||||
|
||||
✓ All entries have valid user references
|
||||
|
||||
Sample entry structure:
|
||||
_id (entry): ObjectId('...') (ObjectId: True)
|
||||
userId: ObjectId('...') (ObjectId: True)
|
||||
entryDate present: True
|
||||
encryption present: True
|
||||
|
||||
======================================================================
|
||||
✓ Migration Complete
|
||||
======================================================================
|
||||
Duplicate users removed: 11
|
||||
Entries migrated: 150
|
||||
Orphaned entries found: 0
|
||||
|
||||
✓ Data integrity verified successfully!
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 5: Create Indexes
|
||||
|
||||
After migration, create indexes for optimized performance:
|
||||
|
||||
```bash
|
||||
python backend/scripts/create_indexes.py
|
||||
```
|
||||
|
||||
**Expected Output:**
|
||||
|
||||
```
|
||||
✓ Connected to MongoDB: grateful_journal
|
||||
|
||||
Creating indexes for 'users' collection...
|
||||
✓ Created unique index on email
|
||||
✓ Created index on createdAt
|
||||
|
||||
Creating indexes for 'entries' collection...
|
||||
✓ Created compound index on (userId, createdAt)
|
||||
✓ Created compound index on (userId, entryDate)
|
||||
✓ Created index on tags
|
||||
✓ Created index on entryDate
|
||||
|
||||
============================================================
|
||||
✓ Index Creation Complete
|
||||
============================================================
|
||||
Total indexes created: 7
|
||||
• users.email_unique
|
||||
• users.createdAt_desc
|
||||
• entries.userId_createdAt
|
||||
• entries.userId_entryDate
|
||||
• entries.tags
|
||||
• entries.entryDate_desc
|
||||
|
||||
✓ Disconnected from MongoDB
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 6: Verify Schema
|
||||
|
||||
Verify the new schema is correct:
|
||||
|
||||
```bash
|
||||
mongosh --db grateful_journal << 'EOF'
|
||||
// Check user structure
|
||||
db.users.findOne()
|
||||
|
||||
// Check entry structure
|
||||
db.entries.findOne()
|
||||
|
||||
// Count documents
|
||||
db.users.countDocuments({})
|
||||
db.entries.countDocuments({})
|
||||
|
||||
// Verify indexes
|
||||
db.users.getIndexes()
|
||||
db.entries.getIndexes()
|
||||
EOF
|
||||
```
|
||||
|
||||
**Expected Sample Output:**
|
||||
|
||||
```javascript
|
||||
// User document
|
||||
{
|
||||
_id: ObjectId("507f1f77bcf86cd799439011"),
|
||||
email: "jeet.debnath2004@gmail.com",
|
||||
displayName: "Jeet Debnath",
|
||||
photoURL: "https://...",
|
||||
theme: "light",
|
||||
createdAt: ISODate("2026-03-04T06:51:32.598Z"),
|
||||
updatedAt: ISODate("2026-03-05T10:30:00.000Z")
|
||||
}
|
||||
|
||||
// Entry document
|
||||
{
|
||||
_id: ObjectId("507f1f77bcf86cd799439012"),
|
||||
userId: ObjectId("507f1f77bcf86cd799439011"), // ← Now ObjectId!
|
||||
title: "Today's Gratitude",
|
||||
content: "I'm grateful for...",
|
||||
mood: "grateful",
|
||||
tags: ["family", "work"],
|
||||
isPublic: false,
|
||||
entryDate: ISODate("2026-03-05T00:00:00.000Z"), // ← New field!
|
||||
createdAt: ISODate("2026-03-05T12:30:15.123Z"),
|
||||
updatedAt: ISODate("2026-03-05T12:30:15.123Z"),
|
||||
encryption: { // ← New field!
|
||||
encrypted: false,
|
||||
iv: null,
|
||||
algorithm: null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Step 7: Test Backend
|
||||
|
||||
Start the backend and verify it works with the new schema:
|
||||
|
||||
```bash
|
||||
cd /Users/jeet/Desktop/Jio/grateful-journal/backend
|
||||
|
||||
# Start the backend (in a new terminal)
|
||||
python -m uvicorn main:app --reload --port 8001
|
||||
```
|
||||
|
||||
**Test endpoints:**
|
||||
|
||||
```bash
|
||||
# Health check
|
||||
curl http://localhost:8001/health
|
||||
|
||||
# Get user by email (replace with your email)
|
||||
curl -X GET "http://localhost:8001/api/users/by-email/jeet.debnath2004@gmail.com"
|
||||
|
||||
# Get user entries
|
||||
curl -X GET "http://localhost:8001/api/entries/{user_id}?limit=10&skip=0"
|
||||
```
|
||||
|
||||
Expected: All requests succeed with 200 status.
|
||||
|
||||
---
|
||||
|
||||
## Step 8: Restart Frontend
|
||||
|
||||
Once confident the backend works, restart the frontend:
|
||||
|
||||
```bash
|
||||
# In a new terminal
|
||||
cd /Users/jeet/Desktop/Jio/grateful-journal
|
||||
npm run dev # or your dev command
|
||||
```
|
||||
|
||||
Test the full application:
|
||||
|
||||
- Login via Google
|
||||
- Create an entry
|
||||
- View entries in history
|
||||
- Check calendar view
|
||||
|
||||
---
|
||||
|
||||
## Rollback Procedure
|
||||
|
||||
If something goes wrong:
|
||||
|
||||
```bash
|
||||
# Restore from backup
|
||||
mongorestore --drop --db grateful_journal ./backup-2026-03-05-120000
|
||||
|
||||
# Restart backend and frontend
|
||||
```
|
||||
|
||||
This will revert the database to its pre-migration state.
|
||||
|
||||
---
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### Issue: "invalid ObjectId" errors
|
||||
|
||||
**Cause:** Some entries still have string userId references.
|
||||
**Fix:** Re-run the migration script:
|
||||
|
||||
```bash
|
||||
python backend/scripts/migrate_data.py
|
||||
```
|
||||
|
||||
### Issue: Entries not showing up
|
||||
|
||||
**Cause:** userId is still a string in old entries.
|
||||
**Fix:** Check the entry structure:
|
||||
|
||||
```bash
|
||||
mongosh --db grateful_journal
|
||||
db.entries.findOne() # Check userId type
|
||||
```
|
||||
|
||||
If userId is a string, run migration again.
|
||||
|
||||
### Issue: "duplicate key error" on email index
|
||||
|
||||
**Cause:** Index creation failed due to duplicate emails.
|
||||
**Fix:** The migration script handles this, but if you hit this:
|
||||
|
||||
```bash
|
||||
# Rerun migration
|
||||
python scripts/migrate_data.py
|
||||
```
|
||||
|
||||
### Issue: Script won't run
|
||||
|
||||
```bash
|
||||
# Ensure you're in the backend directory
|
||||
cd /Users/jeet/Desktop/Jio/grateful-journal/backend
|
||||
|
||||
# Check Python path
|
||||
python --version
|
||||
|
||||
# Run with explicit module path
|
||||
python -m scripts.migrate_data
|
||||
```
|
||||
|
||||
### Issue: MongoDB connection refused
|
||||
|
||||
```bash
|
||||
# Check if MongoDB is running
|
||||
mongosh
|
||||
|
||||
# If not running, start it:
|
||||
# On macOS with Homebrew:
|
||||
brew services start mongodb-community
|
||||
|
||||
# Or manually:
|
||||
mongod
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Post-Migration
|
||||
|
||||
### Update Documentation
|
||||
|
||||
- [x] Update [SCHEMA.md](./SCHEMA.md) with new schema
|
||||
- [x] Update [models.py](./models.py)
|
||||
- [x] Update router docstrings
|
||||
|
||||
### Performance Tuning
|
||||
|
||||
Monitor slow queries:
|
||||
|
||||
```bash
|
||||
mongosh --db grateful_journal << 'EOF'
|
||||
// Monitor slow queries
|
||||
db.setProfilingLevel(1, { slowms: 100 })
|
||||
|
||||
// Check profiling
|
||||
db.system.profile.find().pretty()
|
||||
EOF
|
||||
```
|
||||
|
||||
### Data Analysis
|
||||
|
||||
Check migration statistics:
|
||||
|
||||
```bash
|
||||
mongosh --db grateful_journal << 'EOF'
|
||||
// Total users and entries
|
||||
db.users.countDocuments({})
|
||||
db.entries.countDocuments({})
|
||||
|
||||
// Entries with encryption
|
||||
db.entries.countDocuments({ "encryption.encrypted": true })
|
||||
|
||||
// Entries without entryDate (should be 0)
|
||||
db.entries.countDocuments({ entryDate: { $exists: false } })
|
||||
EOF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Monitor**: Watch logs for any errors or warnings
|
||||
2. **Test**: Thoroughly test all features (login, create, read, update, delete)
|
||||
3. **Celebrate**: You've successfully migrated! 🎉
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues:
|
||||
|
||||
1. Check [SCHEMA.md](./SCHEMA.md) for schema details
|
||||
2. Review backend logs: `tail -f logs/backend.log`
|
||||
3. Inspect MongoDB: Use mongosh to query directly
|
||||
4. Consult the code: Check [routers/users.py](./routers/users.py) and [routers/entries.py](./routers/entries.py)
|
||||
|
||||
---
|
||||
|
||||
_Happy journaling! 📔_
|
||||
@@ -1,453 +0,0 @@
|
||||
# Database Refactoring Summary
|
||||
|
||||
**Project:** Grateful Journal
|
||||
**Version:** 2.1 (Database Schema Refactoring)
|
||||
**Date:** 2026-03-05
|
||||
**Status:** Complete ✓
|
||||
|
||||
---
|
||||
|
||||
## What Changed
|
||||
|
||||
This refactoring addresses critical database issues and optimizes the MongoDB schema for the Grateful Journal application.
|
||||
|
||||
### Problems Addressed
|
||||
|
||||
| Issue | Solution |
|
||||
| ---------------------------- | ----------------------------------------- |
|
||||
| Duplicate users (same email) | Unique email index + upsert pattern |
|
||||
| userId as string | Convert to ObjectId; index |
|
||||
| No database indexes | Create 7 indexes for common queries |
|
||||
| Missing journal date | Add `entryDate` field to entries |
|
||||
| Settings in separate table | Move user preferences to users collection |
|
||||
| No encryption support | Add `encryption` metadata field |
|
||||
| Poor pagination support | Add compound indexes for pagination |
|
||||
|
||||
---
|
||||
|
||||
## Files Modified
|
||||
|
||||
### Backend Core
|
||||
|
||||
1. **[models.py](./models.py)** — Updated Pydantic models
|
||||
- Changed `User.id: str` → now uses `_id` alias for ObjectId
|
||||
- Added `JournalEntry.entryDate: datetime`
|
||||
- Added `EncryptionMetadata` model for encryption support
|
||||
- Added pagination response models
|
||||
|
||||
2. **[routers/users.py](./routers/users.py)** — Rewrote user logic
|
||||
- Changed user registration from `insert_one` → `update_one` with upsert
|
||||
- Prevents duplicate users (one per email)
|
||||
- Validates ObjectId conversions with error handling
|
||||
- Added `get_user_by_id` endpoint
|
||||
|
||||
3. **[routers/entries.py](./routers/entries.py)** — Updated entry handling
|
||||
- Convert all `userId` from string → ObjectId
|
||||
- Enforce user existence check before entry creation
|
||||
- Added `entryDate` field support
|
||||
- Added `get_entries_by_month` for calendar queries
|
||||
- Improved pagination with `hasMore` flag
|
||||
- Better error messages for invalid ObjectIds
|
||||
|
||||
### New Scripts
|
||||
|
||||
4. **[scripts/migrate_data.py](./scripts/migrate_data.py)** — Data migration
|
||||
- Deduplicates users by email (keeps oldest)
|
||||
- Converts `entries.userId` string → ObjectId
|
||||
- Adds `entryDate` field (defaults to createdAt)
|
||||
- Adds encryption metadata
|
||||
- Verifies data integrity post-migration
|
||||
|
||||
5. **[scripts/create_indexes.py](./scripts/create_indexes.py)** — Index creation
|
||||
- Creates unique index on `users.email`
|
||||
- Creates compound indexes:
|
||||
- `entries(userId, createdAt)` — for history/pagination
|
||||
- `entries(userId, entryDate)` — for calendar view
|
||||
- Creates supporting indexes for tags and dates
|
||||
|
||||
### Documentation
|
||||
|
||||
6. **[SCHEMA.md](./SCHEMA.md)** — Complete schema documentation
|
||||
- Full field descriptions and examples
|
||||
- Index rationale and usage
|
||||
- Query patterns with examples
|
||||
- Data type conversions
|
||||
- Security considerations
|
||||
|
||||
7. **[MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md)** — Step-by-step migration
|
||||
- Pre-migration checklist
|
||||
- Backup instructions
|
||||
- Running migration and index scripts
|
||||
- Rollback procedure
|
||||
- Troubleshooting guide
|
||||
|
||||
---
|
||||
|
||||
## New Database Schema
|
||||
|
||||
### Users Collection
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId,
|
||||
email: string (unique), // ← Unique constraint prevents duplicates
|
||||
displayName: string,
|
||||
photoURL: string,
|
||||
theme: "light" | "dark", // ← Moved from settings collection
|
||||
createdAt: datetime,
|
||||
updatedAt: datetime
|
||||
}
|
||||
```
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
- ✓ Unique email index
|
||||
- ✓ Settings embedded (theme field)
|
||||
- ✓ No separate settings collection
|
||||
|
||||
### Entries Collection
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId,
|
||||
userId: ObjectId, // ← Now ObjectId, not string
|
||||
title: string,
|
||||
content: string,
|
||||
mood: string | null,
|
||||
tags: string[],
|
||||
isPublic: boolean,
|
||||
|
||||
entryDate: datetime, // ← NEW: Logical journal date
|
||||
createdAt: datetime,
|
||||
updatedAt: datetime,
|
||||
|
||||
encryption: { // ← NEW: Encryption metadata
|
||||
encrypted: boolean,
|
||||
iv: string | null,
|
||||
algorithm: string | null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Key Changes:**
|
||||
|
||||
- ✓ `userId` is ObjectId
|
||||
- ✓ `entryDate` separates "when written" (createdAt) from "which day it's for" (entryDate)
|
||||
- ✓ Encryption metadata for future encrypted storage
|
||||
- ✓ No separate settings collection
|
||||
|
||||
---
|
||||
|
||||
## API Changes
|
||||
|
||||
### User Registration (Upsert)
|
||||
|
||||
**Old:**
|
||||
|
||||
```python
|
||||
POST /api/users/register
|
||||
# Created new user every time (duplicates!)
|
||||
```
|
||||
|
||||
**New:**
|
||||
|
||||
```python
|
||||
POST /api/users/register
|
||||
# Idempotent: updates if exists, inserts if not
|
||||
# Returns 200 regardless (existing or new)
|
||||
```
|
||||
|
||||
### Get User by ID
|
||||
|
||||
**New Endpoint:**
|
||||
|
||||
```
|
||||
GET /api/users/{user_id}
|
||||
```
|
||||
|
||||
Returns user by ObjectId instead of only by email.
|
||||
|
||||
### Create Entry
|
||||
|
||||
**Old:**
|
||||
|
||||
```json
|
||||
POST /api/entries/{user_id}
|
||||
{
|
||||
"title": "...",
|
||||
"content": "..."
|
||||
}
|
||||
```
|
||||
|
||||
**New:**
|
||||
|
||||
```json
|
||||
POST /api/entries/{user_id}
|
||||
{
|
||||
"title": "...",
|
||||
"content": "...",
|
||||
"entryDate": "2026-03-05T00:00:00Z", // ← Optional; defaults to today
|
||||
"encryption": { // ← Optional
|
||||
"encrypted": false,
|
||||
"iv": null,
|
||||
"algorithm": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Get Entries
|
||||
|
||||
**Improved Response:**
|
||||
|
||||
```json
|
||||
{
|
||||
"entries": [...],
|
||||
"pagination": {
|
||||
"total": 150,
|
||||
"skip": 0,
|
||||
"limit": 50,
|
||||
"hasMore": true // ← New: easier to implement infinite scroll
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### New Endpoint: Get Entries by Month
|
||||
|
||||
**For Calendar View:**
|
||||
|
||||
```
|
||||
GET /api/entries/{user_id}/by-month/{year}/{month}?limit=100
|
||||
```
|
||||
|
||||
Returns all entries for a specific month, optimized for calendar display.
|
||||
|
||||
---
|
||||
|
||||
## Execution Plan
|
||||
|
||||
### Step 1: Deploy Updated Backend Code
|
||||
|
||||
✓ Update models.py
|
||||
✓ Update routers/users.py
|
||||
✓ Update routers/entries.py
|
||||
|
||||
**Time:** Immediate (code change only, no data changes)
|
||||
|
||||
### Step 2: Run Data Migration
|
||||
|
||||
```bash
|
||||
python backend/scripts/migrate_data.py
|
||||
```
|
||||
|
||||
- Removes 11 duplicate users (keeps oldest)
|
||||
- Updates 150 entries to use ObjectId userId
|
||||
- Adds entryDate field
|
||||
- Adds encryption metadata
|
||||
|
||||
**Time:** < 1 second for 150 entries
|
||||
|
||||
### Step 3: Create Indexes
|
||||
|
||||
```bash
|
||||
python backend/scripts/create_indexes.py
|
||||
```
|
||||
|
||||
- Creates 7 indexes on users and entries
|
||||
- Improves query performance by 10-100x for large datasets
|
||||
|
||||
**Time:** < 1 second
|
||||
|
||||
### Step 4: Restart Backend & Test
|
||||
|
||||
```bash
|
||||
# Restart FastAPI server
|
||||
python -m uvicorn main:app --reload --port 8001
|
||||
|
||||
# Run tests
|
||||
curl http://localhost:8001/health
|
||||
curl -X GET "http://localhost:8001/api/users/by-email/..."
|
||||
```
|
||||
|
||||
**Time:** < 1 minute
|
||||
|
||||
### Step 5: Test Frontend
|
||||
|
||||
Login, create entries, view history, check calendar.
|
||||
|
||||
**Time:** 5-10 minutes
|
||||
|
||||
---
|
||||
|
||||
## Performance Impact
|
||||
|
||||
### Query Speed Improvements
|
||||
|
||||
| Query | Before | After | Improvement |
|
||||
| ---------------------------------- | ------ | ----- | ----------- |
|
||||
| Get user by email | ~50ms | ~5ms | 10x |
|
||||
| Get 50 user entries (paginated) | ~100ms | ~10ms | 10x |
|
||||
| Get entries for a month (calendar) | N/A | ~20ms | New query |
|
||||
| Delete all user entries | ~200ms | ~20ms | 10x |
|
||||
|
||||
### Index Sizes
|
||||
|
||||
- `users` indexes: ~1 KB
|
||||
- `entries` indexes: ~5-50 KB (depends on data size)
|
||||
|
||||
### Storage
|
||||
|
||||
No additional storage needed; indexes are standard MongoDB practice.
|
||||
|
||||
---
|
||||
|
||||
## Breaking Changes
|
||||
|
||||
### Frontend
|
||||
|
||||
No breaking changes if using the API correctly. However:
|
||||
|
||||
- Remove any code that assumes multiple users per email
|
||||
- Update any hardcoded user ID handling if needed
|
||||
- Test login flow (upsert pattern is transparent)
|
||||
|
||||
### Backend
|
||||
|
||||
- All `userId` parameters must now be valid ObjectIds
|
||||
- Query changes if you were accessing internal DB directly
|
||||
- Update any custom MongoDB scripts/queries
|
||||
|
||||
---
|
||||
|
||||
## Safety & Rollback
|
||||
|
||||
### Backup Created
|
||||
|
||||
✓ Before migration, create backup:
|
||||
|
||||
```bash
|
||||
mongodump --db grateful_journal --out ./backup-2026-03-05
|
||||
```
|
||||
|
||||
### Rollback Available
|
||||
|
||||
If issues occur:
|
||||
|
||||
```bash
|
||||
mongorestore --drop --db grateful_journal ./backup-2026-03-05
|
||||
```
|
||||
|
||||
This restores the database to pre-migration state.
|
||||
|
||||
---
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
After migration, verify:
|
||||
|
||||
- [ ] No duplicate users with same email
|
||||
- [ ] All entries have ObjectId userId
|
||||
- [ ] All entries have entryDate field
|
||||
- [ ] All entries have encryption metadata
|
||||
- [ ] 7 indexes created successfully
|
||||
- [ ] Backend starts without errors
|
||||
- [ ] Health check (`/health`) returns 200
|
||||
- [ ] Can login via Google
|
||||
- [ ] Can create new entry
|
||||
- [ ] Can view history with pagination
|
||||
- [ ] Calendar view works
|
||||
|
||||
---
|
||||
|
||||
## Documentation
|
||||
|
||||
- **Schema:** See [SCHEMA.md](./SCHEMA.md) for full schema reference
|
||||
- **Migration:** See [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) for step-by-step instructions
|
||||
- **Code:** See inline docstrings in models.py, routers
|
||||
|
||||
---
|
||||
|
||||
## Future Enhancements
|
||||
|
||||
Based on this new schema, future features are now possible:
|
||||
|
||||
1. **Client-Side Encryption** — Use `encryption` metadata field
|
||||
2. **Tag-Based Search** — Use `tags` index for searching
|
||||
3. **Advanced Calendar** — Use `entryDate` compound index
|
||||
4. **Entry Templates** — Add template field to entries
|
||||
5. **Sharing/Collaboration** — Use `isPublic` and sharing metadata
|
||||
6. **Entry Archiving** — Use createdAt/updatedAt for archival features
|
||||
|
||||
---
|
||||
|
||||
## Questions & Answers
|
||||
|
||||
### Q: Will users be locked out?
|
||||
|
||||
**A:** No. Upsert pattern is transparent. Any login attempt will create/update the user account.
|
||||
|
||||
### Q: Will I lose any entries?
|
||||
|
||||
**A:** No. Migration preserves all entries. Only removes duplicate user documents (keeping the oldest).
|
||||
|
||||
### Q: What if migration fails?
|
||||
|
||||
**A:** Restore from backup (see MIGRATION_GUIDE.md). The process is fully reversible.
|
||||
|
||||
### Q: Do I need to update the frontend?
|
||||
|
||||
**A:** No breaking changes. The API remains compatible. Consider updating for better UX (e.g., using `hasMore` flag for pagination).
|
||||
|
||||
### Q: How long does migration take?
|
||||
|
||||
**A:** < 30 seconds for typical datasets (100-500 entries). Larger datasets may take 1-2 minutes.
|
||||
|
||||
---
|
||||
|
||||
## Support
|
||||
|
||||
If you encounter issues during or after migration:
|
||||
|
||||
1. **Check logs:**
|
||||
|
||||
```bash
|
||||
tail -f backend/logs/backend.log
|
||||
```
|
||||
|
||||
2. **Verify database:**
|
||||
|
||||
```bash
|
||||
mongosh --db grateful_journal
|
||||
db.users.countDocuments({})
|
||||
db.entries.countDocuments({})
|
||||
```
|
||||
|
||||
3. **Review documents:**
|
||||
- [SCHEMA.md](./SCHEMA.md) — Schema reference
|
||||
- [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) — Troubleshooting section
|
||||
- [models.py](./models.py) — Pydantic model definitions
|
||||
|
||||
4. **Consult code:**
|
||||
- [routers/users.py](./routers/users.py) — User logic
|
||||
- [routers/entries.py](./routers/entries.py) — Entry logic
|
||||
|
||||
---
|
||||
|
||||
## Summary
|
||||
|
||||
We've successfully refactored the Grateful Journal MongoDB database to:
|
||||
|
||||
✓ Ensure one user per email (eliminate duplicates)
|
||||
✓ Use ObjectId references throughout
|
||||
✓ Optimize query performance with strategic indexes
|
||||
✓ Prepare for client-side encryption
|
||||
✓ Simplify settings storage
|
||||
✓ Support calendar view queries
|
||||
✓ Enable pagination at scale
|
||||
|
||||
The new schema is backward-compatible with existing features and sets the foundation for future enhancements.
|
||||
|
||||
**Status:** Ready for migration 🚀
|
||||
|
||||
---
|
||||
|
||||
_Last Updated: 2026-03-05 | Next Review: 2026-06-05_
|
||||
@@ -1,526 +0,0 @@
|
||||
# Grateful Journal — MongoDB Schema Documentation
|
||||
|
||||
**Version:** 2.0 (Refactored)
|
||||
**Last Updated:** 2026-03-05
|
||||
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This document describes the refactored MongoDB schema for the Grateful Journal application. The schema has been redesigned to:
|
||||
|
||||
- Ensure one user per email (deduplicated)
|
||||
- Use ObjectId references instead of strings
|
||||
- Optimize queries for common operations (history pagination, calendar view)
|
||||
- Prepare for client-side encryption
|
||||
- Add proper indexes for performance
|
||||
|
||||
---
|
||||
|
||||
## Collections
|
||||
|
||||
### 1. `users` Collection
|
||||
|
||||
Stores user profile information. One document per unique email.
|
||||
|
||||
#### Schema
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId,
|
||||
email: string (unique),
|
||||
displayName: string,
|
||||
photoURL: string,
|
||||
theme: "light" | "dark",
|
||||
createdAt: Date,
|
||||
updatedAt: Date
|
||||
}
|
||||
```
|
||||
|
||||
#### Field Descriptions
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
| ------------- | -------- | -------- | ---------------------------------------- |
|
||||
| `_id` | ObjectId | Yes | Unique primary key, auto-generated |
|
||||
| `email` | String | Yes | User's email; unique constraint; indexed |
|
||||
| `displayName` | String | Yes | User's display name (from Google Auth) |
|
||||
| `photoURL` | String | No | User's profile photo URL |
|
||||
| `theme` | String | Yes | Theme preference: "light" or "dark" |
|
||||
| `createdAt` | Date | Yes | Account creation timestamp |
|
||||
| `updatedAt` | Date | Yes | Last profile update timestamp |
|
||||
|
||||
#### Unique Constraints
|
||||
|
||||
- `email`: Unique index ensures one user per email address
|
||||
|
||||
#### Example Document
|
||||
|
||||
```json
|
||||
{
|
||||
"_id": ObjectId("507f1f77bcf86cd799439011"),
|
||||
"email": "jeet.debnath2004@gmail.com",
|
||||
"displayName": "Jeet Debnath",
|
||||
"photoURL": "https://lh3.googleusercontent.com/a/ACg8...",
|
||||
"theme": "light",
|
||||
"createdAt": ISODate("2026-03-04T06:51:32.598Z"),
|
||||
"updatedAt": ISODate("2026-03-05T10:30:00.000Z")
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. `entries` Collection
|
||||
|
||||
Stores journal entries for each user. Each entry has a logical journal date and optional encryption metadata.
|
||||
|
||||
#### Schema
|
||||
|
||||
```javascript
|
||||
{
|
||||
_id: ObjectId,
|
||||
userId: ObjectId,
|
||||
title: string,
|
||||
content: string,
|
||||
mood: "happy" | "sad" | "neutral" | "anxious" | "grateful" | null,
|
||||
tags: string[],
|
||||
isPublic: boolean,
|
||||
|
||||
entryDate: Date, // Logical journal date
|
||||
createdAt: Date,
|
||||
updatedAt: Date,
|
||||
|
||||
encryption: {
|
||||
encrypted: boolean,
|
||||
iv: string | null, // Base64-encoded initialization vector
|
||||
algorithm: string | null // e.g., "AES-256-GCM"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Field Descriptions
|
||||
|
||||
| Field | Type | Required | Notes |
|
||||
| ------------ | -------- | -------- | ----------------------------------------- |
|
||||
| `_id` | ObjectId | Yes | Entry ID; auto-generated; indexed |
|
||||
| `userId` | ObjectId | Yes | Reference to user.\_id; indexed; enforced |
|
||||
| `title` | String | Yes | Entry title/headline |
|
||||
| `content` | String | Yes | Entry body content |
|
||||
| `mood` | String | No | Mood selector (null if not set) |
|
||||
| `tags` | Array | Yes | Array of user-defined tags [] |
|
||||
| `isPublic` | Bool | Yes | Public sharing flag (currently unused) |
|
||||
| `entryDate` | Date | Yes | Logical journal date (start of day, UTC) |
|
||||
| `createdAt` | Date | Yes | Database write timestamp |
|
||||
| `updatedAt` | Date | Yes | Last modification timestamp |
|
||||
| `encryption` | Object | Yes | Encryption metadata (nested) |
|
||||
|
||||
#### Encryption Metadata
|
||||
|
||||
```javascript
|
||||
{
|
||||
encrypted: boolean, // If true, content is encrypted
|
||||
iv: string | null, // Base64 initialization vector
|
||||
algorithm: string | null // Encryption algorithm name
|
||||
}
|
||||
```
|
||||
|
||||
**Notes:**
|
||||
|
||||
- `encrypted: false` by default (plain text storage)
|
||||
- When setting `encrypted: true`, client provides `iv` and `algorithm`
|
||||
- Server stores metadata but does NOT decrypt; decryption happens client-side
|
||||
|
||||
#### Example Document
|
||||
|
||||
```json
|
||||
{
|
||||
"_id": ObjectId("507f1f77bcf86cd799439012"),
|
||||
"userId": ObjectId("507f1f77bcf86cd799439011"),
|
||||
"title": "Today's Gratitude",
|
||||
"content": "I'm grateful for my family, coffee, and a good day at work.",
|
||||
"mood": "grateful",
|
||||
"tags": ["family", "work", "coffee"],
|
||||
"isPublic": false,
|
||||
"entryDate": ISODate("2026-03-05T00:00:00.000Z"),
|
||||
"createdAt": ISODate("2026-03-05T12:30:15.123Z"),
|
||||
"updatedAt": ISODate("2026-03-05T12:30:15.123Z"),
|
||||
"encryption": {
|
||||
"encrypted": false,
|
||||
"iv": null,
|
||||
"algorithm": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Indexes
|
||||
|
||||
Indexes optimize query performance. All indexes are created by the `scripts/create_indexes.py` script.
|
||||
|
||||
### Users Indexes
|
||||
|
||||
```javascript
|
||||
// Unique index on email (prevents duplicates)
|
||||
db.users.createIndex({ email: 1 }, { unique: true });
|
||||
|
||||
// For sorting users by creation date
|
||||
db.users.createIndex({ createdAt: -1 });
|
||||
```
|
||||
|
||||
### Entries Indexes
|
||||
|
||||
```javascript
|
||||
// Compound index for history pagination (most recent first)
|
||||
db.entries.createIndex({ userId: 1, createdAt: -1 });
|
||||
|
||||
// Compound index for calendar queries by date
|
||||
db.entries.createIndex({ userId: 1, entryDate: 1 });
|
||||
|
||||
// For tag-based searches (future feature)
|
||||
db.entries.createIndex({ tags: 1 });
|
||||
|
||||
// For sorting by entry date
|
||||
db.entries.createIndex({ entryDate: -1 });
|
||||
```
|
||||
|
||||
### Index Rationale
|
||||
|
||||
- **`(userId, createdAt)`**: Supports retrieving a user's entries in reverse chronological order with pagination
|
||||
- **`(userId, entryDate)`**: Supports calendar view queries (entries for a specific month/date)
|
||||
- **`tags`**: Supports future tag filtering/search
|
||||
- **`entryDate`**: Supports standalone date-range queries
|
||||
|
||||
---
|
||||
|
||||
## Query Patterns
|
||||
|
||||
### User Queries
|
||||
|
||||
#### Find or Create User (Upsert)
|
||||
|
||||
```python
|
||||
db.users.update_one(
|
||||
{ "email": email },
|
||||
{
|
||||
"$setOnInsert": {
|
||||
"email": email,
|
||||
"displayName": displayName,
|
||||
"photoURL": photoURL,
|
||||
"theme": "light",
|
||||
"createdAt": datetime.utcnow()
|
||||
},
|
||||
"$set": {
|
||||
"updatedAt": datetime.utcnow()
|
||||
}
|
||||
},
|
||||
upsert=True
|
||||
)
|
||||
```
|
||||
|
||||
**Why:** Ensures exactly one user per email. Frontend calls this after any Firebase login.
|
||||
|
||||
#### Get User by Email
|
||||
|
||||
```python
|
||||
user = db.users.find_one({ "email": email })
|
||||
```
|
||||
|
||||
**Index Used:** Unique index on `email`
|
||||
|
||||
---
|
||||
|
||||
### Entry Queries
|
||||
|
||||
#### Create Entry
|
||||
|
||||
```python
|
||||
db.entries.insert_one({
|
||||
"userId": ObjectId(user_id),
|
||||
"title": title,
|
||||
"content": content,
|
||||
"mood": mood,
|
||||
"tags": tags,
|
||||
"isPublic": False,
|
||||
"entryDate": entry_date, # Start of day UTC
|
||||
"createdAt": datetime.utcnow(),
|
||||
"updatedAt": datetime.utcnow(),
|
||||
"encryption": {
|
||||
"encrypted": False,
|
||||
"iv": None,
|
||||
"algorithm": None
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
#### Get Entries for User (Paginated, Recent First)
|
||||
|
||||
```python
|
||||
entries = db.entries.find(
|
||||
{ "userId": ObjectId(user_id) }
|
||||
).sort("createdAt", -1).skip(skip).limit(limit)
|
||||
```
|
||||
|
||||
**Index Used:** `(userId, createdAt)`
|
||||
**Use Case:** History page with pagination
|
||||
|
||||
#### Get Entries by Month (Calendar View)
|
||||
|
||||
```python
|
||||
start_date = datetime(year, month, 1)
|
||||
end_date = datetime(year, month + 1, 1)
|
||||
|
||||
entries = db.entries.find({
|
||||
"userId": ObjectId(user_id),
|
||||
"entryDate": {
|
||||
"$gte": start_date,
|
||||
"$lt": end_date
|
||||
}
|
||||
}).sort("entryDate", -1)
|
||||
```
|
||||
|
||||
**Index Used:** `(userId, entryDate)`
|
||||
**Use Case:** Calendar view showing entries for a specific month
|
||||
|
||||
#### Get Entry for Specific Date
|
||||
|
||||
```python
|
||||
target_date = datetime(year, month, day)
|
||||
next_date = target_date + timedelta(days=1)
|
||||
|
||||
entries = db.entries.find({
|
||||
"userId": ObjectId(user_id),
|
||||
"entryDate": {
|
||||
"$gte": target_date,
|
||||
"$lt": next_date
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
**Index Used:** `(userId, entryDate)`
|
||||
**Use Case:** Daily view or fetching today's entry
|
||||
|
||||
#### Update Entry
|
||||
|
||||
```python
|
||||
db.entries.update_one(
|
||||
{ "_id": ObjectId(entry_id), "userId": ObjectId(user_id) },
|
||||
{
|
||||
"$set": {
|
||||
"title": new_title,
|
||||
"content": new_content,
|
||||
"mood": new_mood,
|
||||
"updatedAt": datetime.utcnow()
|
||||
}
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
#### Delete Entry
|
||||
|
||||
```python
|
||||
db.entries.delete_one({
|
||||
"_id": ObjectId(entry_id),
|
||||
"userId": ObjectId(user_id)
|
||||
})
|
||||
```
|
||||
|
||||
#### Delete All User Entries (on account deletion)
|
||||
|
||||
```python
|
||||
db.entries.delete_many({ "userId": ObjectId(user_id) })
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Data Types & Conversions
|
||||
|
||||
### ObjectId
|
||||
|
||||
**MongoDB Storage:** `ObjectId`
|
||||
**Python Type:** `bson.ObjectId`
|
||||
**JSON Representation:** String (24-character hex)
|
||||
|
||||
**Conversion:**
|
||||
|
||||
```python
|
||||
from bson import ObjectId
|
||||
|
||||
# String to ObjectId
|
||||
oid = ObjectId(string_id)
|
||||
|
||||
# ObjectId to String (for JSON responses)
|
||||
string_id = str(oid)
|
||||
|
||||
# Check if valid ObjectId string
|
||||
try:
|
||||
oid = ObjectId(potential_string)
|
||||
except:
|
||||
# Invalid ObjectId
|
||||
pass
|
||||
```
|
||||
|
||||
### Datetime
|
||||
|
||||
**MongoDB Storage:** ISODate (UTC)
|
||||
**Python Type:** `datetime.datetime`
|
||||
**JSON Representation:** ISO 8601 string
|
||||
|
||||
**Conversion:**
|
||||
|
||||
```python
|
||||
from datetime import datetime
|
||||
|
||||
# Create UTC datetime
|
||||
now = datetime.utcnow()
|
||||
|
||||
# ISO string to datetime
|
||||
dt = datetime.fromisoformat(iso_string.replace("Z", "+00:00"))
|
||||
|
||||
# Datetime to ISO string
|
||||
iso_string = dt.isoformat()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Migration from Old Schema
|
||||
|
||||
### What Changed
|
||||
|
||||
| Aspect | Old Schema | New Schema |
|
||||
| ------------ | ----------------------- | ------------------------------ |
|
||||
| Users | Many per email possible | One per email (unique) |
|
||||
| User \_id | ObjectId (correct) | ObjectId (unchanged) |
|
||||
| Entry userId | String | ObjectId |
|
||||
| Entry date | Only `createdAt` | `createdAt` + `entryDate` |
|
||||
| Encryption | Not supported | Metadata in `encryption` field |
|
||||
| Settings | Separate collection | Merged into `users.theme` |
|
||||
| Indexes | None | Comprehensive indexes |
|
||||
|
||||
### Migration Steps
|
||||
|
||||
See [MIGRATION_GUIDE.md](./MIGRATION_GUIDE.md) for detailed instructions.
|
||||
|
||||
**Quick Summary:**
|
||||
|
||||
```bash
|
||||
# 1. Backup database
|
||||
mongodump --db grateful_journal --out ./backup
|
||||
|
||||
# 2. Run migration script
|
||||
python backend/scripts/migrate_data.py
|
||||
|
||||
# 3. Create indexes
|
||||
python backend/scripts/create_indexes.py
|
||||
|
||||
# 4. Verify data
|
||||
python backend/scripts/verify_schema.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Security
|
||||
|
||||
### User Isolation
|
||||
|
||||
- All entry queries filter by `userId` to ensure users only access their own data
|
||||
- Frontend enforces user_id matching via Firebase auth token
|
||||
- Backend validates ObjectId conversions
|
||||
|
||||
### Encryption Ready
|
||||
|
||||
- `entries.encryption` metadata prepares schema for future client-side encryption
|
||||
- Server stores encrypted content as-is without decryption
|
||||
- Client responsible for IV, algorithm, and decryption keys
|
||||
|
||||
### Indexes & Performance
|
||||
|
||||
- Compound indexes prevent full collection scans
|
||||
- Unique email index prevents user confusion
|
||||
- Pagination support prevents memory overload
|
||||
|
||||
---
|
||||
|
||||
## Backup & Recovery
|
||||
|
||||
### Backup
|
||||
|
||||
```bash
|
||||
# Full database
|
||||
mongodump --db grateful_journal --out ./backup-$(date +%Y%m%d-%H%M%S)
|
||||
|
||||
# Specific collection
|
||||
mongodump --db grateful_journal --collection entries --out ./backup-entries
|
||||
```
|
||||
|
||||
### Restore
|
||||
|
||||
```bash
|
||||
# Full database
|
||||
mongorestore --db grateful_journal ./backup-2026-03-05-120000
|
||||
|
||||
# Specific collection
|
||||
mongorestore --db grateful_journal ./backup-entries
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## FAQ
|
||||
|
||||
### Q: Can I change the entryDate of an entry?
|
||||
|
||||
**A:** Yes. Send a PUT request with `entryDate` in the body. The entry will be re-indexed for calendar queries.
|
||||
|
||||
### Q: How do I encrypt entry content?
|
||||
|
||||
**A:**
|
||||
|
||||
1. Client encrypts content client-side using a key (not transmitted)
|
||||
2. Client sends encrypted content + metadata (iv, algorithm)
|
||||
3. Server stores content + encryption metadata as-is
|
||||
4. On retrieval, client decrypts using stored IV and local key
|
||||
|
||||
### Q: What if I have duplicate users?
|
||||
|
||||
**A:** Run the migration script:
|
||||
|
||||
```bash
|
||||
python backend/scripts/migrate_data.py
|
||||
```
|
||||
|
||||
It detects duplicates, keeps the oldest, and consolidates entries.
|
||||
|
||||
### Q: Should I paginate entries?
|
||||
|
||||
**A:** Yes. Use `skip` and `limit` to prevent loading thousands of entries:
|
||||
|
||||
```
|
||||
GET /api/entries/{user_id}?skip=0&limit=50
|
||||
```
|
||||
|
||||
### Q: How do I query entries by date range?
|
||||
|
||||
**A:** Use the calendar endpoint or build a query:
|
||||
|
||||
```python
|
||||
db.entries.find({
|
||||
"userId": oid,
|
||||
"entryDate": {
|
||||
"$gte": start_date,
|
||||
"$lt": end_date
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [FastAPI Backend Routes](../routers/)
|
||||
- [Pydantic Models](../models.py)
|
||||
- [Migration Script](../scripts/migrate_data.py)
|
||||
- [Index Creation Script](../scripts/create_indexes.py)
|
||||
- [MongoDB Documentation](https://docs.mongodb.com/)
|
||||
|
||||
---
|
||||
|
||||
_For questions or issues, refer to the project README or open an issue on GitHub._
|
||||
Binary file not shown.
Reference in New Issue
Block a user