Rename the Response struct fields to match their json tags (Err to Error, Msg to Message) and the Error() method to ErrorString() to avoid a field/method clash; update callers and tests. JSON wire format is unchanged.
Rename the i18n notification/error envelope fields from id/params to messageId/messageParams across the WS notify Data and the i18n.Response body, so they are unambiguous next to session, log, and correlation ids. Updates the frontend consumers (notify.vue, api.js), tests, swagger, and the event/locales READMEs.
The i18n.Response envelope now carries the untranslated source id and params; the frontend (api.js) renders them via Tp in the user's UI locale, with the server-rendered error/message as the instance-locale fallback. Common not-found/conflict/bad-request handlers were switched from hardcoded literals and raw err.Error() to Abort/i18n.Message helpers, so they benefit automatically.
FindByPath now resolves a path's containing filesystem by longest-prefix match against the raw mount list, instead of the df-style display filter that hides overlay/pseudo roots. This fixes PathInfo/Free/StorageLow for paths like "/" whose mount is hidden (e.g. a container overlay root), which previously resolved to an arbitrary masked tmpfs.
Backend *Msg helpers now publish the untranslated message id and params alongside the rendered fallback; the frontend translates the id and applies a positional-placeholder shim so notifications render in each user's UI language.
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.
- Add scoped session revocation (RevokeSessions / RevokeDerivedSessions)
shared by the user-update API and the CLI: a privilege-level change now
revokes logins and app-password-derived sessions while keeping the app
passwords themselves, so configured devices keep working.
- Identify app-password-derived sessions by auth_method "session".
- Deny app passwords on the REST API when the account cannot log in
(DenyLogIn); WebDAV access stays governed by CanUseWebDAV.
- Reduce the WebDAV auth cache expiration to one minute.
- Add IsSystemOrInvalid; allow deleting the initial admin account and skip
re-initializing a deactivated or deleted admin in InitAccount.
- Move session-revocation scopes to pkg/authn and extend tests.
Mirror the lens make/model editing surface for cameras: entity UpdateMakeModel/SaveForm, form validation, query, search, the GET/PUT /api/v1/cameras endpoints, and the cameras CLI command, plus a cameras ACL resource and scope.
Also tidy the lens surface for parity: self-validating SaveForm, empty make/model guard, X-Count search header, service-role grant, the empty-id/slug docs, and order cameras before lenses everywhere.
Adds the ability to override a lens's Make/Model (e.g. fixing Pentax lenses that ExifTool decodes as `4 38`) via a new photoprism lenses update CLI command and a `PUT /api/v1/lenses/:id endpoint`, plus a `GET /api/v1/lenses` search endpoint, a new lenses ACL resource, and an lenses ls list command.
Implements three OIDC follow-ups from specs/portal/cluster-oidc.md:
- Zero-touch instance OIDC RP config: PHOTOPRISM_CLUSTER_OIDC derives the
OIDC RP client_id/secret from the node client credentials and defaults
the issuer to the instance's own origin (scheme.OriginURL), so a single
boot reaches a working Portal login. Explicit oidc-client/secret/uri win.
- Human-friendly authorize errors: redirectable errors 302 back to the
validated redirect_uri; non-redirectable ones content-negotiate a
branded HTML page (oauth-error.gohtml) vs JSON via shared helpers in
internal/api/oauth_error.go. The RP callback surfaces inbound ?error=.
- Cluster-wide Sign-Out (Tier 2): session.logoutEverywhere fans out a
best-effort DELETE per peer session, clears their storage, then signs
out locally; session-delete clears the OP cookie on any shared-domain
OIDC-against-Portal node (OIDCSessionCookieClearPath).
Move the Portal OIDC OP authorize/token/userinfo onto the shared CE
/api/v1/oauth/* routes (the reserved #4368/#4369 stubs) and drop the parallel
root /oauth/* set.
- Add overridable handler hooks (oauth_handlers.go) mirroring
server.WebDAVHandler; OAuthToken delegates only the authorization_code grant
(gated on a form content type) and keeps client_credentials/password/session
unchanged; userinfo now serves GET and POST.
- Scope the OP session cookie to /api/v1/oauth (base-path aware) so the browser
still sends it to the moved authorize endpoint instead of looping.
- Stop the proxy re-scoping instance->Portal authorize redirects
(portalRootPathPrefixes -> /api/v1/oauth/).
- Point discovery at issuer + /api/v1/oauth/*; fix the JWKS URL base-path
doubling for sub-path deployments (also in CE openid.go).
Add Formats.Match to report which exclude-list entry matched, and use it in the
remux, download, transcode, and index skip messages instead of always printing
the codec (which could be empty or differ from the entry the user configured).
Also apply the exclude list when building "videos transcode" plans so excluded
files are skipped during dry runs and real runs, and report exclusion clearly
when no preview image can be generated for an excluded video.
Combine the HEVC and MagicYUV sample-entry scans into one pass over the file
head, so the probe reads the head only once when go-mp4 does not report the
codec directly. Rename the scan limit to HeadScanLimit and group the
sample-entry chunk sets in brands.go.
Recognize MagicYUV sample entries in the built-in go-mp4 probe and consider the
probe's codec in addition to the metadata codec when applying the FFmpeg exclude
list, so a video can be matched even when one detector misses it. Rename the
shared sample-entry scan limit to CodecHeadScanLimit.
Also exclude the Matroska Video for Windows wrapper (v_ms/vfw/fourcc) by default,
since its actual codec is not exposed by metadata and the format is uncommon.
Read the video codec from the AVI VideoCodec metadata tag instead of falling
back to the container name, so codec-based entries on the FFmpeg exclude list
are applied during indexing.
Match exclude-list codecs by their canonical alias so the original name
reported by a metadata tool (e.g. m8ra), the human-readable name (magicyuv),
and the canonical FourCC (magy) are treated as equivalent. Add h264/h265
aliases and make magy the canonical MagicYUV codec name.
Replace strings.Split with the lazy strings.SplitSeq iterator (Go 1.23+)
in loops that only range over the tokens, avoiding the intermediate
slice allocation. The yielded substrings are identical to Split, so
behavior is unchanged. Applies the suggestion from #5599 to its three
sites (CIDR parsing, WebDAV hidden-path, overlay path checks) and
extends it to two more split-and-iterate loops in the server compression
negotiator and MCP config-options builder for consistency. The slice
returning splitPath helper is intentionally left on strings.Split.
Split the catch-all values.go into one file per concept, each mirrored
by its test: distance.go, norm.go, stats.go, product.go, centroid.go,
plus the mean methods folded into mean.go and Copy/Dim/Sum into
vector.go. Remove values.go, values_test.go, and values_more_test.go so
functionality and tests live where developers expect them.
Hoist the two 512-dimensional face embeddings shared by the distance,
norm, and cosine tests into fixtures_test.go, removing the previous
triplication, and decompose the monolithic TestVector into per-concept
tests. Close pre-existing coverage gaps in the integer converters and
the GeometricMean/HarmonicMean method wrappers, bringing the package to
100% statement coverage. Pure code movement; no behavior change.
Prepare pkg/vector for a future refactoring of internal/ai/face without
touching the face package yet. Normalize scales a vector to unit length
(L2) in place and leaves a zero vector unchanged; Normalized returns a
normalized copy. Centroid returns the element-wise mean of a set of
vectors, ignoring entries whose length differs from the first and
averaging over the vectors actually included. Together with the existing
EuclideanDist, EuclideanNorm, and NewVector helpers these cover the math
currently hand-rolled in the face embeddings code. Fully covered by
tests (100% of the new functions).
The root file of the vector package was missing the standard package
description and AGPL header required by the Go style rules. The vendored
alg subpackage keeps its own MIT license and is left unchanged.
CosineDist now returns a true cosine distance (1 - similarity), so
identical vectors yield 0; the previous similarity behavior moves to a
new CosineSimilarity function/method. GeometricMean no longer treats a
zero element as an uninitialized accumulator: zeros now correctly yield
0 and negative inputs return NaN. NewVector copies float64 and Vector
inputs so the result is independent of the source slice, matching the
other converters. Norm uses absolute values so odd powers (L1) stay
well-defined for negative components, variance is simplified, and the
numeric error paths return NaN instead of the Epsilon sentinel. Add
focused tests for the mean, distance, norm, and copy helpers.
Extract the per-worker scan range size into partitionSize() and floor
it at 1 so the nearest() dispatch loop always advances and terminates,
even if the worker count exceeds the number of data points. The size
based numWorkers buckets keep points >= workers today, so this is a
defensive guard against future tuning. Add focused tests for the helper
and a high-worker end-to-end case.
The vendored OPTICS implementation was never invoked: face clustering
uses DBSCAN (alg.DBSCANWithProgress), and OPTICS() had no callers or
tests. Removing it also drops a latent no-op loop (PVS-Studio V8016)
in extract(). Drop the now-orphaned errZeroXi and update the rangeJob
comment and README to match. See discussion #5628.
The portal proxy was prefixing the per-instance path scope onto absolute
Location headers whose host matched the proxy, breaking instance-to-Portal
redirects like the Pro OIDC RP pointing at the Portal authorize endpoint.
Paths under /oauth/, /.well-known/, and /portal/ are now recognized as
Portal-root and left untouched.