Vývoj API
Vývoj API
Section titled “Vývoj API”Sencai backend je postavená na Strapi v5. Tento průvodce vás naučí, jak přidávat nové API endpointy a content-types.
Struktura Strapi projektu
Section titled “Struktura Strapi projektu”sencai.space/├── src/│ ├── api/ # Content-types a logika│ │ ├── [content-type]/│ │ │ ├── controllers/ # Obslužné programy│ │ │ ├── services/ # Business logic│ │ │ └── routes/ # Route definice│ │ └── middleware/ # Globální middleware│ ├── policies/ # Globální RBAC policies (prefix global::)│ ├── lifecycles/ # Lifecycle hooks (user-kc-sync.ts)│ └── config/ # Konfigurace├── database/│ └── migrations/ # Databázové migrace└── types/ # TypeScript typyPřidání nového content-type
Section titled “Přidání nového content-type”Přes Strapi Admin UI (jednodušeji)
Section titled “Přes Strapi Admin UI (jednodušeji)”-
Přihlaste se do Strapi Admin:
http://localhost:1337/admin -
V levém menu klikněte na Content-Type Builder
-
Klikněte na Create new collection type
-
Vyplňte údaje:
- Display name — název typu (např. “Organisation”)
- API ID — identifikátor (auto-generated, např. “organisation”)
- Visible in API — zapnuto
-
Klikněte na Continue
-
Přidejte pole:
- Klikněte na Add another field pro každé pole
- Vyberte typ (Text, Email, Rich text, Relation, atd.)
- Nakonfigurujte (povinné, default hodnota, atd.)
-
Klikněte na Save
-
Restartujte Strapi (změny se uloží do
database/migrations/)
Přidání nového controlleru
Section titled “Přidání nového controlleru”Controllers obsahují obslužný kód pro API endpointy.
Příklad: Custom action
Section titled “Příklad: Custom action”import type { Core } from '@strapi/strapi';
const controller = ({ strapi }: { strapi: Core.Strapi }) => ({ // Standard CRUD jsou auto-generované, tady vlastní akce
async getMembers(ctx) { const { id } = ctx.params;
// Validace if (!id) { return ctx.badRequest('ID je povinné'); }
try { const org = await strapi.entityService.findOne( 'api::organisation.organisation', id, { populate: ['members'], } );
ctx.send({ members: org.members || [], }); } catch (error) { ctx.internalServerError('Chyba při načítání členů'); } },});
export default controller;entityService vs. Document Service API
Section titled “entityService vs. Document Service API”Příklad výše používá legacy strapi.entityService API, které ve Strapi v5 stále funguje.
Novější kód by měl preferovat Document Service API (strapi.documents(uid)) — nativní
způsob čtení/zápisu obsahu ve Strapi v5:
const org = await strapi.documents('api::organisation.organisation').findOne({ documentId, populate: ['members'],});Pozor na rozdíl v identifikátoru: entityService bere numerické id, zatímco Document Service
API bere string documentId (stabilní, nerostoucí identifikátor Strapi v5). Route parametry
jsou v tomto repozitáři zpravidla documentId string — viz RBAC policies níže, které řeší
params.id || params.documentId právě z tohoto důvodu.
Definování routů
Section titled “Definování routů”export default { routes: [ { method: 'GET', path: '/organisations/:id/members', handler: 'organisations.getMembers', config: { policies: ['global::require-auth', 'global::is-organisation-member'], auth: false, }, }, ],};Policies (RBAC)
Section titled “Policies (RBAC)”Skutečné RBAC policies žijí jako globální policies v src/policies/ (ne per-content-type)
a odkazují se s prefixem global::. Hlavní z nich:
| Soubor | Účel | Config |
|---|---|---|
is-organisation-member.ts | Uživatel má jakékoliv členství (libovolnou roli) v organizaci | žádný |
is-organisation-role.ts | Role uživatele dosahuje minimálního prahu | { minRole: 'owner' | 'admin' | 'member' | 'viewer' } |
is-organisation-scoped.ts | Rozřeší a vynutí organization scope na požadavku | — |
is-organisation-scoped-or-cross-tenant.ts | Totéž, ale povoluje i cross-tenant přístup odvozený z relationship (MSP/agentura) | — |
Hierarchie rolí (od nejvyšší): owner (4) > admin (3) > member (2) > viewer (1).
Aplikace policies v routách:
{ config: { policies: [ 'global::require-auth', { name: 'global::is-organisation-role', config: { minRole: 'admin' } }, ], },}Nebo pro prostou kontrolu členství:
policies: ['global::require-auth', 'global::is-organisation-member'],Lifecycle hooks
Section titled “Lifecycle hooks”Hooks se spouští při CRUD akcích.
Příklad: Sync do Keycloaku
Section titled “Příklad: Sync do Keycloaku”// Automaticky se spustí v sencai.space/src/lifecycles/user-kc-sync.ts// při jakékoliv změně User entity
export default { definition: { kind: 'collectionType', collectionName: 'users', }, async afterCreate(event) { const { result } = event;
// Sync do Keycloaku await syncUserToKeycloak(result); },};Webhooky pro async events
Section titled “Webhooky pro async events”Strapi webhooky jsou integrovány s RabbitMQ přes Webhook Publisher.
// Když vytvoříte nový Instance, Webhook Publisher jej zachytí// a pošle do RabbitMQ jako cloud.events.instance.create
// auth-service-consumer jej poslouchá// a může reagovat asynchronněInput validace
Section titled “Input validace”Validujte vstupy v controlleru:
async createOrganisation(ctx) { const { name, description } = ctx.request.body;
// Validace if (!name || name.trim().length === 0) { return ctx.badRequest('Název je povinný'); }
if (name.length > 100) { return ctx.badRequest('Název je příliš dlouhý (max 100 znaků)'); }
const org = await strapi.entityService.create( 'api::organisation.organisation', { data: { name, description }, } );
ctx.created(org);}Autentizace a autorizace
Section titled “Autentizace a autorizace”Keycloak JWT
Section titled “Keycloak JWT”Všechny request obsahují JWT token z Keycloaku:
// Kontrola uživatele v controlleruasync getOrganisations(ctx) { const user = ctx.state.user;
if (!user) { return ctx.unauthorized('Přihlášení je povinné'); }
// `user` obsahuje KC subjekt, email, atd. console.log(user.email, user.sub); // KC UUID}Keycloak → Strapi sync
Section titled “Keycloak → Strapi sync”Při prvním přihlášení se uživatel sync z Keycloaku:
// Loop guard: runWithoutKcSync() zabraňuje cyklům
export async function syncKcUserToStrapi(kcUser) { return runWithoutKcSync(async () => { // Strapi User update se NEPOSÍLÁ zpět do Keycloaku await strapi.entityService.update('plugin::users-permissions.user', userId, { data: { email: kcUser.email, name: kcUser.firstName }, }); });}Audit logging
Section titled “Audit logging”Každý mutující endpoint na platformě má produkovat audit stopu. Před psaním mutace si projděte
.claude/context/audit-patterns.md, kde je kompletní referenční vzor.
HTTP mutace jsou zachyceny automaticky. Globální middleware global::audit-logger
(src/middlewares/audit-logger.ts) zachytává každý POST/PUT/PATCH/DELETE response se statusem
< 400 a asynchronně publikuje audit event — ve standardním Strapi controlleru není potřeba
volat nic explicitně.
Explicitní volání jsou potřeba v non-HTTP cestách — cron joby, background workery a
lifecycle hooky, které mutují data mimo HTTP request. V takových případech volejte
publishAuditEvent() (nebo ekvivalentní audit.log() helper v non-Strapi službách) přímo:
import { publishAuditEvent, AuditAction } from '@sencai/audit';
await publishAuditEvent({ actor_user_id: `system:${serviceName}`, organisation_id: orgId, action: AuditAction.CLOUD_INSTANCE_PROVISIONED, resource_type: 'cloud-instance', resource_id: String(entity.id), changes: { before: null, after: sanitizeForAudit(entity) }, risk_level: 'high',});Vynucení lintem. ESLint pravidlo sencai/require-audit-log (v eslint-plugin-sencai)
tvrdě shodí npm run lint na jakékoliv mutující funkci (matchované podle jména: create,
update, delete, revoke, …), která nemá audit volání. Pokud je funkce jen tenký wrapper
nad již-auditovaným voláním o úroveň níž (domain boundary), místo přidání redundantního audit
volání ji anotujte:
/** @no-audit { reason: "low-level HTTP wrapper; audit emitted at domain boundary" } */sencai.space už má plně nastavené testování — Jest v29 + ts-jest, s 89 existujícími testovacími
soubory. Není potřeba instalovat Jest znovu od nuly.
cd sencai.spacenpm test # spustí všechny testy jednounpm run test:watch # watch modenpm run test:coverage # vygeneruje coverage reportZnovu použijte existující helpery místo psaní mocků od nuly:
| Helper | Účel |
|---|---|
tests/helpers/auth.helper.ts | Vytvoření JWT, mock KC odpovědi |
tests/helpers/strapi.helper.ts | Mock Strapi kontext, db.query, lifecycle mocky |
tests/helpers/fixtures.helper.ts | Fixture factories pro User, organisation, member |
Konfigurace je v jest.config.ts (ts-jest preset, SQLite-style in-memory setup přes
tests/setup.ts, sekvenční běh s maxWorkers: 1 kvůli stabilitě DB).
Rate limiting
Section titled “Rate limiting”API Manager má vestavené rate-limiting:
# 100 requests per 15 minutes per IPGET /api/organisationsX-RateLimit-Limit: 100X-RateLimit-Remaining: 99X-RateLimit-Reset: 1234567890Error handling
Section titled “Error handling”try { const org = await strapi.entityService.findOne(...); ctx.send(org);} catch (error) { if (error.message.includes('not found')) { ctx.notFound('Organizace nenalezena'); } else { ctx.internalServerError('Interní chyba serveru'); }}Best practices
Section titled “Best practices”- Validujte vstup — nikdy nedůvěřujte user datům
- Logujte důležité akce — přes audit pattern výše
- Používejte policies — pro RBAC
- Vyhnete se N+1 queryům — populujte relace v
entityService.findOne() - Testujte — minimálně CRUD operace, s použitím existujících helperů
- Dokumentujte — přidejte JSDoc pro custom akce
Přehled endpointů
Section titled “Přehled endpointů”Tato stránka není vyčerpávající index — příklady níže pokrývají nejčastěji integrované
endpointy. Kompletní, generovanou referenci všech operací /api/v1/* (všech ~213 Strapi
content-types plus ručně zdokumentovaných custom routes, včetně request/response schémat)
najdete v plné API Reference (zatím jen v angličtině — generátor
zatím nepodporuje lokalizaci), generované z
sencai.space/openapi/sencai-platform.public.v1.yaml (F4.DEVPORTAL.01/.03). Nejdřív si
přečtěte Verzování API — pokrývá jmenný prostor /api/v1/ a
deprecation politiku, se kterou reference počítá.
Stavíte externí integraci v TypeScriptu, Pythonu nebo Go místo přímého volání REST API? Podívejte se na generované návody SDK — Začínáme (F4.DEVPORTAL.02/.04) — instalace, inicializace klienta, kompletní příklad list + create a konvence zpracování chyb per jazyk.
Auth hlavička
Section titled “Auth hlavička”Všechny API požadavky vyžadují JWT token v hlavičce Authorization:
Authorization: Bearer YOUR_JWT_TOKENUživatelé
Section titled “Uživatelé”GET /api/users— Seznam všech uživatelůGET /api/users/{id}— Podrobnosti o uživateliPUT /api/users/{id}— Aktualizace uživatele (standardní users-permissions update)PUT /api/users/me/profile— Aktualizace vlastního profilu přihlášeného uživatele (whitelisted pole:name,surname,jobTitle,street,city,zip_code,country) — preferovaný endpoint pro self-service editaci profilu, obsluhovanýapi::auth.auth.updateMyProfile
Organizace
Section titled “Organizace”GET /api/organisations— Seznam organizacíPOST /api/organisations— Vytvoření organizacePUT /api/organisations/{id}— Aktualizace organizace (vyžaduje roliadmin+)POST /api/organisations/:id/invite— Pozvání uživatele emailem (jen owner/admin)
Všechny endpointy výše jsou dostupné také pod verzovaným jmenným prostorem /api/v1/
(např. /api/v1/organisations) — viz Verzování API pro deprecation
politiku a migrační průvodce.
Ostatní API platformy
Section titled “Ostatní API platformy”Strapi backend není jediná API plocha na platformě — a generovaná API Reference výše pokrývá jen Strapi. Ostatní služby vystavují vlastní REST API, které jinde v této dokumentaci nejsou indexované:
- API Manager (port 3200) — vydává a rotuje Strapi API tokeny ostatním microservices;
GET /api/token/:service(viz Verzování API pro jeho verzovaný alias/v1/token/:service). - GraphQL Gateway (port 4000) — sjednocené GraphQL schéma nad core Strapi entitami, Apollo
Server 5 na Fastify; resolvery delegují na podkladové Strapi REST endpointy. Vyžaduje
GRAPHQL_ENABLED=truea Keycloak Bearer JWT. Kompletní schéma, ukázkové query, auth/org-scoping a limity depth/complexity/alias najdete na dedikované stránce GraphQL Gateway. - LLM Service (port 4430) — provider-agnostic LLM fasáda, vystavuje
/chata/completes Keycloak JWT auth a per-org budget enforcement. - Billing Adapter (port 3600) — integrace Lago/Stripe billing; kromě požadavků běží i sync/webhook joby.
- Agent Gateway (port 4400) — HTTPS/WebSocket fleet C2 brána pro on-host Sencai agenty (enrollment, heartbeat, dispatch runbooků).
Další čtení
Section titled “Další čtení”- Root CLAUDE.md — Keycloak sync architektura
- Strapi v5 docs —
https://docs.strapi.io - Webhook Publisher —
webhook-publisher/src/routes/