# Rollen-System Dokumentation **Version:** 1.0 **Letztes Update:** Januar 2025 --- ## Überblick Das Rollen-System von my.experimenta.science ermöglicht es, Produkte und UI-Komponenten basierend auf der aktiven Rolle des Benutzers anzuzeigen oder zu verstecken. Dies ermöglicht rollenspezifische Preise, Produkte und Funktionen. --- ## 1. Rollen-Typen ### 1.1 Verfügbare Rollen | Code | Display Name | Beschreibung | Approval erforderlich | |------|--------------|--------------|----------------------| | `private` | Privatperson | Private Nutzung | Nein (auto-assigned) | | `educator` | Pädagoge | Lehrkräfte und Schulen | Ja (Post-MVP) | | `company` | Unternehmen | Geschäftskunden | Ja (Post-MVP) | ### 1.2 TypeScript Type Definition ```typescript type RoleCode = 'private' | 'educator' | 'company' interface Role { code: RoleCode // Primary key displayName: string // "Privatperson", "Pädagoge", "Unternehmen" description: string requiresApproval: boolean // false for 'private', true for others sortOrder: number active: boolean } ``` --- ## 2. Automatische Rollen-Zuweisung ### 2.1 Auto-Assignment bei First Login (MVP) Alle neuen Benutzer erhalten automatisch die Rolle `private` beim ersten Login: ```typescript // server/api/auth/login.post.ts if (!user) { // Neuer User - erstelle Profil const [newUser] = await db.insert(users).values({...}).returning() // Auto-assign 'private' role await assignRoleToUser(newUser.id, 'private', { adminNotes: 'Auto-assigned on first login', }) } ``` **Wichtig:** - Status ist immer `approved` (kein Approval-Workflow in MVP) - Jeder User hat mindestens eine Rolle - Safety-Check: Existing users ohne Rollen bekommen auch `private` role --- ## 3. Rollen-basierte Produktsichtbarkeit ### 3.1 Konzept Produkte sind NUR sichtbar, wenn: 1. Das Produkt `product_role_visibility` Einträge hat 2. Der User eine genehmigte (`approved`) Rolle hat, die in `product_role_visibility` vorkommt **Opt-in Visibility:** - Produkt ohne `product_role_visibility` Einträge → Unsichtbar für ALLE - User ohne genehmigte Rollen → Sieht KEINE Produkte ### 3.2 Database Schema ```sql -- Many-to-Many: Product ↔ Roles CREATE TABLE product_role_visibility ( id UUID PRIMARY KEY, product_id UUID NOT NULL REFERENCES products(id), role_code TEXT NOT NULL REFERENCES roles(code), UNIQUE(product_id, role_code) ); ``` ### 3.3 Automatische Zuordnung (ERP Import) Beim Import aus NAV ERP werden Produkte automatisch Rollen zugeordnet: ```typescript // server/utils/roles.ts const categoryRoleMapping = { 'makerspace-annual-pass': ['private', 'educator'], 'annual-pass': ['private'], 'educator-annual-pass': ['educator'], 'company-annual-pass': ['company'] } // server/api/erp/products.post.ts await assignRolesToProductByCategory(product.id, category) ``` ### 3.4 API Filtering Pattern ```typescript // server/api/products/index.get.ts export default defineEventHandler(async (event) => { const { user } = await getUserSession(event) // Unauthenticated users see NO products if (!user) return [] // Get visible product IDs for user's roles const visibleProductIds = await getVisibleProductIdsForUser(user.id) // Fetch products with role filter const products = await db.query.products.findMany({ where: and( eq(products.active, true), inArray(products.id, visibleProductIds) ) }) return products }) ``` --- ## 4. Rollen-basierte Menüpunkt-Sichtbarkeit ### 4.1 Konzept Navigation-Tabs können für bestimmte Rollen ein-/ausgeblendet werden: - `roleVisibility: 'all'` → Sichtbar für alle Rollen - `roleVisibility: ['educator']` → Nur für Pädagogen - `roleVisibility: ['educator', 'company']` → Für mehrere Rollen ### 4.2 Implementation (AreaTabs.vue) ```typescript interface ProductArea { id: string label: string icon: any enabled: boolean visible: boolean badge?: string route: string roleVisibility?: 'all' | RoleCode[] // NEW } const areas: ProductArea[] = [ { id: 'start', roleVisibility: 'all' }, // Alle Rollen { id: 'makerspace', roleVisibility: 'all' }, // Alle Rollen { id: 'educator', roleVisibility: ['educator'] }, // Nur Pädagogen { id: 'experimenta', roleVisibility: 'all' }, // Alle Rollen ] const { activeRole } = useActiveRole() const visibleAreas = computed(() => { return areas.filter(area => { if (!area.visible) return false if (!area.roleVisibility) return true if (area.roleVisibility === 'all') return true return area.roleVisibility.includes(activeRole.value as RoleCode) }) }) ``` ### 4.3 Automatische Updates Menüpunkte aktualisieren sich automatisch beim Rollenwechsel: - ✅ Vue Computed Property reagiert auf `activeRole` Änderungen - ✅ Keine manuellen Refresh-Calls nötig - ✅ Sofortige UI-Updates --- ## 5. Client-Side Composable ### 5.1 useActiveRole() ```typescript // app/composables/useActiveRole.ts export function useActiveRole() { const activeRole = useState('activeRole', () => 'private') const roles = useState('roles', () => []) // Fetch current role status from server async function fetchRoleStatus() { ... } // Switch to a different role async function switchRole(roleCode: string) { await $fetch('/api/user/active-role', { method: 'PATCH', body: { roleCode }, }) activeRole.value = roleCode // Auto-refresh product pages await Promise.all([ refreshNuxtData('products-list'), refreshNuxtData('educator-products'), refreshNuxtData('experimenta-products'), ]) } return { activeRole, roles, approvedRoles: computed(() => roles.value.filter(r => r.hasRole)), hasMultipleRoles: computed(() => approvedRoles.value.length > 1), fetchRoleStatus, switchRole, } } ``` ### 5.2 Auto-Initialization Rollen werden automatisch beim Login geladen: ```typescript const { loggedIn } = useUserSession() if (loggedIn.value) { callOnce('init-roles', () => fetchRoleStatus()) } ``` --- ## 6. Server-Side Utilities ### 6.1 Wichtige Functions ```typescript // server/utils/roles.ts // Get user's approved role codes await getUserApprovedRoleCodes(userId) // => ['private', 'educator'] // Get visible product IDs for user await getVisibleProductIdsForUser(userId) // => ['uuid-1', 'uuid-2', ...] // Get visible product IDs for specific role await getVisibleProductIdsForRole(userId, roleCode) // => ['uuid-1', 'uuid-2', ...] // Check if product is visible for user await isProductVisibleForUser(productId, userId) // => true/false // Assign role to user (MVP: always approved) await assignRoleToUser(userId, 'private', { adminNotes: 'Auto-assigned', }) // Auto-assign roles to product by category await assignRolesToProductByCategory(productId, 'annual-pass') ``` --- ## 7. Session Management ### 7.1 Active Role Session Die aktive Rolle wird server-side in der Session gespeichert: ```typescript // server/utils/role-session.ts // Get user's active role (with TTL validation) const activeRole = await getUserActiveRole(event) // => 'private' | 'educator' | 'company' // Set user's active role in session + DB await setUserActiveRole(event, userId, 'educator') // Validate active role (TTL-based) const isValid = await validateActiveRole(event, userId) ``` **Session TTL:** - Active role ist 30 Tage gültig - Nach Ablauf: Fallback auf erste approved role - Update bei jedem `switchRole()` --- ## 8. API Endpoints ### 8.1 Role Management Endpoints | Endpoint | Method | Beschreibung | |----------|--------|--------------| | `/api/user/role-status` | GET | Aktuellen Role-Status abrufen | | `/api/user/active-role` | PATCH | Aktive Rolle wechseln | #### GET /api/user/role-status **Response:** ```json { "activeRoleCode": "private", "roles": [ { "code": "private", "displayName": "Privatperson", "description": "Private Nutzung", "hasRole": true, "requiresApproval": false }, { "code": "educator", "displayName": "Pädagoge", "description": "Lehrkräfte und Schulen", "hasRole": true, "requiresApproval": true } ], "roleChangedByAdmin": false } ``` #### PATCH /api/user/active-role **Request:** ```json { "roleCode": "educator" } ``` **Validation:** - ✅ User muss eingeloggt sein - ✅ User muss die Rolle haben (approved status) - ✅ Rolle muss existieren --- ## 9. UI Components ### 9.1 RoleSwitcher Component Der RoleSwitcher zeigt die aktive Rolle an und ermöglicht Rollenwechsel: ```vue ``` **Features:** - ✅ Zeigt nur approved roles - ✅ Markiert aktive Rolle mit Checkmark - ✅ Disabled für roles die User nicht hat - ✅ Triggert automatische Product-Refresh --- ## 10. Testing ### 10.1 Test User Setup ```typescript // Test User mit multiple roles const user = await createTestUser({ email: 'test@example.com', roles: ['private', 'educator'] }) // Setze aktive Rolle await setUserActiveRole(event, user.id, 'educator') ``` ### 10.2 Test Scenarios **Scenario 1: Privatperson Login** - ✅ User erhält `private` role - ✅ Sieht nur Produkte mit `private` role - ✅ Sieht Navigation: Start, Makerspace, experimenta **Scenario 2: Pädagoge Login** - ✅ User hat `private` + `educator` roles - ✅ Kann zwischen Rollen wechseln - ✅ Als Pädagoge: Zusätzliche Produkte sichtbar - ✅ Als Pädagoge: "Bildung" Tab sichtbar **Scenario 3: Rollenwechsel** - ✅ Produktliste aktualisiert sich automatisch - ✅ Navigation aktualisiert sich automatisch - ✅ Session + DB werden aktualisiert - ✅ Product detail page: 404 redirect wenn nicht sichtbar --- ## 11. MVP Limitations ### 11.1 Was NICHT implementiert ist (Post-MVP) **Approval Workflow:** - ❌ Keine UI für Rollen-Antrag (Pädagogen/Unternehmen) - ❌ Kein Admin-Panel für Genehmigung - ❌ Status bleibt immer `approved` **Role Management:** - ❌ Keine Self-Service Rollen-Verwaltung - ❌ Keine Admin-Funktionen zum Hinzufügen/Entfernen von Rollen - ❌ Keine Rollen-History für Admins **Advanced Features:** - ❌ Keine Organisation-Level Rollen (z.B. alle Lehrer einer Schule) - ❌ Keine zeitlich begrenzte Rollen - ❌ Keine Rollen-Hierarchien ### 11.2 Database prepared for Post-MVP Trotz MVP Limitations ist die Datenbank bereits vorbereitet: ```typescript // user_roles table (prepared but status always 'approved' in MVP) interface UserRole { status: 'pending' | 'approved' | 'rejected' | 'revoked' organizationName?: string // Prepared requestMessage?: string // Prepared proofDocument?: string // Prepared adminNotes?: string // Prepared statusHistory: StatusHistoryEntry[] // JSONB audit trail } ``` --- ## 12. Best Practices ### 12.1 Beim Hinzufügen neuer Rollen 1. **Database:** Füge neue Rolle zu `roles` table hinzu 2. **Type:** Update `RoleCode` type in schema 3. **Mapping:** Update `categoryRoleMapping` in `server/utils/roles.ts` 4. **UI:** Update RoleSwitcher display names 5. **Navigation:** Update `roleVisibility` in AreaTabs.vue ### 12.2 Beim Hinzufügen neuer Produkte 1. **Category:** Definiere eine klare Kategorie 2. **Mapping:** Füge Kategorie zu `categoryRoleMapping` hinzu 3. **Visibility:** Produkt wird automatisch Rollen zugeordnet beim Import ### 12.3 Beim Hinzufügen neuer Navigation-Items ```typescript { id: 'new-tab', label: 'Neuer Tab', roleVisibility: 'all' // oder ['educator', 'company'] } ``` --- ## 13. Troubleshooting ### Problem: User sieht keine Produkte **Prüfen:** 1. Hat User eine approved Rolle? → `getUserApprovedRoleCodes(userId)` 2. Hat Produkt `product_role_visibility` Einträge? 3. Matchen Rolle und Produkt-Visibility? ### Problem: Menüpunkt erscheint nicht nach Rollenwechsel **Prüfen:** 1. Ist `roleVisibility` korrekt gesetzt? 2. Ist `activeRole` korrekt aktualisiert? → Vue DevTools 3. Ist computed property `visibleAreas` reaktiv? ### Problem: Produkt bleibt sichtbar nach Rollenwechsel **Prüfen:** 1. Wurde `refreshNuxtData()` aufgerufen in `switchRole()`? 2. Sind die richtigen keys verwendet? (`products-list`, etc.) 3. Hat API-Endpoint role-based filtering? --- ## 14. Weitere Dokumentation - **Architektur:** `docs/ARCHITECTURE.md` - Database Schema Details - **PRD:** `docs/PRD.md` - Product Requirements - **Testing:** `docs/TESTING.md` - Test Credentials & Scenarios - **Cidaas Integration:** `docs/CIDAAS_INTEGRATION.md` - Auth Flow --- ## 15. Changelog | Version | Datum | Änderung | |---------|-------|----------| | 1.0 | Jan 2025 | Initial documentation - MVP implementation |