import { createRouter, createWebHistory } from 'vue-router' import { useAuthStore } from '../stores/auth.js' // FileManagerView is kept synchronous because it is the critical first authenticated // surface rendered at '/'. Lazy-loading it would delay the initial paint for logged-in // users with a valid refresh cookie (the most common app entry point). // All other authenticated routes are lazy-loaded to split them into separate chunks. // See PERF-03 / Plan 11-02 and D-10 in 11-CONTEXT.md. import FileManagerView from '../views/FileManagerView.vue' const routes = [ // File manager is the home — handles both root and folder views. // FileManagerView stays synchronous (see comment above). { path: '/', component: FileManagerView }, { path: '/topics', component: () => import('../views/TopicsView.vue') }, { path: '/topics/:name', component: () => import('../views/TopicsView.vue') }, { path: '/document/:id', component: () => import('../views/DocumentView.vue') }, { path: '/settings', component: () => import('../views/SettingsView.vue') }, { path: '/login', component: () => import('../views/auth/LoginView.vue'), meta: { public: true, layout: 'auth' }, }, { path: '/register', component: () => import('../views/auth/RegisterView.vue'), meta: { public: true, layout: 'auth' }, }, { path: '/password-reset', component: () => import('../views/auth/PasswordResetView.vue'), meta: { public: true, layout: 'auth' }, }, { path: '/password-reset/confirm', component: () => import('../views/auth/NewPasswordView.vue'), meta: { public: true, layout: 'auth' }, }, { path: '/account', redirect: '/settings' }, // Admin panel — standalone route subtree with AdminLayout as the route component. // Children do NOT re-declare requiresAdmin — the guard uses to.matched.some() to // inherit from the parent (Vue Router 4 does not propagate meta to children). { path: '/admin', component: () => import('../layouts/AdminLayout.vue'), meta: { requiresAdmin: true }, children: [ { path: '', component: () => import('../views/admin/AdminOverviewView.vue') }, { path: 'users', component: () => import('../views/admin/AdminUsersView.vue') }, { path: 'quotas', component: () => import('../views/admin/AdminQuotasView.vue') }, { path: 'ai', component: () => import('../views/admin/AdminAiView.vue') }, { path: 'audit', component: () => import('../views/admin/AdminAuditView.vue') }, ], }, { path: '/cloud', name: 'cloud', component: () => import('../views/CloudStorageView.vue'), meta: { requiresAuth: true }, }, { path: '/cloud/:connectionId/:folderId(.*)', name: 'cloud-folder', component: () => import('../views/CloudFolderView.vue'), meta: { requiresAuth: true }, }, { path: '/folders/:folderId', name: 'folder', component: FileManagerView, meta: { requiresAuth: true }, }, { path: '/shared', name: 'shared', component: () => import('../views/SharedView.vue'), meta: { requiresAuth: true }, }, ] const router = createRouter({ history: createWebHistory(), routes, }) // Navigation guard — enforces auth and admin role separation (D-09, D-10). // On page reload the access token is gone (memory-only per CLAUDE.md), so we attempt // a silent refresh via the httpOnly cookie before any role checks fire. router.beforeEach(async (to) => { const authStore = useAuthStore() if (!to.meta.public && !authStore.accessToken) { try { await authStore.refresh() } catch { return { path: '/login', query: { redirect: to.fullPath } } } } const isAdminRoute = to.matched.some(r => r.meta.requiresAdmin) const isAdmin = authStore.user?.role === 'admin' // D-10a: non-admin attempting an admin route → redirect to /. if (isAdminRoute && !isAdmin) { return { path: '/' } } // D-09: admin attempting a non-admin, non-public route → redirect to /admin. if (!isAdminRoute && !to.meta.public && isAdmin) { return { path: '/admin' } } }) export default router