Firmy a prava: tenance napric portalem
Portal nemel zadnou tenanci. Kterykoliv prihlaseny uzivatel videl vsechny tickety vsech firem i cely seznam resitelu, requireRole se nikde nevolal. Tenant je hranice viditelnosti, tenantId na ticketu, resiteli i automatizaci. Uzivatel muze patrit do vic firem, v kazde s jinou roli. Pristup napric firmami je zvlast jako platformAdmin. Tri pohledy na tickety: all, tenant, mine. Admin mezi nimi prepina vcetne vyberu firmy. O pravech rozhoduje jedine data/access.ts, klient si nic nedovozuje a bere je z GET /api/dashboard/access. Filtr na firmu je v ulozistich povinny argument, takze zapomenuty filtr neznamena vse, ale nezkompiluje se. Cizi firma vraci 403 nebo 404, nikdy tise zuzeny vysledek. Prirazeni jen v ramci firmy. Prehazovat praci mezi lidmi smi jen admin, agent si smi vzit ticket na sebe. Zmena prihlasovani: ucet klient@firma.cz zanikl, demo ucty jsou nove. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,8 @@ React aplikaci ze slozky `dist/public`.
|
||||
| Obsah ticketu a sablony | hotovo | `{{parametr}}` ze spoustece do poli akce |
|
||||
| Vystupy kroku a predvalidace | hotovo | podminka se umi zeptat, co vratil predchozi krok |
|
||||
| Kanaly WhatsApp, FB, Instagram | hotovo | vcetne vzorovych automatizaci na prijem |
|
||||
| Firmy a prava | hotovo | tri pohledy, uzivatel muze byt ve vic firmach |
|
||||
| Sprava clenstvi z portalu | chybi | memberships jdou zmenit jen v kodu |
|
||||
| Bugs a wishes | chybi | vyvojarska agenda, samostatna evidence vedle ticketu |
|
||||
| Beh automatizaci | chybi | ulozeny strom se nevykonava, neni runtime |
|
||||
| Databaze | chybi | data jsou v pameti, restart je vrati na vychozi stav |
|
||||
|
||||
@@ -41,6 +41,8 @@ image jen `dist`, takze staci jedna slozka.
|
||||
| `src/routes/contact.ts` | poptavkovy formular z webu |
|
||||
| `src/data/ticketStore.ts` | tickety, jejich resitele, log prubehu, prehled vytizeni |
|
||||
| `src/data/people.ts` | resitele ticketu - oddeleni od uzivatelu portalu |
|
||||
| `src/data/tenants.ts` | firmy, ktere portal pouzivaji |
|
||||
| `src/data/access.ts` | kdo co vidi - jedno misto pro cely portal |
|
||||
| `src/data/incidentStore.ts` | incidenty vcetne zmen a udalosti |
|
||||
| `src/data/automationStore.ts` | automatizace, strom akci, tokeny webhooku |
|
||||
| `src/data/connectors.ts` | katalog konektoru, jejich spousteču a akci |
|
||||
@@ -87,6 +89,14 @@ Kod zustava anglicky.
|
||||
**Data v pameti.** Vedome zjednoduseni prototypu. Uloziste jsou oddelena od rout,
|
||||
takze napojeni na databazi znamena prepsat soubory v `src/data/`, ne endpointy.
|
||||
|
||||
**Filtr na firmu je povinny argument.** `listTickets`, `listPeople`
|
||||
i `listAutomations` vyzaduji `tenantIds`. Zapomenuty filtr tak neznamena "vse",
|
||||
ale nezkompiluje se. Podrobnosti v [07-firmy-a-prava.md](07-firmy-a-prava.md).
|
||||
|
||||
**Prava se nikdy nedovozuji na klientovi.** Server vraci `GET /api/dashboard/access`
|
||||
s tim, co uzivatel smi. Kdyby si to klient pocital sam, pocitalo by se to na dvou
|
||||
mistech a jednou se to rozejde.
|
||||
|
||||
**Resitel neni uzivatel.** Uzivatel se prihlasuje do portalu, resitel ma u sebe
|
||||
tickety. Technik muze mit tickety a ucet nikdy nemit. Spojka je e-mail,
|
||||
podrobnosti v [06-tickety.md](06-tickety.md).
|
||||
|
||||
+15
-4
@@ -22,6 +22,7 @@ Vyzaduji `Authorization: Bearer <token>`:
|
||||
| ------ | --------------------------------------------------- |
|
||||
| GET | `/api/auth/me` |
|
||||
| POST | `/api/auth/logout` |
|
||||
| GET | `/api/dashboard/access` |
|
||||
| GET | `/api/dashboard/summary` |
|
||||
| GET | `/api/dashboard/people` |
|
||||
| GET | `/api/dashboard/tickets` |
|
||||
@@ -113,14 +114,24 @@ curl -X POST https://services.csbot.cz/apps/<app-id>/webhook/<token> \
|
||||
Prototyp pozadavek prijme, zvaliduje a zapocita do metrik, ale strom akci
|
||||
nevykona - runtime neexistuje.
|
||||
|
||||
## Firmy a pohledy
|
||||
|
||||
Prava popisuje [07-firmy-a-prava.md](07-firmy-a-prava.md), tady jen API.
|
||||
|
||||
Endpointy dashboardu berou `scope` (`all`, `tenant`, `mine`) a `tenantId`.
|
||||
`GET /api/dashboard/access` rekne, co uzivatel smi, aby to klient nedovozoval.
|
||||
|
||||
Pozadavek na pohled nebo firmu bez opravneni vraci **403 nebo 404**, nikdy tise
|
||||
zuzeny vysledek. Uzivatel nesmi koukat na cizi cisla v domneni, ze jsou spravna.
|
||||
|
||||
## Tickety
|
||||
|
||||
Popis modelu je v [06-tickety.md](06-tickety.md), tady jen to, co se tyka API.
|
||||
|
||||
`GET /api/dashboard/tickets` bere filtry v query: `assignee`, `status`, `channel`.
|
||||
U `assignee` jsou dve zvlastni hodnoty: `me` znamena resitele odpovidajiciho
|
||||
prihlasenemu uzivateli, `unassigned` frontu bez resitele. Neznama hodnota filtru
|
||||
se zaloguje a ignoruje - je lepsi ukazat vic ticketu nez prazdny seznam
|
||||
`GET /api/dashboard/tickets` bere navic filtry `assignee`, `status`, `channel`.
|
||||
U `assignee` je zvlastni hodnota `unassigned` pro frontu bez resitele.
|
||||
U pohledu `mine` se `assignee` ignoruje, pohled je silnejsi. Neznama hodnota
|
||||
filtru se zaloguje a ignoruje - je lepsi ukazat vic ticketu nez prazdny seznam
|
||||
bez vysvetleni.
|
||||
|
||||
Odpoved nese vedle `items` jeste `meId`. Klient podle nej pozna, ktere tickety
|
||||
|
||||
@@ -96,6 +96,24 @@ autorita, kopie na klientovi existuje jen proto, aby UI nenabidlo nesmysl.
|
||||
Dokud spoustec nema zadny parametr, nejde pridat podminka - nebylo by podle ceho
|
||||
se rozhodovat. Dialog to vysvetli.
|
||||
|
||||
## Sirka karet pri zanoreni
|
||||
|
||||
Vetve ANO a NE jsou vedle sebe jen tehdy, kdyz je na to v dane karte misto.
|
||||
Rozhoduje **sirka karty, ne sirka okna** - pouzivaji se container queries
|
||||
(`@container` a `@2xl:grid-cols-2` v `FlowCanvas.tsx`).
|
||||
|
||||
Duvod: kazde zanoreni pulí dostupnou sirku. S beznym `lg:grid-cols-2` vypadal
|
||||
strom na sirokem monitoru dobre v prvni urovni a ve treti uz mel karty siroke
|
||||
par desitek pixelu, takze se popisy lamaly po jednom slove. Container query se
|
||||
od urcite hloubky sama prepne na vetve pod sebou.
|
||||
|
||||
Ze stejneho duvodu se v uzke karte skryva popis akce (`hidden @xs:block`),
|
||||
zmensuje ikona a ovladaci tlacitka se skladaji do sloupce. Nazev kroku
|
||||
a nastaveni poli zustavaji vzdy videt - to je to podstatne.
|
||||
|
||||
**Pri uprave stromu nepouzivat `sm:` / `lg:` na veci uvnitr karet.** Reaguji
|
||||
na okno a v zanoreni lzou.
|
||||
|
||||
## Co je v kterem kroku videt
|
||||
|
||||
Krok vidi parametry spoustece **plus vystupy vsech kroku pred nim**. Akce muze
|
||||
|
||||
@@ -0,0 +1,124 @@
|
||||
# 07 - Firmy a prava
|
||||
|
||||
## Dve ruzne "firmy"
|
||||
|
||||
Snadno se pletou, proto hned na zacatku:
|
||||
|
||||
- **Tenant** je firma, ktera portal pouziva. Ma v nem svuj tym, svoje tickety
|
||||
a svoje automatizace. Je to hranice viditelnosti.
|
||||
- **`ticket.customer`** je zakaznik toho tenanta, tedy kdo pozadavek poslal.
|
||||
S pravy nema nic spolecneho.
|
||||
|
||||
Tenanti jsou v `src/data/tenants.ts`.
|
||||
|
||||
## Uzivatel muze byt ve vic firmach
|
||||
|
||||
Proto `memberships`, ne jedno `tenantId`. Typicky externista, ktery dela
|
||||
servicedesk dvema klientum a v kazdem ma jinou roli.
|
||||
|
||||
```ts
|
||||
interface User {
|
||||
id: string;
|
||||
email: string;
|
||||
name: string;
|
||||
platformAdmin: boolean; // vidi napric vsemi firmami
|
||||
memberships: Membership[]; // { tenantId, role }
|
||||
}
|
||||
|
||||
type TenantRole = 'admin' | 'agent';
|
||||
```
|
||||
|
||||
**Role je vzdy az uvnitr firmy.** Pristup napric firmami je zvlast jako
|
||||
`platformAdmin` - to je nase pravo, ne zakaznicke. Kdyby byla role jen jedna
|
||||
globalni, nesla by tahle situace vubec zapsat.
|
||||
|
||||
## Tri pohledy na tickety
|
||||
|
||||
| Pohled | Co ukazuje | Kdo smi |
|
||||
| -------- | ------------------------- | ------------------------------ |
|
||||
| `all` | napric vsemi firmami | jen `platformAdmin` |
|
||||
| `tenant` | cela jedna firma | kdokoliv, kdo do ni patri |
|
||||
| `mine` | jen tickety prihlaseneho | kdo ma navazaneho resitele |
|
||||
|
||||
Posilaji se jako query: `?scope=tenant&tenantId=tnt_automia`.
|
||||
|
||||
U `tenant` a `mine` je potreba vedet **kterou** firmu, protoze uzivatel jich
|
||||
muze mit vic. Bez `tenantId` se pouzije prvni.
|
||||
|
||||
## Server je autorita
|
||||
|
||||
Vsechno rozhoduje `src/data/access.ts`, jedno misto pro cely portal. Kdyby se to
|
||||
rozlezlo po routach, driv nebo pozdeji vznikne endpoint, ktery filtr zapomene.
|
||||
|
||||
`GET /api/dashboard/access` vraci, co uzivatel smi:
|
||||
|
||||
```json
|
||||
{
|
||||
"scopes": ["all", "tenant", "mine"],
|
||||
"tenants": [{ "id": "tnt_automia", "name": "Automia" }],
|
||||
"defaultTenantId": "tnt_automia",
|
||||
"canAssignOthers": true,
|
||||
"personId": "ppl_uhlir"
|
||||
}
|
||||
```
|
||||
|
||||
Klient podle toho kresli prepinac. **Nesmi si to dovozovat sam** - jinak by se
|
||||
prava pocitala na dvou mistech a jednou se rozejdou.
|
||||
|
||||
## Nikdy tise nezuzujeme
|
||||
|
||||
Pozadavek na pohled nebo firmu, na kterou uzivatel nema pravo, vraci **chybu**,
|
||||
ne potichu zuzeny vysledek:
|
||||
|
||||
| Situace | Odpoved |
|
||||
| -------------------------------- | ------- |
|
||||
| pohled bez opravneni | 403 |
|
||||
| firma, do ktere nepatri | 404 |
|
||||
| ucet bez firmy | 403 |
|
||||
| prirazeni ostatnim bez prava | 403 |
|
||||
|
||||
Duvod: kdyby se pozadavek na cizi firmu jen prepnul na vlastni, uzivatel by
|
||||
koukal na cizi cisla v domneni, ze jsou spravna. To je horsi nez chyba.
|
||||
|
||||
## Filtr v ulozisti je povinny
|
||||
|
||||
`listTickets`, `listPeople` a `listAutomations` maji `tenantIds` jako **povinny**
|
||||
argument, ne volitelny. Zapomenuty filtr tak neznamena "vse", ale nezkompiluje se.
|
||||
|
||||
Zapis (`assignTicket`, `updateTicketStatus`, `addComment`, `updateAutomation`,
|
||||
`deleteAutomation`) bere `tenantIds` taky. Cizi zaznam se chova jako neexistujici,
|
||||
tedy 404, ne 403 - z odpovedi nemá jit poznat, ze takove ID vubec existuje.
|
||||
|
||||
Vyjimka je verejny webhook. Ten se autorizuje tokenem v adrese, ne prihlasenim,
|
||||
takze si automatizaci najde pres vsechny firmy.
|
||||
|
||||
## Prirazeni jen v ramci firmy
|
||||
|
||||
`assignTicket` odmitne resitele z jine firmy. Jinak by ticket zmizel z prehledu
|
||||
sve firmy a objevil se nekomu, kdo do ni nepatri.
|
||||
|
||||
Prehazovat praci mezi lidmi smi jen `admin`. `agent` si smi vzit ticket na sebe,
|
||||
ale nemuze ho poslat kolegovi - to hlida `canAssignOthers`.
|
||||
|
||||
## Demo ucty
|
||||
|
||||
Heslo je u vsech `demo1234`.
|
||||
|
||||
| E-mail | Kdo je |
|
||||
| ------------------------- | ----------------------------------------------- |
|
||||
| `admin@automia.cz` | spravce platformy, vidi vsechny tri firmy |
|
||||
| `karel.vomacka@automia.cz`| agent v Automii, admin u Nordisu - dve firmy |
|
||||
| `martin.kriz@automia.cz` | bezny resitel jedne firmy |
|
||||
|
||||
Druhy ucet je ten zajimavy: ukazuje prepinac firem i to, ze prava se lisi
|
||||
podle toho, ktera firma je prave zvolena.
|
||||
|
||||
## Co chybi
|
||||
|
||||
| Chybi | Poznamka |
|
||||
| ------------------------ | ---------------------------------------------------- |
|
||||
| Sprava clenstvi z portalu| memberships jdou zmenit jen v kodu |
|
||||
| Pozvanky uzivatelu | zadny onboarding |
|
||||
| Tenant u incidentu | incidenty jsou zatim spolecne, nefiltruji se |
|
||||
| Tenant u konektoru | katalog je spolecny, napojeni se zatim neeviduje |
|
||||
| Audit pristupu | odepreni se jen loguje, nikde se neuklada |
|
||||
@@ -91,6 +91,29 @@ co vratil. Bez toho byla predvalidace k nicemu.
|
||||
Prijmove zamerne neprirazuji, smerovani si ticket prevezme a podminkou
|
||||
`assigned neni splneno` neprepise rucni rozhodnuti.
|
||||
|
||||
### Firmy a prava
|
||||
|
||||
Treti a nejvazneji dira: portal nemel zadnou tenanci. Kterykoliv prihlaseny
|
||||
uzivatel videl vsechny tickety vsech firem a cely seznam resitelu, `requireRole`
|
||||
se nikde nevolal. Popis v [07-firmy-a-prava.md](07-firmy-a-prava.md).
|
||||
|
||||
- Tenant jako hranice viditelnosti. `tenantId` na ticketu, resiteli
|
||||
i automatizaci.
|
||||
- Uzivatel muze patrit do **vic firem**, v kazde s jinou roli (`memberships`).
|
||||
Pristup napric firmami je zvlast jako `platformAdmin`.
|
||||
- Tri pohledy na tickety: `all`, `tenant`, `mine`. Admin mezi nimi prepina,
|
||||
vcetne vyberu firmy.
|
||||
- `src/data/access.ts` jako jedine misto, kde se rozhoduje o pravech.
|
||||
`GET /api/dashboard/access` rekne klientovi, co smi kreslit.
|
||||
- Filtr na firmu je v ulozistich **povinny argument**. Zapomenuty filtr
|
||||
neznamena "vse", ale nezkompiluje se.
|
||||
- Nikdy tise nezuzujeme. Cizi firma vraci 403 nebo 404.
|
||||
- Prirazeni jen v ramci firmy. Prehazovat praci mezi lidmi smi jen admin,
|
||||
agent si smi vzit ticket na sebe.
|
||||
- `requireRole` nahrazen `requirePlatformAdmin`. Prava uvnitr firmy resi
|
||||
`access.ts`, protoze zavisi na tom, ktera firma pozadavek zajima.
|
||||
- Demo ucty pokryvaji vsechny tri situace vcetne cloveka ve dvou firmach.
|
||||
|
||||
### Zapsano jako otevrene rozhodnuti
|
||||
|
||||
Vsechny automatizace se stejnym spoustecem se spusti. Doporucene rozdeleni na to
|
||||
|
||||
Reference in New Issue
Block a user