← EP21 Internal Docs Podpora21.cz
Solution Design · DS-0003 · v0.1.0

Multi-tenant Architecture

draft Authors: david.sorf, claude-sonnet-4-6 Created: 2026-04-10 Updated: 2026-04-10
tenant multi-tenant architecture db saas
Podpora21 je SaaS helpdesk. Každý zákazník (tenant) je plně izolovaná entita — má vlastní uživatele, tickety, kategorie, SLA politiky a klienty. Izolace je zajištěna row-level přes tenant_id na všech tenant-owned tabulkách. V první fázi existuje jeden tenant (EP21). Přidání dalšího tenanta nevyžaduje schema změny — pouze nový řádek v core_tenant a nové uživatele s tenant_id.
Related docs:
Related tasks:

Tenant entita

Tenant = zákazník provozující vlastní instanci helpdesku v rámci podpora21 platformy.

Kanonická tabulka: public.core_tenant (existuje, zděděna z core migrace)

Seed záznam pro EP21:

name: 'ep21'

display_name: 'EP21 s.r.o.'

Tenant se resolví při každém requestu přes TenantResolverService — v fázi 1 staticky z konfigurace (TENANT_KEY=ep21 v .env). V fázi 2 z URL prefixu nebo subdomény.

Scope tenanta — co je tenant-owned

Tabulkatenant_id?Poznámka

podpora21.user

ANO

Uživatelé (agenti i klienti) patří tenantu

podpora21.support_ticket

ANO

Každý ticket patří tenantu

podpora21.support_client

ANO

Klientský záznam je per-tenant

podpora21.support_category

ANO

Kategorie definuje tenant

podpora21.support_sla_policy

ANO

SLA politiky per-tenant

podpora21.support_ticket_comment

NE

Izolováno přes ticket FK

podpora21.support_ticket_history

NE

Izolováno přes ticket FK

podpora21.rbac_role

NE

Sdílené globální role (fáze 1)

podpora21.rbac_person_role

NE

Izolováno přes user FK

public.core_tenant

N/A

Kořenová tabulka tenantů

TenantResolverService

Singleton service registrovaná v DI. Resolví aktuálního tenanta pro každý request.

Fáze 1 (implementováno v TASK-0344):

Fáze 2 (plánováno):

Použití v Presenterech: @inject TenantResolverService, $this->tenantResolver->get()->getId()

Použití v Modelech: tenant_id se předává explicitně do každé operace — bezpečnější než implicitní.

DB migrační plán — přidání tenant_id

MigraceCoPhinx configZávislost

20260410020000_tenant_seed_ep21

INSERT do core_tenant (name='ep21')

phinx-podpora21.php

žádná

20260410030000_user_tenant_id

ADD tenant_id NOT NULL DEFAULT ep21.id na podpora21.user

phinx-podpora21.php

tenant seed

20260410040000_helpdesk_tenant_id

ADD tenant_id na support_ticket, support_client, support_category, support_sla_policy

phinx-podpora21.php

user tenant_id

20260410050000_helpdesk_tenant_rls (opt)

PostgreSQL RLS policies

phinx-podpora21.php

helpdesk tenant_id

Query pattern — tenant isolation

Všechny queries na tenant-owned tabulky MUSÍ obsahovat WHERE tenant_id = :tenant_id.

Vzor v TicketModel (po migraci):

$this->database->table(self::TAB_NAME)

->where('tenant_id', $this->tenantId)

->...

Vzor v raw SQL:

SELECT * FROM podpora21.support_ticket

WHERE tenant_id = ? AND ...

Bezpečnostní pravidlo: žádná query na tenant-owned tabulku bez tenant_id filtru.

V Quick Ticket formuláři (anonymní): tenant_id z TenantResolverService — klient nemusí znát tenanta.

Typy uživatelů a tenant příslušnost

Typuser_typetenant_idPřístup

Agent / operátor

agent

ep21

/helpdesk — plný helpdesk backend

System admin

admin

ep21

/helpdesk + /rbac-admin + /tek

Klient (zákazník)

client

ep21

/portal — vlastní tickety only

Anonymní

/ a /portal (read + quick ticket)

Implementační plán

FázeCoTaskBlokuje

1a — DB

Tenant seed + tenant_id migrace (4 migrace)

TASK-0343

vše ostatní

1b — Service

TenantResolverService (statický z .env TENANT_KEY)

TASK-0344

Quick Ticket, Model update

1c — Model

TicketModel + CategoryModel + ClientModel přidají tenant_id do queries

TASK-0344

Quick Ticket form

1d — Portal

Landing page + /portal info + Quick Ticket form

TASK-0342

TASK-0343 + TASK-0344

2a — Client auth

Klientský login, self-registrace, client user_type

budoucí

TASK-0342 done

2b — Multi-tenant routing

Subdomain resolver nebo URL prefix

budoucí

ADR-0002 fáze 2 trigger

Omezení a plánovaná vylepšení

Changelog

VersionDateAuthorNote
0.1.02026-04-10david.sorf + claude-sonnet-4-6

Initial draft — row-level tenant isolation, migrační plán, TenantResolverService, query pattern.