apps/recallassess/recallassess-api/src/config/navigation/services/navigation-filter.service.ts
Navigation Filter Service Filters navigation items based on user role, permissions, and feature flags
This service is designed to be extensible:
Methods |
|
| filterItems | ||||||||||||
filterItems(items: RoleBasedNavigationItem[], context: NavigationFilterContext)
|
||||||||||||
|
Filter navigation items based on context
Parameters :
Returns :
RoleBasedNavigationItem[]
Filtered array of navigation items |
| getItemsForRole | ||||||||||||
getItemsForRole(items: RoleBasedNavigationItem[], role: ParticipantRole | string)
|
||||||||||||
|
Get navigation items for a specific role (convenience method)
Parameters :
Returns :
RoleBasedNavigationItem[]
Filtered navigation items for the role |
| Private hasRequiredFeatureFlags | ||||||||||||
hasRequiredFeatureFlags(userFlags: string[], requiredFlags: string[])
|
||||||||||||
|
Check if user has required feature flags
Parameters :
Returns :
boolean
true if user has at least one required feature flag |
| Private hasRequiredPermissions | ||||||||||||
hasRequiredPermissions(userPermissions: string[], requiredPermissions: string[])
|
||||||||||||
|
Check if user has required permissions
Parameters :
Returns :
boolean
true if user has at least one required permission |
| Private hasRequiredRole | ||||||||||||
hasRequiredRole(userRole: ParticipantRole | string, allowedRoles: ParticipantRole[])
|
||||||||||||
|
Check if user has required role
Parameters :
Returns :
boolean
true if user has at least one of the allowed roles |
| Private processItem | ||||||||||||
processItem(item: RoleBasedNavigationItem, context: NavigationFilterContext)
|
||||||||||||
|
Process a navigation item (filter children, clean metadata, etc.)
Parameters :
Returns :
RoleBasedNavigationItem
Processed navigation item |
| Private shouldShowItem | ||||||||||||
shouldShowItem(item: RoleBasedNavigationItem, context: NavigationFilterContext)
|
||||||||||||
|
Determine if an item should be shown based on filter context
Parameters :
Returns :
boolean
true if item should be shown, false otherwise |
import { Injectable } from "@nestjs/common";
import { ParticipantRole, RoleBasedNavigationItem } from "../types";
/**
* Navigation Filter Context
* Contains all information needed to filter navigation items
*/
export interface NavigationFilterContext {
/**
* User's role (from ParticipantRole enum)
*/
role: ParticipantRole | string;
/**
* Active feature flags (for future expansion)
*/
featureFlags?: string[];
/**
* User permissions (for future expansion)
*/
permissions?: string[];
/**
* Additional context data
*/
metadata?: Record<string, unknown>;
}
/**
* Navigation Filter Service
* Filters navigation items based on user role, permissions, and feature flags
*
* This service is designed to be extensible:
* - Add new filter criteria by extending NavigationFilterContext
* - Implement custom filtering logic in filterItems method
* - Support nested navigation items (children)
*/
@Injectable()
export class NavigationFilterService {
/**
* Filter navigation items based on context
* @param items - Array of navigation items to filter
* @param context - Filter context containing role, permissions, etc.
* @returns Filtered array of navigation items
*/
filterItems(items: RoleBasedNavigationItem[], context: NavigationFilterContext): RoleBasedNavigationItem[] {
return items
.filter((item) => this.shouldShowItem(item, context))
.map((item) => this.processItem(item, context));
}
/**
* Determine if an item should be shown based on filter context
* @param item - Navigation item to check
* @param context - Filter context
* @returns true if item should be shown, false otherwise
*/
private shouldShowItem(item: RoleBasedNavigationItem, context: NavigationFilterContext): boolean {
// Check if item is explicitly hidden
if (item.hidden) {
return false;
}
// Check role-based access
if (item.allowedRoles && item.allowedRoles.length > 0) {
if (!this.hasRequiredRole(context.role, item.allowedRoles)) {
return false;
}
}
// Check feature flags (if specified)
if (item.featureFlags && item.featureFlags.length > 0) {
if (!this.hasRequiredFeatureFlags(context.featureFlags || [], item.featureFlags)) {
return false;
}
}
// Check permissions (if specified) - for future expansion
if (item.permissions && item.permissions.length > 0) {
if (!this.hasRequiredPermissions(context.permissions || [], item.permissions)) {
return false;
}
}
return true;
}
/**
* Process a navigation item (filter children, clean metadata, etc.)
* @param item - Navigation item to process
* @param context - Filter context
* @returns Processed navigation item
*/
private processItem(item: RoleBasedNavigationItem, context: NavigationFilterContext): RoleBasedNavigationItem {
// Create a copy to avoid mutating the original
const processedItem = { ...item };
// Recursively filter children if they exist
if (processedItem.children && processedItem.children.length > 0) {
processedItem.children = this.filterItems(processedItem.children, context);
// If all children are filtered out, mark parent as hidden (optional)
// Uncomment if you want to hide parent items with no visible children
// if (processedItem.children.length === 0) {
// processedItem.hidden = true;
// }
}
// Remove internal metadata that shouldn't be sent to frontend
// (Keep allowedRoles etc. for debugging, or remove them here)
// delete processedItem.allowedRoles;
// delete processedItem.featureFlags;
// delete processedItem.permissions;
return processedItem;
}
/**
* Check if user has required role
* @param userRole - User's role
* @param allowedRoles - Array of allowed roles
* @returns true if user has at least one of the allowed roles
*/
private hasRequiredRole(userRole: ParticipantRole | string, allowedRoles: ParticipantRole[]): boolean {
return allowedRoles.includes(userRole as ParticipantRole);
}
/**
* Check if user has required feature flags
* @param userFlags - User's active feature flags
* @param requiredFlags - Required feature flags (at least one must match)
* @returns true if user has at least one required feature flag
*/
private hasRequiredFeatureFlags(userFlags: string[], requiredFlags: string[]): boolean {
if (requiredFlags.length === 0) return true;
return requiredFlags.some((flag) => userFlags.includes(flag));
}
/**
* Check if user has required permissions
* @param userPermissions - User's permissions
* @param requiredPermissions - Required permissions (at least one must match)
* @returns true if user has at least one required permission
*/
private hasRequiredPermissions(userPermissions: string[], requiredPermissions: string[]): boolean {
if (requiredPermissions.length === 0) return true;
return requiredPermissions.some((permission) => userPermissions.includes(permission));
}
/**
* Get navigation items for a specific role (convenience method)
* @param items - Array of navigation items
* @param role - User role
* @returns Filtered navigation items for the role
*/
getItemsForRole(items: RoleBasedNavigationItem[], role: ParticipantRole | string): RoleBasedNavigationItem[] {
return this.filterItems(items, { role });
}
}