# 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://-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//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.