## File: app/SmartAlbums/README.md

# Lychee Smart Albums Documentation

Smart Albums are virtual, dynamically-generated albums in Lychee that automatically contain photos based on specific criteria. Unlike regular albums where photos are explicitly assigned, Smart Albums collect photos that match certain conditions like being highlighted, recently uploaded, or unassigned to any album.

## Overview

Smart Albums implement the `AbstractAlbum` interface but extend `BaseSmartAlbum` instead of being traditional Eloquent models. They are singleton classes that cannot be created or deleted by users - they always exist when enabled in the configuration.

### Key Characteristics

- **Virtual Albums**: No database storage for the album itself, only for photos
- **Dynamic Content**: Photos appear/disappear based on their properties
- **Singleton Pattern**: One instance per smart album type
- **Read-Only**: Cannot be created, deleted, or have photos manually added/removed
- **Configurable**: Can be enabled/disabled via configuration settings

## Architecture

### BaseSmartAlbum Logic

The `BaseSmartAlbum` class provides the common functionality for all smart albums:

#### Core Components

1. **Smart Photo Condition**: A closure that defines the filtering criteria
2. **Photo Query Policy**: Applies security and visibility filters
3. **Caching**: Results are cached for performance
4. **Thumbnail Generation**: Automatic thumbnail selection based on configuration

#### Key Methods

```php
// Constructor accepts the smart album type and filtering condition
protected function __construct(SmartAlbumType $id, \Closure $smart_condition)

// Returns photos matching the smart condition with security filters applied
public function photos(): Builder

// Cached photo retrieval with default sorting
protected function getPhotosAttribute(): LengthAwarePaginator

// Thumbnail generation with random or sorted selection
protected function getThumbAttribute(): ?Thumb
```

#### Security and Visibility

Smart Albums respect Lychee's security model through:

- **PhotoQueryPolicy**: Filters photos based on user permissions
- **Searchability Filter**: Ensures users only see photos they have access to
- **Sensitivity Filtering**: Respects the `hide_nsfw_in_smart_albums` configuration
- **Override Setting**: `SA_override_visibility` can bypass some security filters

## Smart Album Types

### 1. Recent Album (`RecentAlbum`)

**Purpose**: Contains recently uploaded photos based on their creation date.

**Filtering Logic**:
```php
$query->where('photos.created_at', '>=', $recent_cutoff_date);
```

**Configuration**:
- `enable_recent`: Enable/disable the Recent album
- `recent_age`: Number of days to consider "recent" (default varies)

**Behavior**: Shows photos uploaded within the configured number of days from the current date.

### 2. Highlighted Album (`HighlightedAlbum`)

**Purpose**: Contains all photos marked as highlighted by users.

**Filtering Logic**:
```php
$query->where('photos.is_highlighted', '=', true);
```

**Configuration**:
- `enable_highlighted`: Enable/disable the Highlighted album

**Behavior**: Shows all photos with the `is_highlighted` flag set to true, regardless of their album assignment.

### 3. On This Day Album (`OnThisDayAlbum`)

**Purpose**: Contains photos taken on the same month and day in previous years.

**Filtering Logic**:
```php
$query->where(fn (Builder $q) => $q
    ->whereMonth('photos.taken_at', '=', $today->month)
    ->whereDay('photos.taken_at', '=', $today->day))
->orWhere(fn (Builder $q) => $q
    ->whereNull('photos.taken_at')
    ->whereYear('photos.created_at', '<', $today->year)
    ->whereMonth('photos.created_at', '=', $today->month)
    ->whereDay('photos.created_at', '=', $today->day));
```

**Configuration**:
- `enable_on_this_day`: Enable/disable the On This Day album

**Behavior**: 
- Primary: Shows photos taken on this day in previous years (uses `taken_at` EXIF data)
- Fallback: For photos without EXIF date, uses upload date (`created_at`) from previous years

### 4. Unsorted Album (`UnsortedAlbum`) - Edge Case

**Purpose**: Contains photos that are not assigned to any regular album.

**Filtering Logic**:
```php
$query->whereNull(PA::ALBUM_ID); // Where album_id is null in the photo_album relationship
```

**Configuration**:
- `enable_unsorted`: Enable/disable the Unsorted album

**Special Behavior - The Edge Case**:

The `UnsortedAlbum` has unique authorization logic that differs from other smart albums:

```php
public function photos(): Builder
{
    if ($this->public_permissions !== null) {
        // If Unsorted album is made public, ALL unsorted photos become visible
        // This bypasses normal user ownership checks
        return Photo::query()
            ->leftJoin(PA::PHOTO_ALBUM, 'photos.id', '=', PA::PHOTO_ID)
            ->with(['size_variants', 'statistics', 'palette'])
            ->where($this->smart_photo_condition);
    }

    // Normal security filtering applies
    return parent::photos();
}
```

**Why It's an Edge Case**:

1. **Ownership Bypass**: When the Unsorted album is made public, it shows ALL unsorted photos from ALL users, not just the current user's photos
2. **Security Implications**: This can expose photos that users might not intend to be public
3. **Different Authorization**: Unlike other smart albums that always respect photo ownership, Unsorted can override this when public
4. **Administrative Feature**: Primarily intended for admin users to see all unorganized content in the system

### 5. Untagged Album (`UntaggedAlbum`)

**Purpose**: Contains photos that are not assigned to any tag.

**Filtering Logic**:
```php
$query->whereDoesntHave('tags');
```

**Configuration**:
- `enable_untagged`: Enable/disable the Untagged album

**Behavior**: Shows all photos that do not have any tags associated with them.

## Configuration Settings

Smart Albums can be individually enabled/disabled:

```php
// In Configs table
'enable_unsorted'     => true/false
'enable_highlighted'  => true/false  
'enable_recent'       => true/false
'enable_on_this_day'  => true/false
'enable_untagged'     => true/false

// Additional settings
'recent_age'                    => 30 (days)
'SA_override_visibility'        => false
'hide_nsfw_in_smart_albums'     => true
'SA_random_thumbs'              => false
```

## Implementation Details

### Singleton Pattern

Each smart album uses the singleton pattern to ensure only one instance exists:

```php
private ?self $instance = null;

public function getInstance(): self
{
    return self::$instance ??= new self();
}
```

### MimicModel Trait

Smart Albums use the `MimicModel` trait to behave like Eloquent models while not being stored in the database:

- Provides `__get()` method for dynamic property access
- Enables consistent API with regular albums
- Allows smart albums to be treated uniformly in the frontend

### Performance Considerations

- **Query Caching**: Photo results are cached after first retrieval
- **Lazy Loading**: Photos are only queried when actually accessed
- **Optimized Queries**: Use joins and indexes for efficient filtering
- **Thumbnail Caching**: Thumbnails are cached and can be randomized

### Internationalization

Smart album titles are automatically translated using Laravel's localization system:

```php
$this->title = __('gallery.smart_album.' . strtolower($id->name)) ?? $id->name;
```

Translation keys in `lang/{locale}/gallery.php`:
- `gallery.smart_album.unsorted`
- `gallery.smart_album.highlighted`
- `gallery.smart_album.recent`
- `gallery.smart_album.on_this_day`
- `gallery.smart_album.untagged`

## Usage in Application

Smart Albums appear alongside regular albums in the main gallery view when enabled. They:

1. Are fetched by the `Albums/Top.php` Action
2. Appear in the frontend with special smart album styling
3. Support the same photo operations (mostly) as regular albums
4. Respect user permissions and privacy settings
5. Can have their own access permissions via the `AccessPermission` model

The smart album system provides users with convenient ways to access their photos based on common organizational patterns without manual curation.

---

*Last updated: August 14, 2025*

---

## File: app/Rules/README.md

# Lychee Validation Rules Documentation

This document explains the custom validation rules in Lychee, their purposes, and implementation patterns. Lychee uses Laravel's validation system with extensive custom rules to ensure data integrity and security.

## Overview

Lychee implements comprehensive validation through custom Laravel validation rules. These rules handle domain-specific validation requirements that go beyond Laravel's built-in validation rules, ensuring data consistency, security, and proper business logic enforcement.

## Rule Categories

### 1. ID Validation Rules

**Purpose**: Validate various identifier formats used throughout Lychee

- **`RandomIDRule.php`** - Validates Lychee's random ID format (base64-like strings)
- **`RandomIDListRule.php`** - Validates arrays of random IDs
- **`AlbumIDRule.php`** - Validates album IDs (random IDs + smart album types)
- **`AlbumIDListRule.php`** - Validates arrays of album IDs
- **`IntegerIDRule.php`** - Validates integer-based IDs
- **`OwnerIdRule.php`** - Validates user/owner ID references

### 2. Configuration Rules

**Purpose**: Validate configuration values and keys with type safety

- **`ConfigKeyRule.php`** - Validates configuration key names
- **`ConfigValueRule.php`** - Validates configuration values with type checking
- **`OwnerConfigRule.php`** - Validates owner-specific configuration settings

### 3. Content Validation Rules

**Purpose**: Validate user-provided content with security considerations

- **`TitleRule.php`** - Validates album/photo titles
- **`DescriptionRule.php`** - Validates descriptions with length and content rules
- **`CopyrightRule.php`** - Validates copyright information format
- **`UsernameRule.php`** - Validates username format and restrictions
- **`PasswordRule.php`** - Validates password strength and requirements
- **`CurrentPasswordRule.php`** - Validates current password for sensitive operations

### 4. File and Media Rules

**Purpose**: Validate file-related operations and formats

- **`ExtensionRule.php`** - Validates file extensions against allowed types
- **`FileUuidRule.php`** - Validates file UUID format for uploads
- **`PhotoUrlRule.php`** - Validates photo URL format and accessibility
- **`GpxFileRule.php`** - Validates uploaded tracks are genuine GPX files (delegates to `App\Services\GpxValidation`)

### 5. Type and Support Rules

**Purpose**: Handle conditional validation based on system support

- **`StringRule.php`** - Basic string validation with custom constraints
- **`StringRequireSupportRule.php`** - String validation requiring license verification
- **`BooleanRequireSupportRule.php`** - Boolean validation with support requirements
- **`IntegerRequireSupportRule.php`** - Integer validation with support requirements
- **`EnumRequireSupportRule.php`** - Enum validation with support requirements
- **`ConfigKeyRequireSupportRule.php`** - Configuration key validation with support requirements

## Implementation Patterns

### Legacy Pattern (ValidateTrait)

📝 **Note**: Some rules use `ValidateTrait.php` as a legacy mechanism for compatibility with Laravel's validation system evolution from version 9 to 10.

```php
// ValidateTrait.php - Legacy compatibility
trait ValidateTrait
{
    public function validate(string $attribute, mixed $value, \Closure $fail): void
    {
        if (!$this->passes($attribute, $value)) {
            $fail($this->message());
        }
    }
}
```

**Rules using ValidateTrait:**
- `AlbumIDRule`
- `RandomIDRule`
- `ConfigValueRule`
- Most ID and basic validation rules

**Pattern Usage:**
```php
final class RandomIDRule implements ValidationRule
{
    use ValidateTrait;  // Legacy compatibility
    
    public function passes(string $attribute, mixed $value): bool
    {
        // Validation logic
    }
    
    public function message(): string
    {
        // Error message
    }
}
```

### Modern Pattern (Direct Implementation)

Newer rules implement `ValidationRule` directly:

```php
final class StringRequireSupportRule implements ValidationRule
{
    public function validate(string $attribute, mixed $value, \Closure $fail): void
    {
        // Direct validation implementation
        if (!$this->isValid($value)) {
            $fail('Validation failed');
        }
    }
}
```

## Advanced Rule Example: ConfigValueRule

The `ConfigValueRule` demonstrates advanced validation patterns including data awareness and complex validation logic:

```php
final class ConfigValueRule implements DataAwareRule, ValidationRule
{
    use ValidateTrait;

    /** @var Collection<int,Configs> */
    private Collection $configs;

    /**
     * All of the data under validation.
     *
     * @var array<string,mixed>
     */
    protected $data = [];

    public function __construct()
    {
        $this->configs = Configs::all();
    }

    /**
     * Set the data under validation.
     *
     * @param array<string,mixed> $data
     */
    public function setData(array $data): static
    {
        $this->data = $data;
        return $this;
    }

    public function passes(string $attribute, mixed $value): bool
    {
        // Parse nested attribute path (e.g., "settings.0.value")
        $path = explode('.', $attribute);
        if (count($path) !== 3) {
            throw new LycheeLogicException('ConfigValueRule: attribute must be in the form of "xxx.*.value"');
        }

        // Extract configuration key from related data
        $array_key = $this->data[$path[0]][intval($path[1])]['key'];
        
        // Validate value against configuration schema
        $template = 'Error: Expected %s, got ' . ($value ?? 'NULL') . '.';
        return '' === $this->configs->first(fn (Configs $c) => $c->key === $array_key)->sanity($value, $template);
    }

    public function message(): string
    {
        return ':attribute is not a valid configuration value.';
    }
}
```

### Key Features of ConfigValueRule:

**1. Data Awareness (`DataAwareRule`)**
- Implements `setData()` to access all form data
- Enables cross-field validation logic
- Allows validation based on related field values

**2. Complex Attribute Parsing**
- Handles nested array attributes (`settings.0.value`)
- Extracts related configuration keys from sibling fields
- Provides meaningful error context

**3. Dynamic Validation Logic**
- Loads all configuration schemas at runtime
- Validates values against specific configuration type requirements
- Uses configuration's built-in `sanity()` method for type checking

**4. Usage in Nested Forms**
```php
// Example form data structure:
[
    'settings' => [
        0 => ['key' => 'gallery_title', 'value' => 'My Gallery'],
        1 => ['key' => 'upload_chunk_size', 'value' => '1024'],
    ]
]

// ConfigValueRule validates that 'value' matches the expected type for 'key'
```

## Usage in Request Classes

Validation rules are used in Request classes:

```php
class UpdateAlbumRequest extends BaseApiRequest
{
    public function rules(): array
    {
        return [
            'id' => ['required', new AlbumIDRule(false)],
            'title' => ['sometimes', new TitleRule()],
            'description' => ['sometimes', new DescriptionRule()],
            'parent_id' => ['sometimes', new AlbumIDRule(true)],
        ];
    }
}
```

This validation rule system ensures that Lychee maintains high data quality and security standards while providing clear, maintainable validation logic throughout the application.

---

*Last updated: August 14, 2025*

---

## File: app/Policies/README.md

# Lychee Policies Documentation

This document explains the authorization and access control system in Lychee, focusing on the distinction between regular Policies and Query Policies, and their roles in securing the application.

## Overview

Lychee implements a comprehensive authorization system using Laravel's Policy classes with custom extensions. The system is designed to handle both individual model authorization and complex query-level filtering for security and performance.

## Policy Types

### Regular Policies (Authorization Policies)

Regular policies handle **individual model authorization** and define what actions a user can perform on specific resources. These policies work at the **model instance level**.

**Files:**
- `AlbumPolicy.php` - Album-specific permissions
- `PhotoPolicy.php` - Photo-specific permissions  
- `UserPolicy.php` - User management permissions
- `UserGroupPolicy.php` - User group permissions
- `SettingsPolicy.php` - Application settings permissions
- `MetricsPolicy.php` - Metrics and analytics permissions
- `BasePolicy.php` - Base class with admin override logic

**Key Characteristics:**
- **Instance-based**: Work with specific model instances (e.g., "Can user X edit album Y?")
- **Action-oriented**: Define specific actions (view, edit, delete, upload, share)
- **Permission checking**: Used by controllers to verify if a user can perform an action
- **Gate integration**: Work with Laravel's Gate system for authorization

**Example Authorization Methods:**
```php
// AlbumPolicy.php
public function canEdit(User $user, AbstractAlbum|null $album): bool
{
    return $this->isOwner($user, $album) || 
           $this->hasEditPermission($user, $album);
}

public function canUpload(?User $user, AbstractAlbum|null $album): bool
{
    return $this->isOwner($user, $album) ||
        $album->current_user_permissions()?->grants_upload === true ||
        $album->public_permissions()?->grants_upload === true;
}
```

⚠️ **Important: User Type Implications**

Pay careful attention to the difference between `User?` (nullable) and `User` (non-nullable) in policy method signatures:

- **`?User $user`**: Allows both authenticated and guest users. The method must handle `null` values for unauthenticated requests.
- **`User $user`**: Requires an authenticated user. Laravel will automatically deny access if no user is authenticated.

```php
// Allows guests - must handle null user
public function canSee(?User $user, Album $album): bool
{
    if ($user === null) {
        // Handle guest user logic
        return $album->is_public && !$album->requires_link;
    }
    // Handle authenticated user logic
    return $this->isOwner($user, $album) || $album->is_public;
}

// Requires authentication - Laravel denies if user is null
public function canEdit(User $user, Album $album): bool
{
    // $user is guaranteed to be non-null here
    return $this->isOwner($user, $album);
}
```

**Admin Override via `before()` Method:**

All policies inherit from `BasePolicy` which implements a `before()` method that runs **before any specific policy check**:

```php
// BasePolicy.php
public function before(?User $user, $ability)
{
    if ($user?->may_administrate === true) {
        return true; // Admin bypass - skips all other checks
    }
    // Returns null/void - continues to specific policy method
}
```

This means admin users automatically pass all policy checks without executing the specific permission logic, providing a global override for administrative access.

**Admin-Only Actions:**

As a consequence of the admin override, a policy method can return `false` for all regular users while still being valid due to the admin bypass. This pattern effectively creates admin-only actions:

```php
// app/Policies/SettingsPolicy.php
public function canEdit(User $user): bool
{
    // Another admin-only action
    // Regular users: false, Admins: true (via before() override)
	return false;
}
```

This design pattern allows for clean separation between actions available to regular users and those restricted to administrators only.


### Query Policies (Query Filtering Policies)

Query policies handle **database query filtering** and define what data a user can see when querying collections. These policies work at the **query builder level**.

**Files:**
- `AlbumQueryPolicy.php` - Album query filtering and visibility
- `PhotoQueryPolicy.php` - Photo query filtering and visibility

**Key Characteristics:**
- **Query-based**: Work with query builders to filter results before execution
- **Collection filtering**: Define what records a user can see in listings
- **Performance-oriented**: Apply filters at database level for efficiency
- **Visibility control**: Implement complex visibility rules (public, shared, private)
- **Hierarchical awareness**: Handle nested album structures and inheritance

**Key Concepts:**
- **Visibility**: What albums/photos appear in listings
- **Accessibility**: What albums/photos a user can access directly
- **Reachability**: What albums can be reached through navigation
- **Browsability**: What albums can be found by "clicking around"
- **Searchability**: What photos appear in search results

## Regular Policies Deep Dive

### AlbumPolicy

The `AlbumPolicy` class manages permissions for album operations:

**Core Permission Methods:**
```php
const IS_OWNER = 'isOwner';
const CAN_SEE = 'canSee';
const CAN_ACCESS = 'canAccess';
const CAN_ACCESS_FULL_PHOTO = 'canAccessFullPhoto';
const CAN_DOWNLOAD = 'canDownload';
const CAN_DELETE = 'canDelete';
const CAN_UPLOAD = 'canUpload';
const CAN_EDIT = 'canEdit';
const CAN_SHARE = 'canShare';
```

**Permission Hierarchy:**
1. **Admin Override**: Admins have full access to everything
2. **Ownership**: Album owners have full control
3. **Shared Permissions**: Based on `AccessPermission` records
4. **Password Protection**: Albums can require passwords
5. **Public Access**: Based on `AccessPermission` records with a null `user_id`
   - **Direct Link Only**: When `is_link_required` is true, the album is public but not visible in listings - only accessible via direct link
   - **Full Public**: When `is_link_required` is false, the album is fully public, visible in listings, and browsable

**Session Management:**
- Unlocked albums are tracked in user sessions
- Password-protected albums remain unlocked during session
- Session key: `AlbumPolicy::UNLOCKED_ALBUMS_SESSION_KEY`

### PhotoPolicy

The `PhotoPolicy` class manages permissions for individual photos:

**Core Permission Methods:**
```php
const CAN_SEE = 'canSee';
const CAN_DOWNLOAD = 'canDownload';
const CAN_EDIT = 'canEdit';
const CAN_ACCESS_FULL_PHOTO = 'canAccessFullPhoto';
const CAN_DELETE_BY_ID = 'canDeleteById';
```

**Permission Logic:**
1. **Album-based Inheritance**: Photo permissions often inherit from parent album
2. **Direct Ownership**: Photo owners have control regardless of album
3. **Shared Album Access**: Photos in shared albums follow album permissions
4. **Public Photos**: Photos in public albums are publicly accessible

### BasePolicy

The `BasePolicy` provides common functionality:

**Admin Override Logic:**
```php
public function before(?User $user, $ability)
{
    if ($user?->may_administrate === true) {
        return true;
    }
}
```

All policies extend `BasePolicy` to inherit admin privilege override.

## Query Policies Deep Dive

### AlbumQueryPolicy

The `AlbumQueryPolicy` manages album query filtering with sophisticated visibility rules:

#### Core Filtering Methods

**1. Visibility Filtering (`applyVisibilityFilter`)**
- Determines which albums appear in listings
- Considers: ownership, sharing, public access, link requirements
- Used for: album trees, navigation menus

```php
public function applyVisibilityFilter(AlbumBuilder $query): AlbumBuilder
{
    // Admin users see everything
    if (Auth::user()?->may_administrate === true) {
        return $query;
    }
    
    // Apply visibility conditions based on:
    // - User ownership
    // - Shared permissions  
    // - Public access settings
    // - Link requirements
}
```

**2. Reachability Filtering (`applyReachabilityFilter`)**
- Determines which albums can be directly accessed
- Combines visibility and accessibility rules
- Used for: direct album access validation

**3. Browsability Filtering (`applyBrowsabilityFilter`)**
- Determines which albums can be reached through navigation
- Ensures entire path from root to album is accessible
- Used for: preventing access to orphaned albums

#### Key Concepts Explained

**The Three Levels of Album Access:**

1. **Visible**: Album appears in listings and trees (basic visibility), but might be protected by a password.
2. **Reachable**: Album can be accessed via direct link, but requires parent albums to be accessible (not private or blocked by is_link_required=true).
3. **Browsable**: Album can be navigated to through click-through from parent albums (requires entire path to be accessible and visible).

**Important Distinction:**
- **Reachability** depends on the album's own permissions AND its parent chain accessibility
- **Browsability** depends on the entire path from origin being both accessible AND visible (not blocked by private albums or is_link_required=true)

```
┌─ Album A (Private) ───────────────────────────────────────┐
│ ┌─ Album B (Public, is_link_required = false) ───────────┐│
│ │ ┌─ Album C (Public, is_link_required = false ) ───────┐││
│ │ │ ┌─ Album D (Public, is_link_required = false ) ────┐│││
│ │ │ │ ┌─ Album E (Public, is_link_required = true ) ──┐││││
│ │ │ │ └───────────────────────────────────────────────┘││││
│ │ │ └──────────────────────────────────────────────────┘│││
│ │ └─────────────────────────────────────────────────────┘││
│ └────────────────────────────────────────────────────────┘│
└───────────────────────────────────────────────────────────┘
```

**Access Analysis:**

**Via Direct Link (Anonymous User with specific URLs):**
- **Album A**: ❌ Not accessible (private album, user is not owner)
- **Album B**: ✅ Accessible (public album, direct link bypasses private parent A)
- **Album C**: ✅ Accessible (public album, direct link bypasses private parent A)
- **Album D**: ✅ Accessible (public album, direct link bypasses private parent A)
- **Album E**: ✅ Accessible (public album with is_link_required=true, accessible only via direct link)

**From Origin (Anonymous User Perspective):**
- **Album A**: ❌ Not visible, ❌ not reachable, ❌ not browsable (private)
- **Album B**: ❌ Not visible, ❌ not reachable, ❌ not browsable (parent A is private, blocks all access)
- **Album C**: ❌ Not visible, ❌ not reachable, ❌ not browsable (path blocked by private A)
- **Album D**: ❌ Not visible, ❌ not reachable, ❌ not browsable (path blocked by private A)
- **Album E**: ❌ Not visible, ❌ not reachable, ❌ not browsable (path blocked by private A)

**From Album B Perspective (if user has been given direct link to B):**
- **Album A**: ❌ Not visible, ❌ not reachable, ❌ not browsable (private, user is not owner)
- **Album B**: ✅ Visible (from A), ✅ reachable, ❌ not browsable (current location, accessible via direct link)
- **Album C**: ✅ Visible (from B), ✅ reachable (from B), ❌ not browsable (public child, parent B is accessible)
- **Album D**: ✅ Visible (from C), ✅ reachable (from B), ❌ not browsable (public child, parent B is accessible)
- **Album E**: ❌ Not visible (from D), ❌ not reachable, ❌ not browsable (is_link_required=true makes parent C not accessible, breaking the chain)

**Key Insight: Parent Chain Dependency**
Reachability requires the entire parent chain to be accessible. If any parent has `is_link_required=true` or is private (and user lacks access), it breaks reachability for all descendants, even if those descendants are public.

**Access Level Hierarchy:**
```
Browsable ⊆ Reachable ⊆ Visible
```

- If an album is **browsable**, it must also be **reachable** and **visible**
- If an album is **reachable**, it must also be **visible**
- An album can be **visible** but not **reachable** (e.g., link-required albums)
- An album can be **reachable** but not **browsable** (e.g., child of private parent)

**Computed Access Permissions:**
- Dynamic JOIN with `access_permissions` table
- Aggregates user, user group, and public permissions
- Optimizes permission checking at database level

```php
private function getComputedAccessPermissionSubQuery(): BaseBuilder
{
    // Creates subquery that computes effective permissions
    // by combining user permissions, group permissions, and public settings
}
```

### PhotoQueryPolicy

The `PhotoQueryPolicy` manages photo query filtering with album-aware logic:

#### Core Filtering Methods

**1. Visibility Filtering (`applyVisibilityFilter`)**
- Determines which photos appear in listings
- Considers: photo ownership, album accessibility, public access
- Used for: photo galleries, recent photos

```php
public function applyVisibilityFilter(FixedQueryBuilder $query): FixedQueryBuilder
{
    // Admin users see everything
    if (Auth::user()?->may_administrate === true) {
        return $query;
    }
    
    // Apply visibility based on:
    // - Direct photo ownership
    // - Album accessibility (via AlbumQueryPolicy)
    // - Public photo settings
}
```

**2. Searchability Filtering (`applySearchabilityFilter`)**
- Determines which photos appear in search results
- Restricts search scope to accessible albums
- Handles album hierarchy constraints

```php
public function applySearchabilityFilter(
    FixedQueryBuilder $query, 
    ?Album $origin = null, 
    bool $include_nsfw = true
): FixedQueryBuilder
```

**3. Sensitivity Filtering (`applySensitivityFilter`)**
- Filters out photos in sensitive albums
- Respects user preferences and album settings
- Handles recursive sensitivity (sensitive parent albums)

#### Advanced Features

**Album Hierarchy Integration:**
```php
// Photos must be in albums that form an unbroken accessible path
$query->whereNotExists(function (BaseBuilder $q) {
    $this->album_query_policy->appendUnreachableAlbumsCondition($q);
});
```

**Root Level Photos:**
- Special handling for photos not in albums (root level)
- Different permission rules apply
- Owner-only or public access based on configuration

**Performance Optimizations:**
```php
private function prepareModelQueryOrFail(
    FixedQueryBuilder $query, 
    bool $add_albums, 
    bool $add_base_albums
): void
{
    // Automatically joins necessary tables
    // - photo_albums (for album relationships)
    // - albums (for hierarchy data)
    // - base_albums (for album metadata)
    // - computed_access_permissions (for permission data)
}
```

## Usage Patterns

### In Controllers

**Regular Policies:**
```php
// Check if user can edit specific album
Gate::authorize(AlbumPolicy::CAN_EDIT, $album);

// Check if user can upload to album
if (Gate::denies(AlbumPolicy::CAN_UPLOAD, $album)) {
    throw new UnauthorizedException();
}
```

**Query Policies:**
```php
// Get albums visible to current user
$albums = Album::query()
    ->when(true, fn($q) => $album_query_policy->applyVisibilityFilter($q))
    ->get();

// Search photos with restrictions
$photos = Photo::query()
    ->when(true, fn($q) => $photo_query_policy->applySearchabilityFilter($q, $origin))
    ->where('title', 'like', "%{$search}%")
    ->get();
```

### In Relationships

Query policies are automatically applied in custom relationship classes:

```php
// HasManyChildAlbums.php
public function addConstraints()
{
    if (static::$constraints) {
        parent::addConstraints();
        $this->album_query_policy->applyVisibilityFilter($this->getRelationQuery());
    }
}
```

## Security Considerations

### Defense in Depth

1. **Controller Level**: Regular policies check specific actions
2. **Query Level**: Query policies filter data at source
3. **Relationship Level**: Automatic filtering in model relationships
4. **Frontend Level**: UI hides unauthorized options

### Performance Impact

**Query Policies Benefits:**
- Filter data at database level (efficient)
- Reduce memory usage by not loading unauthorized data
- Enable complex permission logic in single queries

**Potential Concerns:**
- Complex JOIN operations for permission checking
- Nested subqueries for hierarchy validation
- Can impact query performance with large datasets

---

*Last updated: August 14, 2025*

---

## File: app/Http/Controllers/Install/README.md

# Lychee Installation Controllers Documentation

This document explains the controllers responsible for Lychee's web-based installation process. These controllers guide users through a step-by-step installation workflow, ensuring all requirements are met and the system is properly configured.

> **Note**: It is very likely that you won't ever need to modify these controllers unless you want revamp the complete installation process. Lychee aims to be installed mostly via docker, or having the key generation and database migration done via CLI. The screen that will be visited by the user is only for the initial setup of the admin user, which is required to access the application.

## Overview

The installation controllers implement a multi-step installation wizard that validates system requirements, configures the environment, sets up the database, and creates the initial admin user. Each controller represents a specific step in the installation process.

### Installation Flow

The installation process follows this sequence:
1. **Welcome** - Introduction and start
2. **Requirements** - System requirements validation  
3. **Permissions** - File/directory permissions check
4. **Environment** - `.env` file configuration
5. **Migration** - Database setup and key generation
6. **Admin Setup** - Create initial admin user

## Controllers Overview

### 1. WelcomeController

**Purpose**: Displays the welcome screen and installation introduction.

**Route**: `GET /install/` (named route: `install-welcome`)

**Key Features**:
- Simple welcome view with no validation
- Sets installation step to 0
- Entry point for the installation process

**Usage**:
```php
public function view(): View
{
    return view('install.welcome', [
        'title' => 'Lychee-installer',
        'step' => 0,
    ]);
}
```

### 2. RequirementsController

**Purpose**: Validates system requirements for running Lychee.

**Route**: `GET /install/req` (named route: `install-req`)

**Dependencies**:
- `RequirementsChecker` - Validates PHP version and extensions
- `DefaultConfig` - Provides minimum requirements configuration

**Key Features**:
- PHP version validation against minimum requirements
- Extension and module availability checks
- Displays detailed error information for failed requirements

**Validation Process**:
1. Check PHP version compatibility
2. Validate required PHP extensions
3. Check optional but recommended extensions
4. Return detailed status for each requirement

**Usage**:
```php
public function view(): View
{
    $php_support_info = $this->requirements->checkPHPVersion(
        $this->config->get_core()['minPhpVersion']
    );
    $reqs = $this->requirements->check(
        $this->config->get_requirements()
    );

    return view('install.requirements', [
        'phpSupportInfo' => $php_support_info,
        'requirements' => $reqs['requirements'],
        'errors' => $reqs['errors'] ?? null,
    ]);
}
```

### 3. PermissionsController

**Purpose**: Validates file and directory permissions required by Lychee.

**Route**: `GET /install/perm` (named route: `install-perm`)

**Dependencies**:
- `PermissionsChecker` - Validates directory write permissions
- `DefaultConfig` - Provides required permissions configuration

**Key Features**:
- Directory write permission validation
- Windows compatibility detection
- Detailed permission status reporting
- Error highlighting for insufficient permissions

**Checked Permissions**:
- Storage directories (photos, cache, logs)
- Configuration file access
- Public directory permissions
- Upload directory permissions

**Usage**:
```php
public function view(): View
{
    $perms = $this->permissions->check(
        $this->config->get_permissions()
    );

    return view('install.permissions', [
        'permissions' => $perms['permissions'],
        'errors' => $perms['errors'],
        'windows' => $this->permissions->is_win(),
    ]);
}
```

### 4. EnvController

**Purpose**: Manages `.env` file configuration for database and application settings.

**Route**: `GET|POST /install/env` (named route: `install-env`)

**Key Features**:
- Displays `.env` configuration editor
- Handles both viewing and saving of environment configuration
- Automatically loads `.env.example` as template if `.env` doesn't exist
- Validates and saves environment configuration

**Configuration Process**:
1. **GET Request**: Display current `.env` or `.env.example` content
2. **POST Request**: Save provided environment configuration
3. File locking for safe writes
4. Error handling for I/O operations

**Usage**:
```php
public function view(Request $request): View
{
    if ($request->has('envConfig')) {
        // Save new configuration
        $env = str_replace("\r", '', $request->get('envConfig'));
        file_put_contents(base_path('.env'), $env, LOCK_EX);
        $exists = true;
    } elseif (file_exists(base_path('.env'))) {
        // Load existing configuration
        $env = file_get_contents(base_path('.env'));
        $exists = true;
    } else {
        // Load template configuration
        $env = file_get_contents(base_path('.env.example'));
        $exists = false;
    }

    return view('install.env', [
        'env' => $env,
        'exists' => $exists,
    ]);
}
```


### 5. MigrationController

**Purpose**: Executes database migrations and generates application key.

**Route**: `GET /install/migrate` (named route: `install-migrate`)

**Key Features**:
- Uses Laravel Pipeline pattern for sequential operations
- Database migration execution
- Application key generation
- View cache clearing
- Comprehensive error reporting

**Pipeline Operations**:
1. **ArtisanViewClear** - Clear compiled views
2. **ArtisanMigrate** - Run database migrations  
3. **QueryExceptionChecker** - Validate database operations
4. **Spacer** - Add output formatting
5. **ArtisanKeyGenerate** - Generate application key

**Usage**:
```php
public function view(): View
{
    $output = [];
    $has_errors = false;
    
    try {
        $output = app(Pipeline::class)
            ->send($output)
            ->through([
                ArtisanViewClear::class,
                ArtisanMigrate::class,
                QueryExceptionChecker::class,
                Spacer::class,
                ArtisanKeyGenerate::class,
                Spacer::class,
            ])
            ->thenReturn();
    } catch (InstallationFailedException) {
        $has_errors = true;
    }

    return view('install.migrate', [
        'lines' => $output,
        'errors' => $has_errors,
    ]);
}
```

**Error Handling**:
- Catches and reports installation failures
- Provides detailed output from each pipeline step
- Displays user-friendly error messages

### 6. SetUpAdminController

**Purpose**: Creates the initial administrator user account.

**Routes**: 
- `GET /install/admin` - Display admin creation form
- `POST /install/admin` - Process admin creation

**Dependencies**:
- `SetUpAdminRequest` - Validates admin user input
- `User` model - Creates admin user record
- `Configs` model - Sets owner configuration

**Key Features**:
- Admin user creation with full privileges
- Password hashing and security
- Owner configuration setup
- Form validation and error handling

**Admin User Privileges**:
- `may_upload` - Can upload photos
- `may_edit_own_settings` - Can modify personal settings
- `may_administrate` - Has administrative access

**Usage**:
```php
// GET - Display form
public function init(): View
{
    return view('install.setup-admin', ['step' => 5]);
}

// POST - Create admin user
public function create(SetUpAdminRequest $request): View
{
    try {
        $user = new User();
        $user->may_upload = true;
        $user->may_edit_own_settings = true;
        $user->may_administrate = true;
        $user->username = $request->username();
        $user->password = Hash::make($request->password());
        $user->save();

        Configs::set('owner_id', $user->id);
        
        return view('install.setup-success');
    } catch (\Throwable $e) {
        return view('install.setup-admin', [
            'error' => $e->getMessage(),
        ]);
    }
}
```

## Middleware and Security

### Installation Middleware

The installation controllers use specific middleware to control access:

- **`installation:incomplete`** - Prevents access to main application during installation
- **`installation:complete`** - Only allows admin setup after successful installation
- **`admin_user:unset`** - Ensures admin setup only runs when no admin exists

### Request Validation

**SetUpAdminRequest** validates admin creation:
- `username` - Must pass `UsernameRule` validation
- `password` - Must pass `PasswordRule` validation and be confirmed
- Custom error bag for installation-specific errors

## Installation State Management

The installation process maintains state through:

1. **File Existence Checks** - Validates `.env` and other critical files
2. **Database State** - Checks for completed migrations
3. **Admin User Existence** - Determines if initial setup is complete
4. **Middleware Guards** - Prevents access to inappropriate installation steps

## Error Handling Patterns

### Common Error Types

1. **File Permission Errors** - Insufficient write access to directories
2. **Database Connection Errors** - Invalid database configuration
3. **PHP Extension Errors** - Missing required PHP modules
4. **Environment Errors** - Invalid `.env` configuration

### Error Display Strategy

- **Requirements/Permissions** - Detailed technical information for system administrators
- **Environment** - Configuration-specific guidance
- **Migration** - Step-by-step operation feedback
- **Admin Setup** - User-friendly validation messages

## Best Practices

### For Developers

1. **Error Handling** - Always provide meaningful error messages
2. **State Validation** - Check installation state before proceeding
3. **Security** - Validate all user input and sanitize outputs
4. **Logging** - Use pipeline pattern for trackable operations

### For System Administrators

1. **Prerequisites** - Ensure all requirements are met before starting
2. **Backup** - Have database backups before running migrations
3. **Permissions** - Set appropriate file/directory permissions
4. **Configuration** - Verify database credentials before proceeding

This installation system provides a robust, user-friendly way to deploy Lychee while ensuring all requirements are met and the system is properly configured for production use.

---

*Last updated: August 14, 2025*

---

## File: app/Actions/Shop/Gateway/README.md

# Payment Gateway Integration

This directory contains custom payment gateway implementations and response objects that extend the Omnipay payment processing library for the Lychee webshop.

## Purpose

The Gateway pattern provides a unified interface for integrating with different payment providers (PayPal, Stripe, Mollie, etc.). While Lychee primarily uses the Omnipay library for payment processing, some payment providers require custom implementations that don't fit perfectly into Omnipay's standard interface.

## Why Custom Gateways?

**Standard Omnipay Integration**: Most payment providers (Stripe, Mollie) work perfectly with Omnipay's built-in drivers and require no custom code.

**Custom Gateway Requirements**: Some payment providers (like PayPal) have specific SDK requirements or API patterns that necessitate:
- Direct SDK integration (PayPal Server SDK)
- Custom request/response handling
- Provider-specific error processing
- Non-standard authentication flows

## Components

### PaypalGateway.php

Custom implementation of Omnipay's `AbstractGateway` for PayPal integration using the official PayPal Server SDK.

**Key Features**:
- **Direct SDK Integration**: Uses `PaypalServerSdkClient` instead of generic HTTP requests
- **Order Creation**: Converts Lychee orders into PayPal order format with line items
- **Payment Capture**: Handles the two-step PayPal flow (create order → capture payment)
- **Error Handling**: Translates PayPal-specific errors into consistent response objects

**Methods**:
- `getOrderDetails(Order $order)`: Transforms Lychee order data into PayPal order structure
- `purchase(array $data)`: Creates a PayPal order and returns transaction reference
- `completePurchase($options)`: Captures the payment after customer approval

### Response Objects

These classes implement Omnipay's `ResponseInterface` to provide consistent response handling across different payment providers:

#### OrderCreatedResponse.php

Represents a successful order creation with the payment provider.

**Properties**:
- `transaction_reference`: Unique identifier from payment provider for tracking

**Usage**: Returned when an order is successfully created but payment is not yet captured (two-step payment flow).

#### CapturedResponse.php

Extends `OrderCreatedResponse` to represent a successful payment capture.

**Usage**: Returned when payment has been successfully captured and funds are secured.

#### OrderFailedResponse.php

Represents a failed order creation or processing error.

**Features**:
- Extracts error details from provider-specific error formats
- Provides user-friendly error messages
- Includes debug information for troubleshooting

**Usage**: Returned when order creation fails due to validation errors, API issues, or provider rejections.

#### CaptureFailedResponse.php

Extends `OrderFailedResponse` to represent a failed payment capture.

**Usage**: Returned when payment capture fails (insufficient funds, card declined, etc.).

## Integration with Omnipay

The custom gateway extends Omnipay's `AbstractGateway` to maintain compatibility with the existing payment infrastructure:

```php
class PaypalGateway extends AbstractGateway implements GatewayInterface
{
    // Custom implementation using PayPal SDK
}
```

This allows the CheckoutService to use PayPal the same way it uses other providers:

```php
$gateway = Omnipay::create('PayPal');
$gateway->initialize($config);
$response = $gateway->purchase($orderDetails);
```

## Response Flow

### Successful Payment Flow

1. **Order Creation**: `PaypalGateway::purchase()` → `OrderCreatedResponse`
   - Transaction reference stored in database
   - Customer redirected to PayPal for approval

2. **Payment Capture**: `PaypalGateway::completePurchase()` → `CapturedResponse`
   - Payment captured after customer approval
   - Order marked as completed

### Failed Payment Flow

1. **Order Creation Failure**: `PaypalGateway::purchase()` → `OrderFailedResponse`
   - API errors, validation failures, configuration issues

2. **Capture Failure**: `PaypalGateway::completePurchase()` → `CaptureFailedResponse`
   - Payment declined, insufficient funds, cancelled by user

## Adding New Payment Providers

To add a new payment provider that requires custom integration:

1. **Evaluate Omnipay Support**: Check if an Omnipay driver exists first
2. **Create Custom Gateway** (if needed): Extend `AbstractGateway`
3. **Implement Required Methods**:
   - `initialize()`: Setup with provider credentials
   - `purchase()`: Create order/payment intent
   - `completePurchase()`: Capture/finalize payment
4. **Create Response Objects**: Implement `ResponseInterface` for provider-specific responses
5. **Register in CheckoutService**: Add provider configuration and initialization logic

## Error Handling Best Practices

Response objects should:
- Extract meaningful error messages from provider responses
- Include debug information (transaction IDs, error codes) for troubleshooting
- Return consistent boolean values for `isSuccessful()`, `isCancelled()`, etc.
- Handle edge cases gracefully (missing fields, unexpected formats)

## Testing Considerations

- **Sandbox Mode**: Use provider sandbox/test environments for development
- **Mock Responses**: Create mock response objects for unit testing
- **Error Scenarios**: Test all error response types (declined, timeout, invalid data)
- **Transaction IDs**: Verify transaction references are properly stored and retrieved

## See Also

- [CheckoutService](../CheckoutService.php) - Orchestrates payment processing
- [OrderService](../OrderService.php) - Manages order lifecycle
- [Omnipay Documentation](https://omnipay.thephpleague.com/) - Payment processing library

---

*Last updated: December 19, 2024*

---

## File: app/Actions/Photo/README.md

# Photo Actions Documentation

This document provides an overview of the Photo Actions in Lychee, with special focus on the `Create.php` action and its pipeline architecture. This will help developers understand how photo processing works and how to extend or modify the system.

## Overview

Photo Actions handle all operations related to photo management in Lychee. They follow the **Action Pattern** combined with a **Pipeline Pattern** for complex operations like photo creation, which involves multiple processing steps.

## Design Pattern: Action + Pipeline

### Action Pattern
The Action pattern encapsulates business logic operations in dedicated classes. Each action:
- Has a single responsibility
- Can be easily tested in isolation
- Provides a clean interface between controllers and domain logic
- Returns consistent response formats

### Pipeline Pattern
For complex operations like photo creation, we use the Pipeline pattern where:
- Data flows through a series of processing steps (pipes)
- Each pipe performs a specific transformation or validation
- Pipes can be easily added, removed, or reordered
- Each pipe receives the data, processes it, and passes it to the next pipe

## Photo Creation Pipeline (Create.php)

The `Create.php` action is the most complex photo action, implementing a multi-stage pipeline for processing uploaded photos. Here's how it works:

### Entry Point
```php
public function do(UploadedFile $uploaded_file, ?Album $album = null): Photo
{
    $photoFile = new PhotoFile($uploaded_file);
    
    return $this->pipe($photoFile, [
        Init::class,
        ExtractMetadata::class,
        // ... other pipes
    ]);
}
```

## Key Classes and Their Roles

In this section we will detail the key pipes and DTO used in the photo creation pipeline.

### DTOs (Data Transfer Objects)

The photo creation pipeline uses several specialized DTOs to carry state through different stages:

#### PhotoDTO Interface
```php
interface PhotoDTO
{
    public function getPhoto(): Photo;
}
```
Base interface for all photo-related DTOs that eventually produce a Photo model.

#### InitDTO
Used in the initial preprocessing phase:
- Contains import mode and parameters
- Holds intended owner information
- Stores extracted EXIF metadata (Extractor object)
- Manages file paths and basic photo information
- Used by pipes implementing `InitPipe` interface

#### StandaloneDTO (implements PhotoDTO)
For processing individual photo files:
- Contains image handler for processing
- Manages size variant naming strategy
- Handles temporary video files for processing
- Stores target and backup file references
- Includes stream statistics for performance monitoring

#### DuplicateDTO (implements PhotoDTO) 
For handling duplicate photo detection and processing:
- Manages duplicate detection logic
- Contains reference to original photo if duplicate found
- Handles duplicate resolution strategies

#### PhotoPartnerDTO & VideoPartnerDTO
For processing paired files (e.g., Live Photos):
- PhotoPartnerDTO: Handles photo file partnerships
- VideoPartnerDTO: Manages video components of Live Photos
- Both coordinate processing of related file pairs

### Pipe Interfaces

The pipeline uses different pipe interfaces for different processing stages:

#### InitPipe
```php
interface InitPipe
{
    public function handle(InitDTO $state, \Closure $next): InitDTO;
}
```
- **Purpose**: Initial preprocessing steps before main photo processing
- **Input/Output**: InitDTO containing basic file and import information
- **Use Cases**: File validation, metadata extraction, initial setup

#### PhotoPipe
```php
interface PhotoPipe
{
    public function handle(PhotoDTO $state, \Closure $next): PhotoDTO;
}
```
- **Purpose**: General photo processing operations
- **Input/Output**: Any PhotoDTO implementation
- **Use Cases**: Image manipulation, size variant generation, final processing

#### StandalonePipe
```php
interface StandalonePipe
{
    public function handle(StandaloneDTO $state, \Closure $next): StandaloneDTO;
}
```
- **Purpose**: Processing individual standalone photos
- **Input/Output**: StandaloneDTO with image processing context
- **Use Cases**: Individual photo operations, standard image processing

#### DuplicatePipe
```php
interface DuplicatePipe
{
    public function handle(DuplicateDTO $state, \Closure $next): DuplicateDTO;
}
```
- **Purpose**: Handle duplicate photo detection and resolution
- **Input/Output**: DuplicateDTO with duplicate detection context
- **Use Cases**: Duplicate checking, merge operations, conflict resolution

#### SharedPipe
```php
interface SharedPipe
{
    public function handle(StandaloneDTO|DuplicateDTO $state, \Closure $next): StandaloneDTO|DuplicateDTO;
}
```
- **Purpose**: Operations that work on both standalone and duplicate photos
- **Input/Output**: Union type supporting both StandaloneDTO and DuplicateDTO
- **Use Cases**: Common processing steps, shared validation logic

#### PhotoPartnerPipe & VideoPartnerPipe
```php
interface PhotoPartnerPipe
{
    public function handle(PhotoPartnerDTO $state, \Closure $next): PhotoPartnerDTO;
}

interface VideoPartnerPipe  
{
    public function handle(VideoPartnerDTO $state, \Closure $next): VideoPartnerDTO;
}
```
- **Purpose**: Handle paired file processing (Live Photos)
- **Input/Output**: Respective partner DTOs
- **Use Cases**: Paired file coordination, Live Photo processing, RAW workflow

### Pipeline Flow by Type

The actual pipeline varies based on the type of photo being processed:

1. **Standard Photos**: InitPipe → StandalonePipe → PhotoPipe
2. **Duplicate Photos**: InitPipe → DuplicatePipe → PhotoPipe
3. **Paired Files**: It depends whether the file is a photo or video:
   - Video (photo already in Database): InitPipe → VideoPartnerPipe → PhotoPipe
   - Photo (video already in Database): InitPipe → StandalonePipe → VideoPartnerPipe → PhotoPartnerPipe → PhotoPipe

During the pipeline execution, when an operation is shared between the different Pipes, the `SharedPipe` can be used at any stage for common logic.


## Adding New Pipes

To add a new processing step:

1. **Create the pipe class**:
```php
<?php

namespace App\Actions\Photo\Pipes;

use App\Actions\Photo\PhotoFile;
use Closure;

class YourNewPipe
{
    public function handle(PhotoFile $photoFile, Closure $next): PhotoFile
    {
        // Your processing logic here
        
        return $next($photoFile);
    }
}
```

2. **Add to the pipeline** in `Create.php`:
```php
return $this->pipe($photoFile, [
    Init::class,
    ExtractMetadata::class,
    YourNewPipe::class, // Add your pipe here
    // ... other pipes
]);
```

---

*Last updated: August 14, 2025*

---

## File: docs/README.md

# Lychee Documentation

Welcome to the Lychee documentation! This folder contains comprehensive documentation about the Lychee photo management system and is primarily focused on the internal workings of Lychee.
This includes its architecture, request lifecycles, data structures, and validation systems. It is intended for developers and contributors who want to understand how Lychee operates under the hood.

Before getting started, we would like to highlight the folders of interest at the root level of the repository:

```
./
├── app/        # Contains the core of Lychee: models, controllers, actions, and business logic
├── database/   # Database migrations for setting up the database schema
├── docs/       # Developer documentation (this folder) explaining Lychee's internal workings
│   └── specs/  # Structured documentation following Diátaxis framework
├── lang/       # Translation files for internationalization
├── resources/  # Frontend assets including Vue.js components
├── routes/     # API and web route definitions
└── tests/      # Automated test suites for ensuring code quality and functionality
```

The missing folders (`bootstrap`, `composer-cache`, `config`, `docker`, `phpstan`, `public`, `scripts` and `storage`) are still essential for the overall functionality of Lychee but not of primary concern for understanding the system.

## Documentation Structure

Documentation is organized following the [Diátaxis framework](https://diataxis.fr/) in the `specs/` directory:

- **[0-overview](specs/0-overview/)** - High-level project documentation
- **[1-concepts](specs/1-concepts/)** - Conceptual explanations (domain model, photos, albums, permissions)
- **[2-how-to](specs/2-how-to/)** - Practical how-to guides
- **[3-reference](specs/3-reference/)** - Technical reference documentation
- **[4-architecture](specs/4-architecture/)** - Architecture decisions and designs
- **[5-operations](specs/5-operations/)** - Operational runbooks
- **[6-decisions](specs/6-decisions/)** - Architectural Decision Records (ADRs)

## Getting Started

### Backend Architecture
- [Backend Architecture](specs/4-architecture/backend-architecture.md) - Laravel structure, design patterns, and key components
- [API Design](specs/3-reference/api-design.md) - RESTful API patterns, authentication, and response structure
- [Database Schema](specs/3-reference/database-schema.md) - Models, relationships, smart albums vs regular albums
- [Request Lifecycle: Album Creation](specs/4-architecture/request-lifecycle-album-creation.md) - Complete album creation flow
- [Request Lifecycle: Photo Upload](specs/4-architecture/request-lifecycle-photo-upload.md) - Photo upload and processing flow

### Frontend Architecture
- [Frontend Architecture](specs/3-reference/frontend-architecture.md) - Vue3, TypeScript, Pinia, composables, and Lychee conventions
- [Frontend Gallery Views](specs/3-reference/frontend-gallery.md) - Gallery viewing modes and component architecture
- [Frontend Layout System](specs/3-reference/frontend-layouts.md) - Photo layout algorithms
- [Customize the v8 (Nuxt UI) Frontend](specs/2-how-to/customize-v8-nuxt-ui.md) - How-to guide for theming, per-component style overrides, icons, and toasts/dialogs in the Nuxt UI migration

### Data Structures
- [Album Tree Structure](specs/4-architecture/album-tree-structure.md) - Nested set model for hierarchical album organization
- [Tag System](specs/4-architecture/tag-system.md) - Tag architecture and operations
- [Smart Albums Documentation](../app/SmartAlbums/README.md) - Virtual albums that dynamically contain photos based on criteria

### Authorization and Validation
- [Policies Documentation](../app/Policies/README.md) - Authorization and access control system with regular and query policies
- [Rules Documentation](../app/Rules/README.md) - Custom validation rules with patterns, security considerations, and implementation examples

### Localization and Internationalization
- [Localization Reference](specs/3-reference/localization.md) - Technical reference for the translation system
- [Translating Lychee](specs/2-how-to/translating-lychee.md) - How-to guide for adding translations

### Contributing
- [Contribution Guide](Contribute.md) - How to contribute to Lychee
- [Coding Conventions](specs/3-reference/coding-conventions.md) - PHP and Vue3 coding standards
- [AI/Claude Guidelines](Contribute.md#using-aiclaude-for-contributions) - Guidelines for AI-assisted development
- [AGENTS.md](../AGENTS.md) - Instructions for AI agents working on this codebase

## Additional Resources

For more information about Lychee:
- [Main Repository](https://github.com/LycheeOrg/Lychee)
- [Official Website](https://lycheeorg.dev/)
- [Admin Documentation](https://lycheeorg.dev/docs/)
- [Knowledge Map](specs/4-architecture/knowledge-map.md) - Module and dependency relationships

---

*Last updated: January 21, 2026*