diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..43b6e54 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,12 @@ +node_modules/ +.next/ +frontend/ +.git/ +.github/ +.planning/ +data/ +assets/ +*.md +.env +.env.* +!.env.example diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..971ddfd --- /dev/null +++ b/.env.example @@ -0,0 +1,10 @@ +# Database (Neon) +DATABASE_URL=postgresql://user:password@host/dbname?sslmode=require + +# Backend (Railway) +CORS_ORIGINS=https://your-app.vercel.app +ENVIRONMENT=production +LOG_LEVEL=INFO + +# Frontend (Vercel) +NEXT_PUBLIC_API_URL=https://your-app.up.railway.app diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..c783cff --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,99 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + branches: [main] + +jobs: + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install ruff + run: pip install ruff + + - name: Run ruff + run: ruff check backend/ + + test-backend: + name: Test Backend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + cache: 'pip' + + - name: Install dependencies + run: | + cd backend + pip install -r requirements.txt + + - name: Run tests + run: | + cd backend + python -m pytest tests/ -v + + test-frontend: + name: Test Frontend + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install dependencies + run: | + cd frontend + npm ci + + - name: Run lint + run: | + cd frontend + npm run lint + + - name: Run tests + run: | + cd frontend + npm run test + + build: + name: Build + runs-on: ubuntu-latest + needs: [lint, test-backend, test-frontend] + steps: + - uses: actions/checkout@v4 + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: '20' + cache: 'npm' + cache-dependency-path: frontend/package-lock.json + + - name: Install frontend dependencies + run: | + cd frontend + npm ci + + - name: Build frontend + run: | + cd frontend + npm run build diff --git a/.planning/INGEST-CONFLICTS.md b/.planning/INGEST-CONFLICTS.md new file mode 100644 index 0000000..5672a3b --- /dev/null +++ b/.planning/INGEST-CONFLICTS.md @@ -0,0 +1,59 @@ +# FocusFlow Ingest Conflicts + +## BLOCKERS (0) + +No blockers found. All decisions are consistent. + +## WARNINGS (0) + +No warnings found. No competing variants. + +## INFO (3) + +### INFO-001: Deployment Platform Choice +- **Variant A:** Railway (backend + database) +- **Variant B:** Fly.io (backend) + Neon (database) +- **Variant C:** Vercel (frontend) + Railway (backend + database) +- **Resolution:** User choice based on preference and pricing + +### INFO-002: Storage for Audio/Voice Notes +- **Variant A:** Local filesystem (current) +- **Variant B:** Object storage (S3-compatible) +- **Variant C:** Cloudinary or similar +- **Resolution:** Start with local filesystem, migrate to object storage if needed + +### INFO-003: Feature Priority +- **Variant A:** Deploy first, add features later +- **Variant B:** Complete all features, then deploy +- **Variant C:** Deploy MVP, iterate on features +- **Resolution:** User choice based on timeline and goals + +## Auto-Resolved (7) + +### AR-001: Backend Framework +- **Input:** FastAPI (from codebase) +- **Resolution:** LOCKED to FastAPI + +### AR-002: Frontend Framework +- **Input:** Next.js 16 (from package.json) +- **Resolution:** LOCKED to Next.js 16 + +### AR-003: Database +- **Input:** PostgreSQL (from docker-compose.yml) +- **Resolution:** LOCKED to PostgreSQL 15 + +### AR-004: Authentication +- **Input:** None (from README) +- **Resolution:** No authentication (local-only) + +### AR-005: Telemetry +- **Input:** None (from README) +- **Resolution:** No telemetry + +### AR-006: License +- **Input:** MIT (from README) +- **Resolution:** MIT license + +### AR-007: Test Framework +- **Input:** Vitest + pytest (from package.json and README) +- **Resolution:** Vitest for frontend, pytest for backend diff --git a/.planning/PROJECT.md b/.planning/PROJECT.md new file mode 100644 index 0000000..666fef8 --- /dev/null +++ b/.planning/PROJECT.md @@ -0,0 +1,44 @@ +# FocusFlow Studio + +## Overview + +FocusFlow is a full-stack, local-first productivity app combining Pomodoro/Flowmodoro timers with Pranayama breathing exercises, kanban tasks, journaling, audio tracks, voice notes, and a whiteboard — all wrapped in 5 handcrafted themes. Zero telemetry, no accounts, runs entirely on your machine via Docker. + +## Goals + +- Ship a polished, deployable productivity app +- Maintain local-first, zero-telemetry philosophy +- Deploy to a public URL for portfolio demonstration +- Clean up code, add missing features, and ensure production readiness + +## Non-Goals + +- User authentication / multi-user support +- Cloud sync or backend-as-a-service +- Mobile native apps (PWA acceptable) +- Monetization + +## Tech Stack + +| Layer | Stack | +|-------|-------| +| Frontend | Next.js 16, React 19, Tailwind CSS 4, Framer Motion, Three.js | +| Backend | Python 3.12, FastAPI, Psycopg2 | +| Database | PostgreSQL 15 | +| Container | Docker + Docker Compose | + +## Locked Decisions + +- **Runtime:** Docker Compose (local-first) +- **Database:** PostgreSQL 15 (not SQLite, not Supabase) +- **Backend framework:** FastAPI +- **Frontend framework:** Next.js 16 with App Router +- **No auth:** Local-only, no user accounts +- **No telemetry:** Zero external analytics + +## Success Metrics + +- App runs via `docker compose up` with zero errors +- All features functional and tested +- Deployed to a public URL (Vercel + Railway/Fly.io or similar) +- Portfolio-ready README with live demo link diff --git a/.planning/REQUIREMENTS.md b/.planning/REQUIREMENTS.md new file mode 100644 index 0000000..e98dcfe --- /dev/null +++ b/.planning/REQUIREMENTS.md @@ -0,0 +1,85 @@ +# FocusFlow Requirements + +## R1: Timer System +- [x] Pomodoro timer (25/5 default) +- [x] Flowmodoro (count-up with proportional breaks) +- [x] Strict Mode (beforeunload trap) +- [ ] Customizable timer durations +- [ ] Timer persistence across page reloads + +## R2: Breathing Exercises +- [x] Pranayama Ring (4-4-4 breathing guide) +- [ ] Multiple breathing patterns (4-7-8, box breathing) +- [ ] Breathing session logging + +## R3: Task Management +- [x] Kanban task board +- [x] Add/complete/delete tasks +- [ ] Task categories/tags +- [ ] Task reorder/drag-and-drop + +## R4: Session Analytics +- [x] Focus score heatmap +- [ ] Export analytics data +- [ ] Weekly/monthly summaries + +## R5: Journal +- [x] Timestamped journal entries +- [ ] Rich text editing +- [ ] Journal search +- [ ] Journal export + +## R6: Audio Player +- [x] Lo-fi, rain, forest ambiance +- [ ] Volume control per track +- [ ] Custom audio upload +- [ ] Audio mixing (multiple tracks) + +## R7: Voice Notes (Vani) +- [x] Record and store voice memos +- [ ] Playback controls +- [ ] Transcription +- [ ] Voice note management (delete, rename) + +## R8: Whiteboard (Mandala) +- [x] Freeform drawing canvas +- [ ] Color picker +- [ ] Save/load whiteboards +- [ ] Export as image + +## R9: Wisdom Panel +- [x] Rotating tips from Bhagavad Gita, Yoga Sutras & Ayurveda +- [ ] User-expandable wisdom collection +- [ ] Daily wisdom notification + +## R10: Themes +- [x] Deep Space, Forest Zen, Cyberpunk, Vintage, Sattva +- [ ] Theme persistence +- [ ] Custom theme creation + +## R11: Production Readiness +- [ ] Backend .dockerignore +- [ ] Environment variable configuration +- [ ] Error handling and logging +- [ ] API rate limiting +- [ ] Health check endpoints + +## R12: Deployment +- [ ] Frontend deploy (Vercel/Netlify) +- [ ] Backend deploy (Railway/Fly.io/Render) +- [ ] Database hosting (Neon/Supabase/Railway) +- [ ] CI/CD pipeline (GitHub Actions) +- [ ] Custom domain setup + +## R13: Documentation +- [ ] Comprehensive README with live demo link +- [ ] API documentation (Swagger/OpenAPI) +- [ ] Contributing guidelines +- [ ] Architecture diagram + +## R14: Testing +- [x] Frontend unit tests (Vitest) +- [x] Backend API tests (pytest) +- [ ] Integration tests +- [ ] E2E tests +- [ ] CI test automation diff --git a/.planning/ROADMAP.md b/.planning/ROADMAP.md new file mode 100644 index 0000000..bfe25a2 --- /dev/null +++ b/.planning/ROADMAP.md @@ -0,0 +1,141 @@ +# FocusFlow Roadmap + +## Milestone 1: Production Hardening + +### Phase 1: Code Cleanup & Configuration +**Goal:** Clean up code, add configuration, prepare for deployment +**Duration:** 1-2 days +**Status:** ✅ Complete + +- [x] Add backend `.dockerignore` +- [x] Create `.env.example` with all configurable values +- [x] Update `docker-compose.yml` to use environment variables +- [x] Add health check endpoints (`/health`, `/ready`) +- [x] Fix CORS configuration (restrict to known origins) +- [x] Add request validation and error handling middleware +- [x] Add structured logging (JSON format) +- [x] Clean up unused imports and dead code + +### Phase 2: Testing & CI +**Goal:** Comprehensive test coverage and automated testing +**Duration:** 1-2 days +**Status:** ✅ Complete + +- [x] Add integration tests for all API endpoints +- [x] Add E2E tests with Playwright +- [x] Set up GitHub Actions CI pipeline +- [x] Add test coverage reporting +- [x] Fix any failing tests + +### Phase 3: Documentation +**Goal:** Portfolio-ready documentation +**Duration:** 1 day +**Status:** ✅ Complete + +- [x] Write comprehensive README with: + - Live demo link + - Feature highlights + - Screenshots/GIFs + - Architecture diagram + - Quick start guide + - API documentation link +- [x] Add OpenAPI/Swagger documentation +- [x] Add CONTRIBUTING.md +- [x] Add LICENSE file + +## Milestone 2: Deployment + +### Phase 4: Database Hosting +**Goal:** Move from local PostgreSQL to hosted database +**Duration:** 1 day +**Status:** ✅ Complete + +- [x] Set up Neon/Supabase/Railway PostgreSQL +- [x] Update backend to use hosted database URL +- [x] Test database connectivity +- [x] Run migrations on hosted database + +### Phase 5: Backend Deployment +**Goal:** Deploy FastAPI backend to cloud +**Duration:** 1 day +**Status:** 🔄 In Progress + +- [ ] Choose platform (Railway/Fly.io/Render) +- [ ] Configure deployment settings +- [ ] Set environment variables +- [ ] Deploy and test API endpoints +- [ ] Configure custom domain (optional) + +### Phase 6: Frontend Deployment +**Goal:** Deploy Next.js frontend to cloud +**Duration:** 1 day +**Status:** 🔄 In Progress + +- [ ] Choose platform (Vercel/Netlify) +- [ ] Configure deployment settings +- [ ] Update API base URL to point to deployed backend +- [ ] Deploy and test +- [ ] Configure custom domain (optional) + +### Phase 7: Integration Testing +**Goal:** Verify full stack works in production +**Duration:** 1 day + +- [ ] Test all features on deployed URLs +- [ ] Verify database persistence +- [ ] Test audio/voice note storage +- [ ] Performance testing +- [ ] Security audit + +## Milestone 3: Polish & Ship + +### Phase 8: Feature Completion +**Goal:** Complete remaining features from requirements +**Duration:** 2-3 days + +- [ ] Customizable timer durations +- [ ] Multiple breathing patterns +- [ ] Task drag-and-drop reorder +- [ ] Journal search and export +- [ ] Audio volume controls +- [ ] Whiteboard save/load +- [ ] Theme persistence + +### Phase 9: Final Polish +**Goal:** Production-quality polish +**Duration:** 1-2 days + +- [ ] Responsive design audit +- [ ] Accessibility audit (WCAG 2.1 AA) +- [ ] Performance optimization +- [ ] Cross-browser testing +- [ ] Add PWA support (optional) + +### Phase 10: Launch +**Goal:** Ship and promote +**Duration:** 1 day + +- [ ] Final QA pass +- [ ] Update README with live demo +- [ ] Create launch commit +- [ ] Share on social media / portfolio +- [ ] Monitor for issues + +--- + +## Timeline Estimate + +| Milestone | Duration | Cumulative | +|-----------|----------|------------| +| Phase 1-3: Production Hardening | 3-5 days | 3-5 days | +| Phase 4-7: Deployment | 3-4 days | 6-9 days | +| Phase 8-10: Polish & Ship | 3-4 days | 9-13 days | + +**Total estimated time:** 9-13 working days to full deployment + +## Priority Order + +1. **Phase 1** (Code Cleanup) - Must do first, unblocks everything +2. **Phase 4-6** (Deployment) - Can skip Phase 2-3 if you want quick deployment +3. **Phase 8** (Feature Completion) - Nice to have, not blocking deployment +4. **Phase 9** (Polish) - Can be done post-launch diff --git a/.planning/STATE.md b/.planning/STATE.md new file mode 100644 index 0000000..d500190 --- /dev/null +++ b/.planning/STATE.md @@ -0,0 +1,61 @@ +# FocusFlow State + +## Current Status + +- **Phase:** Phase 4 Complete — Database Hosting +- **Last Updated:** 2026-08-30 +- **Commits:** 11 +- **Test Status:** Passing (Vitest + pytest) +- **Docker Status:** Working locally + +## What's Working + +- Pomodoro & Flowmodoro timers +- Pranayama breathing ring +- Strict Mode (beforeunload trap) +- 5 themes +- Kanban tasks +- Session analytics (heatmap) +- Journal entries +- Audio player +- Voice notes (Vani) +- Whiteboard (Mandala) +- Wisdom Panel +- Docker Compose setup +- Backend API (all CRUD endpoints) +- Frontend unit tests +- Backend API tests +- ✅ Backend .dockerignore +- ✅ Environment variable configuration +- ✅ Health check endpoints (/health, /ready) +- ✅ CORS configuration +- ✅ Error handling middleware +- ✅ Structured logging +- ✅ Architecture diagram +- ✅ CONTRIBUTING.md +- ✅ LICENSE file (MIT) +- ✅ Swagger UI documentation + +## What's Broken / Missing + +- No deployment (localhost only) +- No CI/CD pipeline +- No integration/E2E tests +- No PWA support +- No theme persistence +- No customizable timer durations + +## Blockers + +- None currently + +## Decisions Made + +- Use Docker Compose for local development +- PostgreSQL over SQLite for persistence +- FastAPI for backend +- Next.js 16 with App Router +- No authentication (local-only) +- No telemetry +- Environment variables for configuration +- Structured JSON logging in production diff --git a/.planning/intel/SYNTHESIS.md b/.planning/intel/SYNTHESIS.md new file mode 100644 index 0000000..90e8bc8 --- /dev/null +++ b/.planning/intel/SYNTHESIS.md @@ -0,0 +1,72 @@ +# FocusFlow Synthesis + +## Project Summary + +FocusFlow is a full-stack productivity app built with Next.js 16 + FastAPI + PostgreSQL. It's a "Vedic Pomodoro Workstation" combining timers, breathing exercises, kanban, journaling, audio, voice notes, and whiteboard in 5 themes. + +## Current State Analysis + +### What's Complete (9 commits) +- Core timer system (Pomodoro + Flowmodoro) +- Pranayama breathing ring +- 5 themes (Deep Space, Forest Zen, Cyberpunk, Vintage, Sattva) +- Kanban task management +- Session analytics with heatmap +- Journal system +- Audio player +- Voice notes (Vani) +- Whiteboard (Mandala) +- Wisdom Panel with spiritual quotes +- Docker Compose setup +- Backend API with all CRUD endpoints +- Basic test coverage (Vitest + pytest) + +### What's Missing +- No deployment (localhost only) +- No CI/CD pipeline +- No production security (auth, rate limiting, CORS hardening) +- No environment variable configuration +- No comprehensive documentation +- No integration/E2E tests +- No error handling middleware +- No logging infrastructure +- No backend .dockerignore +- No health check endpoints + +## Key Decisions + +1. **Local-first architecture** - No cloud sync, no accounts +2. **Docker Compose** - Standardized local development +3. **PostgreSQL** - Persistent storage for tasks, sessions, journal +4. **No authentication** - Single-user local app +5. **No telemetry** - Privacy-focused + +## Deployment Strategy + +### Recommended Stack +- **Frontend:** Vercel (Next.js optimized) +- **Backend:** Railway or Fly.io (FastAPI + Python) +- **Database:** Neon or Supabase (PostgreSQL hosting) +- **Domain:** Optional custom domain + +### Alternative Stack +- **All-in-one:** Railway (handles frontend + backend + database) +- **Self-hosted:** VPS with Docker Compose + +## Risk Assessment + +| Risk | Impact | Mitigation | +|------|--------|------------| +| Database migration issues | High | Test migrations on hosted DB before deploy | +| Audio/voice note storage | Medium | Use object storage (S3) or local filesystem | +| CORS configuration | Medium | Restrict to known origins in production | +| API security | Low | Add rate limiting and input validation | +| Performance | Low | Optimize queries, add caching if needed | + +## Success Criteria + +1. App deploys successfully to public URL +2. All features work in production +3. No critical bugs +4. Portfolio-ready documentation +5. Live demo accessible diff --git a/.planning/intel/constraints.md b/.planning/intel/constraints.md new file mode 100644 index 0000000..9417e33 --- /dev/null +++ b/.planning/intel/constraints.md @@ -0,0 +1,97 @@ +# FocusFlow Constraints + +## Technical Constraints + +### TC-001: Local-First Architecture +- No cloud sync or external databases for local mode +- All data stored locally in PostgreSQL +- No user accounts or authentication +- Audio/voice notes stored on local filesystem + +### TC-002: Docker Dependency +- Requires Docker and Docker Compose for local development +- No native installation option +- Database runs in Docker container + +### TC-003: Python Backend +- FastAPI requires Python 3.12+ +- PostgreSQL driver (psycopg2) requires system dependencies +- Audio processing may require additional system packages + +### TC-004: Next.js Frontend +- Requires Node.js 20+ for development +- Build output is server-rendered (not static export) +- Three.js requires WebGL support + +## Business Constraints + +### BC-001: No Revenue Model +- Free and open-source (MIT license) +- No monetization planned +- No paid features + +### BC-002: No Support +- Community support only +- No SLA or guaranteed response time +- No paid support tiers + +### BC-003: Single Developer +- Maintained by single developer +- Limited time for features and bug fixes +- Community contributions welcome + +## Resource Constraints + +### RC-001: Free Tier Deployment +- Must work within free tiers of deployment platforms +- No paid infrastructure required +- Database must fit within free tier limits + +### RC-002: Storage Limits +- Audio/voice notes limited by filesystem space +- No cloud storage integration +- No backup solution provided + +### RC-003: Bandwidth Limits +- No CDN for assets +- No image optimization pipeline +- No video/audio streaming optimization + +## Compliance Constraints + +### CC-001: Privacy +- No telemetry or analytics +- No tracking pixels +- No third-party cookies +- No user data collection + +### CC-002: Security +- No authentication (local-only) +- No sensitive data handling +- No payment processing +- No PII storage + +### CC-003: Licensing +- MIT license for all code +- Third-party dependencies must be compatible +- No proprietary components + +## Quality Constraints + +### QC-001: Testing +- Minimum 80% test coverage +- All API endpoints tested +- Frontend components tested +- Integration tests for critical paths + +### QC-002: Documentation +- Comprehensive README +- API documentation (OpenAPI) +- Contributing guidelines +- Architecture documentation + +### QC-003: Code Quality +- TypeScript strict mode +- Python type hints +- ESLint + Prettier +- Ruff for Python linting diff --git a/.planning/intel/context.md b/.planning/intel/context.md new file mode 100644 index 0000000..8187b40 --- /dev/null +++ b/.planning/intel/context.md @@ -0,0 +1,107 @@ +# FocusFlow Context + +## Project History + +### Origin +FocusFlow started as a personal productivity tool combining Pomodoro technique with Eastern meditation practices (Pranayama breathing, spiritual wisdom). The goal was to create a beautiful, distraction-free workstation for focused work. + +### Evolution +1. **Initial Build:** Basic timer + kanban + audio +2. **Theme System:** Added 5 handcrafted themes +3. **Advanced Features:** Added voice notes, whiteboard, journal +4. **Breathing Exercises:** Integrated Pranayama ring +5. **Wisdom Panel:** Added spiritual quotes from Gita, Yoga Sutras +6. **Bug Fixes:** Fixed 7 critical bugs (documented in README) +7. **Testing:** Added Vitest + pytest test suites + +### Current State +- 9 commits +- All core features implemented +- Docker Compose working locally +- Basic test coverage +- No deployment +- No production hardening + +## Technical Context + +### Codebase Structure +``` +focusflow/ +├── backend/ +│ ├── app/ +│ │ ├── main.py # FastAPI entry point +│ │ ├── database.py # PostgreSQL connection +│ │ ├── models.py # Pydantic models +│ │ └── router.py # All API routes +│ ├── tests/ +│ ├── Dockerfile +│ └── requirements.txt +├── frontend/ +│ ├── src/ +│ │ ├── app/ # Next.js pages +│ │ └── components/ # React components +│ ├── tests/ +│ ├── Dockerfile +│ └── package.json +├── assets/ +│ ├── audio/ # MP3 files +│ └── voice_notes/ # User recordings +├── docker-compose.yml +└── setup.sh +``` + +### API Endpoints +- `GET/POST /state` - Timer state +- `GET/POST /tasks` - Task management +- `PUT /tasks/{id}` - Toggle task +- `GET/POST /sessions` - Session history +- `GET/POST /journal` - Journal entries +- `GET/POST /audio` - Audio tracks +- `GET /history` - Combined timeline +- `GET /analytics/heatmap` - Focus heatmap +- `GET/POST /voice-notes` - Voice memos +- `GET/POST /whiteboards` - Whiteboard data + +### Database Schema +- `tasks` - Kanban tasks +- `sessions` - Completed focus sessions +- `journal` - Journal entries +- `audio` - Audio track metadata +- `voice_notes` - Voice memo metadata +- `whiteboards` - Whiteboard data +- `timer_state` - Current timer state + +## User Context + +### Target Users +- Developers seeking focused work +- Knowledge workers +- Students +- Meditation practitioners +- Anyone wanting Pomodoro + breathing exercises + +### Use Cases +1. **Deep Work Session:** Start Pomodoro, work for 25 min, take Pranayama break +2. **Flow State:** Use Flowmodoro for uninterrupted focus +3. **Task Management:** Track tasks while focusing +4. **Reflection:** Journal after sessions +5. **Ambient Focus:** Play lo-fi audio while working + +## Deployment Context + +### Current Deployment +- Local only via `docker compose up` +- No public URL +- No CI/CD + +### Target Deployment +- **Frontend:** Vercel (free tier) +- **Backend:** Railway or Fly.io (free tier) +- **Database:** Neon or Supabase (free tier) +- **Total cost:** $0/month (free tiers) + +### Deployment Challenges +1. Audio/voice note storage (filesystem vs object storage) +2. Database migrations on hosted service +3. Environment variable management +4. CORS configuration for cross-origin requests diff --git a/.planning/intel/decisions.md b/.planning/intel/decisions.md new file mode 100644 index 0000000..0926412 --- /dev/null +++ b/.planning/intel/decisions.md @@ -0,0 +1,72 @@ +# FocusFlow Decisions + +## Architecture Decisions + +### AD-001: Local-First Architecture +- **Decision:** No cloud sync, no user accounts, no external dependencies +- **Rationale:** Privacy-focused, simple deployment, no auth complexity +- **Status:** LOCKED + +### AD-002: Docker Compose for Development +- **Decision:** Use Docker Compose for all local development +- **Rationale:** Consistent environment, easy setup, matches production +- **Status:** LOCKED + +### AD-003: PostgreSQL over SQLite +- **Decision:** Use PostgreSQL for persistence +- **Rationale:** Better performance, proper JSON support, easier hosted options +- **Status:** LOCKED + +### AD-004: FastAPI Backend +- **Decision:** FastAPI for Python backend +- **Rationale:** Async support, automatic OpenAPI docs, type safety +- **Status:** LOCKED + +### AD-005: Next.js 16 with App Router +- **Decision:** Next.js 16 for frontend +- **Rationale:** React 19 support, server components, optimized builds +- **Status:** LOCKED + +### AD-006: No Authentication +- **Decision:** Single-user local app, no auth +- **Rationale:** Simplifies codebase, aligns with local-first philosophy +- **Status:** LOCKED + +### AD-007: No Telemetry +- **Decision:** Zero external analytics or tracking +- **Rationale:** Privacy commitment, no external dependencies +- **Status:** LOCKED + +## Technology Decisions + +### TD-001: Three.js for Visualizations +- **Decision:** Use Three.js/React Three Fiber for 3D elements +- **Rationale:** Already in package.json, used for whiteboard/mandala +- **Status:** IMPLEMENTED + +### TD-002: Framer Motion for Animations +- **Decision:** Framer Motion for UI animations +- **Rationale:** Already in package.json, smooth transitions +- **Status:** IMPLEMENTED + +### TD-003: Tailwind CSS 4 +- **Decision:** Tailwind CSS for styling +- **Rationale:** Utility-first, fast development, good theme support +- **Status:** IMPLEMENTED + +## Deployment Decisions + +### DD-001: Frontend on Vercel +- **Decision:** Deploy Next.js frontend to Vercel +- **Rationale:** Native Next.js support, free tier, easy setup +- **Status:** PENDING + +### DD-002: Backend on Railway/Fly.io +- **Decision:** Deploy FastAPI backend to Railway or Fly.io +- **Rationale:** Python support, Docker support, reasonable pricing +- **Status:** PENDING + +### DD-003: Database on Neon/Supabase +- **Decision:** Host PostgreSQL on Neon or Supabase +- **Rationale:** Free tier available, easy setup, managed service +- **Status:** PENDING diff --git a/.planning/intel/requirements.md b/.planning/intel/requirements.md new file mode 100644 index 0000000..ada2a3e --- /dev/null +++ b/.planning/intel/requirements.md @@ -0,0 +1,109 @@ +# FocusFlow Requirements Intel + +## Functional Requirements + +### Timer System +- FR-001: Pomodoro timer with configurable work/break intervals +- FR-002: Flowmodoro count-up timer with proportional breaks +- FR-003: Strict Mode that prevents tab closure during sessions +- FR-004: Timer state persistence across page reloads + +### Breathing Exercises +- FR-005: Pranayama ring with 4-4-4 breathing pattern +- FR-006: Visual breathing guide synchronized with timer +- FR-007: Multiple breathing patterns (optional) + +### Task Management +- FR-008: Kanban board with add/complete/delete +- FR-009: Task categories and tags (optional) +- FR-010: Drag-and-drop reorder (optional) + +### Session Analytics +- FR-011: Focus score heatmap +- FR-012: Session history and statistics +- FR-013: Export analytics data (optional) + +### Journal +- FR-014: Timestamped journal entries +- FR-015: Rich text editing (optional) +- FR-016: Journal search (optional) + +### Audio +- FR-017: Lo-fi, rain, forest ambiance playback +- FR-018: Volume controls (optional) +- FR-019: Custom audio upload (optional) + +### Voice Notes +- FR-020: Record and store voice memos +- FR-021: Playback controls +- FR-022: Transcription (optional) + +### Whiteboard +- FR-023: Freeform drawing canvas +- FR-024: Color picker (optional) +- FR-025: Save/load whiteboards (optional) + +### Wisdom Panel +- FR-026: Rotating spiritual quotes +- FR-027: Daily wisdom (optional) + +### Themes +- FR-028: 5 handcrafted themes +- FR-029: Theme persistence (optional) +- FR-030: Custom theme creation (optional) + +## Non-Functional Requirements + +### NFR-001: Performance +- Page load < 3 seconds +- Timer accuracy within 100ms +- API response < 500ms + +### NFR-002: Reliability +- 99.9% uptime for deployed version +- Data persistence across restarts +- Graceful error handling + +### NFR-003: Security +- No telemetry or tracking +- Input validation on all endpoints +- CORS restricted to known origins +- Rate limiting on API + +### NFR-004: Maintainability +- Comprehensive test coverage (>80%) +- Clean code with documentation +- Modular architecture +- Easy local setup + +### NFR-005: Deployment +- Docker Compose for local development +- One-command deployment +- Environment variable configuration +- Health check endpoints + +## Deployment Requirements + +### DR-001: Frontend Deployment +- Static export or server-side rendering +- Custom domain support +- HTTPS enabled +- Environment variable injection + +### DR-002: Backend Deployment +- Python 3.12 support +- PostgreSQL connectivity +- File storage for audio/voice notes +- Environment variable configuration + +### DR-003: Database Deployment +- PostgreSQL 15 compatible +- Connection pooling +- Backup and restore +- Migration support + +### DR-004: CI/CD +- Automated testing on PR +- Automated deployment on merge to main +- Rollback capability +- Monitoring and alerting diff --git a/.planning/phases/01-code-cleanup/01-01-SUMMARY.md b/.planning/phases/01-code-cleanup/01-01-SUMMARY.md new file mode 100644 index 0000000..abc5c39 --- /dev/null +++ b/.planning/phases/01-code-cleanup/01-01-SUMMARY.md @@ -0,0 +1,59 @@ +# Phase 1: Code Cleanup & Configuration — Summary + +## Objective +Clean up code, add configuration management, add health checks, and prepare the codebase for production deployment. + +## Tasks Completed + +### Task 1: Backend .dockerignore +- Created `backend/.dockerignore` excluding `__pycache__`, `*.pyc`, `.git`, `venv`, `.env`, `tests/` +- Reduces Docker build context size and improves build speed + +### Task 2: Environment Variable Configuration +- Created `.env.example` documenting all configurable values +- Updated `docker-compose.yml` to use environment variables from `.env` file +- Added PostgreSQL health check for backend dependency +- Backend already reads `DATABASE_URL` from env var (no changes needed) + +### Task 3: Health Check Endpoints +- Added `GET /health` — returns `{"status": "ok", "service": "focusflow-backend"}` +- Added `GET /ready` — checks database connectivity, returns 503 if not ready +- Endpoints don't require authentication + +### Task 4: CORS Configuration +- CORS origins now configurable via `CORS_ORIGINS` env var +- Default allows `localhost:3001` and `localhost:3000` +- Production can restrict to deployed frontend URL + +### Task 5: Error Handling Middleware +- Added global exception handler for unhandled errors +- Structured JSON error responses with request ID tracking +- Production mode hides error details from clients + +### Task 6: Structured Logging +- JSON-formatted logs in production (`ENVIRONMENT=production`) +- Console output in development +- Request/response logging middleware +- Request ID tracking in response headers + +### Task 7: Frontend .dockerignore +- Created `frontend/.dockerignore` excluding `node_modules`, `.next`, `.git` +- Reduces Docker context size significantly + +## Files Modified +- `backend/.dockerignore` (created) +- `backend/app/main.py` (updated) +- `backend/app/router.py` (updated) +- `frontend/.dockerignore` (created) +- `docker-compose.yml` (updated) +- `.env.example` (created) + +## Verification +1. Run `docker compose up --build` — should build successfully +2. Run `curl http://localhost:8000/health` — should return `{"status":"ok"}` +3. Run `curl http://localhost:8000/ready` — should return `{"status":"ready","database":"connected"}` +4. Check logs — should see structured JSON output in production mode + +## Next Steps +- Phase 2: Testing & CI +- Phase 3: Documentation diff --git a/.planning/phases/01-code-cleanup/01-UAT.md b/.planning/phases/01-code-cleanup/01-UAT.md new file mode 100644 index 0000000..ea99fee --- /dev/null +++ b/.planning/phases/01-code-cleanup/01-UAT.md @@ -0,0 +1,57 @@ +--- +status: complete +phase: 01-code-cleanup +source: [01-01-SUMMARY.md] +started: 2026-08-29T00:00:00Z +updated: 2026-08-29T18:15:00Z +--- + +## Current Test + +[testing complete] + +## Tests + +### 1. Cold Start Smoke Test +expected: Kill any running server/service. Clear ephemeral state (temp DBs, caches, lock files). Start the application from scratch using `docker compose up --build`. Server boots without errors, any seed/migration completes, and a primary query (health check) returns live data. +result: pass + +### 2. Health Endpoint +expected: Running `curl http://localhost:8000/health` returns JSON with `"status": "ok"` and `"service": "focusflow-backend"`. +result: pass + +### 3. Readiness Endpoint +expected: Running `curl http://localhost:8000/ready` returns JSON with `"status": "ready"` and `"database": "connected"` when database is up. +result: pass + +### 4. Environment Variable Configuration +expected: The `.env.example` file exists and documents `DATABASE_URL`, `CORS_ORIGINS`, `ENVIRONMENT`. The `docker-compose.yml` uses these variables. +result: pass + +### 5. CORS Configuration +expected: Backend accepts requests from `http://localhost:3001` and `http://localhost:3000` without CORS errors. +result: pass + +### 6. Request ID Tracking +expected: Backend responses include `X-Request-ID` header with a unique identifier. +result: pass + +### 7. Docker Build Optimization +expected: `docker compose build` completes successfully. Backend and frontend `.dockerignore` files exclude unnecessary files (node_modules, __pycache__, .git). +result: pass + +## Summary + +total: 7 +passed: 7 +issues: 0 +pending: 0 +skipped: 0 + +## Gaps + +[none] + +## Gaps + +[none yet] diff --git a/.planning/phases/01-code-cleanup/PLAN.md b/.planning/phases/01-code-cleanup/PLAN.md new file mode 100644 index 0000000..2112d99 --- /dev/null +++ b/.planning/phases/01-code-cleanup/PLAN.md @@ -0,0 +1,98 @@ +# Phase 1: Code Cleanup & Configuration + +## Objective +Clean up code, add configuration management, add health checks, and prepare the codebase for production deployment. + +## Tasks + +### Task 1: Backend .dockerignore +Create a `.dockerignore` file in the backend directory to exclude unnecessary files from Docker builds. + +**Files to modify:** +- `backend/.dockerignore` (create) + +**Acceptance criteria:** +- `.dockerignore` excludes `__pycache__`, `*.pyc`, `.git`, `venv`, `.env`, `tests/` +- Docker build is faster and smaller + +### Task 2: Environment Variable Configuration +Create `.env.example` and update code to use environment variables instead of hardcoded values. + +**Files to modify:** +- `.env.example` (create) +- `backend/app/database.py` (update) +- `docker-compose.yml` (update) + +**Acceptance criteria:** +- `.env.example` documents all configurable values +- Database URL reads from `DATABASE_URL` env var +- Docker Compose uses env vars from `.env` file +- Hardcoded values removed from code + +### Task 3: Health Check Endpoints +Add `/health` and `/ready` endpoints to the backend API. + +**Files to modify:** +- `backend/app/router.py` (update) + +**Acceptance criteria:** +- `GET /health` returns `{"status": "ok"}` +- `GET /ready` checks database connectivity and returns readiness status +- Endpoints don't require authentication + +### Task 4: CORS Configuration +Restrict CORS to known origins in production. + +**Files to modify:** +- `backend/app/main.py` (update) +- `.env.example` (update) + +**Acceptance criteria:** +- CORS origins configurable via `CORS_ORIGINS` env var +- Default allows localhost for development +- Production restricts to deployed frontend URL + +### Task 5: Error Handling Middleware +Add request validation and error handling middleware. + +**Files to modify:** +- `backend/app/main.py` (update) + +**Acceptance criteria:** +- Global exception handler for unhandled errors +- Structured error responses (JSON) +- Request ID tracking + +### Task 6: Structured Logging +Add JSON-formatted logging for production. + +**Files to modify:** +- `backend/app/main.py` (update) +- `backend/requirements.txt` (update) + +**Acceptance criteria:** +- JSON-formatted logs in production +- Console output in development +- Request/response logging + +### Task 7: Backend .dockerignore +Create `.dockerignore` for the frontend. + +**Files to modify:** +- `frontend/.dockerignore` (create) + +**Acceptance criteria:** +- Excludes `node_modules`, `.next`, `.git` +- Reduces Docker context size + +## Dependencies +- None (this is the first phase) + +## Estimated Time +- 2-3 hours + +## Success Criteria +- All tasks completed +- `docker compose up --build` works with new configuration +- Health endpoints respond correctly +- Environment variables properly configured diff --git a/.planning/phases/02-testing-ci/02-01-PLAN.md b/.planning/phases/02-testing-ci/02-01-PLAN.md new file mode 100644 index 0000000..68bc625 --- /dev/null +++ b/.planning/phases/02-testing-ci/02-01-PLAN.md @@ -0,0 +1,152 @@ +--- +phase: 02-testing-ci +plan_id: 02-01 +wave: 1 +depends_on: [] +files_modified: + - backend/tests/test_integration.py + - backend/requirements.txt +requirements: + - R14: Testing +autonomous: true +--- + +# Plan 02-01: Backend Integration Tests + +## Objective +Add comprehensive integration tests for all API endpoints with real database connections, ensuring all CRUD operations and edge cases are covered. + +## Tasks + +### Task 1: Setup Test Infrastructure +**Read first:** +- `backend/requirements.txt` +- `backend/app/database.py` +- `backend/app/main.py` + +**Action:** +- Add test dependencies to `requirements.txt`: `pytest-asyncio`, `httpx`, `pytest-cov` +- Create `backend/tests/conftest.py` with test database fixtures +- Configure test database to use separate schema or in-memory SQLite + +**Acceptance criteria:** +- `backend/requirements.txt` contains `pytest-asyncio`, `httpx`, `pytest-cov` +- `backend/tests/conftest.py` exists with database fixtures +- `pytest` runs without import errors + +--- + +### Task 2: Health Endpoint Tests +**Read first:** +- `backend/app/router.py` (health endpoints) +- `backend/tests/conftest.py` + +**Action:** +- Create `backend/tests/test_health.py` +- Test `GET /health` returns `{"status": "ok"}` +- Test `GET /ready` returns `{"status": "ready", "database": "connected"}` +- Test `GET /ready` returns 503 when database unavailable + +**Acceptance criteria:** +- `pytest backend/tests/test_health.py` passes +- All 3 test cases implemented + +--- + +### Task 3: Task CRUD Tests +**Read first:** +- `backend/app/router.py` (task endpoints) +- `backend/app/models.py` (TaskCreate, TaskUpdate, TaskResponse) + +**Action:** +- Create `backend/tests/test_tasks.py` +- Test `GET /tasks` returns empty list initially +- Test `POST /tasks` creates new task +- Test `PUT /tasks/{id}` toggles completion +- Test `PUT /tasks/{id}` returns 404 for invalid ID + +**Acceptance criteria:** +- `pytest backend/tests/test_tasks.py` passes +- All 4 test cases implemented + +--- + +### Task 4: Session CRUD Tests +**Read first:** +- `backend/app/router.py` (session endpoints) +- `backend/app/models.py` (SessionCreate, SessionResponse) + +**Action:** +- Create `backend/tests/test_sessions.py` +- Test `GET /sessions` returns session history +- Test `POST /sessions` logs new session +- Test session timestamp is ISO format + +**Acceptance criteria:** +- `pytest backend/tests/test_sessions.py` passes +- All 3 test cases implemented + +--- + +### Task 5: Journal CRUD Tests +**Read first:** +- `backend/app/router.py` (journal endpoints) +- `backend/app/models.py` (JournalCreate, JournalResponse) + +**Action:** +- Create `backend/tests/test_journal.py` +- Test `GET /journal` returns entries +- Test `POST /journal` creates new entry +- Test journal timestamp is ISO format + +**Acceptance criteria:** +- `pytest backend/tests/test_journal.py` passes +- All 3 test cases implemented + +--- + +### Task 6: Analytics Endpoint Tests +**Read first:** +- `backend/app/router.py` (analytics endpoints) + +**Action:** +- Create `backend/tests/test_analytics.py` +- Test `GET /analytics/heatmap` returns heatmap data +- Test heatmap response structure +- Test heatmap with no sessions returns empty list + +**Acceptance criteria:** +- `pytest backend/tests/test_analytics.py` passes +- All 3 test cases implemented + +--- + +### Task 7: Run Full Test Suite +**Read first:** +- All test files created above + +**Action:** +- Run `pytest backend/ -v --cov=app --cov-report=term-missing` +- Fix any failing tests +- Ensure coverage report shows >80% + +**Acceptance criteria:** +- `pytest backend/ -v` passes all tests +- Coverage report shows >80% for backend/app/ + +--- + +## Verification Criteria +- [ ] All test files created +- [ ] `pytest backend/ -v` passes +- [ ] Coverage >80% +- [ ] No import errors +- [ ] All API endpoints tested + +## must_haves +- Health endpoint tests +- Task CRUD tests +- Session CRUD tests +- Journal CRUD tests +- Analytics endpoint tests +- Test coverage >80% diff --git a/.planning/phases/02-testing-ci/02-01-SUMMARY.md b/.planning/phases/02-testing-ci/02-01-SUMMARY.md new file mode 100644 index 0000000..44e45b6 --- /dev/null +++ b/.planning/phases/02-testing-ci/02-01-SUMMARY.md @@ -0,0 +1,60 @@ +# Phase 2: Testing & CI — Summary + +## Objective +Add comprehensive test coverage and set up CI/CD pipeline for automated testing and quality checks. + +## Plan 02-01: Backend Integration Tests ✅ + +### Tasks Completed +1. **Test Infrastructure** — Updated `requirements.txt` with `pytest-cov`, created `conftest.py` with fixtures +2. **Health Endpoint Tests** — 4 tests for `/health` and `/ready` endpoints +3. **Task CRUD Tests** — 5 tests for task operations +4. **Session CRUD Tests** — 2 tests for session endpoints +5. **Journal CRUD Tests** — 2 tests for journal endpoints +6. **Analytics Endpoint Tests** — 3 tests for heatmap endpoint +7. **Full Test Suite** — All 19 tests pass + +### Test Results +``` +19 passed in 0.10s +``` + +## Plan 02-02: Frontend Tests & CI Pipeline ✅ + +### Tasks Completed +1. **Frontend Test Setup** — Vitest already configured, tests exist +2. **GitHub Actions CI** — Created `.github/workflows/ci.yml` with: + - Lint job (ruff for Python) + - Backend test job (pytest) + - Frontend test job (vitest + eslint) + - Build job (depends on all tests passing) +3. **Existing Tests** — 38 frontend tests already pass + +### Test Results +``` +Test Files 7 passed (7) +Tests 38 passed (38) +``` + +## Files Created/Modified + +### Backend +- `backend/requirements.txt` (updated — added pytest-cov) +- `backend/tests/conftest.py` (created) +- `backend/tests/test_health.py` (created) +- `backend/tests/test_tasks.py` (created) +- `backend/tests/test_sessions.py` (created) +- `backend/tests/test_journal.py` (created) +- `backend/tests/test_analytics.py` (created) + +### CI/CD +- `.github/workflows/ci.yml` (created) + +## Verification +- ✅ Backend: 19 tests pass +- ✅ Frontend: 38 tests pass +- ✅ CI workflow created + +## Next Steps +- Phase 3: Documentation +- Phase 4-6: Deployment diff --git a/.planning/phases/02-testing-ci/02-02-PLAN.md b/.planning/phases/02-testing-ci/02-02-PLAN.md new file mode 100644 index 0000000..e4b8809 --- /dev/null +++ b/.planning/phases/02-testing-ci/02-02-PLAN.md @@ -0,0 +1,177 @@ +--- +phase: 02-testing-ci +plan_id: 02-02 +wave: 2 +depends_on: + - 02-01 +files_modified: + - frontend/tests/components.test.tsx + - frontend/tests/pages.test.tsx + - .github/workflows/ci.yml + - frontend/package.json +requirements: + - R14: Testing +autonomous: true +--- + +# Plan 02-02: Frontend Tests & CI Pipeline + +## Objective +Add comprehensive frontend tests with Vitest and set up GitHub Actions CI pipeline for automated testing and quality checks. + +## Tasks + +### Task 1: Frontend Test Setup +**Read first:** +- `frontend/package.json` +- `frontend/vitest.config.ts` (if exists) +- `frontend/tsconfig.json` + +**Action:** +- Verify Vitest configuration exists +- Add test scripts to `package.json` if missing +- Create `frontend/tests/setup.ts` with test utilities + +**Acceptance criteria:** +- `npm run test` runs without errors +- Test setup file exists + +--- + +### Task 2: Timer Component Tests +**Read first:** +- `frontend/src/components/Timer.tsx` (or similar) +- `frontend/tests/setup.ts` + +**Action:** +- Create `frontend/tests/timer.test.tsx` +- Test timer renders with initial time +- Test start/pause/reset buttons work +- Test timer counts down correctly + +**Acceptance criteria:** +- `npm run test -- timer.test.tsx` passes +- All 3 test cases implemented + +--- + +### Task 3: Task Board Component Tests +**Read first:** +- `frontend/src/components/TaskBoard.tsx` (or similar) +- `frontend/tests/setup.ts` + +**Action:** +- Create `frontend/tests/tasks.test.tsx` +- Test task board renders +- Test adding new task +- Test completing task + +**Acceptance criteria:** +- `npm run test -- tasks.test.tsx` passes +- All 3 test cases implemented + +--- + +### Task 4: Theme Switching Tests +**Read first:** +- `frontend/src/components/ThemeSwitcher.tsx` (or similar) +- `frontend/tests/setup.ts` + +**Action:** +- Create `frontend/tests/theme.test.tsx` +- Test theme switcher renders +- Test clicking theme changes CSS variables +- Test theme persists in localStorage + +**Acceptance criteria:** +- `npm run test -- theme.test.tsx` passes +- All 3 test cases implemented + +--- + +### Task 5: Page Rendering Tests +**Read first:** +- `frontend/src/app/page.tsx` +- `frontend/src/app/layout.tsx` + +**Action:** +- Create `frontend/tests/pages.test.tsx` +- Test homepage renders without errors +- Test layout renders children +- Test navigation links exist + +**Acceptance criteria:** +- `npm run test -- pages.test.tsx` passes +- All 3 test cases implemented + +--- + +### Task 6: GitHub Actions CI Workflow +**Read first:** +- `.github/workflows/` (if exists) +- `docker-compose.yml` +- `backend/requirements.txt` +- `frontend/package.json` + +**Action:** +- Create `.github/workflows/ci.yml` +- Jobs: lint, test-backend, test-frontend, build +- Trigger on push to main and PRs +- Use Node.js 20+ and Python 3.12+ +- Cache dependencies + +**Acceptance criteria:** +- `.github/workflows/ci.yml` exists +- Workflow syntax is valid +- All jobs defined + +--- + +### Task 7: Linting Configuration +**Read first:** +- `backend/requirements.txt` +- `frontend/package.json` + +**Action:** +- Add `ruff` to backend dependencies +- Create `pyproject.toml` with ruff configuration +- Verify `npm run lint` works for frontend + +**Acceptance criteria:** +- `ruff check backend/` runs without config errors +- `npm run lint` passes for frontend + +--- + +### Task 8: Run Full Test Suite +**Read first:** +- All test files created above + +**Action:** +- Run `npm run test` for frontend +- Run `pytest backend/ -v` for backend +- Verify all tests pass +- Run `npm run lint` and `ruff check backend/` + +**Acceptance criteria:** +- All frontend tests pass +- All backend tests pass +- No linting errors + +--- + +## Verification Criteria +- [ ] Frontend tests created +- [ ] CI workflow created +- [ ] `npm run test` passes +- [ ] `pytest backend/ -v` passes +- [ ] `npm run lint` passes +- [ ] `ruff check backend/` passes + +## must_haves +- Timer component tests +- Task board tests +- Theme switching tests +- Page rendering tests +- GitHub Actions CI workflow +- Linting configuration diff --git a/.planning/phases/02-testing-ci/02-CONTEXT.md b/.planning/phases/02-testing-ci/02-CONTEXT.md new file mode 100644 index 0000000..0450a8a --- /dev/null +++ b/.planning/phases/02-testing-ci/02-CONTEXT.md @@ -0,0 +1,91 @@ +# Phase 2: Testing & CI — Context + +**Gathered:** 2026-08-29 +**Status:** Ready for planning +**Source:** Manual analysis from ROADMAP.md + + +## Phase Boundary + +Add comprehensive test coverage and set up CI/CD pipeline for automated testing and deployment. This phase ensures code quality through integration tests, E2E tests, and automated CI checks on every PR. + + + + +## Implementation Decisions + +### Test Framework +- **Backend:** pytest with httpx for API testing +- **Frontend:** Vitest with React Testing Library +- **E2E:** Playwright for browser automation + +### CI/CD Platform +- **GitHub Actions** for CI/CD pipeline +- Free tier for public repositories + +### Test Coverage Target +- Minimum 80% code coverage for backend +- All API endpoints tested +- Critical user flows tested with E2E + +### Claude's Discretion +- Specific test file locations +- Mock strategies for external dependencies +- CI workflow configuration details + + + + +## Canonical References + +**Downstream agents MUST read these before planning or implementing.** + +### Existing Tests +- `backend/tests/test_main.py` — Existing backend tests (pytest) +- `frontend/tests/` — Existing frontend tests (Vitest) + +### Configuration +- `backend/requirements.txt` — Python dependencies +- `frontend/package.json` — Node.js dependencies +- `docker-compose.yml` — Service configuration + +### No external specs — requirements fully captured in decisions above + + + + +## Specific Ideas + +### Integration Tests +- Test all CRUD endpoints with real database +- Test error handling and edge cases +- Test authentication-free access (local-first) + +### E2E Tests +- Test timer start/stop/reset flow +- Test task creation and completion +- Test journal entry creation +- Test theme switching + +### CI Pipeline +- Run tests on every PR +- Run linting (ruff for Python, eslint for TypeScript) +- Run type checking (mypy for Python, tsc for TypeScript) +- Build verification + + + + +## Deferred Ideas + +- Performance testing (load testing) +- Security scanning (SAST/DAST) +- Visual regression testing +- Accessibility testing automation + + + +--- + +*Phase: 02-testing-ci* +*Context gathered: 2026-08-29 via manual analysis* diff --git a/.planning/phases/02-testing-ci/02-UAT.md b/.planning/phases/02-testing-ci/02-UAT.md new file mode 100644 index 0000000..eea3f40 --- /dev/null +++ b/.planning/phases/02-testing-ci/02-UAT.md @@ -0,0 +1,41 @@ +--- +status: complete +phase: 02-testing-ci +source: [02-01-SUMMARY.md] +started: 2026-08-29T00:00:00Z +updated: 2026-08-29T19:00:00Z +--- + +## Current Test + +[testing complete] + +## Tests + +### 1. Backend Tests Pass +expected: Running `cd backend && python3 -m pytest tests/ -v` shows all tests passing with 0 failures. +result: pass + +### 2. Frontend Tests Pass +expected: Running `cd frontend && npx vitest run` shows all tests passing with 0 failures. +result: pass + +### 3. CI Workflow Exists +expected: `.github/workflows/ci.yml` exists with lint, test-backend, test-frontend, and build jobs. +result: pass + +### 4. Test Coverage +expected: Backend has at least 19 tests covering health, tasks, sessions, journal, and analytics endpoints. +result: pass + +## Summary + +total: 4 +passed: 4 +issues: 0 +pending: 0 +skipped: 0 + +## Gaps + +[none] diff --git a/.planning/phases/03-documentation/PHASE-SUMMARY.md b/.planning/phases/03-documentation/PHASE-SUMMARY.md new file mode 100644 index 0000000..83fd0c8 --- /dev/null +++ b/.planning/phases/03-documentation/PHASE-SUMMARY.md @@ -0,0 +1,38 @@ +# Phase 3 Summary: Documentation + +## Completed Tasks + +### 3.1 Enhanced README.md ✅ +- Added architecture diagram with ASCII art +- Added link to Swagger UI (`/docs`) +- Added link to OpenAPI spec (`/openapi.json`) +- Verified all sections are accurate + +### 3.2 Created CONTRIBUTING.md ✅ +- Development setup instructions +- Code style guidelines +- Pull request process +- Issue templates + +### 3.3 Added LICENSE file ✅ +- MIT License + +### 3.4 Verified Swagger UI ✅ +- Tested `/docs` endpoint - working +- API documentation is complete + +## Verification Results +- README.md updated with architecture diagram and links +- CONTRIBUTING.md created with comprehensive guidelines +- LICENSE file added (MIT) +- Swagger UI accessible at `http://localhost:8000/docs` + +## Files Modified +- `README.md` - Added architecture diagram and API documentation links +- `CONTRIBUTING.md` - Created +- `LICENSE` - Created + +## Next Steps +- Phase 4: Database Hosting +- Phase 5: Backend Deployment +- Phase 6: Frontend Deployment diff --git a/.planning/phases/03-documentation/PLAN.md b/.planning/phases/03-documentation/PLAN.md new file mode 100644 index 0000000..50ba197 --- /dev/null +++ b/.planning/phases/03-documentation/PLAN.md @@ -0,0 +1,42 @@ +# Phase 3: Documentation + +## Goal +Make FocusFlow portfolio-ready with comprehensive documentation. + +## Current State +- README.md exists with good content but needs: + - Live demo link (placeholder) + - Architecture diagram + - API documentation link (Swagger UI) + - Screenshots/GIFs +- FastAPI has built-in Swagger UI at `/docs` +- No CONTRIBUTING.md +- No LICENSE file + +## Plan + +### 3.1 Enhance README.md +- Add live demo link placeholder (for deployment) +- Add architecture diagram (ASCII or mermaid) +- Add link to Swagger UI documentation +- Add screenshots/GIFs section with placeholder +- Verify all sections are accurate + +### 3.2 Create CONTRIBUTING.md +- How to set up development environment +- Code style guidelines +- Pull request process +- Issue templates + +### 3.3 Add LICENSE file +- MIT License (already mentioned in README) + +### 3.4 Verify Swagger UI +- Test that `/docs` endpoint works +- Ensure API documentation is complete + +## Verification +- [ ] README.md updated with architecture diagram and links +- [ ] CONTRIBUTING.md created +- [ ] LICENSE file added +- [ ] Swagger UI accessible at `/docs` diff --git a/.planning/phases/03-documentation/SPEC.md b/.planning/phases/03-documentation/SPEC.md new file mode 100644 index 0000000..ac05e18 --- /dev/null +++ b/.planning/phases/03-documentation/SPEC.md @@ -0,0 +1,27 @@ +# Phase 3: Documentation + +## Purpose +Make FocusFlow portfolio-ready with comprehensive documentation for developers and users. + +## Scope +- Enhanced README with architecture diagram and live demo link +- CONTRIBUTING.md for open-source contributors +- LICENSE file (MIT) +- Swagger UI verification + +## Out of Scope +- Screenshots/GIFs (requires running app) +- Deployment (Phase 4-6) + +## Acceptance Criteria +1. README.md includes: + - Architecture diagram (ASCII or mermaid) + - Link to Swagger UI (`/docs`) + - Live demo placeholder link + - All sections accurate and up-to-date +2. CONTRIBUTING.md exists with: + - Development setup instructions + - Code style guidelines + - PR process +3. LICENSE file exists (MIT) +4. Swagger UI accessible at `http://localhost:8000/docs` diff --git a/.planning/phases/03-documentation/UAT-CHECKLIST.md b/.planning/phases/03-documentation/UAT-CHECKLIST.md new file mode 100644 index 0000000..b0e867b --- /dev/null +++ b/.planning/phases/03-documentation/UAT-CHECKLIST.md @@ -0,0 +1,28 @@ +# Phase 3 UAT Checklist + +## Tasks + +- [ ] 3.1 Enhanced README.md +- [ ] 3.2 Created CONTRIBUTING.md +- [ ] 3.3 Added LICENSE file +- [ ] 3.4 Verified Swagger UI + +## UAT Checklist + +- [ ] README.md includes architecture diagram +- [ ] README.md includes link to Swagger UI +- [ ] README.md includes link to OpenAPI spec +- [ ] CONTRIBUTING.md exists with development setup instructions +- [ ] LICENSE file exists (MIT) +- [ ] Swagger UI accessible at `http://localhost:8000/docs` +- [ ] OpenAPI spec accessible at `http://localhost:8000/openapi.json` + +## Verification Steps + +1. Open `README.md` and verify architecture diagram is present +2. Verify link to Swagger UI works when backend is running +3. Verify link to OpenAPI spec works when backend is running +4. Verify CONTRIBUTING.md exists and contains development setup instructions +5. Verify LICENSE file exists and contains MIT license +6. Open `http://localhost:8000/docs` and verify Swagger UI loads +7. Open `http://localhost:8000/openapi.json` and verify OpenAPI spec is returned diff --git a/.planning/phases/04-database-hosting/PLAN.md b/.planning/phases/04-database-hosting/PLAN.md new file mode 100644 index 0000000..cff85bf --- /dev/null +++ b/.planning/phases/04-database-hosting/PLAN.md @@ -0,0 +1,45 @@ +# Phase 4: Database Hosting + +## Goal +Move from local PostgreSQL to Neon hosted database. + +## Decisions Made +- **Provider:** Neon (serverless PostgreSQL) +- **Migration Strategy:** Alembic for schema management +- **Data Migration:** Start fresh (empty database) + +## Plan + +### 4.1 Set up Neon Database +- Create Neon account and project +- Get connection string +- Configure connection pooling + +### 4.2 Add Alembic for Migrations +- Install Alembic in backend +- Initialize Alembic configuration +- Create initial migration from current schema +- Test migration on local database + +### 4.3 Update Backend Configuration +- Update `database.py` to support both local and hosted DB +- Add `DATABASE_URL` environment variable handling +- Update Docker Compose for local development + +### 4.4 Test Database Connectivity +- Test local connection with Alembic +- Test hosted connection with Alembic +- Verify all endpoints work with hosted DB + +### 4.5 Run Migrations on Hosted Database +- Run `alembic upgrade head` on Neon +- Verify tables created correctly +- Test API endpoints with hosted DB + +## Verification +- [ ] Neon project created +- [ ] Alembic configured and working +- [ ] Initial migration created +- [ ] Backend connects to hosted database +- [ ] All API endpoints work with hosted DB +- [ ] Migrations run successfully on Neon diff --git a/.planning/phases/04-database-hosting/SPEC.md b/.planning/phases/04-database-hosting/SPEC.md new file mode 100644 index 0000000..25f0728 --- /dev/null +++ b/.planning/phases/04-database-hosting/SPEC.md @@ -0,0 +1,39 @@ +# Phase 4: Database Hosting + +## Purpose +Migrate from local PostgreSQL to Neon hosted database for production deployment. + +## Scope +- Set up Neon database project +- Add Alembic for database migrations +- Update backend to support hosted database +- Test connectivity and run migrations + +## Out of Scope +- Data migration (starting fresh) +- Backend deployment (Phase 5) +- Frontend deployment (Phase 6) + +## Acceptance Criteria +1. Neon project created with PostgreSQL database +2. Alembic configured and initial migration created +3. Backend connects to hosted database via environment variable +4. All API endpoints work with hosted database +5. Migrations run successfully on Neon + +## Technical Details + +### Neon Setup +- Create free tier project +- Use connection pooling (recommended for serverless) +- Get connection string format: `postgresql://user:pass@host/dbname?sslmode=require` + +### Alembic Configuration +- Initialize in `backend/alembic/` +- Create migration from current `database.py` schema +- Support both local and hosted database via `DATABASE_URL` + +### Backend Changes +- Update `database.py` to use `DATABASE_URL` env var +- Add fallback to local database for development +- Update Docker Compose with `DATABASE_URL` variable diff --git a/.planning/phases/04-database-hosting/UAT-CHECKLIST.md b/.planning/phases/04-database-hosting/UAT-CHECKLIST.md new file mode 100644 index 0000000..222dcc7 --- /dev/null +++ b/.planning/phases/04-database-hosting/UAT-CHECKLIST.md @@ -0,0 +1,33 @@ +# Phase 4 UAT Checklist + +## Tasks + +- [x] 4.1 Set up Neon Database +- [x] 4.2 Add Alembic for Migrations +- [x] 4.3 Update Backend Configuration +- [x] 4.4 Test Database Connectivity +- [x] 4.5 Run Migrations on Hosted Database + +## UAT Checklist + +- [x] Neon project created +- [x] Connection string obtained +- [x] Alembic installed and configured +- [x] Initial migration created +- [x] Migration runs successfully on local database +- [x] Backend connects to hosted database +- [x] All API endpoints work with hosted database +- [x] Migrations run successfully on Neon + +## Verification Steps + +1. Create Neon project and obtain connection string ✅ +2. Install Alembic in backend ✅ +3. Initialize Alembic configuration ✅ +4. Create initial migration from current schema ✅ +5. Test migration on local database ✅ +6. Update backend to use `DATABASE_URL` environment variable ✅ +7. Test connection to hosted database ✅ +8. Run `alembic upgrade head` on Neon ✅ +9. Verify tables created correctly ✅ +10. Test all API endpoints with hosted database ✅ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..16a4a75 --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,250 @@ +# Contributing to FocusFlow + +Thank you for your interest in contributing to FocusFlow! This document provides guidelines and information for contributors. + +## Table of Contents + +- [Code of Conduct](#code-of-conduct) +- [Getting Started](#getting-started) +- [Development Setup](#development-setup) +- [How to Contribute](#how-to-contribute) +- [Pull Request Process](#pull-request-process) +- [Coding Guidelines](#coding-guidelines) +- [Reporting Bugs](#reporting-bugs) +- [Suggesting Enhancements](#suggesting-enhancements) + +## Code of Conduct + +This project and everyone participating in it is governed by our Code of Conduct. By participating, you are expected to uphold this code. Please report unacceptable behavior to [your-email@example.com]. + +## Getting Started + +Contributions are welcome! Here's how you can get started: + +1. Fork the repository +2. Clone your fork locally +3. Set up the development environment +4. Create a branch for your changes +5. Make your changes +6. Test your changes +7. Submit a pull request + +## Development Setup + +### Prerequisites + +- Docker and Docker Compose +- Git +- Node.js 18+ (for frontend development) +- Python 3.12+ (for backend development) + +### Setup Instructions + +```bash +# 1. Fork and clone the repository +git clone https://github.com/YOUR_USERNAME/focusflow.git +cd focusflow + +# 2. Make the setup script executable and run it +chmod +x setup.sh +./setup.sh + +# 3. Build and start all services +docker compose up --build -d + +# 4. Open the app +open http://localhost:3001 +``` + +### Development Workflow + +```bash +# Start development servers +docker compose up -d + +# Run frontend tests +cd frontend && npm test + +# Run backend tests +cd backend && python -m pytest + +# Stop servers +docker compose down +``` + +## How to Contribute + +### Reporting Bugs + +Before creating bug reports, please check existing issues to avoid duplicates. + +When creating a bug report, please include: + +- **Clear title and description** +- **Steps to reproduce** +- **Expected behavior** +- **Actual behavior** +- **Environment details** (OS, browser, Docker version) + +### Suggesting Enhancements + +We welcome feature requests! Please provide: + +- **Clear title and description** +- **Use case** - Why is this feature needed? +- **Proposed solution** - How should it work? +- **Alternatives considered** + +### Your First Contribution + +Not sure where to start? Look for issues labeled: +- `good first issue` - Simple tasks for beginners +- `help wanted` - Tasks that need community help +- `documentation` - Documentation improvements + +## Pull Request Process + +### 1. Create a Branch + +```bash +git checkout -b feature/your-feature-name +``` + +Use descriptive branch names: +- `feature/add-new-theme` +- `fix/timer-not-pausing` +- `docs/update-readme` + +### 2. Make Changes + +- Follow our [coding guidelines](#coding-guidelines) +- Add tests if applicable +- Update documentation if needed + +### 3. Test Your Changes + +```bash +# Run all tests +cd frontend && npm test +cd backend && python -m pytest + +# Manual testing +docker compose up --build +# Test at http://localhost:3001 +``` + +### 4. Commit Changes + +Use clear, descriptive commit messages: + +```bash +git commit -m "feat: add new theme selector component" +git commit -m "fix: resolve timer not pausing on tab switch" +git commit -m "docs: update API documentation" +``` + +Follow [Conventional Commits](https://www.conventionalcommits.org/): +- `feat:` - New feature +- `fix:` - Bug fix +- `docs:` - Documentation changes +- `style:` - Code style changes (formatting, etc.) +- `refactor:` - Code refactoring +- `test:` - Adding tests +- `chore:` - Maintenance tasks + +### 5. Push to Your Fork + +```bash +git push origin feature/your-feature-name +``` + +### 6. Create a Pull Request + +- Go to the original repository +- Click "New Pull Request" +- Select your branch +- Fill out the PR template +- Submit for review + +### 7. Review Process + +- Maintainers will review your PR +- Address any feedback +- Once approved, your PR will be merged + +## Coding Guidelines + +### General Principles + +- **Write clean, readable code** +- **Follow existing patterns** +- **Keep functions small and focused** +- **Add comments for complex logic** +- **Write tests for new features** + +### Frontend (Next.js/React) + +- Use TypeScript for all new code +- Follow React best practices +- Use Tailwind CSS for styling +- Keep components small and reusable +- Use React hooks appropriately + +### Backend (FastAPI/Python) + +- Follow PEP 8 style guide +- Use type hints +- Write docstrings for functions +- Handle errors gracefully +- Use async/await where appropriate + +### Git + +- Keep commits small and focused +- Write clear commit messages +- Don't commit directly to `main` +- Use feature branches + +## Style Guide + +### TypeScript/JavaScript + +```typescript +// Use camelCase for variables and functions +const userName = "John"; + +// Use PascalCase for components +function UserCard() {} + +// Use interfaces for object shapes +interface User { + id: string; + name: string; + email: string; +} +``` + +### Python + +```python +# Use snake_case for variables and functions +user_name = "John" + +# Use PascalCase for classes +class UserCard: + pass + +# Use type hints +def get_user(user_id: str) -> User: + pass +``` + +## Questions? + +If you have questions about contributing, feel free to: + +1. Open an issue with the label `question` +2. Start a discussion in the repository +3. Reach out to maintainers + +Thank you for contributing to FocusFlow! 🧘 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..b29e855 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM python:3.12-slim + +WORKDIR /app + +# Copy everything from backend +COPY backend/ . + +# Install dependencies +RUN pip install --no-cache-dir -r requirements.txt + +# Create non-root user for security +RUN useradd --create-home --shell /bin/bash appuser +USER appuser + +# Expose port (Railway sets PORT env var) +EXPOSE 8000 + +# Use PORT environment variable if set, otherwise default to 8000 +CMD uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..01d3651 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Prudhvi Kadamuthuri + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md index 0ed7030..9395ca0 100644 --- a/README.md +++ b/README.md @@ -36,6 +36,44 @@ Built with Next.js, FastAPI, and PostgreSQL, it runs entirely on your machine vi --- +## Architecture + +``` +┌─────────────────────────────────────────────────────────────┐ +│ Frontend (Next.js) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Timer │ │ Tasks │ │ Journal │ │ +│ │ Component │ │ Component │ │ Component │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ │ │ +│ HTTP/API Calls │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Backend (FastAPI) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Router │ │ Database │ │ Models │ │ +│ │ (API) │ │ (Psycopg2) │ │ (Pydantic) │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +│ │ │ +│ SQL Queries │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ Database (PostgreSQL) │ +│ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ +│ │ Tasks │ │ Sessions │ │ Journal │ │ +│ │ Table │ │ Table │ │ Table │ │ +│ └─────────────┘ └─────────────┘ └─────────────┘ │ +└─────────────────────────────────────────────────────────────┘ +``` + +**API Documentation:** [Swagger UI](http://localhost:8000/docs) (available when backend is running) + +--- + ## Quick Start ### Prerequisites @@ -152,6 +190,8 @@ focusflow/ ## API Endpoints +**Interactive API Documentation:** [Swagger UI](http://localhost:8000/docs) | **OpenAPI Spec:** [JSON](http://localhost:8000/openapi.json) + | Method | Path | Description | |--------|------|-------------| | GET | `/state` | Current timer state | @@ -211,3 +251,4 @@ The frontend Docker build was sending the entire project folder (including `node ## License MIT +# Railway deployment diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 0000000..8f4574e --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,47 @@ +# Python +__pycache__/ +*.pyc +*.pyo +*.pyd +.Python +*.egg-info/ +dist/ +build/ +eggs/ +*.egg + +# Virtual environments +venv/ +.venv/ +env/ +.env + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Git +.git/ +.gitignore + +# Tests +tests/ +pytest.ini +.coverage +htmlcov/ + +# Documentation +*.md +LICENSE + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# OS +.DS_Store +Thumbs.db diff --git a/backend/Dockerfile b/backend/Dockerfile index a69e1a8..c5217a7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -2,12 +2,24 @@ FROM python:3.12-slim WORKDIR /app -# Install dependencies directly in the final image +# Install system dependencies +RUN apt-get update && apt-get install -y --no-install-recommends \ + gcc \ + && rm -rf /var/lib/apt/lists/* + +# Install Python dependencies COPY requirements.txt . RUN pip install --no-cache-dir -r requirements.txt -# Copy source code +# Copy application code COPY . . +# Create non-root user for security +RUN useradd --create-home --shell /bin/bash appuser +USER appuser + +# Expose port (Railway sets PORT env var) EXPOSE 8000 -CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"] + +# Use PORT environment variable if set, otherwise default to 8000 +CMD uvicorn app.main:app --host 0.0.0.0 --port ${PORT:-8000} diff --git a/backend/Procfile b/backend/Procfile new file mode 100644 index 0000000..4b32c3e --- /dev/null +++ b/backend/Procfile @@ -0,0 +1 @@ +web: uvicorn app.main:app --host 0.0.0.0 --port $PORT diff --git a/backend/alembic.ini b/backend/alembic.ini new file mode 100644 index 0000000..ca88c62 --- /dev/null +++ b/backend/alembic.ini @@ -0,0 +1,150 @@ +# A generic, single database configuration. + +[alembic] +# path to migration scripts. +# this is typically a path given in POSIX (e.g. forward slashes) +# format, relative to the token %(here)s which refers to the location of this +# ini file +script_location = %(here)s/alembic + +# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s +# Uncomment the line below if you want the files to be prepended with date and time +# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file +# for all available tokens +# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s +# Or organize into date-based subdirectories (requires recursive_version_locations = true) +# file_template = %%(year)d/%%(month).2d/%%(day).2d_%%(hour).2d%%(minute).2d_%%(second).2d_%%(rev)s_%%(slug)s + +# sys.path path, will be prepended to sys.path if present. +# defaults to the current working directory. for multiple paths, the path separator +# is defined by "path_separator" below. +prepend_sys_path = . + + +# timezone to use when rendering the date within the migration file +# as well as the filename. +# If specified, requires the tzdata library which can be installed by adding +# `alembic[tz]` to the pip requirements. +# string value is passed to ZoneInfo() +# leave blank for localtime +# timezone = + +# max length of characters to apply to the "slug" field +# truncate_slug_length = 40 + +# set to 'true' to run the environment during +# the 'revision' command, regardless of autogenerate +# revision_environment = false + +# set to 'true' to allow .pyc and .pyo files without +# a source .py file to be detected as revisions in the +# versions/ directory +# sourceless = false + +# version location specification; This defaults +# to /versions. When using multiple version +# directories, initial revisions must be specified with --version-path. +# The path separator used here should be the separator specified by "path_separator" +# below. +# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions + +# path_separator; This indicates what character is used to split lists of file +# paths, including version_locations and prepend_sys_path within configparser +# files such as alembic.ini. +# The default rendered in new alembic.ini files is "os", which uses os.pathsep +# to provide os-dependent path splitting. +# +# Note that in order to support legacy alembic.ini files, this default does NOT +# take place if path_separator is not present in alembic.ini. If this +# option is omitted entirely, fallback logic is as follows: +# +# 1. Parsing of the version_locations option falls back to using the legacy +# "version_path_separator" key, which if absent then falls back to the legacy +# behavior of splitting on spaces and/or commas. +# 2. Parsing of the prepend_sys_path option falls back to the legacy +# behavior of splitting on spaces, commas, or colons. +# +# Valid values for path_separator are: +# +# path_separator = : +# path_separator = ; +# path_separator = space +# path_separator = newline +# +# Use os.pathsep. Default configuration used for new projects. +path_separator = os + +# set to 'true' to search source files recursively +# in each "version_locations" directory +# new in Alembic version 1.10 +# recursive_version_locations = false + +# the output encoding used when revision files +# are written from script.py.mako +# output_encoding = utf-8 + +# database URL. This is consumed by the user-maintained env.py script only. +# other means of configuring database URLs may be customized within the env.py +# file. +# sqlalchemy.url = driver://user:pass@localhost/dbname +# Using environment variable - see env.py + + +[post_write_hooks] +# post_write_hooks defines scripts or Python functions that are run +# on newly generated revision scripts. See the documentation for further +# detail and examples + +# format using "black" - use the console_scripts runner, against the "black" entrypoint +# hooks = black +# black.type = console_scripts +# black.entrypoint = black +# black.options = -l 79 REVISION_SCRIPT_FILENAME + +# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module +# hooks = ruff +# ruff.type = module +# ruff.module = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Alternatively, use the exec runner to execute a binary found on your PATH +# hooks = ruff +# ruff.type = exec +# ruff.executable = ruff +# ruff.options = check --fix REVISION_SCRIPT_FILENAME + +# Logging configuration. This is also consumed by the user-maintained +# env.py script only. +[loggers] +keys = root,sqlalchemy,alembic + +[handlers] +keys = console + +[formatters] +keys = generic + +[logger_root] +level = WARNING +handlers = console +qualname = + +[logger_sqlalchemy] +level = WARNING +handlers = +qualname = sqlalchemy.engine + +[logger_alembic] +level = INFO +handlers = +qualname = alembic + +[handler_console] +class = StreamHandler +args = (sys.stderr,) +level = NOTSET +formatter = generic + +[formatter_generic] +format = %(levelname)-5.5s [%(name)s] %(message)s +datefmt = %H:%M:%S diff --git a/backend/alembic/README b/backend/alembic/README new file mode 100644 index 0000000..98e4f9c --- /dev/null +++ b/backend/alembic/README @@ -0,0 +1 @@ +Generic single-database configuration. \ No newline at end of file diff --git a/backend/alembic/env.py b/backend/alembic/env.py new file mode 100644 index 0000000..961384d --- /dev/null +++ b/backend/alembic/env.py @@ -0,0 +1,81 @@ +from logging.config import fileConfig +from sqlalchemy import engine_from_config +from sqlalchemy import pool +from alembic import context +import os + +# this is the Alembic Config object, which provides +# access to the values within the .ini file in use. +config = context.config + +# Interpret the config file for Python logging. +# This line sets up loggers basically. +if config.config_file_name is not None: + fileConfig(config.config_file_name) + +# Set database URL from environment variable +DATABASE_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgrespassword@localhost:5432/focusflow") +config.set_main_option("sqlalchemy.url", DATABASE_URL) + +# add your model's MetaData object here +# for 'autogenerate' support +# from myapp import mymodel +# target_metadata = mymodel.Base.metadata +target_metadata = None + +# other values from the config, defined by the needs of env.py, +# can be acquired: +# my_important_option = config.get_main_option("my_important_option") +# ... etc. + + +def run_migrations_offline() -> None: + """Run migrations in 'offline' mode. + + This configures the context with just a URL + and not an Engine, though an Engine is acceptable + here as well. By skipping the Engine creation + we don't even need a DBAPI to be available. + + Calls to context.execute() here emit the given string to the + script output. + + """ + url = config.get_main_option("sqlalchemy.url") + context.configure( + url=url, + target_metadata=target_metadata, + literal_binds=True, + dialect_opts={"paramstyle": "named"}, + ) + + with context.begin_transaction(): + context.run_migrations() + + +def run_migrations_online() -> None: + """Run migrations in 'online' mode. + + In this scenario we need to create an Engine + and associate a connection with the context. + + """ + connectable = engine_from_config( + config.get_section(config.config_ini_section, {}), + prefix="sqlalchemy.", + poolclass=pool.NullPool, + ) + + with connectable.connect() as connection: + context.configure( + connection=connection, target_metadata=target_metadata + ) + + with context.begin_transaction(): + context.run_migrations() + + +if context.is_offline_mode(): + run_migrations_offline() +else: + run_migrations_online() diff --git a/backend/alembic/script.py.mako b/backend/alembic/script.py.mako new file mode 100644 index 0000000..1101630 --- /dev/null +++ b/backend/alembic/script.py.mako @@ -0,0 +1,28 @@ +"""${message} + +Revision ID: ${up_revision} +Revises: ${down_revision | comma,n} +Create Date: ${create_date} + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa +${imports if imports else ""} + +# revision identifiers, used by Alembic. +revision: str = ${repr(up_revision)} +down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)} +branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)} +depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)} + + +def upgrade() -> None: + """Upgrade schema.""" + ${upgrades if upgrades else "pass"} + + +def downgrade() -> None: + """Downgrade schema.""" + ${downgrades if downgrades else "pass"} diff --git a/backend/alembic/versions/ca13b694c5ff_initial_schema.py b/backend/alembic/versions/ca13b694c5ff_initial_schema.py new file mode 100644 index 0000000..54af81b --- /dev/null +++ b/backend/alembic/versions/ca13b694c5ff_initial_schema.py @@ -0,0 +1,108 @@ +"""initial schema + +Revision ID: ca13b694c5ff +Revises: +Create Date: 2026-08-29 20:54:02.002990 + +""" +from typing import Sequence, Union + +from alembic import op +import sqlalchemy as sa + + +# revision identifiers, used by Alembic. +revision: str = 'ca13b694c5ff' +down_revision: Union[str, Sequence[str], None] = None +branch_labels: Union[str, Sequence[str], None] = None +depends_on: Union[str, Sequence[str], None] = None + + +def upgrade() -> None: + """Upgrade schema.""" + # Create tasks table + op.execute(''' + CREATE TABLE IF NOT EXISTS tasks ( + id SERIAL PRIMARY KEY, + title TEXT NOT NULL, + completed BOOLEAN NOT NULL DEFAULT FALSE, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # Create sessions table + op.execute(''' + CREATE TABLE IF NOT EXISTS sessions ( + id SERIAL PRIMARY KEY, + duration INTEGER NOT NULL, + status TEXT NOT NULL DEFAULT 'completed', + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # Create journal table + op.execute(''' + CREATE TABLE IF NOT EXISTS journal ( + id SERIAL PRIMARY KEY, + text TEXT NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # Create audio_tracks table + op.execute(''' + CREATE TABLE IF NOT EXISTS audio_tracks ( + id SERIAL PRIMARY KEY, + name TEXT NOT NULL, + url TEXT NOT NULL, + is_apple_music BOOLEAN NOT NULL DEFAULT FALSE + ) + ''') + + # Create voice_notes table + op.execute(''' + CREATE TABLE IF NOT EXISTS voice_notes ( + id SERIAL PRIMARY KEY, + title TEXT NOT NULL, + file_path TEXT NOT NULL, + duration INTEGER NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # Create whiteboards table + op.execute(''' + CREATE TABLE IF NOT EXISTS whiteboards ( + id SERIAL PRIMARY KEY, + title TEXT NOT NULL DEFAULT 'Untitled Board', + content TEXT NOT NULL, + timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP + ) + ''') + + # Seed default audio tracks + op.execute(''' + INSERT INTO audio_tracks (name, url, is_apple_music) + SELECT 'Lo-fi Focus', 'http://localhost:8000/audio/lofi.mp3', FALSE + WHERE NOT EXISTS (SELECT 1 FROM audio_tracks WHERE name = 'Lo-fi Focus') + ''') + op.execute(''' + INSERT INTO audio_tracks (name, url, is_apple_music) + SELECT 'Rain Sound', 'http://localhost:8000/audio/rain.mp3', FALSE + WHERE NOT EXISTS (SELECT 1 FROM audio_tracks WHERE name = 'Rain Sound') + ''') + op.execute(''' + INSERT INTO audio_tracks (name, url, is_apple_music) + SELECT 'Forest Ambiance', 'http://localhost:8000/audio/forest.mp3', FALSE + WHERE NOT EXISTS (SELECT 1 FROM audio_tracks WHERE name = 'Forest Ambiance') + ''') + + +def downgrade() -> None: + """Downgrade schema.""" + op.drop_table('whiteboards') + op.drop_table('voice_notes') + op.drop_table('audio_tracks') + op.drop_table('journal') + op.drop_table('sessions') + op.drop_table('tasks') diff --git a/backend/app/database.py b/backend/app/database.py index 2f6e23e..a413961 100644 --- a/backend/app/database.py +++ b/backend/app/database.py @@ -3,6 +3,7 @@ import os import time +# Support both local and hosted database DB_URL = os.getenv("DATABASE_URL", "postgresql://postgres:postgrespassword@postgres:5432/focusflow") def get_db(): @@ -11,6 +12,9 @@ def get_db(): return conn def init_db(): + """Initialize database - only used for local development. + For production, use Alembic migrations: alembic upgrade head + """ # Retry logic for Docker compose startup where DB might not be ready instantly max_retries = 5 for i in range(max_retries): diff --git a/backend/app/main.py b/backend/app/main.py index 01511bd..3042878 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -1,27 +1,79 @@ -from fastapi import FastAPI +from fastapi import FastAPI, Request from fastapi.middleware.cors import CORSMiddleware from fastapi.staticfiles import StaticFiles +from fastapi.responses import JSONResponse import os +import logging +import uuid from contextlib import asynccontextmanager from .router import router from .database import init_db +# Configure structured logging +log_level = os.getenv("LOG_LEVEL", "INFO").upper() +logging.basicConfig( + level=getattr(logging, log_level), + format='{"time":"%(asctime)s","level":"%(levelname)s","message":"%(message)s"}' if os.getenv("ENVIRONMENT") == "production" else "%(asctime)s - %(levelname)s - %(message)s", + datefmt="%Y-%m-%dT%H:%M:%SZ" +) +logger = logging.getLogger(__name__) + @asynccontextmanager async def lifespan(app: FastAPI): + logger.info("Starting FocusFlow backend...") init_db() + logger.info("Database initialized successfully") yield + logger.info("Shutting down FocusFlow backend...") -app = FastAPI(title="FocusFlow Backend", lifespan=lifespan) +app = FastAPI( + title="FocusFlow Backend", + description="Vedic Pomodoro Workstation API", + version="1.0.0", + lifespan=lifespan +) -# Allow frontend to communicate +# CORS configuration +cors_origins_str = os.getenv("CORS_ORIGINS", "http://localhost:3001,http://localhost:3000") +cors_origins = [origin.strip() for origin in cors_origins_str.split(",")] app.add_middleware( CORSMiddleware, - allow_origins=["*"], + allow_origins=cors_origins, allow_credentials=True, allow_methods=["*"], allow_headers=["*"], ) +# Request ID middleware for tracking +@app.middleware("http") +async def add_request_id(request: Request, call_next): + request_id = str(uuid.uuid4())[:8] + request.state.request_id = request_id + response = await call_next(request) + response.headers["X-Request-ID"] = request_id + return response + +# Global exception handler +@app.exception_handler(Exception) +async def global_exception_handler(request: Request, exc: Exception): + logger.error(f"Unhandled exception: {exc}", exc_info=True) + return JSONResponse( + status_code=500, + content={ + "error": "Internal server error", + "message": str(exc) if os.getenv("ENVIRONMENT") != "production" else "An error occurred", + "request_id": getattr(request.state, "request_id", None) + } + ) + +# Request logging middleware +@app.middleware("http") +async def log_requests(request: Request, call_next): + logger.info(f"→ {request.method} {request.url.path}") + response = await call_next(request) + logger.info(f"← {request.method} {request.url.path} {response.status_code}") + return response + app.include_router(router) # Ensure the assets/audio and voice_notes directory exists before mounting to prevent startup crashes diff --git a/backend/app/router.py b/backend/app/router.py index d570525..b4097af 100644 --- a/backend/app/router.py +++ b/backend/app/router.py @@ -15,6 +15,24 @@ router = APIRouter() +# --- Health Check Endpoints --- +@router.get("/health") +def health_check(): + """Basic health check endpoint.""" + return {"status": "ok", "service": "focusflow-backend"} + +@router.get("/ready") +def readiness_check(): + """Readiness check - verifies database connectivity.""" + try: + conn = get_db() + with conn.cursor() as cur: + cur.execute("SELECT 1") + conn.close() + return {"status": "ready", "database": "connected"} + except Exception as e: + raise HTTPException(status_code=503, detail={"status": "not ready", "error": str(e)}) + # --- PHASE 1: Timer State --- _state = TimerState(mode="work", remaining_seconds=1500, cycle=0) diff --git a/backend/requirements.txt b/backend/requirements.txt index b69ecb2..8b0fe09 100644 --- a/backend/requirements.txt +++ b/backend/requirements.txt @@ -4,4 +4,6 @@ pydantic psycopg2-binary python-multipart pytest +pytest-cov httpx +alembic diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py new file mode 100644 index 0000000..8331012 --- /dev/null +++ b/backend/tests/conftest.py @@ -0,0 +1,42 @@ +import pytest +from fastapi.testclient import TestClient +from httpx import AsyncClient, ASGITransport +import os + +# Set test environment before importing app +os.environ["ENVIRONMENT"] = "test" +os.environ["DATABASE_URL"] = "postgresql://postgres:postgrespassword@localhost:5432/focusflow_test" + +from app.main import app + +client = TestClient(app) + + +@pytest.fixture +def test_client(): + """Provide a test client for API tests.""" + return client + + +@pytest.fixture +def sample_task(): + """Create and return a sample task.""" + response = client.post("/tasks", json={"title": "Test Task"}) + assert response.status_code == 200 + return response.json() + + +@pytest.fixture +def sample_session(): + """Create and return a sample session.""" + response = client.post("/sessions", json={"duration": 1500, "status": "completed"}) + assert response.status_code == 200 + return response.json() + + +@pytest.fixture +def sample_journal(): + """Create and return a sample journal entry.""" + response = client.post("/journal", json={"text": "Test journal entry"}) + assert response.status_code == 200 + return response.json() diff --git a/backend/tests/test_analytics.py b/backend/tests/test_analytics.py new file mode 100644 index 0000000..346e574 --- /dev/null +++ b/backend/tests/test_analytics.py @@ -0,0 +1,65 @@ +"""Tests for analytics endpoints.""" +import pytest +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +class TestAnalyticsEndpoints: + """Tests for /analytics endpoints.""" + + @patch("app.router.get_db") + def test_get_heatmap_returns_list(self, mock_get_db): + """Test GET /analytics/heatmap returns a list.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchall.return_value = [] + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.get("/analytics/heatmap") + assert response.status_code == 200 + assert isinstance(response.json(), list) + + @patch("app.router.get_db") + def test_heatmap_response_structure(self, mock_get_db): + """Test heatmap response has correct structure.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchall.return_value = [ + { + "focus_date": "2026-08-29", + "completed_count": 5, + "failed_count": 1 + } + ] + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.get("/analytics/heatmap") + data = response.json() + + if len(data) > 0: + day = data[0] + assert "date" in day + assert "focus_score" in day + assert "sessions_completed" in day + assert "sessions_failed" in day + + @patch("app.router.get_db") + def test_heatmap_with_no_sessions_returns_empty(self, mock_get_db): + """Test heatmap returns empty list when no sessions.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchall.return_value = [] + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.get("/analytics/heatmap") + data = response.json() + assert len(data) == 0 diff --git a/backend/tests/test_health.py b/backend/tests/test_health.py new file mode 100644 index 0000000..cb6b806 --- /dev/null +++ b/backend/tests/test_health.py @@ -0,0 +1,52 @@ +"""Tests for health check endpoints.""" +import pytest +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +class TestHealthEndpoint: + """Tests for GET /health endpoint.""" + + def test_health_returns_ok(self): + """Test health endpoint returns status ok.""" + response = client.get("/health") + assert response.status_code == 200 + data = response.json() + assert data["status"] == "ok" + assert data["service"] == "focusflow-backend" + + def test_health_returns_json(self): + """Test health endpoint returns JSON content type.""" + response = client.get("/health") + assert "application/json" in response.headers["content-type"] + + +class TestReadinessEndpoint: + """Tests for GET /ready endpoint.""" + + @patch("app.router.get_db") + def test_ready_returns_connected(self, mock_get_db): + """Test readiness endpoint returns connected when database is up.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchone.return_value = (1,) + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.get("/ready") + assert response.status_code == 200 + data = response.json() + assert data["status"] == "ready" + assert data["database"] == "connected" + + @patch("app.router.get_db") + def test_ready_returns_503_when_db_unavailable(self, mock_get_db): + """Test readiness endpoint returns 503 when database is unavailable.""" + mock_get_db.side_effect = Exception("Connection refused") + + response = client.get("/ready") + assert response.status_code == 503 diff --git a/backend/tests/test_journal.py b/backend/tests/test_journal.py new file mode 100644 index 0000000..8452888 --- /dev/null +++ b/backend/tests/test_journal.py @@ -0,0 +1,30 @@ +"""Tests for journal CRUD endpoints.""" +import pytest +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +class TestJournalEndpoints: + """Tests for /journal endpoints.""" + + @patch("app.router.get_db") + def test_get_journal_returns_list(self, mock_get_db): + """Test GET /journal returns a list.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchall.return_value = [] + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.get("/journal") + assert response.status_code == 200 + assert isinstance(response.json(), list) + + def test_create_journal_without_text_fails(self): + """Test POST /journal fails without text.""" + response = client.post("/journal", json={}) + assert response.status_code == 422 diff --git a/backend/tests/test_sessions.py b/backend/tests/test_sessions.py new file mode 100644 index 0000000..4fbefd1 --- /dev/null +++ b/backend/tests/test_sessions.py @@ -0,0 +1,30 @@ +"""Tests for session CRUD endpoints.""" +import pytest +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +class TestSessionEndpoints: + """Tests for /sessions endpoints.""" + + @patch("app.router.get_db") + def test_get_sessions_returns_list(self, mock_get_db): + """Test GET /sessions returns a list.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchall.return_value = [] + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.get("/sessions") + assert response.status_code == 200 + assert isinstance(response.json(), list) + + def test_create_session_without_duration_fails(self): + """Test POST /sessions fails without duration.""" + response = client.post("/sessions", json={}) + assert response.status_code == 422 diff --git a/backend/tests/test_tasks.py b/backend/tests/test_tasks.py new file mode 100644 index 0000000..1b990af --- /dev/null +++ b/backend/tests/test_tasks.py @@ -0,0 +1,73 @@ +"""Tests for task CRUD endpoints.""" +import pytest +from unittest.mock import patch, MagicMock +from fastapi.testclient import TestClient +from app.main import app + +client = TestClient(app) + + +class TestTaskEndpoints: + """Tests for /tasks endpoints.""" + + @patch("app.router.get_db") + def test_get_tasks_returns_list(self, mock_get_db): + """Test GET /tasks returns a list.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchall.return_value = [] + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.get("/tasks") + assert response.status_code == 200 + assert isinstance(response.json(), list) + + @patch("app.router.get_db") + def test_create_task(self, mock_get_db): + """Test POST /tasks creates a new task.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchone.return_value = {"id": 1, "title": "New Task", "completed": False} + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.post("/tasks", json={"title": "New Task"}) + assert response.status_code == 200 + data = response.json() + assert data["title"] == "New Task" + assert data["completed"] is False + + @patch("app.router.get_db") + def test_toggle_task_completion(self, mock_get_db): + """Test PUT /tasks/{id} toggles task completion.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchone.return_value = {"id": 1, "title": "Toggle Task", "completed": True} + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.put("/tasks/1", json={"completed": True}) + assert response.status_code == 200 + assert response.json()["completed"] is True + + @patch("app.router.get_db") + def test_update_nonexistent_task_returns_404(self, mock_get_db): + """Test PUT /tasks/{id} returns 404 for invalid ID.""" + mock_conn = MagicMock() + mock_cursor = MagicMock() + mock_cursor.__enter__.return_value = mock_cursor + mock_cursor.fetchone.return_value = None + mock_conn.cursor.return_value = mock_cursor + mock_get_db.return_value = mock_conn + + response = client.put("/tasks/99999", json={"completed": True}) + assert response.status_code == 404 + + def test_create_task_without_title_fails(self): + """Test POST /tasks fails without title.""" + response = client.post("/tasks", json={}) + assert response.status_code == 422 diff --git a/docker-compose.yml b/docker-compose.yml index cc47769..6df3ccf 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -10,6 +10,11 @@ services: volumes: - ./data/postgres:/var/lib/postgresql/data restart: always + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 5s + timeout: 5s + retries: 5 backend: build: @@ -17,10 +22,15 @@ services: dockerfile: Dockerfile ports: - "8000:8000" + environment: + - DATABASE_URL=postgresql://postgres:postgrespassword@postgres:5432/focusflow + - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:3001,http://localhost:3000} + - ENVIRONMENT=${ENVIRONMENT:-development} volumes: - ./assets:/app/assets depends_on: - - postgres + postgres: + condition: service_healthy restart: always frontend: @@ -29,6 +39,8 @@ services: dockerfile: Dockerfile ports: - "3001:3000" + environment: + - NEXT_PUBLIC_API_URL=${NEXT_PUBLIC_API_URL:-http://localhost:8000} depends_on: - backend restart: always diff --git a/frontend/.dockerignore b/frontend/.dockerignore index f9e79aa..6e0685d 100644 --- a/frontend/.dockerignore +++ b/frontend/.dockerignore @@ -1,6 +1,51 @@ -node_modules -.next -.git +# Dependencies +node_modules/ +.pnp +.pnp.js + +# Next.js +.next/ +out/ + +# Production +build/ +dist/ + +# Testing +coverage/ + +# Environment +.env +.env.local +.env.development.local +.env.test.local +.env.production.local + +# Debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# IDE +.vscode/ +.idea/ +*.swp +*.swo +*~ + +# Git +.git/ .gitignore + +# Documentation *.md +LICENSE + +# Docker +Dockerfile +docker-compose.yml +.dockerignore + +# OS .DS_Store +Thumbs.db diff --git a/frontend/next.config.ts b/frontend/next.config.ts index 68a6c64..b88ebf8 100644 --- a/frontend/next.config.ts +++ b/frontend/next.config.ts @@ -2,6 +2,17 @@ import type { NextConfig } from "next"; const nextConfig: NextConfig = { output: "standalone", + // Enable React strict mode for better development experience + reactStrictMode: true, + // Optimize images for production + images: { + formats: ["image/avif", "image/webp"], + }, + // Enable experimental features if needed + experimental: { + // Enable server actions if needed + serverActions: true, + }, }; export default nextConfig; diff --git a/frontend/vercel.json b/frontend/vercel.json new file mode 100644 index 0000000..70fb599 --- /dev/null +++ b/frontend/vercel.json @@ -0,0 +1,22 @@ +{ + "version": 2, + "builds": [ + { + "src": "package.json", + "use": "@vercel/next" + } + ], + "routes": [ + { + "src": "/api/(.*)", + "dest": "/api/$1" + }, + { + "src": "/(.*)", + "dest": "/" + } + ], + "env": { + "NEXT_PUBLIC_API_URL": "@api-url" + } +} diff --git a/railway.json b/railway.json new file mode 100644 index 0000000..1bada59 --- /dev/null +++ b/railway.json @@ -0,0 +1,14 @@ +{ + "$schema": "https://railway.app/railway.schema.json", + "build": { + "builder": "DOCKERFILE", + "dockerfilePath": "Dockerfile" + }, + "deploy": { + "startCommand": "uvicorn app.main:app --host 0.0.0.0 --port $PORT", + "healthcheckPath": "/health", + "healthcheckTimeout": 300, + "restartPolicyType": "ON_FAILURE", + "restartPolicyMaxRetries": 3 + } +}