-
-
Notifications
You must be signed in to change notification settings - Fork 362
Add Javascript-based embed support #3778
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Implement Phase 1 of embeddable photo albums feature:
- Add EmbedController with getAlbum endpoint for public albums
- Create EmbedAlbumResource and EmbedPhotoResource for optimized API responses
- Configure CORS with 24-hour cache for embed endpoints
- Add comprehensive test suite (4 tests, 233 assertions)
- Restrict embeds to public albums without password/link requirements
API endpoint: GET /api/Embed/{albumId}
Returns album metadata and all photos with size variants and EXIF data.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Implement foundational setup for embeddable widget: - Add vite.embed.config.ts for standalone widget build - Add build:embed npm script - Create comprehensive TypeScript types for all data structures - Implement configuration validation and data attribute parsing - Create API client for fetching album data with error handling - Add responsive column calculation utilities - Create base EmbedWidget Vue component with loading/error states - Implement main entry point with auto-initialization - Add public/embed/ to .gitignore for build outputs Build output: 236KB (83KB gzipped) with sourcemaps Next phase: Implement layout algorithms (Square, Masonry, Grid, Justified, Filmstrip) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Implement complete layout system for embeddable widget: Layout Algorithms: - Square: Equal-sized squares with row synchronization - Masonry: Pinterest-style shortest-column placement - Grid: Structured grid with aspect ratio preservation - Justified: Flickr-style rows using justified-layout v4.1.0 - Filmstrip: Large photo viewer with thumbnail strip Widget Integration: - Dynamic layout switching based on config - Responsive recalculation on window resize - Proper event listener cleanup (no memory leaks) - Best size variant selection for optimal image quality - Lazy loading support for performance Technical Details: - All algorithms follow Lychee's native layout specifications - Absolute positioning with JavaScript-calculated dimensions - Remainder space distribution for pixel-perfect layouts - Bundle size: 246KB (85KB gzipped) with sourcemaps Exports: - All layout functions exported for advanced users - Layout results include positioned photos and container height Next: Phase 3 - Lightbox viewer and filmstrip navigation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add guard against division by zero in calculateJustifiedRowHeight to handle edge case where all aspect ratios sum to zero. While this shouldn't occur with valid photos (getAspectRatio returns minimum of 1), defensive programming ensures robustness. Tests: All 4 tests pass (233 assertions) Build: Widget builds successfully (246KB / 85KB gzipped) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add comprehensive lightbox viewer with full feature set: Lightbox Features: - Full-screen photo display with optimal image quality - Prev/Next navigation (buttons + arrow keys) - Close functionality (button, overlay click, Escape key) - Photo metadata display (title, description) - Complete EXIF data (camera, lens, settings, date) - Photo counter (current / total) - Loading and error states - Responsive design with mobile support Technical Implementation: - Teleport to body for proper z-index management - Body scroll lock when open - Keyboard navigation (←/→ arrows, Esc) - Accessible (ARIA labels, keyboard support) - Smooth fade transitions - SVG icons (no external dependencies) - Scoped styles (no conflicts with host site) - Proper cleanup on unmount Integration: - Connected to EmbedWidget photo grid - Photo index tracking - Configurable caption/EXIF visibility Bundle Impact: - Previous: 246KB (85KB gzipped) - Current: 252KB (87KB gzipped) - Increase: +6KB raw / +2KB gzipped Next: Filmstrip navigation enhancement 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Code review fixes: - Replace querySelector with template ref in Lightbox.vue for better Vue patterns and to avoid potential null reference issues - Use overlayRef.value?.focus() instead of DOM query - Apply prettier formatting to all embed files Documentation: - Add comprehensive demo.html in .ai/embeds/ showing: - Interactive layout switching - Feature list - Quick start guide - Configuration options - Programmatic API examples Phase 3.1 (Lightbox) now complete and reviewed. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
UI Components: - Add EmbedCodeDialog.vue component with full embed configuration UI - Layout selection (Square, Masonry, Grid, Justified, Filmstrip) - Advanced options (spacing, row height, column width) - Display options toggles (title, description, captions, EXIF) - Theme selection (light/dark) - Generated code display with copy-to-clipboard - Collapsible advanced options section - Add embed button to AlbumHero.vue - Icon: pi-code - Only shown for publicly accessible albums (no password/link required) - Checks album.policy for embeddability Integration: - Wire up embed modal state in ModalsState store - Add toggleEmbedCode function to galleryModals composable - Connect dialog to AlbumPanel.vue - Auto-generate embed code with current album ID and API URL Translations: - Add dialogs.embed_code section with all UI strings - Add gallery.album.hero.embed tooltip text Build Configuration: - Fix .gitignore to properly ignore /public/embed/ build outputs - Separate from report_* pattern Phase 4 (UI Integration) complete. Embed button appears on public albums and generates ready-to-use HTML embed code. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Preview Implementation: - Dynamically load embed widget assets (CSS and JS) from /embed/ - Initialize real embed widget in preview container with current config - Update preview in real-time when configuration changes - Use actual album data from API for authentic preview - Fixed height (200px) for preview to fit in dialog - Error handling with fallback message if widget fails to load Technical Details: - Load lychee-embed.js and lychee-embed.css dynamically - Use window.LycheeEmbed global from UMD bundle - Create data-lychee-embed div with all config attributes - Watch config changes with deep watching - Clean up preview when dialog closes User Experience: - Preview shows exactly what the embedded widget will look like - Layout changes are immediately visible - Theme switching updates preview instantly - Advanced options (spacing, heights) reflect in preview 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…embedding Fixes multiple issues preventing the embed widget from working on external websites: - Update embed widget API client to use correct /api/v2/Embed/ endpoint - Configure embed route to bypass strict API middleware while maintaining CORS support - Add explicit OPTIONS method handling for CORS preflight requests - Return explicit JSON response from controller to ensure proper serialization - Define process.env.NODE_ENV in Vite config to enable production optimizations - Add cache-busting to embed preview for development The embed endpoint now works correctly from local HTML files (file:// protocol), external websites with proper CORS headers, and the Lychee preview modal. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Implements two new features for the embed widget: 1. Lightbox Info Cycling: - Click on photo in lightbox to cycle through: none → title → title+EXIF → none - Matches the behavior of the main Lychee UI - Adds pointer cursor to indicate clickability 2. Gallery Link in Header: - Adds a ↗ link in the album header - Opens the album in the Lychee gallery in a new tab - Uses Unicode arrow character (U+2197) - Styled with hover effects for both light and dark themes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Moves the ↗ gallery link from the far right of the header to be inline with the album title for better visual grouping. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Adds configurable photo limit and updates default settings: 1. Maximum Photos Option: - New maxPhotos configuration option (default: 15) - Limits number of photos displayed in embed - Range: 1-100 photos - Configurable in advanced options UI - Added to embed code data attributes 2. Default Row Height: - Changed default targetRowHeight from 320px to 200px - More compact default layout for justified and filmstrip layouts 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Removes show title, show description, show captions, and show EXIF checkboxes from the embed code dialog advanced options since users can now cycle through these display modes by clicking on photos in the lightbox. The embed code is simplified and the defaults (all enabled) are always used, giving users runtime control via the lightbox click functionality. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add tests for: - Password-protected albums (should return 403) - Link-required albums (should return 403) - Albums with no photos (should work normally) - Remove placeholder CORS test Tests validate all access control scenarios for embed functionality.
Use null-safe operator (?->) when accessing license->localization() to prevent errors when albums don't have a license set.
Include version number (v1.0.0) in generated embed code for cache busting and version tracking. This helps identify which widget version is deployed and makes future updates easier to manage.
Make photo thumbnails keyboard-accessible with: - role="button" for screen readers - tabindex="0" for keyboard navigation - aria-label with photo title - Enter and Space key handlers to open lightbox - Visible focus outline for keyboard users Improves WCAG compliance and usability for keyboard and screen reader users.
Allow users to press Space bar (in addition to clicking the image) to cycle through info display modes: none → title only → title + EXIF → none. Improves keyboard accessibility and provides consistent interaction patterns.
Replace static demo with interactive form that allows users to: - Input their own Lychee API URL and album ID - Configure layout, theme, spacing, and max photos - Dynamically load and reload the widget with custom settings - See real-time error messages Makes testing and demonstrating the embed widget much easier.
Add embed_code section to dialogs.php with translations for: - title, info, layout, spacing, row_height, column_width - max_photos, theme, preview, code - copy, copied, show_advanced, hide_advanced Add hero.embed translation to gallery.php for all locales. Uses English as placeholder text following Lychee convention for new features. Covers locales: ar, cz, de, el, es, fa, fr, hu, it, ja, nl, no, pl, pt, ru, sk, sv, vi, zh_CN, zh_TW
Chain embed widget build with main app build so Docker containers built by Lychee-Docker project include embed widget files. Fixes 404 errors for lychee-embed.js and lychee-embed.css in Docker deployments. The build command now runs: - vite build (main app) - vite build --config vite.embed.config.ts (embed widget) This ensures npm run build produces all necessary static assets.
Add new HeaderPlacement type ('top' | 'bottom' | 'none') to replace
the theme system. Update config validation and defaults:
- Remove theme validation
- Add headerPlacement validation with default 'top'
- Update parseDataAttributes to read data-header-placement
- Remove theme from DEFAULT_CONFIG
This is infrastructure for making the embed widget transparent
and allowing flexible header positioning.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
Widget styling changes: - Remove theme-based styling (light/dark forced backgrounds) - Use transparent background and inherited text colors - Widget now adapts naturally to host page design - Update error state to use semi-transparent backgrounds - Remove header bottom border - Simplify hover states with adaptive gray overlay Header placement implementation: - Add conditional rendering for 'top' header placement - Add new footer section for 'bottom' placement - Footer shows: "View '[Album]' in Lychee Gallery ↗" - Both options include gallery link, just different positions - 'none' placement shows no header/footer The widget now blends seamlessly with any host page while providing flexible attribution options. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replace theme selector with header placement options: - Remove themeOptions (Light/Dark) - Add headerPlacementOptions (Top/Bottom/None) - Update config to use headerPlacement instead of theme - Update generated embed code data attribute This completes the UI for the new transparent/adaptive embed system. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Update embed_code translation key in all 21 locale files: - Replace 'theme' => 'Theme' - With 'header_placement' => 'Header Placement' Files updated: ar, cz, de, el, en, es, fa, fr, hu, it, ja, nl, no, pl, pt, ru, sk, sv, vi, zh_CN, zh_TW 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replace instructional title "Click to cycle info display" with the actual photo title. This provides better accessibility and user experience while the click-to-cycle functionality remains discoverable through usage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add 'lychee-' prefix to all lightbox CSS classes to: - Prevent naming conflicts with host page styles - Make classes easier for users to target with custom CSS - Follow consistent naming convention with other embed classes Changed classes: - lightbox-overlay → lychee-lightbox-overlay - lightbox-container → lychee-lightbox-container - lightbox-close → lychee-lightbox-close - lightbox-nav → lychee-lightbox-nav - lightbox-content → lychee-lightbox-content - lightbox-image-container → lychee-lightbox-image-container - lightbox-image → lychee-lightbox-image - lightbox-loading → lychee-lightbox-loading - lightbox-error → lychee-lightbox-error - lightbox-info → lychee-lightbox-info - lightbox-info-content → lychee-lightbox-info-content - lightbox-title → lychee-lightbox-title - lightbox-description → lychee-lightbox-description - lightbox-exif → lychee-lightbox-exif - lightbox-exif-item → lychee-lightbox-exif-item - lightbox-exif-label → lychee-lightbox-exif-label - lightbox-counter → lychee-lightbox-counter - lightbox-fade → lychee-lightbox-fade Also updated JavaScript classList check for overlay click handling. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replace all rem units with em units throughout the embed widget:
- Widget now scales naturally with host page font size
- Users can resize entire widget by setting font-size on container
- Better accessibility - respects user font size preferences
- More adaptive to host page context
Benefits:
- Host integration: Scales with parent element font size
- User control: Simple CSS override to resize widget
- Accessibility: Honors host page's font size settings
- Responsive: Truly contextual sizing
Changed in:
- EmbedWidget.vue: 12 occurrences (padding, margins, font sizes)
- Lightbox.vue: 23 occurrences (buttons, spacing, text)
Example usage for hosts:
.lychee-embed { font-size: 14px; } /* Smaller widget */
.lychee-embed { font-size: 18px; } /* Larger widget */
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
UI changes: - Add sort order dropdown in embed code dialog (Newest/Oldest first) - Extend max photos options: add 120, 180, 300, and 500 photo limits - Update embed code generation to include data-sort-order attribute - Update preview widget initialization with sortOrder config Translation updates: - Add 'sort_order' key to English dialogs for embed code UI This completes the embed enhancements, allowing users to: 1. Control photo ordering in embedded galleries 2. Display up to 500 photos (previously limited to 100) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Change the photo focus outline color from #0066cc to #38bdf8 for consistency with the embed UI color scheme. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Convert string sort parameters to proper enum types: - Use ColumnSortingType::CREATED_AT instead of string 'created_at' - Convert 'asc'/'desc' strings to OrderSortingType enum values Fixes 500 error: "Argument #1 ($column) must be of type App\Enum\ColumnSortingType, string given" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replace unsafe type cast with explicit type checks for better type safety and to resolve TypeScript lint error. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Replace direct count() call on relation with getQuery()->count() to avoid calling private method on BelongsToMany relation. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add 'sort_order' => 'Sort Order' translation key to 20 language files to resolve localization validation errors. Languages updated: ar, cz, de, el, es, fa, fr, hu, it, ja, nl, no, pl, pt, ru, sk, sv, vi, zh_CN, zh_TW 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 2
♻️ Duplicate comments (1)
app/Http/Controllers/Gallery/EmbedController.php (1)
112-124: Align default stream limit with specThe SPECIFICATION.md (Line 421) still states
limitdefaults to 18, but the controller returns 100. Please pick one canonical default and make both the docs and code match—either drop the code to 18 or update every doc/test reference so integrators aren’t surprised.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (4)
.ai/embeds/IMPLEMENTATION.md(1 hunks).ai/embeds/SPECIFICATION.md(1 hunks)app/Http/Controllers/Gallery/EmbedController.php(1 hunks)lang/en/dialogs.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*.php
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.php: New PHP files must include the project license header and have a single blank line after the opening <?php tag
Use snake_case for PHP variable names
Always call in_array() with true as the third parameter for strict checking
Only booleans should be used as conditions in if statements (avoid integers/strings)
Use strict comparisons (===, !==) instead of loose comparisons (==, !=)
Avoid duplicating logic in both if and else branches; refactor common code
Do not use empty(); use explicit checks instead
Do not use float/double for money; store amounts as integers in the smallest currency unit (e.g., cents)
Files:
app/Http/Controllers/Gallery/EmbedController.phplang/en/dialogs.php
{composer.json,**/*.php}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Apply PSR-4: configure autoload in composer.json and ensure PHP namespaces align with directory structure
Files:
app/Http/Controllers/Gallery/EmbedController.phplang/en/dialogs.php
🧠 Learnings (1)
📓 Common learnings
Learnt from: ildyria
PR: LycheeOrg/Lychee#3641
File: lang/no/settings.php:9-9
Timestamp: 2025-08-22T06:11:18.329Z
Learning: For lang/* translation files in the Lychee project: only review PHP-related issues (syntax, structure, etc.), not translation content, grammar, or language-related nitpicks. The maintainer ildyria has explicitly requested this approach.
🧬 Code graph analysis (1)
app/Http/Controllers/Gallery/EmbedController.php (8)
app/Models/Configs.php (3)
Configs(65-362)getValue(220-238)getValueAsBool(277-280)app/Models/Extensions/BaseAlbum.php (2)
BaseAlbum(56-154)getEffectivePhotoSorting(150-153)app/Models/Extensions/SortingDecorator.php (2)
SortingDecorator(21-248)orderPhotosBy(124-136)app/Policies/PhotoQueryPolicy.php (2)
PhotoQueryPolicy(23-338)applySearchabilityFilter(105-133)app/Http/Resources/Embed/EmbedAlbumResource.php (2)
__construct(32-44)fromModel(53-56)app/Http/Resources/Embed/EmbedStreamResource.php (2)
__construct(35-39)fromPhotos(49-52)app/Http/Resources/Embed/EmbedPhotoResource.php (2)
__construct(32-53)fromModel(62-65)app/Http/Resources/Models/Utils/AlbumProtectionPolicy.php (1)
ofBaseAlbum(51-62)
🪛 GitHub Check: 2️⃣ PHP 8.3 - PHPStan
app/Http/Controllers/Gallery/EmbedController.php
[failure] 200-200:
Call to private method count() of parent class Illuminate\Database\Eloquent\Relations\BelongsToMany<App\Models\Photo,App\Models\Album,Illuminate\Database\Eloquent\Relations\Pivot,string>.
🪛 LanguageTool
.ai/embeds/SPECIFICATION.md
[style] ~31-~31: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ... leaving my site - As a photographer, I want to embed all my public photos as a dynamic...
(REP_WANT_TO_VB)
[style] ~32-~32: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...ortfolio website - As a photographer, I want to customize the layout and appearance of ...
(REP_WANT_TO_VB)
[style] ~34-~34: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...t technical knowledge - As a blogger, I want to embed my latest photos automatically wi...
(REP_WANT_TO_VB)
[style] ~37-~37: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...bsite Visitor - As a website visitor, I want to view photos in a beautiful gallery with...
(REP_WANT_TO_VB)
[style] ~39-~39: You have already used this phrasing in nearby sentences. Consider replacing it to add variety to your writing.
Context: ...ll on my device - As a keyboard user, I want to navigate the gallery using keyboard sho...
(REP_WANT_TO_VB)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: 3️⃣ PHP dist / 8.4 - sqlite
- GitHub Check: 3️⃣ PHP dist / 8.4 - mariadb
- GitHub Check: 3️⃣ PHP dist / 8.3 - postgresql
- GitHub Check: 3️⃣ PHP dist / 8.3 - mariadb
- GitHub Check: 3️⃣ PHP dist / 8.3 - sqlite
- GitHub Check: 2️⃣ PHP tests / 8.4 - postgresql -- Install
- GitHub Check: 2️⃣ PHP tests / 8.4 - sqlite -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Unit
- GitHub Check: 2️⃣ PHP tests / 8.4 - mariadb -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Feature_v2
- GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Webshop
- GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Feature_v2
- GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Unit
- GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Webshop
- GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Webshop
- GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Feature_v2
- GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Unit
🔇 Additional comments (1)
.ai/embeds/IMPLEMENTATION.md (1)
1-1827: Planning document appropriately placed in .ai directory; detailed implementation verification would require reviewing source files.This is a comprehensive implementation plan and architecture reference document. Per your PR guidance to lightly review .ai files, this planning document appears well-structured with clear phases, architecture diagrams, code examples, and success criteria.
Note: Actual implementation verification should focus on the concrete source files (PHP controllers/resources, TypeScript widget code, Vue components) rather than this reference document. The code examples here are illustrative templates.
Change $siteTitle to $site_title in EmbedStreamResource constructor, fromPhotos method, and all docblocks to match project convention. Update EmbedController to use $site_title variable name when calling EmbedStreamResource::fromPhotos(). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Fix EmbedStreamTest to use Configs::getValueAsBool() instead of getValueAsString() with extra argument, which was causing a runtime error. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
♻️ Duplicate comments (5)
lang/el/dialogs.php (1)
84-104: PHP structure is correct; translation content per project policy.The past review comment flagging English strings in the
embed_codeblock should not have been raised. Per project policy and maintainer guidance, translation files initially contain English placeholders, and actual translations are managed through Weblate. Only PHP syntax and structure issues should be reviewed in lang/* files.The PHP structure, array syntax, and formatting are all correct in this section and throughout the file.
Based on learnings
app/Http/Controllers/Gallery/EmbedController.php (3)
113-113: Align default stream limit with spec.The default
limitis set to 100, but the documented specification requires a default of 18. Update to match the spec while keeping the validation that caps at 500.Apply this diff:
- $limit = $request->query('limit', 100); + $limit = $request->query('limit', 18);
75-88: Check access before loading album payload.
findAlbum()hydrates the full photo set (count + sorted results) before confirming the album is embeddable. An unauthenticated request against a large private album forces all that work, creating a resource-exhaustion vector. Gate onisPubliclyAccessible()before running the expensive pagination/sorting—split into "find album metadata" + "load photos" helpers to resolve cleanly.
199-199: Fix total photo count query (PHPStan failure).PHPStan fails here ("Call to private method count() of parent class BelongsToMany"), blocking the pipeline. Use
loadCount()/withCount()to populatephotos_countand access it as an attribute instead of callingcount()on the relation.Apply this diff:
- $totalPhotos = $album->photos()->getQuery()->count(); + $album->loadCount('photos'); + $totalPhotos = $album->photos_count;lang/es/dialogs.php (1)
212-214: Fix malformed placeholder spacing.
"% s"introduces an unintended space when formatted (e.g., the photo name renders with a leading space). Please switch to"%s"like the other locales so the placeholder expands correctly.- 'confirm' => '¿Estás seguro de que quieres eliminar la foto "% s"?', + 'confirm' => '¿Estás seguro de que quieres eliminar la foto "%s"?',
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
app/Http/Controllers/Gallery/EmbedController.php(1 hunks)app/Http/Resources/Embed/EmbedStreamResource.php(1 hunks)lang/ar/dialogs.php(1 hunks)lang/cz/dialogs.php(1 hunks)lang/de/dialogs.php(1 hunks)lang/el/dialogs.php(1 hunks)lang/es/dialogs.php(1 hunks)lang/fa/dialogs.php(1 hunks)lang/fr/dialogs.php(1 hunks)lang/hu/dialogs.php(1 hunks)lang/it/dialogs.php(1 hunks)
🧰 Additional context used
📓 Path-based instructions (3)
**/*.php
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.php: New PHP files must include the project license header and have a single blank line after the opening <?php tag
Use snake_case for PHP variable names
Always call in_array() with true as the third parameter for strict checking
Only booleans should be used as conditions in if statements (avoid integers/strings)
Use strict comparisons (===, !==) instead of loose comparisons (==, !=)
Avoid duplicating logic in both if and else branches; refactor common code
Do not use empty(); use explicit checks instead
Do not use float/double for money; store amounts as integers in the smallest currency unit (e.g., cents)
Files:
app/Http/Resources/Embed/EmbedStreamResource.phplang/it/dialogs.phpapp/Http/Controllers/Gallery/EmbedController.phplang/cz/dialogs.phplang/fa/dialogs.phplang/fr/dialogs.phplang/ar/dialogs.phplang/hu/dialogs.phplang/el/dialogs.phplang/es/dialogs.phplang/de/dialogs.php
{composer.json,**/*.php}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Apply PSR-4: configure autoload in composer.json and ensure PHP namespaces align with directory structure
Files:
app/Http/Resources/Embed/EmbedStreamResource.phplang/it/dialogs.phpapp/Http/Controllers/Gallery/EmbedController.phplang/cz/dialogs.phplang/fa/dialogs.phplang/fr/dialogs.phplang/ar/dialogs.phplang/hu/dialogs.phplang/el/dialogs.phplang/es/dialogs.phplang/de/dialogs.php
app/Http/Resources/**/*.php
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Resource classes must extend Spatie Data instead of JsonResource
Files:
app/Http/Resources/Embed/EmbedStreamResource.php
🧠 Learnings (8)
📓 Common learnings
Learnt from: ildyria
PR: LycheeOrg/Lychee#3641
File: lang/no/settings.php:9-9
Timestamp: 2025-08-22T06:11:18.329Z
Learning: For lang/* translation files in the Lychee project: only review PHP-related issues (syntax, structure, etc.), not translation content, grammar, or language-related nitpicks. The maintainer ildyria has explicitly requested this approach.
📚 Learning: 2025-08-14T10:17:35.082Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3616
File: app/Actions/Album/PositionData.php:38-39
Timestamp: 2025-08-14T10:17:35.082Z
Learning: PositionDataResource uses toPhotoResources() method to convert photos to PhotoResource instances, and PhotoResource accesses the tags relationship ($photo->tags->pluck('name')->all()), making the 'tags' eager-loading in PositionData necessary to avoid N+1 queries.
Applied to files:
app/Http/Resources/Embed/EmbedStreamResource.php
📚 Learning: 2025-08-27T08:48:45.672Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3654
File: lang/es/gallery.php:210-210
Timestamp: 2025-08-27T08:48:45.672Z
Learning: The project maintainer ildyria has indicated that language localization consistency is not a priority ("Lang = don't care"), meaning English text in non-English language files is acceptable and should not be flagged as an issue.
Applied to files:
lang/it/dialogs.phplang/hu/dialogs.phplang/el/dialogs.php
📚 Learning: 2025-09-16T21:56:01.607Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3683
File: app/Actions/Shop/OrderService.php:65-73
Timestamp: 2025-09-16T21:56:01.607Z
Learning: The validation for album-photo membership in PurchasableService::getEffectivePurchasableForPhoto() uses a JOIN query with the photo_album pivot table to ensure that only purchasables for albums that actually contain the specified photo are returned, preventing price spoofing attacks where clients could use arbitrary album_ids.
Applied to files:
app/Http/Controllers/Gallery/EmbedController.php
📚 Learning: 2025-09-18T07:09:17.176Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3683
File: app/Actions/Shop/PurchasableService.php:150-168
Timestamp: 2025-09-18T07:09:17.176Z
Learning: In PurchasablePhotoRequest, the validation for photo-album membership is implemented at the request validation level rather than in the service layer, using a count comparison to ensure all photos belong to the target album before creating purchasables.
Applied to files:
app/Http/Controllers/Gallery/EmbedController.php
📚 Learning: 2025-08-27T08:48:32.956Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3654
File: lang/pl/gallery.php:210-210
Timestamp: 2025-08-27T08:48:32.956Z
Learning: The user ildyria does not prioritize strict localization consistency for new menu items in language files, as indicated by their "Lang = don't care" response when suggested to translate 'Import from Server' to Polish in lang/pl/gallery.php.
Applied to files:
lang/cz/dialogs.phplang/hu/dialogs.phplang/de/dialogs.php
📚 Learning: 2025-08-22T06:11:18.329Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3641
File: lang/no/settings.php:9-9
Timestamp: 2025-08-22T06:11:18.329Z
Learning: For lang/* translation files in the Lychee project: only review PHP-related issues (syntax, structure, etc.), not translation content, grammar, or language-related nitpicks. The maintainer ildyria has explicitly requested this approach.
Applied to files:
lang/hu/dialogs.phplang/el/dialogs.phplang/es/dialogs.php
📚 Learning: 2025-08-20T20:35:04.474Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3637
File: lang/nl/renamer.php:10-94
Timestamp: 2025-08-20T20:35:04.474Z
Learning: In Lychee, translation files are initially created with English strings as placeholders, and actual translations are handled through Weblate (a web-based translation management system). This means finding English text in non-English locale files (like lang/nl/, lang/de/, etc.) is expected and part of their translation workflow, not an issue to flag.
Applied to files:
lang/hu/dialogs.phplang/el/dialogs.phplang/de/dialogs.php
🧬 Code graph analysis (2)
app/Http/Resources/Embed/EmbedStreamResource.php (1)
resources/js/lychee.d.ts (1)
EmbedPhotoResource(269-275)
app/Http/Controllers/Gallery/EmbedController.php (8)
app/Models/Configs.php (3)
Configs(65-362)getValue(220-238)getValueAsBool(277-280)app/Models/Extensions/BaseAlbum.php (2)
BaseAlbum(56-154)getEffectivePhotoSorting(150-153)app/Models/Extensions/SortingDecorator.php (2)
SortingDecorator(21-248)orderPhotosBy(124-136)app/Policies/PhotoQueryPolicy.php (2)
PhotoQueryPolicy(23-338)applySearchabilityFilter(105-133)app/Http/Resources/Embed/EmbedStreamResource.php (2)
__construct(35-39)fromPhotos(49-52)app/Http/Resources/Embed/EmbedAlbumResource.php (2)
__construct(32-44)fromModel(53-56)app/Http/Resources/Embed/EmbedPhotoResource.php (2)
__construct(32-53)fromModel(62-65)app/Http/Resources/Models/Utils/AlbumProtectionPolicy.php (1)
ofBaseAlbum(51-62)
⏰ Context from checks skipped due to timeout of 180000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (20)
- GitHub Check: 3️⃣ PHP dist / 8.3 - mariadb
- GitHub Check: 3️⃣ PHP dist / 8.4 - postgresql
- GitHub Check: 3️⃣ PHP dist / 8.3 - postgresql
- GitHub Check: 3️⃣ PHP dist / 8.4 - sqlite
- GitHub Check: 3️⃣ PHP dist / 8.4 - mariadb
- GitHub Check: 3️⃣ PHP dist / 8.3 - sqlite
- GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Feature_v2
- GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Webshop
- GitHub Check: 2️⃣ PHP tests / 8.4 - sqlite -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Feature_v2
- GitHub Check: 2️⃣ PHP tests / 8.4 - mariadb -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Feature_v2
- GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Webshop
- GitHub Check: 2️⃣ PHP tests / 8.3 - sqlite -- Unit
- GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Webshop
- GitHub Check: 2️⃣ PHP tests / 8.3 - postgresql -- Unit
- GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Install
- GitHub Check: 2️⃣ PHP tests / 8.3 - mariadb -- Unit
🔇 Additional comments (5)
lang/fa/dialogs.php (1)
1-244: No PHP-related issues found.Per project policy, translation files are managed via Weblate and only PHP syntax/structure should be reviewed. The PHP syntax and array structure in this file are correct. The past review comment regarding English strings in the
embed_codearray relates to translation content, which should not be reviewed.Based on learnings.
lang/ar/dialogs.php (1)
1-248: PHP structure is correct.The file has valid PHP syntax and proper array structure. The blank line after the opening
<?phptag follows best practices.Per project policy and maintainer instructions, translation content (language strings) in
lang/*files is managed via Weblate and should not be reviewed for translation quality or completeness. Only PHP-related issues (syntax, structure) are in scope for code review.Based on learnings.
lang/cz/dialogs.php (1)
80-100: Translation content review skipped per project policy.Following the project's established policy for translation files: only PHP-related issues (syntax, structure) are reviewed, not translation content. The PHP structure here is correct.
Based on learnings
app/Http/Resources/Embed/EmbedStreamResource.php (1)
35-52: LGTM! Variable naming now follows conventions.The constructor and factory method correctly use
$site_title(snake_case) throughout, and the implementation properly transforms photos toEmbedPhotoResourceinstances.As per coding guidelines
lang/it/dialogs.php (1)
151-153: Remove misplaced embedding-related keys from new_album block.The keys
mode,mode_album_help, andmode_stream_helpare specific to the embed functionality and don't belong in thenew_albumdialog context. These appear to have been accidentally copied from theembed_codeblock above (lines 88-90).Apply this diff to remove the misplaced keys:
'new_album' => [ 'menu' => 'Create Album', 'info' => 'Enter a title for the new album:', - 'mode' => 'Modalità', - 'mode_album_help' => 'Incorpora foto da questo album specifico.', - 'mode_stream_help' => 'Incorpora tutte le tue foto pubbliche come flusso cronologico (più recenti per prime).', 'title' => 'title', 'create' => 'Create Album', ],⛔ Skipped due to learnings
Learnt from: ildyria PR: LycheeOrg/Lychee#3641 File: lang/no/settings.php:9-9 Timestamp: 2025-08-22T06:11:18.329Z Learning: For lang/* translation files in the Lychee project: only review PHP-related issues (syntax, structure, etc.), not translation content, grammar, or language-related nitpicks. The maintainer ildyria has explicitly requested this approach.Learnt from: ildyria PR: LycheeOrg/Lychee#3654 File: lang/pl/gallery.php:210-210 Timestamp: 2025-08-27T08:48:32.956Z Learning: The user ildyria does not prioritize strict localization consistency for new menu items in language files, as indicated by their "Lang = don't care" response when suggested to translate 'Import from Server' to Polish in lang/pl/gallery.php.
Update photo sorting in both stream and album embed endpoints to use EXIF taken_at timestamp when available, falling back to created_at when not available. This ensures photos are ordered by when they were actually taken rather than when they were uploaded. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…e exhaustion Split findAlbum() into two methods to prevent resource exhaustion on unauthenticated embed requests: - findAlbumMetadata(): Lightweight method that only loads album record - loadAlbumPhotos(): Handles expensive photo loading with pagination/sorting The controller now checks public accessibility before loading any photos, preventing malicious requests from wasting I/O resources on private albums. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
…tions Remove mode, mode_album_help, and mode_stream_help keys from new_album and new_tag_album sections in all locales. These keys were accidentally copied from the embed_code section and should only exist there. Affected locales: ar, cz, de, el, es, fa, fr, hu, it, ja, no, pl, pt, ru, sk, sv, vi, zh_CN, zh_TW. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
Add the two new embed routes to the cache configuration:
- api/v2/Embed/stream
- api/v2/Embed/{albumId}
Both are configured as cacheable with GALLERY tag and user_dependant=false
since they are public endpoints with 15-minute TTL.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
♻️ Duplicate comments (1)
app/Http/Controllers/Gallery/EmbedController.php (1)
117-117: Default limit should be 18 per specification.As noted in the previous review comment, the documented default is 18, but the code uses 100. Please align with the specification.
Apply this diff:
- $limit = $request->query('limit', 100); + $limit = $request->query('limit', 18);
🧹 Nitpick comments (1)
app/Http/Controllers/Gallery/EmbedController.php (1)
221-221: Consider usingloadCount()for efficiency.The current
getQuery()->count()works correctly, but Laravel'sloadCount('photos')pattern would be more efficient and idiomatic—it retrieves the count in a single query via aCOUNT(*)subselect rather than a separate query.Apply this diff:
- $totalPhotos = $album->photos()->getQuery()->count(); + $album->loadCount('photos'); + $totalPhotos = $album->photos_count;This populates the
photos_countattribute that's already being set at line 249, making the code more consistent.
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (11)
app/Http/Controllers/Gallery/EmbedController.php(1 hunks)app/Metadata/Cache/RouteCacheManager.php(1 hunks)lang/ar/dialogs.php(1 hunks)lang/cz/dialogs.php(1 hunks)lang/de/dialogs.php(1 hunks)lang/el/dialogs.php(1 hunks)lang/es/dialogs.php(1 hunks)lang/fa/dialogs.php(1 hunks)lang/fr/dialogs.php(1 hunks)lang/hu/dialogs.php(1 hunks)lang/it/dialogs.php(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
- lang/it/dialogs.php
- lang/fa/dialogs.php
🧰 Additional context used
📓 Path-based instructions (2)
**/*.php
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
**/*.php: New PHP files must include the project license header and have a single blank line after the opening <?php tag
Use snake_case for PHP variable names
Always call in_array() with true as the third parameter for strict checking
Only booleans should be used as conditions in if statements (avoid integers/strings)
Use strict comparisons (===, !==) instead of loose comparisons (==, !=)
Avoid duplicating logic in both if and else branches; refactor common code
Do not use empty(); use explicit checks instead
Do not use float/double for money; store amounts as integers in the smallest currency unit (e.g., cents)
Files:
app/Http/Controllers/Gallery/EmbedController.phpapp/Metadata/Cache/RouteCacheManager.phplang/el/dialogs.phplang/hu/dialogs.phplang/cz/dialogs.phplang/es/dialogs.phplang/ar/dialogs.phplang/fr/dialogs.phplang/de/dialogs.php
{composer.json,**/*.php}
📄 CodeRabbit inference engine (.github/copilot-instructions.md)
Apply PSR-4: configure autoload in composer.json and ensure PHP namespaces align with directory structure
Files:
app/Http/Controllers/Gallery/EmbedController.phpapp/Metadata/Cache/RouteCacheManager.phplang/el/dialogs.phplang/hu/dialogs.phplang/cz/dialogs.phplang/es/dialogs.phplang/ar/dialogs.phplang/fr/dialogs.phplang/de/dialogs.php
🧠 Learnings (9)
📓 Common learnings
Learnt from: ildyria
PR: LycheeOrg/Lychee#3683
File: database/factories/PurchasableFactory.php:103-109
Timestamp: 2025-09-13T14:47:30.798Z
Learning: Photos and albums have a Many-Many relationship in Lychee, meaning a photo can belong to multiple albums. There is no single photo->album_id property. When creating purchasables for photos, both photo_id and album_id must be specified to indicate which photo-album combination the purchasable applies to.
📚 Learning: 2025-09-16T21:56:01.607Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3683
File: app/Actions/Shop/OrderService.php:65-73
Timestamp: 2025-09-16T21:56:01.607Z
Learning: The validation for album-photo membership in PurchasableService::getEffectivePurchasableForPhoto() uses a JOIN query with the photo_album pivot table to ensure that only purchasables for albums that actually contain the specified photo are returned, preventing price spoofing attacks where clients could use arbitrary album_ids.
Applied to files:
app/Http/Controllers/Gallery/EmbedController.php
📚 Learning: 2025-09-16T21:56:01.607Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3683
File: app/Actions/Shop/OrderService.php:65-73
Timestamp: 2025-09-16T21:56:01.607Z
Learning: The validation for album-photo membership in OrderService::addPhotoToOrder() is implemented within PurchasableService::getEffectivePurchasableForPhoto() using a whereHas('albums') constraint that ensures the photo belongs to the specified album_id before applying pricing logic.
Applied to files:
app/Http/Controllers/Gallery/EmbedController.php
📚 Learning: 2025-09-18T07:09:17.176Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3683
File: app/Actions/Shop/PurchasableService.php:150-168
Timestamp: 2025-09-18T07:09:17.176Z
Learning: In PurchasablePhotoRequest, the validation for photo-album membership is implemented at the request validation level rather than in the service layer, using a count comparison to ensure all photos belong to the target album before creating purchasables.
Applied to files:
app/Http/Controllers/Gallery/EmbedController.php
📚 Learning: 2025-08-27T05:48:07.098Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3647
File: app/Metadata/Cache/RouteCacheManager.php:115-116
Timestamp: 2025-08-27T05:48:07.098Z
Learning: The ResponseCache middleware in Lychee only caches GET requests. It explicitly checks `$request->method() !== 'GET'` at line 41 and bypasses caching for all other HTTP methods, making it safe to have cache configurations for endpoints that handle multiple HTTP verbs.
Applied to files:
app/Metadata/Cache/RouteCacheManager.php
📚 Learning: 2025-08-27T08:48:45.672Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3654
File: lang/es/gallery.php:210-210
Timestamp: 2025-08-27T08:48:45.672Z
Learning: The project maintainer ildyria has indicated that language localization consistency is not a priority ("Lang = don't care"), meaning English text in non-English language files is acceptable and should not be flagged as an issue.
Applied to files:
lang/el/dialogs.phplang/hu/dialogs.php
📚 Learning: 2025-08-20T20:35:04.474Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3637
File: lang/nl/renamer.php:10-94
Timestamp: 2025-08-20T20:35:04.474Z
Learning: In Lychee, translation files are initially created with English strings as placeholders, and actual translations are handled through Weblate (a web-based translation management system). This means finding English text in non-English locale files (like lang/nl/, lang/de/, etc.) is expected and part of their translation workflow, not an issue to flag.
Applied to files:
lang/el/dialogs.phplang/hu/dialogs.phplang/de/dialogs.php
📚 Learning: 2025-08-22T06:11:18.329Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3641
File: lang/no/settings.php:9-9
Timestamp: 2025-08-22T06:11:18.329Z
Learning: For lang/* translation files in the Lychee project: only review PHP-related issues (syntax, structure, etc.), not translation content, grammar, or language-related nitpicks. The maintainer ildyria has explicitly requested this approach.
Applied to files:
lang/el/dialogs.phplang/hu/dialogs.phplang/cz/dialogs.phplang/es/dialogs.php
📚 Learning: 2025-08-27T08:48:32.956Z
Learnt from: ildyria
PR: LycheeOrg/Lychee#3654
File: lang/pl/gallery.php:210-210
Timestamp: 2025-08-27T08:48:32.956Z
Learning: The user ildyria does not prioritize strict localization consistency for new menu items in language files, as indicated by their "Lang = don't care" response when suggested to translate 'Import from Server' to Polish in lang/pl/gallery.php.
Applied to files:
lang/hu/dialogs.phplang/cz/dialogs.php
🧬 Code graph analysis (2)
app/Http/Controllers/Gallery/EmbedController.php (10)
resources/js/lychee.d.ts (4)
ColumnSortingType(23-32)OrderSortingType(107-107)EmbedAlbumResource(258-268)AlbumProtectionPolicy(728-736)resources/js/embed/types.ts (2)
Album(118-125)Photo(95-113)app/Models/Configs.php (3)
Configs(65-362)getValue(220-238)getValueAsBool(277-280)app/Models/Extensions/BaseAlbum.php (2)
BaseAlbum(56-154)getEffectivePhotoSorting(150-153)app/Models/Extensions/SortingDecorator.php (2)
SortingDecorator(21-248)orderPhotosBy(124-136)app/Policies/PhotoQueryPolicy.php (2)
PhotoQueryPolicy(23-338)applySearchabilityFilter(105-133)app/Http/Resources/Embed/EmbedStreamResource.php (2)
__construct(35-39)fromPhotos(49-52)app/Http/Resources/Embed/EmbedAlbumResource.php (2)
__construct(32-44)fromModel(53-56)app/Http/Resources/Embed/EmbedPhotoResource.php (2)
__construct(32-53)fromModel(62-65)app/Http/Resources/Models/Utils/AlbumProtectionPolicy.php (1)
ofBaseAlbum(51-62)
app/Metadata/Cache/RouteCacheManager.php (2)
app/Metadata/Cache/RouteCacheConfig.php (1)
RouteCacheConfig(13-30)resources/js/lychee.d.ts (1)
CacheTag(20-20)
🔇 Additional comments (6)
app/Http/Controllers/Gallery/EmbedController.php (6)
1-37: LGTM! Clean structure and proper DI pattern.The file follows project conventions with the correct license header, proper namespacing, and clean dependency injection of
PhotoQueryPolicy. The constructor pattern is appropriate for this controller.
57-92: Excellent access control pattern.The two-step approach (load metadata → verify access → load photos) properly addresses the previous review concern about resource exhaustion. Access is now gated before expensive photo loading, and the validation logic correctly handles all edge cases with strict comparisons.
150-176: Well-implemented public photo retrieval.The security filtering via
PhotoQueryPolicy, eager loading ofsize_variants, and theCOALESCE(taken_at, created_at)fallback for sorting demonstrate solid query design. This avoids N+1 queries and handles edge cases properly.
191-205: LGTM! Lightweight metadata loading.This helper correctly loads only album metadata without hydrating photos, enabling efficient access checks. The logging provides useful audit information.
232-250: Solid sorting implementation with proper fallback.The use of
SortingDecoratorcorrectly handles both custom sort overrides and album-default sorting. The logic properly prioritizes explicit sort parameters while respecting album configuration when no override is provided.
264-272: LGTM! Correct embed accessibility logic.The access check correctly enforces the three requirements for embeddability (public, no password, no link restriction) using the
AlbumProtectionPolicyabstraction. The boolean conditions are clear and follow the specification.
|
🎉 @ildyria thank you, as always. |

This PR adds support for embedding Lychee albums, or the full (public) Lychee photo stream, in an external website using JavaScript. The embedded album integrates neatly into existing site designs and is easy to customize with CSS.
Supported layouts are: Square, Masonry, Grid, Justified, Filmstrip.
You can see a live demo of this feature at https://www.dzombak.com/blog/2025/10/latergram-bird-photos-from-kensington-metropark-july-2025/ .
Suggested Review Strategy
I would personally:
.ai, or don't bother to read them in detail. (See note 3 below.)resources/js/embedto make sure you don't see any red flags, but not spend too much time on this in detail. Rather, verify the embed widget works with different options and layouts, using the.ai/embeds/demo.htmltool.Notes
.aifolder contains documentation about this new feature written by Claude Code and intended for reference by CC or other AI agents who work on this feature in the future. It's not intended to be human-facing documentation; it's so that Claude Code et al. can familiarize themselves with the feature without having to explore the codebase. We could opt to remove this folder entirely; I don't feel strongly about it, and it may be of limited use now that this feature is complete.Open Questions
.ai/embeds/demo.htmlmove elsewhere?.aidirectory?Summary by CodeRabbit