solar/docs/PR_FRONTEND_PLAN.md

8.1 KiB
Raw Permalink Blame History

PR Frontend (pr.degelas.be) Plan & Deep-Dive

Goal

  • Subdomain: pr.degelas.be (Public Relations manage campaigns and scheduled posts).
  • Container: degelas-pr (React/Vite frontend only).
  • Auth: Login page; only authenticated users can access the PR app. Backend protects API with JWT.
  • Certs/SSL: To be added later; this plan assumes HTTP first, then HTTPS when certs are in place.

Architecture

[User browser] → pr.degelas.be (nginx in fullstack_degelas)
                      ↓
              [degelas-pr container]  (static React app)
                      ↓
              /api/* → proxy to degelas-backend (same as degelas.be)
                      ↓
              [degelas-backend]  (FastAPI: scheduled-posts, campaigns, browser, pr-auth)
                      ↓
              [PostgreSQL] + [degelas-chrome via CDP]
  • degelas-pr serves only the React SPA (built static files). No backend in this container.
  • API: All calls from the PR app go to /api (same origin once nginx proxies pr.degelas.be/api to the backend). Backend already has scheduled-posts, campaigns, browser; we add pr-auth (login, JWT) and protect PR-relevant routes when PR auth is enabled.
  • CORS: Backend will allow https://pr.degelas.be and http://pr.degelas.be so that if the frontend ever calls degelas.be/api from pr.degelas.be (e.g. different proxy setup), it still works.

1. Backend (degelas) PR Auth

1.1 Env (config)

  • PR_AUTH_ENABLED (default 0): When 1, routes under /scheduled-posts, /campaigns, /browser require a valid JWT. When 0, no auth (current behaviour).
  • PR_USER: Allowed username for PR login (e.g. pr-admin).
  • PR_PASSWORD: Password (plain; prefer strong value, HTTPS in production).
  • PR_JWT_SECRET: Secret to sign JWTs (long random string). If unset and PR_AUTH_ENABLED=1, log a warning and reject login.

1.2 New routes

  • POST /api/pr-auth/login
    Body: { "username": "...", "password": "..." }.
    If PR_AUTH_ENABLED and credentials match env: return { "token": "<JWT>", "expires_in": 86400 }.
    JWT payload: sub=username, exp=now+24h.
    If auth disabled or credentials wrong: 401.

  • GET /api/pr-auth/me (optional): Validate token, return { "username": "..." }. Used by frontend to check session.

1.3 Protection

  • Dependency: require_pr_auth reads Authorization: Bearer <token>, verifies JWT with PR_JWT_SECRET, returns 401 if missing/invalid. Applied to:
    • /scheduled-posts (all methods)
    • /campaigns (all methods)
    • /browser (all methods)
  • When PR_AUTH_ENABLED=0, dependency does nothing (no check).

1.4 CORS

  • Add to allow_origins: https://pr.degelas.be, http://pr.degelas.be, http://localhost:5174 (local PR dev).

2. Frontend (degelas-pr) React/Vite

2.1 Scope

  • Container name: degelas-pr.
  • Stack: React 18, Vite, TypeScript. No backend in this repo; calls /api only.
  • Build: Production build (npm run build); serve with nginx inside the container (so one service, one port).

2.2 Routes (client-side)

  • /login Login form (username, password). On success: store token (e.g. localStorage), redirect to /.
  • / Dashboard/home (e.g. list campaigns + next scheduled posts).
  • /campaigns List campaigns; link to calendar.
  • /campaigns/:id Campaign detail / calendar (posts by date).
  • /posts List scheduled posts (filters: status, platform, campaign, date range).
  • /post-now (optional) Simple “Post now to X” test (calls POST /api/browser/post).

All routes except /login are protected: if no token (or 401 from /api), redirect to /login.

2.3 Auth flow

  • Token storage: localStorage key e.g. pr_token. Alternative: memory + refresh; localStorage is simpler for “stay logged in”.
  • API client: For every request to /api, add header Authorization: Bearer <token>. On 401: clear token, redirect to /login.
  • Login page: Form → POST /api/pr-auth/login → store token → navigate to /.

2.4 API base URL

  • Production (pr.degelas.be): Use relative /api so the same nginx that serves the SPA can proxy pr.degelas.be/api to the backend. No CORS issues.
  • Build-time: VITE_API_URL=/api in Dockerfile/env so fetch(${import.meta.env.VITE_API_URL}/scheduled-posts) becomes /api/scheduled-posts.

2.5 Docker

  • Dockerfile: Multi-stage: (1) Node: install deps, build (Vite); (2) nginx: copy dist/, copy nginx.conf, expose 80. Serve SPA with fallback to index.html for client routing.
  • nginx.conf: location / { root /usr/share/nginx/html; try_files $uri $uri/ /index.html; }, location /api { proxy_pass http://degelas-backend:8000; proxy_set_header Host $host; } but wait: in production, nginx is outside this container (fullstack_degelas). So the degelas-pr container only serves static files; it does not proxy /api (the external nginx will do that). So in-container nginx only needs to serve static and try_files for SPA. No /api in this container.
  • Compose: New service degelas-pr, build ./pr-frontend, container_name degelas-pr, expose 80, networks: degelas + proxy. Env: VITE_API_URL=/api at build time (ARG in Dockerfile).

3. Nginx (fullstack_degelas) pr.degelas.be

  • Later (certs/SSL): Add server block for pr.degelas.be (and optionally www.pr.degelas.be).
    • SSL: certbot for pr.degelas.be when ready.
    • Root: proxy_pass to degelas-pr:80 (or the port degelas-pr exposes).
    • location /api/: proxy_pass to degelas-backend:8000 (same as degelas.be/api).
      So the browser sees one origin (pr.degelas.be), and /api goes to the same backend.
  • For now: Document only; no nginx changes in this repo. When you add the block, CORS is already set for pr.degelas.be.

4. Security Summary

  • Auth: Single user from env (PR_USER/PR_PASSWORD). JWT 24h expiry. No user DB in this phase.
  • HTTPS: Mandatory in production for login (password in body). “Certs later” = use HTTP only for local/testing and document that production must use HTTPS.
  • JWT secret: Must be set in production (PR_JWT_SECRET); strong random value.
  • Rate limiting: Optional later (e.g. limit POST /api/pr-auth/login).

5. Implementation Order

  1. Backend: Config (PR_* env), JWT util, POST /pr-auth/login, GET /pr-auth/me, require_pr_auth dependency, apply to scheduled-posts/campaigns/browser, CORS update.
  2. Frontend: Scaffold React/Vite/TS in pr-frontend/, Dockerfile (build + nginx serve static), login page, auth context, API client with Bearer token, routes (dashboard, campaigns, posts), wire to existing APIs.
  3. Compose: Add degelas-pr service, build from pr-frontend, expose 80, networks degelas + proxy.
  4. Docs: README in pr-frontend (how to run, env), update main docs with pr.degelas.be and nginx/cert todo.

6. File Layout (after implementation)

solar_trading_engine/
  pr-frontend/                 # NEW
    public/
    src/
      api/                     # client (base URL, auth header, 401 → login)
      components/
      contexts/                # AuthContext
      pages/
        Login.tsx
        Dashboard.tsx
        Campaigns.tsx
        CampaignCalendar.tsx
        ScheduledPosts.tsx
      App.tsx
      main.tsx
    index.html
    package.json
    vite.config.ts
    Dockerfile                 # build + nginx
    nginx.conf                 # SPA only
  docker-compose.yml           # + degelas-pr service
  backend/
    app/
      pr_auth.py               # NEW: login, me, JWT, dependency
    main.py                    # include pr_auth router, CORS, apply dependency

Certs/SSL and nginx server block for pr.degelas.be remain “for later” as requested.


Implemented

  • Backend: app/pr_auth.py (login, me, JWT, require_pr_auth), config PR_* in config.py, CORS and router dependencies in main.py.
  • Frontend: pr-frontend/ React/Vite, Login, Dashboard, Campaigns, CampaignCalendar, ScheduledPosts, AuthContext, api client. Container degelas-pr in docker-compose.
  • Nginx/SSL: documented in pr-frontend/README.md; to be configured in fullstack_degelas when ready.