---
title: "JSON API — Events for Laravel | Roundly"
description: "Voliteľné JSON API — 49 rout, telá požiadaviek, filtre a radenie, resources podľa roly, referenčné resources, chyby a limity požiadaviek."
url: https://roundly-consulting.com/sk/open-source/navody/events-for-laravel/json-api
language: sk
---

[Všetky balíky](https://roundly-consulting.com/sk/open-source.md)

[Events for Laravel](https://roundly-consulting.com/sk/open-source/navody/events-for-laravel.md)

# JSON API

Kým ho nezapnete, je vypnuté:

```bash
EVENTS_ROUTES=on
```

- Prefix events.routes.prefix (api), názvy rout events.\*, middleware events.routes.middleware (\['api', 'throttle:events-api'\]); zapisovacie routy pridajú events.routes.auth\_middleware (\['auth'\]).
- Nenastavený prepínač zostáva vypnutý — aj v publikovanej konfigurácii, ktorej pole routes kľúč nemá, pretože zlučovanie je plytké.
- Ak chcete upraviť súbor rout, publikujte --tag=events-routes: keď routes/events.php vo vašej aplikácii existuje, načíta sa namiesto súboru balíka a prepínač ho naďalej riadi.
- Zápisy vždy vyžadujú prihláseného používateľa. Po vašom auth\_middleware spustí každá zapisovacia routa vlastnú kontrolu balíka: chýbajúci prihlásený používateľ alebo používateľ guardu, ktorý nie je Eloquent model, je 401 — aj pri auth\_middleware = \[\]. Beží pred validáciou, takže hosť sa nikdy nedozvie tvar požiadavky. Čítacie routy a routy odkazu z pozvánky hostí naďalej pustia.

## Konvencie

- {event} je uuid — obmedzené tak, aby sa naň /events/feed.ics nikdy nedostal — a podujatie, ktoré volajúci vidieť nesmie, je 404 presne ako neexistujúce, na každom endpointe. {rsvp} a {tier} sú obmedzené na podujatie (typ vstupenky podľa id alebo kľúča); {venue} je číselné.
- Na vaše modely sa API odvoláva cez alias, nikdy cez názov triedy: {type: user, id: 42} v telách a user:42 v cestách a filtroch (events.organizers, events.attendees). V tele je každé zlyhanie — neznámy alias, chybný kľúč, chýbajúci riadok — rovnaké 422 unknown\_alias.
- Časy v požiadavkách sú miestny čas v pásme podujatia alebo okamih ISO-8601 s posunom; odpovede používajú ISO-8601 s vlastným posunom podujatia.
- Textové polia prijímajú reťazec (aktuálny jazyk) alebo mapu jazykov. Ceny sú desatinné reťazce s voliteľnou menou; odpovede vykresľujú MoneyResource z money-for-laravel — {minor, decimal, currency, exponent, formatted}, pričom minor je reťazec a formatted je len na zobrazenie. Bezplatný typ vstupenky má price: null.
- Zoznamy sú length-aware paginátory z Laravelu (data, links, current\_page, per\_page, total, …) s per\_page obmedzeným na events.routes.max\_per\_page.

## Routy

Všetkých 49 rout, zoskupených, s cestami relatívne k prefixu. Riadky s poznámkou „Aj hostia“ odpovedajú aj neprihláseným; každá iná routa vyžaduje prihláseného používateľa:

| Metóda · cesta | Názov | Oprávnenie | Poznámky |
| --- | --- | --- | --- |
| `GET /events` | `events.index` | `viewAny` | Aj hostia. Filtre objavovania nižšie → stránkovaný EventResource. |
| `GET /events/feed.ics` | `events.feed` | — | Aj hostia. ?category=&label=&organizer=alias:key&venue= → text/calendar (inline); throttle events-feeds. |
| `GET /events/{event}` | `events.show` | `view` | Aj hostia. {data: EventResource} s hosts a viewer.can. |
| `GET /events/{event}/calendar.ics` | `events.calendar` | `view` | Aj hostia. text/calendar (príloha); odkaz na pripojenie len pre diváka, ktorý ho smie vidieť. |
| `GET /events/{event}/tiers` | `events.tiers.index` | `view` | Aj hostia. Skryté len pre správcov. |
| `GET /events/{event}/attendees` | `events.attendees.index` | `viewAttendees` | Aj hostia. ?status=&checked\_in=&tier=&per\_page= (predvolene 25) — filtre len pre správcov. |
| `GET /events/{event}/hosts` | `events.hosts.index` | `view` | Aj hostia. {data: \[referencia spoluorganizátora\]}. |
| `GET /event-venues` | `events.venues.index` | — | Aj hostia. ?mine=1&near=lat,lng&radius=&per\_page= (predvolene 25). |
| `GET /event-venues/{venue}` | `events.venues.show` | — | Aj hostia. |
| `POST /event-invitations/accept · decline` | `events.invitations.accept · decline` | token | Aj hostia. {token, response?} (predvolene going, interested) → RsvpResource (pohľad vlastníka) / {status: declined}; throttle events-rsvp. |
| `POST /events` | `events.store` | `create` | 201 {data: EventResource}. |
| `PATCH /events/{event} · DELETE /events/{event}` | `events.update · destroy` | `update · delete` | Zmenia sa len poslané polia; ?notify=0 zapíše potichu · 204. |
| `POST /events/{event}/restore` | `events.restore` | `restore` | Zmazané podujatia, len správcovia (ostatní 404). |
| `POST /events/{event}/publish · unpublish · complete` | `events.publish · …` | `publish · …` | {data: EventResource} |
| `POST /events/{event}/postpone · cancel` | `events.postpone · cancel` | `postpone · cancel` | {reason?} (≤ 500). |
| `POST /events/{event}/reschedule` | `events.reschedule` | `reschedule` | {starts\_at, ends\_at?, reason?} |
| `POST /events/{event}/move-online` | `events.move-online` | `moveOnline` | {url, keep\_venue?} |
| `PUT · DELETE /events/{event}/poster` | `events.poster.update · destroy` | `update` | Multipart poster alebo token draftu · odstráni ho. |
| `POST /events/{event}/tiers · tiers/reorder` | `events.tiers.store · reorder` | `manageTiers` | 201 AdmissionTierResource · {order: \[id typu, …\]} → 204. |
| `PATCH · DELETE /events/{event}/tiers/{tier}` | `events.tiers.update · destroy` | `manageTiers` | Typ podľa id alebo kľúča; len poslané polia · 204. |
| `GET /events/{event}/rsvp · rsvp/qr.svg` | `events.rsvp.show · qr` | — | Vaše RSVP (pohľad vlastníka) alebo null · váš kód ako image/svg+xml, Cache-Control: no-store, private. |
| `PUT · DELETE /events/{event}/rsvp` | `events.rsvp.update · destroy` | `rsvp` | {response, guests?, tier?, note?} · odpovie not\_going; throttle events-rsvp. |
| `POST /events/{event}/attendees` | `events.attendees.store` | `manageAttendees` | {attendee: {type, id}, tier?, guests?, over\_capacity?} → 201 |
| `PATCH · DELETE /events/{event}/attendees/{rsvp}` | `events.attendees.update · destroy` | `manageAttendees` | {tier?, guests?} — uplatní sa oboje alebo nič · 204. |
| `POST · DELETE /events/{event}/attendees/{rsvp}/check-in` | `events.attendees.check-in · undo-check-in` | `checkIn` | Ručný check-in {guests?} (bez kódu) · zrušenie. |
| `POST /events/{event}/check-ins` | `events.check-ins.store` | `checkIn` | Skenovanie pri vstupe {code, guests?} → CheckInResultResource, 201 prvý raz, 200 opakovane; throttle events-check-in. |
| `GET /events/{event}/waitlist · POST …/waitlist/promote` | `events.waitlist.index · promote` | `viewWaitlist · manageAttendees` | V poradí posúvania · {limit?} (1–1000). |
| `POST · DELETE /events/{event}/hosts…` | `events.hosts.store · destroy` | `manageHosts` | {person: {type, id}} (Organizer) → 201 · {host} = alias:key → 204. |
| `GET · POST · DELETE /events/{event}/invitations…` | `events.invitations.index · store · destroy` | `invite` | Tokeny sa nikdy nevykreslia · {person: {type, id}, message?} (≤ 1000) → 201 · {invitee} = alias:key → 204. |
| `POST · PATCH · DELETE /event-venues…` | `events.venues.store · update · destroy` | `VenuePolicy` | Telo miesta konania nižšie → 201 · len poslané polia · 204. |
| `POST /event-venues/{venue}/geocode` | `events.venues.geocode` | VenuePolicy geocode | Vždy geokóduje znova (force). |

Routa na potvrdenie platby neexistuje: potvrďte ju z webhooku poskytovateľa v PHP cez Events::rsvps()->confirmPayment($reference).

## Vytvorenie podujatia

```json
{
  "title": { "en": "Jazz Night", "sk": "Jazzový večer" },
  "venue_id": 7,
  "starts_at": "2026-11-14 20:00",
  "duration": "PT3H",
  "capacity": 120,
  "max_guests": 1,
  "category": "jazz",
  "labels": ["Live", "Late night"],
  "tiers": [
    { "name": "Free entry", "key": "free" },
    { "name": "Supporter", "price": "15.00", "currency": "EUR", "capacity": 30 }
  ],
  "publish": true
}
```

| Pole | Pravidlá |
| --- | --- |
| `title` | Povinné; reťazec alebo mapa jazykov, každý ≤ 200 znakov. |
| `description` | ≤ 20 000. |
| `organizer` | {type, id} z events.organizers — predvolene volajúci; musí volajúceho povoliť. |
| `venue_id · venue` | Existujúce miesto konania (akýkoľvek organizátor môže usporiadať podujatie na akomkoľvek mieste) — alebo nové miesto priamo v tele, vlastnené volajúcim: name (povinné), street, city, region, postal\_code, country, coordinates, timezone. |
| `online_url · attendance_mode · visibility · guest_list · timezone` | ≤ 2048 · hodnoty enumov · názov IANA. |
| `starts_at · ends_at · duration · doors_open_at · all_day_from · all_day_through` | starts\_at povinné, ak nie je all\_day\_from; celodenné dátumy Y-m-d. |
| `capacity · max_guests` | 0–10,000,000 · 0–1000 |
| `waitlist · waitlist_auto_promote · show_counts · publish` | Boolean hodnoty; publish: true ho ohlási v tej istej transakcii. |
| `rsvp_opens_at · rsvp_closes_at · reminders` | Reťazce; ≤ 5 pripomienok. |
| `category · labels` | Kategória stromu · ≤ 10 reťazcov (v katalógu organizátora pri events.tags.scope = organizer). |
| `tiers` | ≤ 50 položiek {name, key?, price?, currency?, capacity?, hidden?}; price v tvare ^\\d{1,12}(\\.\\d{1,4})?$. |
| `poster_draft` | Token draftu z media-library. |

PATCH /events/{event} zmení len poslané polia a null vymaže pole, ktoré môže byť prázdne; category (null vymaže) a labels (presne tieto) sa uplatnia v tej istej transakcii. Nový začiatok ohláseného podujatia sa odmietne — použite POST /reschedule. ?notify=0, alebo notify: false v tele, zapíše potichu.

## Typy vstupeniek a miesta konania

Telá typov vstupeniek prijímajú name (pri vytvorení povinné, ≤ 200), description (≤ 2000), key (≤ 64; bez neho sa odvodí z názvu), price + currency, capacity, sale\_starts\_at, sale\_ends\_at, min\_party (1–1000), max\_party, hidden a position. Telá miest konania prijímajú name (pri vytvorení povinné), street, city, region, postal\_code, country (2 písmená), formatted\_address, coordinates (lat,lng, \[lat, lng\] alebo {latitude, longitude}), timezone, capacity a geocode a pri vytvorení owner ({type, id}, ktorý musí volajúceho povoliť) alebo shared (len administrátori a abilities.shared\_venues). Bez owner a shared patrí nové miesto volajúcemu. Pri PATCH sa každé pole adresy mení samostatne a vlastníctvo sa nikdy nemení.

## Vaše RSVP

PUT /api/events/{event}/rsvp s telom:

```json
{ "response": "going", "tier": "free", "guests": 1, "note": "Wheelchair access" }
```

vráti vaše vlastné RSVP v pohľade vlastníka, s kódom pre check-in a URL QR kódu:

```json
{
  "data": {
    "id": 42,
    "status": "going",
    "guests": 1,
    "party_size": 2,
    "tier": { "id": 1, "key": "free", "name": "Free entry" },
    "note": "Wheelchair access",
    "waitlist_position": null,
    "held_until": null,
    "amount": null,
    "checked_in_at": null,
    "checked_in_guests": null,
    "responded_at": "2026-11-02T18:12:40+01:00",
    "payment_url": null,
    "check_in_code": "EV1-…",
    "qr_url": "https://example.com/api/events/0195f2c4-7a1e-7c3b-9d2e-5b8a1f4c6e21/rsvp/qr.svg"
  }
}
```

## Zoznam hostí

GET /events/{event}/attendees vypíše správcom akýkoľvek stav (?status=, predvolene going) so záznamom o check-ine — nikdy s kódmi — a filtrom podľa checked\_in a tier. Ktokoľvek iný, kto zoznam smie vidieť, uvidí len účastníkov so stavom going ako {attendee, guests}: status=interested je preňho 403 a status, checked\_in a tier sa ignorujú — vždy celý zoznam, nikdy zúžený na to, kto je na mieste alebo kto má skrytý typ vstupenky.

## Objavovanie: GET /events

Postavené na scopes modelu, takže PHP aj HTTP filtrujú rovnako; query-builder rozdelí každú hodnotu podľa čiarok. Základom je browse($viewer) — viditeľné, verejne uvedené, ohlásené:

| Parameter | Scope |
| --- | --- |
| `filter[when]=upcoming\|ongoing\|past` | `upcoming() …` |
| `filter[from], filter[to]` | Prekrývajúce sa s rozsahom, miestny čas v ?tz= (predvolene app.timezone). |
| `filter[date]=2026-11-14` | onLocalDate() — skutočný dátum (2027-02-30 je 422). |
| `filter[near]=48.14,17.11,25` | near() — polomer voliteľný, ≤ geo.max\_radius\_km. |
| `filter[category]=jazz` | inCategory() vrátane podkategórií. |
| `filter[labels]=live,outdoor` | Všetky štítky. |
| `filter[organizer]=user:42` | organizedBy() — riadok sa nikdy nevyhľadáva; neexistujúci nezodpovedá ničomu. |
| `filter[venue]=7,8 · filter[mode]=online,mixed` | `atVenue() · withAttendanceMode()` |
| `filter[free]=true · filter[available]=true` | `free() · withSeatsAvailable()` |
| `filter[search]=…` | search() — vyhľadávanie podreťazca v názve a popise so zneškodnenými znakmi (čiarky zostanú). |
| `filter[status]=postponed,rescheduled` | `inPhase()` |
| `filter[mine]=organizing\|attending\|interested` | Vaše podujatia, každá viditeľnosť (organizing vrátane konceptov); hostia nedostanú nič. |
| `sort=starts_at (default), -starts_at, created_at, -created_at, popularity, distance` | distance vyžaduje filter\[near\] (inak 400); popularity počíta podujatie s vypnutým show\_counts ako nula účastníkov — okrem filter\[mine\]=organizing. |
| `per_page` | Predvolene 15, ≤ events.routes.max\_per\_page. |

Neplatná hodnota filtra je 422 invalid\_query. Stránka načíta miesta konania a ich vlastníkov, organizátorov, štítky, plagáty, typy vstupeniek, vlastné RSVP diváka, cesty kategórií a to, ktoré podujatia divák spravuje, v pevnom počte dotazov — počet nerastie s veľkosťou stránky.

## Resources

Bezplatné podujatie tak, ako ho vidí hosť v zozname:

```json
{
  "id": "0195f2c4-7a1e-7c3b-9d2e-5b8a1f4c6e21",
  "slug": "jazz-night",
  "title": "Jazz Night",
  "description": "A live quartet, then an open jam session.",
  "phase": "scheduled",
  "schema_status": "EventScheduled",
  "attendance_mode": "offline",
  "starts_at": "2026-11-14T20:00:00+01:00",
  "ends_at": "2026-11-14T23:00:00+01:00",
  "doors_open_at": null,
  "previous_starts_at": null,
  "all_day": false,
  "timezone": "Europe/Bratislava",
  "starts_on": "2026-11-14",
  "ends_on": "2026-11-14",
  "venue": {
    "id": 7,
    "name": "Blue Note",
    "slug": "blue-note",
    "address": {
      "street": "Hlavná 1", "city": "Bratislava", "region": null, "postal_code": "811 01",
      "country": "SK", "formatted": "Hlavná 1, 811 01 Bratislava, SK"
    },
    "coordinates": { "latitude": 48.1439, "longitude": 17.1097 },
    "timezone": "Europe/Bratislava",
    "capacity": null,
    "owner": null,
    "distance_km": null,
    "deleted": false
  },
  "online_url": null,
  "organizer": { "id": 1, "type": "User" },
  "category": { "id": 3, "slug": "jazz", "name": "Jazz", "color": null, "icon": null, "path": ["Music"] },
  "labels": [{ "id": 4, "slug": "live", "name": "Live", "color": null, "icon": null }],
  "poster": null,
  "tiers": [
    {
      "id": 1, "key": "free", "name": "Free entry", "description": null, "price": null, "free": true,
      "capacity": null, "seats_left": null, "on_sale": true, "sale_starts_at": null, "sale_ends_at": null,
      "min_party": 1, "max_party": null, "position": 1
    }
  ],
  "capacity": 120,
  "seats_left": 85,
  "counts": { "going": 35, "waitlisted": 0 },
  "viewer": { "rsvp": null },
  "links": {
    "self": "https://example.com/api/events/0195f2c4-7a1e-7c3b-9d2e-5b8a1f4c6e21",
    "ics": "https://example.com/api/events/0195f2c4-7a1e-7c3b-9d2e-5b8a1f4c6e21/calendar.ics",
    "url": "https://example.com/en/events/jazz-night"
  }
}
```

| Resource | Tvar |
| --- | --- |
| `EventResource` | Pozrite príklad vyššie. status a visibility len pre správcov; online\_url pre správcov a účastníkov so stavom going; seats\_left a counts pre správcov, alebo pre všetkých pri show\_counts (counts.interested len pre správcov); hosts a viewer.can len v detaile. |
| `VenueResource` | id, name, slug, address {street, city, region, postal\_code, country, formatted}, coordinates {latitude, longitude} \| null, timezone, capacity, owner, distance\_km (3 decimals, when selected), deleted — never meta |
| `AdmissionTierResource` | id, key, name, description, price (MoneyResource alebo null), free, capacity, seats\_left, on\_sale, sale\_starts\_at, sale\_ends\_at, min\_party, max\_party, position a hidden pre správcov. |
| `RsvpResource` | id, status, guests, party\_size, tier {id, key, name}, note, waitlist\_position, held\_until, amount, checked\_in\_at, checked\_in\_guests, responded\_at; pohľad vlastníka pridá payment\_url (kým je rezervované), check\_in\_code a qr\_url; pohľad správcov pridá attendee, check\_in\_method a checked\_in\_by — nikdy kód. |
| `InvitationResource` | id, status, invitee, invited\_by, message, expires\_at (null, kým je odložené), responded\_at, sent\_at — nikdy token. |
| `CheckInResultResource` | first\_check\_in, guests, checked\_in\_at, method, attendee, rsvp (RsvpResource) |

## Referenčné resources

Každá osoba či tím, ktorý odpoveď spomína, sa vykreslí cez vlastný resource — predvolene {id, type}, kde type je krátky názov triedy modelu (User), nikdy názov PHP triedy ani morph typ, aj bez morph mapy. Chýbajúci model sa vykreslí ako null:

| Konfiguračný kľúč | Predvolené | Vykresľuje |
| --- | --- | --- |
| `events.resources.organizer` | `EventOrganizerResource` | `organizer` |
| `events.resources.host` | `EventHostResource` | spoluorganizátori |
| `events.resources.attendee` | `EventAttendeeResource` | účastníci |
| `events.resources.invitee` | `EventInviteeResource` | pozvaní |
| `events.resources.actor` | `EventActorResource` | `invited_by, checked_in_by` |
| `events.resources.venue_owner` | `VenueOwnerResource` | vlastník miesta konania |

Ak chcete pridať meno či avatar, nastavte kľúč na vlastný JsonResource — dostane model. Prázdna hodnota znamená predvolený; čokoľvek, čo nie je inštanciovateľná podtrieda JsonResource, vyhodí výnimku s názvom kľúča. Zoznamy súvisiace modely načítajú vopred, takže resource, ktorý číta len ich atribúty, nepridá žiadne dotazy:

```php
use Illuminate\Http\Request;
use Illuminate\Http\Resources\Json\JsonResource;

final class PersonResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return ['id' => $this->resource->getKey(), 'name' => $this->resource->name, 'avatar' => $this->resource->avatar_url];
    }
}

// config/events.php
'resources' => [
    'organizer' => App\Http\Resources\PersonResource::class,
    'host' => EventHostResource::class,
    'attendee' => App\Http\Resources\PersonResource::class,
    'invitee' => EventInviteeResource::class,
    'actor' => EventActorResource::class,
    'venue_owner' => VenueOwnerResource::class,
],
```

## Chyby

{message, code} so stavom balíka — plus reason pri odmietnutí check-inu, denials pri zamietnutom prechode a errors pri validačnom 422. Správy sú preložené a nikdy neuvádzajú triedu a váš exception handler sa nikdy nemení:

```json
{ "message": "This code is for another event.", "code": "check_in_refused", "reason": "wrong_event" }
```

| Stav | code | Kedy |
| --- | --- | --- |
| 401 | (váš handler) | Zápis bez prihláseného Eloquent používateľa. |
| 403 | `forbidden · invitation_required` | Policy zamietla · súkromné podujatie bez pozvánky. |
| 404 | `event_not_found, venue_not_found, tier_not_found, rsvp_not_found, invalid_code, invalid_invitation` | Neexistuje alebo je skryté. |
| 409 | `event_trashed, rsvp_closed, event_full, tier_sold_out, tier_not_on_sale, hold_expired, invalid_rsvp_transition, check_in_refused` | Konflikt stavu (check\_in\_refused pridá reason). |
| 422 | `validation_failed, invalid_event, invalid_schedule, invalid_venue, venue_in_use, invalid_tier, tier_key_taken, tier_in_use, tier_required, invalid_guest_count, invalid_organizer, unknown_alias, unknown_transition, transition_denied, invalid_query, tag_not_found, tag_limit_exceeded, invalid_tag, invalid_poster` | Chybný vstup (validation\_failed pridá errors, transition\_denied pridá denials). |
| 400 | `invalid_query` | sort=distance bez filter\[near\]. |
| 429 | transition\_denied (+ Retry-After) alebo throttle | Prekročený limit požiadaviek. |

Štítok iného rozsahu odpovie tag\_not\_found, rovnako ako chýbajúci. Plagát, ktorý media-library odmietne, alebo neznámy či vypršaný draft je invalid\_poster.

## Limity požiadaviek

Definujú sa, len keď sú routy zapnuté, za minútu:

| Limiter | Predvolené | Kľúč | Routy |
| --- | --- | --- | --- |
| `events-api` | `routes.rate_limits.api = 120` | používateľ, inak IP | Každá routa (v routes.middleware). |
| `events-rsvp` | `rate_limits.rsvp = 20` | používateľ (alebo IP) a podujatie | PUT / DELETE /rsvp, prijatie / odmietnutie pozvánky. |
| `events-check-in` | `rate_limits.check_in = 300` | používateľ | POST /check-ins — skenery pri vstupe pracujú v nárazoch. |
| `events-feeds` | `rate_limits.feeds = 60` | `IP` | feed.ics, calendar.ics |

## Prejavte lásku k open source

Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.

[Ďalšie spôsoby podpory vrátane kryptomien](https://roundly-consulting.com/sk/podporte-nas.md)

Odoslaním daru súhlasíte s našimi [podmienkami prijímania darov](https://roundly-consulting.com/sk/podmienky-darov.md).

[Podporte našu open-source prácu (otvorí sa v novej karte)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Pridajte sa k nám na Patreone (otvorí sa v novej karte)](https://www.patreon.com/cw/roundly)

## Chcete to zabudovať do svojho produktu?

Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.

[Ponuka do 48 hodín](https://roundly-consulting.com/sk/kontakt.md) [Prezrieť všetky balíky](https://roundly-consulting.com/sk/open-source/navody/events-for-laravel.md)
