mirror of
https://github.com/photoprism/photoprism.git
synced 2026-07-17 16:49:04 +00:00
Add pkg/txt.JoinOr plus acl.RolesCliUsageString and UserRoleUsageFor so the users/clients --role and --auth help derive from the role tables instead of hand-maintained literals, filtering the app/uploader aliases and visitor. Each edition lists its own roles; federatable contexts (LDAP, OIDC group-role, cluster grants) use ClusterInstanceRoles.
5.1 KiB
5.1 KiB
API & Config Changes
- Respect precedence:
options.ymloverrides CLI/env values, which override defaults. - Adding a new option: update
internal/config/options.go(yaml/flag tags), register ininternal/config/flags.go, expose a getter, surface it in*config.Report(), and write generated values back tooptions.yml. UseCliTestContextininternal/config/test.goto exercise new flags. - Adding a
customize.FeatureSettingsflag: a new field defaults totruevia reflection (features_default.go) and is operator-disableable throughPHOTOPRISM_DISABLE_FEATURES— no new CLI option needed. It cascades: update the full-struct literals ininternal/config/customize/{acl,scope}_test.goandinternal/config/client_config_test.go(the longest field name re-aligns every literal via gofmt), andtestdata/settings.ymlself-updates viaTestSettings_Save. If the flag is only meaningful for accounts/roles, gate it per-session incustomize.Settings.ApplyACL/ApplyScope(e.g.Account/AppPasswordsrequireResourcePassword/ActionUpdate); that shapes the Web UI client config only — enforce server-side behavior on the global flag via aConfig.DisableX()helper. - Identify an app-password credential by its session, not its token:
(*entity.Session).IsApplication()(auth providerapplication) covers every grant that mints one (password/session/cli). The token format and grant type vary, so don't gate onrnd.IsAppPasswordorGrantType. - For
options.ymlwrites, prefer config-owned persistence helpers:Config.SaveOptionsPatch(...)for generic merges,Config.SaveClusterOptionsUpdate(...)for cluster-managed metadata. - Use
pkg/fs.ConfigFilePathfor config filenames so existing.ymlfiles stay valid and new installs can adopt.yamltransparently. - Use the public accessors on
*config.Config(e.g.JWKSUrl(),SetJWKSUrl()) instead of mutatingConfig.Options()directly; reserve raw option tweaks for test fixtures. - New metadata sources (e.g.
SrcOllama,SrcOpenAI) must be defined in bothinternal/entity/src.goand the frontend lookup tables (frontend/src/common/util.js). - Config init order: load
options.yml(c.initSettings()), runEarlyExt().InitEarly(c), connect/register the DB, thenExt().Init(c). - Favor explicit CLI flags: check
c.cliCtx.IsSet("<flag>")before overriding user-supplied values. - Database helpers: reuse
conf.Db()/conf.Database*(), avoid GORMWithContext, quote MySQL identifiers, and reject unsupported drivers early.
Handler Conventions
- Reuse limiter stacks (
limiter.Auth,limiter.Login) andlimiter.AbortJSONfor 429s. Lean onapi.ClientIP,header.BearerToken, andAbort*helpers. - Compare secrets with constant-time checks; set
Cache-Control: no-storeon sensitive responses. - Register routes in
internal/server/routes.go. New list endpoints defaultcount=100(max 1000) andoffset≥0; document parameters explicitly. - Set portal mode via
PHOTOPRISM_NODE_ROLE=portalplusPHOTOPRISM_JOIN_TOKENwhen needed.
API Shape Checklist
When renaming or adding fields:
- Field casing: TitleCase (
UUID,Name,SiteUrl) for fields backed by a DB entity (mirror the entity/model), camelCase (storageNamespace,redirectUri) for generated/artificial payloads (client config, session, action/RPC bodies). A filtered/computed entity projection stays TitleCase; an action payload stays camelCase but MAY TitleCase its single entity-identity field (e.g.UUID). Seespecs/common/field-casing.md. - Update DTOs in
internal/service/cluster/response.goand any mappers. - Update handlers and regenerate Swagger:
make fmt-go swag-fmt swag. - Update tests (search/replace old field names) and examples in
specs/. - Quick grep:
rg -n 'oldField|newField' -Sacross code, tests, and specs.
Testing Helpers
- Isolate config paths with
t.TempDir(); reuseNewConfig,CliTestContext, andNewApiTest()harnesses. - Authenticate via
AuthenticateAdmin,AuthenticateUser, orOAuthToken. Toggle auth withconf.SetAuthMode(config.AuthModePasswd). - Prefer OAuth client tokens over non-admin fixtures for negative permission checks.
Roles & ACL
- Map roles via the shared tables: users through
acl.ParseRole(s)/acl.UserRoles[...], clients throughacl.ClientRoles[...]. - Treat
RoleAliasNone("none") and an empty string asRoleNone; default unknown client roles toRoleClient. - Build CLI role help from the registered role map (never hand-maintained literals) so each edition lists exactly the roles it accepts:
commands.UserRoleUsageFor(<map>)/Roles.CliUsageString(), passing CEacl.UserRolesor an edition's own staticauth.UserRoles(reference the edition map directly, not the runtime-reassignedacl.UserRoles, to avoid the init-order trap; Portal's map includescluster_admin). For federatable / cluster-instance contexts (LDAP, OIDC group→role, cluster grants) useacl.ClusterInstanceRolesCliUsageString()— it excludescluster_admin/visitor.pkg/txt.JoinOrrenders the "a, b, or c" style. - For JWT/client scope checks, use the shared helpers (
acl.ScopePermits/acl.ScopeAttrPermits).