# NewEnglandBio Rust API RESTful API for heating oil/biofuel price comparison built with Axum 0.6. Serves the SvelteKit frontend and manages users, companies, listings, and scraped oil prices. ## Tech Stack - **Framework:** Axum 0.6 (Tokio async runtime) - **Database:** PostgreSQL via sqlx 0.6 - **Auth:** JWT (jsonwebtoken) + Argon2 password hashing - **CORS:** tower-http - **Logging:** tracing + tracing-subscriber ## Project Structure ``` src/ ├── main.rs # Server startup, route definitions, CORS config ├── auth/ │ ├── auth.rs # Register, login, logout, auth middleware │ └── structs.rs # User, Claims, LoginRequest, RegisterRequest ├── data/ │ └── data.rs # get_user endpoint ├── company/ │ ├── company.rs # Company CRUD (single handler, method dispatch) │ ├── category.rs # Service categories endpoint │ └── structs.rs # Company, ServiceCategory ├── listing/ │ ├── data.rs # Listing CRUD + public county listings │ └── structs.rs # Listing, CreateListingRequest, UpdateListingRequest ├── state/ │ ├── data.rs # County lookup endpoints │ └── structs.rs # County, ErrorResponse └── oil_prices/ ├── data.rs # Oil prices by county └── structs.rs # OilPrice ``` ## API Endpoints Server listens on `0.0.0.0:9552`. ### Public Endpoints | Method | Path | Description | |--------|------|-------------| | POST | `/auth/register` | Register a new user | | POST | `/auth/login` | Login, returns JWT in httpOnly cookie | | POST | `/auth/logout` | Clear auth cookie | | GET | `/state/:state_abbr` | List counties in a state | | GET | `/state/:state_abbr/:county_id` | Get a specific county | | GET | `/categories` | List all service categories | | GET | `/oil-prices/county/:county_id` | Oil prices for a county (sorted by price) | | GET | `/listings/county/:county_id` | Active listings in a county | ### Protected Endpoints (require JWT) | Method | Path | Description | |--------|------|-------------| | GET | `/user` | Get authenticated user info | | GET | `/company` | Get user's active company | | POST | `/company` | Create company | | PUT | `/company` | Update company (or create if none) | | DELETE | `/company` | Soft-delete company (sets active=false) | | GET | `/listing` | Get all user's listings | | GET | `/listing/:id` | Get a specific listing (owner only) | | POST | `/listing` | Create listing | | PUT | `/listing/:id` | Update listing (partial updates supported) | | DELETE | `/listing/:id` | Delete listing | ### Request/Response Examples **Register:** ```bash curl -X POST http://localhost:9552/auth/register \ -H "Content-Type: application/json" \ -d '{"username":"dealer1","password":"secret123","email":"dealer@example.com"}' ``` **Login:** ```bash curl -X POST http://localhost:9552/auth/login \ -H "Content-Type: application/json" \ -c cookies.txt \ -d '{"username":"dealer1","password":"secret123"}' ``` **Get counties in Massachusetts:** ```bash curl http://localhost:9552/state/MA ``` **Get oil prices for county 5:** ```bash curl http://localhost:9552/oil-prices/county/5 ``` **Create a listing (authenticated):** ```bash curl -X POST http://localhost:9552/listing \ -H "Content-Type: application/json" \ -b cookies.txt \ -d '{ "company_name": "Acme Oil", "is_active": true, "price_per_gallon": 3.29, "price_per_gallon_cash": 3.19, "bio_percent": 5, "service": true, "online_ordering": "none", "county_id": 5, "town": "Worcester" }' ``` ### Validation Rules - `price_per_gallon` must be > 0 - `price_per_gallon_cash` must be >= 0 - `bio_percent` must be 0-100 - `minimum_order` must be >= 0 ## Setup ### Environment Create `.env`: ``` DATABASE_URL=postgres://postgres:password@192.168.1.204:5432/fuelprices JWT_SECRET=YourSecretKeyHereAtLeast32Characters FRONTEND_ORIGIN=http://localhost:9551 RUST_LOG=api_rust=info ``` | Variable | Required | Description | |----------|----------|-------------| | `DATABASE_URL` | Yes | PostgreSQL connection string | | `JWT_SECRET` | Yes | JWT signing key | | `FRONTEND_ORIGIN` | No | CORS allowed origin (default `http://localhost:9551`) | | `RUST_LOG` | No | Log level filter (default `api_rust=info`) | ### Database Initialize the schema and seed data: ```bash psql $DATABASE_URL -f schema.sql psql $DATABASE_URL -f seed_categories.sql ``` ### Run Locally ```bash cargo run ``` ### Run with Hot Reload ```bash cargo install cargo-watch cargo watch -x run ``` ## Docker **Production:** ```bash docker build -t api-rust . docker run -p 9552:9552 --env-file .env api-rust ``` **Development (with cargo-watch):** ```bash docker build -f Dockerfile.dev -t api-rust-dev . docker run -p 9552:9552 -v $(pwd):/usr/src/app --env-file .env api-rust-dev ``` ## Authentication Flow 1. User registers or logs in 2. Server returns JWT as httpOnly cookie (`auth_token`, 24h expiry, SameSite=Lax) 3. Subsequent requests include cookie automatically 4. Auth middleware validates JWT, loads user from DB, attaches to request 5. Also accepts `Authorization: Bearer ` header as fallback 6. Logout clears the cookie ## Database Tables | Table | Description | |-------|-------------| | `users` | User accounts (username, hashed password, email) | | `company` | Company profiles (soft-delete via `active` flag) | | `listings` | Price listings per county | | `county` | Reference table of NE counties | | `oil_prices` | Scraped price data from crawler | | `service_categories` | Service category metadata | No foreign key constraints — integrity enforced at application level. ## Error Responses All errors return JSON: ```json {"error": "description of what went wrong"} ``` Status codes: 400 (validation), 401 (unauthorized), 404 (not found), 500 (internal).