Phoenix 1.8 app that scrapes ammunition retailers (Lucky Gunner, SGAmmo) for price data and displays historical price trends. - Data model: retailers, calibers, products, price snapshots - Scraper infrastructure with Req, Floki, realistic browser headers - Oban-scheduled scrape jobs (every 4h with randomized delays) - LiveView pages: homepage with category cards, caliber detail with price table, Chart.js price history, and price stats banner - 18 seeded calibers across handgun/rifle/rimfire/shotgun categories - 77 tests
4 KiB
4 KiB
CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
Project Overview
Ammoprices is a Phoenix 1.8 web application (Elixir ~> 1.15) with LiveView, Ecto/PostgreSQL, and Tailwind CSS v4 with daisyUI. Currently a fresh scaffold with no domain logic implemented yet.
Common Commands
mix setup # Install deps, create DB, run migrations, build assets
mix phx.server # Start dev server (localhost:4000)
iex -S mix phx.server # Start dev server with IEx shell
mix test # Run all tests (auto-creates/migrates test DB)
mix test test/path_test.exs # Run a single test file
mix test test/path_test.exs:42 # Run a specific test by line number
mix test --failed # Re-run previously failed tests
mix precommit # Compile (warnings-as-errors) + unlock unused deps + format + test
mix format # Format code
mix ecto.gen.migration name # Generate a new migration
mix ecto.migrate # Run pending migrations
mix ecto.reset # Drop and recreate database
mix precommit is the required pre-commit check — always run it before finishing changes.
Architecture
Application Structure
Ammoprices.Application— Supervision tree: Telemetry, Repo, DNSCluster, PubSub, EndpointAmmoprices.Repo— Ecto repo using PostgreSQL adapterAmmoprices.Mailer— Swoosh mailer (local adapter in dev, viewable at/dev/mailbox)
Web Layer (lib/ammoprices_web/)
AmmopricesWeb(ammoprices_web.ex) — Definesusemacros for:router,:controller,:live_view,:live_component,:html. Thehtml_helpers/0block importsCoreComponents, aliasesLayoutsandPhoenix.LiveView.JS, and sets up verified routesAmmopricesWeb.Router— Browser pipeline only. Currently justGET / → PageController.homeAmmopricesWeb.CoreComponents— UI component library using daisyUI + Tailwind. Includesflash/1,button/1,icon/1, standard form/table componentsAmmopricesWeb.Layouts—app/1layout with navbar and theme toggle (light/dark/system). Templates wrap content with<Layouts.app flash={@flash}>
Config
config/config.exs— Generator defaults: UTC timestamps, binary IDsconfig/dev.exs— DB:ammoprices_dev, live reload watchers for esbuild/tailwindconfig/test.exs— DB:ammoprices_test, SQL sandbox mode
Assets (assets/)
- Tailwind CSS v4 with
@import "tailwindcss"syntax (notailwind.config.js) - daisyUI plugin with custom light/dark themes defined in
app.css - esbuild bundles
app.js— onlyapp.jsandapp.cssbundles are supported - Vendor libs:
topbar.js,heroicons.js,daisyui.js,daisyui-theme.js - JS hooks for LiveView use colocated hook syntax (names prefixed with
.)
Test Support (test/support/)
ConnCase— For controller/LiveView tests, sets up conn and DB sandboxDataCase— For context/schema tests, provideserrors_on/1helperLazyHTML— Available in tests for HTML assertion selectors
Key Conventions
- HTTP client: Use
Req(already included). Never use HTTPoison, Tesla, or:httpc - LiveView templates: Always begin with
<Layouts.app flash={@flash}>wrapper - Icons: Use
<.icon name="hero-x-mark" />component, never Heroicons modules - Forms: Always use
to_form/2assigned in LiveView, access via@form[:field]in templates - Collections: Always use LiveView streams, never assign raw lists
- JS in templates: Use colocated hook
<script :type={Phoenix.LiveView.ColocatedHook}>with.prefixed names, never raw<script>tags - No inline scripts: Import vendor deps into
app.js/app.css, no externalsrc/hrefin layouts - Tailwind classes: Use list syntax
class={["base", condition && "extra"]}for conditional classes - Never use
@applyin CSS