Przejdź do treści

Media (Media)

Ostatnia aktualizacja dokumentacji: 26 lutego 2026 Stan synchronizacji z kodem: ✅ Zsynchronizowany

Zarządzanie zdjęciami i plikami multimedialnymi — galerie obiektów i zajęć, miniatury, automatyczna optymalizacja i konwersja do WebP.


1. Opis ogólny

Domena Media zarządza zdjęciami i plikami multimedialnymi w systemie. To jak album fotograficzny dla każdego obiektu i zajęć — możesz dodawać zdjęcia, system automatycznie tworzy miniatury i optymalizuje pliki do wyświetlania w internecie. Domena obsługuje polimorficzne dowiązanie mediów do różnych encji (Item, Activity) oraz automatyczne zarządzanie miniaturami i kolejnością zdjęć w galerii.

System obsługuje:

  • Zdjęcia obiektów (Item)
  • Zdjęcia zajęć (Activity)
  • Automatyczne tworzenie miniatur (300×300 WebP)
  • Konwersję do formatu WebP (1200×1200)
  • Galerie z wieloma zdjęciami i zarządzaniem kolejnością
  • Konfigurowalny limit mediów na encję

2. Architektura domeny

2.1 Modele danych

Model Opis Tabela Klucz główny
Media Rozszerza Spatie\MediaLibrary\MediaCollections\Models\Media media UUID (HasUuids)

Model Media — kluczowe cechy:

  • Rozszerza model Spatie Media Library (nie standardowy Eloquent)
  • Klucz główny UUID (nie auto-increment)
  • Polimorficzna relacja do modelu nadrzędnego (model_type + model_id)
  • Global scopes: UserAccessesGlobalScope, IsActiveGlobalScope
  • Observer: MediaObserver (pusty — placeholder na przyszłe hooki)
  • Fillable: id, model_type, model_id, uuid, collection_name, name, file_name, mime_type, disk, conversions_disk, size, manipulations, custom_properties, generated_conversions, responsive_images, order_column, created_at, updated_at
  • Public filters (do filtrowania API): id, model_type, model_id, uuid, collection_name

2.2 Diagram relacji

erDiagram
    Media ||--o{ Item : "model_type = Item"
    Media ||--o{ Activity : "model_type = Activity"
    Media ||--o{ Complaint : "model_type = Complaint (attachments)"
    Media ||--o{ Reservation : "model_type = Reservation (attachments)"

    Media {
        uuid id PK
        string model_type
        uuid model_id
        string collection_name
        string file_name
        string mime_type
        int order_column
    }

2.3 Struktura tabeli w bazie danych

Migracja: 0001_01_01_000025_create_spatie_medias.php

Kolumna Typ Nullable Default Opis
id uuid Nie Klucz główny UUID
model_type string Nie FQCN modelu nadrzędnego (polimorfizm)
model_id uuid Nie ID modelu nadrzędnego
uuid uuid Tak null Unikalny identyfikator Spatie (unique index)
collection_name string Nie Nazwa kolekcji: thumbnail lub gallery
name string Nie Oryginalna nazwa pliku (bez rozszerzenia)
file_name string Nie Pełna nazwa pliku
mime_type string Tak null Typ MIME pliku
disk string Nie Dysk przechowywania
conversions_disk string Tak null Dysk konwersji
size unsignedBigInteger Nie Rozmiar pliku w bajtach
manipulations json Nie Konfiguracja manipulacji
custom_properties json Nie Niestandardowe właściwości
generated_conversions json Nie Status wygenerowanych konwersji
responsive_images json Nie Dane responsywnych obrazów
order_column unsignedInteger Tak null Pozycja w galerii (index)
created_at timestamp Tak null Data utworzenia
updated_at timestamp Tak null Data modyfikacji

Indeksy:

  • Primary key: id
  • Unique: uuid
  • Index: order_column
  • Index: model_type + model_id (polimorficzny morphs)

3. Endpointy API

3.1 Endpointy wymagające autoryzacji

Prefix: /api/medias | Middleware: auth:api, global_database_transaction, app_mode, to_user_access, user_accepted_agreements

GET /api/medias

  • Opis: Lista mediów z filtrowaniem i paginacją
  • Autoryzacja: Zalogowany użytkownik (brak ograniczeń roli)
  • Query Parameters:
    • filter[id] — filtr po UUID media
    • filter[model_id] — filtr po UUID modelu nadrzędnego
    • filter[model_type] — filtr po typie modelu (FQCN)
    • filter[uuid] — filtr po UUID Spatie
    • sort — sortowanie po order_column
    • Parametry paginacji (standardowe)
  • Reguły biznesowe: Zwraca tylko media z kolekcji thumbnail lub gallery. Stosowane global scopes: UserAccessesGlobalScope, IsActiveGlobalScope
  • Response: Paginowana kolekcja MediaResource

GET /api/medias/{mediaId}

  • Opis: Szczegóły pojedynczego media
  • Autoryzacja: Zalogowany użytkownik
  • Parametry URL: mediaId (UUID)
  • Reguły biznesowe: Zwraca tylko media z kolekcji thumbnail lub gallery
  • Response (sukces):
    {
      "id": "uuid",
      "model_type": "Domain\\Item\\Model\\Item",
      "model_id": "uuid",
      "collection_name": "gallery",
      "name": "zdjecie-basenu",
      "file_name": "zdjecie-basenu.jpg",
      "mime_type": "image/jpeg",
      "size": 1024000,
      "order_column": 2,
      "url": "https://example.com/storage/media/uuid/conversions/web.webp",
      "can_be_edit": true,
      "created_at": "2026-01-15 10:30:00",
      "updated_at": "2026-01-15 10:30:05",
      "can": {
        "view": true,
        "update": true
      },
      "model": { }
    }
    
  • Response (błędy): 404 — Media nie znalezione

POST /api/medias

  • Opis: Upload nowych zdjęć
  • Autoryzacja: Rola: ADMIN, SUPERVISOR lub EMPLOYEE (middleware) + Policy create (sprawdza przynależność do organizacji)
  • Body (Request):
    {
      "model_type": "required|string|in:Domain\\Item\\Model\\Item,Domain\\Activity\\Model\\Activity",
      "model_id": "required|uuid|CheckExistModelIdRule",
      "files": "required|array",
      "files.*": "required|file|mimetypes:image/jpeg,image/png,image/webp|max:5120",
      "order_column": "sometimes|integer|min:0",
      "collection_name": "sometimes|string|max:255|in:thumbnail,gallery"
    }
    
  • Reguły biznesowe:
    1. Walidacja CheckExistModelIdRule — sprawdza czy model o podanym model_id istnieje w bazie
    2. Limit mediów — konfigurowalny parametr LIMIT_MEDIA (sprawdza czy aktualna_ilość + nowe_pliki > limit)
    3. Model musi implementować HasMedia (Spatie)
    4. Domyślna kolekcja: gallery (jeśli nie podano collection_name)
    5. Po uploadzie automatycznie ustawiana jest miniatura (SetThumbnailIfNotExist)
  • Response (sukces): 200"The Media has been successfully added"
  • Response (błędy):
    • 409StoreModelMediaException — przekroczono limit lub model nie implementuje HasMedia
    • 422 — błędy walidacji

PATCH /api/medias/{mediaId}

  • Opis: Aktualizacja media (zmiana kolekcji)
  • Autoryzacja: Rola: ADMIN, SUPERVISOR lub EMPLOYEE + Policy update (przynależność do organizacji)
  • Parametry URL: mediaId (UUID)
  • Body (Request):
    {
      "collection_name": "sometimes|string|max:255|in:thumbnail,gallery"
    }
    
  • Reguły biznesowe:
    1. Jeśli collection_name = thumbnail → wszystkie inne media tego modelu dostają collection_name = gallery
    2. Media jest przenoszone na pozycję 1 w kolejności (order_column = 1)
    3. Zmiana kolejności wykonywana algorytmem ChangeMediaOrder
  • Response (sukces): 200"The Media has been successfully updated"

PATCH /api/medias/{mediaId}/change-order

  • Opis: Zmiana kolejności media w galerii
  • Autoryzacja: Rola: ADMIN, SUPERVISOR lub EMPLOYEE + Policy update
  • Parametry URL: mediaId (UUID)
  • Body (Request):
    {
      "order_column": "required|integer|min:0"
    }
    
  • Reguły biznesowe:
    1. Algorytm przesunięcia: media między pozycjami target a current są przesuwane o ±1
    2. Po zmianie kolejności automatycznie ustawiana miniatura (SetThumbnailIfNotExist)
  • Response (sukces): 200"The Media has been successfully updated"

DELETE /api/medias/{mediaId}

  • Opis: Usunięcie media
  • Autoryzacja: Rola: ADMIN, SUPERVISOR lub EMPLOYEE + Policy update (uwaga: sprawdza update, nie delete)
  • Parametry URL: mediaId (UUID)
  • Reguły biznesowe:
    1. Po usunięciu automatycznie przenumerowywana jest kolekcja (ReorderMediaCollection)
    2. Automatycznie ustawiana miniatura (SetThumbnailIfNotExist)
  • Response (sukces): 200"The Media was deleted successfully"
  • Response (błędy): 404 — Media nie znalezione

3.2 Endpointy publiczne (bez autoryzacji)

Prefix: /api/no-auth/medias | Middleware: throttle:api, global_database_transaction

GET /api/no-auth/medias

  • Opis: Publiczna lista mediów (identyczna logika jak endpoint auth)
  • Autoryzacja: Brak — publiczny dostęp
  • Response: Paginowana kolekcja MediaResource (bez pola can — usuwane przez reduceResourceForNoAuth)

GET /api/no-auth/medias/{mediaId}

  • Opis: Publiczne szczegóły pojedynczego media
  • Autoryzacja: Brak — publiczny dostęp
  • Response: MediaResource (bez pola can)

4. Logika biznesowa

4.1 Główne procesy

Proces uploadu zdjęć

flowchart TD
    A[Admin wybiera zdjęcia] --> B[Walidacja FormRequest]
    B --> C{model_type ∈ Item, Activity?}
    C -->|Nie| D[422 - Błąd walidacji]
    C -->|Tak| E{Model istnieje? CheckExistModelIdRule}
    E -->|Nie| D
    E -->|Tak| F[Policy: create - sprawdź organizację]
    F --> G{Należy do organizacji?}
    G -->|Nie| H[403 - Brak uprawnień]
    G -->|Tak| I{Limit mediów nie przekroczony?}
    I -->|Nie| J[409 - StoreModelMediaException]
    I -->|Tak| K[Upload plików do kolekcji]
    K --> L[Spatie: konwersje preview + web]
    L --> M[SetThumbnailIfNotExist]
    M --> N[200 - Sukces]

Proces zmiany miniatury (Update)

flowchart TD
    A[Admin zmienia collection_name na thumbnail] --> B[Znajdź media po ID]
    B --> C{collection_name = thumbnail?}
    C -->|Tak| D["Wszystkie inne media → gallery"]
    C -->|Nie| E[Zmień kolekcję]
    D --> F["Przenieś media na pozycję 1"]
    E --> F
    F --> G[Zapisz zmiany]

Proces usuwania media

flowchart TD
    A[Admin usuwa media] --> B[Policy: update]
    B --> C[Usuń media z bazy i dysku]
    C --> D[ReorderMediaCollection - przenumeruj]
    D --> E[SetThumbnailIfNotExist - ustaw miniaturę]
    E --> F[200 - Sukces]

4.2 Reguły biznesowe

  1. Dozwolone modele — media można dodawać tylko do Item i Activity (przez API uploadu)
  2. Limit mediów — konfigurowalny parametr LIMIT_MEDIA (pobierany z domeny Configuration); przy przekroczeniu rzucany jest StoreModelMediaException (HTTP 409)
  3. Automatyczna miniatura — pierwsze media (wg order_column) jest zawsze ustawiane jako thumbnail; reszta jako gallery
  4. Zmiana na thumbnail — ustawienie media jako thumbnail automatycznie resetuje kolekcję wszystkich innych mediów na gallery i przenosi media na pozycję 1
  5. Zmiana kolejności — algorytm przesuwa media pomiędzy pozycjami current a target, aktualizując order_column sąsiadów o ±1
  6. Usunięcie media — po usunięciu kolekcja jest przenumerowywana sekwencyjnie (1, 2, 3, ...) i automatycznie ustawiana nowa miniatura
  7. Oryginały nie są przechowywane — konfiguracja preserve_original: false oznacza usunięcie oryginału po konwersji
  8. Kolekcje — system obsługuje dokładnie dwie kolekcje: thumbnail i gallery
  9. Global scopesUserAccessesGlobalScope i IsActiveGlobalScope filtrują media na poziomie zapytań

4.3 Walidacje

StoreMediaControllerRequest:

Pole Reguły Opis
model_type required\|string\|in:Domain\Item\Model\Item,Domain\Activity\Model\Activity Typ modelu nadrzędnego
model_id required\|uuid\|CheckExistModelIdRule UUID modelu nadrzędnego
files required\|array Tablica plików
files.* required\|file\|mimetypes:image/jpeg,image/png,image/webp\|max:5120 Każdy plik: JPEG/PNG/WebP, max 5 MB
order_column sometimes\|integer\|min:0 Opcjonalna pozycja
collection_name sometimes\|string\|max:255\|in:thumbnail,gallery Opcjonalna kolekcja

UpdateMediaControllerRequest:

Pole Reguły Opis
collection_name sometimes\|string\|max:255\|in:thumbnail,gallery Zmiana kolekcji

ChangeOrderMediaControllerRequest:

Pole Reguły Opis
order_column required\|integer\|min:0 Nowa pozycja w galerii

Custom rule — CheckExistModelIdRule:

  • Sprawdza czy model_type jest podklasą Eloquent\Model
  • Sprawdza czy rekord o podanym model_id istnieje w bazie

5. Autoryzacja i uprawnienia

5.1 Middleware (role)

Operacje zapisu (store, update, updateOrder, delete) wymagają roli: ADMIN, SUPERVISOR lub EMPLOYEE.

5.2 Policy (MediaPolicy)

Akcja Logika autoryzacji
view Rozwiązuje organization_id z modelu nadrzędnego → jeśli brak organizacji: true; jeśli jest: sprawdza belongsToOrganization
create Wymaga modelu nadrzędnego → rozwiązuje organization_id → jeśli brak: tylko SUPERVISOR; jeśli jest: sprawdza belongsToOrganization
update Rozwiązuje organization_id z modelu nadrzędnego → jeśli brak organizacji: tylko SUPERVISOR; jeśli jest: sprawdza belongsToOrganization

Mechanizm resolveOrganizationId:

  1. Pobiera model_type z media (np. Domain\Item\Model\Item)
  2. Szuka modelu nadrzędnego po model_id (z withoutGlobalScopes())
  3. Zwraca organization_id modelu lub null

Ważne

BasePolicy daje administratorom (ADMIN) pełny bypass — nie muszą spełniać warunków policy.

5.3 Tabela uprawnień

Rola Przeglądanie Upload Edycja Zmiana kolejności Usuwanie
ADMIN Tak Tak Tak Tak Tak
SUPERVISOR Tak W organizacji W organizacji W organizacji W organizacji
EMPLOYEE Tak W organizacji W organizacji W organizacji W organizacji
Niezalogowany Publiczne Nie Nie Nie Nie

6. Eventy i efekty uboczne

Domena Media nie posiada własnych eventów, listenerów ani notyfikacji.

Element Opis
Observer MediaObserver — pusty placeholder (brak hooków)
Joby Spatie PerformConversionsJob — konwersje obrazów (kolejka media-library)
Joby Spatie GenerateResponsiveImagesJob — responsywne obrazy

7. Kolekcje mediów

System używa dwóch kolekcji dla każdej encji:

Kolekcja Opis Użycie
thumbnail Zdjęcie główne (miniatura) Listy, karty obiektów
gallery Galeria zdjęć Strona szczegółów

Thumbnail (Miniatura)

  • Jedno zdjęcie per obiekt — automatycznie pierwsze wg order_column
  • Wyświetlane na listach i w wynikach wyszukiwania
  • Ustawiane automatycznie przez SetThumbnailIfNotExist
  • Fallback: APP_URL/storage/media/base_img.webp (jeśli brak mediów)
  • Wiele zdjęć per obiekt
  • Wyświetlane na stronie szczegółów
  • Kolejność zarządzana przez order_column

Algorytm SetThumbnailIfNotExist

flowchart TD
    A[Sprawdź media modelu] --> B{Są jakieś media?}
    B -->|Nie| C[Koniec - brak miniatur]
    B -->|Tak| D["Ustaw wszystkie na gallery"]
    D --> E["Pierwsze wg order_column → thumbnail"]

8. Automatyczne konwersje

Przy każdym uploadzie system automatycznie tworzy konwersje (zdefiniowane w modelach Item i Activity):

Konwersja Rozmiar Format Kolejkowana Użycie
preview 300×300 px WebP Nie Miniatury, listy
web 1200×1200 px WebP Nie Galerie, strony szczegółów

Oryginały nie są przechowywane

Konfiguracja preserve_original: false oznacza, że po wygenerowaniu konwersji oryginalne pliki są usuwane. Przechowywane są tylko skonwertowane wersje.

Format WebP

Dlaczego WebP?

Format WebP oferuje:

  • 25-35% mniejszy rozmiar niż JPEG przy tej samej jakości
  • Przezroczystość jak PNG
  • Wsparcie przeglądarek — wszystkie nowoczesne przeglądarki

Optymalizatory obrazów

System konfiguruje następujące optymalizatory (z config/media-library.php):

Optymalizator Opis
Jpegoptim Optymalizacja JPEG
Pngquant Kompresja PNG
Optipng Optymalizacja PNG
Svgo Optymalizacja SVG
Gifsicle Optymalizacja GIF
Cwebp Konwersja do WebP
Avifenc Konwersja do AVIF

9. Limity plików

Parametr Wartość Źródło
Maksymalny rozmiar pliku (walidacja) 5 MB (5120 KB) StoreMediaControllerRequest
Maksymalny rozmiar pliku (config) 10 MB config/media-library.php
Dozwolone formaty (upload API) JPEG, PNG, WebP StoreMediaControllerRequest
Maksymalna liczba mediów per model Konfigurowalna (LIMIT_MEDIA) Domena Configuration
Domyślny limit (stała w kodzie) 20 ExceptionStoreModelMedia::LIMIT_MEDIA

10. Sortowanie w galerii

Algorytm ChangeMediaOrder

Zmiana pozycji media z current na target:

flowchart TD
    A["Zmiana pozycji: current → target"] --> B{target < current?}
    B -->|Tak| C["Media w [target, current-1] → order_column + 1"]
    B -->|Nie| D["Media w [current+1, target] → order_column - 1"]
    C --> E["Media na pozycji current → target"]
    D --> E

ReorderMediaCollection

Po usunięciu media, kolekcja jest przenumerowywana sekwencyjnie:

  • Pobiera wszystkie media modelu posortowane domyślnie
  • Nadaje order_column = 1, 2, 3, ... (indeks + 1)

11. Enum — MediaEnumService

Przypadek Wartość Opis
LIMIT_MEDIA "LIMIT_MEDIA" Klucz konfiguracji limitu
MEDIA_COLLECTION_TYPE_THUMBNAIL "thumbnail" Kolekcja miniatur
MEDIA_COLLECTION_TYPE_GALLERY "gallery" Kolekcja galerii
MEDIA_TYPE_IMAGE "image" Typ: obraz
MEDIA_TYPE_VIDEO "video" Typ: wideo
MEDIA_TYPE_DOCUMENT "document" Typ: dokument (PDF, Word, Excel)
MEDIA_TYPE_OTHER "other" Typ: inny

Klasyfikacja typów mediów (GetMediaType)

MIME type Klasyfikacja
image/* image
video/* video
application/pdf, application/msword, application/vnd.openxmlformats-officedocument.* document
Wszystko inne other

12. Struktura odpowiedzi JSON (MediaResource)

{
  "id": "string (UUID)",
  "model_type": "string (FQCN modelu)",
  "model_id": "string (UUID)",
  "collection_name": "string (thumbnail|gallery)",
  "name": "string (nazwa pliku bez rozszerzenia)",
  "file_name": "string (pełna nazwa pliku)",
  "mime_type": "string (typ MIME)",
  "size": "integer (rozmiar w bajtach)",
  "order_column": "integer (pozycja w galerii)",
  "url": "string (URL konwersji 'web')",
  "can_be_edit": "boolean",
  "created_at": "string|null (Y-m-d H:i:s)",
  "updated_at": "string|null (Y-m-d H:i:s)",
  "can": {
    "view": "boolean",
    "update": "boolean"
  },
  "model": "object|null (relacja do modelu nadrzędnego)"
}

Endpointy publiczne

Pole can jest usuwane dla niezalogowanych użytkowników (konfiguracja UNALLOWED_NO_AUTH_KEYS).


13. Powiązania z innymi domenami

Domena Powiązanie Opis
Zasoby (Item) Polimorficzne HasMedia Galeria zdjęć obiektu; konwersje preview i web
Aktywności (Activity) Polimorficzne HasMedia Zdjęcia zajęć; konwersje preview i web
Reklamacje (Complaint) Polimorficzne HasMedia Kolekcja attachments (zdjęcia, wideo, PDF, Word)
Rezerwacje (Reservation) Polimorficzne HasMedia Kolekcja attachments (zdjęcia, wideo, PDF, Word)
Konfiguracja (Configuration) Odczyt parametru LIMIT_MEDIA Konfigurowalny limit mediów per model
Organizacja (Organization) Pośrednio przez Policy Autoryzacja sprawdza organization_id modelu nadrzędnego

Complaint i Reservation

Modele Complaint i Reservation rejestrują kolekcję attachments bezpośrednio w swoich modelach. Nie korzystają z endpointów API domeny Media do uploadu — ich media są zarządzane w ramach ich własnych domen.


14. Konfiguracja

config/media-library.php

Parametr Wartość Opis
disk_name env('MEDIA_DISK', 'media') Dysk przechowywania
max_file_size 10 MB (1024 × 1024 × 10) Maksymalny rozmiar pliku (config)
queue_conversions_by_default true Konwersje domyślnie kolejkowane
media_model \Domain\Media\Model\Media::class Nadpisany model Media
image_driver env('IMAGE_DRIVER', 'gd') Sterownik przetwarzania obrazów
preserve_original false Oryginały usuwane po konwersji

Parametr konfiguracyjny

Slug Opis Źródło
LIMIT_MEDIA Maksymalna liczba mediów na model Domena Configuration

15. Kolejka przetwarzania

Element Opis
Kolejka media-library
Job konwersji Spatie\MediaLibrary\Conversions\Jobs\PerformConversionsJob
Job responsywnych Spatie\MediaLibrary\ResponsiveImages\Jobs\GenerateResponsiveImagesJob

Asynchroniczne przetwarzanie

Upload zdjęcia jest natychmiastowy. Konwersje (miniatury, WebP) mogą być tworzone w tle na kolejce media-library. Użytkownik nie musi czekać.


16. Znane ograniczenia i TODO

  • MediaObserver jest pusty — placeholder bez logiki
  • Usuwanie media sprawdza policy update zamiast delete
  • ReorderMediaCollection nie sortuje mediów przed przenumerowaniem (kolejność zależy od domyślnego sortowania bazy)
  • Brak walidacji czy order_column mieści się w zakresie istniejących mediów (w ChangeOrderMediaControllerRequest)

Technologia

Element Technologia
Biblioteka Spatie Laravel Media Library
Przetwarzanie obrazów GD (konfigurowalne)
Przechowywanie System plików (media disk)
Kolejka Laravel Queue (media-library)
Optymalizacja Jpegoptim, Pngquant, Optipng, Cwebp, Avifenc

Wartość biznesowa

Argumenty dla decydentów

  1. Lepsze pierwsze wrażenie — zdjęcia przyciągają klientów
  2. Szybkość strony — zoptymalizowane pliki WebP = lepsze SEO
  3. Profesjonalizm — spójna prezentacja wszystkich obiektów
  4. Oszczędność miejsca — automatyczna kompresja i usuwanie oryginałów
  5. Bezpieczeństwo — autoryzacja na poziomie organizacji

Best practices

Dla administratorów

Wskazówki

  1. Używaj zdjęć wysokiej jakości — system je zoptymalizuje
  2. Wybieraj reprezentatywne miniatury — to pierwsze co widzi klient
  3. Dodawaj wiele zdjęć — pokazuj obiekt z różnych perspektyw
  4. Aktualizuj zdjęcia — sezonowe zmiany, remonty
  5. Dozwolone formaty — JPEG, PNG lub WebP (max 5 MB)

Rekomendowane parametry zdjęć źródłowych

Parametr Rekomendacja
Minimalna rozdzielczość 1920×1080 px
Format JPEG (dla zdjęć), PNG (dla grafik)
Rozmiar < 5 MB
Proporcje 16:9 lub 4:3