This document tracks modules, dependencies, and architectural relationships across the Lychee codebase. Update this when new modules, dependencies, or contracts appear.
- Controllers (
app/Http/Controllers/) - Handle HTTP requests and route to services - Requests (
app/Http/Requests/) - Validate and sanitize incoming requests - Resources (
app/Http/Resources/) - Transform models to API responses (use Spatie Data) - Middleware (
app/Http/Middleware/) - Request/response filtering and authentication
- Models (
app/Models/) - Eloquent ORM models for database entities- Album Model - Nested set tree structure with pre-computed statistical fields:
num_children- Count of direct child albumsnum_photos- Count of photos directly in this album (not descendants)min_taken_at,max_taken_at- Date range of photos in album + descendantsauto_cover_id_max_privilege- Cover photo for admin/owner view (ignores access control)auto_cover_id_least_privilege- Cover photo for public view (respects PhotoQueryPolicy + AlbumQueryPolicy)
- Album Model - Nested set tree structure with pre-computed statistical fields:
- Services (
app/Services/) - Business logic and orchestration- LDAP Service (
app/Services/Auth/LdapService.php) - Enterprise directory integration (wrapper over LdapRecord)- Search-first authentication pattern: searches for user by username → gets DN → binds with DN + password
- Attribute retrieval: syncs email and display_name from LDAP
- Group membership queries for role assignment
- TLS/SSL support with configurable certificate validation
- Graceful error handling with connection timeout (default: 5 seconds)
- Dependencies: LdapRecord Laravel package, php-ldap extension
- LDAP Service (
- Actions (
app/Actions/) - Single-responsibility command objects- ProvisionLdapUser (
app/Actions/User/ProvisionLdapUser.php) - Auto-provision users from LDAP- Creates or updates local user from LdapUser DTO
- Syncs attributes (email, display_name) on each login
- Queries LDAP groups to assign admin role (
may_administrate) - Sets random password for LDAP users (local password not used)
- Import Actions (
app/Actions/Import/) - CLI sync and import pipelineExec::do(string $path, ?Album $parent_album)— tree-based directory import (BuildTree pipe chain)Exec::doFiles(array $file_paths, ?Album $parent_album)— direct file import, bypasses tree-based album creation (Feature 024)
- ProvisionLdapUser (
- DTOs (
app/DTO/) - Data transfer objects (Spatie Data)- LdapConfiguration (
app/DTO/LdapConfiguration.php) - Validates LDAP environment variables - LdapUser (
app/DTO/LdapUser.php) - LDAP authentication result (username, userDn, email, display_name)
- LdapConfiguration (
- Enums (
app/Enum/) - Type-safe enumeration classes
- Repositories - Data access abstraction (if used)
- Events (
app/Events/) - Domain event definitionsPhotoSaved,PhotoDeleted- Trigger album stats recomputation when photos changeAlbumSaved,AlbumDeleted- Trigger parent album stats recomputation when album structure changes
- Listeners (
app/Listeners/) - Event handlersRecomputeAlbumStatsOnPhotoChange- Dispatches recomputation job for photo's albumRecomputeAlbumStatsOnAlbumChange- Dispatches recomputation job for parent album
- Jobs (
app/Jobs/) - Asynchronous task definitionsRecomputeAlbumStatsJob- Recomputes album statistics and propagates changes to ancestors- Uses
WithoutOverlappingmiddleware (keyed by album_id) to prevent concurrent updates - Atomic transaction with 3 retries + exponential backoff
- Propagates to parent album after successful update (cascades to root)
- Stops propagation on failure (logs error, does not dispatch parent job)
- Uses
- Notifications (
app/Notifications/) - User notification logic - Worker Mode (
docker/scripts/entrypoint.sh) - Container mode selection for horizontal scaling- Web Mode (default): Runs FrankenPHP/Octane web server for handling HTTP requests
- Worker Mode: Runs Laravel
queue:workfor background job processing - Mode Selection: Controlled by
LYCHEE_MODEenvironment variable (web|worker) - Auto-Restart: Worker mode includes automatic restart loop for memory leak mitigation
- Configuration:
QUEUE_NAMES(queue priority),WORKER_MAX_TIME(restart interval) - Deployment: See deploy-worker-mode.md for docker compose examples
- UI Components (
resources/js/components/) - PrimeVue-based interface elements- Gallery components (album, photo, flow, search modules)
- PhotoThumbPanelControl - Layout selector with star rating filter (Feature 006)
- 5 clickable stars for minimum rating threshold filter
- Conditional rendering (hidden when no rated photos)
- Toggle behavior (click same star to clear)
- Keyboard accessible (Arrow keys, Enter/Space)
- PhotoThumbPanelControl - Layout selector with star rating filter (Feature 006)
- Forms, modals, drawers, settings components
- Gallery components (album, photo, flow, search modules)
- Views (
resources/js/views/) - Page-level Vue components- Gallery views: Albums, Album, Favourites, Flow, Frame, Map, Search
- Admin views: Settings, Users, Permissions, Maintenance, Diagnostics
- Composables (
resources/js/composables/) - Reusable composition functions- Album, photo, search, selection, context menu composables
- Services (
resources/js/services/) - API communication layer using axios - Layouts (
resources/js/layouts/) - Photo layout algorithms (square, justified, masonry, grid)
- Pinia Stores (
resources/js/stores/) - Centralized state management- Auth, LycheeState, LeftMenuState, ModalsState, FlowState, FavouriteState
- PhotosState - Photos collection management with rating filter support:
photoRatingFilter- Current filter setting (null | 1-5)hasRatedPhotosgetter - Checks if any photo has user ratingfilteredPhotosgetter - Returns photos filtered by minimum rating thresholdfilteredPhotosTimelinegetter - Returns timeline-grouped photos filtered by rating
- Vue3 reactive state and composables
- Vue Router (
resources/js/router/) - Client-side routing configuration
- Laravel Framework - Web application framework
- Spatie Data - DTOs and data transformation
- moneyphp/money - Monetary value handling
- LdapRecord Laravel - LDAP/Active Directory integration (v3.4.2)
- Dependency: php-ldap PHP extension required
- Vue3 - Progressive JavaScript framework (Composition API)
- TypeScript - Type-safe JavaScript
- PrimeVue - UI component library
- Axios - HTTP client
- HTTP Request → Route → Middleware
- Controller → Request Validation
- Service/Action → Business Logic
- Model/Repository → Database
- Resource/DTO → Response Transform
- HTTP Response
LDAP-first authentication with automatic fallback to local credentials:
- Login Request →
AuthController::login()receives username/password - LDAP Attempt (if enabled):
LdapService::authenticate()uses search-first pattern:- Connect to LDAP server with service account
- Search for user by username (gets DN)
- Bind with user DN + password
- Retrieve attributes (email, display_name)
- Returns
LdapUserDTO or null
- User Provisioning (on LDAP success):
ProvisionLdapUseraction creates/updates local user- Queries LDAP groups via
LdapService::queryGroups() - Assigns admin role if user in
LDAP_ADMIN_GROUP_DN - Syncs attributes from LDAP
- Local Fallback (if LDAP fails or disabled):
- Standard Laravel
Auth::attempt()with local credentials
- Standard Laravel
- Graceful Degradation:
- LDAP connection errors trigger automatic fallback to local auth
- All errors logged with contextual data (username, host, error message)
- User-friendly error messages (no LDAP implementation details)
Implements offset-based pagination for albums and photos to efficiently handle large collections:
Backend Architecture:
- Separate Endpoints - Three new endpoints replace monolithic album loading:
GET /Album::head- Album metadata without children/photos (HeadAlbumResource)GET /Album::albums?page={n}- Paginated child albums (PaginatedAlbumsResource)GET /Album::photos?page={n}- Paginated photos (PaginatedPhotosResource)
- Repository Methods -
AlbumRepository::getChildrenPaginated()andPhotoRepository::getPhotosForAlbumPaginated()use SortingDecorator for efficient queries - Album Type Support - Works with regular albums, Smart albums (Recent, Highlighted), and Tag albums
- Backward Compatibility - Legacy
/Albumendpoint unchanged, returns full album data
Frontend Architecture:
- Service Layer -
album-service.tsprovidesgetHead(),getAlbums(),getPhotos()methods - State Management -
AlbumStatestore manages pagination state (current_page, last_page, per_page, total) for both photos and albums - UI Components:
PaginationLoadMore.vue- "Load More (N remaining)" buttonPaginationInfiniteScroll.vue- Intersection Observer auto-loadingusePagination.tscomposable for shared logic
Configuration (configs table):
albums_per_page(integer, default: 30) - Child albums per pagephotos_per_page(integer, default: 100) - Photos per pagealbums_pagination_ui_mode(enum: infinite_scroll, load_more_button, page_navigation)photos_pagination_ui_mode(enum: infinite_scroll, load_more_button, page_navigation)
Data Flow:
- Album open → Frontend calls
getHead(),getAlbums(page=1),getPhotos(page=1)in parallel - User interaction (scroll/button/page) → Frontend calls
getPhotos(page=N)orgetAlbums(page=N) - Response includes pagination metadata:
{data: [...], current_page, last_page, per_page, total}
Replaces on-the-fly virtual column computation with physical database fields updated asynchronously:
- Mutation Events - Photo/album changes trigger domain events
- Photo: created, deleted, updated (taken_at, is_highlighted, NSFW status changes)
- Album: created, deleted, moved, NSFW status changes
- Event Listeners - Dispatch
RecomputeAlbumStatsJobfor affected album - Job Execution - Recomputes 6 fields in database transaction:
- Count fields:
num_children,num_photos - Date range:
min_taken_at,max_taken_at(recursive descendants) - Dual covers:
auto_cover_id_max_privilege(admin view),auto_cover_id_least_privilege(public view)
- Count fields:
- Propagation - After successful update, job dispatches itself for parent album → cascades to root
- Failure Handling - On failure (after 3 retries), logs error and stops propagation
- CLI Commands:
lychee:recompute-album-stats- Unified command: with album_id for single-album recompute, without album_id for bulk backfill of all albumslychee:recompute-album-stats {album_id}- Manual recovery after propagation failureslychee:sync {paths*}- Sync files and directories to Lychee (Feature 024): accepts both directory paths (tree-based import) and individual file paths (direct import viaExec::doFiles()); mixed invocations supported;--album_idoptional
Benefits: 50%+ query time reduction for album listings, removes expensive nested set JOINs from read path
Preserves original camera RAW / HEIC / PSD files as a dedicated size variant while converting to a displayable JPEG for the gallery.
Size Variant numbering: RAW=0, ORIGINAL=1, MEDIUM2X=2, MEDIUM=3, SMALL2X=4, SMALL=5, THUMB2X=6, THUMB=7, PLACEHOLDER=8
Upload flow (Init pipes):
DetectAndStoreRaw— if extension is inCONVERTIBLE_RAW_EXTENSIONS(.nef,.cr2,.cr3,.arw,.dng,.orf,.rw2,.raf,.pef,.srw,.nrw,.psd,.heic,.heif): stores original inInitDTO::$raw_source_file, converts to JPEG viaRawToJpeg. On failure: graceful fallback (keeps original file as-is, no RAW variant). PDF files are explicitly excluded.AssertSupportedMedia→FetchLastModifiedTime→MayLoadFileMetadata→FindDuplicate(unchanged)
Upload flow (Standalone pipes, added after CreateOriginalSizeVariant):
CreateRawSizeVariant— if$raw_source_fileis set in DTO: copies raw file to storage, creates DB row withtype=RAW(0)and0×0dimensions.
Key classes:
app/Actions/Photo/Convert/RawToJpeg.php— Imagick converter, quality 92, does NOT delete sourceapp/Actions/Photo/Pipes/Init/DetectAndStoreRaw.php— replaces deletedConvertUnsupportedMediaapp/Actions/Photo/Pipes/Standalone/CreateRawSizeVariant.php
Removed classes: HeifToJpeg, ConvertUnsupportedMedia, PhotoConverterFactory, ConvertableImageType, PhotoConverter interface
API: DownloadVariantType::RAW maps to the RAW download endpoint gated by raw_download_enabled config. The frontend checks whether a RAW size variant exists in the size_variants response to conditionally show the download button.
Migrations: 2026_02_28_000001 (shift type values), 2026_02_28_000002 (add raw_download_enabled config), 2026_02_28_000003 (reclassify existing raw-format ORIGINAL rows), 2026_02_28_000004 (add size_raw to album_size_statistics)
- PHP: snake_case for variables, PSR-4 for classes
- Vue3: Composition API with TypeScript
- No async/await in Vue3, use
.then()instead - Function declarations:
function functionName() {}not arrow functions
- User making request:
$this->user - User from query:
$this->user2 - Resource classes extend Spatie Data (not JsonResource)
- No Blade views - Vue3 only
- Base URL:
${Constants.getApiUrl()} - Services in
services/directory - Axios for HTTP requests
- Use
moneyphp/moneylibrary - Store as integers (smallest currency unit)
- Example: $10.99 = 1099 cents
- Photos - Content model, size variants, EXIF, palettes
- Albums - Album architecture and types
- Permissions - Access control system
- Users - User accounts and authentication
- E-commerce - Webshop system
- System Features - Statistics, jobs, OAuth, config
- Add OAuth Provider - Step-by-step OAuth integration
- Configure Pagination - Album and photo pagination settings
- Translating Lychee - Translation guide for developers and translators
- Using Renamer - Filename transformation during import
- Frontend Architecture - Vue3, TypeScript, Pinia, composables
- Frontend Gallery Views - Gallery viewing modes and component architecture
- Frontend Layout System - Photo layout algorithms
- API Design - RESTful API patterns, authentication, and response structure
- Database Schema - Models, relationships, smart albums vs regular albums
- Image Processing - Size variant generation and processing pipeline
- Renamer System - Filename transformation system architecture
- Shop Implementation - E-commerce models, services, and API endpoints
- Timestamps Handling - Timestamp handling conventions and best practices
- Localization - Translation system and file structure
- Coding Conventions - PHP and Vue3 conventions
- Backend Architecture - Laravel structure, design patterns, and key components
- Album Tree Structure - Nested set model implementation
- Request Lifecycle: Album Creation - Complete album creation flow
- Request Lifecycle: Photo Upload - Photo upload and processing flow
- Shop Architecture - E-commerce architecture and integration
- Tag System - Tag architecture and operations
- Architecture Graph - Up-to-date module snapshot
- Verifying Releases - Code signing and release verification
- See feature specs in
features/for detailed component interactions
Last updated: January 14, 2026