Greenfield task hub for a single Red Hat Solutions Architect: auto-extracted open tasks from Gmail, Calendar, and Drive; contacts from Gmail; rule-based recommendations with optional local Ollama wording. All data and LLM stay on your machine — no cloud AI or database.
| Area | Route | Description |
|---|---|---|
| Dashboard | / |
Onboarding splash until Google is connected and first sync completes; then widgets & recommendations |
| Tasks | /tasks |
Open tasks (30d window), grouped by company, source badge + origin link |
| Contacts | /contacts |
Gmail addresses, search/filter, company override, ignore, Ollama enrich |
| Assets | /assets |
Subscriptions & hardware per company (from existing companies in the app) |
| Settings | /settings |
Google OAuth, manual sync, Slack stub status |
API: REST /api/v1/* — OpenAPI at /docs
Sync: Hourly APScheduler + POST /api/v1/sync
Webhooks: POST /api/v1/webhooks/register (stub delivery logs only)
After pulling new code on the server:
cd /path/to/sa-helper
docker compose up -d --buildVerify the new backend is running:
curl -s https://sa-hub.digitalgiants.net/api/v1/health
# Expect JSON with "app_version":"2026.05.22-recommendations-datetime" (or newer) and "auth_enabled":true
curl -s https://sa-hub.digitalgiants.net/api/v1/auth/me
# Expect {"authenticated":false,"login_configured":true,...} — NOT {"detail":"Not Found"}If auth/me returns Not Found, the backend image was not rebuilt. Run docker compose build --no-cache backend && docker compose up -d.
Collect these on the server (replace the host if needed):
# 1) Confirm the backend image includes assets fixes
curl -s http://localhost:8000/api/v1/health
# expect: "app_version":"2026.05.22-assets-timestamps" (or newer)
# 2) Assets DB diagnostic (after logging into the app in your browser)
curl -s -b /tmp/sa-cookies.txt http://localhost:8000/api/v1/assets/ready
# expect: {"ready":true,"company_assets_rows":4}
# if ready:false, the "error" field says what is wrong (usually missing table)
# 3) Backend traceback (most useful) — reproduce the error, then immediately:
podman compose logs backend --tail=80
# 4) Optional: save session cookie from browser (DevTools → Application → Cookies),
# then test the failing call:
curl -s -b "sa_task_hub_session=PASTE_VALUE" http://localhost:8000/api/v1/assetsIn the browser: DevTools → Network → click the failed red request (e.g. assets or companies/3) → copy Status, Response tab text. Newer frontends show 500 /api/v1/assets/...: Could not load assets: … with the real reason.
After git pull, rebuild and recreate the backend so migrations run:
podman compose build --no-cache backend && podman compose up -d --force-recreate backendThe splash needs a new frontend and backend build. Verify:
curl -s http://localhost:8000/api/v1/health
# expect "app_version":"2026.05.22-onboarding-splash"
curl -s -b cookies.txt http://localhost:8000/api/v1/onboarding/status
# expect JSON with onboarding_complete, not 500Then hard-refresh the browser (Cmd+Shift+R). If /api/v1/settings or /api/v1/dashboard still fail, the splash should appear anyway on builds from 2026.05.22-onboarding-splash onward.
Usually not a browser problem until the server is actually running new images. Check in this order:
-
Confirm code on the machine you build —
git pullin the repo directory before compose. -
Backend version (definitive) — on the same host/URL you use in the browser:
curl -s http://localhost:8000/api/v1/health # or https://sa-hub.digitalgiants.net/api/v1/healthCompare
app_versionin JSON to what you expect. Old version → build/deploy (image cache, wrong host, or compose run from another checkout). -
Force a real rebuild (Podman often reuses layers):
podman compose build --no-cache frontend backend podman compose up -d --force-recreate
-
.envchanges — editing.envdoes not update running containers until recreate:podman compose up -d --force-recreate
(Rebuild is only needed for code/Dockerfile changes.)
-
Browser — if
app_versionis new but the UI is missing widgets, hard refresh (Cmd+Shift+R) or a private window. Frontend is static files baked into thefrontendimage; nginx now sendsno-cacheonindex.htmlafter you rebuild the frontend image. -
Wrong target — building on your laptop but opening production (or the reverse). Build and curl health on the same machine/URL you browse.
cd sa-task-hub
cp .env.example .env
# Edit .env: APP_PASSWORD, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, SECRET_KEY, FERNET_KEY
# FERNET_KEY: backend/.venv/bin/python backend/scripts/generate_fernet_key.py
docker compose up --build -d
# Pull a local model (once):
docker compose exec ollama ollama pull llama3.2- UI: http://localhost:8080
- API: http://localhost:8000
- Docs: http://localhost:8000/docs
- Set login in
.envon the server (APP_USERNAME=admin,APP_PASSWORD=…), rebuild, then open/loginand sign in with that same username/password (not your Google password). - Open Settings → Connect Google (add redirect URI in Google Cloud console).
- Run sync now — populates Tasks, Contacts, Dashboard.
- Create a Google Cloud project → OAuth consent screen (internal/test).
- Credentials → OAuth client (Web).
- Authorized redirect URI: value of
GOOGLE_REDIRECT_URI(defaulthttp://localhost:8000/api/v1/oauth/google/callback). - Scopes: Gmail readonly, Calendar readonly, Drive readonly (configured in
.env). - APIs & Services → Library → enable Google Calendar API and Google Drive API (required for sync; without them those connectors show
error).
Backend
cd backend
python3.12 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export $(grep -v '^#' ../.env | xargs) 2>/dev/null || true
uvicorn app.main:app --reload --port 8000Frontend
cd frontend
npm install
npm run devTests
cd backend && pip install pytest && pytestsa-task-hub/
├── backend/ # FastAPI, SQLAlchemy async, SQLite, extractors
├── frontend/ # React + Vite + TypeScript + Tailwind
├── deploy/ # Caddyfile.example
├── docker-compose.yml
├── .env.example
└── README.md
- Extractors: pluggable (
gmail,calendar,drive,slackstub). Upsert idempotent on(source, source_id). - Recommendations: rule-ranked scores; optional Ollama polish (degrades if Ollama down).
- Tokens: Fernet-encrypted in
oauth_tokenstable. - Deferred: Slack sync, manual task CRUD, multi-user, CRM, native apps, real webhook HTTP delivery.
See deploy/Caddyfile.example — reverse-proxy /api and /docs to backend, everything else to frontend.
See .env.example. No OpenAI/Anthropic or other cloud LLM keys are used anywhere in this repo.
Login: Set APP_PASSWORD (and optionally APP_USERNAME, default admin). Use a strong SECRET_KEY. For HTTPS deployments set AUTH_COOKIE_SECURE=true.
Sync can take several minutes. Newer builds return 202 immediately and run sync in the background; the UI polls until done. If you still see 504 from nginx:
- Rebuild:
docker compose up -d --build - If you use host nginx in front of Docker, increase
proxy_read_timeout(e.g.600s) for/api/ - If you use Caddy, see
deploy/Caddyfile.example(read_timeout 10m)
Sync health calendar: success only means the sync job read the calendar for tasks. The Today's meetings widget calls the Calendar API again when you open the dashboard.
Common causes:
- Missing timezone data in the backend container — the widget uses
USER_TIMEZONE(defaultAmerica/New_York). Slim Python images need thetzdatapackage (included in recentbackend/Dockerfile). Rebuild:podman compose build --no-cache backend && podman compose up -d --force-recreate. - Invalid
USER_TIMEZONE— must be an IANA name (e.g.America/Chicago), notESTorEDT. - No external meetings today — internal-only
@redhat.commeetings are hidden on purpose; you should see “No external meetings…” with no error line. - After deploy,
curl -s …/api/v1/healthshould showapp_version2026.05.22-calendar-today-tzor newer.
On the dashboard Sync health row, expand the red text under recommendations — that is the real error from the last sync.
Common causes:
can't compare offset-naive and offset-aware datetimes— calendar tasks store timezone-awaredue_atvalues; older recommendation code compared them in SQL. Fixed inapp_version2026.05.22-recommendations-datetimeor newer. Rebuild backend:podman compose build --no-cache backend && podman compose up -d --force-recreate.- Ollama — optional polish only; failures are skipped and should not fail sync. If you still see errors on an old image, redeploy as above.
- After a successful sync, run Sync now again;
recommendationsshould showsuccess.
curl -s http://localhost:8000/api/v1/health | grep app_version
curl -s http://localhost:8000/api/v1/sync/status- 500 on
/api/v1/oauth/google/callback: Ensure.envhasGOOGLE_REDIRECT_URI=https://your-host/api/v1/oauth/google/callback(HTTPS, exact match with Google Console). Redeploy backend + frontend after changes. Check logs:docker compose logs backend --tail=50forgoogle_oauth_callback_failed. - Drive sync fails (403): Enable Google Drive API in the same Google Cloud project as your OAuth client, then reconnect Google in Settings.
- Check which connector failed:
GET /api/v1/sync/status;drive.errorhas the message (allnevermeans OAuth never completed). - Set
USER_EMAILin.envto your Google address so mention filtering works.