Claude-skill-registry layer-interfaces
Use when creating or modifying code in nomarr/interfaces/. Covers API routes, CLI commands, and web handlers. Interfaces are thin adapters that validate inputs, call services, and serialize outputs.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/layer-interfaces" ~/.claude/skills/majiayu000-claude-skill-registry-layer-interfaces && rm -rf "$T"
skills/data/layer-interfaces/SKILL.mdInterfaces Layer
Purpose: Expose Nomarr to the outside world via HTTP (FastAPI), CLI (Typer), and web handlers.
Interfaces are thin adapters. They do three things:
- Validate inputs
- Call one service method
- Serialize outputs
No business logic lives here.
Allowed Imports
# ✅ Allowed from nomarr.services import LibraryService, TaggingService from nomarr.helpers.dto import LibraryDict, FileDict from nomarr.interfaces.api.types import LibraryResponse # Pydantic models
Forbidden Imports
# ❌ NEVER import these in interfaces from nomarr.workflows import ... # Call services, not workflows from nomarr.components import ... # Call services, not components from nomarr.persistence import ... # No direct DB access
The One Service Call Rule
Each route handler should call exactly one service method.
# ✅ Good - one service call @router.get("/library/{library_id}") def get_library(library_id: str, library_service: LibraryService = Depends(...)) -> LibraryResponse: library = library_service.get_library(library_id) return LibraryResponse.from_dto(library) # ❌ Bad - multiple service calls @router.post("/process/{library_id}") def process(library_id: str, library_service: LibraryService = Depends(...), tagging_service: TaggingService = Depends(...)): library = library_service.get_library(library_id) tagging_service.tag_library(library.key) # ← Extract to service method return {"status": "ok"}
If you need multiple service calls: Extract a service method that orchestrates them.
Data Flow
Request Flow
JSON → Pydantic Request Model → .to_dto() → Service (DTO)
Response Flow
Service (DTO) → .from_dto() → Pydantic Response Model → JSON
Pydantic models live only in interfaces. Never let them leak into services.
Authentication Rules
MANDATORY: All API endpoints require authentication except login.
Web API (/api/web/*
)
/api/web/*- Uses
for session token authenticationverify_session - All routes MUST include
ordependencies=[Depends(verify_session)]
as a parameter_session: dict = Depends(verify_session) - Exception:
is the only unauthenticated endpoint/api/web/auth/login
v1 API (/api/v1/*
)
/api/v1/*- Uses
for API key authenticationverify_key - All routes MUST include
dependencies=[Depends(verify_key)] - Exception:
is intentionally public (version info)/api/v1/public/*
API Consumer Separation — DO NOT MIX
| Router | Auth Method | Consumer | Frontend Calls? |
|---|---|---|---|
| Session token () | Web frontend | YES |
| API key () | External tools (Navidrome, scripts) | NEVER |
The web frontend MUST ONLY call
endpoints./api/web/*
The v1 API uses API key authentication. The web frontend uses session authentication. These are incompatible — the frontend cannot authenticate to v1 endpoints.
If functionality exists in v1 but the frontend needs it, create a parallel route under web API.
Error Handling
- HTTP routes: Raise
HTTPException - CLI commands: Raise
typer.Exit(1) - Let services/workflows raise domain exceptions, catch them here
@router.get("/file/{file_key}") def get_file(file_key: str, library_service: LibraryService = Depends(...)) -> FileResponse: file = library_service.get_file(file_key) if not file: raise HTTPException(status_code=404, detail="File not found") return FileResponse.from_dto(file)
Validation Checklist
Before committing interface code, verify:
- Does this file import from workflows, components, or persistence? → Violation
- Does this route call more than one service method? → Extract to service
- Does this route contain business logic (loops, branching, computation)? → Move to service
- Are Pydantic models staying in this layer only? → Services return DTOs
- Is the DTO-to-Pydantic conversion explicit? → Use
.from_dto() - Does this route have authentication? → Add
(web) orverify_session
(v1)verify_key - Is the frontend calling
? → Create web API route instead/api/v1/*
Layer Scripts
This skill includes validation scripts in
.github/skills/layer-interfaces/scripts/:
lint.py
lint.pyRuns all linters on the interfaces layer:
python .github/skills/layer-interfaces/scripts/lint.py
Executes: ruff, mypy, vulture, bandit, radon, lint-imports
check_naming.py
check_naming.pyValidates interfaces naming conventions:
python .github/skills/layer-interfaces/scripts/check_naming.py
Checks:
- Files must end in
_if.py - Route handlers should be thin (validate, call service, serialize)