Added admin page.
This commit is contained in:
parent
bf6066e949
commit
75680d1041
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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() {
|
||||||
|
|
|
@ -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>
|
|
@ -17,14 +17,13 @@
|
||||||
|
|
||||||
<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;
|
||||||
|
|
||||||
|
|
|
@ -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 () => {
|
||||||
|
|
|
@ -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.
|
||||||
|
}
|
||||||
|
}
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue