carcost/AGENTS.md
yuany3721 71e11eaf30 feat: Add OIDC authentication with Authentik and refactor project structure
Backend:
- Add auth.py for JWT token verification
- Update main.py to protect all routes with auth middleware
- Remove dashboard router (frontend handles aggregation)
- Add Docker support with Dockerfile and docker-compose.yml

Frontend:
- Add OIDC authentication using oidc-client-ts with PKCE flow
- Create router.js with auth guards for automatic login/logout
- Add api.js for unified Axios instance with auth headers
- Add composables: useAuth.js, useVehicleData.js for caching
- Add views/Main.vue as main application page
- Simplify App.vue to router-view container
- Add deploy-web.sh deployment script

Documentation:
- Update AGENTS.md with new architecture and auth flow
2026-04-12 13:31:27 +08:00

200 lines
5.9 KiB
Markdown

# CarCost - Agent Instructions
## Project Overview
Full-stack vehicle expense tracking application with OIDC authentication.
- **Frontend**: Vue 3 + Vite + Element Plus + ECharts (`web/`)
- **Backend**: FastAPI + SQLAlchemy + PostgreSQL (`api/`)
- **Authentication**: Authentik OIDC with PKCE flow
## Architecture
### Monorepo Structure
```
carcost/
├── deploy-web.sh # Frontend deployment script
├── web/
│ ├── src/
│ │ ├── App.vue # Root container (router-view only)
│ │ ├── main.js # Entry with Element Plus + ECharts
│ │ ├── router.js # Vue Router with OIDC auth guard
│ │ ├── api.js # Unified Axios instance with auth
│ │ ├── composables/ # Vue composables
│ │ │ ├── useVehicleData.js
│ │ │ └── useAuth.js # OIDC configuration (userManager only)
│ │ ├── views/ # Page views
│ │ │ └── Main.vue # Main application page
│ │ └── components/ # Reusable components
│ └── dist/ # Build output
├── api/
│ ├── main.py # FastAPI entry with auth middleware
│ ├── auth.py # JWT verification module
│ ├── config.py # Configuration
│ ├── database.py # Database connection
│ ├── models.py # SQLAlchemy models
│ ├── schemas.py # Pydantic schemas
│ └── routers/
│ ├── vehicles.py # Vehicle CRUD
│ ├── fuel_records.py # Fuel record CRUD
│ └── costs.py # Cost record CRUD
```
### Authentication Flow
**Frontend (Vue Router Guard)**:
1. `router.beforeEach` checks authentication status
2. Unauthenticated → redirect to Authentik OIDC login
3. Callback `/auth/callback` → process code, exchange for token
4. Token stored in localStorage, set to Axios headers
5. All API requests include `Authorization: Bearer <token>`
**Backend (FastAPI JWT)**:
1. `auth.py` validates JWT token from Authentik
2. All routes protected by `get_current_user` dependency
3. Returns 401 for invalid/missing tokens
### API Routing Convention
All API routes include `/carcost` prefix:
- `GET/POST /carcost/vehicles/*` - Vehicle CRUD
- `GET/POST /carcost/fuel-records/*` - Fuel records
- `GET/POST /carcost/costs/*` - Cost records
Frontend API base: `https://api.yuany3721.site/carcost`
### Key Files
**Frontend Auth**:
- `web/src/router.js` - Route guards handle OIDC flow
- `web/src/api.js` - Unified Axios instance with auth header management
- `web/src/composables/useAuth.js` - OIDC client configuration
**Backend Auth**:
- `api/auth.py` - JWT token verification
- `api/main.py` - Protected routes with auth dependency
### Database Models
#### Vehicle
- `id`, `name`, `purchase_date`, `initial_mileage`
- `is_deleted` - Soft delete flag
- `created_at`, `updated_at`
#### FuelRecord
- `id`, `vehicle_id`, `date`, `mileage`
- `fuel_amount` - Liters
- `fuel_price` - Price per liter
- `display_cost` - Machine display amount (机显总价)
- `actual_cost` - Actual paid amount (实付金额)
- `is_full_tank` - Boolean
- `notes` - Gas station name etc.
- `is_deleted` - Soft delete flag
#### Cost
- `id`, `vehicle_id`, `date`, `type`, `amount`
- `mileage` - Optional
- `notes`
- `is_installment` - Whether to spread across months
- `installment_months` - Number of months to spread
- `is_deleted` - Soft delete flag
**Cost Types**: 保养/维修/保险/停车/过路费/洗车/违章/其他
All models use soft delete (`is_deleted` boolean).
## Authentication (Authentik OIDC)
### Authentik Configuration
**OIDC Discovery URL**: `https://auth.yuany3721.site/application/o/car-cost/.well-known/openid-configuration`
**Client ID**: `27UljrOp2LfCg3fjEo1n8vOiRyCFzqEcnBFiUK59`
**Endpoints**:
- **Authorization**: `https://auth.yuany3721.site/application/o/authorize/`
- **Token**: `https://auth.yuany3721.site/application/o/token/`
- **UserInfo**: `https://auth.yuany3721.site/application/o/userinfo/`
- **End Session**: `https://auth.yuany3721.site/application/o/car-cost/end-session/`
### Environment Variables
```bash
# api/.env
DATABASE_URL=postgresql://user:pass@host:port/carcost
DEBUG=true
AUTHENTIK_ISSUER=https://auth.yuany3721.site/application/o/car-cost/
AUTHENTIK_CLIENT_ID=27UljrOp2LfCg3fjEo1n8vOiRyCFzqEcnBFiUK59
```
## Development Commands
### Backend (api/)
```bash
cd api
source venv/bin/activate
# Run dev server
python main.py
# or
uvicorn main:app --host 0.0.0.0 --port 7030 --reload
# Import fuel records from CSV
python import_fuel_records.py
```
### Frontend (web/)
```bash
cd web
npm install
# Dev server
npm run dev
# Build for production
npm run build
# Preview production build
npm run preview
```
### Deployment
```bash
# Deploy frontend (from carcost root)
./deploy-web.sh
```
## Key Configuration Files
### api/.env
```
DATABASE_URL=postgresql://user:pass@host:port/carcost
DEBUG=true
AUTHENTIK_ISSUER=https://auth.yuany3721.site/application/o/car-cost/
AUTHENTIK_CLIENT_ID=27UljrOp2LfCg3fjEo1n8vOiRyCFzqEcnBFiUK59
```
### web/vite.config.js
- `server.allowedHosts: ['yuany3721.site', '.yuany3721.site']`
## Code Patterns
### Frontend
- Vue 3 Composition API with `<script setup>`
- Element Plus for UI components
- Unified Axios instance (`api.js`) with automatic auth header
- Vue Router guards handle all auth logic before component render
- `useVehicleData` composable for global data caching
### Backend
- FastAPI with JWT token verification
- All routes protected by default
- Manual session management
- SQLAlchemy 2.0 style
## Important Notes
- **Port**: Backend runs on 7030, frontend dev server on 5173
- **CORS**: Backend allows all origins (`["*"]`) for dev
- **Soft Deletes**: All entities use `is_deleted` flag
- **Frontend Max Width**: Content constrained to 900px centered
- **Authentication**: Fully automatic, no login/logout buttons in UI