photoprism/internal/api
Keith Martin b6687b6c29
Tests: Improve isolation and add fixes #5263
* Tests: fix random selection of deleted record causing test failure
* Tests: fix test folders being left after test
* Tests: ensure that required records are available
* Tests: make each package execute against it's own testdata folder to ensure no interpackage test failures
* Tests: add unit tests of CleanupTestFolder
* Tests:  Allow defer in TestMain
* Tests: rename testMain to runTestMain
2026-07-12 05:47:21 +02:00
..
download Tests: Improve isolation and add fixes #5263 2026-07-12 05:47:21 +02:00
embed UX: Refactor video formats and codecs in front and backend #1307 #3168 2025-01-28 23:26:52 +01:00
testdata AI: Finalize facial embeddings, labels and nsfw API endpoints #127 #1090 2025-04-10 20:28:26 +02:00
abort.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
AGENTS.md Docs: Split AGENTS guides into a hierarchical structure 2026-04-09 19:28:14 +02:00
albums.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
albums_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
albums_search_test.go Config: Add "develop" feature flag to disable new viewer sidebar #3168 2025-02-03 12:29:02 +01:00
albums_test.go API: Scope photo reads and updates to the session 2026-05-29 07:04:47 +02:00
api.go Links: Use canonical trailing-slash form for website URLs 2026-06-24 17:20:30 +02:00
api_auth.go Auth: Log anonymous API requests at debug level instead of warning #5647 2026-06-15 23:56:56 +00:00
api_auth_jwt.go Cluster: Improve usage of canonical terms in code/docs based on glossary 2026-02-23 16:16:29 +01:00
api_auth_jwt_test.go Auth: Authorize cluster JWT for instance user management 2026-06-03 17:12:59 +02:00
api_auth_test.go Tests: Add unit tests for recent metadata, auth, and config changes #5697 2026-06-26 14:12:59 +02:00
api_client_config.go People: Load name suggestions from typeahead cache #5666 2026-06-15 14:38:04 +00:00
api_client_config_test.go API: Add config.updated broadcast tests to api_client_config_test.go 2026-05-10 15:07:37 +02:00
api_event.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
api_event_test.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
api_log.go Logs: Shorten the names of error log helper functions 2024-01-18 11:23:59 +01:00
api_methods.go API: Improve form and tests for POST /batch/photos/edit endpoint #271 2025-07-08 10:40:45 +02:00
api_request.go Pkg: Move /service/http/... to /http/... and add package /http/dns 2025-10-19 21:08:48 +02:00
api_response.go Auth: Use hashed auth tokens for enhanced security #3943 #808 #782 2024-01-06 17:35:19 +01:00
api_response_headers.go Pkg: Move /service/http/... to /http/... and add package /http/dns 2025-10-19 21:08:48 +02:00
api_response_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
api_test.go Tests: Improve isolation and add fixes #5263 2026-07-12 05:47:21 +02:00
auth_tokens.go
batch_albums.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
batch_labels.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
batch_photos.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
batch_photos_edit.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
batch_photos_edit_test.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
batch_photos_test.go API: Align i18n.Response Go field names with JSON keys #5682 2026-06-23 12:42:57 +00:00
cache.go Pkg: Move /service/http/... to /http/... and add package /http/dns 2025-10-19 21:08:48 +02:00
cache_test.go Tests: Improve isolation and add fixes #5263 2026-07-12 05:47:21 +02:00
cameras.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cameras_search.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cameras_search_test.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cameras_test.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
cluster_health_test.go Security: Add gosec fixes with shared URL and fs validation helpers 2026-03-03 16:38:41 +01:00
cluster_metrics.go AI: Enhance "GET /api/v1/metrics" endpoint with additional stats #213 2025-10-31 15:38:10 +01:00
cluster_metrics_test.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
cluster_nodes.go Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
cluster_nodes_redaction_test.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
cluster_nodes_register.go Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
cluster_nodes_register_test.go Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
cluster_nodes_test.go Cluster: Accept all instance roles in group config, tolerate spaces 2026-06-13 12:54:54 +00:00
cluster_nodes_update_siteurl_test.go Cluster: Revalidate portal proxy cache & tighten OAuth scope usage 2026-02-26 22:42:05 +01:00
cluster_permissions_test.go Cluster: Revalidate portal proxy cache & tighten OAuth scope usage 2026-02-26 22:42:05 +01:00
cluster_summary.go Cluster: Revalidate portal proxy cache & tighten OAuth scope usage 2026-02-26 22:42:05 +01:00
cluster_test_helpers.go Cluster: Protect register mutations and enforce CIDR gate 2026-02-26 19:59:33 +01:00
cluster_theme.go Logs: Replace status string literals with generic constants 2025-10-21 15:08:10 +02:00
cluster_theme_test.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
config_options.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
config_options_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
config_settings.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
config_settings_test.go API: Extend pre-parse request limits to more JSON handlers 2026-03-08 10:29:34 +01:00
connect.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
connect_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
covers.go Config: Add an option to disable the web user interface #5111 2025-07-14 19:30:24 +02:00
covers_test.go API: Apply "golangci-lint" recommendations #5330 2025-11-22 09:25:01 +01:00
doc_overrides.go API: Add const, func, and struct comments for easier troubleshooting 2025-09-30 22:02:43 +02:00
docs.go Backend: Remove legacy Go build tags #5330 2025-11-22 09:24:28 +01:00
download.go UX: Render backend error responses in the current UI locale #5682 2026-06-23 12:06:49 +00:00
download_album.go API: Apply "golangci-lint" recommendations #5330 2025-11-22 09:25:01 +01:00
download_album_test.go Tests: Add unit tests 2025-03-18 15:20:51 +01:00
download_test.go UX: Render backend error responses in the current UI locale #5682 2026-06-23 12:06:49 +00:00
echo.go PWA: Improve service worker server endpoint #5274 2025-10-18 12:30:38 +02:00
echo_test.go CLI: Added JWT issuance and diagnostics sub commands #5230 2025-09-26 02:38:49 +02:00
errors.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
errors_test.go API: Add additional fields to label and subject edit forms #383 #3168 2025-01-17 02:55:07 +01:00
faces.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
faces_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
faces_search_test.go Config: Add "develop" feature flag to disable new viewer sidebar #3168 2025-02-03 12:29:02 +01:00
faces_test.go Auth: Refactor cluster configuration and provisioning API endpoints #98 2025-09-24 08:28:38 +02:00
file_delete.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
file_delete_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
file_orientation.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
files.go API: Scope file lookup and zip selection to the session 2026-05-29 07:04:47 +02:00
files_test.go API: Cover shared-only session access to the files endpoint 2026-06-01 15:21:10 +00:00
folders_cover.go Config: Add an option to disable the web user interface #5111 2025-07-14 19:30:24 +02:00
folders_cover_test.go Auth: Refactor cluster configuration and provisioning API endpoints #98 2025-09-24 08:28:38 +02:00
folders_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
folders_search_test.go Tests: Add unit tests for recent metadata, auth, and config changes #5697 2026-06-26 14:12:59 +02:00
health.go API: Add cluster operations endpoints to manage and register nodes #98 2025-09-15 06:43:43 +02:00
import.go API: Align i18n.Response Go field names with JSON keys #5682 2026-06-23 12:42:57 +00:00
import_test.go API: Align i18n.Response Go field names with JSON keys #5682 2026-06-23 12:42:57 +00:00
index.go API: Align i18n.Response Go field names with JSON keys #5682 2026-06-23 12:42:57 +00:00
index_test.go API: Align i18n.Response Go field names with JSON keys #5682 2026-06-23 12:42:57 +00:00
labels.go UX: Render backend error responses in the current UI locale #5682 2026-06-23 12:06:49 +00:00
labels_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
labels_search_test.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
labels_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
lenses.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
lenses_search.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
lenses_search_test.go Metadata: Add Camera Make and Model updates via CLI & API #5663 #5656 2026-06-15 09:25:44 +00:00
lenses_test.go Metadata: Add Lens Make and Model updates via CLI & API #5644 #5656 2026-06-15 09:37:32 +02:00
links.go UX: Render backend error responses in the current UI locale #5682 2026-06-23 12:06:49 +00:00
links_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
markers.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
markers_test.go Backend: Clean up error handling and unit tests in internal/ packages 2026-02-09 12:00:33 +01:00
mcp.go Backend: Compact verbose comments in config and MCP handler 2026-05-15 17:50:08 +00:00
mcp_test.go MCP: Add HTTP scope-gate tests for admin app password #5024 2026-05-10 15:00:43 +02:00
metrics.go Metrics: Use string constants in API endpoint #5355 2025-11-30 10:50:26 +01:00
metrics_test.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
moments_time.go API: Fix incorrect Swagger response schemas and "Fore more" typos 2026-04-15 15:56:13 +02:00
moments_time_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
oauth_authorize.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oauth_authorize_test.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oauth_error.go Auth: Land cluster sign-out on the Portal login without return_to 2026-06-12 19:56:44 +00:00
oauth_error_test.go Auth: Land cluster sign-out on the Portal login without return_to 2026-06-12 19:56:44 +00:00
oauth_handlers.go Auth: Add RP-initiated OIDC logout via PHOTOPRISM_OIDC_LOGOUT #5684 #5433 2026-06-24 17:21:00 +02:00
oauth_logout.go Auth: Add RP-initiated OIDC logout via PHOTOPRISM_OIDC_LOGOUT #5684 #5433 2026-06-24 17:21:00 +02:00
oauth_logout_test.go Auth: Add RP-initiated OIDC logout via PHOTOPRISM_OIDC_LOGOUT #5684 #5433 2026-06-24 17:21:00 +02:00
oauth_revoke.go API: Apply "golangci-lint" recommendations #5330 2025-11-22 09:25:01 +01:00
oauth_revoke_test.go API: Apply "golangci-lint" recommendations #5330 2025-11-22 09:25:01 +01:00
oauth_token.go Auth: Gate app passwords behind a settings feature flag #5647 2026-06-09 17:36:24 +00:00
oauth_token_ratelimit_test.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
oauth_token_test.go Auth: Gate app passwords behind a settings feature flag #5647 2026-06-09 17:36:24 +00:00
oauth_userinfo.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oauth_userinfo_test.go Auth: Serve Portal OIDC OP endpoints under /api/v1/oauth/* #4368 #4369 2026-06-03 15:28:05 +00:00
oidc_login.go Auth: Localize login/session/OIDC error responses via messageId #5699 #5682 2026-06-27 11:34:54 +00:00
oidc_login_test.go Tests: Add unit tests 2024-07-17 16:35:49 +02:00
oidc_redirect.go Auth: Localize login/session/OIDC error responses via messageId #5699 #5682 2026-06-27 11:34:54 +00:00
oidc_redirect_test.go Auth: Localize login/session/OIDC error responses via messageId #5699 #5682 2026-06-27 11:34:54 +00:00
oidc_session_cookie.go Cluster: Compact code comments in the OIDC follow-up changes 2026-06-09 10:20:17 +00:00
oidc_session_cookie_test.go Cluster: Add cluster OIDC, branded authorize errors, and sign-out 2026-06-09 09:39:01 +00:00
options.go API: Update swagger endpoint documentation #5133 2025-08-28 11:00:19 +02:00
photo_label.go UX: Render backend error responses in the current UI locale #5682 2026-06-23 12:06:49 +00:00
photo_label_test.go UX: Render backend error responses in the current UI locale #5682 2026-06-23 12:06:49 +00:00
photo_unstack.go Photos: Limit by-UID label, marker & file edits to the session scope #1307 2026-06-14 15:37:57 +00:00
photo_unstack_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
photos.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
photos_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
photos_search_geo.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
photos_search_geo_test.go
photos_search_test.go API: Document "GET /api/v1/photos/view" in swagger.json 2026-04-15 14:01:19 +02:00
photos_test.go Tests: Isolate reaction redaction subtest from mutation fixtures 2026-06-14 16:38:34 +00:00
places_reverse.go Places: Add config option to specify location details locale #465 #883 2025-07-03 12:58:20 +02:00
places_search.go UX: Render backend error responses in the current UI locale #5682 2026-06-23 12:06:49 +00:00
places_test.go Tests: Add unit tests #465 2025-07-09 14:57:32 +02:00
reactions.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
README.md Docs: Document the API JSON field-casing convention 2026-06-09 03:18:15 +00:00
request_limits.go MCP: Cap /api/v1/mcp body size and shorten session idle timeout #5024 2026-04-20 13:54:07 +02:00
server.go API: Apply "golangci-lint" recommendations #5330 2025-11-22 09:25:01 +01:00
services.go API: Fix incorrect Swagger response schemas and "Fore more" typos 2026-04-15 15:56:13 +02:00
services_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
services_search_test.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
services_test.go API: Update endpoints to return HTTP 201 when a new resource was created 2025-10-20 16:46:59 +02:00
services_upload.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
services_upload_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
session.go Backend: Move get package to /internal/photoprism/get 2024-07-02 08:03:30 +02:00
session_create.go Auth: Localize login/session/OIDC error responses via messageId #5699 #5682 2026-06-27 11:34:54 +00:00
session_delete.go Auth: Delegate cluster OIDC logout to the Portal end-session endpoint #5684 2026-06-24 15:22:04 +00:00
session_delete_test.go Auth: Add RP-initiated OIDC logout via PHOTOPRISM_OIDC_LOGOUT #5684 #5433 2026-06-24 17:21:00 +02:00
session_get.go API: Set Cache-Control no-store on the session response 2026-06-05 21:03:38 +02:00
session_ratelimit_test.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
session_response.go Auth: Localize login/session/OIDC error responses via messageId #5699 #5682 2026-06-27 11:34:54 +00:00
session_test.go Auth: Localize login/session/OIDC error responses via messageId #5699 #5682 2026-06-27 11:34:54 +00:00
share.go Config: Refactor frontend URI handling for configurable base paths 2026-02-25 00:55:16 +01:00
share_preview.go Clean-up: Drop imaging and pigo library integrations #5353 #5508 #668 2026-04-01 13:47:42 +02:00
share_preview_test.go Auth: Add CLI command to create access tokens for apps #782 #808 #3943 2024-01-05 16:31:07 +01:00
share_test.go API: Apply "golangci-lint" recommendations #5330 2025-11-22 09:25:01 +01:00
status.go API: Update swagger endpoint documentation #5133 2025-08-28 11:20:00 +02:00
status_test.go API: Add additional fields to label and subject edit forms #383 #3168 2025-01-17 02:55:07 +01:00
subjects.go Events: Refactor entity change notifications for sharing features #1307 2026-06-11 15:40:57 +00:00
subjects_search.go API: Search X-Count header for Labels and Services #5649 2026-06-15 10:00:08 +02:00
subjects_search_test.go Config: Add "develop" feature flag to disable new viewer sidebar #3168 2025-02-03 12:29:02 +01:00
subjects_test.go UX: New Action Menu for Faces in People Editing Tab #4151 #797 #5249 2025-10-10 13:02:31 +02:00
svg.go API: Add missing Swagger annotations and update swagger.json 2025-10-30 11:00:16 +01:00
svg_test.go Test: Use PascalCase names for all Go subtests in /internal 2025-10-02 14:50:02 +02:00
swagger.json Settings: Reorganize UI and add an Accessibility section #848 #799 #5429 2026-07-08 14:11:36 +02:00
thumbnails.go API: Fix incorrect Swagger response schemas and "Fore more" typos 2026-04-15 15:56:13 +02:00
thumbnails_test.go
users_avatar.go Upload: Report a full disk as insufficient storage #5613 2026-05-30 22:23:14 +00:00
users_avatar_test.go API: Enforce pre-parse body limits for login and uploads 2026-03-07 15:31:30 +01:00
users_passcode.go Auth: Revoke derived sessions & gate app passwords by login state #5647 2026-06-15 23:41:01 +00:00
users_passcode_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
users_password.go Auth: Revoke derived sessions & gate app passwords by login state #5647 2026-06-15 23:41:01 +00:00
users_password_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
users_sessions.go API: Improve user profile authorization checks and code comments #5619 2026-05-26 22:10:58 +02:00
users_sessions_test.go Videos: Refactor codec, content and file type specifications #4770 2025-02-05 00:30:45 +01:00
users_update.go Users: Apply 2FA changes for the cluster service principal 2026-06-29 21:59:48 +00:00
users_update_test.go Users: Prevent a non-super-admin admin from locking out the super admin 2026-06-25 16:24:51 +00:00
users_upload.go API: Align i18n.Response Go field names with JSON keys #5682 2026-06-23 12:42:57 +00:00
users_upload_multipart_test.go API: Enforce pre-parse body limits for login and uploads 2026-03-07 15:31:30 +01:00
users_upload_test.go API: Enforce pre-parse body limits for login and uploads 2026-03-07 15:31:30 +01:00
video.go API: Fix incorrect hash parameter name in GetVideo Swagger annotation 2026-05-29 13:14:19 +00:00
video_test.go Pkg: Move /service/http/... to /http/... and add package /http/dns 2025-10-19 21:08:48 +02:00
vision_caption.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_caption_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_face.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_face_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_labels.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_labels_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_nsfw.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
vision_nsfw_test.go API: Export request limit helpers and align test guidance 2026-03-08 13:49:50 +01:00
websocket.go Config: Gate node roles by edition and align tests/lint 2026-02-26 15:37:55 +01:00
websocket_create.go API: Add missing Swagger annotations and update swagger.json 2025-10-30 11:00:16 +01:00
websocket_reader.go Auth: Add "node" and "portal" roles, refactor session entity #98 2025-09-18 13:33:18 +02:00
websocket_test.go Auth: Refactor cluster configuration and provisioning API endpoints #98 2025-09-24 08:28:38 +02:00
websocket_topics_test.go Portal: Add cluster admin UI #98 2025-10-16 16:21:56 +02:00
websocket_writer.go Go: Apply go fix modernizations across backend packages 2026-02-20 03:54:33 +01:00
zip.go API: Scope file lookup and zip selection to the session 2026-05-29 07:04:47 +02:00
zip_test.go Config: Centralize options patch persistence and update related docs 2026-02-24 10:20:16 +01:00

API Package Guide

Overview

The API package exposes PhotoPrisms 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.go by 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.ContentTypeJSON and ensure responses include proper cache headers (no-store for 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, and limit following the defaults (100 max 1000). Validate offset >= 0 and clamp count to the allowed range.
  • When responses need role-specific fields, build DTOs that redact sensitive data for non-admin roles so the handler stays deterministic.
  • JSON field casing: use TitleCase field names (UUID, Name, SiteUrl, CreatedAt) for request/response bodies that correspond to a database entity (mirroring the entity/model, e.g. the cluster Node and ClusterInstance DTOs), and camelCase (storageNamespace, redirectUri) for generated or artificial payloads that do not map to a specific entity — client config, session responses, and action/RPC bodies. A filtered or computed projection of an entity stays TitleCase; an action payload that operates on an entity stays camelCase but MAY TitleCase the single identity field that mirrors the entity (e.g. a UUID).

Security & Middleware

  • Authenticate requests using the standard middleware (AuthRequired) and check roles via helpers in internal/auth/acl (acl.ParseRole, acl.ScopePermits, acl.ScopeAttrPermits).
  • Bound request bodies before parsing JSON or multipart payloads. Use LimitRequestBodyBytes(...) with a route-appropriate cap before BindJSON(...) / ShouldBindJSON(...), detect IsRequestBodyTooLarge(err), and return 413 Request Entity Too Large via AbortRequestTooLarge(...).
  • Keep new JSON binding sites on the shared request-limit path by running make check-api-request-limits (also included in make lint) after adding or refactoring API handlers in the root repo or private overlays.
  • Never log secrets or tokens. Prefer structured logging through event.Log and redact sensitive values before logging.
  • Enforce rate limiting with the shared limiters (limiter.Auth, limiter.Login) and respond with limiter.AbortJSON to maintain consistent 429 JSON payloads.
  • Derive client IPs through api.ClientIP and extract bearer tokens with header.BearerToken or 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.
  • Upload-time NSFW screening (users_upload.go) — when PHOTOPRISM_UPLOAD_NSFW=false, the upload handler runs vision.DetectNSFW against every accepted file and deletes any file flagged above the NSFW threshold before it reaches originals/. The check is skipped entirely when UPLOAD_NSFW=true (default). See internal/ai/nsfw/README.md for the full NSFW call-graph and flag matrix.

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, or status.Error(err) when the sanitized error message should be the outcome; nothing comes after it.
  • 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)
    

User-Visible Notifications vs Audit Log

event.AuditInfo / AuditWarn / AuditErr write to the audit log and broadcast on audit.log.<level> — the toast component on the frontend does NOT subscribe to that channel, so an audit entry alone produces no UI feedback. To raise a red or green toast in the browser, publish on the notify.* channel via event.Error(msg) / event.ErrorMsg(id, …) (red) or event.Success(msg) (green).

The two helpers have distinct subscribers; choose based on who the message is for:

  • Short endpoints whose response the frontend reads (single-shot CRUD, login, settings updates). The calling component renders the response, so AuditErr plus an HTTP error is enough — the UI gets the error string from the response body.
  • Long-running endpoints that the UI drives via the event hub (POST /api/v1/index, POST /api/v1/import/*path, and similar). The frontend cancels the in-flight HTTP request on the first index.* / import.* wire event, so the response body is invisible in normal operation. In-flight failures that need a specific toast MUST be published via event.ErrorMsg(...) on notify.error; an HTTP error alone produces only the frontend's generic fallback toast (or nothing, if the cancel has already fired).
  • Forensic events that don't need UI surfacing (rate limiting, ACL denials, internal aborts whose user-visible signal comes from a sibling channel). AuditErr alone is the right call.

When in doubt, ask: "after this handler returns, what does the user see?" If the answer is "the frontend will read the response", AuditErr covers it. If the answer is "the page is already subscribed to wire events and the response is discarded", publish on notify.* as well.

// Forensic audit only — frontend will read the response body and render the error.
event.AuditErr([]string{ClientIP(c), "session %s", "delete album", status.Failed}, s.RefID)
AbortBadRequest(c, err)

// Forensic audit + specific red toast — needed when the request was already canceled by the wire.
event.AuditErr([]string{ClientIP(c), "session %s", "index files", status.Failed}, s.RefID)
event.ErrorMsg(i18n.ErrIndexingFailed)

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 updates internal/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 from pkg/http/header.
  • When handlers interact with the database, initialize fixtures through config helpers such as config.NewTestConfig("api") or config.NewMinimalTestConfigWithDb("api", t.TempDir()) depending on fixture needs.
  • Stub external dependencies (httptest.Server) for remote calls and set AllowPrivate=true explicitly 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.
  • Do not run internal/api tests in parallel. These suites share fixture files, temporary assets, and database state, so parallel go test invocations can cause false failures and readonly/fixture-conflict errors.

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=1 with the matching API suite to ensure DTOs remain compatible.
  • Keep focused internal/api runs sequential. Do not launch multiple go test ./internal/api ... commands at the same time.

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=1 alongside relevant API routes to confirm cluster DTOs stay aligned.
  • Verify that photoprism show commands --json reflects any new API-driven flags or outputs when CLI exposure changes.