{"id":51572317,"url":"https://github.com/ds17f/emma-shop","last_synced_at":"2026-07-10T20:31:48.312Z","repository":{"id":366268303,"uuid":"1275553929","full_name":"ds17f/emma-shop","owner":"ds17f","description":null,"archived":false,"fork":false,"pushed_at":"2026-07-01T14:34:07.000Z","size":754,"stargazers_count":0,"open_issues_count":1,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-07-01T15:23:26.181Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/ds17f.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":"AGENTS.md","dco":null,"cla":null}},"created_at":"2026-06-20T21:05:41.000Z","updated_at":"2026-07-01T13:33:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/ds17f/emma-shop","commit_stats":null,"previous_names":["ds17f/emma-shop"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/ds17f/emma-shop","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ds17f%2Femma-shop","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ds17f%2Femma-shop/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ds17f%2Femma-shop/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ds17f%2Femma-shop/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/ds17f","download_url":"https://codeload.github.com/ds17f/emma-shop/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/ds17f%2Femma-shop/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35343135,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-10T02:00:06.465Z","response_time":60,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-07-10T20:31:48.233Z","updated_at":"2026-07-10T20:31:48.295Z","avatar_url":"https://github.com/ds17f.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Comet Tail Crafts 🐱☄️\n\nA small, self-owned e-commerce site for selling handmade goods (blankets, crochet,\netc.) — storefront + CMS/admin + cart + Stripe checkout + inventory, all in one\ncodebase. Space/cat theme.\n\n**Stack:** Next.js 16 (App Router, TypeScript) · SQLite + Prisma · Stripe Checkout\n· Tailwind CSS v4 · NextAuth (Auth.js v5).\n\n\u003e **Coming back after a while?** Jump to [Returning to this project](#returning-to-this-project)\n\u003e for the \"get it running again\" cheat sheet. Run `make help` to list every command.\n\n---\n\n## What it does\n\n- **Storefront** — home, catalog with category filter + search, product detail\n  with image gallery + variant picker + live stock, cart (localStorage), Stripe\n  checkout, order confirmation.\n- **Admin** (`/admin`, login-protected):\n  - Dashboard with low-stock alerts\n  - Products: create/edit/delete, multiple variants (price + stock each), image upload\n  - Categories\n  - Orders: status filter, email search, CSV export, per-order status updates\n  - **Users**: change your password, add/remove admins, customers list\n  - **Settings**: shop name/tagline/contact email + shipping (flat rate + optional\n    free-shipping threshold)\n- **Inventory** — per-variant stock; auto-decrements when an order is paid (via the\n  Stripe webhook); shows \"Sold out\" at zero (handles one-of-a-kind items).\n\nSee [`PLAN.md`](./PLAN.md) for the original spec and [`DEPLOY.md`](./DEPLOY.md) for\nthe production/hosting plan.\n\n---\n\n## Quick start (local development)\n\nRequires Node 22+.\n\n```bash\nmake setup     # installs deps, copies .env, runs migrations, seeds sample data\n# → edit .env to set AUTH_SECRET (npx auth secret) and admin creds\nmake dev       # http://localhost:3000\n```\n\nDefault seeded admin: **`admin@example.com` / `changeme123`** → sign in at\n**`/admin`**. Change `ADMIN_EMAIL` / `ADMIN_PASSWORD` in `.env` before seeding, or\nchange the password later in the admin Users page.\n\n### Everyday commands (`make help` for all)\n\n| Command | What it does |\n|---|---|\n| `make dev` | Dev server (hot reload) |\n| `make build` / `make start` | Production build / run it locally |\n| `make lint` / `make typecheck` | ESLint / TypeScript check |\n| `make db-migrate` | Create + apply a new dev migration |\n| `make db-seed` | Seed sample data + admin (**dev only**) |\n| `make db-studio` | Browse/edit the DB in Prisma Studio |\n| `make db-reset` | Wipe local DB, re-migrate, re-seed (**dev only**) |\n| `make docker-run` | Build + run the prod image locally on :3002 |\n| `make docker-logs` / `make docker-stop` | Tail / stop the local container |\n\n---\n\n## Environment variables\n\nSet in `.env` (copy from `.env.example`). Never commit `.env`.\n\n| Variable | Required | What it's for |\n|---|---|---|\n| `DATABASE_URL` | yes | SQLite path, e.g. `file:./dev.db` (prod: `file:/app/data/prod.db`) |\n| `AUTH_SECRET` | yes | NextAuth signing secret. Generate: `npx auth secret` or `openssl rand -base64 32` |\n| `AUTH_URL` | prod | Public URL, e.g. `https://shop.example.com` |\n| `AUTH_TRUST_HOST` | prod | `true` for self-hosting (also hard-coded via `trustHost` in `src/auth.config.ts`) |\n| `STRIPE_SECRET_KEY` | for checkout | Stripe API key (test or live) |\n| `STRIPE_WEBHOOK_SECRET` | for checkout | Signing secret for the Stripe webhook |\n| `ADMIN_EMAIL` / `ADMIN_PASSWORD` | first run | Admin account created by the seed / `ensure-admin` |\n\n---\n\n## Stripe checkout (test mode)\n\n1. Create a Stripe account → put your **test** secret key in `STRIPE_SECRET_KEY`.\n2. Forward webhooks locally:\n   ```bash\n   stripe listen --forward-to localhost:3000/api/webhooks/stripe\n   ```\n   Put the printed `whsec_...` in `STRIPE_WEBHOOK_SECRET`.\n3. Checkout uses Stripe-hosted Checkout. Test card `4242 4242 4242 4242`, any future\n   expiry/CVC.\n4. On payment, the webhook marks the order **PAID** and decrements inventory.\n\nShipping is configured in the admin **Settings** page (flat rate + optional\nfree-shipping threshold), read at checkout in `src/app/api/checkout/route.ts`.\nFor sales tax, enable Stripe Tax and add `automatic_tax` to the Checkout session.\n\n---\n\n## Running the production image locally\n\n```bash\nmake docker-run    # builds the image, runs it on http://localhost:3002\nmake docker-logs   # watch boot: migrate → ensure-admin → server ready\nmake docker-stop\n```\n\nThe image runs `next start` with `node_modules` present so the `better-sqlite3`\nnative binding + Prisma CLI are available. The entrypoint\n(`docker-entrypoint.sh`) runs `prisma migrate deploy`, then `ensure-admin`, then\nstarts the server.\n\n---\n\n## Deploying to production\n\nFull plan in [`DEPLOY.md`](./DEPLOY.md). Summary: the shop runs as its own\nDocker Compose stack on a Hetzner box, behind the existing **deadly** Caddy\n(which handles Let's Encrypt TLS automatically). The two projects are\nindependent — they share only a Docker network and one Caddy route.\n\n**One-time on the box / DNS:**\n1. `docker network create web`\n2. Point DNS `shop.\u003cdomain\u003e` (A record) → the server IP.\n3. Put a production `.env` on the box (AUTH_SECRET, **live** Stripe keys, admin creds, `AUTH_URL=https://shop.\u003cdomain\u003e`).\n4. In the **deadly** repo: add a Caddy block `shop.\u003cdomain\u003e { reverse_proxy emma-shop:3000 }` and attach Caddy to the `web` network → its CI redeploys Caddy.\n5. Stripe Dashboard: add webhook `https://shop.\u003cdomain\u003e/api/webhooks/stripe` (event `checkout.session.completed`).\n\n**Each deploy** (two ways, both resolve the box's IP live from the Hetzner API —\nnothing is hardcoded):\n```bash\n# CI (recommended): push to main → .github/workflows/deploy.yml builds→GHCR→deploys.\n#   Needs repo secrets HCLOUD_TOKEN + SSH_PRIVATE_KEY (see DEPLOY.md §5).\n\n# Manual:\nexport HCLOUD_TOKEN=...                    # Hetzner API token\nmake deploy                                # builds, pushes to GHCR, pulls + restarts on the box\nmake deploy REMOTE_HOST=\u003cssh-alias\u003e        # or target an explicit host instead of auto-resolving\n```\nThe container entrypoint applies migrations automatically. The deploy SSH key is\nnever stored in the repo — keep it in ssh-agent / `~/.ssh/config` (or the CI\n`SSH_PRIVATE_KEY` secret).\n\n### Backups\nAutomated. [`.github/workflows/backup.yml`](./.github/workflows/backup.yml) backs\nup the prod SQLite DB and the `emma_uploads` volume to Backblaze B2 **daily**\n(newest 14 kept); run one on demand with `gh workflow run backup.yml`. Full\nlist / download / **restore** steps are in [`DEPLOY.md`](./DEPLOY.md#backups--restore).\n\n---\n\n## Project layout\n\n```\nprisma/schema.prisma          data model (Product, Variant, Category, Order, AdminUser, StoreSettings)\nprisma/seed.ts                sample data + admin (dev only)\nprisma/ensure-admin.ts        idempotent admin bootstrap (runs on container start)\nsrc/lib/db.ts                 Prisma client (SQLite driver adapter)\nsrc/lib/queries.ts            storefront data access\nsrc/lib/settings.ts           store settings accessor (singleton)\nsrc/lib/cart.tsx              client cart (localStorage)\nsrc/lib/stripe.ts             Stripe client\nsrc/auth.ts, auth.config.ts   NextAuth setup (admin login; trustHost for prod)\nmiddleware.ts                 gates /admin routes\nsrc/app/                      storefront pages (page.tsx, products/, cart/, order/)\nsrc/app/admin/                admin: dashboard, products, categories, orders, users, settings\nsrc/app/admin/actions.ts      admin server actions (CRUD, settings, users)\nsrc/app/api/checkout/         creates the Stripe Checkout session (reads shipping settings)\nsrc/app/api/webhooks/stripe/  payment → order + inventory decrement\nsrc/app/api/admin/upload/     product image upload\nsrc/app/api/admin/orders/export/  orders CSV export\nDockerfile, docker-compose.yml, docker-entrypoint.sh   containerization\n```\n\n---\n\n## Returning to this project\n\nAfter a long break, to get back up and running locally:\n\n```bash\ngit pull\nmake install        # refresh deps\nmake db-migrate     # apply any new migrations (or `make db-reset` for a clean dev DB)\nmake dev            # http://localhost:3000  — admin at /admin\n```\n\nIf something's off, see Troubleshooting below, then `make help` for all commands.\nThe architecture and deploy details live in [`DEPLOY.md`](./DEPLOY.md).\n\n---\n\n## Troubleshooting / gotchas (learned the hard way)\n\n- **Admin login redirects back / `UntrustedHost` in production.** NextAuth needs a\n  trusted host in prod. We set `trustHost: true` in `src/auth.config.ts` and\n  `AUTH_TRUST_HOST=true` in the prod env. If you fork the auth setup, keep this.\n- **Changes to the app don't show up when testing the prod build.** A stale\n  `next start` / container may still own the port. Check: `ss -ltnp | grep :3000`\n  (or `:3002` for the container); kill the old PID. The dev server (`make dev`)\n  hot-reloads and avoids this.\n- **Favicon/logo looks old.** Browsers cache favicons hard — hard-refresh or open\n  a new tab. The files live in `src/app/favicon.ico` + `src/app/icon.png`.\n- **Docker build fails needing a database.** The root layout is `force-dynamic`\n  precisely so the build never touches the DB. Don't remove that without\n  providing a DB at build time.\n- **`better-sqlite3` errors in the container.** It's a native module compiled in\n  the Docker build stage; the runtime stage must keep `node_modules` (we don't use\n  Next `standalone` for this reason). Rebuild the image after dependency changes.\n- **Empty catalog in production.** Expected — prod starts with only the admin user\n  (sample products are dev-only via `make db-seed`). Add real products in `/admin`.\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fds17f%2Femma-shop","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fds17f%2Femma-shop","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fds17f%2Femma-shop/lists"}