diff --git a/README.md b/README.md index 3a3b3ff..24122e0 100644 --- a/README.md +++ b/README.md @@ -37,8 +37,6 @@ open http://localhost:8000 ### Using Docker Compose ```yaml -version: "3.8" - services: baby-monitor: image: gitea.gt-proj.com/brian/baby-monitor:latest diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..22536ac --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,60 @@ +services: + app: + build: + context: . + dockerfile: Dockerfile + ports: + - "8000:8000" + volumes: + # Persist database to local directory + - ./data:/data + environment: + # Set environment to production + - ENVIRONMENT=production + # Set admin credentials + - ADMIN_USERNAME=admin + - ADMIN_PASSWORD=password + # Use Redis for token storage + - REDIS_URI=redis://redis:6379/0 + # Use PostgreSQL for database + - DATABASE_URL=postgresql://baby_monitor:securepassword@postgres:5432/b + command: ["--host", "0.0.0.0", "--port", "8000"] + restart: unless-stopped + depends_on: + redis: + condition: service_healthy + postgres: + condition: service_healthy + redis: + image: redis:7-alpine + ports: + - "6379:6379" + volumes: + - redis-data:/data + restart: unless-stopped + healthcheck: + test: ["CMD", "redis-cli", "ping"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 1s + postgres: + image: postgres:14-alpine + environment: + - POSTGRES_USER=baby_monitor + - POSTGRES_PASSWORD=securepassword + - POSTGRES_DB=baby_monitor_db + ports: + - "5432:5432" + volumes: + - pgdata:/var/lib/postgresql/data + restart: unless-stopped + healthcheck: + test: ["CMD-SHELL", "pg_isready -U baby_monitor -d baby_monitor_db"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 10s +volumes: + redis-data: + pgdata: diff --git a/docker-compose.yml b/docker-compose.yml index f8a9740..7051d5f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -16,19 +16,5 @@ services: # Optional: Override admin credentials for development - ADMIN_USERNAME=admin - ADMIN_PASSWORD=password - # Optional: Use Redis for token storage (uncomment redis service below) - # - REDIS_URI=redis://redis:6379/0 command: ["--host", "0.0.0.0", "--port", "8000", "--reload"] restart: unless-stopped - - # Uncomment to enable Redis token storage - # redis: - # image: redis:7-alpine - # ports: - # - "6379:6379" - # volumes: - # - redis-data:/data - # restart: unless-stopped -# Uncomment if using Redis -# volumes: -# redis-data: diff --git a/src/baby_monitor/main.py b/src/baby_monitor/main.py index aeeef30..0d45a8b 100644 --- a/src/baby_monitor/main.py +++ b/src/baby_monitor/main.py @@ -91,6 +91,12 @@ def serve_log_feeding() -> FileResponse: return FileResponse(static_path / "log-feeding.html") +@app.get("/feedings.html", include_in_schema=False) +def serve_feedings() -> FileResponse: + """Serve the feedings overview page.""" + return FileResponse(static_path / "feedings.html") + + @app.get("/api/") def read_root(token: Annotated[str, Depends(verify_token)]) -> dict: """API root endpoint (requires authentication).""" diff --git a/src/baby_monitor/static/feedings.html b/src/baby_monitor/static/feedings.html new file mode 100644 index 0000000..68dc416 --- /dev/null +++ b/src/baby_monitor/static/feedings.html @@ -0,0 +1,1058 @@ + + + + + + Feedings - Baby Monitor + + + +
+
+
+
+
+ + + + + +
+

🍼 Feedings

+

Overview of all feeding sessions

+ +
+

Loading...

+
+
+ + + + + diff --git a/src/baby_monitor/static/index.html b/src/baby_monitor/static/index.html index 28f6ddf..00d7fca 100644 --- a/src/baby_monitor/static/index.html +++ b/src/baby_monitor/static/index.html @@ -216,6 +216,7 @@ @@ -285,6 +286,10 @@ window.location.href = "/add-child.html"; }); + document.getElementById("menuFeedings").addEventListener("click", () => { + window.location.href = "/feedings.html"; + }); + document.getElementById("menuLogout").addEventListener("click", () => { closeMenu(); logout(); diff --git a/src/baby_monitor/static/log-feeding.html b/src/baby_monitor/static/log-feeding.html index ba0750b..97ee15a 100644 --- a/src/baby_monitor/static/log-feeding.html +++ b/src/baby_monitor/static/log-feeding.html @@ -212,14 +212,28 @@ const loadingDiv = document.getElementById("loadingChildren"); const childSelect = document.getElementById("childId"); - // Set default start time to now - const now = new Date(); - const localDateTime = new Date( - now.getTime() - now.getTimezoneOffset() * 60000, - ) - .toISOString() - .slice(0, 16); - document.getElementById("startTime").value = localDateTime; + // Check if we're editing an existing feeding + const urlParams = new URLSearchParams(window.location.search); + const feedingId = urlParams.get("id"); + const isEditMode = !!feedingId; + + // Set default start time to now (only for new feedings) + if (!isEditMode) { + const now = new Date(); + const localDateTime = new Date( + now.getTime() - now.getTimezoneOffset() * 60000, + ) + .toISOString() + .slice(0, 16); + document.getElementById("startTime").value = localDateTime; + } + + // Update title and button text if editing + if (isEditMode) { + document.querySelector("h1").textContent = "🍼 Edit Feeding"; + document.querySelector(".subtitle").textContent = "Update feeding session details"; + submitBtn.textContent = "Update Feeding"; + } // Load user's children async function loadChildren() { @@ -255,6 +269,11 @@ // Show form, hide loading loadingDiv.style.display = "none"; form.style.display = "block"; + + // If editing, load the feeding data + if (isEditMode) { + loadFeedingData(); + } } else { showMessage("Failed to load children", "error"); } @@ -263,6 +282,44 @@ } } + async function loadFeedingData() { + try { + const response = await fetch(`/api/feedings/${feedingId}`, { + headers: { + Authorization: `Bearer ${token}`, + }, + }); + + if (response.ok) { + const feeding = await response.json(); + + // Populate form with existing data + document.getElementById("childId").value = feeding.child_id; + + // Format datetime for datetime-local input + const startTime = new Date(feeding.start_time); + const startLocalDateTime = new Date( + startTime.getTime() - startTime.getTimezoneOffset() * 60000 + ).toISOString().slice(0, 16); + document.getElementById("startTime").value = startLocalDateTime; + + if (feeding.end_time) { + const endTime = new Date(feeding.end_time); + const endLocalDateTime = new Date( + endTime.getTime() - endTime.getTimezoneOffset() * 60000 + ).toISOString().slice(0, 16); + document.getElementById("endTime").value = endLocalDateTime; + } + + document.getElementById("feedingType").value = feeding.feeding_type; + } else { + showMessage("Failed to load feeding data", "error"); + } + } catch (error) { + showMessage("Network error loading feeding", "error"); + } + } + loadChildren(); form.addEventListener("submit", async (e) => { @@ -275,12 +332,15 @@ // Disable button during request submitBtn.disabled = true; - submitBtn.textContent = "Logging..."; + submitBtn.textContent = isEditMode ? "Updating..." : "Logging..."; messageDiv.classList.remove("show"); try { - const response = await fetch("/api/feedings", { - method: "POST", + const url = isEditMode ? `/api/feedings/${feedingId}` : "/api/feedings"; + const method = isEditMode ? "PUT" : "POST"; + + const response = await fetch(url, { + method: method, headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, @@ -296,14 +356,17 @@ const data = await response.json(); if (response.ok) { - showMessage("Feeding logged successfully!", "success"); - // Redirect to home page after 1 second + showMessage( + isEditMode ? "Feeding updated successfully!" : "Feeding logged successfully!", + "success" + ); + // Redirect to feedings page after 1 second setTimeout(() => { - window.location.href = "/"; + window.location.href = "/feedings.html"; }, 1000); } else { showMessage( - data.detail || "Failed to log feeding. Please try again.", + data.detail || `Failed to ${isEditMode ? 'update' : 'log'} feeding. Please try again.`, "error", ); } @@ -311,7 +374,7 @@ showMessage("Network error. Please try again.", "error"); } finally { submitBtn.disabled = false; - submitBtn.textContent = "Log Feeding"; + submitBtn.textContent = isEditMode ? "Update Feeding" : "Log Feeding"; } });