Spaces:
Running
Running
|
Download README.md from Shoraky/Sporalize-api: direct link, hf CLI and curl.
- Browser
- Download file 15.2 kB
-
https://huggingface.co/spaces/Shoraky/Sporalize-api/resolve/main/README.md
- Command line
-
hf download hf://spaces/Shoraky/Sporalize-api/README.md
-
curl -L -o README.md https://huggingface.co/spaces/Shoraky/Sporalize-api/resolve/main/README.md
15.2 kB
| 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: | |
| ```powershell | |
| 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`](../Colab%20Server/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`](../Database/schema.sql), and the access rules are documented in [`../Database/access-permissions.md`](../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): | |
| ```html | |
| <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: | |
| ```html | |
| <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 }}&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. | |