| # services/ β business logic | |
| Each module holds the domain logic behind the asset extraction endpoint. | |
| Routers stay thin; heavy lifting lives here so it can be unit-tested in | |
| isolation. | |
| ## Services | |
| | File | What it does | | |
| |------|--------------| | |
| | `fetcher.py` | Safe async HTML fetch: SSRF block-list, redirect/size/timeout caps, content-type check. | | |
| | `resolver.py` | Turn `src`/`href`/CSS `url()` references into absolute URLs. | | |
| | `asset_service.py` | Orchestrates fetch β parse β extract β group. | | |
| ## `extractors/` β asset category extractors | |
| Each extractor parses the (BeautifulSoup) document and returns absolute URLs | |
| for one category. See [`extractors/README.md`](./extractors/README.md). | |
| ## Conventions | |
| - Services raise a domain-specific exception (e.g. `FetchError`, | |
| `AssetExtractError`) that the router maps to an HTTP response. | |
| - URL resolution is centralized in `resolver.py` β every extractor should | |
| funnel its raw references through it to avoid duplicated/drifted logic. | |