SuperSeller3000/CLAUDE.md

108 lines
5.5 KiB
Markdown
Raw Normal View History

# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Commands
All PHP commands run inside Docker. The app container is named `app`.
```bash
# Unit tests
docker compose exec app php vendor/bin/phpunit tests/Unit/
# Single test file or method
docker compose exec app php vendor/bin/phpunit tests/Unit/Domain/Article/ArticleTest.php
docker compose exec app php vendor/bin/phpunit --filter testSomeMethod
# Integration tests (loads .env.local secrets automatically)
bin/test-integration
bin/test-integration tests/Integration/Channel/EbayAdapterTest.php
# PHPStan (level 9 — must be clean before committing)
docker compose exec app php vendor/bin/phpstan analyse
# CS Fixer (dry-run to check, no --dry-run to fix)
docker compose exec app php vendor/bin/php-cs-fixer fix --dry-run --diff
docker compose exec app php vendor/bin/php-cs-fixer fix
# Migrations
docker compose exec app php bin/console doctrine:migrations:migrate --no-interaction
docker compose exec app php bin/console doctrine:migrations:diff # generate from entity changes
# Cache
docker compose exec app php bin/console cache:clear
# Create first user / API key
docker compose exec app php bin/console app:users:create
docker compose exec app php bin/console app:api-keys:create
```
## Architecture
Hexagonal architecture: **Domain → Application → Infrastructure**. The boundary is enforced by convention and PHPStan.
- `src/Domain/` — pure PHP, zero framework imports. Entities, enums, value objects, repository *interfaces*. Doctrine attributes on entities are the pragmatic exception.
- `src/Application/` — use cases, message handlers, service interfaces (ports). Orchestrates domain via interfaces only.
- `src/Infrastructure/` — all framework/external-system code: Doctrine repositories, Symfony controllers, Messenger handlers, channel adapters, AI clients.
### Dependency Injection
Every Domain repository interface is aliased to its Doctrine implementation in `config/services.yaml`. All Application interfaces (ports) are aliased there too. When adding a new interface+implementation pair, add the alias manually — autowiring alone won't resolve interfaces.
Channel adapters are collected via `tagged_iterator app.channel_adapter` into `ChannelAdapterRegistry`. Tag new adapters in `services.yaml`.
### Routing (Symfony 8 gotcha)
`routing.controllers` auto-discovers all controllers. API controllers **must** declare the `/api` prefix in their class-level `#[Route]` attribute — a yaml `prefix:` on top of auto-discovery is silently ignored.
### AI Pipeline
The AI backend is `MistralClient` (vision: pixtral-12b, text: mistral-large). The interface is named `OllamaClientInterface` for historical reasons — the `services.yaml` alias points it to `MistralClient`. Web search uses `TavilyWebSearch` behind `WebSearchInterface`.
Pipeline A (photo) chains Messages sequentially — each handler dispatches the next:
`PhotoUpload → SpecsResearch → JsonCoding → Validation → DraftArticle → EbayText`
After vision, `findCompletedByModelNumber()` checks the DB for a cache hit and skips the remaining AI steps if found.
`PipelineJobFailureListener` catches `WorkerMessageFailedEvent` after all retries are exhausted and sets `AIPipelineJob.status = failed`.
### Messenger Transports
Three isolated Redis streams — a failing worker never blocks the others:
| Transport | Worker service | Retries | Delay |
|---|---|---|---|
| `ai_pipeline` | `worker-ai` | 3 | 2 s ×2 |
| `orders` | `worker-orders` | 5 | 1 s ×2 |
| `channel_sync` | `worker-channel` | 5 | 2 s ×2, max 60 s |
Exhausted messages land in `failed` transport (persistent). Replay with `messenger:failed:retry`.
### eBay Integration
`EbayAdapter` implements `ChannelAdapterInterface`. It uses:
- `EbayInventoryApiClient` — inventory items, offers, publish/withdraw, stock updates, tracking
- `EbayFulfillmentApiClient` — order fetching
- `EbayOAuthClient` — Client-Credentials token with `cache.app` caching
- `EbayTaxonomyService` — category/aspect lookup, also cached
`ArticleTypePlatformConfig` holds per-ArticleType eBay settings (category ID, business policy IDs). `ArticleTypeEbayMapping` maps each eBay aspect name to either an `Article` field (`SOURCE_ARTICLE_FIELD`) or an `AttributeDefinition` (`SOURCE_ATTRIBUTE`). `EbayAdapter.buildAspects()` reads from this table.
### EasyAdmin (Admin Panel)
`DashboardController` carries `#[AdminDashboard]` and `configureMenuItems()`. All CRUD controllers in `src/Infrastructure/Http/Controller/Admin/` are auto-discovered. Menu items reference controller class names directly via `MenuItem::linkTo()`.
### PostgreSQL Schemas
Three schemas: `app` (all entities), `logs` (live log entries), `logs_archive` (rotated). `doctrine.yaml` sets `schema_filter` to exclude `logs_archive.*` and `app.inventory_seq` from migration diffs — never remove that filter.
### Auth
Browser login: form + optional TOTP (`scheb/two-factor-bundle`). API access: `X-Api-Key` header — stored as bcrypt hash with prefix for lookup. `PermissionVoter` checks `User.permissions` and `ApiKey.permissions` (both jsonb) uniformly. Permission constants live in `PermissionVoter`.
### Docker
All PHP services (`app`, `worker-*`, `cron`) run as user `1000:1000` with `HOME=/tmp`. Never run commands as root inside the container — it creates root-owned files on the host.
`docker-compose.override.yml` exists only for local dev (exposes Postgres/Redis ports). Do not use it on the production server.