You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

13 KiB

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

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:

// 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

-- 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:

// 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

// 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)

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()

// app/composables/useActiveRole.ts
export function useActiveRole() {
  const activeRole = useState<string>('activeRole', () => 'private')
  const roles = useState<RoleWithStatus[]>('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:

const { loggedIn } = useUserSession()
if (loggedIn.value) {
  callOnce('init-roles', () => fetchRoleStatus())
}

6. Server-Side Utilities

6.1 Wichtige Functions

// 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:

// 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:

{
  "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:

{
  "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:

<!-- app/components/navigation/RoleSwitcher.vue -->
<template>
  <DropdownMenu>
    <DropdownMenuTrigger>
      Du kaufst als: {{ activeRoleDisplay }}
    </DropdownMenuTrigger>
    <DropdownMenuContent>
      <DropdownMenuItem
        v-for="role in approvedRoles"
        @click="switchRole(role.code)"
      >
        {{ role.displayName }}
      </DropdownMenuItem>
    </DropdownMenuContent>
  </DropdownMenu>
</template>

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

// 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:

// 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

{
  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