# Feature: Migrate Backend Persistence from TinyDB to MongoDB Atlas

## Overview

**Goal:** Replace the TinyDB JSON-file persistence layer in the Flask backend with MongoDB Atlas while keeping the existing API surface, model definitions, and test infrastructure largely unchanged. Introduce a migration script that exports existing TinyDB data into MongoDB.

**User Story:** As a developer and operator, I want the application to run on a managed, scalable document database so that concurrent access, multi-worker deployments, and production hosting are reliable.

**Rules:**
- Backend routes and response shapes must remain unchanged.
- Python models in `backend/models/` keep their `from_dict()` / `to_dict()` interface.
- Every mutation must continue to emit SSE events through the existing broadcaster.
- The migration must be reversible (keep TinyDB files as a backup) and idempotent.
- All existing tests must continue to pass; new MongoDB-backed tests must be added.

---

## Data Model Changes

### Backend Model
- No changes to dataclass fields or `to_dict()` / `from_dict()` output.
- The model's `id` field is stored as MongoDB's `_id` primary key. The original `id` field is removed from storage so documents do not contain duplicate values.
- On read, `_id` is mapped back to `id` and the document is wrapped as a TinyDB-compatible `Document` with `doc_id` set.
- `created_at` and `updated_at` remain epoch floats.

### Frontend Model
- No changes. The API contract is preserved.

---

## Backend Implementation

### 1. Database client initialization
- Create `backend/db/mongo_client.py`.
- Do **not** create a `MongoClient` at module import; it is created lazily on first access.
- `get_mongo_client()` returns a cached process-level singleton so tests, scripts, and Flask requests all share the same client.
- For Gunicorn multi-worker deployments, `backend/gunicorn.conf.py` defines a `post_fork` hook that calls `init_mongo_client()` so each worker process gets its own client after forking.
- Fail-fast settings for development:
  ```python
  MongoClient(
      os.getenv("MONGO_URI"),
      serverSelectionTimeoutMS=5000,
      connectTimeoutMS=5000,
      maxPoolSize=20,
  )
  ```

### 2. Collection mapping
- One MongoDB collection per existing TinyDB file:
  - `children` ← `children.json`
  - `tasks` ← `tasks.json`
  - `routines` ← `routines.json`
  - `routine_items` ← `routine_items.json`
  - `routine_schedules` ← `routine_schedules.json`
  - `routine_extensions` ← `routine_extensions.json`
  - `rewards` ← `rewards.json`
  - `images` ← `images.json`
  - `pending_rewards` ← `pending_rewards.json` (legacy)
  - `pending_confirmations` ← `pending_confirmations.json`
  - `users` ← `users.json`
  - `tracking_events` ← `tracking_events.json`
  - `child_overrides` ← `child_overrides.json`
  - `chore_schedules` ← `chore_schedules.json`
  - `task_extensions` ← `task_extensions.json`
  - `refresh_tokens` ← `refresh_tokens.json`
  - `push_subscriptions` ← `push_subscriptions.json`
  - `digest_action_tokens` ← `digest_action_tokens.json`

### 3. `LockedTable` MongoDB adapter
- Refactor `backend/db/db.py` so that each exported `*_db` object continues to expose the same methods: `search(...)`, `get(...)`, `all()`, `insert(...)`, `insert_multiple(...)`, `update(...)`, `remove(...)`, `truncate()`.
- Implement a new `MongoLockedTable` class that mirrors the existing `LockedTable` API but delegates to a MongoDB collection.
- Keep `LockedTable` (TinyDB) as a fallback selectable by `USE_MONGODB=true|false` (default `true`).
- Store the model `id` field as MongoDB `_id` on writes; do not keep a duplicate `id` field in storage.
- Restore the model `id` field from `_id` on reads and return TinyDB-compatible `Document` objects with `doc_id` set so existing callers that rely on `doc_id` continue to work.
- Support `update(fields, doc_ids=[...])` and callable updater functions (`update(lambda rec: ..., cond)`) to match TinyDB behavior.
- Reuse function names where possible to minimize API file changes.

### 4. Indexes
- Create indexes on application startup (`backend/main.py`) and in the pytest session fixture.  Index creation is intentionally **not** done at module import time so the `MongoClient` remains lazily initialized.
- Required indexes:
  - Unique primary key is provided by MongoDB `_id` (mapped from model `id`); no separate `id` index is created.
  - `user_id` — secondary on `children`, `tasks`, `routines`, `routine_items`, `rewards`, `images`, `tracking_events`, `pending_confirmations`, `chore_schedules`, `task_extensions`, `refresh_tokens`, `push_subscriptions`, `digest_action_tokens`.
  - `child_id` — secondary on `child_overrides`, `chore_schedules`, `task_extensions`, `pending_confirmations`, `tracking_events`.
  - `entity_id` + `entity_type` — compound secondary on `pending_confirmations`, `tracking_events`, `child_overrides`.
  - `token` — unique on `refresh_tokens` and `digest_action_tokens`.

### 5. Migration script
- Create `backend/scripts/migrate_to_mongodb.py`.
- Reads each TinyDB JSON file in `data/db/` (or `test_data/db/` when `DB_ENV=test`).
- Inserts each `_default` record into the corresponding MongoDB collection, mapping TinyDB integer keys to the record's own `id` field.
- Skips records that already exist by `id` (idempotent).
- Backs up TinyDB files to `data/db/backups/<timestamp>/` before first run.
- Prints a summary of migrated records per collection.
- Usage:
  ```bash
  cd backend
  python -m scripts.migrate_to_mongodb [--dry-run]
  ```

### 6. Environment variables
- `MONGO_URI` — required when `USE_MONGODB=true` (the default).
- `MONGO_DB_NAME` — optional database name; defaults parsed from `MONGO_URI` or falls back to `chore_db`/`chore_db_test`/`chore_db_e2e` based on `DB_ENV`/`DATA_ENV`.
- `USE_MONGODB` — `true` to use MongoDB backend (default); `false` to keep TinyDB.

### 7. Default data initialization
- `backend/db/default.py` (`initializeImages`, `createDefaultTasks`, `createDefaultRewards`) must work with the new MongoDB-backed `*_db` objects. No API changes.

### 8. Scheduler compatibility
- Background schedulers (`account_deletion_scheduler`, `digest_scheduler`, etc.) read/write through the same `*_db` objects, so they require no changes once the adapter is in place.

### 9. Tracking event ordering
- `db/tracking.py` adds a hidden monotonic `_seq` field to tracking events on insert and sorts by `(occurred_at, created_at, _seq)` descending. This guarantees deterministic ordering when consecutive events share the same timestamp (common in fast tests).

### 10. Gunicorn / Docker deployment
- `backend/gunicorn.conf.py` provides the `post_fork` hook required by the wiki plan.
- `backend/Dockerfile` now loads the config via `-c gunicorn.conf.py`.

---

## Backend Tests

- [x] Add `mongomock` to test dependencies and create `backend/tests/test_mongo_adapter.py` that verifies CRUD operations against `mongomock`.
- [x] Update `backend/tests/conftest.py` to set `USE_MONGODB=true` and point `MONGO_URI` at `mongomock` for the default test run.
- [x] Ensure every existing API test still passes with the MongoDB adapter by running `pytest tests/`.
- [x] Add integration test script `backend/scripts/run_integration_tests.ps1` that starts a local MongoDB Docker container, runs a targeted pytest suite, and tears down the container.
- [x] Add `frontend/.env.test` with MongoDB configuration and update Playwright webServer env to pass `USE_MONGODB`/`MONGO_URI` to the Flask backend; E2E database is reset via the existing `/auth/e2e-seed` endpoint.

---

## Frontend Implementation

- No frontend changes are required. The API contract remains unchanged.

## Frontend Tests

- [ ] Run `npx playwright test` and confirm E2E tests pass against the MongoDB-backed Flask backend with the isolated `chore_db_e2e` database.

---

## Future Considerations

- After a production burn-in period, remove the TinyDB fallback and `LockedTable` code.
- Add database connection health-check endpoint.
- Consider MongoDB transactions for multi-document operations (e.g., child deletion cascades).

---

## Acceptance Criteria (Definition of Done)

### Backend
- [x] `backend/db/db.py` exports MongoDB-backed `*_db` objects when `USE_MONGODB=true`.
- [x] `backend/db/mongo_client.py` implements lazy client initialization and a Gunicorn-compatible init hook.
- [x] All existing API endpoints continue to work without route or response changes.
- [x] `backend/scripts/migrate_to_mongodb.py` migrates existing TinyDB JSON files idempotently.
- [x] Required unique and secondary indexes are created on application startup or migration.
- [x] Unit tests pass with `mongomock` (`pytest tests/`).
- [ ] Integration tests pass against a local Docker MongoDB container (requires a running Docker daemon; script verified to start/stop container when daemon is available).

### Frontend
- [x] E2E setup and representative parent-mode smoke tests pass against the isolated `chore_db_e2e` mongomock database, with the DB reset via `/auth/e2e-seed`.

### Operations
- [x] `MONGO_URI` and optional `MONGO_DB_NAME` environment variables are documented.
- [x] Rollback instructions exist to switch back to TinyDB by setting `USE_MONGODB=false`.
