|
All checks were successful
deploy / deploy (push) Successful in 1m16s
Two bugs fixed: 1. Page Settings section wasn't appearing for system pages because Defaults.for_slug didn't return all required fields (type, meta_description, published, etc). Also changed page_renderer to use bracket notation for safer field access. 2. Blocks weren't loading when navigating directly to ?edit=page because the PageEditorHook's handle_params ran before Shop.Page assigned @page. Added pending page mode mechanism: hook sets a flag when edit mode is requested but @page is nil, then Shop.Page calls maybe_enter_pending_page_mode after @page is assigned. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com> |
||
|---|---|---|
| .gitea/workflows | ||
| assets | ||
| config | ||
| docs | ||
| lib | ||
| priv | ||
| rel | ||
| test | ||
| .credo.exs | ||
| .dialyzer_ignore.exs | ||
| .dockerignore | ||
| .envrc.example | ||
| .formatter.exs | ||
| .gitignore | ||
| .mcp.json | ||
| AGENTS.md | ||
| CLAUDE.md | ||
| Dockerfile | ||
| fly.toml | ||
| LICENSE | ||
| mise.toml | ||
| mix.exs | ||
| mix.lock | ||
| PROGRESS.md | ||
| README.md | ||
| ROADMAP.md | ||
Berrypod
A customisable e-commerce storefront for print-on-demand sellers, built with Phoenix LiveView. Professional shops without design expertise, privacy-respecting by default, fully self-hostable.
Features
Shop
Complete storefront with all the pages you need:
- Home — hero banner, category navigation, featured products, newsletter
- Products — grid layout with hover effects, sorting, filtering by collection
- Product detail — image gallery with per-colour filtering, variant selector, related products
- Cart — drawer + full page, quantity controls, shipping estimate, cross-tab sync
- Checkout — Stripe-hosted checkout with shipping costs, order confirmation
- Custom pages — CMS pages at any URL with 26 block types
- Legal pages — auto-generated privacy, delivery, and terms from actual shop settings
- Search — FTS5 full-text search with live modal, keyboard nav, ARIA
- Contact — contact form with order status lookup
Admin
- Theme editor — 8 presets, real-time preview, comprehensive customisation
- Page builder — drag-free block editor with undo/redo, live editing on shop pages
- Analytics — privacy-first, cookie-free, comparison mode, CSV export
- Orders — status tracking, fulfilment timeline, provider submission
- Media library — image management with alt text, usage tracking, orphan detection
- Activity log — global event feed, "needs attention" tab, contextual retry
- URL redirects — auto-redirect on slug change, 404 monitoring, dead link scanning
- Email settings — 10 adapter options, test email, env var precedence
- Print providers — Printify + Printful with sync, orders, shipping, webhooks
Technical highlights
- Hand-written CSS with three-layer architecture (9.8 KB gzipped shop, 17.8 KB admin)
- SQLite with BLOB storage, IMMEDIATE transactions, WAL, mmap
- SQLCipher encryption at rest (AES-256, optional for dev, required for prod)
- Image optimisation pipeline (AVIF/WebP/JPEG responsive variants via Oban)
- ETS caching for CSS, pages, redirects, favicons
- 99-100 PageSpeed mobile, no-JS support across all key flows
- 1679+ tests, CI with credo + dialyzer
Getting started
Prerequisites
- Elixir 1.19+
- Erlang/OTP 28+
- Node.js 20+ (for esbuild asset bundling)
Setup
git clone <repo-url>
cd berrypod
mix setup # install deps, create DB, run migrations, build assets
mix phx.server # start dev server at localhost:4000
Visit http://localhost:4000/setup to create your admin account and connect a print provider.
Running tests
mix test # all tests
mix test path/to.exs # specific file
mix precommit # compile warnings + format + test (run before committing)
Project structure
lib/berrypod/ # core business logic
├── accounts.ex # user accounts + auth
├── analytics.ex # privacy-first pageview tracking
├── activity_log.ex # system event logging
├── cart.ex # session-based cart
├── media.ex # image uploads, optimisation, media library
├── newsletter.ex # email list + campaigns
├── orders.ex # order lifecycle + fulfilment
├── pages.ex # page builder (blocks, cache, defaults)
├── products.ex # products, variants, categories
├── providers.ex # POD provider abstraction (Printify, Printful)
├── redirects.ex # URL redirects + dead link monitoring
├── search.ex # FTS5 full-text search
├── settings.ex # theme + shop settings
├── shipping.ex # shipping rates + country detection
├── theme/ # CSS generation, presets, ETS cache
└── workers/ # Oban background jobs
lib/berrypod_web/ # web layer
├── components/
│ ├── layouts/ # app, admin, and shop layouts
│ ├── shop_components/ # shop UI (product cards, cart, gallery, etc.)
│ └── block_editor_components.ex
├── live/
│ ├── admin/ # admin LiveViews (orders, pages, media, etc.)
│ ├── shop/ # shop LiveViews (home, collection, product, etc.)
│ └── auth/ # authentication
├── page_renderer.ex # generic block-to-component dispatch
└── controllers/ # Stripe webhooks, favicons, images, cart API
assets/css/
├── shop/ # shop component styles
├── admin/ # admin component styles
├── theme-layer1-primitives.css # design tokens
├── theme-layer2-attributes.css # theme-specific values
└── theme-layer3-semantic.css # component styles
Database encryption
Berrypod uses SQLCipher to encrypt the entire SQLite database at rest. Two independent secrets provide defence in depth:
| Secret | Purpose |
|---|---|
SECRET_KEY_BASE |
Phoenix sessions, Cloak field encryption |
SECRET_KEY_DB |
SQLCipher whole-database encryption |
Development
Encryption is optional for development. To test locally with encryption:
# Generate a key (hex-only recommended)
openssl rand -hex 32
# Set environment variable
export SECRET_KEY_DB="your-hex-key"
# Recreate database with encryption
mix ecto.reset
mix phx.server
Without SECRET_KEY_DB, the database is unencrypted.
Production
Both secrets are required. Generate them:
mix phx.gen.secret # → SECRET_KEY_BASE
openssl rand -hex 32 # → SECRET_KEY_DB (or mix phx.gen.secret)
For Fly.io deployment:
fly secrets set SECRET_KEY_BASE="..." SECRET_KEY_DB="..."
Backup and restore
Admin > Backup provides:
- Database stats (size, encryption status, table breakdown)
- Download backup (encrypted with same key)
- Restore from backup (validates key matches)
Key management:
- Lost key = lost data. No recovery possible.
- Store keys securely (password manager, secrets manager).
- Backups are portable — copy file + set same key = working shop.
Stripe setup
- Create a Stripe account
- Navigate to Admin > Settings in your shop
- Paste your secret key and click "Connect Stripe"
For local webhook testing:
stripe listen --forward-to localhost:4000/webhooks/stripe
Test cards: 4242 4242 4242 4242 (success), 4000 0000 0000 0002 (declined).
Documentation
- PROGRESS.md — current status and next tasks
- ROADMAP.md — future vision and planned features
- CLAUDE.md — coding guidelines and conventions
- docs/plans/ — feature implementation plans
License
AGPL-3.0