frontend: Configures the application routing

Sets up domain-based routing for different app features.

This change introduces a structured approach to managing application routes,
making it easier to add, modify, and maintain different sections of the application.
It configures routes for authentication, meetings, rooms, recordings, and the console.
This commit is contained in:
Carlos Santos 2026-01-14 13:57:03 +01:00
parent ca2d41b05e
commit 082aa8480c
9 changed files with 313 additions and 148 deletions

View File

@ -0,0 +1,15 @@
import { DomainRouteConfig } from '../../../shared/models/domain-routes.model';
import { checkUserNotAuthenticatedGuard } from '../guards/auth.guard';
/**
* Auth domain route configurations
*/
export const authDomainRoutes: DomainRouteConfig[] = [
{
route: {
path: 'login',
loadComponent: () => import('../pages/login/login.component').then((m) => m.LoginComponent),
canActivate: [checkUserNotAuthenticatedGuard]
}
}
];

View File

@ -2,6 +2,7 @@ import { Component } from '@angular/core';
import { ConsoleNavLink } from '../../../../shared/models/sidenav.model';
import { AuthService } from '../../../auth/services/auth.service';
import { ConsoleNavComponent } from '../../components/console-nav/console-nav.component';
import { consoleChildRoutes } from '../../routes/console.routes';
@Component({
selector: 'ov-console',
@ -10,28 +11,25 @@ import { ConsoleNavComponent } from '../../components/console-nav/console-nav.co
styleUrl: './console.component.scss'
})
export class ConsoleComponent {
navLinks: ConsoleNavLink[] = [
{ label: 'Overview', route: 'overview', icon: 'dashboard' },
{ label: 'Rooms', route: 'rooms', icon: 'video_chat', iconClass: 'ov-room-icon' },
{ label: 'Recordings', route: 'recordings', icon: 'video_library', iconClass: 'ov-recording-icon' },
{
label: 'Embedded',
route: 'embedded',
icon: 'code_blocks',
iconClass: 'material-symbols-outlined ov-developer-icon'
},
{
label: 'Users & Permissions',
route: 'users-permissions',
icon: 'passkey',
iconClass: 'ov-users-permissions material-symbols-outlined'
},
{ label: 'Configuration', route: 'config', icon: 'settings', iconClass: 'ov-settings-icon' }
navLinks: ConsoleNavLink[];
// { label: 'About', route: 'about', icon: 'info' }
];
constructor(private authService: AuthService) {}
constructor(private authService: AuthService) {
// Build navigation links from console child route configurations
this.navLinks = consoleChildRoutes
.filter((config) => config.navMetadata) // Only include routes with navigation metadata
.map((config) => ({
label: config.navMetadata!.label,
route: config.navMetadata!.route,
icon: config.navMetadata!.icon,
iconClass: config.navMetadata?.iconClass
}))
.sort((a, b) => {
// Sort by order if available
const orderA = consoleChildRoutes.find((r) => r.navMetadata?.route === a.route)?.navMetadata?.order ?? 999;
const orderB = consoleChildRoutes.find((r) => r.navMetadata?.route === b.route)?.navMetadata?.order ?? 999;
return orderA - orderB;
});
}
async logout() {
await this.authService.logout();

View File

@ -0,0 +1,92 @@
import { Route } from '@angular/router';
import { DomainRouteConfig } from '../../../shared/models/domain-routes.model';
import { checkUserAuthenticatedGuard } from '../../auth/guards/auth.guard';
import { recordingsConsoleRoutes } from '../../recordings/routes/recordings.routes';
import { roomsConsoleRoutes } from '../../rooms/routes/rooms.routes';
/**
* All console child routes configuration (includes console pages + rooms + recordings)
* Used by ConsoleComponent to build navigation links
*/
export const consoleChildRoutes: DomainRouteConfig[] = [
{
route: {
path: 'overview',
loadComponent: () => import('../pages/overview/overview.component').then((m) => m.OverviewComponent)
},
navMetadata: {
label: 'Overview',
route: 'overview',
icon: 'dashboard',
order: 1
}
},
...roomsConsoleRoutes,
...recordingsConsoleRoutes,
{
route: {
path: 'embedded',
loadComponent: () => import('../pages/embedded/embedded.component').then((m) => m.EmbeddedComponent)
},
navMetadata: {
label: 'Embedded',
route: 'embedded',
icon: 'code_blocks',
iconClass: 'material-symbols-outlined ov-developer-icon',
order: 4
}
},
{
route: {
path: 'users-permissions',
loadComponent: () =>
import('../pages/users-permissions/users-permissions.component').then((m) => m.UsersPermissionsComponent)
},
navMetadata: {
label: 'Users & Permissions',
route: 'users-permissions',
icon: 'passkey',
iconClass: 'ov-users-permissions material-symbols-outlined',
order: 5
}
},
{
route: {
path: 'config',
loadComponent: () => import('../pages/config/config.component').then((m) => m.ConfigComponent)
},
navMetadata: {
label: 'Configuration',
route: 'config',
icon: 'settings',
iconClass: 'ov-settings-icon',
order: 6
}
}
];
/**
* Console domain routes (error page + authenticated console shell with all child routes)
* Used by base-routes.ts
*/
export const consoleDomainRoutes: Route[] = [
{
path: 'error',
loadComponent: () => import('../pages/error/error.component').then((m) => m.ErrorComponent)
},
{
path: '',
loadComponent: () => import('../pages/console/console.component').then((m) => m.ConsoleComponent),
canActivate: [checkUserAuthenticatedGuard],
children: [
{
path: '',
redirectTo: 'overview',
pathMatch: 'full'
},
...consoleChildRoutes.map((config) => config.route),
{ path: '**', redirectTo: 'overview' }
]
}
];

View File

@ -0,0 +1,31 @@
import { WebComponentProperty } from '@openvidu-meet/typings';
import { extractRoomQueryParamsGuard } from '../../../shared/guards/extract-query-params.guard';
import { removeQueryParamsGuard } from '../../../shared/guards/remove-query-params.guard';
import { runGuardsSerially } from '../../../shared/guards/run-serially.guard';
import { DomainRouteConfig } from '../../../shared/models/domain-routes.model';
import { validateRoomAccessGuard } from '../../rooms/guards/room-validate-access.guard';
/**
* Meeting domain route configurations
*/
export const meetingDomainRoutes: DomainRouteConfig[] = [
{
route: {
path: 'room/:room-id',
loadComponent: () => import('../pages/meeting/meeting.component').then((m) => m.MeetingComponent),
canActivate: [
runGuardsSerially(
extractRoomQueryParamsGuard,
validateRoomAccessGuard,
removeQueryParamsGuard(['secret', WebComponentProperty.E2EE_KEY])
)
]
}
},
{
route: {
path: 'disconnected',
loadComponent: () => import('../pages/end-meeting/end-meeting.component').then((m) => m.EndMeetingComponent)
}
}
];

View File

@ -0,0 +1,35 @@
import { DomainRouteConfig } from '../../../shared/models/domain-routes.model';
import { validateRecordingAccessGuard } from '../guards/recording-validate-access.guard';
/**
* Recordings domain public route configurations
*/
export const recordingsDomainRoutes: DomainRouteConfig[] = [
{
route: {
path: 'recording/:recording-id',
loadComponent: () =>
import('../pages/view-recording/view-recording.component').then((m) => m.ViewRecordingComponent),
canActivate: [validateRecordingAccessGuard]
}
}
];
/**
* Console child routes for recordings domain
*/
export const recordingsConsoleRoutes: DomainRouteConfig[] = [
{
route: {
path: 'recordings',
loadComponent: () => import('../pages/recordings/recordings.component').then((m) => m.RecordingsComponent)
},
navMetadata: {
label: 'Recordings',
route: 'recordings',
icon: 'video_library',
iconClass: 'ov-recording-icon',
order: 3
}
}
];

View File

@ -0,0 +1,59 @@
import { WebComponentProperty } from '@openvidu-meet/typings';
import { extractRecordingQueryParamsGuard } from '../../../shared/guards/extract-query-params.guard';
import { removeQueryParamsGuard } from '../../../shared/guards/remove-query-params.guard';
import { runGuardsSerially } from '../../../shared/guards/run-serially.guard';
import { DomainRouteConfig } from '../../../shared/models/domain-routes.model';
import { checkEditableRoomGuard } from '../guards/room-edit-check.guard';
import { validateRoomRecordingsAccessGuard } from '../guards/room-validate-access.guard';
/**
* Rooms domain route configurations
*/
export const roomsDomainRoutes: DomainRouteConfig[] = [
{
route: {
path: 'room/:room-id/recordings',
loadComponent: () =>
import('../pages/room-recordings/room-recordings.component').then((m) => m.RoomRecordingsComponent),
canActivate: [
runGuardsSerially(
extractRecordingQueryParamsGuard,
validateRoomRecordingsAccessGuard,
removeQueryParamsGuard(['secret', WebComponentProperty.E2EE_KEY])
)
]
}
}
];
/**
* Console child routes for rooms domain
*/
export const roomsConsoleRoutes: DomainRouteConfig[] = [
{
route: {
path: 'rooms',
loadComponent: () => import('../pages/rooms/rooms.component').then((m) => m.RoomsComponent)
},
navMetadata: {
label: 'Rooms',
route: 'rooms',
icon: 'video_chat',
iconClass: 'ov-room-icon',
order: 2
}
},
{
route: {
path: 'rooms/new',
loadComponent: () => import('../pages/room-wizard/room-wizard.component').then((m) => m.RoomWizardComponent)
}
},
{
route: {
path: 'rooms/:roomId/edit',
loadComponent: () => import('../pages/room-wizard/room-wizard.component').then((m) => m.RoomWizardComponent),
canActivate: [checkEditableRoomGuard]
}
}
];

View File

@ -0,0 +1,39 @@
import { Route } from '@angular/router';
/**
* Navigation metadata for a domain route that should appear in the console navigation
*/
export interface DomainNavMetadata {
/** Display label for the navigation link */
label: string;
/** Route path (relative to console) */
route: string;
/** Material icon name */
icon: string;
/** Optional CSS class for the icon */
iconClass?: string;
/** Optional order for sorting navigation items */
order?: number;
}
/**
* Complete route configuration for a domain
* Includes both the route definition and optional navigation metadata
*/
export interface DomainRouteConfig {
/** Angular route configuration */
route: Route;
/** Optional navigation metadata (if this route should appear in console nav) */
navMetadata?: DomainNavMetadata;
}
/**
* Domain routes registry
* Maps domain identifiers to their route configurations
*/
export interface DomainRoutesRegistry {
/** Console child routes (appear in the authenticated console area) */
consoleRoutes: DomainRouteConfig[];
/** Public routes (standalone routes outside console) */
publicRoutes: DomainRouteConfig[];
}

View File

@ -1,5 +1,8 @@
export * from './app.model';
export * from './config.model';
export * from './domain-routes.model';
export * from './navigation.model';
export * from './notification.model';
export * from './sidenav.model';
export * from './storage.model';

View File

@ -1,132 +1,25 @@
import { Routes } from '@angular/router';
import { WebComponentProperty } from '@openvidu-meet/typings';
import { checkUserAuthenticatedGuard, checkUserNotAuthenticatedGuard } from '../../domains/auth/guards/auth.guard';
import { validateRecordingAccessGuard } from '../../domains/recordings/guards/recording-validate-access.guard';
import { checkEditableRoomGuard } from '../../domains/rooms/guards/room-edit-check.guard';
import {
validateRoomAccessGuard,
validateRoomRecordingsAccessGuard
} from '../../domains/rooms/guards/room-validate-access.guard';
import { extractRecordingQueryParamsGuard, extractRoomQueryParamsGuard } from '../guards/extract-query-params.guard';
import { removeQueryParamsGuard } from '../guards/remove-query-params.guard';
import { runGuardsSerially } from '../guards/run-serially.guard';
export const baseRoutes: Routes = [
{
path: 'login',
loadComponent: () => import('../../domains/auth/pages/login/login.component').then((m) => m.LoginComponent),
canActivate: [checkUserNotAuthenticatedGuard]
},
{
path: 'room/:room-id',
loadComponent: () =>
import('../../domains/meeting/pages/meeting/meeting.component').then((m) => m.MeetingComponent),
canActivate: [
runGuardsSerially(
extractRoomQueryParamsGuard,
validateRoomAccessGuard,
removeQueryParamsGuard(['secret', WebComponentProperty.E2EE_KEY])
)
]
},
{
path: 'room/:room-id/recordings',
loadComponent: () =>
import('../../domains/rooms/pages/room-recordings/room-recordings.component').then(
(m) => m.RoomRecordingsComponent
),
canActivate: [
runGuardsSerially(
extractRecordingQueryParamsGuard,
validateRoomRecordingsAccessGuard,
removeQueryParamsGuard(['secret', WebComponentProperty.E2EE_KEY])
)
]
},
{
path: 'recording/:recording-id',
loadComponent: () =>
import('../../domains/recordings/pages/view-recording/view-recording.component').then(
(m) => m.ViewRecordingComponent
),
canActivate: [validateRecordingAccessGuard]
},
{
path: 'disconnected',
loadComponent: () =>
import('../../domains/meeting/pages/end-meeting/end-meeting.component').then((m) => m.EndMeetingComponent)
},
{
path: 'error',
loadComponent: () => import('../../domains/console/pages/error/error.component').then((m) => m.ErrorComponent)
},
{
path: '',
loadComponent: () =>
import('../../domains/console/pages/console/console.component').then((m) => m.ConsoleComponent),
canActivate: [checkUserAuthenticatedGuard],
children: [
{
path: '',
redirectTo: 'overview',
pathMatch: 'full'
},
{
path: 'overview',
loadComponent: () =>
import('../../domains/console/pages/overview/overview.component').then((m) => m.OverviewComponent)
},
{
path: 'rooms',
loadComponent: () =>
import('../../domains/rooms/pages/rooms/rooms.component').then((m) => m.RoomsComponent)
},
{
path: 'rooms/new',
import { authDomainRoutes } from '../../domains/auth/routes/auth.routes';
import { consoleDomainRoutes } from '../../domains/console/routes/console.routes';
import { meetingDomainRoutes } from '../../domains/meeting/routes/meeting.routes';
import { recordingsDomainRoutes } from '../../domains/recordings/routes/recordings.routes';
import { roomsDomainRoutes } from '../../domains/rooms/routes/rooms.routes';
loadComponent: () =>
import('../../domains/rooms/pages/room-wizard/room-wizard.component').then(
(m) => m.RoomWizardComponent
)
},
{
path: 'rooms/:roomId/edit',
loadComponent: () =>
import('../../domains/rooms/pages/room-wizard/room-wizard.component').then(
(m) => m.RoomWizardComponent
),
canActivate: [checkEditableRoomGuard]
},
{
path: 'recordings',
loadComponent: () =>
import('../../domains/recordings/pages/recordings/recordings.component').then(
(m) => m.RecordingsComponent
)
},
{
path: 'embedded',
loadComponent: () =>
import('../../domains/console/pages/embedded/embedded.component').then((m) => m.EmbeddedComponent)
},
{
path: 'users-permissions',
loadComponent: () =>
import('../../domains/console/pages/users-permissions/users-permissions.component').then(
(m) => m.UsersPermissionsComponent
)
},
{
path: 'config',
loadComponent: () =>
import('../../domains/console/pages/config/config.component').then((m) => m.ConfigComponent)
},
// {
// path: 'about',
// component: AboutComponent
// },
{ path: '**', redirectTo: 'overview' }
]
},
export const baseRoutes: Routes = [
// Auth domain routes
...authDomainRoutes.map((config) => config.route),
// Meeting domain routes
...meetingDomainRoutes.map((config) => config.route),
// Rooms domain public routes
...roomsDomainRoutes.map((config) => config.route),
// Recordings domain public routes
...recordingsDomainRoutes.map((config) => config.route),
// Console domain routes (includes console shell, child routes, and guards)
...consoleDomainRoutes,
// Redirect all other routes to the console
{ path: '**', redirectTo: '' }