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:
JiriUhlir
2026-08-03 12:59:30 +02:00
co-authored by Claude Opus 5
parent dd021b5f69
commit 2a3d75c85e
25 changed files with 997 additions and 163 deletions
+25 -6
View File
@@ -1,4 +1,18 @@
export type UserRole = 'admin' | 'client';
/**
* Uzivatel muze patrit do vic firem najednou (napr. externista, ktery dela
* servicedesk dvema klientum). Proto `memberships`, ne jedno `tenantId`.
*
* Role je vzdy az uvnitr firmy. Pristup napric firmami je zvlast, jako
* `platformAdmin` - to je nase pravo, ne zakaznicke.
*/
/** Role uvnitr jedne firmy. */
export type TenantRole = 'admin' | 'agent';
export interface Membership {
tenantId: string;
role: TenantRole;
}
export interface User {
id: string;
@@ -6,8 +20,9 @@ export interface User {
/** bcrypt hash - nikdy neposilat na klienta */
passwordHash: string;
name: string;
role: UserRole;
company: string;
/** Vidi napric vsemi firmami a muze mezi nimi prepinat. */
platformAdmin: boolean;
memberships: Membership[];
}
/** Verze uzivatele bezpecna pro odeslani na klienta. */
@@ -15,17 +30,21 @@ export interface PublicUser {
id: string;
email: string;
name: string;
role: UserRole;
company: string;
platformAdmin: boolean;
memberships: Membership[];
}
export interface JwtPayload {
sub: string;
email: string;
role: UserRole;
}
export function toPublicUser(user: User): PublicUser {
const { passwordHash: _passwordHash, ...rest } = user;
return rest;
}
/** Role uzivatele v dane firme, nebo undefined kdyz do ni nepatri. */
export function roleIn(user: User, tenantId: string): TenantRole | undefined {
return user.memberships.find((m) => m.tenantId === tenantId)?.role;
}