| # DEPLOY.md — Etsy Listing Optimizer |
|
|
| This walks through getting every free-tier account, wiring the keys, and |
| deploying to a Hugging Face Space. Follow it top to bottom the first time. |
|
|
| **Total cost to get running: 0 Kč upfront.** Stripe only takes a cut once you |
| actually charge someone. |
|
|
| --- |
|
|
| ## 0. What you'll end up with |
|
|
| - A live app at `https://<your-hf-username>-etsy-listing-optimizer.hf.space` |
| - Real auth + database (Supabase) |
| - Real AI generation (Groq, with Gemini as fallback) |
| - Real subscription billing (Stripe) |
|
|
| Until you add the keys below, the app **runs in demo mode** (in-memory |
| storage, a mock AI provider) so you can click around and see it work first. |
|
|
| --- |
|
|
| ## 1. Get every account + API key |
|
|
| ### 1.1 Supabase (database + auth) — free, no credit card |
|
|
| 1. Go to **https://supabase.com** → sign up → "New Project". |
| 2. Pick an **EU region (Frankfurt)** — this matters for GDPR data residency, |
| set it now, it can't be changed later without migrating. |
| 3. Set a strong database password (you won't need it day-to-day; Supabase |
| manages connections for you). |
| 4. Once the project finishes provisioning, go to **Project Settings → API**: |
| - `Project URL` → this is your `SUPABASE_URL` |
| - `anon` `public` key → this is your `SUPABASE_ANON_KEY` |
| - `service_role` `secret` key → this is your `SUPABASE_SERVICE_ROLE_KEY` |
| (⚠️ never expose this one client-side or commit it — it bypasses every |
| Row Level Security rule) |
| 5. Go to **Authentication → Providers → Email** and make sure Email auth is |
| enabled (it is by default). |
| 6. Go to **Authentication → Policies** (or **Auth → Settings** depending on |
| your dashboard version) and turn on **"Leaked password protection"**. |
| This is a checkbox, not code — do it now so you don't forget. |
| 7. Go to the **SQL Editor**, paste the entire contents of |
| `supabase_schema.sql` from this project, and run it. This creates all |
| tables and Row Level Security policies. |
| |
| ### 1.2 Groq (primary AI provider) — free tier, no credit card |
|
|
| 1. Go to **https://console.groq.com/keys**, sign up, click "Create API Key". |
| 2. Copy it → this is your `GROQ_API_KEY`. |
| 3. Free tier has generous rate limits for Llama 3.3 70B; if you ever hit |
| them, the app automatically falls back to Gemini (see below) — no code |
| changes needed, just add both keys. |
|
|
| ### 1.3 Gemini (fallback AI provider) — free tier, no credit card |
|
|
| 1. Go to **https://aistudio.google.com/app/apikey**, sign in with a Google |
| account, click "Create API key". |
| 2. Copy it → this is your `GEMINI_API_KEY`. |
| 3. This is optional for v1 (Groq alone works), but recommended — it's what |
| keeps `/generate` working if Groq ever rate-limits you. |
|
|
| ### 1.4 Stripe (payments) — free to set up, per-transaction fee only |
|
|
| 1. Go to **https://dashboard.stripe.com/register**, sign up. |
| 2. Stay in **Test mode** first (toggle top-right of the dashboard) — you can |
| test the entire flow with fake card numbers before going live. |
| 3. Go to **Developers → API keys** → copy the **Secret key** |
| (`sk_test_...`) → this is your `STRIPE_SECRET_KEY`. |
| 4. Go to **Product catalog → Add product**, create three recurring products: |
| - "Starter" — 149 CZK / month |
| - "Pro" — 349 CZK / month |
| - "Business" — 799 CZK / month |
| For each, copy the **Price ID** (looks like `price_1AbCdE...`) → these |
| become `STRIPE_PRICE_STARTER`, `STRIPE_PRICE_PRO`, `STRIPE_PRICE_BUSINESS`. |
| 5. Webhook: you'll finish this step *after* deploying (step 3 below), because |
| Stripe needs your live URL first. |
| 6. When you're ready for real money, flip to **Live mode** in the dashboard |
| and repeat steps 3–4 to get live keys/price IDs (test and live keys are |
| different). |
|
|
| ### 1.5 Resend (transactional email) — optional for v1, free tier |
|
|
| 1. Go to **https://resend.com**, sign up, verify a sending domain (or use |
| their test domain while developing). |
| 2. **API Keys** → create one → this is your `RESEND_API_KEY`. |
| 3. Not wired into the app yet in v1 (password reset emails currently go |
| through Supabase's own email delivery, which works out of the box on |
| the free tier with Supabase's default templates/limits). Add this later |
| if you want branded emails. |
|
|
| ### 1.6 Hugging Face (hosting) — free CPU tier, no credit card |
|
|
| 1. Go to **https://huggingface.co/join** if you don't have an account. |
| 2. You'll create the Space in step 2 below. |
|
|
| --- |
|
|
| ## 2. Create the Hugging Face Space |
|
|
| 1. Go to **https://huggingface.co/new-space**. |
| 2. Name: `etsy-listing-optimizer` (or whatever you like). |
| 3. **SDK: choose "Gradio"** and version **5.12.0**. This project's root |
| `README.md` already contains the metadata block HF Spaces reads |
| (`sdk: gradio`, `app_file: server.py`) — as long as you keep that file at |
| the repo root, the Space will run `server.py`, which starts the full |
| FastAPI app (auth, billing, webhooks, GDPR routes, etc.) with the Gradio |
| UI mounted on top of it, all on port 7860. No Dockerfile needed. |
| 4. Hardware: **CPU basic** (free). |
| 5. Visibility: your choice (Public is fine and free; Private also works on |
| the free tier). |
| 6. Click "Create Space". |
|
|
| ### 2.1 Push the code |
|
|
| From this project's folder: |
|
|
| ```bash |
| git init |
| git remote add space https://huggingface.co/spaces/<your-username>/etsy-listing-optimizer |
| git add . |
| git commit -m "Initial deploy" |
| git push space main |
| ``` |
|
|
| (Use your HF username/token when prompted for credentials — Hugging Face |
| will show you a personal access token to use as the password under |
| **Settings → Access Tokens** if you don't have one yet.) |
|
|
| Once pushed, check the Space's **Logs** tab — you should see uvicorn start |
| up and, in demo mode, a line noting no LLM provider is configured yet. If |
| you instead see a build error about `app_file`, double-check `README.md` |
| is present at the repo root with its metadata block intact (git sometimes |
| excludes dotfiles/READMEs if you cherry-picked what to push — push the |
| whole folder). |
|
|
| ### 2.2 Add your secrets |
|
|
| In the Space, go to **Settings → Variables and secrets** → **New secret** |
| for each of these (values from section 1 above): |
|
|
| ``` |
| SUPABASE_URL |
| SUPABASE_ANON_KEY |
| SUPABASE_SERVICE_ROLE_KEY |
| GROQ_API_KEY |
| GEMINI_API_KEY |
| STRIPE_SECRET_KEY |
| STRIPE_WEBHOOK_SECRET (added in step 3 below, after this first deploy) |
| STRIPE_PRICE_STARTER |
| STRIPE_PRICE_PRO |
| STRIPE_PRICE_BUSINESS |
| APP_BASE_URL (e.g. https://your-username-etsy-listing-optimizer.hf.space) |
| ``` |
|
|
| Adding a secret automatically restarts the Space. Leave |
| `STRIPE_WEBHOOK_SECRET` empty for now — you'll get that value in step 3. |
|
|
| Once `SUPABASE_URL`, a Supabase service role key, at least one of |
| `GROQ_API_KEY`/`GEMINI_API_KEY`, and `STRIPE_SECRET_KEY` are all set, the |
| app automatically leaves demo mode and starts using real services — no code |
| changes required. |
|
|
| --- |
|
|
| ## 3. Wire up the Stripe webhook |
|
|
| 1. Once your Space is live, note its URL, e.g. |
| `https://your-username-etsy-listing-optimizer.hf.space`. |
| 2. In Stripe, go to **Developers → Webhooks → Add endpoint**. |
| 3. Endpoint URL: `https://your-username-etsy-listing-optimizer.hf.space/billing/webhook` |
| 4. Select these events to listen for: |
| - `checkout.session.completed` |
| - `customer.subscription.created` |
| - `customer.subscription.updated` |
| - `customer.subscription.deleted` |
| 5. Save, then copy the **Signing secret** (`whsec_...`) shown on the |
| endpoint's page → set this as `STRIPE_WEBHOOK_SECRET` in your HF Space |
| secrets (section 2.2). This restarts the Space again. |
| 6. Test it: in Stripe's webhook page, click "Send test webhook" and confirm |
| your Space logs show `200 received: true` — check the Space's **Logs** |
| tab. |
|
|
| --- |
|
|
| ## 4. Verify everything end-to-end |
|
|
| 1. Open your Space URL. You should **no longer** see the "Demo mode" banner. |
| 2. Sign up with a real email in the **Account** tab. |
| 3. Generate a listing in the **Generate listing** tab — this now calls Groq |
| for real. |
| 4. In the **Account** tab, click "Get checkout link" for a paid tier, use a |
| Stripe **test card** (`4242 4242 4242 4242`, any future expiry, any CVC) |
| to complete checkout. |
| 5. Confirm your tier updated (try generating more than 5 times — the Free |
| cap should no longer apply). |
| 6. Test **Export my data** and **Delete my account** — confirm the exported |
| JSON looks right and that deletion removes the Supabase Auth user (check |
| **Authentication → Users** in Supabase — the row should be gone). |
|
|
| --- |
|
|
| ## 5. Going to production (real payments) |
|
|
| - Flip Stripe to **Live mode**, repeat section 1.4 steps 3–4 for live keys |
| and live Price IDs, update the HF secrets, and set up a **second** webhook |
| endpoint pointed at the same URL but using your live webhook signing |
| secret. |
| - Have a lawyer (or a reputable EU-focused generator) review the draft |
| Privacy Policy / Terms / Refund Policy at `/legal/privacy`, `/legal/terms`, |
| `/legal/refunds` before taking real customers — the current text is a |
| placeholder, not legal advice (see the banner on each page). |
| - Run `pip-audit` periodically against `requirements.txt` to catch known |
| vulnerabilities in dependencies (see note in `requirements.txt`). |
| - Consider setting up log monitoring/alerting on your HF Space (or moving to |
| a platform with better observability) once you have real users — the free |
| CPU tier is fine for launch but has no built-in alerting. |
|
|
| --- |
|
|
| ## Design decisions driven by the "zero upfront cost" constraint |
|
|
| - **Groq over a paid API (OpenAI/Anthropic):** Groq's free tier is generous |
| and fast (LPU inference); the tradeoff is Llama 3.3 70B is a notch below |
| GPT-4-class models on nuanced copywriting, which is why the code layers |
| heavy programmatic validation + a critic pass on top rather than trusting |
| raw model output. |
| - **Gemini as fallback, not primary:** Gemini's free tier has stricter |
| per-minute limits than Groq's, so it's used only when Groq is unavailable. |
| - **Supabase over a self-hosted Postgres:** free managed Postgres + auth + |
| RLS in one product, with an EU-region option for GDPR, without paying for |
| a VPS or a database host. |
| - **Hugging Face Spaces (Gradio SDK, CPU basic) over a paid PaaS |
| (Fly.io/Render paid tier):** free forever for a single small instance; the |
| tradeoff is you're on a single instance with no autoscaling — fine for a |
| v1 micro-SaaS, but you'll want to move off it if traffic grows (the |
| in-memory rate limiter and demo-mode fallback both assume single-instance |
| too). |
| - **slowapi (in-memory) rate limiting over Redis-backed:** free, zero extra |
| infra; the tradeoff is limits reset if the Space restarts, and won't work |
| correctly if you ever scale to multiple instances — swap in a Redis |
| `storage_uri` at that point. |
| - **Resend stubbed rather than wired up:** Supabase's built-in auth emails |
| (confirmation, password reset) work out of the box for free, so Resend |
| was left as a v2 nice-to-have rather than a v1 requirement, keeping the |
| initial integration surface smaller. |
|
|