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
73 lines
4 KiB
Markdown
73 lines
4 KiB
Markdown
# 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
|
|
|
|
```bash
|
|
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, Endpoint
|
|
- **`Ammoprices.Repo`** — Ecto repo using PostgreSQL adapter
|
|
- **`Ammoprices.Mailer`** — Swoosh mailer (local adapter in dev, viewable at `/dev/mailbox`)
|
|
|
|
### Web Layer (`lib/ammoprices_web/`)
|
|
|
|
- **`AmmopricesWeb`** (`ammoprices_web.ex`) — Defines `use` macros for `:router`, `:controller`, `:live_view`, `:live_component`, `:html`. The `html_helpers/0` block imports `CoreComponents`, aliases `Layouts` and `Phoenix.LiveView.JS`, and sets up verified routes
|
|
- **`AmmopricesWeb.Router`** — Browser pipeline only. Currently just `GET / → PageController.home`
|
|
- **`AmmopricesWeb.CoreComponents`** — UI component library using daisyUI + Tailwind. Includes `flash/1`, `button/1`, `icon/1`, standard form/table components
|
|
- **`AmmopricesWeb.Layouts`** — `app/1` layout 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 IDs
|
|
- **`config/dev.exs`** — DB: `ammoprices_dev`, live reload watchers for esbuild/tailwind
|
|
- **`config/test.exs`** — DB: `ammoprices_test`, SQL sandbox mode
|
|
|
|
### Assets (`assets/`)
|
|
|
|
- **Tailwind CSS v4** with `@import "tailwindcss"` syntax (no `tailwind.config.js`)
|
|
- **daisyUI** plugin with custom light/dark themes defined in `app.css`
|
|
- **esbuild** bundles `app.js` — only `app.js` and `app.css` bundles 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 sandbox
|
|
- **`DataCase`** — For context/schema tests, provides `errors_on/1` helper
|
|
- **`LazyHTML`** — 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/2` assigned 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 external `src`/`href` in layouts
|
|
- **Tailwind classes**: Use list syntax `class={["base", condition && "extra"]}` for conditional classes
|
|
- **Never use `@apply`** in CSS
|