mongog setup

This commit is contained in:
2026-03-04 12:23:13 +05:30
parent bed32863da
commit a9eaa7599c
32 changed files with 2577 additions and 670 deletions

6
backend/.env.example Normal file
View File

@@ -0,0 +1,6 @@
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB_NAME=grateful_journal
API_PORT=8001
ENVIRONMENT=development
FRONTEND_URL=http://localhost:8000

88
backend/README.md Normal file
View File

@@ -0,0 +1,88 @@
# Grateful Journal Backend API
FastAPI backend for Grateful Journal - a private-first gratitude journaling app.
**Port:** 8001
**API Docs:** http://localhost:8001/docs
## Quick Start
### 1. Prerequisites
- MongoDB running on `mongodb://localhost:27017`
- Python 3.9+
See [MongoDB Setup Guide](../docs/MONGODB_SETUP.md) for installation.
### 2. Install & Run
```bash
# Create virtual environment
python3 -m venv venv
source venv/bin/activate # macOS/Linux
# Install dependencies
pip install -r requirements.txt
# Run API
python main.py
```
API starts on http://0.0.0.0:8001
### 3. Environment Variables
Copy `.env.example` to `.env`. Defaults work for local dev:
```env
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB_NAME=grateful_journal
API_PORT=8001
ENVIRONMENT=development
FRONTEND_URL=http://localhost:8000
```
## Architecture
- **`main.py`** — FastAPI app, CORS, route registration, lifespan events
- **`config.py`** — Settings management (environment variables)
- **`db.py`** — MongoDB connection (singleton pattern)
- **`models.py`** — Pydantic data models
- **`routers/`** — API endpoints
- `users.py` — User registration, profile updates, deletion
- `entries.py` — Journal entry CRUD, date filtering
## API Endpoints
### Users
```
POST /api/users/register Register user (after Firebase auth)
GET /api/users/by-email/{email} Get user by email
PUT /api/users/update/{user_id} Update user profile
DELETE /api/users/{user_id} Delete user & all data
```
### Entries
```
POST /api/entries/{user_id} Create new entry
GET /api/entries/{user_id} List entries (paginated)
GET /api/entries/{user_id}/{entry_id} Get single entry
PUT /api/entries/{user_id}/{entry_id} Update entry
DELETE /api/entries/{user_id}/{entry_id} Delete entry
GET /api/entries/{user_id}/date/{date} Get entries by date
```
## Authentication
- Frontend authenticates via **Firebase Google Auth**
- User ID is passed in URL path (no token validation yet; implementation depends on frontend requirements)
- Optional: Add Firebase token verification in middleware later
## Development Notes
- **CORS** enabled for `localhost:8000`
- **Async/await** used throughout for scalability
- **Pydantic** models for request/response validation
- **MongoDB** auto-creates collections on first write

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

19
backend/config.py Normal file
View File

@@ -0,0 +1,19 @@
from pydantic_settings import BaseSettings
from functools import lru_cache
class Settings(BaseSettings):
mongodb_uri: str = "mongodb://localhost:27017"
mongodb_db_name: str = "grateful_journal"
api_port: int = 8001
environment: str = "development"
frontend_url: str = "http://localhost:8000"
class Config:
env_file = ".env"
case_sensitive = False
@lru_cache()
def get_settings():
return Settings()

31
backend/db.py Normal file
View File

@@ -0,0 +1,31 @@
from pymongo import MongoClient
from config import get_settings
from typing import Optional
class MongoDB:
client: Optional[MongoClient] = None
db = None
@staticmethod
def connect_db():
settings = get_settings()
MongoDB.client = MongoClient(settings.mongodb_uri)
MongoDB.db = MongoDB.client[settings.mongodb_db_name]
print(f"✓ Connected to MongoDB: {settings.mongodb_db_name}")
@staticmethod
def close_db():
if MongoDB.client:
MongoDB.client.close()
print("✓ Disconnected from MongoDB")
@staticmethod
def get_db():
return MongoDB.db
# Get database instance
def get_database():
return MongoDB.get_db()

65
backend/main.py Normal file
View File

@@ -0,0 +1,65 @@
from fastapi import FastAPI, HTTPException, Depends
from fastapi.middleware.cors import CORSMiddleware
from db import MongoDB, get_database
from config import get_settings
from routers import entries, users
from contextlib import asynccontextmanager
settings = get_settings()
@asynccontextmanager
async def lifespan(app: FastAPI):
# Startup
MongoDB.connect_db()
yield
# Shutdown
MongoDB.close_db()
app = FastAPI(
title="Grateful Journal API",
description="Backend API for Grateful Journal - private journaling app",
version="0.1.0",
lifespan=lifespan
)
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=[settings.frontend_url,
"http://localhost:8000", "http://127.0.0.1:8000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Include routers
app.include_router(users.router, prefix="/api/users", tags=["users"])
app.include_router(entries.router, prefix="/api/entries", tags=["entries"])
@app.get("/health")
async def health_check():
return {
"status": "ok",
"environment": settings.environment,
"api_version": "0.1.0"
}
@app.get("/")
async def root():
return {
"message": "Grateful Journal API",
"version": "0.1.0",
"docs": "/docs"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(
"main:app",
host="0.0.0.0",
port=settings.api_port,
reload=settings.environment == "development"
)

84
backend/models.py Normal file
View File

@@ -0,0 +1,84 @@
from pydantic import BaseModel, Field
from datetime import datetime
from typing import Optional, List
from enum import Enum
# ========== User Models ==========
class UserCreate(BaseModel):
email: str
displayName: Optional[str] = None
photoURL: Optional[str] = None
class UserUpdate(BaseModel):
displayName: Optional[str] = None
photoURL: Optional[str] = None
theme: Optional[str] = None
class User(BaseModel):
id: str
email: str
displayName: Optional[str] = None
photoURL: Optional[str] = None
createdAt: datetime
updatedAt: datetime
theme: Optional[str] = "light"
# ========== Journal Entry Models ==========
class MoodEnum(str, Enum):
happy = "happy"
sad = "sad"
neutral = "neutral"
anxious = "anxious"
grateful = "grateful"
class JournalEntryCreate(BaseModel):
title: str
content: str
mood: Optional[MoodEnum] = None
tags: Optional[List[str]] = None
isPublic: Optional[bool] = False
class JournalEntryUpdate(BaseModel):
title: Optional[str] = None
content: Optional[str] = None
mood: Optional[MoodEnum] = None
tags: Optional[List[str]] = None
isPublic: Optional[bool] = None
class JournalEntry(BaseModel):
id: str
userId: str
title: str
content: str
mood: Optional[MoodEnum] = None
tags: Optional[List[str]] = None
isPublic: bool = False
createdAt: datetime
updatedAt: datetime
# ========== Settings Models ==========
class UserSettingsUpdate(BaseModel):
notifications: Optional[bool] = None
emailNotifications: Optional[bool] = None
theme: Optional[str] = None
language: Optional[str] = None
class UserSettings(BaseModel):
userId: str
notifications: bool = True
emailNotifications: bool = False
theme: str = "light"
language: str = "en"
updatedAt: datetime

8
backend/requirements.txt Normal file
View File

@@ -0,0 +1,8 @@
fastapi==0.104.1
uvicorn==0.24.0
pymongo==4.6.0
pydantic==2.5.0
python-dotenv==1.0.0
pydantic-settings==2.1.0
python-multipart==0.0.6
cors==1.0.1

View File

@@ -0,0 +1 @@
# Routers package

Binary file not shown.

Binary file not shown.

Binary file not shown.

165
backend/routers/entries.py Normal file
View File

@@ -0,0 +1,165 @@
"""Journal entry routes"""
from fastapi import APIRouter, HTTPException
from db import get_database
from models import JournalEntryCreate, JournalEntryUpdate
from datetime import datetime
from typing import List
from bson import ObjectId
router = APIRouter()
@router.post("/{user_id}", response_model=dict)
async def create_entry(user_id: str, entry_data: JournalEntryCreate):
"""Create a new journal entry"""
db = get_database()
try:
entry_doc = {
"userId": user_id,
"title": entry_data.title,
"content": entry_data.content,
"mood": entry_data.mood,
"tags": entry_data.tags or [],
"isPublic": entry_data.isPublic,
"createdAt": datetime.utcnow(),
"updatedAt": datetime.utcnow()
}
result = db.entries.insert_one(entry_doc)
entry_doc["id"] = str(result.inserted_id)
return {
"id": entry_doc["id"],
"message": "Entry created successfully"
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{user_id}")
async def get_user_entries(user_id: str, limit: int = 50, skip: int = 0):
"""Get all entries for a user (paginated, most recent first)"""
db = get_database()
try:
entries = list(
db.entries.find(
{"userId": user_id}
).sort("createdAt", -1).skip(skip).limit(limit)
)
for entry in entries:
entry["id"] = str(entry["_id"])
del entry["_id"]
total = db.entries.count_documents({"userId": user_id})
return {
"entries": entries,
"total": total,
"skip": skip,
"limit": limit
}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{user_id}/{entry_id}")
async def get_entry(user_id: str, entry_id: str):
"""Get a specific entry"""
db = get_database()
try:
entry = db.entries.find_one({
"_id": ObjectId(entry_id),
"userId": user_id
})
if not entry:
raise HTTPException(status_code=404, detail="Entry not found")
entry["id"] = str(entry["_id"])
del entry["_id"]
return entry
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.put("/{user_id}/{entry_id}")
async def update_entry(user_id: str, entry_id: str, entry_data: JournalEntryUpdate):
"""Update a journal entry"""
db = get_database()
try:
update_data = entry_data.model_dump(exclude_unset=True)
update_data["updatedAt"] = datetime.utcnow()
result = db.entries.update_one(
{
"_id": ObjectId(entry_id),
"userId": user_id
},
{"$set": update_data}
)
if result.matched_count == 0:
raise HTTPException(status_code=404, detail="Entry not found")
return {"message": "Entry updated successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{user_id}/{entry_id}")
async def delete_entry(user_id: str, entry_id: str):
"""Delete a journal entry"""
db = get_database()
try:
result = db.entries.delete_one({
"_id": ObjectId(entry_id),
"userId": user_id
})
if result.deleted_count == 0:
raise HTTPException(status_code=404, detail="Entry not found")
return {"message": "Entry deleted successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/{user_id}/date/{date_str}")
async def get_entries_by_date(user_id: str, date_str: str):
"""Get entries for a specific date (format: YYYY-MM-DD)"""
db = get_database()
try:
from datetime import datetime as dt
# Parse date
target_date = dt.strptime(date_str, "%Y-%m-%d")
next_date = dt.fromtimestamp(target_date.timestamp() + 86400)
entries = list(
db.entries.find({
"userId": user_id,
"createdAt": {
"$gte": target_date,
"$lt": next_date
}
}).sort("createdAt", -1)
)
for entry in entries:
entry["id"] = str(entry["_id"])
del entry["_id"]
return {"entries": entries, "date": date_str}
except ValueError:
raise HTTPException(
status_code=400, detail="Invalid date format. Use YYYY-MM-DD")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))

99
backend/routers/users.py Normal file
View File

@@ -0,0 +1,99 @@
"""User management routes"""
from fastapi import APIRouter, HTTPException, Header
from pymongo.errors import DuplicateKeyError
from db import get_database
from models import UserCreate, UserUpdate, User
from datetime import datetime
from typing import Optional, List
router = APIRouter()
@router.post("/register", response_model=dict)
async def register_user(user_data: UserCreate):
"""
Register a new user (called after Firebase Google Auth)
Stores user profile in MongoDB
"""
db = get_database()
try:
user_doc = {
"email": user_data.email,
"displayName": user_data.displayName or user_data.email.split("@")[0],
"photoURL": user_data.photoURL,
"createdAt": datetime.utcnow(),
"updatedAt": datetime.utcnow(),
"theme": "light"
}
result = db.users.insert_one(user_doc)
user_doc["id"] = str(result.inserted_id)
return {
"id": user_doc["id"],
"email": user_doc["email"],
"displayName": user_doc["displayName"],
"message": "User registered successfully"
}
except DuplicateKeyError:
raise HTTPException(status_code=400, detail="User already exists")
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.get("/by-email/{email}", response_model=dict)
async def get_user_by_email(email: str):
"""Get user profile by email (called after Firebase Auth)"""
db = get_database()
user = db.users.find_one({"email": email})
if not user:
raise HTTPException(status_code=404, detail="User not found")
user["id"] = str(user["_id"])
return user
@router.put("/update/{user_id}", response_model=dict)
async def update_user(user_id: str, user_data: UserUpdate):
"""Update user profile"""
db = get_database()
from bson import ObjectId
try:
update_data = user_data.model_dump(exclude_unset=True)
update_data["updatedAt"] = datetime.utcnow()
result = db.users.update_one(
{"_id": ObjectId(user_id)},
{"$set": update_data}
)
if result.matched_count == 0:
raise HTTPException(status_code=404, detail="User not found")
return {"message": "User updated successfully"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
@router.delete("/{user_id}")
async def delete_user(user_id: str):
"""Delete user account and all associated data"""
db = get_database()
from bson import ObjectId
try:
# Delete user
db.users.delete_one({"_id": ObjectId(user_id)})
# Delete all entries by user
db.entries.delete_many({"userId": user_id})
# Delete user settings
db.settings.delete_one({"userId": user_id})
return {"message": "User and associated data deleted"}
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))