Added admin page.

This commit is contained in:
Andrew Lalis 2023-03-27 19:56:02 +02:00
parent bf6066e949
commit 75680d1041
8 changed files with 91 additions and 13 deletions

View File

@ -1,6 +1,7 @@
package nl.andrewlalis.gymboard_api.domains.auth.controller; package nl.andrewlalis.gymboard_api.domains.auth.controller;
import nl.andrewlalis.gymboard_api.domains.auth.dto.*; import nl.andrewlalis.gymboard_api.domains.auth.dto.*;
import nl.andrewlalis.gymboard_api.domains.auth.model.Role;
import nl.andrewlalis.gymboard_api.domains.auth.model.User; import nl.andrewlalis.gymboard_api.domains.auth.model.User;
import nl.andrewlalis.gymboard_api.domains.auth.model.UserPreferences; import nl.andrewlalis.gymboard_api.domains.auth.model.UserPreferences;
import nl.andrewlalis.gymboard_api.domains.auth.service.UserAccessService; import nl.andrewlalis.gymboard_api.domains.auth.service.UserAccessService;
@ -11,6 +12,9 @@ import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import java.util.ArrayList;
import java.util.List;
@RestController @RestController
public class UserController { public class UserController {
private final UserService userService; private final UserService userService;
@ -156,4 +160,9 @@ public class UserController {
userService.reportUser(userId, payload); userService.reportUser(userId, payload);
return ResponseEntity.ok().build(); return ResponseEntity.ok().build();
} }
@GetMapping(path = "/auth/me/roles")
public List<String> getMyRoles(@AuthenticationPrincipal User myUser) {
return myUser.getRoles().stream().map(Role::getShortName).toList();
}
} }

View File

@ -1,5 +1,5 @@
import { api } from 'src/api/main/index'; import { api } from 'src/api/main/index';
import { AuthStoreType } from 'stores/auth-store'; import {AuthStoreType, useAuthStore} from 'stores/auth-store';
import Timeout = NodeJS.Timeout; import Timeout = NodeJS.Timeout;
import { WeightUnit } from 'src/api/main/submission'; import { WeightUnit } from 'src/api/main/submission';
import {Page, PaginationOptions, toQueryParams} from "src/api/main/models"; import {Page, PaginationOptions, toQueryParams} from "src/api/main/models";
@ -81,8 +81,14 @@ class AuthModule {
authStore.token = await this.getNewToken(credentials); authStore.token = await this.getNewToken(credentials);
authStore.user = await this.getMyUser(authStore); authStore.user = await this.getMyUser(authStore);
// Load the user's attached data right away too. // Load the user's attached data right away too.
authStore.user.personalDetails = await this.getMyPersonalDetails(authStore); const [personalDetails, preferences, roles] = await Promise.all([
authStore.user.preferences = await this.getMyPreferences(authStore); this.getMyPersonalDetails(authStore),
this.getMyPreferences(authStore),
this.getMyRoles(authStore)
]);
authStore.user.personalDetails = personalDetails;
authStore.user.preferences = preferences;
authStore.roles = roles;
clearTimeout(this.tokenRefreshTimer); clearTimeout(this.tokenRefreshTimer);
this.tokenRefreshTimer = setTimeout( this.tokenRefreshTimer = setTimeout(
@ -288,6 +294,11 @@ class AuthModule {
authStore.axiosConfig authStore.axiosConfig
); );
} }
public async getMyRoles(authStore: AuthStoreType): Promise<string[]> {
const response = await api.get('/auth/me/roles', authStore.axiosConfig);
return response.data;
}
} }
export default AuthModule; export default AuthModule;

View File

@ -37,6 +37,11 @@
<q-item-label>Users</q-item-label> <q-item-label>Users</q-item-label>
</q-item-section> </q-item-section>
</q-item> </q-item>
<q-item clickable to="/admin" v-if="authStore.isAdmin">
<q-item-section>
<q-item-label>Admin Panel</q-item-label>
</q-item-section>
</q-item>
</q-list> </q-list>
</q-drawer> </q-drawer>
@ -49,7 +54,9 @@
<script setup lang="ts"> <script setup lang="ts">
import { ref } from 'vue'; import { ref } from 'vue';
import AccountMenuItem from 'components/AccountMenuItem.vue'; import AccountMenuItem from 'components/AccountMenuItem.vue';
import {useAuthStore} from 'stores/auth-store';
const authStore = useAuthStore();
const leftDrawerOpen = ref(false); const leftDrawerOpen = ref(false);
function toggleLeftDrawer() { function toggleLeftDrawer() {

View File

@ -0,0 +1,29 @@
<!--
The main page for the admin panel that's only accessible to users with the
'admin' role.
-->
<template>
<q-page>
<StandardCenteredPage>
<h3 class="q-my-md text-center">Admin Panel</h3>
<PageMenu
base-route="/admin"
:items="[
{label: 'Dashboard', to: ''}
]"
/>
<router-view/>
</StandardCenteredPage>
</q-page>
</template>
<script setup lang="ts">
import StandardCenteredPage from 'components/StandardCenteredPage.vue';
import PageMenu from 'components/PageMenu.vue';
import {onMounted} from 'vue';
onMounted(async () => {
console.log('admin');
});
</script>

View File

@ -16,15 +16,14 @@
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { onMounted, ref, Ref } from 'vue'; import {onMounted, ref, Ref} from 'vue';
import { useRoute, useRouter } from 'vue-router'; import {useRouter} from 'vue-router';
import StandardCenteredPage from 'components/StandardCenteredPage.vue'; import StandardCenteredPage from 'components/StandardCenteredPage.vue';
import { getGymFromRoute, getGymRoute } from 'src/router/gym-routing'; import {getGymFromRoute, getGymRoute} from 'src/router/gym-routing';
import { Gym } from 'src/api/main/gyms'; import {Gym} from 'src/api/main/gyms';
import PageMenu from 'components/PageMenu.vue'; import PageMenu from 'components/PageMenu.vue';
import {useI18n} from 'vue-i18n'; import {useI18n} from 'vue-i18n';
const route = useRoute();
const router = useRouter(); const router = useRouter();
const t = useI18n().t; const t = useI18n().t;

View File

@ -82,10 +82,12 @@ const isOwnUser = ref(false);
// will end up on the same route component, which means the router won't // will end up on the same route component, which means the router won't
// re-render. // re-render.
watch(route, async (updatedRoute) => { watch(route, async (updatedRoute) => {
if (updatedRoute.params.userId && updatedRoute.params.userId.length > 0) {
const userId = updatedRoute.params.userId[0]; const userId = updatedRoute.params.userId[0];
if (!profile.value || (profile.value.id !== userId)) { if (!profile.value || (profile.value.id !== userId)) {
await loadUser(userId); await loadUser(userId);
} }
}
}); });
onMounted(async () => { onMounted(async () => {

View File

@ -13,6 +13,8 @@ import SubmissionPage from 'pages/SubmissionPage.vue';
import UserPage from 'pages/user/UserPage.vue'; import UserPage from 'pages/user/UserPage.vue';
import UserSettingsPage from 'pages/auth/UserSettingsPage.vue'; import UserSettingsPage from 'pages/auth/UserSettingsPage.vue';
import UserSearchPage from 'pages/UserSearchPage.vue'; import UserSearchPage from 'pages/UserSearchPage.vue';
import AdminPage from "pages/admin/AdminPage.vue";
import {useAuthStore} from "stores/auth-store";
const routes: RouteRecordRaw[] = [ const routes: RouteRecordRaw[] = [
// Auth-related pages, which live outside the main layout. // Auth-related pages, which live outside the main layout.
@ -43,6 +45,12 @@ const routes: RouteRecordRaw[] = [
], ],
}, },
{ path: 'submissions/:submissionId', component: SubmissionPage }, { path: 'submissions/:submissionId', component: SubmissionPage },
{ // The admin page, and all child pages within it, are only accessible for users with the 'admin' role.
path: 'admin', component: AdminPage, beforeEnter: () => {
const s = useAuthStore();
if (!s.isAdmin) return '/'; // Redirect non-admins to the main page.
}
}
], ],
}, },

View File

@ -7,16 +7,28 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { User } from 'src/api/main/auth'; import { User } from 'src/api/main/auth';
import {AxiosRequestConfig} from "axios"; import {AxiosRequestConfig} from 'axios';
interface AuthState { interface AuthState {
/**
* The currently authenticated user.
*/
user: User | null; user: User | null;
/**
* The token that was used to authenticate the current user.
*/
token: string | null; token: string | null;
/**
* The list of roles that the currently authenticated user has.
*/
roles: string[];
} }
export const useAuthStore = defineStore('authStore', { export const useAuthStore = defineStore('authStore', {
state: (): AuthState => { state: (): AuthState => {
return { user: null, token: null }; return { user: null, token: null, roles: [] };
}, },
getters: { getters: {
loggedIn: (state) => state.user !== null && state.token !== null, loggedIn: (state) => state.user !== null && state.token !== null,
@ -29,6 +41,7 @@ export const useAuthStore = defineStore('authStore', {
return {}; return {};
} }
}, },
isAdmin: state => state.roles.indexOf('admin') !== -1,
}, },
}); });