NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky

Routy, form requesty, controllery, resources, filtre zoznamu cez query-builder a limity požiadaviek — vypnuté, kým ich nezapnete:

TAGS_ROUTES=on
  • Vypnuté, kým ho nezapnete: nenastavený prepínač — chýbajúci v publikovanej konfigurácii, ktorej pole routes prepisuje len niektoré kľúče, null alebo prázdny — ho nechá vypnuté.
  • Prefix tags.routes.prefix (api), názvy rout tags.*, middleware tags.routes.middleware (['api', 'auth', 'throttle:tags-api']).
  • {type} je názov typu; {tag} je kľúč vždy, keď zodpovedá typu kľúča, inak slug alebo názov. suggest, popular a reorder sa registrujú pred {tag}, takže štítok s jedným z týchto slugov sa adresuje kľúčom.
  • Ak chcete routu zmeniť, publikujte routes/tags.php (--tag=tags-routes); publikovaná kópia nahradí súbor balíka a stále ju riadi tags.routes.enabled.
  • Každý zápis ide cez manažéra, takže Tags::fake() zaznamená aj volania API.

Aliasy, Sanctum a oprávnenia

API nikdy neprijme názov triedy: rozsahy a modely so štítkami sa pomenúvajú aliasmi, ktoré povolíte. Balík nedefinuje žiadny Gate a nedefinované oprávnenie zamieta — definujte tie, ktoré používate:

// config/tags.php
'scopes' => ['team' => App\Models\Team::class],          // ?scope=team:12
'taggables' => ['concert' => App\Models\Concert::class], // popular?on=concert

'routes' => [
    'middleware' => ['api', 'auth:sanctum', 'throttle:tags-api'],
    // …
],

// AppServiceProvider::boot() — the package defines no gate; an undefined ability denies
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Gate;

Gate::define('tags.manage', fn (User $user, string $type, ?Model $scope = null) => $scope === null
    ? $user->is_admin                                     // global tags: admins only
    : $user->team_id === $scope->getKey() && $user->is_team_admin);
Gate::define('tags.scope', fn (User $user, Model $scope) => $user->team_id === $scope->getKey());
  • manage (tags.manage) sa pýta pred každým zápisom s názvom typu a cieľovým rozsahom — null pri globálnom zápise — takže správcovia tenanta môžu spravovať vlastné štítky a nikdy nie globálne. Rozhoduje aj o tom, kto vidí súkromné typy a — vždy v rozsahu daného štítku, takže globálny štítok si pýta globálnu správu bez ohľadu na rozsah, ktorý požiadavka číta — usage_count, preklady a filter[trashed]; kontrola beží raz na typ a rozsah v rámci požiadavky.
  • scope (tags.scope) sa pýta s modelom rozsahu vždy, keď požiadavka pošle ?scope=. Zamietnutý rozsah odpovie 422 unknown_alias presne ako rozsah, ktorý neexistuje, takže odkaz neprezradí id žiadneho tenanta.
  • view (predvolene null) sa pýta pred čítaním typu a jeho štítkov s názvom typu a rozsahom; bez neho stačí na čítanie middleware routy.
  • is_admin, is_team_admin a team_id vyššie zastupujú vaše vlastné stĺpce či kontroly rolí; funguje aj balík oprávnení, ktorý ich udeľuje cez Gate::before.

Routy

Metóda a cestaNázovOprávneniePoznámky
GET tag-typestags.types.index—Typy, ktoré volajúci smie vidieť — súkromné len pre ich správcov. Pri zozname typov sa view nepýta.
GET tag-types/{type}tags.types.showviewJeden typ.
GET tags/{type}tags.indexviewZoznam — filtre, radenie, stránkovanie — alebo ?tree=1 pre vnorené uzly.
POST tags/{type}tags.storemanageVytvorí v rozsahu požiadavky; 201.
GET tags/{type}/suggesttags.suggestview?q= (1–64 znakov) a limit; navyše throttle:tags-suggest.
GET tags/{type}/populartags.popularview?limit a on=alias z tags.taggables.
POST tags/{type}/reordertags.reordermanage{parent, order}
GET tags/{type}/{tag}tags.showviewJeden štítok s predkami.
PATCH tags/{type}/{tag}tags.updatemanageZmenia sa len poslané polia.
DELETE tags/{type}/{tag}tags.destroymanage?children=refuse (predvolené), reparent alebo delete; vráti zmazaný štítok.
POST tags/{type}/{tag}/movetags.movemanage{parent: key|null, position}
POST tags/{type}/{tag}/restoretags.restoremanagePodľa kľúča alebo podľa slugu či názvu zmazaného štítku presne z rozsahu požiadavky.
GET    /api/tag-types
GET    /api/tags/genre?tree=1
GET    /api/tags/label?scope=team:12&filter[search]=pri&sort=-usage_count&per_page=50
GET    /api/tags/label/suggest?q=liv&limit=5
GET    /api/tags/label/popular?on=concert&limit=10
GET    /api/tags/genre/jazz
POST   /api/tags/genre                       {"name": {"en": "Bebop"}, "parent": "2", "color": "#f24e29"}
PATCH  /api/tags/genre/bebop                 {"name": {"en": "Bebop jazz"}, "description": {"sk": null}, "color": null}
POST   /api/tags/genre/bebop/move            {"parent": null, "position": 0}
POST   /api/tags/genre/reorder               {"parent": "1", "order": ["4", "2", "3"]}
DELETE /api/tags/genre/music?children=reparent
POST   /api/tags/genre/rock/restore

Telá požiadaviek

EndpointTelo / query
POST tags/{type}name (mapa jazykov, aspoň jeden jazyk, každý ≤ names.max_length), description (mapa jazykov), slug (mapa jazykov — len kurátorované typy), color, icon, parent (kľúč toho istého typu presne v rozsahu požiadavky; null žiada koreň), position (0–2147483647, číslo alebo reťazec číslic). Názov, ktorý drží zmazaný štítok, tento štítok vráti s atribútmi z tela — tam, kde bol, pokiaľ telo nemá kľúč parent.
PATCH tags/{type}/{tag}name, description (null vymaže všetky jazyky; null alebo prázdna položka vymaže daný jazyk), slug, color (null vymaže), icon (null vymaže) — zmení sa len poslané; mapy sa zlúčia po jazykoch.
POST …/{tag}/moveparent — povinný, kľúč alebo null pre koreň; position ako pri vytvorení.
POST tags/{type}/reorderparent (kľúč alebo null pre korene), order — povinné, pole s najviac 1000 kľúčmi.
DELETE tags/{type}/{tag}?children=refuse|reparent|delete
každý endpoint?scope=alias:kľúč (alias z tags.scopes — čítanie vidí rozsah a globálne štítky, zápisy idú do rozsahu) a ?scope_only=1 (čítanie vidí len štítky rozsahu).

Zoznam štítkov

ParameterVýznam
filter[search]Názvy, ktoré obsahujú výraz (escapovaný), v ktoromkoľvek podporovanom jazyku. Vždy jeden výraz — čiarka zostáva jeho súčasťou; prázdny výraz nefiltruje nič.
filter[parent]Kľúč rodiča alebo root.
filter[depth]Číslo úrovne.
filter[ids]Kľúče oddelené čiarkou; chybné sa preskočia.
filter[slug]Slugy oddelené čiarkou, v ktoromkoľvek jazyku reťaze.
filter[trashed]Len pre správcov (pre ostatných neznámy filter, 400): with alebo only — a len zmazané štítky rozsahov, ktoré volajúci spravuje, takže správca tímu, ktorý nesmie spravovať globálne štítky, zmazané globálne štítky nikdy neuvidí.
sortposition (predvolené), name (v aktuálnom jazyku), usage_count, created_at; - pre zostupne, oddelené čiarkou.
per_page, pageNajviac tags.routes.max_per_page; predvolene 20.
tree=1Vnorené uzly hierarchického typu (pri plochom type prázdny zoznam); viac než max_tree_nodes odpovie 422 tree_too_large.
include=translationsVšetky jazyky name, description a slug — ak volajúci spravuje typ v rozsahu požiadavky, a len pri štítkoch, ktorých rozsah spravuje tiež.

Každý zoznam — index, suggest, popular aj reorder — načíta vlastníkov rozsahov svojich štítkov jedným dotazom, takže stránka stojí rovnaký počet dotazov bez ohľadu na jej veľkosť.

Resources

ResourceKaždý, kto smie čítaťSprávcovia navyše
TagResourceid, type (názov typu), name, slug a description v aktuálnom jazyku, color, icon, parent_id, depth, position, scope (cez tags.resources.scope; pri globálnom štítku null), ancestors (ak sú načítané — endpoint show ich načíta), deleted_at (ak je zmazaný), created_at, updated_at.usage_count — ktorý pri globálnom štítku sčíta priradenia všetkých tenantov — a translations s ?include=translations, pre správcov rozsahu daného štítku: globálny štítok si pýta globálnu správu bez ohľadu na rozsah, ktorý požiadavka číta.
TagSummaryResourceid, type, name, slug, color, icon, depth — plus ancestors, ak sú načítané. Bez počtu použití, rozsahu, rodiča a časových pečiatok, takže bez Gate kontroly, vzťahu aj dotazu.—
TagNodeResourceSúhrn plus parent_id, position a children.—
TagTypeResourcename, label (preložený pri vykreslení), hierarchical, max_depth, max, curated.—
TagScopeResource{id, type} — kľúč a krátky názov triedy vlastníka (Team), nikdy celý názov triedy.—

GET /api/tags/genre/jazz pre čitateľa:

{
  "data": {
    "id": 2,
    "type": "genre",
    "name": "Jazz",
    "slug": "jazz",
    "description": null,
    "color": null,
    "icon": "sax",
    "parent_id": 1,
    "depth": 2,
    "position": 0,
    "scope": null,
    "ancestors": [
      { "id": 1, "type": "genre", "name": "Music", "slug": "music", "color": "#f24e29", "icon": null, "depth": 1 }
    ],
    "created_at": "2026-10-19T09:30:00+00:00",
    "updated_at": "2026-10-19T09:30:00+00:00"
  }
}

Označenie tímu v zozname pre správcu tohto tímu s ?include=translations — vlastník sa vykreslí cez TagScopeResource ako kľúč a krátky názov triedy, nikdy nie celý názov triedy, a scope_type ani scope_key server nikdy neopustia:

{
  "id": 31,
  "type": "label",
  "name": "Printer",
  "slug": "printer",
  "description": "Office printers.",
  "color": null,
  "icon": null,
  "parent_id": null,
  "depth": 1,
  "position": 0,
  "usage_count": 14,
  "scope": { "id": 12, "type": "Team" },
  "translations": {
    "name": { "en": "Printer", "sk": "Tlačiareň" },
    "description": { "en": "Office printers.", "sk": "Kancelárske tlačiarne." },
    "slug": { "en": "printer", "sk": "tlaciaren" }
  },
  "created_at": "2026-10-19T09:30:00+00:00",
  "updated_at": "2026-10-19T09:30:00+00:00"
}

GET /api/tag-types:

{
  "data": [
    { "name": "genre", "label": "Genre", "hierarchical": true, "max_depth": 5, "max": 1, "curated": true },
    { "name": "label", "label": "Label", "hierarchical": false, "max_depth": null, "max": 5, "curated": false }
  ]
}

GET /api/tags/genre?tree=1:

{
  "data": [
    {
      "id": 1, "type": "genre", "name": "Music", "slug": "music", "color": "#f24e29", "icon": null, "depth": 1,
      "parent_id": null, "position": 0,
      "children": [
        {
          "id": 2, "type": "genre", "name": "Jazz", "slug": "jazz", "color": null, "icon": "sax", "depth": 2,
          "parent_id": 1, "position": 0,
          "children": []
        }
      ]
    }
  ]
}

Vlastný tvar vlastníka

Nastavte tags.resources.scope na vlastný JsonResource, ak chcete vykresliť meno alebo čokoľvek iné, čo vaše API potrebuje; dostane model rozsahu:

// config/tags.php
'resources' => [
    'scope' => App\Http\Resources\TagOwnerResource::class,
],

// app/Http/Resources/TagOwnerResource.php — receives the scope model
final class TagOwnerResource extends JsonResource
{
    public function toArray(Request $request): array
    {
        return [
            'id' => $this->resource->getKey(),
            'name' => $this->resource->name,
        ];
    }
}

Súkromné typy

Typ deklarovaný cez private() je neviditeľný pre každého, kto ho nesmie spravovať: chýba v /tag-types a každý endpoint pod ním odpovie 404 unknown_tag_type, presne ako typ, ktorý neexistuje.

Limity požiadaviek

Definujú sa len pri zapnutých routách: tags-api povolí tags.routes.rate_limit (120) požiadaviek za minútu na prihláseného používateľa, inak na IP; endpoint na našepkávanie pridáva tags-suggest s tags.routes.suggest_rate_limit (120).

Chyby

Chyby balíka sa mapujú v jeho vlastných controlleroch — nikdy vo vašom exception handleri — na {message, code} v jazyku požiadavky. Segment cesty, ktorý neoznačuje nič, čo volajúci smie vidieť, je 404, odkaz v tele 422 a štítok iného rozsahu odpovie presne ako chýbajúci:

{ "message": "Unknown tag type [nope].", "code": "unknown_tag_type" }
{ "message": "A tag with the slug [jazz] in [en] already exists.", "code": "duplicate_tag" }
StavcodeKedy
404unknown_tag_typeSegment {type} neoznačuje žiadny typ, ktorý volajúci smie vidieť — súkromný typ je pre nesprávcov neznámy.
404tag_not_foundSegment {tag} neoznačuje žiadny štítok v čitateľných rozsahoch (pri zápisoch presne v rozsahu požiadavky). Štítok iného rozsahu odpovie úplne rovnako.
422tag_not_foundKľúč parent alebo order v tele, ktorý chýba alebo je iného typu či rozsahu.
422unknown_aliasAlias ?scope= či on=, ktorý povolené zoznamy nepoznajú, názov triedy, chybný kľúč, kľúč bez záznamu — alebo rozsah, ktorý volajúci nesmie použiť: zamietnuté oprávnenie scope odpovie úplne rovnako.
422invalid_tag, invalid_hierarchy, tag_type_mismatchOdmietnutý atribút (farba, slug, jazyk…); cyklus, príliš hlboký strom či zmazaný rodič; štítok iného typu.
422tree_too_large?tree=1 nad tags.routes.max_tree_nodes.
409duplicate_tag, tag_has_childrenSlug, ktorý drží iný štítok typu a rozsahu (aj zmazaný); zmazanie, ktoré by osirelo živé deti.
403—Gate manage alebo view zamietol — nedefinované oprávnenie zamieta.
400—Neznámy filter či radenie pri predvolenom mode.unknown_filter / unknown_sort = reject v query-builder; ignore ho namiesto toho zahodí.
422 / 429—Validačné chyby Laravelu (názvy polí z tags::validation.attributes); limitery požiadaviek.

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

Odoslaním daru súhlasíte s našimi podmienkami prijímania darov.

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.