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

View File

@@ -1,162 +1,112 @@
<!-- BMAD:START --> # Grateful Journal — Project Instructions for Copilot
# BMAD Method — Project Instructions ## Project Overview
## Project Configuration **Grateful Journal** — A minimal, private-first gratitude journaling web app. Three main pages (Write, History/calendar, Settings/profile) plus Google auth. No feeds or algorithms; privacy by design with client-side encryption; daily use, even one sentence.
- **Project**: grateful-journal **User:** Jeet
- **User**: Jeet
- **Communication Language**: English
- **Document Output Language**: English
- **User Skill Level**: intermediate
- **Output Folder**: {project-root}/\_bmad-output
- **Planning Artifacts**: {project-root}/\_bmad-output/planning-artifacts
- **Implementation Artifacts**: {project-root}/\_bmad-output/implementation-artifacts
- **Project Knowledge**: {project-root}/docs
## BMAD Runtime Structure
- **Agent definitions**: `_bmad/bmm/agents/` (BMM module) and `_bmad/core/agents/` (core)
- **Workflow definitions**: `_bmad/bmm/workflows/` (organized by phase)
- **Core tasks**: `_bmad/core/tasks/` (help, editorial review, indexing, sharding, adversarial review)
- **Core workflows**: `_bmad/core/workflows/` (brainstorming, party-mode, advanced-elicitation)
- **Workflow engine**: `_bmad/core/tasks/workflow.xml` (executes YAML-based workflows)
- **Module configuration**: `_bmad/bmm/config.yaml`
- **Core configuration**: `_bmad/core/config.yaml`
- **Agent manifest**: `_bmad/_config/agent-manifest.csv`
- **Workflow manifest**: `_bmad/_config/workflow-manifest.csv`
- **Help manifest**: `_bmad/_config/bmad-help.csv`
- **Agent memory**: `_bmad/_memory/`
## Key Conventions
- Always load `_bmad/bmm/config.yaml` before any agent activation or workflow execution
- Store all config fields as session variables: `{user_name}`, `{communication_language}`, `{output_folder}`, `{planning_artifacts}`, `{implementation_artifacts}`, `{project_knowledge}`
- MD-based workflows execute directly — load and follow the `.md` file
- YAML-based workflows require the workflow engine — load `workflow.xml` first, then pass the `.yaml` config
- Follow step-based workflow execution: load steps JIT, never multiple at once
- Save outputs after EACH step when using the workflow engine
- The `{project-root}` variable resolves to the workspace root at runtime
## Available Agents
| Agent | Persona | Title | Capabilities |
| ------------------- | ----------- | -------------------------------------------------------------------- | ---------------------------------------------------------------------------------------- |
| bmad-master | BMad Master | BMad Master Executor, Knowledge Custodian, and Workflow Orchestrator | runtime resource management, workflow orchestration, task execution, knowledge custodian |
| analyst | Mary | Business Analyst | market research, competitive analysis, requirements elicitation, domain expertise |
| architect | Winston | Architect | distributed systems, cloud infrastructure, API design, scalable patterns |
| dev | Amelia | Developer Agent | story execution, test-driven development, code implementation |
| pm | John | Product Manager | PRD creation, requirements discovery, stakeholder alignment, user interviews |
| qa | Quinn | QA Engineer | test automation, API testing, E2E testing, coverage analysis |
| quick-flow-solo-dev | Barry | Quick Flow Solo Dev | rapid spec creation, lean implementation, minimum ceremony |
| sm | Bob | Scrum Master | sprint planning, story preparation, agile ceremonies, backlog management |
| tech-writer | Paige | Technical Writer | documentation, Mermaid diagrams, standards compliance, concept explanation |
| ux-designer | Sally | UX Designer | user research, interaction design, UI patterns, experience strategy |
## Slash Commands
When the user's message starts with a `/bmad-` command (with or without additional text), execute it by following the steps below. Always load `_bmad/bmm/config.yaml` first and store config as session variables, then load and follow the referenced file exactly.
### Workflow Commands
| Command | Action |
| ------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------- |
| `/bmad-help` | Load and follow `_bmad/core/tasks/help.md` |
| `/bmad-brainstorming` | Load and follow `_bmad/core/workflows/brainstorming/workflow.md` |
| `/bmad-party-mode` | Load and follow `_bmad/core/workflows/party-mode/workflow.md` |
| `/bmad-bmm-create-product-brief` | Load and follow `_bmad/bmm/workflows/1-analysis/create-product-brief/workflow.md` |
| `/bmad-bmm-market-research` | Load and follow `_bmad/bmm/workflows/1-analysis/research/workflow-market-research.md` |
| `/bmad-bmm-domain-research` | Load and follow `_bmad/bmm/workflows/1-analysis/research/workflow-domain-research.md` |
| `/bmad-bmm-technical-research` | Load and follow `_bmad/bmm/workflows/1-analysis/research/workflow-technical-research.md` |
| `/bmad-bmm-create-prd` | Load and follow `_bmad/bmm/workflows/2-plan-workflows/create-prd/workflow-create-prd.md` |
| `/bmad-bmm-edit-prd` | Load and follow `_bmad/bmm/workflows/2-plan-workflows/create-prd/workflow-edit-prd.md` |
| `/bmad-bmm-validate-prd` | Load and follow `_bmad/bmm/workflows/2-plan-workflows/create-prd/workflow-validate-prd.md` |
| `/bmad-bmm-create-ux-design` | Load and follow `_bmad/bmm/workflows/2-plan-workflows/create-ux-design/workflow.md` |
| `/bmad-bmm-create-architecture` | Load and follow `_bmad/bmm/workflows/3-solutioning/create-architecture/workflow.md` |
| `/bmad-bmm-create-epics-and-stories` | Load and follow `_bmad/bmm/workflows/3-solutioning/create-epics-and-stories/workflow.md` |
| `/bmad-bmm-check-implementation-readiness` | Load and follow `_bmad/bmm/workflows/3-solutioning/check-implementation-readiness/workflow.md` |
| `/bmad-bmm-sprint-planning` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/4-implementation/sprint-planning/workflow.yaml` |
| `/bmad-bmm-sprint-status` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/4-implementation/sprint-status/workflow.yaml` |
| `/bmad-bmm-create-story` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/4-implementation/create-story/workflow.yaml` |
| `/bmad-bmm-dev-story` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/4-implementation/dev-story/workflow.yaml` |
| `/bmad-bmm-code-review` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/4-implementation/code-review/workflow.yaml` |
| `/bmad-bmm-retrospective` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/4-implementation/retrospective/workflow.yaml` |
| `/bmad-bmm-correct-course` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/4-implementation/correct-course/workflow.yaml` |
| `/bmad-bmm-qa-automate` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/qa/automate/workflow.yaml` |
| `/bmad-bmm-quick-spec` | Load and follow `_bmad/bmm/workflows/bmad-quick-flow/quick-spec/workflow.md` |
| `/bmad-bmm-quick-dev` | Load and follow `_bmad/bmm/workflows/bmad-quick-flow/quick-dev/workflow.md` |
| `/bmad-bmm-document-project` | Load `_bmad/core/tasks/workflow.xml` (engine), then execute `_bmad/bmm/workflows/document-project/workflow.yaml` |
| `/bmad-bmm-generate-project-context` | Load and follow `_bmad/bmm/workflows/generate-project-context/workflow.md` |
| `/bmad-index-docs` | Load and execute `_bmad/core/tasks/index-docs.xml` |
| `/bmad-shard-doc` | Load and execute `_bmad/core/tasks/shard-doc.xml` |
| `/bmad-editorial-review-prose` | Load and execute `_bmad/core/tasks/editorial-review-prose.xml` |
| `/bmad-editorial-review-structure` | Load and execute `_bmad/core/tasks/editorial-review-structure.xml` |
| `/bmad-review-adversarial-general` | Load and execute `_bmad/core/tasks/review-adversarial-general.xml` |
| `/bmad-bmm-write-document` | Load `_bmad/bmm/agents/tech-writer/tech-writer.md`, activate Paige persona, execute Write Document (WD) |
| `/bmad-bmm-update-standards` | Load `_bmad/bmm/agents/tech-writer/tech-writer.md`, activate Paige persona, execute Update Standards (US) |
| `/bmad-bmm-mermaid-generate` | Load `_bmad/bmm/agents/tech-writer/tech-writer.md`, activate Paige persona, execute Mermaid Generate (MG) |
| `/bmad-bmm-validate-document` | Load `_bmad/bmm/agents/tech-writer/tech-writer.md`, activate Paige persona, execute Validate Document (VD) |
| `/bmad-bmm-explain-concept` | Load `_bmad/bmm/agents/tech-writer/tech-writer.md`, activate Paige persona, execute Explain Concept (EC) |
### Agent Activator Commands
| Command | Agent File |
| --------------------------- | --------------------------------------------- |
| `/bmad-bmad-master` | `_bmad/core/agents/bmad-master.md` |
| `/bmad-analyst` | `_bmad/bmm/agents/analyst.md` |
| `/bmad-architect` | `_bmad/bmm/agents/architect.md` |
| `/bmad-dev` | `_bmad/bmm/agents/dev.md` |
| `/bmad-pm` | `_bmad/bmm/agents/pm.md` |
| `/bmad-qa` | `_bmad/bmm/agents/qa.md` |
| `/bmad-quick-flow-solo-dev` | `_bmad/bmm/agents/quick-flow-solo-dev.md` |
| `/bmad-sm` | `_bmad/bmm/agents/sm.md` |
| `/bmad-tech-writer` | `_bmad/bmm/agents/tech-writer/tech-writer.md` |
| `/bmad-ux-designer` | `_bmad/bmm/agents/ux-designer.md` |
For agent commands: load the agent file, follow ALL activation instructions, display the welcome/greeting, present the numbered menu, and wait for user input.
--- ---
## Project Context Maintenance (Critical) ## Technology Stack & Versions
**Purpose:** `project-context.md` serves as the single source of truth for the project state, implementation patterns, and active features. | Layer | Technology | Notes |
| -------- | -------------------- | ----------------------------------------------------- |
| Frontend | React 19, TypeScript | Vite 7 build; port 8000 |
| Routing | react-router-dom 7 | Routes: `/`, `/history`, `/settings`, `/login` |
| Auth | Firebase 12 | Google sign-in only (no database) |
| Styling | Plain CSS | `src/index.css` (globals), `src/App.css` (components) |
| Backend | FastAPI 0.104 | Python; port 8001; modular routes |
| Database | MongoDB 6.x | Local instance; collections: users, entries, settings |
### When to Update `project-context.md` ---
1. **After each feature implementation** — Update relevant sections: ## Critical Implementation Rules
- Technology stack (versions)
- Critical implementation rules (new patterns or constraints)
- File layout (new files/folders)
- Known issues or deferred work
2. **When user announces new features** — Immediately add to the file: ### Frontend
- Feature description in project overview
- Any new technology or dependency
- Updated file structure (if applicable)
- New implementation rules or conventions
- Status of feature (e.g., _In Progress_, _Planned_, _Complete_)
3. **Before starting implementation** — Ensure the file reflects current state - **Colour palette (Coolors):** Use CSS variables from `src/index.css`. Primary green `#1be62c`, background soft `#f1eee1`, surface `#ffffff`, accent light `#cff2dc`, accent bright `#c3fd2f`. Do not introduce new palette colours without reason.
- Validate that all recent changes are documented - **Layout:** Responsive for all screens. Breakpoints: `--bp-sm` 480px, `--bp-md` 768px, `--bp-lg` 1024px, `--bp-xl` 1280px. On laptop (1024px+), page is single-screen 100vh — no vertical scroll; fonts and spacing scaled so content fits one viewport.
- Flag any deferred work or known blockers - **Touch targets:** Minimum 44px (`--touch-min`) on interactive elements for small screens.
- Cross-reference with current codebase - **Safe areas:** Use `env(safe-area-inset-*)` for padding where the app can sit under notches or system UI. Viewport meta includes `viewport-fit=cover`.
- **Structure:** Main app layout: page container → header + main content + fixed `BottomNav`. Content max-width `min(680px, 100%)` (or `--content-max` 720px where appropriate).
### Format Rules ### Backend
- **Last updated:** Always update the timestamp at the bottom: `_Last updated: [YYYY-MM-DD]_` - **Framework:** FastAPI. APIs in Python only.
- **Feature status markers:** Use `_Planning_`, `_In Progress_`, `_Complete)_`, `_Deferred_` - **Modularity:** Separate file per route. Each feature (users, entries) has its own router module.
- **Keep it concise:** One-line descriptions; refer to external docs for details - **Database:** MongoDB. Setup instructions in `docs/MONGODB_SETUP.md`.
- **Match codebase reality:** If the code differs from the document, the document is wrong—fix it immediately - **Port:** 8001 (backend); 8000 (frontend). CORS configured between them.
- **Authentication:** Relies on Firebase Google Auth token from frontend (passed in Authorization header).
### Example Updates ### Conventions
When user says: "Add dark mode toggle to settings" - **Fonts:** Inter for UI, Playfair Display for headings/editorial, Lora for body/entry text. Loaded via Google Fonts in `index.html`.
- **Naming:** CSS uses BEM-like class names (e.g. `.journal-card`, `.journal-prompt`). Keep the same pattern for new components.
- **Build:** Fixing the current TypeScript/ESLint build errors is deferred to a later step; do not assume a clean build when adding features.
```markdown ---
| Feature | Status | Notes |
| Dark mode toggle | In Progress | Added to SettingsPage; CSS variables predefined | ## File Layout (Reference)
```
src/ # Frontend
App.tsx, App.css # Root layout, routes, global page styles
index.css # Resets, :root vars, base typography
main.tsx
pages/ # HomePage, HistoryPage, SettingsPage, LoginPage
components/ # BottomNav, LoginCard, GoogleSignInButton, ProtectedRoute
contexts/ # AuthContext (Firebase Google Auth)
lib/
firebase.ts # Firebase auth config (Firestore removed)
backend/ # FastAPI backend (Port 8001)
main.py # FastAPI app, CORS, routes, lifespan
config.py # Settings, environment variables
db.py # MongoDB connection manager
models.py # Pydantic models (User, JournalEntry, Settings)
requirements.txt # Python dependencies
.env.example # Environment variables template
routers/
users.py # User registration, update, delete endpoints
entries.py # Entry CRUD, date filtering endpoints
``` ```
When implementation is done: Update to `Complete` and add any implementation notes. ---
<!-- BMAD:END --> ## Recent Changes & Status
### Port Configuration (Updated)
✅ Frontend port changed to **8000** (was 5173)
✅ Backend port remains **8001**
✅ CORS configuration updated in FastAPI
✅ Vite config updated with server port 8000
### Backend Setup (Completed)
✅ FastAPI backend initialized (port 8001)
✅ MongoDB connection configured (local instance)
✅ Pydantic models for User, JournalEntry, UserSettings
✅ Route structure: `/api/users/*` and `/api/entries/*`
✅ CORS enabled for frontend (localhost:8000)
✅ Firestore database files removed (`firestoreService.ts`, `firestoreConfig.ts`)
✅ Firebase authentication kept (Google sign-in only)
### API Ready
- User registration, profile updates, deletion
- Entry CRUD (create, read, update, delete)
- Entry filtering by date
- Pagination support
### Next Steps (Implementation)
🔄 Connect frontend React app to backend APIs
🔄 Pass Firebase user ID from frontend to backend
🔄 Integrate Auth context with entry save/load
🔄 Add optional: Firebase token verification in backend middleware
---
_Last updated: 2026-03-04_

241
BACKEND_QUICKSTART.md Normal file
View File

@@ -0,0 +1,241 @@
# MongoDB & FastAPI Backend Setup - Quick Start
## What's Been Set Up
**Backend directory structure** (`/backend`)
**FastAPI application** (main.py with routes, CORS, lifecycle)
**MongoDB connection** (config.py, db.py)
**Pydantic models** (User, JournalEntry, UserSettings)
**API routes** (users.py, entries.py)
**Environment configuration** (.env.example)
**Documentation** (README.md, MONGODB_SETUP.md)
**Firebase auth preserved** (Google sign-in, no Firestore)
---
## How to Start MongoDB
### 1. Install MongoDB (if not already installed)
```bash
# macOS
brew tap mongodb/brew
brew install mongodb-community
# Linux (Ubuntu)
curl -fsSL https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" \
| sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
# Windows: Download from https://www.mongodb.com/try/download/community
```
### 2. Start MongoDB Service
```bash
# macOS
brew services start mongodb-community
# Linux
sudo systemctl start mongod
# Windows
net start MongoDB
# Verify it's running
mongosh # Should connect successfully
```
---
## How to Start FastAPI Backend
### 1. Navigate to backend directory
```bash
cd backend
```
### 2. Create Python virtual environment
```bash
python3 -m venv venv
# Activate it
source venv/bin/activate # macOS/Linux
# or
venv\Scripts\activate # Windows
```
### 3. Install dependencies
```bash
pip install -r requirements.txt
```
### 4. Configure environment (optional)
```bash
cp .env.example .env
# Edit .env if you need to change defaults
```
### 5. Start the API server
```bash
python main.py
```
You should see:
```
✓ Connected to MongoDB: grateful_journal
INFO: Uvicorn running on http://0.0.0.0:8001
```
### 6. Access API Documentation
Open in browser:
- **Interactive API Docs**: http://localhost:8001/docs
- **Alternative Docs**: http://localhost:8001/redoc
- **Health Check**: http://localhost:8001/health
---
## Complete Startup (All Services)
### Terminal 1: Start MongoDB
```bash
brew services start mongodb-community
mongosh # Verify connection
```
### Terminal 2: Start Frontend
```bash
npm run dev -- --port 8000
```
### Terminal 3: Start Backend
```bash
cd backend
source venv/bin/activate
python main.py
```
Now you have:
- **Frontend**: http://localhost:8000
- **Backend**: http://localhost:8001
- **MongoDB**: localhost:27017
---
## API Endpoints Ready to Use
### User Management
- `POST /api/users/register` — Register user after Firebase auth
- `GET /api/users/by-email/{email}` — Fetch user profile
- `PUT /api/users/update/{user_id}` — Update profile
- `DELETE /api/users/{user_id}` — Delete account & data
### Journal Entries
- `POST /api/entries/{user_id}` — Create entry
- `GET /api/entries/{user_id}` — List entries (paginated)
- `GET /api/entries/{user_id}/{entry_id}` — Get 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/{YYYY-MM-DD}` — Entries by date
---
## Authentication Flow
1. **Frontend**: User clicks "Sign in with Google"
2. **Firebase**: Returns auth token + user info (email, displayName, photoURL)
3. **Frontend**: Calls `POST /api/users/register` with user data
4. **Backend**: Stores user in MongoDB, returns user ID
5. **Frontend**: Uses user ID for all subsequent API calls
---
## File Structure
```
grateful-journal/
├── backend/ # NEW: FastAPI backend
│ ├── main.py # FastAPI app
│ ├── config.py # Settings
│ ├── db.py # MongoDB connection
│ ├── models.py # Pydantic models
│ ├── requirements.txt # Python dependencies
│ ├── .env.example # Environment template
│ ├── README.md # Backend docs
│ └── routers/ # API routes
│ ├── users.py
│ └── entries.py
├── src/ # Frontend (existing)
│ └── lib/
│ └── firebase.ts # Auth only (Firestore removed)
├── docs/
│ ├── FIRESTORE_SETUP.md # (old, keep for reference)
│ └── MONGODB_SETUP.md # NEW: MongoDB setup guide
└── project-context.md # Updated with backend info
```
---
## Removed Files (Firestore)
-`src/lib/firestoreService.ts` (removed — use MongoDB API instead)
-`src/lib/firestoreConfig.ts` (removed — use MongoDB API instead)
---
## Next Steps
1. **Start MongoDB** (see above)
2. **Start FastAPI** (see above)
3. **Connect Frontend to Backend**
- Update `src/contexts/AuthContext.tsx` to call `/api/users/register`
- Create hooks to fetch/save entries from `/api/entries/*`
- Pass Firebase user ID to all API calls
4. **Test in API Docs** (http://localhost:8001/docs)
- Try creating a user
- Try creating an entry
- Try fetching entries
---
## Troubleshooting
**"MongoDB connection refused"**
- Is the service running? `brew services list` (macOS) or `systemctl status mongod` (Linux)
- Check port 27017 is free: `lsof -i :27017`
**"ModuleNotFoundError: pymongo"**
- Is venv activated? Run `. venv/bin/activate` again
- Did you run `pip install -r requirements.txt`?
**"CORS error in browser console"**
- Backend running on 8001? Check `http://localhost:8001/docs`
- Frontend URL in `.env`? Should be `http://localhost:8000`
---
For detailed setup instructions, see:
- [MONGODB_SETUP.md](./docs/MONGODB_SETUP.md) — Full MongoDB installation & configuration
- [backend/README.md](./backend/README.md) — Backend architecture & endpoints
- [project-context.md](./project-context.md) — Implementation rules & conventions

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

219
docs/MONGODB_SETUP.md Normal file
View File

@@ -0,0 +1,219 @@
# MongoDB Setup Guide for Grateful Journal
## Prerequisites
- MongoDB installed on your system
- Python 3.9+
- pip package manager
## Installation Steps
### 1. Install MongoDB
#### macOS (using Homebrew)
```bash
brew tap mongodb/brew
brew install mongodb-community
```
#### Linux (Ubuntu/Debian)
```bash
curl -fsSL https://www.mongodb.org/static/pgp/server-6.0.asc | sudo apt-key add -
echo "deb [ arch=amd64,arm64 ] https://repo.mongodb.org/apt/ubuntu focal/mongodb-org/6.0 multiverse" | sudo tee /etc/apt/sources.list.d/mongodb-org-6.0.list
sudo apt-get update
sudo apt-get install -y mongodb-org
```
#### Windows
Download and run the installer from [MongoDB Community Download](https://www.mongodb.com/try/download/community)
### 2. Start MongoDB Server
#### macOS / Linux
```bash
# Start as a service (recommended)
brew services start mongodb-community
# Or run directly
mongod --config /usr/local/etc/mongod.conf
```
#### Windows
MongoDB should run as a service. If not:
```bash
net start MongoDB
```
### 3. Verify MongoDB is Running
```bash
mongosh
```
If you see a connection prompt, MongoDB is running successfully.
### 4. Set up Python Backend Environment
Navigate to the backend directory:
```bash
cd backend
```
Create a virtual environment:
```bash
python3 -m venv venv
source venv/bin/activate # macOS/Linux
# or
venv\Scripts\activate # Windows
```
Install dependencies:
```bash
pip install -r requirements.txt
```
### 5. Configure Environment Variables
Copy the example env file:
```bash
cp .env.example .env
```
Edit `.env` with your settings (defaults work for local development):
```env
MONGODB_URI=mongodb://localhost:27017
MONGODB_DB_NAME=grateful_journal
API_PORT=8001
ENVIRONMENT=development
FRONTEND_URL=http://localhost:8000
```
### 6. Run the FastAPI Server
```bash
python main.py
```
You should see:
```
✓ Connected to MongoDB: grateful_journal
INFO: Uvicorn running on http://0.0.0.0:8001
```
### 7. Access API Documentation
Open your browser and go to:
- **Swagger UI**: http://localhost:8001/docs
- **ReDoc**: http://localhost:8001/redoc
- **Health Check**: http://localhost:8001/health
## MongoDB Collections Overview
The following collections will be created automatically on first write:
### `users`
Stores user profiles after Firebase Google Auth.
```json
{
"_id": ObjectId,
"email": "user@example.com",
"displayName": "John Doe",
"photoURL": "https://...",
"theme": "light",
"createdAt": ISODate,
"updatedAt": ISODate
}
```
### `entries`
Stores journal entries.
```json
{
"_id": ObjectId,
"userId": "user_id_string",
"title": "Today's thoughts",
"content": "Long-form journal content...",
"mood": "grateful",
"tags": ["reflection", "gratitude"],
"isPublic": false,
"createdAt": ISODate,
"updatedAt": ISODate
}
```
### `settings`
Stores user preferences and settings.
```json
{
"_id": ObjectId,
"userId": "user_id_string",
"notifications": true,
"emailNotifications": false,
"theme": "light",
"language": "en",
"updatedAt": ISODate
}
```
## API Endpoints
### Users
- `POST /api/users/register` — Register user after Firebase auth
- `GET /api/users/by-email/{email}` — Get user profile by email
- `PUT /api/users/update/{user_id}` — Update user profile
- `DELETE /api/users/{user_id}` — Delete user and associated data
### Entries
- `POST /api/entries/{user_id}` — Create new entry
- `GET /api/entries/{user_id}` — Get all entries (paginated)
- `GET /api/entries/{user_id}/{entry_id}` — Get specific 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_str}` — Get entries by date (YYYY-MM-DD)
## Troubleshooting
**MongoDB connection refused**
- Check that MongoDB service is running: `brew services list` (macOS)
- Verify port 27017 is not blocked
**ModuleNotFoundError: pymongo**
- Ensure virtual environment is activated
- Run `pip install -r requirements.txt` again
**CORS errors in frontend**
- Check `FRONTEND_URL` in `.env` matches your frontend URL
- Default allows http://localhost:8000
## Next Steps
Once MongoDB and FastAPI are running:
1. Frontend calls Firebase Google Auth
2. Frontend sends auth token to `/api/users/register`
3. Backend creates user in MongoDB
4. Frontend can now call `/api/entries/*` endpoints with user_id

View File

@@ -14,14 +14,14 @@ _This file contains critical rules and patterns that AI agents must follow when
## Technology stack & versions ## Technology stack & versions
| Layer | Technology | Notes | | Layer | Technology | Notes |
|-----------|--------------------------|--------------------------------------------| | -------- | -------------------- | ----------------------------------------------------- |
| Frontend | React 19, TypeScript | Vite 7 build | | Frontend | React 19, TypeScript | Vite 7 build; port 8000 |
| Routing | react-router-dom 7 | Routes: `/`, `/history`, `/settings`, `/login` | | Routing | react-router-dom 7 | Routes: `/`, `/history`, `/settings`, `/login` |
| Auth | Firebase 12 | Google sign-in only | | Auth | Firebase 12 | Google sign-in only (no database) |
| Styling | Plain CSS | `src/index.css` (globals), `src/App.css` (components) | | Styling | Plain CSS | `src/index.css` (globals), `src/App.css` (components) |
| Backend | _Planned_ Python/FastAPI | Modular: one file per page/function | | Backend | FastAPI 0.104 | Python; port 8001; modular routes |
| Database | _Planned_ MongoDB | To be set up after responsive UI | | Database | MongoDB 6.x | Local instance; collections: users, entries, settings |
--- ---
@@ -38,8 +38,10 @@ _This file contains critical rules and patterns that AI agents must follow when
### Backend (when implemented) ### Backend (when implemented)
- **Framework:** FastAPI. APIs in Python only. - **Framework:** FastAPI. APIs in Python only.
- **Modularity:** Separate file per page and its functions. Each app page (write, history, settings, auth) has its own backend module; avoid one monolithic API file. - **Modularity:** Separate file per route. Each feature (users, entries) has its own router module.
- **Database:** MongoDB. Setup comes after frontend responsive work. - **Database:** MongoDB. Setup instructions below.
- **Port:** 8001 (backend); 8000 (frontend). CORS configured between them.
- **Authentication:** Relies on Firebase Google Auth token from frontend (passed in Authorization header).
### Conventions ### Conventions
@@ -52,16 +54,80 @@ _This file contains critical rules and patterns that AI agents must follow when
## File layout (reference) ## File layout (reference)
``` ```
src/ src/ # Frontend
App.tsx, App.css # Root layout, routes, global page styles App.tsx, App.css # Root layout, routes, global page styles
index.css # Resets, :root vars, base typography index.css # Resets, :root vars, base typography
main.tsx main.tsx
pages/ # HomePage, HistoryPage, SettingsPage, LoginPage pages/ # HomePage, HistoryPage, SettingsPage, LoginPage
components/ # BottomNav, LoginCard, GoogleSignInButton, ProtectedRoute components/ # BottomNav, LoginCard, GoogleSignInButton, ProtectedRoute
contexts/ # AuthContext (Firebase) contexts/ # AuthContext (Firebase Google Auth)
lib/ # firebase.ts, firestoreService.ts lib/
firebase.ts # Firebase auth config (Firestore removed)
backend/ # FastAPI backend (Port 8001)
main.py # FastAPI app, CORS, routes, lifespan
config.py # Settings, environment variables
db.py # MongoDB connection manager
models.py # Pydantic models (User, JournalEntry, Settings)
requirements.txt # Python dependencies
.env.example # Environment variables template
routers/
users.py # User registration, update, delete endpoints
entries.py # Entry CRUD, date filtering endpoints
``` ```
--- ---
_Last updated from session context and codebase review._ _Last updated: 2026-03-04_
## Recent Changes & Status
### Port Configuration (Updated)
✅ Frontend port changed to **8000** (was 5173)
✅ Backend port remains **8001**
✅ CORS configuration updated in FastAPI
✅ Vite config updated with server port 8000
### Backend Setup (Completed)
✅ FastAPI backend initialized (port 8001)
✅ MongoDB connection configured (local instance)
✅ Pydantic models for User, JournalEntry, UserSettings
✅ Route structure: `/api/users/*` and `/api/entries/*`
✅ CORS enabled for frontend (localhost:8000)
✅ Firestore database files removed (`firestoreService.ts`, `firestoreConfig.ts`)
✅ Firebase authentication kept (Google sign-in only)
### API Ready
- User registration, profile updates, deletion
- Entry CRUD (create, read, update, delete)
- Entry filtering by date
- Pagination support
### Frontend-Backend Integration (Completed)
**API Service Layer** — Created `src/lib/api.ts` with all backend calls
**AuthContext Updated** — Now syncs users with MongoDB on login
- Auto-registers new users in MongoDB
- Fetches existing user profiles
- Provides `userId` (MongoDB ID) to all pages
**HomePage** — Entry creation via POST `/api/entries/{userId}`
- Save with success/error feedback
- Clears form after save
**HistoryPage** — Fetches entries via GET `/api/entries/{userId}`
- Calendar shows days with entries
- Lists recent entries with timestamps
- Filters by current month
**SettingsPage** — Updates user settings via PUT `/api/users/update/{userId}`
- Theme selector (light/dark) with MongoDB persistence
- Profile info from Firebase
### Next Steps (Implementation)
🔄 Add entry detail view / edit functionality
🔄 Firebase token verification in backend middleware
🔄 Search/filter entries by date range
🔄 Client-side encryption for entries

File diff suppressed because it is too large Load Diff

View File

@@ -13,12 +13,12 @@ import {
signOut as firebaseSignOut, signOut as firebaseSignOut,
type User, type User,
} from 'firebase/auth' } from 'firebase/auth'
import { auth, googleProvider, db } from '../lib/firebase' import { auth, googleProvider } from '../lib/firebase'
import { doc, setDoc } from 'firebase/firestore' import { registerUser, getUserByEmail } from '../lib/api'
import { COLLECTIONS } from '../lib/firestoreConfig'
type AuthContextValue = { type AuthContextValue = {
user: User | null user: User | null
userId: string | null
loading: boolean loading: boolean
signInWithGoogle: () => Promise<void> signInWithGoogle: () => Promise<void>
signOut: () => Promise<void> signOut: () => Promise<void>
@@ -28,21 +28,33 @@ const AuthContext = createContext<AuthContextValue | null>(null)
export function AuthProvider({ children }: { children: ReactNode }) { export function AuthProvider({ children }: { children: ReactNode }) {
const [user, setUser] = useState<User | null>(null) const [user, setUser] = useState<User | null>(null)
const [userId, setUserId] = useState<string | null>(null)
const [loading, setLoading] = useState(true) const [loading, setLoading] = useState(true)
// Save user info to Firestore when they authenticate // Register or fetch user from MongoDB
async function saveUserToFirestore(authUser: User) { async function syncUserWithDatabase(authUser: User) {
try { try {
const userRef = doc(db, COLLECTIONS.USERS, authUser.uid) const token = await authUser.getIdToken()
await setDoc(userRef, { const email = authUser.email!
id: authUser.uid,
email: authUser.email || '', // Try to get existing user
displayName: authUser.displayName || '', try {
photoURL: authUser.photoURL || '', const existingUser = await getUserByEmail(email, token)
lastLoginAt: Date.now(), setUserId(existingUser.id)
}, { merge: true }) } catch (error) {
// User doesn't exist, register them
const newUser = await registerUser(
{
email,
displayName: authUser.displayName || undefined,
photoURL: authUser.photoURL || undefined,
},
token
)
setUserId(newUser.id)
}
} catch (error) { } catch (error) {
console.error('Error saving user to Firestore:', error) console.error('Error syncing user with database:', error)
} }
} }
@@ -50,7 +62,9 @@ export function AuthProvider({ children }: { children: ReactNode }) {
const unsubscribe = onAuthStateChanged(auth, async (u) => { const unsubscribe = onAuthStateChanged(auth, async (u) => {
setUser(u) setUser(u)
if (u) { if (u) {
await saveUserToFirestore(u) await syncUserWithDatabase(u)
} else {
setUserId(null)
} }
setLoading(false) setLoading(false)
}) })
@@ -64,10 +78,12 @@ export function AuthProvider({ children }: { children: ReactNode }) {
async function signOut() { async function signOut() {
await firebaseSignOut(auth) await firebaseSignOut(auth)
setUserId(null)
} }
const value: AuthContextValue = { const value: AuthContextValue = {
user, user,
userId,
loading, loading,
signInWithGoogle, signInWithGoogle,
signOut, signOut,

View File

@@ -2,91 +2,95 @@
*, *,
*::before, *::before,
*::after { *::after {
box-sizing: border-box; box-sizing: border-box;
} }
:root { :root {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Helvetica Neue", sans-serif; font-family:
line-height: 1.5; -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto",
font-weight: 400; "Helvetica Neue", sans-serif;
/* Fixed 16px we're always rendering at phone scale */ line-height: 1.5;
font-size: 16px; font-weight: 400;
/* Fixed 16px we're always rendering at phone scale */
font-size: 16px;
--touch-min: 44px; --touch-min: 44px;
--color-primary: #22c55e; --color-primary: #22c55e;
--color-primary-hover: #16a34a; --color-primary-hover: #16a34a;
--color-bg-soft: #f5f0e8; --color-bg-soft: #f5f0e8;
--color-surface: #ffffff; --color-surface: #ffffff;
--color-accent-light: #dcfce7; --color-accent-light: #dcfce7;
--color-text: #1a1a1a; --color-text: #1a1a1a;
--color-text-muted: #6b7280; --color-text-muted: #6b7280;
--color-border: #e5e7eb; --color-border: #e5e7eb;
color: var(--color-text); color: var(--color-text);
background-color: var(--color-bg-soft); background-color: var(--color-bg-soft);
font-synthesis: none; font-synthesis: none;
text-rendering: optimizeLegibility; text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased; -webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale; -moz-osx-font-smoothing: grayscale;
} }
a { a {
font-weight: 500; font-weight: 500;
color: var(--color-primary); color: var(--color-primary);
text-decoration: inherit; text-decoration: inherit;
} }
a:hover { a:hover {
color: var(--color-primary-hover); color: var(--color-primary-hover);
} }
html { html {
height: 100%; height: 100%;
-webkit-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;
} }
body { body {
margin: 0; margin: 0;
height: 100%; height: 100%;
min-height: 100dvh; min-height: 100dvh;
overflow: hidden; overflow: hidden;
/* Desktop: show as phone on a desk surface */ /* Desktop: show as phone on a desk surface */
background: #ccc8c0; background: #ccc8c0;
} }
/* ── Phone shell on desktop ───────────────────────────── */ /* ── Phone shell on desktop ───────────────────────────── */
@media (min-width: 600px) { @media (min-width: 600px) {
body { body {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center; justify-content: center;
background: #bbb7af; background: #bbb7af;
} }
#root { #root {
width: 390px !important; width: 390px !important;
max-width: 390px !important; max-width: 390px !important;
height: 100dvh; height: 100dvh;
max-height: 100dvh; max-height: 100dvh;
border-radius: 0; border-radius: 0;
overflow: hidden; overflow: hidden;
box-shadow: 0 24px 80px rgba(0, 0, 0, 0.35), 0 4px 16px rgba(0, 0, 0, 0.2); box-shadow:
position: relative; 0 24px 80px rgba(0, 0, 0, 0.35),
flex-shrink: 0; 0 4px 16px rgba(0, 0, 0, 0.2);
} position: relative;
flex-shrink: 0;
}
} }
h1 { h1 {
font-size: 1.75rem; font-size: 1.75rem;
line-height: 1.2; line-height: 1.2;
} }
button { button {
font-family: inherit; font-family: inherit;
cursor: pointer; cursor: pointer;
} }
button:focus, button:focus,
button:focus-visible { button:focus-visible {
outline: 2px solid var(--color-primary); outline: 2px solid var(--color-primary);
outline-offset: 2px; outline-offset: 2px;
} }

173
src/lib/api.ts Normal file
View File

@@ -0,0 +1,173 @@
/**
* API Service Layer
* Handles all communication with the backend API
*/
const API_BASE_URL = 'http://localhost:8001'
type ApiOptions = {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
body?: unknown
token?: string | null
}
async function apiCall<T>(
endpoint: string,
options: ApiOptions = {}
): Promise<T> {
const { method = 'GET', body, token } = options
const headers: HeadersInit = {
'Content-Type': 'application/json',
}
if (token) {
headers['Authorization'] = `Bearer ${token}`
}
const config: RequestInit = {
method,
headers,
credentials: 'include',
}
if (body) {
config.body = JSON.stringify(body)
}
const response = await fetch(`${API_BASE_URL}${endpoint}`, config)
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw new Error(error.detail || `API error: ${response.statusText}`)
}
return response.json() as Promise<T>
}
// ============================================
// USER ENDPOINTS
// ============================================
export async function registerUser(
userData: {
email: string
displayName?: string
photoURL?: string
},
token: string
) {
return apiCall('/api/users/register', {
method: 'POST',
body: userData,
token,
})
}
export async function getUserByEmail(email: string, token: string) {
return apiCall(`/api/users/by-email/${email}`, { token })
}
export async function updateUserProfile(
userId: string,
updates: { displayName?: string; photoURL?: string; theme?: string },
token: string
) {
return apiCall(`/api/users/update/${userId}`, {
method: 'PUT',
body: updates,
token,
})
}
// ============================================
// ENTRY ENDPOINTS
// ============================================
export interface JournalEntryCreate {
title: string
content: string
mood?: string
tags?: string[]
isPublic?: boolean
}
export interface JournalEntry extends JournalEntryCreate {
id: string
userId: string
createdAt: string
updatedAt: string
}
export async function createEntry(
userId: string,
entryData: JournalEntryCreate,
token: string
) {
return apiCall<{ id: string; message: string }>(
`/api/entries/${userId}`,
{
method: 'POST',
body: entryData,
token,
}
)
}
export async function getUserEntries(
userId: string,
token: string,
limit = 50,
skip = 0
) {
return apiCall<{ entries: JournalEntry[]; total: number }>(
`/api/entries/${userId}?limit=${limit}&skip=${skip}`,
{ token }
)
}
export async function getEntry(
userId: string,
entryId: string,
token: string
) {
return apiCall<JournalEntry>(`/api/entries/${userId}/${entryId}`, {
token,
})
}
export async function updateEntry(
userId: string,
entryId: string,
updates: Partial<JournalEntryCreate>,
token: string
) {
return apiCall(`/api/entries/${userId}/${entryId}`, {
method: 'PUT',
body: updates,
token,
})
}
export async function deleteEntry(
userId: string,
entryId: string,
token: string
) {
return apiCall(`/api/entries/${userId}/${entryId}`, {
method: 'DELETE',
token,
})
}
export async function getEntriesByDate(
userId: string,
startDate: string,
endDate: string,
token: string
) {
return apiCall<JournalEntry[]>(
`/api/entries/${userId}/date-range?startDate=${startDate}&endDate=${endDate}`,
{ token }
)
}

View File

@@ -1,11 +1,9 @@
import { initializeApp } from 'firebase/app' import { initializeApp } from 'firebase/app'
import { getAuth, GoogleAuthProvider } from 'firebase/auth' import { getAuth, GoogleAuthProvider } from 'firebase/auth'
import { getFirestore, connectFirestoreEmulator } from 'firebase/firestore'
const firebaseConfig = { const firebaseConfig = {
apiKey: import.meta.env.VITE_FIREBASE_API_KEY, apiKey: import.meta.env.VITE_FIREBASE_API_KEY,
authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN, authDomain: import.meta.env.VITE_FIREBASE_AUTH_DOMAIN,
databaseURL: import.meta.env.VITE_FIREBASE_DATABASE_URL,
projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID, projectId: import.meta.env.VITE_FIREBASE_PROJECT_ID,
storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET, storageBucket: import.meta.env.VITE_FIREBASE_STORAGE_BUCKET,
messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID, messagingSenderId: import.meta.env.VITE_FIREBASE_MESSAGING_SENDER_ID,
@@ -14,14 +12,6 @@ const firebaseConfig = {
const app = initializeApp(firebaseConfig) const app = initializeApp(firebaseConfig)
// Auth initialization // Google Auth initialization
export const auth = getAuth(app) export const auth = getAuth(app)
export const googleProvider = new GoogleAuthProvider() export const googleProvider = new GoogleAuthProvider()
// Firestore initialization
export const db = getFirestore(app)
// Enable Firestore emulator in development (uncomment when testing locally)
// if (import.meta.env.DEV) {
// connectFirestoreEmulator(db, 'localhost', 8080)
// }

View File

@@ -1,17 +1,33 @@
import { useState } from 'react' import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext'
import { getUserEntries, type JournalEntry } from '../lib/api'
import BottomNav from '../components/BottomNav' import BottomNav from '../components/BottomNav'
interface JournalEntry {
id: string
date: Date
title: string
content: string
}
export default function HistoryPage() { export default function HistoryPage() {
const { user, userId, loading } = useAuth()
const [currentMonth, setCurrentMonth] = useState(new Date()) const [currentMonth, setCurrentMonth] = useState(new Date())
const [entries, setEntries] = useState<JournalEntry[]>([])
const entries: JournalEntry[] = [] const [loadingEntries, setLoadingEntries] = useState(false)
// Fetch entries on mount and when userId changes
useEffect(() => {
if (!user || !userId) return
const fetchEntries = async () => {
setLoadingEntries(true)
try {
const token = await user.getIdToken()
const response = await getUserEntries(userId, token, 100, 0)
setEntries(response.entries)
} catch (error) {
console.error('Error fetching entries:', error)
} finally {
setLoadingEntries(false)
}
}
fetchEntries()
}, [user, userId])
const getDaysInMonth = (date: Date) => { const getDaysInMonth = (date: Date) => {
const year = date.getFullYear() const year = date.getFullYear()
@@ -20,43 +36,50 @@ export default function HistoryPage() {
const lastDay = new Date(year, month + 1, 0) const lastDay = new Date(year, month + 1, 0)
const daysInMonth = lastDay.getDate() const daysInMonth = lastDay.getDate()
const startingDayOfWeek = firstDay.getDay() const startingDayOfWeek = firstDay.getDay()
return { daysInMonth, startingDayOfWeek } return { daysInMonth, startingDayOfWeek }
} }
const hasEntryOnDate = (day: number) => { const hasEntryOnDate = (day: number) => {
return entries.some(entry => { return entries.some((entry) => {
const entryDate = new Date(entry.date) const entryDate = new Date(entry.createdAt)
return entryDate.getDate() === day && return (
entryDate.getDate() === day &&
entryDate.getMonth() === currentMonth.getMonth() && entryDate.getMonth() === currentMonth.getMonth() &&
entryDate.getFullYear() === currentMonth.getFullYear() entryDate.getFullYear() === currentMonth.getFullYear()
)
}) })
} }
const isToday = (day: number) => { const isToday = (day: number) => {
const today = new Date() const today = new Date()
return day === today.getDate() && return (
day === today.getDate() &&
currentMonth.getMonth() === today.getMonth() && currentMonth.getMonth() === today.getMonth() &&
currentMonth.getFullYear() === today.getFullYear() currentMonth.getFullYear() === today.getFullYear()
)
} }
const formatDate = (date: Date) => { const formatDate = (date: string) => {
return date.toLocaleDateString('en-US', { return new Date(date).toLocaleDateString('en-US', {
weekday: 'short', weekday: 'short',
month: 'short', month: 'short',
day: '2-digit' day: '2-digit',
}).toUpperCase() }).toUpperCase()
} }
const formatTime = (date: Date) => { const formatTime = (date: string) => {
return date.toLocaleTimeString('en-US', { return new Date(date).toLocaleTimeString('en-US', {
hour: '2-digit', hour: '2-digit',
minute: '2-digit' minute: '2-digit',
}).toUpperCase() }).toUpperCase()
} }
const { daysInMonth, startingDayOfWeek } = getDaysInMonth(currentMonth) const { daysInMonth, startingDayOfWeek } = getDaysInMonth(currentMonth)
const monthName = currentMonth.toLocaleDateString('en-US', { month: 'long', year: 'numeric' }) const monthName = currentMonth.toLocaleDateString('en-US', {
month: 'long',
year: 'numeric',
})
const previousMonth = () => { const previousMonth = () => {
setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1)) setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() - 1))
@@ -66,6 +89,24 @@ export default function HistoryPage() {
setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1)) setCurrentMonth(new Date(currentMonth.getFullYear(), currentMonth.getMonth() + 1))
} }
// Get entries for current month
const currentMonthEntries = entries.filter((entry) => {
const entryDate = new Date(entry.createdAt)
return (
entryDate.getMonth() === currentMonth.getMonth() &&
entryDate.getFullYear() === currentMonth.getFullYear()
)
})
if (loading) {
return (
<div className="history-page" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<p style={{ color: '#9ca3af' }}>Loading</p>
<BottomNav />
</div>
)
}
return ( return (
<div className="history-page"> <div className="history-page">
<header className="history-header"> <header className="history-header">
@@ -116,7 +157,7 @@ export default function HistoryPage() {
const day = i + 1 const day = i + 1
const hasEntry = hasEntryOnDate(day) const hasEntry = hasEntryOnDate(day)
const isTodayDate = isToday(day) const isTodayDate = isToday(day)
return ( return (
<button <button
key={day} key={day}
@@ -133,28 +174,36 @@ export default function HistoryPage() {
<section className="recent-entries"> <section className="recent-entries">
<h3 className="recent-entries-title">RECENT ENTRIES</h3> <h3 className="recent-entries-title">RECENT ENTRIES</h3>
<div className="entries-list"> {loadingEntries ? (
{entries.length === 0 ? ( <p style={{ color: '#9ca3af', fontSize: '0.875rem', textAlign: 'center', padding: '1.5rem 0', fontFamily: 'Inter, sans-serif' }}>
<p style={{ color: '#9ca3af', fontSize: '0.875rem', textAlign: 'center', padding: '1.5rem 0', fontFamily: 'Inter, sans-serif' }}> Loading entries
No entries yet. Start writing! </p>
</p> ) : (
) : entries.map(entry => ( <div className="entries-list">
<button {currentMonthEntries.length === 0 ? (
key={entry.id} <p style={{ color: '#9ca3af', fontSize: '0.875rem', textAlign: 'center', padding: '1.5rem 0', fontFamily: 'Inter, sans-serif' }}>
type="button" No entries for this month yet. Start writing!
className="entry-card" </p>
onClick={() => console.log('Open entry', entry.id)} ) : (
> currentMonthEntries.map((entry) => (
<div className="entry-header"> <button
<span className="entry-date">{formatDate(entry.date)}</span> key={entry.id}
<span className="entry-time">{formatTime(entry.date)}</span> type="button"
</div> className="entry-card"
<h4 className="entry-title">{entry.title}</h4> onClick={() => console.log('Open entry', entry.id)}
<p className="entry-preview">{entry.content}</p> >
</button> <div className="entry-header">
))} <span className="entry-date">{formatDate(entry.createdAt)}</span>
</div> <span className="entry-time">{formatTime(entry.createdAt)}</span>
</div>
<h4 className="entry-title">{entry.title}</h4>
<p className="entry-preview">{entry.content}</p>
</button>
))
)}
</div>
)}
</section> </section>
</main> </main>

View File

@@ -1,12 +1,15 @@
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { Link } from 'react-router-dom' import { Link } from 'react-router-dom'
import { useState } from 'react' import { useState } from 'react'
import { createEntry } from '../lib/api'
import BottomNav from '../components/BottomNav' import BottomNav from '../components/BottomNav'
export default function HomePage() { export default function HomePage() {
const { user, loading, signOut } = useAuth() const { user, userId, loading, signOut } = useAuth()
const [entry, setEntry] = useState('') const [entry, setEntry] = useState('')
const [title, setTitle] = useState('') const [title, setTitle] = useState('')
const [saving, setSaving] = useState(false)
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
if (loading) { if (loading) {
return ( return (
@@ -32,10 +35,39 @@ export default function HomePage() {
.toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' }) .toLocaleDateString('en-US', { weekday: 'long', month: 'short', day: 'numeric' })
.toUpperCase() .toUpperCase()
const handleWrite = () => { const handleWrite = async () => {
// TODO: Save to Firebase if (!userId || !title.trim() || !entry.trim()) {
setTitle('') setMessage({ type: 'error', text: 'Please add a title and entry content' })
setEntry('') return
}
setSaving(true)
setMessage(null)
try {
const token = await user.getIdToken()
await createEntry(
userId,
{
title: title.trim(),
content: entry.trim(),
isPublic: false,
},
token
)
setMessage({ type: 'success', text: 'Entry saved successfully!' })
setTitle('')
setEntry('')
// Clear success message after 3 seconds
setTimeout(() => setMessage(null), 3000)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to save entry'
setMessage({ type: 'error', text: errorMessage })
} finally {
setSaving(false)
}
} }
return ( return (
@@ -53,14 +85,40 @@ export default function HomePage() {
placeholder="Title your thoughts..." placeholder="Title your thoughts..."
value={title} value={title}
onChange={(e) => setTitle(e.target.value)} onChange={(e) => setTitle(e.target.value)}
disabled={saving}
/> />
<textarea <textarea
className="journal-entry-textarea" className="journal-entry-textarea"
placeholder="" placeholder=""
value={entry} value={entry}
onChange={(e) => setEntry(e.target.value)} onChange={(e) => setEntry(e.target.value)}
disabled={saving}
/> />
</div> </div>
{message && (
<div style={{
padding: '0.75rem',
marginTop: '1rem',
borderRadius: '8px',
fontSize: '0.875rem',
backgroundColor: message.type === 'success' ? '#f0fdf4' : '#fef2f2',
color: message.type === 'success' ? '#15803d' : '#b91c1c',
textAlign: 'center',
}}>
{message.text}
</div>
)}
<div style={{ marginTop: '1.5rem', display: 'flex', gap: '0.75rem' }}>
<button
className="journal-write-btn"
onClick={handleWrite}
disabled={saving || !title.trim() || !entry.trim()}
>
{saving ? 'Saving...' : 'Save Entry'}
</button>
</div>
</div> </div>
</main> </main>

View File

@@ -1,15 +1,39 @@
import { useState } from 'react' import { useState, useEffect } from 'react'
import { useAuth } from '../contexts/AuthContext' import { useAuth } from '../contexts/AuthContext'
import { updateUserProfile } from '../lib/api'
import BottomNav from '../components/BottomNav' import BottomNav from '../components/BottomNav'
export default function SettingsPage() { export default function SettingsPage() {
const { user, signOut } = useAuth() const { user, userId, signOut, loading } = useAuth()
const [passcodeEnabled, setPasscodeEnabled] = useState(false) const [passcodeEnabled, setPasscodeEnabled] = useState(false)
const [faceIdEnabled, setFaceIdEnabled] = useState(false) const [faceIdEnabled, setFaceIdEnabled] = useState(false)
const [theme, setTheme] = useState<'light' | 'dark'>('light')
const [saving, setSaving] = useState(false)
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null)
const displayName = user?.displayName || 'User' const displayName = user?.displayName || 'User'
const photoURL = user?.photoURL || '' const photoURL = user?.photoURL || ''
const handleThemeChange = async (newTheme: 'light' | 'dark') => {
if (!userId || !user) return
setSaving(true)
setMessage(null)
try {
const token = await user.getIdToken()
await updateUserProfile(userId, { theme: newTheme }, token)
setTheme(newTheme)
setMessage({ type: 'success', text: 'Theme updated successfully!' })
setTimeout(() => setMessage(null), 2000)
} catch (error) {
const errorMessage = error instanceof Error ? error.message : 'Failed to update theme'
setMessage({ type: 'error', text: errorMessage })
} finally {
setSaving(false)
}
}
const handleClearData = () => { const handleClearData = () => {
if (window.confirm('Are you sure you want to clear all local data? This action cannot be undone.')) { if (window.confirm('Are you sure you want to clear all local data? This action cannot be undone.')) {
// TODO: Implement clear local data // TODO: Implement clear local data
@@ -17,6 +41,23 @@ export default function SettingsPage() {
} }
} }
const handleSignOut = async () => {
try {
await signOut()
} catch (error) {
console.error('Error signing out:', error)
}
}
if (loading) {
return (
<div className="settings-page" style={{ display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
<p style={{ color: '#9ca3af' }}>Loading</p>
<BottomNav />
</div>
)
}
return ( return (
<div className="settings-page"> <div className="settings-page">
<header className="settings-header"> <header className="settings-header">
@@ -121,7 +162,7 @@ export default function SettingsPage() {
<div className="settings-divider"></div> <div className="settings-divider"></div>
<button type="button" className="settings-item settings-item-button"> <div className="settings-item">
<div className="settings-item-icon settings-item-icon-blue"> <div className="settings-item-icon settings-item-icon-blue">
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
<circle cx="13.5" cy="6.5" r=".5"></circle> <circle cx="13.5" cy="6.5" r=".5"></circle>
@@ -133,19 +174,44 @@ export default function SettingsPage() {
</div> </div>
<div className="settings-item-content"> <div className="settings-item-content">
<h4 className="settings-item-title">Theme</h4> <h4 className="settings-item-title">Theme</h4>
<p className="settings-item-subtitle">Currently: Warm Beige</p> <p className="settings-item-subtitle">Currently: {theme === 'light' ? 'Warm Beige' : 'Dark'}</p>
</div> </div>
<div className="settings-theme-colors"> <div className="settings-theme-colors">
<span className="settings-theme-dot settings-theme-dot-beige"></span> <button
<span className="settings-theme-dot settings-theme-dot-dark"></span> type="button"
onClick={() => handleThemeChange('light')}
className="settings-theme-dot settings-theme-dot-beige"
style={{ opacity: theme === 'light' ? 1 : 0.5 }}
title="Light theme"
disabled={saving}
></button>
<button
type="button"
onClick={() => handleThemeChange('dark')}
className="settings-theme-dot settings-theme-dot-dark"
style={{ opacity: theme === 'dark' ? 1 : 0.5 }}
title="Dark theme"
disabled={saving}
></button>
</div> </div>
<svg className="settings-item-arrow" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"> </div>
<polyline points="9 18 15 12 9 6"></polyline>
</svg>
</button>
</div> </div>
</section> </section>
{message && (
<div style={{
padding: '0.75rem',
marginBottom: '1rem',
borderRadius: '8px',
fontSize: '0.875rem',
backgroundColor: message.type === 'success' ? '#f0fdf4' : '#fef2f2',
color: message.type === 'success' ? '#15803d' : '#b91c1c',
textAlign: 'center',
}}>
{message.text}
</div>
)}
{/* Clear Data */} {/* Clear Data */}
<button type="button" className="settings-clear-btn" onClick={handleClearData}> <button type="button" className="settings-clear-btn" onClick={handleClearData}>
<span>Clear Local Data</span> <span>Clear Local Data</span>
@@ -156,7 +222,7 @@ export default function SettingsPage() {
</button> </button>
{/* Sign Out */} {/* Sign Out */}
<button type="button" className="settings-signout-btn" onClick={() => signOut()}> <button type="button" className="settings-signout-btn" onClick={handleSignOut}>
Sign Out Sign Out
</button> </button>

52
start-all.sh Executable file
View File

@@ -0,0 +1,52 @@
#!/bin/bash
# Grateful Journal - Start All Services
# Runs MongoDB, FastAPI backend, and Vite frontend in one command
set -e
echo "🚀 Starting Grateful Journal..."
echo ""
# Check if MongoDB is running
echo "📦 Checking MongoDB..."
if lsof -Pi :27017 -sTCP:LISTEN -t >/dev/null 2>&1 ; then
echo "✓ MongoDB already running on port 27017"
else
echo "Starting MongoDB..."
brew services start mongodb-community
sleep 2
echo "✓ MongoDB started on port 27017"
fi
echo ""
# Start Backend (FastAPI with conda environment)
echo "🔄 Starting FastAPI backend..."
# Activate conda and start backend
conda run -n yoyo python backend/main.py &
BACKEND_PID=$!
echo "✓ Backend running on http://localhost:8001 (PID: $BACKEND_PID)"
sleep 2
echo ""
# Start Frontend (Vite)
echo "🔄 Starting Vite frontend..."
npm run dev -- --port 8000 &
FRONTEND_PID=$!
echo "✓ Frontend running on http://localhost:8000 (PID: $FRONTEND_PID)"
echo ""
echo "✅ All services started!"
echo ""
echo "📱 Frontend: http://localhost:8000"
echo "🔌 Backend: http://localhost:8001"
echo "📄 API Docs: http://localhost:8001/docs"
echo ""
echo "To stop all services, press Ctrl+C"
echo ""
# Wait for both processes
wait $BACKEND_PID $FRONTEND_PID

45
start-dev.sh Normal file
View File

@@ -0,0 +1,45 @@
#!/bin/bash
# grateful-journal startup script
echo "🚀 Starting Grateful Journal (Development)"
echo "=========================================="
echo ""
# Check if MongoDB is running
echo "Checking MongoDB..."
if mongosh --eval "db.adminCommand('ping')" &>/dev/null; then
echo "✅ MongoDB is running"
else
echo "❌ MongoDB not running. Start it with:"
echo " brew services start mongodb-community (macOS)"
echo " sudo systemctl start mongod (Linux)"
exit 1
fi
echo ""
echo "Starting Frontend..."
cd "$(dirname "$0")"
npm run dev -- --port 8000 &
FRONTEND_PID=$!
echo "✅ Frontend: http://localhost:8000 (PID: $FRONTEND_PID)"
echo ""
echo "Starting Backend..."
cd backend
source venv/bin/activate 2>/dev/null || . venv/Scripts/activate 2>/dev/null
python main.py &
BACKEND_PID=$!
echo "✅ Backend: http://localhost:8001 (PID: $BACKEND_PID)"
echo " API Docs: http://localhost:8001/docs"
echo ""
echo "=========================================="
echo "Services running:"
echo " • Frontend (React): 8000"
echo " • Backend (FastAPI): 8001"
echo " • MongoDB: 27017"
echo ""
echo "Press Ctrl+C to stop all services"
echo "=========================================="
wait

View File

@@ -4,4 +4,9 @@ import react from '@vitejs/plugin-react'
// https://vite.dev/config/ // https://vite.dev/config/
export default defineConfig({ export default defineConfig({
plugins: [react()], plugins: [react()],
server: {
port: 8000,
strictPort: false,
},
}) })