Implement Role Management Features and UI Enhancements
- Introduced a new composable `useActiveRole` for managing user roles, including fetching role status and switching roles with server validation. - Updated `RoleSwitcher.vue` to utilize the new composable, enhancing role selection with improved error handling and UI feedback. - Added new API endpoints for role management, including fetching user role status and switching active roles. - Enhanced product visibility logic to filter based on the user's active role, ensuring a tailored experience. - Updated database schema to support last active role tracking for users, improving session management. - Refined UI components across the application to reflect role-based changes and improve user experience.
This commit is contained in:
@@ -86,7 +86,16 @@ export default defineEventHandler(async (event) => {
|
||||
}
|
||||
}
|
||||
|
||||
// 6. Create encrypted session
|
||||
// 6. Determine initial active role
|
||||
const approvedRoles = await getUserApprovedRoleCodes(user.id)
|
||||
|
||||
// Use last active role if user had one and still has that role
|
||||
const initialRole =
|
||||
user.lastActiveRoleCode && approvedRoles.includes(user.lastActiveRoleCode)
|
||||
? user.lastActiveRoleCode
|
||||
: approvedRoles[0] || 'private'
|
||||
|
||||
// 7. Create encrypted session
|
||||
await setUserSession(event, {
|
||||
user: {
|
||||
id: user.id,
|
||||
@@ -103,9 +112,14 @@ export default defineEventHandler(async (event) => {
|
||||
},
|
||||
accessToken: tokens.access_token, // Store for logout
|
||||
loggedInAt: new Date().toISOString(),
|
||||
// Role state management
|
||||
activeRoleCode: initialRole,
|
||||
userApprovedRoles: approvedRoles,
|
||||
rolesLastValidated: new Date().toISOString(),
|
||||
roleChangedByAdmin: false,
|
||||
})
|
||||
|
||||
// 7. Return success
|
||||
// 8. Return success
|
||||
return {
|
||||
success: true,
|
||||
}
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
/**
|
||||
* GET /api/products
|
||||
*
|
||||
* Returns a list of products visible to the current user based on their roles.
|
||||
* Returns a list of products visible to the current user based on their active role.
|
||||
*
|
||||
* Role-based Visibility (MVP):
|
||||
* - Unauthenticated users: See NO products (empty array)
|
||||
* - Authenticated users: See products assigned to their approved roles
|
||||
* - Authenticated users: See products assigned to their ACTIVE role only
|
||||
* - Products WITHOUT role assignments: NOT visible (opt-in visibility)
|
||||
*
|
||||
* Query Parameters:
|
||||
@@ -16,7 +16,8 @@
|
||||
|
||||
import { eq, and, inArray } from 'drizzle-orm'
|
||||
import { products } from '../../database/schema'
|
||||
import { getVisibleProductIdsForUser } from '../../utils/roles'
|
||||
import { getVisibleProductIdsForRole } from '../../utils/roles'
|
||||
import { getUserActiveRole } from '../../utils/role-session'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const db = useDatabase()
|
||||
@@ -32,10 +33,13 @@ export default defineEventHandler(async (event) => {
|
||||
return []
|
||||
}
|
||||
|
||||
// Get product IDs visible to this user (based on approved roles)
|
||||
const visibleProductIds = await getVisibleProductIdsForUser(user.id)
|
||||
// Get user's active role (validates with TTL, auto-fallback if revoked)
|
||||
const activeRole = await getUserActiveRole(event)
|
||||
|
||||
// If user has no approved roles or no products are assigned to their roles
|
||||
// Get product IDs visible for the active role only
|
||||
const visibleProductIds = await getVisibleProductIdsForRole(user.id, activeRole)
|
||||
|
||||
// If user has no access to products in their active role
|
||||
if (visibleProductIds.length === 0) {
|
||||
return []
|
||||
}
|
||||
|
||||
64
server/api/user/active-role.patch.ts
Normal file
64
server/api/user/active-role.patch.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
/**
|
||||
* PATCH /api/user/active-role
|
||||
*
|
||||
* Switch user's active role (used by RoleSwitcher component)
|
||||
*
|
||||
* Request body:
|
||||
* {
|
||||
* "roleCode": "educator"
|
||||
* }
|
||||
*
|
||||
* Response:
|
||||
* {
|
||||
* "success": true,
|
||||
* "activeRoleCode": "educator"
|
||||
* }
|
||||
*
|
||||
* Validates that user has the requested role before switching
|
||||
* Updates both session (immediate) and database (preference)
|
||||
*/
|
||||
|
||||
import { z } from 'zod'
|
||||
import { setUserActiveRole } from '../../utils/role-session'
|
||||
|
||||
const switchRoleSchema = z.object({
|
||||
roleCode: z.enum(['private', 'educator', 'company'], {
|
||||
errorMap: () => ({ message: 'Ungültige Rolle' }),
|
||||
}),
|
||||
})
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
// Require authentication
|
||||
await requireUserSession(event)
|
||||
|
||||
// Validate request body
|
||||
const body = await readBody(event)
|
||||
const { roleCode } = switchRoleSchema.parse(body)
|
||||
|
||||
try {
|
||||
// Set active role (validates + updates session + saves to DB)
|
||||
await setUserActiveRole(event, roleCode)
|
||||
|
||||
return {
|
||||
success: true,
|
||||
activeRoleCode: roleCode,
|
||||
}
|
||||
} catch (error: any) {
|
||||
// setUserActiveRole throws 403 if user doesn't have role
|
||||
if (error.statusCode === 403) {
|
||||
setResponseStatus(event, 403)
|
||||
return {
|
||||
success: false,
|
||||
message: error.message || 'Du hast diese Rolle nicht',
|
||||
}
|
||||
}
|
||||
|
||||
// Other errors
|
||||
console.error('Role switch error:', error)
|
||||
setResponseStatus(event, 500)
|
||||
return {
|
||||
success: false,
|
||||
message: 'Fehler beim Wechseln der Rolle',
|
||||
}
|
||||
}
|
||||
})
|
||||
69
server/api/user/role-status.get.ts
Normal file
69
server/api/user/role-status.get.ts
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* GET /api/user/role-status
|
||||
*
|
||||
* Get user's active role and all available roles (for RoleSwitcher dropdown)
|
||||
*
|
||||
* 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": false,
|
||||
* "requiresApproval": true
|
||||
* },
|
||||
* ...
|
||||
* ],
|
||||
* "roleChangedByAdmin": false
|
||||
* }
|
||||
*
|
||||
* - Validates active role with TTL (re-checks DB every 5min)
|
||||
* - Returns ALL roles (approved + not-approved) for dropdown
|
||||
* - Includes "hasRole" flag to show which roles user actually has
|
||||
*/
|
||||
|
||||
import { asc, eq } from 'drizzle-orm'
|
||||
import { roles } from '../../database/schema'
|
||||
import { getUserActiveRole } from '../../utils/role-session'
|
||||
import { getUserApprovedRoleCodes } from '../../utils/roles'
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const session = await requireUserSession(event)
|
||||
|
||||
// Get active role (validates with TTL, auto-fallback if revoked)
|
||||
const activeRole = await getUserActiveRole(event)
|
||||
|
||||
// Get user's approved role codes
|
||||
const approvedRoleCodes = await getUserApprovedRoleCodes(session.user.id)
|
||||
|
||||
// Get ALL roles from database (for dropdown: show all, disabled if not approved)
|
||||
const db = useDatabase()
|
||||
const allRoles = await db.query.roles.findMany({
|
||||
where: eq(roles.active, true),
|
||||
orderBy: asc(roles.sortOrder),
|
||||
})
|
||||
|
||||
// Map roles with "hasRole" status
|
||||
const rolesWithStatus = allRoles.map((role) => ({
|
||||
code: role.code,
|
||||
displayName: role.displayName,
|
||||
description: role.description,
|
||||
hasRole: approvedRoleCodes.includes(role.code),
|
||||
requiresApproval: role.requiresApproval,
|
||||
}))
|
||||
|
||||
return {
|
||||
activeRoleCode: activeRole,
|
||||
roles: rolesWithStatus,
|
||||
roleChangedByAdmin: session.roleChangedByAdmin || false,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user