Skip to content

Implement Structs-Of-Arrays approach - #4652

Merged
ildyria merged 10 commits into
masterfrom
album-listing-v3
Aug 22, 2026
Merged

Implement Structs-Of-Arrays approach#4652
ildyria merged 10 commits into
masterfrom
album-listing-v3

Conversation

@ildyria

@ildyria ildyria commented Aug 22, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added a V3 album-listing API with compact, visibility-aware album data.
    • Administrators can request parent IDs and bulk-edit fields.
    • Added permission-based cover selection.
    • Added an opt-in feature flag, preserving existing behavior by default.
  • Performance
    • Added identity- and option-specific caching.
    • Cache data refreshes after relevant album and media changes.
  • Documentation
    • Documented the endpoint, response format, adoption plans, and architecture updates.
  • Tests
    • Added coverage for access, response data, caching, authorization, and invalidation.

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 017a4fc5-d8bb-4b47-8505-de4211d13d2b

📥 Commits

Reviewing files that changed from the base of the PR and between 186d1c9 and f97521b.

📒 Files selected for processing (2)
  • app/Rules/BooleanRule.php
  • tests/Feature_v2/StructOfArrayInitTest.php

Included review availability: Your plan provides up to 8 included reviews per hour; 4 remain after this review.


📝 Walkthrough

Walkthrough

The change adds a cached GET /api/v3/Albums endpoint with optional administrator fields, Struct-of-Arrays resources, cache invalidation, feature-flag exposure, tests, and architecture records.

Changes

Album Listing v3

Layer / File(s) Summary
API contracts and route
app/Http/Requests/Gallery/AlbumListV3Request.php, app/Http/Resources/V3/*, resources/js/lychee.d.ts, routes/api_v3.php
Adds validated request flags, SoA response resources, TypeScript declarations, and GET /api/v3/Albums.
Listing query and cache execution
app/Http/Controllers/Gallery/AlbumListController.php, app/Policies/AlbumQueryPolicy.php, app/Services/Cache/CacheKeyProvider.php
Adds visibility-filtered queries, optional parent and bulk-edit data, cover resolution, and user- and mode-specific caching.
Album listing cache invalidation
app/Listeners/ManagedCacheAlbumListingInvalidator.php
Evicts the v3 cache tag for album, permission, computed-data, tag, person, photo, and deletion changes.
Struct-of-Arrays feature flag
config/features.php, app/Http/Resources/GalleryConfigs/InitConfig.php, tests/Feature_v2/StructOfArrayInitTest.php
Adds the disabled-by-default feature flag and exposes its state through gallery initialization.
Endpoint and cache validation
tests/Feature_v3/*, tests/Unit/Policies/*, tests/Unit/Services/Cache/*, tests/Unit/Listeners/*, tests/Feature_v3/Base/*, Makefile, app/Rules/BooleanRule.php
Adds endpoint, authorization, response, cache, invalidation, query-join, cache-key, validation, and PHPUnit target coverage.
Architecture and feature records
docs/specs/3-reference/*, docs/specs/4-architecture/*, docs/specs/_current-session.md
Records the completed Feature 057 endpoint and the draft Feature 058 adoption work.

Estimated code review effort: 4 (Complex) | ~60 minutes

Merge Risk: 🟡 Moderate · up to f9752

This PR adds a new album-listing API with request validation, caching, and feature-default behavior. The current head can produce inconsistent created_at responses, silently accept malformed boolean inputs, and leaves a security-relevant descendant check unverified while marking a required analysis gate incomplete. These issues can affect API correctness and access handling, so the PR is not merge-ready until they are fixed or explicitly accepted.

Poem

A rabbit checks each album row,
While cached paths neatly flow.
Flags and covers line up bright,
Tests guard every mode just right.
Hop—the v3 listing takes flight!

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 64.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 74 functions across 18 files. Write docstrings for the functions missing them to satisfy the coverage threshold.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ildyria ildyria added the v8 label Aug 22, 2026
@codecov

codecov Bot commented Aug 22, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 99.59677% with 1 line in your changes missing coverage. Please review.
✅ Project coverage is 91.30%. Comparing base (b8e4d1e) to head (f97521b).

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ildyria
ildyria marked this pull request as ready for review August 22, 2026 13:43
@ildyria
ildyria requested a review from a team as a code owner August 22, 2026 13:43

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 10

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/specs/4-architecture/features/057-album-listing-v3/plan.md (1)

152-156: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required Markdown footer to every affected document.

  • docs/specs/4-architecture/features/057-album-listing-v3/plan.md#L152-L156: append the footer.
  • docs/specs/4-architecture/features/057-album-listing-v3/spec.md#L247-L250: append the footer.
  • docs/specs/4-architecture/features/057-album-listing-v3/tasks.md#L98-L101: append the footer.
  • docs/specs/4-architecture/features/058-album-listing-v3-adoption/plan.md#L121-L125: append the footer.
  • docs/specs/4-architecture/features/058-album-listing-v3-adoption/spec.md#L174-L180: append the footer.
  • docs/specs/4-architecture/features/058-album-listing-v3-adoption/tasks.md#L69-L72: append the footer.
  • docs/specs/4-architecture/open-questions.md#L9-L15: append the footer at end of file.
  • docs/specs/_current-session.md#L38-L41: append the footer.

Source: Coding guidelines

docs/specs/4-architecture/features/057-album-listing-v3/tasks.md (1)

98-101: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required Markdown footer.

At the end of this file, add --- followed by *Last updated: 2026-08-22*.

Source: Coding guidelines

🧹 Nitpick comments (6)
app/Policies/AlbumQueryPolicy.php (2)

673-677: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the empty default for $prefix.

The docblock states $prefix must be non-empty. The signature still defaults it to ''. A call that omits $prefix produces a base_albums alias that collides with the alias applyVisibilityFilter() already adds through joinBaseAlbumOwnerId(). Make the parameter required, or default it to 'bulk_', so the documented constraint is enforced by the signature.

♻️ Proposed change
 	public function joinBaseAlbumBulkEditFields(
 		AlbumBuilder|FixedQueryBuilder|BaseBuilder $query,
 		string $second = 'inner.id',
-		string $prefix = '',
+		string $prefix = 'bulk_',
 	): void {

678-696: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the shared joinSub body.

joinBaseAlbumOwnerId(), joinBaseAlbumBulkEditFields(), and joinBaseAlbumSensitive() now contain the same joinSub call. Only the column list differs. Extract a private helper that takes the column list, $second, and $prefix. This removes the triplicated join wiring.

app/Http/Controllers/Gallery/AlbumListController.php (2)

136-136: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Drop the & on the Collection parameters.

PHP passes objects by handle. Collection &$rows adds no benefit here and none of the three methods reassigns $rows. Remove the reference to keep the signatures conventional.

♻️ Proposed change
-	private function toAlbumListResource(Collection &$rows, ?User $user): AlbumListResource
+	private function toAlbumListResource(Collection $rows, ?User $user): AlbumListResource
-	private function toParentIds(Collection &$rows): array
+	private function toParentIds(Collection $rows): array
-	private function toBulkEditResource(Collection &$rows): AlbumListBulkEditFieldsResource
+	private function toBulkEditResource(Collection $rows): AlbumListBulkEditFieldsResource

Also applies to: 170-170, 187-187


65-114: 🚀 Performance & Scalability | 🔵 Trivial

Confirm the unbounded listing is acceptable at scale.

queryAlbums() returns every visible album with no limit. for_bulk_edit=true adds three joins plus a users join per row. For an installation with tens of thousands of albums, one uncached request builds the whole array set in memory. The cache reduces repeat cost, but every invalidation event evicts the whole album-listing-v3 tag, so a write-heavy installation rebuilds the full listing often.

Add a metric on the uncached build duration and result size so the cost is observable in production.

tests/Feature_v3/Album/AlbumListV3Test.php (1)

265-305: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a test for the documented 422 response.

docs/specs/3-reference/api-design.md line 239 documents 422 when with_parent_id or for_bulk_edit is present but not boolean-parseable. No test covers it. A test that sends with_parent_id=maybe as an admin would pin the validation contract and would also detect a regression where the flag silently falls back to false.

docs/specs/4-architecture/features/058-album-listing-v3-adoption/plan.md (1)

31-34: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Document the existing server-side descendant check.
Move::do() delegates to Album::appendNode(), and lychee-org/nestedset 11.0.1 prevents moving a node into its descendant. Update the plan, spec, and T-058-03 to record that the client-side exclusion is a UX safeguard, not the security boundary.

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 1549e254-f35a-4d21-aa4e-93fe8d3addec

📥 Commits

Reviewing files that changed from the base of the PR and between 2a78f91 and dd8a134.

📒 Files selected for processing (26)
  • Makefile
  • app/Http/Controllers/Gallery/AlbumListController.php
  • app/Http/Requests/Gallery/AlbumListV3Request.php
  • app/Http/Resources/V3/AlbumListBulkEditFieldsResource.php
  • app/Http/Resources/V3/AlbumListResource.php
  • app/Listeners/ManagedCacheAlbumListingInvalidator.php
  • app/Policies/AlbumQueryPolicy.php
  • app/Services/Cache/CacheKeyProvider.php
  • docs/specs/3-reference/api-design.md
  • docs/specs/4-architecture/features/057-album-listing-v3/plan.md
  • docs/specs/4-architecture/features/057-album-listing-v3/spec.md
  • docs/specs/4-architecture/features/057-album-listing-v3/tasks.md
  • docs/specs/4-architecture/features/058-album-listing-v3-adoption/plan.md
  • docs/specs/4-architecture/features/058-album-listing-v3-adoption/spec.md
  • docs/specs/4-architecture/features/058-album-listing-v3-adoption/tasks.md
  • docs/specs/4-architecture/knowledge-map.md
  • docs/specs/4-architecture/open-questions.md
  • docs/specs/4-architecture/roadmap.md
  • docs/specs/_current-session.md
  • resources/js/lychee.d.ts
  • routes/api_v3.php
  • tests/Feature_v3/Album/AlbumListV3Test.php
  • tests/Feature_v3/Base/BaseApiWithDataTest.php
  • tests/Unit/Listeners/ManagedCacheAlbumListingInvalidatorTest.php
  • tests/Unit/Policies/AlbumQueryPolicyTest.php
  • tests/Unit/Services/Cache/CacheKeyProviderTest.php

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread app/Http/Controllers/Gallery/AlbumListController.php
Comment thread docs/specs/4-architecture/features/057-album-listing-v3/plan.md
Comment thread docs/specs/4-architecture/features/057-album-listing-v3/plan.md
Comment thread docs/specs/4-architecture/features/057-album-listing-v3/plan.md
Comment thread docs/specs/4-architecture/features/057-album-listing-v3/spec.md
Comment thread docs/specs/4-architecture/knowledge-map.md
Comment thread docs/specs/4-architecture/open-questions.md
Comment thread tests/Feature_v3/Album/AlbumListV3Test.php

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 97332ff4-96ec-497a-8fa2-922c9a9eafb8

📥 Commits

Reviewing files that changed from the base of the PR and between dd8a134 and 82fe1df.

📒 Files selected for processing (6)
  • app/Http/Requests/Gallery/AlbumListV3Request.php
  • app/Http/Resources/GalleryConfigs/InitConfig.php
  • app/Http/Resources/V3/AlbumListResource.php
  • app/Rules/BooleanRule.php
  • config/features.php
  • tests/Feature_v2/StructOfArrayInitTest.php

Included review availability: Your plan provides up to 8 included reviews per hour; 7 remain after this review.

Comment thread app/Http/Requests/Gallery/AlbumListV3Request.php
Comment thread app/Http/Resources/V3/AlbumListResource.php
Comment thread app/Rules/BooleanRule.php Outdated
Comment thread tests/Feature_v2/StructOfArrayInitTest.php Outdated

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
tests/Feature_v3/Album/AlbumListV3Test.php (2)

54-69: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Restore query logging when the callback throws.

$callback() can throw because the callers execute response assertions inside it. If that happens, the cleanup at Lines 68-69 is skipped. Later tests can inherit enabled query logging and accumulate queries. Use try/finally to flush and disable query logging on every path.


364-374: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Assert all public-permission fields covered by this scenario.

The test documents grants_* behavior but checks only is_publics and is_link_requireds. A regression in grants_full_photo_accesses, grants_downloads, or grants_uploads could pass. Assert all three values from the public access_permissions row.

The AlbumListBulkEditFieldsResource contract includes all three grant arrays.


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 5380afbb-6837-466f-8d11-dff720d84c12

📥 Commits

Reviewing files that changed from the base of the PR and between 82fe1df and 186d1c9.

📒 Files selected for processing (1)
  • tests/Feature_v3/Album/AlbumListV3Test.php

Included review availability: Your plan provides up to 8 included reviews per hour; 5 remain after this review.

Comment thread tests/Feature_v3/Album/AlbumListV3Test.php
@ildyria

ildyria commented Aug 22, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai approve?

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

repo="LycheeOrg/Lychee"
pr="4652"

echo "== PR reviews =="
gh api "repos/${repo}/pulls/${pr}/reviews" --paginate \
  --jq '.[] | {user: .user.login, state: .state, submitted_at: .submitted_at, body: .body}'

echo "== Unresolved review threads =="
gh api graphql \
  -F owner="LycheeOrg" \
  -F name="Lychee" \
  -F number="$pr" \
  -f query='
query($owner: String!, $name: String!, $number: Int!) {
  repository(owner: $owner, name: $name) {
    pullRequest(number: $number) {
      reviewThreads(first: 100) {
        nodes {
          isResolved
          comments(first: 20) {
            nodes {
              author { login }
              body
              path
              line
              originalLine
              createdAt
            }
          }
        }
      }
    }
  }
}' \
  --jq '.data.repository.pullRequest.reviewThreads.nodes[]
    | select(.isResolved == false)
    | .comments.nodes[]
    | {author: .author.login, path: .path, line: (.line // .originalLine), body: .body, createdAt: .createdAt}'

Length of output: 15751


@ildyria Approval is appropriate. GitHub reports no unresolved review threads.

[approve]

You are interacting with an AI system.

@ildyria

ildyria commented Aug 22, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai please approve?

@coderabbitai

coderabbitai Bot commented Aug 22, 2026

Copy link
Copy Markdown
Contributor

@ildyria Approved. No unresolved review threads remain.

✅ Action performed

Comments resolved and changes approved.

@ildyria
ildyria merged commit 109f51d into master Aug 22, 2026
54 checks passed
@ildyria
ildyria deleted the album-listing-v3 branch August 22, 2026 21:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant