feat: tasks app, security hardening, mobile fixes, iOS app shell

- Custom SQLite task manager replacing TickTick wrapper
- 73 tasks migrated from TickTick across 15 projects
- RRULE recurrence engine with lazy materialization
- Dashboard tasks widget (desktop sidebar + mobile card)
- Tasks page with project tabs, add/edit/complete/delete
- Security: locked ports to localhost, removed old containers
- Gitea Actions runner configured and all 3 CI jobs passing
- Fixed mobile overflow on dashboard cards
- iOS Capacitor app shell (Second Brain)
- Frontend/backend guide docs for adding new services
- TickTick Google Calendar sync re-authorized

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Yusuf Suleman
2026-03-30 15:35:57 -05:00
parent 877021ff20
commit 6023ebf9d0
49 changed files with 5207 additions and 23 deletions

View File

@@ -21,10 +21,12 @@ TRIPS_API_TOKEN = os.environ.get("TRIPS_API_TOKEN", "")
SHELFMARK_URL = os.environ.get("SHELFMARK_URL", "http://shelfmark:8084")
SPOTIZERR_URL = os.environ.get("SPOTIZERR_URL", "http://spotizerr-app:7171")
BUDGET_URL = os.environ.get("BUDGET_BACKEND_URL", "http://localhost:3001")
TASKS_URL = os.environ.get("TASKS_BACKEND_URL", "http://tasks-service:8098")
# ── Service API keys (for internal service auth) ──
INVENTORY_SERVICE_API_KEY = os.environ.get("INVENTORY_SERVICE_API_KEY", "")
BUDGET_SERVICE_API_KEY = os.environ.get("BUDGET_SERVICE_API_KEY", "")
TASKS_SERVICE_API_KEY = os.environ.get("TASKS_SERVICE_API_KEY", "")
# ── Booklore (book library manager) ──
BOOKLORE_URL = os.environ.get("BOOKLORE_URL", "http://booklore:6060")

View File

@@ -188,7 +188,7 @@ def handle_dashboard(handler, user):
conn.close()
# Services that use gateway-injected API keys (not per-user tokens)
GATEWAY_KEY_SERVICES = {"inventory", "reader", "books", "music", "budget"}
GATEWAY_KEY_SERVICES = {"inventory", "reader", "books", "music", "budget", "tasks"}
widgets = []
futures = {}

View File

@@ -8,7 +8,7 @@ import bcrypt
from config import (
DB_PATH, TRIPS_URL, FITNESS_URL, INVENTORY_URL,
MINIFLUX_URL, SHELFMARK_URL, SPOTIZERR_URL, BUDGET_URL,
MINIFLUX_URL, SHELFMARK_URL, SPOTIZERR_URL, BUDGET_URL, TASKS_URL,
)
@@ -122,6 +122,13 @@ def init_db():
conn.commit()
print("[Gateway] Added budget app")
# Ensure tasks app exists
tasks = c.execute("SELECT id FROM apps WHERE id = 'tasks'").fetchone()
if not tasks:
c.execute("INSERT INTO apps VALUES ('tasks', 'Tasks', 'check-square', '/tasks', ?, 8, 1, 'today_tasks')", (TASKS_URL,))
conn.commit()
print("[Gateway] Added tasks app")
# Seed admin user from env vars if no users exist
import os
user_count = c.execute("SELECT COUNT(*) FROM users").fetchone()[0]

View File

@@ -283,7 +283,7 @@ class GatewayHandler(ResponseMixin, BaseHTTPRequestHandler):
self._send_json({"error": "Unknown service"}, 404)
return
from config import MINIFLUX_API_KEY, INVENTORY_SERVICE_API_KEY, BUDGET_SERVICE_API_KEY
from config import MINIFLUX_API_KEY, INVENTORY_SERVICE_API_KEY, BUDGET_SERVICE_API_KEY, TASKS_SERVICE_API_KEY
headers = {}
ct = self.headers.get("Content-Type")
if ct:
@@ -298,6 +298,11 @@ class GatewayHandler(ResponseMixin, BaseHTTPRequestHandler):
headers["X-API-Key"] = INVENTORY_SERVICE_API_KEY
elif service_id == "budget" and BUDGET_SERVICE_API_KEY:
headers["X-API-Key"] = BUDGET_SERVICE_API_KEY
elif service_id == "tasks":
# Inject user identity for the task manager
if user:
headers["X-Gateway-User-Id"] = str(user["id"])
headers["X-Gateway-User-Name"] = user.get("display_name", user.get("username", ""))
elif user:
svc_token = get_service_token(user["id"], service_id)
if svc_token: