mirror of
https://github.com/photoprism/photoprism.git
synced 2025-12-11 16:24:11 +01:00
6.8 KiB
6.8 KiB
API Package Guide
Overview
The API package exposes PhotoPrism’s HTTP endpoints via Gin handlers. Each file under internal/api contains the handlers, request/response DTOs, and Swagger annotations for a specific feature area. Handlers remain thin: they validate input, enforce security or ACL checks, and delegate domain work to services in internal/photoprism, internal/service, or other internal packages. Keep exported types aligned with the REST schema and avoid embedding business logic directly in handlers.
Routing & Wiring
- Register handlers in
internal/server/routes.goby attaching them to the proper router group (for example,APIv1 := router.Group(conf.BaseUri("/api/v1"), Api(conf))). - Group endpoints by resource to match existing patterns: sessions, cluster, photos, labels, files, downloads, metadata, and technical routes.
- Apply middleware stacks (
Api,AuthRequired,limiter.Auth, etc.) at the router level to keep handlers focused on request handling. - Use
conf.BaseUri()when constructing route prefixes so configuration overrides propagate consistently. - When new endpoints require feature toggles, gate them in the router rather than inside the handler so disabled routes remain undiscoverable.
Handler Implementation Patterns
- Accept and return JSON using the shared response helpers. Set
header.ContentTypeJSONand ensure responses include proper cache headers (no-storefor sensitive payloads). - Parse parameters with Gin binding and validate inputs before delegating work. For complex payloads, define dedicated request structs with validation tags.
- Use the shared download helpers (
safe.Download,avatar.SafeDownload) when calling outward HTTP APIs to inherit timeout, size, and SSRF protections. - Query and persist data through the corresponding services or repositories; avoid ad-hoc SQL or GORM usage in handlers when dedicated functions exist elsewhere.
- Surface pagination consistently with
count,offset, andlimitfollowing the defaults (100 max 1000). Validateoffset >= 0and clampcountto the allowed range. - When responses need role-specific fields, build DTOs that redact sensitive data for non-admin roles so the handler stays deterministic.
Security & Middleware
- Authenticate requests using the standard middleware (
AuthRequired) and check roles via helpers ininternal/auth/acl(acl.ParseRole,acl.ScopePermits,acl.ScopeAttrPermits). - Never log secrets or tokens. Prefer structured logging through
event.Logand redact sensitive values before logging. - Enforce rate limiting with the shared limiters (
limiter.Auth,limiter.Login) and respond withlimiter.AbortJSONto maintain consistent 429 JSON payloads. - Derive client IPs through
api.ClientIPand extract bearer tokens withheader.BearerTokenor the helper setters. Use constant-time comparison for tokens and secrets. - For downloads or proxy endpoints, validate URLs against allowed schemes (
http,https) and reject private or loopback addresses unless explicitly required.
Audit Logging
- Emit security events via
event.Audit*(AuditInfo,AuditWarn,AuditErr,AuditDebug) and always build the slice as Who → What → Outcome.- Who:
ClientIP(c)followed by the most specific actor context ("session %s","client %s","user %s"). - What: Resource constant plus action segments (for example,
string(acl.ResourceCluster),"node", "%s"). Place extra context such as counts or error placeholders in separate segments before the outcome. - Outcome: End with a single token such as
status.Succeeded,status.Failed,status.Denied, orstatus.Error(err)when the sanitized error message should be the outcome; nothing comes after it.
- Who:
- Prefer existing helpers (
ClientIP,clean.Log,clean.LogQuote,clean.Error) instead of formatting values manually, and avoid inline=expressions. - Example patterns:
event.AuditInfo([]string{ ClientIP(c), "session %s", string(acl.ResourceCluster), "node", "%s", status.Deleted, }, s.RefID, uuid) event.AuditErr([]string{ clientIp, "session %s", string(acl.ResourceCluster), "download theme", status.Error(err), }, refID) - See
specs/common/audit-logs.mdfor the full conventions and additional examples that agents should follow.
Swagger Documentation
- Annotate handlers with Swagger comments that include full
/api/v1/...paths, request/response schemas, and security definitions. Only annotate routes that are externally accessible. - Regenerate docs after adding or updating handlers:
make fmt-go swag-fmt swag. This formats Go files, normalizes annotations, and updatesinternal/api/swagger.json. Do not edit the generated JSON manually. - When adding new DTOs, keep field names aligned with the JSON schema and update client documentation if serialized names change.
- Use enum annotations sparingly and ensure they reflect actual runtime constraints to avoid misleading generated clients.
Testing Strategy
- Build tests around the API harness (
NewApiTest) to obtain a configured Gin router, config, and dependencies. This isolates filesystem paths and avoids polluting global state. - Wrap requests with helper functions (for example,
PerformRequestJSON,PerformAuthenticatedRequest) to capture status codes, headers, and payloads. Assert headers using constants frompkg/http/header. - When handlers interact with the database, initialize fixtures through config helpers such as
config.NewTestConfig("api")orconfig.NewMinimalTestConfigWithDb("api", t.TempDir())depending on fixture needs. - Stub external dependencies (
httptest.Server) for remote calls and setAllowPrivate=trueexplicitly when the test server binds to loopback addresses. - Structure tests with table-driven subtests (
t.Run("CaseName", ...)) and use PascalCase names. Provide cleanup functions (t.Cleanup) to remove temporary files or databases created during tests.
Focused Test Runs
- Fast iteration:
go test ./internal/api -run '<Package|HandlerName>' -count=1 - Cluster endpoints:
go test ./internal/api -run 'Cluster' -count=1 - Downloads and zip streaming:
go test ./internal/api -run 'Download|Archive' -count=1 - Combined CLI and API validation: pair
go test ./internal/commands -run 'Cluster' -count=1with the matching API suite to ensure DTOs remain compatible.
Preflight Checklist
- Format and regenerate documentation:
make fmt-go swag-fmt swag - Compile backend:
go build ./... - Execute targeted API suites:
go test ./internal/api -run '<Name>' -count=1 - Run integration-heavy checks before release:
go test ./internal/service/cluster/registry -count=1alongside relevant API routes to confirm cluster DTOs stay aligned. - Verify that
photoprism show commands --jsonreflects any new API-driven flags or outputs when CLI exposure changes.