File size: 6,549 Bytes
7da356c 16e1aa7 7da356c 16e1aa7 7da356c 16e1aa7 7da356c 16e1aa7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 | ---
title: Etsy Listing Optimizer
emoji: 🏷️
colorFrom: yellow
colorTo: pink
sdk: gradio
sdk_version: 5.12.0
app_file: server.py
pinned: false
license: mit
---
# Etsy Listing Optimizer
A micro-SaaS tool that generates SEO-optimized Etsy listing titles, tags, and
descriptions from a plain-language product description — with every AI
output programmatically validated (character limits, exact tag count, zero
duplicate words, no truncation) before it's ever shown to a user.
Built on an entirely free tier: Gradio + FastAPI on Hugging Face Spaces,
Supabase for auth/database, Groq (with Gemini fallback) for AI generation,
Stripe for billing.
**New here? Read `DEPLOY.md` first** — it's the step-by-step guide to
getting every account/API key and going live. This README is the map of
what's in the codebase.
## Quickstart (local, no API keys needed)
The app runs in **demo mode** — in-memory storage, a deterministic mock AI
provider — whenever real credentials aren't configured, so you can try the
whole flow before signing up for anything.
```bash
pip install -r requirements.txt
uvicorn app.main:app --reload --port 7860
```
Open http://localhost:7860 — sign up with any email/password (10+ chars,
letters + numbers), generate a listing, poke around the tabs. Data resets
every time you restart the process.
To go live with real accounts/AI/payments, follow `DEPLOY.md`.
## Project structure
```
server.py HF Spaces (Gradio SDK) entry point - runs the app on port 7860
app/
main.py FastAPI app: wires up all routers + mounts Gradio UI
ui.py Gradio Blocks UI (calls the FastAPI routes in-process)
config.py Tier/pricing definitions, feature flags, demo-mode detection
security.py Input sanitization / prompt-injection hygiene
llm/
prompts.py System prompt + few-shot examples (edit wording here)
provider.py Groq / Gemini / Mock provider abstraction + fallback
validation.py Programmatic validation of generated output
generator.py Orchestrates generate -> validate -> retry -> critic pass
auth/
service.py Supabase Auth wrapper + in-memory demo auth
deps.py FastAPI dependency: resolve current user from Bearer token
db/
store.py Supabase-backed store + in-memory demo store (same interface)
payments/
stripe_service.py Checkout session creation + webhook verification/handling
routes/
auth_routes.py /auth/signup, /auth/login, /auth/logout, /auth/password-reset
generate.py /generate — the core feature, tier/quota-gated
brand_voice.py /brand-voice CRUD
history.py /history, /history/export.csv
account.py /account/export, /account/delete (GDPR)
payments.py /billing/checkout/{tier}, /billing/webhook
bulk.py /bulk/generate (Business tier, CSV upload)
legal/
pages.py /legal/privacy, /legal/terms, /legal/refunds (draft text)
middleware/
rate_limit.py Per-IP (auth) and per-account (/generate) rate limits
tests/
test_validation.py Unit tests for the validation logic (run: pytest tests/ -q)
supabase_schema.sql Run this in the Supabase SQL editor - tables + RLS policies
requirements.txt Pinned dependency versions
.env.example Every environment variable, with a comment on where to get it
DEPLOY.md Step-by-step deployment + API key walkthrough
```
## How generation is kept honest
`/generate` never trusts the model's output at face value:
1. `llm/generator.py` calls the LLM (Groq, falling back to Gemini on a 429).
2. `llm/validation.py` checks, in code: exact tag count (13), character
limits (140 for titles, 20 for tags), no duplicate words across
tags/title, no mid-word/mid-sentence truncation, and the API's own
`finish_reason` isn't `"length"` (which would mean the response was cut
off by the token limit).
3. If anything fails, the generator re-prompts the model with the *specific*
violation, up to 2 retries.
4. Once formally valid, a second cheap "critic" LLM call checks whether each
tag is actually relevant to the submitted product (catches hallucinated
tags) — if any are flagged, only those specific tags are regenerated, not
the whole listing.
All of this is exercised by `tests/test_validation.py` and was manually
regression-tested end-to-end (signup, generation, tier gating, quota
enforcement, rate limiting, GDPR export/delete, Stripe error handling) before
being handed off.
## Tiers
Defined in one place, `app/config.py` — nowhere else in the codebase
hardcodes a limit or a price. See the table there (or the Account tab in the
UI) for the current Free/Starter/Pro/Business feature matrix.
## Security & compliance notes
- Passwords never touch application code — Supabase Auth handles hashing
(bcrypt); the demo-mode fallback used when Supabase isn't configured hashes
with a salt purely as a sandbox safeguard and is explicitly **not**
production-grade (see the warning in `auth/service.py`).
- Row Level Security is enabled on every table in `supabase_schema.sql`.
- Every generation request is checked against the monthly quota **before**
calling the LLM, so rejected requests never burn API spend.
- Stripe webhooks are rejected outright if signature verification fails.
- `/account/delete` removes both the database rows and the underlying auth
user/session — a deleted account's token stops working immediately.
- See `DEPLOY.md` for the GDPR / legal-pages disclaimer: the shipped
Privacy Policy / Terms / Refund Policy are a starting checklist, not legal
advice — have them reviewed before real customers sign up.
## Known v1 limitations (by design, per the "keep it simple" brief)
- Rate limiting is in-memory (`slowapi`) — fine for a single HF Space
instance, won't work correctly if you scale to multiple instances without
switching to a Redis-backed store.
- Bulk generation (`/bulk/generate`) processes rows synchronously with a
25-row cap per request — adequate for a v1 CPU-tier deployment, not meant
for large batch jobs.
- Resend (transactional email) is stubbed, not wired up — Supabase's default
auth emails cover signup confirmation/password reset for free in the
meantime.
|