diff --git a/front/src/router/index.ts b/front/src/router/index.ts index 3e4cfe7fa..6f39f52e9 100644 --- a/front/src/router/index.ts +++ b/front/src/router/index.ts @@ -1,37 +1,13 @@ -import { createRouter, createWebHistory, NavigationGuardNext, RouteLocationNormalized } from 'vue-router' -import store from '~/store' - -function adminPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) { - if (store.state.auth.authenticated && store.state.auth.availablePermissions.settings) { - next() - } else { - console.log('Not authenticated. Redirecting to library.') - next({ name: 'library.index' }) - } -} - -function moderatorPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) { - if (store.state.auth.authenticated && store.state.auth.availablePermissions.moderation) { - next() - } else { - console.log('Not authenticated. Redirecting to library.') - next({ name: 'library.index' }) - } -} - -function libraryPermissions (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) { - if (store.state.auth.authenticated && store.state.auth.availablePermissions.library) { - next() - } else { - console.log('Not authenticated. Redirecting to library.') - next({ name: 'library.index' }) - } -} +import { createRouter, createWebHistory } from 'vue-router' +import routes from './routes' console.log('PROCESS', import.meta.env) + export default createRouter({ history: createWebHistory(import.meta.env.VUE_APP_ROUTER_BASE_URL as string ?? '/'), linkActiveClass: 'active', + routes, + scrollBehavior (to, from, savedPosition) { if (to.meta.preserveScrollPosition) { return savedPosition ?? { left: 0, top: 0 } @@ -46,971 +22,5 @@ export default createRouter({ resolve(savedPosition ?? { left: 0, top: 0 }) }, 100) }) - }, - routes: [ - { - path: '/', - name: 'index', - component: () => - import('~/components/Home.vue') - }, - { - path: '/front', - name: 'front', - redirect: to => { - const { hash, query } = to - return { name: 'index', hash, query } - } - }, - { - path: '/about', - name: 'about', - component: () => - import('~/components/About.vue') - }, - { - // TODO (wvffle): Make it a child of /about to have the active style on the sidebar link - path: '/about/pod', - name: 'about-pod', - component: () => - import('~/components/AboutPod.vue') - }, - { - path: '/login', - name: 'login', - component: () => - import('~/views/auth/Login.vue'), - props: route => ({ next: route.query.next || '/library' }) - }, - { - path: '/notifications', - name: 'notifications', - component: () => - import('~/views/Notifications.vue') - }, - { - path: '/auth/password/reset', - name: 'auth.password-reset', - component: () => - import('~/views/auth/PasswordReset.vue'), - props: route => ({ - defaultEmail: route.query.email - }) - }, - { - path: '/auth/callback', - name: 'auth.callback', - component: () => - import('~/views/auth/Callback.vue'), - props: route => ({ - code: route.query.code, - state: route.query.state - }) - }, - { - path: '/auth/email/confirm', - name: 'auth.email-confirm', - component: () => - import('~/views/auth/EmailConfirm.vue'), - props: route => ({ - defaultKey: route.query.key - }) - }, - { - path: '/search', - name: 'search', - component: () => import('~/views/Search.vue'), - props: route => ({ - initialId: route.query.id, - initialType: route.query.type || 'artists', - initialQuery: route.query.q, - initialPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: '/auth/password/reset/confirm', - name: 'auth.password-reset-confirm', - component: () => - import( - '~/views/auth/PasswordResetConfirm.vue' - ), - props: route => ({ - defaultUid: route.query.uid, - defaultToken: route.query.token - }) - }, - { - path: '/authorize', - name: 'authorize', - component: () => - import('~/components/auth/Authorize.vue'), - props: route => ({ - clientId: route.query.client_id, - redirectUri: route.query.redirect_uri, - scope: route.query.scope, - responseType: route.query.response_type, - nonce: route.query.nonce, - state: route.query.state - }) - }, - { - path: '/signup', - name: 'signup', - component: () => - import('~/views/auth/Signup.vue'), - props: route => ({ - defaultInvitation: route.query.invitation - }) - }, - { - path: '/logout', - name: 'logout', - component: () => - import('~/components/auth/Logout.vue') - }, - { - path: '/settings', - name: 'settings', - component: () => - import('~/components/auth/Settings.vue') - }, - { - path: '/settings/applications/new', - name: 'settings.applications.new', - props: route => ({ - scopes: route.query.scopes, - name: route.query.name, - redirect_uris: route.query.redirect_uris - }), - component: () => - import( - '~/components/auth/ApplicationNew.vue' - ) - }, - { - path: '/settings/plugins', - name: 'settings.plugins', - component: () => - import( - '~/views/auth/Plugins.vue' - ) - }, - { - path: '/settings/applications/:id/edit', - name: 'settings.applications.edit', - component: () => - import( - '~/components/auth/ApplicationEdit.vue' - ), - props: true - }, - ...[{ suffix: '.full', path: '/@:username@:domain' }, { suffix: '', path: '/@:username' }].map((route) => { - return { - path: route.path, - name: `profile${route.suffix}`, - component: () => - import('~/views/auth/ProfileBase.vue'), - props: true, - children: [ - { - path: '', - name: `profile${route.suffix}.overview`, - component: () => - import( - '~/views/auth/ProfileOverview.vue' - ) - }, - { - path: 'activity', - name: `profile${route.suffix}.activity`, - component: () => - import( - '~/views/auth/ProfileActivity.vue' - ) - } - ] - } - }), - { - path: '/favorites', - name: 'favorites', - component: () => - import('~/components/favorites/List.vue'), - props: route => ({ - defaultOrdering: route.query.ordering, - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: '/content', - component: () => - import('~/views/content/Base.vue'), - children: [ - { - path: '', - name: 'content.index', - component: () => - import('~/views/content/Home.vue') - } - ] - }, - { - path: '/content/libraries/tracks', - component: () => - import('~/views/content/Base.vue'), - children: [ - { - path: '', - name: 'content.libraries.files', - component: () => - import( - '~/views/content/libraries/Files.vue' - ), - props: route => ({ - query: route.query.q - }) - } - ] - }, - { - path: '/content/libraries', - component: () => - import('~/views/content/Base.vue'), - children: [ - { - path: '', - name: 'content.libraries.index', - component: () => - import( - '~/views/content/libraries/Home.vue' - ) - } - ] - }, - { - path: '/content/remote', - component: () => - import('~/views/content/Base.vue'), - children: [ - { - path: '', - name: 'content.remote.index', - component: () => - import('~/views/content/remote/Home.vue') - } - ] - }, - { - path: '/manage/settings', - name: 'manage.settings', - beforeEnter: adminPermissions, - component: () => - import('~/views/admin/Settings.vue') - }, - { - path: '/manage/library', - beforeEnter: libraryPermissions, - component: () => - import('~/views/admin/library/Base.vue'), - children: [ - { - path: 'edits', - name: 'manage.library.edits', - component: () => - import( - '~/views/admin/library/EditsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'artists', - name: 'manage.library.artists', - component: () => - import( - '~/views/admin/library/ArtistsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'artists/:id', - name: 'manage.library.artists.detail', - component: () => - import( - '~/views/admin/library/ArtistDetail.vue' - ), - props: true - }, - { - path: 'channels', - name: 'manage.channels', - component: () => - import( - '~/views/admin/ChannelsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'channels/:id', - name: 'manage.channels.detail', - component: () => - import( - '~/views/admin/ChannelDetail.vue' - ), - props: true - }, - { - path: 'albums', - name: 'manage.library.albums', - component: () => - import( - '~/views/admin/library/AlbumsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'albums/:id', - name: 'manage.library.albums.detail', - component: () => - import( - '~/views/admin/library/AlbumDetail.vue' - ), - props: true - }, - { - path: 'tracks', - name: 'manage.library.tracks', - component: () => - import( - '~/views/admin/library/TracksList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'tracks/:id', - name: 'manage.library.tracks.detail', - component: () => - import( - '~/views/admin/library/TrackDetail.vue' - ), - props: true - }, - { - path: 'libraries', - name: 'manage.library.libraries', - component: () => - import( - '~/views/admin/library/LibrariesList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'libraries/:id', - name: 'manage.library.libraries.detail', - component: () => - import( - '~/views/admin/library/LibraryDetail.vue' - ), - props: true - }, - { - path: 'uploads', - name: 'manage.library.uploads', - component: () => - import( - '~/views/admin/library/UploadsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'uploads/:id', - name: 'manage.library.uploads.detail', - component: () => - import( - '~/views/admin/library/UploadDetail.vue' - ), - props: true - }, - { - path: 'tags', - name: 'manage.library.tags', - component: () => - import( - '~/views/admin/library/TagsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'tags/:id', - name: 'manage.library.tags.detail', - component: () => - import( - '~/views/admin/library/TagDetail.vue' - ), - props: true - } - ] - }, - { - path: '/manage/users', - beforeEnter: adminPermissions, - component: () => - import('~/views/admin/users/Base.vue'), - children: [ - { - path: 'users', - name: 'manage.users.users.list', - component: () => - import( - '~/views/admin/users/UsersList.vue' - ) - }, - { - path: 'invitations', - name: 'manage.users.invitations.list', - component: () => - import( - '~/views/admin/users/InvitationsList.vue' - ) - } - ] - }, - { - path: '/manage/moderation', - beforeEnter: moderatorPermissions, - component: () => - import('~/views/admin/moderation/Base.vue'), - children: [ - { - path: 'domains', - name: 'manage.moderation.domains.list', - component: () => - import( - '~/views/admin/moderation/DomainsList.vue' - ) - }, - { - path: 'domains/:id', - name: 'manage.moderation.domains.detail', - component: () => - import( - '~/views/admin/moderation/DomainsDetail.vue' - ), - props: true - }, - { - path: 'accounts', - name: 'manage.moderation.accounts.list', - component: () => - import( - '~/views/admin/moderation/AccountsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q - } - } - }, - { - path: 'accounts/:id', - name: 'manage.moderation.accounts.detail', - component: () => - import( - '~/views/admin/moderation/AccountsDetail.vue' - ), - props: true - }, - { - path: 'reports', - name: 'manage.moderation.reports.list', - component: () => - import( - '~/views/admin/moderation/ReportsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q, - updateUrl: true - } - } - }, - { - path: 'reports/:id', - name: 'manage.moderation.reports.detail', - component: () => - import( - '~/views/admin/moderation/ReportDetail.vue' - ), - props: true - }, - { - path: 'requests', - name: 'manage.moderation.requests.list', - component: () => - import( - '~/views/admin/moderation/RequestsList.vue' - ), - props: route => { - return { - defaultQuery: route.query.q, - updateUrl: true - } - } - }, - { - path: 'requests/:id', - name: 'manage.moderation.requests.detail', - component: () => - import( - '~/views/admin/moderation/RequestDetail.vue' - ), - props: true - } - ] - }, - { - path: '/library', - component: () => - import('~/components/library/Library.vue'), - children: [ - { - path: '', - component: () => - import('~/components/library/Home.vue'), - name: 'library.index' - }, - { - path: 'me', - component: () => - import('~/components/library/Home.vue'), - name: 'library.me', - props: () => ({ - scope: 'me' - }) - }, - { - path: 'artists/', - name: 'library.artists.browse', - component: () => - import( - '~/components/library/Artists.vue' - ), - props: route => ({ - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultTags: Array.isArray(route.query.tag || []) - ? route.query.tag - : [route.query.tag], - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'me/artists', - name: 'library.artists.me', - component: () => - import( - '~/components/library/Artists.vue' - ), - props: route => ({ - scope: 'me', - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultTags: Array.isArray(route.query.tag || []) - ? route.query.tag - : [route.query.tag], - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'albums/', - name: 'library.albums.browse', - component: () => - import( - '~/components/library/Albums.vue' - ), - props: route => ({ - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultTags: Array.isArray(route.query.tag || []) - ? route.query.tag - : [route.query.tag], - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'podcasts/', - name: 'library.podcasts.browse', - component: () => - import('~/components/library/Podcasts.vue'), - props: route => ({ - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultTags: Array.isArray(route.query.tag || []) - ? route.query.tag - : [route.query.tag], - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'me/albums', - name: 'library.albums.me', - component: () => - import( - '~/components/library/Albums.vue' - ), - props: route => ({ - scope: 'me', - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultTags: Array.isArray(route.query.tag || []) - ? route.query.tag - : [route.query.tag], - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'radios/', - name: 'library.radios.browse', - component: () => - import( - '~/components/library/Radios.vue' - ), - props: route => ({ - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'me/radios/', - name: 'library.radios.me', - component: () => - import( - '~/components/library/Radios.vue' - ), - props: route => ({ - scope: 'me', - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'radios/build', - name: 'library.radios.build', - component: () => - import( - '~/components/library/radios/Builder.vue' - ), - props: true - }, - { - path: 'radios/build/:id', - name: 'library.radios.edit', - component: () => - import( - '~/components/library/radios/Builder.vue' - ), - props: true - }, - { - path: 'radios/:id', - name: 'library.radios.detail', - component: () => - import('~/views/radios/Detail.vue'), - props: true - }, - { - path: 'playlists/', - name: 'library.playlists.browse', - component: () => - import('~/views/playlists/List.vue'), - props: route => ({ - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'me/playlists/', - name: 'library.playlists.me', - component: () => - import('~/views/playlists/List.vue'), - props: route => ({ - scope: 'me', - defaultOrdering: route.query.ordering, - defaultQuery: route.query.query, - defaultPage: route.query.page ? +route.query.page : undefined - }) - }, - { - path: 'playlists/:id', - name: 'library.playlists.detail', - component: () => - import('~/views/playlists/Detail.vue'), - props: route => ({ - id: route.params.id, - defaultEdit: route.query.mode === 'edit' - }) - }, - { - path: 'tags/:id', - name: 'library.tags.detail', - component: () => - import( - '~/components/library/TagDetail.vue' - ), - props: true - }, - { - path: 'artists/:id', - component: () => - import( - '~/components/library/ArtistBase.vue' - ), - props: true, - children: [ - { - path: '', - name: 'library.artists.detail', - component: () => - import( - '~/components/library/ArtistDetail.vue' - ) - }, - { - path: 'edit', - name: 'library.artists.edit', - component: () => - import( - '~/components/library/ArtistEdit.vue' - ) - }, - { - path: 'edit/:editId', - name: 'library.artists.edit.detail', - component: () => - import( - '~/components/library/EditDetail.vue' - ), - props: true - } - ] - }, - { - path: 'albums/:id', - component: () => - import( - '~/components/library/AlbumBase.vue' - ), - props: true, - children: [ - { - path: '', - name: 'library.albums.detail', - component: () => - import( - '~/components/library/AlbumDetail.vue' - ) - }, - { - path: 'edit', - name: 'library.albums.edit', - component: () => - import( - '~/components/library/AlbumEdit.vue' - ) - }, - { - path: 'edit/:editId', - name: 'library.albums.edit.detail', - component: () => - import( - '~/components/library/EditDetail.vue' - ), - props: true - } - ] - }, - { - path: 'tracks/:id', - component: () => - import( - '~/components/library/TrackBase.vue' - ), - props: true, - children: [ - { - path: '', - name: 'library.tracks.detail', - component: () => - import( - '~/components/library/TrackDetail.vue' - ) - }, - { - path: 'edit', - name: 'library.tracks.edit', - component: () => - import( - '~/components/library/TrackEdit.vue' - ) - }, - { - path: 'edit/:editId', - name: 'library.tracks.edit.detail', - component: () => - import( - '~/components/library/EditDetail.vue' - ), - props: true - } - ] - }, - { - path: 'uploads/:id', - name: 'library.uploads.detail', - props: true, - component: () => - import( - '~/components/library/UploadDetail.vue' - ) - }, - { - // browse a single library via it's uuid - path: ':id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', - props: true, - component: () => - import( - '~/views/library/DetailBase.vue' - ), - children: [ - { - path: '', - name: 'library.detail', - component: () => - import( - '~/views/library/DetailOverview.vue' - ) - }, - { - path: 'albums', - name: 'library.detail.albums', - component: () => - import( - '~/views/library/DetailAlbums.vue' - ) - }, - { - path: 'tracks', - name: 'library.detail.tracks', - component: () => - import( - '~/views/library/DetailTracks.vue' - ) - }, - { - path: 'edit', - name: 'library.detail.edit', - component: () => - import( - '~/views/library/Edit.vue' - ) - }, - { - path: 'upload', - name: 'library.detail.upload', - component: () => - import( - '~/views/library/Upload.vue' - ), - props: route => ({ - defaultImportReference: route.query.import - }) - } - ] - } - ] - }, - { - path: '/channels/:id', - props: true, - component: () => - import( - '~/views/channels/DetailBase.vue' - ), - children: [ - { - path: '', - name: 'channels.detail', - component: () => - import( - '~/views/channels/DetailOverview.vue' - ) - }, - { - path: 'episodes', - name: 'channels.detail.episodes', - component: () => - import( - '~/views/channels/DetailEpisodes.vue' - ) - } - ] - }, - { - path: '/subscriptions', - name: 'subscriptions', - props: route => { - return { - defaultQuery: route.query.q - } - }, - component: () => - import( - '~/views/channels/SubscriptionsList.vue' - ) - }, - { - path: '/index.html', - redirect: '/' - }, - { - path: '/:pathMatch(.*)*', - name: '404', - component: () => - import('~/components/PageNotFound.vue') - } - ] + } }) diff --git a/front/src/router/routes/auth.ts b/front/src/router/routes/auth.ts new file mode 100644 index 000000000..ab8e8f09d --- /dev/null +++ b/front/src/router/routes/auth.ts @@ -0,0 +1,65 @@ +import { RouteRecordRaw } from 'vue-router' + +export default [ + { + path: '/login', + name: 'login', + component: () => import('~/views/auth/Login.vue'), + // TODO (wvffle): Use named routes EVERYWHERE + props: route => ({ next: route.query.next || '/library' }) + }, + { + path: '/auth/password/reset', + name: 'auth.password-reset', + component: () => import('~/views/auth/PasswordReset.vue'), + props: route => ({ defaultEmail: route.query.email }) + }, + { + path: '/auth/callback', + name: 'auth.callback', + component: () => import('~/views/auth/Callback.vue'), + props: route => ({ + code: route.query.code, + state: route.query.state + }) + }, + { + path: '/auth/email/confirm', + name: 'auth.email-confirm', + component: () => import('~/views/auth/EmailConfirm.vue'), + props: route => ({ defaultKey: route.query.key }) + }, + { + path: '/auth/password/reset/confirm', + name: 'auth.password-reset-confirm', + component: () => import('~/views/auth/PasswordResetConfirm.vue'), + props: route => ({ + defaultUid: route.query.uid, + defaultToken: route.query.token + }) + }, + { + path: '/authorize', + name: 'authorize', + component: () => import('~/components/auth/Authorize.vue'), + props: route => ({ + clientId: route.query.client_id, + redirectUri: route.query.redirect_uri, + scope: route.query.scope, + responseType: route.query.response_type, + nonce: route.query.nonce, + state: route.query.state + }) + }, + { + path: '/signup', + name: 'signup', + component: () => import('~/views/auth/Signup.vue'), + props: route => ({ defaultInvitation: route.query.invitation }) + }, + { + path: '/logout', + name: 'logout', + component: () => import('~/components/auth/Logout.vue') + }, +] as RouteRecordRaw[] \ No newline at end of file diff --git a/front/src/router/routes/content.ts b/front/src/router/routes/content.ts new file mode 100644 index 000000000..5091c791b --- /dev/null +++ b/front/src/router/routes/content.ts @@ -0,0 +1,41 @@ +import { RouteRecordRaw } from 'vue-router' + +export default [ + { + path: '/content', + component: () => import('~/views/content/Base.vue'), + children: [{ + path: '', + name: 'content.index', + component: () => import('~/views/content/Home.vue') + }] + }, + { + path: '/content/libraries/tracks', + component: () => import('~/views/content/Base.vue'), + children: [{ + path: '', + name: 'content.libraries.files', + component: () => import('~/views/content/libraries/Files.vue'), + props: route => ({ query: route.query.q }) + }] + }, + { + path: '/content/libraries', + component: () => import('~/views/content/Base.vue'), + children: [{ + path: '', + name: 'content.libraries.index', + component: () => import('~/views/content/libraries/Home.vue') + }] + }, + { + path: '/content/remote', + component: () => import('~/views/content/Base.vue'), + children: [{ + path: '', + name: 'content.remote.index', + component: () => import('~/views/content/remote/Home.vue') + }] + }, +] as RouteRecordRaw[] \ No newline at end of file diff --git a/front/src/router/routes/index.ts b/front/src/router/routes/index.ts new file mode 100644 index 000000000..6aba33a9e --- /dev/null +++ b/front/src/router/routes/index.ts @@ -0,0 +1,101 @@ +import { RouteRecordRaw } from 'vue-router' +import settings from './settings' +import library from './library' +import content from './content' +import manage from './manage' +import auth from './auth' +import user from './user' + +export default [ + { + path: '/', + name: 'index', + component: () => import('~/components/Home.vue') + }, + { + path: '/index.html', + redirect: to => { + const { hash, query } = to + return { name: 'index', hash, query } + } + }, + { + path: '/front', + name: 'front', + redirect: to => { + const { hash, query } = to + return { name: 'index', hash, query } + } + }, + + { + path: '/about', + name: 'about', + component: () => import('~/components/About.vue') + }, + { + // TODO (wvffle): Make it a child of /about to have the active style on the sidebar link + path: '/about/pod', + name: 'about-pod', + component: () => import('~/components/AboutPod.vue') + }, + { + path: '/notifications', + name: 'notifications', + component: () => import('~/views/Notifications.vue') + }, + { + path: '/search', + name: 'search', + component: () => import('~/views/Search.vue'), + props: route => ({ + initialId: route.query.id, + initialType: route.query.type || 'artists', + initialQuery: route.query.q, + initialPage: route.query.page ? +route.query.page : undefined + }) + }, + ...auth, + ...settings, + ...user, + { + path: '/favorites', + name: 'favorites', + component: () => import('~/components/favorites/List.vue'), + props: route => ({ + defaultOrdering: route.query.ordering, + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + ...content, + ...manage, + ...library, + { + path: '/channels/:id', + props: true, + component: () => import('~/views/channels/DetailBase.vue'), + children: [ + { + path: '', + name: 'channels.detail', + component: () => import('~/views/channels/DetailOverview.vue') + }, + { + path: 'episodes', + name: 'channels.detail.episodes', + component: () => import('~/views/channels/DetailEpisodes.vue') + } + ] + }, + { + path: '/subscriptions', + name: 'subscriptions', + component: () => import('~/views/channels/SubscriptionsList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: '/:pathMatch(.*)*', + name: '404', + component: () => import('~/components/PageNotFound.vue') + } +] as RouteRecordRaw[] \ No newline at end of file diff --git a/front/src/router/routes/library.ts b/front/src/router/routes/library.ts new file mode 100644 index 000000000..d799b28eb --- /dev/null +++ b/front/src/router/routes/library.ts @@ -0,0 +1,274 @@ +import { RouteRecordRaw } from 'vue-router' + +export default [ + { + path: '/library', + component: () => import('~/components/library/Library.vue'), + children: [ + { + path: '', + component: () => import('~/components/library/Home.vue'), + name: 'library.index' + }, + { + path: 'me', + component: () => import('~/components/library/Home.vue'), + name: 'library.me', + props: () => ({ scope: 'me' }) + }, + { + path: 'artists/', + name: 'library.artists.browse', + component: () => import('~/components/library/Artists.vue'), + props: route => ({ + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultTags: Array.isArray(route.query.tag || []) + ? route.query.tag + : [route.query.tag], + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'me/artists', + name: 'library.artists.me', + component: () => import('~/components/library/Artists.vue'), + props: route => ({ + scope: 'me', + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultTags: Array.isArray(route.query.tag || []) + ? route.query.tag + : [route.query.tag], + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'albums/', + name: 'library.albums.browse', + component: () => import('~/components/library/Albums.vue'), + props: route => ({ + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultTags: Array.isArray(route.query.tag || []) + ? route.query.tag + : [route.query.tag], + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'podcasts/', + name: 'library.podcasts.browse', + component: () => import('~/components/library/Podcasts.vue'), + props: route => ({ + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultTags: Array.isArray(route.query.tag || []) + ? route.query.tag + : [route.query.tag], + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'me/albums', + name: 'library.albums.me', + component: () => import('~/components/library/Albums.vue'), + props: route => ({ + scope: 'me', + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultTags: Array.isArray(route.query.tag || []) + ? route.query.tag + : [route.query.tag], + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'radios/', + name: 'library.radios.browse', + component: () => import('~/components/library/Radios.vue'), + props: route => ({ + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'me/radios/', + name: 'library.radios.me', + component: () => import('~/components/library/Radios.vue'), + props: route => ({ + scope: 'me', + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'radios/build', + name: 'library.radios.build', + component: () => import('~/components/library/radios/Builder.vue'), + props: true + }, + { + path: 'radios/build/:id', + name: 'library.radios.edit', + component: () => import('~/components/library/radios/Builder.vue'), + props: true + }, + { + path: 'radios/:id', + name: 'library.radios.detail', + component: () => import('~/views/radios/Detail.vue'), + props: true + }, + { + path: 'playlists/', + name: 'library.playlists.browse', + component: () => import('~/views/playlists/List.vue'), + props: route => ({ + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'me/playlists/', + name: 'library.playlists.me', + component: () => import('~/views/playlists/List.vue'), + props: route => ({ + scope: 'me', + defaultOrdering: route.query.ordering, + defaultQuery: route.query.query, + defaultPage: route.query.page ? +route.query.page : undefined + }) + }, + { + path: 'playlists/:id', + name: 'library.playlists.detail', + component: () => import('~/views/playlists/Detail.vue'), + props: route => ({ + id: route.params.id, + defaultEdit: route.query.mode === 'edit' + }) + }, + { + path: 'tags/:id', + name: 'library.tags.detail', + component: () => import('~/components/library/TagDetail.vue'), + props: true + }, + { + path: 'artists/:id', + component: () => import('~/components/library/ArtistBase.vue'), + props: true, + children: [ + { + path: '', + name: 'library.artists.detail', + component: () => import('~/components/library/ArtistDetail.vue') + }, + { + path: 'edit', + name: 'library.artists.edit', + component: () => import('~/components/library/ArtistEdit.vue') + }, + { + path: 'edit/:editId', + name: 'library.artists.edit.detail', + component: () => import('~/components/library/EditDetail.vue'), + props: true + } + ] + }, + { + path: 'albums/:id', + component: () => import('~/components/library/AlbumBase.vue'), + props: true, + children: [ + { + path: '', + name: 'library.albums.detail', + component: () => import('~/components/library/AlbumDetail.vue') + }, + { + path: 'edit', + name: 'library.albums.edit', + component: () => import('~/components/library/AlbumEdit.vue') + }, + { + path: 'edit/:editId', + name: 'library.albums.edit.detail', + component: () => import('~/components/library/EditDetail.vue'), + props: true + } + ] + }, + { + path: 'tracks/:id', + component: () => import('~/components/library/TrackBase.vue'), + props: true, + children: [ + { + path: '', + name: 'library.tracks.detail', + component: () => import('~/components/library/TrackDetail.vue') + }, + { + path: 'edit', + name: 'library.tracks.edit', + component: () => import('~/components/library/TrackEdit.vue') + }, + { + path: 'edit/:editId', + name: 'library.tracks.edit.detail', + component: () => import('~/components/library/EditDetail.vue'), + props: true + } + ] + }, + { + path: 'uploads/:id', + name: 'library.uploads.detail', + props: true, + component: () => import('~/components/library/UploadDetail.vue') + }, + { + // browse a single library via it's uuid + path: ':id([0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12})', + props: true, + component: () => import('~/views/library/DetailBase.vue'), + children: [ + { + path: '', + name: 'library.detail', + component: () => import('~/views/library/DetailOverview.vue') + }, + { + path: 'albums', + name: 'library.detail.albums', + component: () => import('~/views/library/DetailAlbums.vue') + }, + { + path: 'tracks', + name: 'library.detail.tracks', + component: () => import('~/views/library/DetailTracks.vue') + }, + { + path: 'edit', + name: 'library.detail.edit', + component: () => import('~/views/library/Edit.vue') + }, + { + path: 'upload', + name: 'library.detail.upload', + component: () => import('~/views/library/Upload.vue'), + props: route => ({ + defaultImportReference: route.query.import + }) + } + ] + } + ] + } +] as RouteRecordRaw[] diff --git a/front/src/router/routes/manage.ts b/front/src/router/routes/manage.ts new file mode 100644 index 000000000..0c4b66da6 --- /dev/null +++ b/front/src/router/routes/manage.ts @@ -0,0 +1,189 @@ +import { NavigationGuardNext, RouteLocationNormalized, RouteRecordRaw } from 'vue-router' +import { Permission } from '~/store/auth' +import store from '~/store' + +const hasPermissions = (permission: Permission) => (to: RouteLocationNormalized, from: RouteLocationNormalized, next: NavigationGuardNext) => { + if (store.state.auth.authenticated && store.state.auth.availablePermissions[permission]) { + return next() + } + + console.log('Not authenticated. Redirecting to library.') + next({ name: 'library.index' }) +} + +export default [ + { + path: '/manage/settings', + name: 'manage.settings', + beforeEnter: hasPermissions('admin'), + component: () => import('~/views/admin/Settings.vue') + }, + { + path: '/manage/library', + beforeEnter: hasPermissions('library'), + component: () => import('~/views/admin/library/Base.vue'), + children: [ + { + path: 'edits', + name: 'manage.library.edits', + component: () => import('~/views/admin/library/EditsList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'artists', + name: 'manage.library.artists', + component: () => import('~/views/admin/library/ArtistsList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'artists/:id', + name: 'manage.library.artists.detail', + component: () => import('~/views/admin/library/ArtistDetail.vue'), + props: true + }, + { + path: 'channels', + name: 'manage.channels', + component: () => import('~/views/admin/ChannelsList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'channels/:id', + name: 'manage.channels.detail', + component: () => import('~/views/admin/ChannelDetail.vue'), + props: true + }, + { + path: 'albums', + name: 'manage.library.albums', + component: () => import('~/views/admin/library/AlbumsList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'albums/:id', + name: 'manage.library.albums.detail', + component: () => import('~/views/admin/library/AlbumDetail.vue'), + props: true + }, + { + path: 'tracks', + name: 'manage.library.tracks', + component: () => import('~/views/admin/library/TracksList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'tracks/:id', + name: 'manage.library.tracks.detail', + component: () => import('~/views/admin/library/TrackDetail.vue'), + props: true + }, + { + path: 'libraries', + name: 'manage.library.libraries', + component: () => import('~/views/admin/library/LibrariesList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'libraries/:id', + name: 'manage.library.libraries.detail', + component: () => import('~/views/admin/library/LibraryDetail.vue'), + props: true + }, + { + path: 'uploads', + name: 'manage.library.uploads', + component: () => import('~/views/admin/library/UploadsList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'uploads/:id', + name: 'manage.library.uploads.detail', + component: () => import('~/views/admin/library/UploadDetail.vue'), + props: true + }, + { + path: 'tags', + name: 'manage.library.tags', + component: () => import('~/views/admin/library/TagsList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'tags/:id', + name: 'manage.library.tags.detail', + component: () => import('~/views/admin/library/TagDetail.vue'), + props: true + } + ] + }, + { + path: '/manage/users', + beforeEnter: hasPermissions('admin'), + component: () => import('~/views/admin/users/Base.vue'), + children: [ + { + path: 'users', + name: 'manage.users.users.list', + component: () => import('~/views/admin/users/UsersList.vue') + }, + { + path: 'invitations', + name: 'manage.users.invitations.list', + component: () => import('~/views/admin/users/InvitationsList.vue') + } + ] + }, + { + path: '/manage/moderation', + beforeEnter: hasPermissions('moderation'), + component: () => import('~/views/admin/moderation/Base.vue'), + children: [ + { + path: 'domains', + name: 'manage.moderation.domains.list', + component: () => import('~/views/admin/moderation/DomainsList.vue') + }, + { + path: 'domains/:id', + name: 'manage.moderation.domains.detail', + component: () => import('~/views/admin/moderation/DomainsDetail.vue'), + props: true + }, + { + path: 'accounts', + name: 'manage.moderation.accounts.list', + component: () => import('~/views/admin/moderation/AccountsList.vue'), + props: route => ({ defaultQuery: route.query.q }) + }, + { + path: 'accounts/:id', + name: 'manage.moderation.accounts.detail', + component: () => import('~/views/admin/moderation/AccountsDetail.vue'), + props: true + }, + { + path: 'reports', + name: 'manage.moderation.reports.list', + component: () => import('~/views/admin/moderation/ReportsList.vue'), + props: route => ({ defaultQuery: route.query.q, updateUrl: true }) + }, + { + path: 'reports/:id', + name: 'manage.moderation.reports.detail', + component: () => import('~/views/admin/moderation/ReportDetail.vue'), + props: true + }, + { + path: 'requests', + name: 'manage.moderation.requests.list', + component: () => import('~/views/admin/moderation/RequestsList.vue'), + props: route => ({ defaultQuery: route.query.q, updateUrl: true }) + }, + { + path: 'requests/:id', + name: 'manage.moderation.requests.detail', + component: () => import('~/views/admin/moderation/RequestDetail.vue'), + props: true + } + ] + }, +] as RouteRecordRaw[] diff --git a/front/src/router/routes/settings.ts b/front/src/router/routes/settings.ts new file mode 100644 index 000000000..2d4f1185b --- /dev/null +++ b/front/src/router/routes/settings.ts @@ -0,0 +1,30 @@ +import { RouteRecordRaw } from 'vue-router' + +export default [ + { + path: '/settings', + name: 'settings', + component: () => import('~/components/auth/Settings.vue') + }, + { + path: '/settings/applications/new', + name: 'settings.applications.new', + props: route => ({ + scopes: route.query.scopes, + name: route.query.name, + redirect_uris: route.query.redirect_uris + }), + component: () => import('~/components/auth/ApplicationNew.vue') + }, + { + path: '/settings/plugins', + name: 'settings.plugins', + component: () => import('~/views/auth/Plugins.vue') + }, + { + path: '/settings/applications/:id/edit', + name: 'settings.applications.edit', + component: () => import('~/components/auth/ApplicationEdit.vue'), + props: true + }, +] as RouteRecordRaw[] \ No newline at end of file diff --git a/front/src/router/routes/user.ts b/front/src/router/routes/user.ts new file mode 100644 index 000000000..59fe1dc38 --- /dev/null +++ b/front/src/router/routes/user.ts @@ -0,0 +1,25 @@ +import { RouteRecordRaw } from 'vue-router' + +export default [ + { suffix: '.full', path: '/@:username@:domain' }, + { suffix: '', path: '/@:username' } +].map((route) => { + return { + path: route.path, + name: `profile${route.suffix}`, + component: () => import('~/views/auth/ProfileBase.vue'), + props: true, + children: [ + { + path: '', + name: `profile${route.suffix}.overview`, + component: () => import('~/views/auth/ProfileOverview.vue') + }, + { + path: 'activity', + name: `profile${route.suffix}.activity`, + component: () => import('~/views/auth/ProfileActivity.vue') + } + ] + } +}) as RouteRecordRaw[] \ No newline at end of file diff --git a/front/src/store/auth.ts b/front/src/store/auth.ts index 0aea65cf4..6cfd5b72f 100644 --- a/front/src/store/auth.ts +++ b/front/src/store/auth.ts @@ -4,7 +4,7 @@ import { Module } from 'vuex' import { RootState } from '~/store/index' import useFormData from '~/composables/useFormData' -export type Permission = 'settings' | 'library' | 'moderation' +export type Permission = 'settings' | 'library' | 'moderation' | 'admin' export interface State { authenticated: boolean username: string @@ -73,7 +73,8 @@ const store: Module = { availablePermissions: { settings: false, library: false, - moderation: false + moderation: false, + admin: false }, profile: null, oauth: getDefaultOauth(), @@ -97,7 +98,8 @@ const store: Module = { state.availablePermissions = { settings: false, library: false, - moderation: false + moderation: false, + admin: false } }, profile: (state, value) => { @@ -113,7 +115,8 @@ const store: Module = { state.availablePermissions = { settings: false, library: false, - moderation: false + moderation: false, + admin: false } } }, diff --git a/front/tsconfig.json b/front/tsconfig.json index 7b0e17ee9..4d2b948f5 100644 --- a/front/tsconfig.json +++ b/front/tsconfig.json @@ -29,8 +29,5 @@ "~/*": ["src/*"] } }, - "vueCompilerOptions": { - "experimentalCompatMode": 2 - }, "include": ["src/**/*.d.ts", "src/**/*.ts", "src/**/*.vue", "vite.config.ts"] }