Sporalize-api / README.md
Shoraky's picture
Remove completed playback backfill tooling
f514c45
|
Raw History Blame Contribute Delete
15.2 kB
metadata
title: Sporalize Backend
sdk: docker
app_port: 7860

Sporalize Backend

One Docker service runs Express for authentication, subscriptions and Supabase access, and FastAPI for video analysis. Node exposes port 7860 in the Space; Python binds only to 127.0.0.1:8000. start.py supervises both processes and stops the service if either exits. Python calls Node over loopback. BACKEND_URL remains the Space origin; production OAuth callbacks use FRONTEND_URL through the Cloudflare Pages proxy so browser cookies stay first-party.

Local development

From this directory:

Copy-Item .env.example .env
npm ci
npm run build
python -m pip install -r requirements.txt
npm start

Fill in .env before starting. Both processes share this file. The default local public port is 4000. Rebuild TypeScript after editing Node code. npm run dev starts only Node; use npm start for the full application.

Hugging Face deployment

Upload the contents of Backend/ as the root of a Docker Space, including this README and Dockerfile. Exclude .env, node_modules, and local build/cache directories. The Space downloads Core from its private dataset repository; local development uses the root Core/ directory.

Set these Space variables/secrets from .env.example:

  • PORT=7860, NODE_ENV=production, TRUST_PROXY_HOPS=1 (adjust to the actual trusted proxy chain).
  • FRONTEND_URL: public HTTPS Cloudflare frontend origin.
  • BACKEND_URL: public HTTPS origin of this Space.
  • SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY.
  • CORE_REPO_ID, STORAGE_REPO_ID, HF_TOKEN; optionally CORE_REVISION.
  • ANALYSIS_PROXY_SECRET: a random secret with at least 32 characters.

Store tokens, the service-role key, and proxy secret as Space secrets. HF_TOKEN requires read access to Core and write access to the private storage dataset. Use a dedicated storage dataset for this application. start.py sets ANALYSIS_API_URL and BACKEND_INTERNAL_URL automatically; do not configure a separate public AI URL.

Optional Colab GPU worker

Analysis automatically uses Colab for inference when its worker lease is healthy. If no worker is registered, Colab is on CPU, or its connection is lost, the backend falls back to its existing local analysis pipeline. Colab returns analysis reports only; the backend still prepares playback videos, writes the normal sessions/{session_id} dataset folder, and updates Supabase.

  1. Generate a separate random secret with at least 32 characters. Add it to the Hugging Face Space as COLAB_WORKER_SECRET. Do not reuse ANALYSIS_PROXY_SECRET.
  2. Open Colab Server/worker.ipynb in Colab and set the fixed variables at the top of the first code cell. Use a fine-grained GitHub token with read-only Contents access to the private Sporalize repository. The Hugging Face token needs read access to Core and read/write access to the private storage dataset; the repository IDs must match the backend values. Set CORE_REVISION too if the backend pins one. The backend URL is the public Space origin, such as https://shoraky-sporalize-api.hf.space; the worker secret must match COLAB_WORKER_SECRET exactly. Keep real token and secret values out of Git commits.
  3. Select a GPU runtime and run the notebook. It starts the worker and Cloudflare quick tunnel, waits for model initialization, then registers the generated URL automatically. Keep the notebook cell running while analyses should use Colab.
  4. The notebook refreshes the worker lease every 20 seconds. The backend treats it as offline after 75 seconds without a heartbeat. Stopping or disconnecting Colab returns new jobs to the normal backend pipeline.

The worker accepts one analysis at a time. The backend stages inputs and receives reports through temporary folders in the private Hugging Face dataset; large files and report payloads do not cross the tunnel. A job already running on Colab can resume on the backend if the tunnel is lost; Colab does not write final session records or artifacts, so fallback does not create duplicate saved sessions. Temporary handoff folders are deleted when the job completes or falls back. The backend still retains its usual CPU processing requirements for fallback. No Supabase schema change is needed.

For a local Docker check, build with docker build -t sporalize-backend ., then run docker run --rm --env-file .env -e PORT=7860 -p 7860:7860 sporalize-backend. The .env NODE_ENV setting controls whether production HTTPS checks apply.

Node health: GET /health. At startup, start.py waits for the internal AI health endpoint at http://127.0.0.1:8000/healthz before starting Node, so the public API opens only after model initialization. This readiness check runs once at startup and adds no latency to normal requests. If AI startup fails or takes longer than 25 minutes, the service exits with a clear error; Hugging Face's default Space startup timeout is 30 minutes. Video uploads go through Node directly to Python without Vercel. Validate hosting timeouts and representative large uploads before release.

Run one container with one Python worker: analysis jobs and retry payloads are in memory and are lost on restart. Temporary local files are ephemeral. Completed videos and results.json are stored in the private HF dataset; Node stores session metadata and frame indices in Supabase. Model constants are in app.py.

The production database must have the analysis functions and action-review structures used by secret-protected /internal/analysis routes. The database also needs the profile columns shown in ../Database/schema.sql and a private profile-images Supabase Storage bucket for JPG, PNG, and WEBP files up to 4 MB. The frontend resizes profile photos to at most 1280 pixels and encodes them as WebP before upload. The backend reads and writes images through its service-role client; browsers access them through authenticated API routes with private, versioned browser caching.

For existing Supabase projects, apply the database SQL supplied in the chat for each update. The full schema is maintained in ../Database/schema.sql, and the access rules are documented in ../Database/access-permissions.md. Database RLS does not replace the ownership and admin checks required on every Backend service-role route.

To remove the unused players.dominant_foot and players.notes columns from an existing database, apply ../Database/migrations/20261017_remove_player_dominant_foot_notes.sql in the Supabase SQL Editor.

Supabase dashboard setup

  1. Authentication β†’ Providers β†’ Email: enable it and enable Confirm email.
  2. Authentication β†’ Providers β†’ Google: enable it, add the Google client ID/secret, and use Supabase's displayed callback URL in Google Cloud.
  3. Authentication β†’ URL Configuration:
    • Site URL: http://localhost:8080 during development.
    • Redirect URLs: http://localhost:4000/api/auth/callback (and its ?next=** variant) for development; https://sporalize.pages.dev/api/auth/callback and https://sporalize.pages.dev/api/auth/callback?next=** for production. Replace the frontend origin if deploying elsewhere. The Google Cloud redirect URI stays the Supabase provider callback.

Brevo email verification

Brevo is configured as Supabase Auth's SMTP provider; the Express API does not send verification messages directly.

  1. Create a Brevo account and verify the sender email/domain you will use.

  2. In Brevo open Transactional β†’ Settings β†’ SMTP & API β†’ SMTP and create an SMTP key.

  3. In Supabase open Authentication β†’ Emails β†’ SMTP Settings, enable custom SMTP, and enter:

    • Host: smtp-relay.brevo.com
    • Port: 587
    • Username: the Brevo SMTP login shown on the SMTP page (not the account email unless they match)
    • Password: the Brevo SMTP key (not the account password)
    • Sender email: the verified Brevo sender
    • Sender name: Sporalize
  4. Keep Confirm email enabled under Authentication β†’ Providers β†’ Email.

  5. In Authentication β†’ Email Templates β†’ Confirm signup, set the confirmation link to the frontend's proxied API URL (replace the domain if deploying elsewhere):

    <h2>Confirm your Sporalize account</h2>
    <p><a href="https://sporalize.pages.dev/api/auth/confirm?token_hash={{ .TokenHash }}">Confirm email</a></p>
    

    Set Site URL to the frontend origin. Allow the frontend callback URLs listed above. The Pages proxy forwards confirmation to the backend, which verifies the one-time token and redirects to /login?verified=1; the user then signs in with their email and password. Existing email links using the Space origin should be replaced by newly issued emails.

  6. In Authentication β†’ Email Templates β†’ Reset Password, use a token-hash link to the public reset page. The page asks the user to confirm before exchanging the one-time token, so email security scanners do not consume it:

    <h2>Reset your Sporalize password</h2>
    <p>We received a request to reset the password for your Sporalize account.</p>
    <p><a href="{{ .SiteURL }}/reset-password?token_hash={{ .TokenHash }}&amp;type=recovery">Reset password</a></p>
    <p>If you did not request this, you can ignore this email.</p>
    

    Keep the Supabase Site URL set to the frontend origin (https://sporalize.pages.dev in production). Add https://sporalize.pages.dev/reset-password and http://localhost:8080/reset-password to the allowed redirect URLs for production and local development.

Brevo credentials stay in Supabase SMTP settings and are not added to the browser or committed to .env.

Action playback timestamps

Run Database/add_action_playback_times.sql in the Supabase SQL Editor before relying on saved action playback times. New completed actions store the presentation timestamps of their first frame and the frame after their last frame from the finalized camera video. Older actions remain playable using the FPS calculation until backfilled.

Routes

  • POST /api/auth/signup β€” { fname, lname, email, password, academy? }; sends Supabase's confirmation email when confirmation is enabled.
  • POST /api/auth/login β€” { email, password }.
  • GET /api/auth/google?redirect=/labs β€” starts Google OAuth.
  • GET /api/auth/callback β€” exchanges the Supabase code and sets HttpOnly cookies.
  • GET /api/auth/confirm?token_hash=... β€” verifies the signup email and redirects to the login page with a confirmation message.
  • POST /api/auth/password/reset β€” requests a password-reset email without revealing whether the email is registered.
  • POST /api/auth/password/recovery/verify β€” verifies the one-time recovery token and starts a short-lived recovery session.
  • POST /api/auth/password/recovery β€” changes the password only during that verified recovery session.
  • GET /api/auth/me β€” verifies the session and refreshes it when necessary.
  • POST /api/auth/logout β€” clears the session cookies.
  • GET /api/auth/profile and PATCH /api/auth/profile β€” read and update the signed-in user's profile.
  • GET, POST, and DELETE /api/auth/image β€” view, upload, or remove the signed-in user's profile image. POST sends raw JPG, PNG, or WEBP bytes with the matching Content-Type.
  • GET, POST, and DELETE /api/players/:playerId/image β€” view, upload, or remove a player profile image.
  • PUT /api/players/:playerId β€” update a player's profile, including gender.
  • GET /api/plan-billing β€” current plan assignment, period and status, plus the signed-in user's player and session counts. Billing history is unavailable until a payment provider is integrated.
  • POST /api/auth/password β€” change or set the signed-in user's password.
  • DELETE /api/auth/account β€” delete the signed-in user's Supabase Auth account and profile row.

New accounts start with a NULL role and academy; users can complete both from /labs/profile.

Subscription activation flow

The access gate already reads users_subscriptions and only unlocks Sporalize Labs while startdate and enddate include today.

When a payment provider is added, the server-side webhook flow should:

  1. Verify the provider signature and confirm the payment is paid.
  2. Resolve the authenticated Sporalize user ID and the selected subscriptions.subscription_id from trusted checkout metadata.
  3. Call assignPaidSubscription from src/subscriptions.ts with the paid period dates.

That helper upserts the user's single current plan in users_subscriptions. The existing access check then opens Labs, session details, and analysis automatically. The browser return page must not assign access by itself. Store provider payment references/idempotency records when the provider integration is implemented.

Browser requests through the Space edge

The frontend sends JSON as text/plain and carries PUT/PATCH/DELETE through POST with an _method query parameter because the Space edge can omit credential headers on preflight responses. Express checks the frontend Origin before restoring the method, parses the JSON with the same body-size limit, and retains route authentication. Internal AI requests remain standard JSON. Do not remove the Origin check or use this transport for internal routes.

Google PKCE verifiers are stored in a short-lived HttpOnly, SameSite=Lax cookie tied to the initiating browser. In production, OAuth start, callback, session checks and protected API/media requests all pass through the Pages proxy. Access and refresh cookies are also SameSite=Lax and host-only. Do not redirect OAuth back to the Space domain: that puts the verifier and session on different hosts. In development, the frontend calls the local backend directly.

The Pages proxy streams uploads and media without buffering. Cloudflare's request-body limit still applies (100 MB on Free/Pro, including all multipart files and overhead). Google Drive import transfers the video server-side and avoids that browser-upload limit. Processing progress and cancellation remain available.

Session and access caching

/api/auth/me returns identity and subscription access together. The frontend keeps that UI snapshot in tab-scoped sessionStorage for 60 seconds, including across reloads, and shares concurrent checks. Logout, account deletion, and starting Google sign-in clear the snapshot. No access or refresh token is stored in browser storage; HttpOnly cookies and backend authorization remain authoritative.

The backend caches subscription lookup results per user for at most 30 seconds (never beyond UTC midnight), with concurrent lookup deduplication and a 1,000-entry bound. Assigning a subscription through assignPaidSubscription invalidates that user's entry immediately. The access-status endpoint bypasses that cache, and Labs polls it every 10 seconds while access is locked, so directly assigned plans unlock without another login. Protected API requests can use a cached result for up to 30 seconds; authentication is still verified for every request.