Skip to content

Commit

Permalink
feat(permissions): Permission based rendering for the point of sales
Browse files Browse the repository at this point in the history
  • Loading branch information
SuperVK committed Dec 1, 2024
1 parent 2e3ff45 commit bc7137a
Show file tree
Hide file tree
Showing 10 changed files with 61 additions and 24 deletions.
11 changes: 8 additions & 3 deletions apps/dashboard/src/components/TopNavbar.vue
Original file line number Diff line number Diff line change
Expand Up @@ -152,12 +152,13 @@ const navItems = computed(() => [
},
{
label: t('common.navigation.admin'),
visible: isAllowed('get', ['all'], 'User', ['any']),
visible: isAllowed('update', ['all'], 'User', ['any'])
|| isAllowed('get', ['all'], 'Banner', ['any']),
items: [
{
label: t('common.navigation.userOverview'),
route: '/user-overview',
visible: isAllowed('get', ['all'], 'User', ['any']),
visible: isAllowed('update', ['all'], 'User', ['any']),
},
{
label: t('common.navigation.banners'),
Expand All @@ -169,6 +170,10 @@ const navItems = computed(() => [
{
label: t('common.navigation.financial'),
notifications: getFinancialNotifications(),
visible: isAllowed('update', ['all'], 'User', ['any'])
|| isAllowed('get', ['all'], 'Invoice', ['any'])
|| isAllowed('get', ['all'], 'Fine', ['any'])
|| isAllowed('get', ['all'], 'SellerPayout', ['any']),
items: [
{
label: t('common.navigation.userOverview'),
Expand Down Expand Up @@ -207,7 +212,7 @@ const navItems = computed(() => [
label: t('common.navigation.posOverview'),
route: '/point-of-sale/overview',
// TODO: Change to `action: get` after https://github.com/GEWIS/sudosos-backend/issues/62 is fully finished
visible: isAllowed('update', ['own', 'organ'], 'PointOfSale', ['any']),
visible: isAllowed('get', ['own', 'organ'], 'PointOfSale', ['any']),
},
...organs.value,
]
Expand Down
6 changes: 5 additions & 1 deletion apps/dashboard/src/components/container/ContainersCard.vue
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<template #topAction>
<div v-if="showCreate || associatedPos" class="flex justify-content-endg gap-2">
<Button
v-if="associatedPos"
v-if="posEditAllowed && associatedPos"
@click="openContainerAdd()"
outlined>{{ t('modules.seller.productContainers.containers.addExisting') }}</Button>
<Button
Expand Down Expand Up @@ -89,6 +89,10 @@ const props = defineProps({
associatedPos: {
type: Object as PropType<PointOfSaleWithContainersResponse>,
required: false
},
posEditAllowed: {
type: Boolean,
required: false
}
});
Expand Down
5 changes: 4 additions & 1 deletion apps/dashboard/src/locales/en/common/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@
}
},
"apiError": "Error",
"errorMessage": "Something is going wrong, please open an issue on {link} describing the issue."
"errorMessage": "Something is going wrong, please open an issue on {link} describing the issue.",
"permissionMessages": {
"transactions": "You are not allowed to view these transactions."
}
}
}
5 changes: 4 additions & 1 deletion apps/dashboard/src/locales/nl/common/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,9 @@
}
},
"apiError": "Fout",
"errorMessage": "Er is iets misgegaan, open een issue op {link} en beschrijf het probleem."
"errorMessage": "Er is iets misgegaan, open een issue op {link} en beschrijf het probleem.",
"permissionMessages": {
"transactions": "Je mag deze transacties niet bekijken."
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,12 @@
<CardComponent :header="t('modules.seller.posOverview.list.header')" class="w-full">
<template #topAction>
<div class="flex flex-row align-items-center justify-content-end">
<Button :label="t('common.create')" icon="pi pi-plus" @click="openCreatePOSModal"/>
<Button
:label="t('common.create')"
icon="pi pi-plus"
@click="openCreatePOSModal"
v-if="isAllowed('create', ['own', 'organ'], 'PointOfSale', ['any'])"
/>
</div>
</template>
<DataTable :rows="rows" :value="pointOfSales" :rowsPerPageOptions="[5, 10, 25, 50, 100]" paginator lazy
Expand Down Expand Up @@ -44,6 +49,7 @@ import Skeleton from "primevue/skeleton";
import router from "@/router";
import POSCreateModal from "@/modules/seller/components/POSCreateModal.vue";
import { useI18n } from "vue-i18n";
import { isAllowed } from "@/utils/permissionUtils";
const { t } = useI18n();
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
<template>
<FormCard :header="t('modules.seller.forms.pos.overview')" v-if="pointOfSale" @cancel="updateFieldValues(pointOfSale)"
@update:modelValue="edit = $event" @save="formSubmit">
@update:modelValue="edit = $event" @save="formSubmit"
:enableEdit="isAllowed('update', ['own', 'organ'], 'PointOfSale', ['any'])">
<div class="flex flex-column justify-content-between gap-2">
<POSSettingsForm :point-of-sale="pointOfSale" :form="form" :edit="edit" @update:edit="edit = $event"/>
<div class="flex flex-row justify-content-end">
<Button
v-if="isAllowed('delete', ['own', 'organ'], 'PointOfSale', ['any'])"
:disabled="!edit"
@click="handleDelete"
icon="pi pi-trash"
Expand All @@ -30,6 +32,7 @@ import router from "@/router";
import { useI18n } from "vue-i18n";
import { useToast } from "primevue/usetoast";
import { handleError } from "@/utils/errorUtils";
import { isAllowed } from "@/utils/permissionUtils";
const confirm = useConfirm();
Expand Down
14 changes: 13 additions & 1 deletion apps/dashboard/src/modules/seller/views/POSInfoView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@
<div class="flex flex-column md:flex-row gap-5 justify-content-between align-items-stretch w-12">
<POSSettingsCard :pos-id="id!" class="flex-1 h-12" />
<CardComponent :header="t('modules.seller.singlePos.sales')" class="flex-1" >
<div class="h-12 text-center text-5xl pb-3">{{ formattedTotalSales }}</div>
<div class="h-12 text-center text-5xl pb-3" v-if="canLoadTransactions">{{ formattedTotalSales }}</div>
<div v-else>{{ t('common.permissionMessages.transactions') }}</div>
</CardComponent>
</div>
<ContainerCard
Expand All @@ -14,14 +15,18 @@
:containers="posContainers"
show-create
:associatedPos="pointsOfSaleWithContainers[id!]"
:pos-edit-allowed="pointsOfSaleWithContainers[id!] &&
isAllowed('update', [getRelation(pointsOfSaleWithContainers[id!].owner!.id)], 'PointOfSale', ['any'])"
/>
<CardComponent :header="t('components.mutations.recent')">
<MutationPOSCard
v-if="canLoadTransactions"
class="pos-transactions"
:get-mutations="getPOSTransactions"
paginator
style="width: 100% !important"
/>
<div v-else>{{ t('common.permissionMessages.transactions') }}</div>
</CardComponent>
</div>
</div>
Expand Down Expand Up @@ -49,6 +54,7 @@ import Dinero from "dinero.js";
import { type StoreGeneric, storeToRefs } from "pinia";
import { useI18n } from "vue-i18n";
import { type ContainerWithProductsResponse } from "@sudosos/sudosos-client/src/api";
import { getRelation, isAllowed } from "@/utils/permissionUtils";
const route = useRoute(); // Use the useRoute function to access the current route
const id = ref<number>();
Expand All @@ -66,6 +72,11 @@ const posName = computed(() => {
: t('common.loading');
});
const canLoadTransactions = computed(() => {
if (pointsOfSaleWithContainers.value[id.value!] == undefined) return false;
return isAllowed('get', [getRelation(pointsOfSaleWithContainers.value[id.value!].owner!.id)], 'Transaction', ['any']);
});
// Fetch containers from the container store, then the ContainerCard will be reactive.
const posContainerIds = computed(() =>
pointsOfSaleWithContainers.value[id.value!]?.containers
Expand All @@ -91,6 +102,7 @@ const formattedTotalSales = computed(() => {
});
onMounted(async () => {
if (!canLoadTransactions.value) return;
const transactionStore = useTransactionStore();
const transactionsInLastWeek = (await transactionStore.fetchTransactionsFromPointOfSale(
apiService,
Expand Down
12 changes: 5 additions & 7 deletions apps/dashboard/src/modules/seller/views/POSOverviewView.vue
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,14 @@

<script setup lang="ts">
import POSOverviewTable from "@/modules/seller/components/POSOverviewTable.vue";
import { computed } from "vue";
import { useUserStore } from "@sudosos/sudosos-frontend-common";
import { UserRole } from "@/utils/rbacUtils";
import { usePointOfSaleStore } from "@/stores/pos.store";
import type { PaginatedPointOfSaleResponse } from "@sudosos/sudosos-client";
import router from "@/router";
import { handleError } from "@/utils/errorUtils";
import { useToast } from "primevue/usetoast";
import { useI18n } from "vue-i18n";
import { isAllowed } from "@/utils/permissionUtils";
const { t } = useI18n();
Expand All @@ -26,18 +25,17 @@ const userStore = useUserStore();
const toast = useToast();
const isBacPM = computed(() => {
return userStore.current.rolesWithPermissions.findIndex(r => r.name == UserRole.BAC_PM) != -1;
});
const getPointsOfSale = async (take: number, skip: number):
Promise<PaginatedPointOfSaleResponse | undefined> => {
if (!userStore.getCurrentUser) {
await router.replace({ path: '/error' });
return;
}
let pointsOfSale;
if (isBacPM.value) {
// If you can get all point of sales, render them. Otherwise only the user ones.
console.log(isAllowed('get', ['all'], 'PointOfSale', ['any']));
// TODO: Change to `action: get` after https://github.com/GEWIS/sudosos-backend/issues/62 is fully finished
if (isAllowed('update', ['all'], 'PointOfSale', ['any'])) {
pointsOfSale = await pointOfSaleStore.getPointsOfSale(take, skip)
.catch((err) => handleError(err, toast));
} else {
Expand Down
16 changes: 8 additions & 8 deletions apps/dashboard/src/utils/permissionUtils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import { useUserStore } from "@sudosos/sudosos-frontend-common";
import { type BaseUserResponse } from "@sudosos/sudosos-client";
import { useAuthStore, useUserStore } from "@sudosos/sudosos-frontend-common";

/**
* Performs an access check for the given parameters.
Expand Down Expand Up @@ -67,15 +66,16 @@ export function isAllowed(

/**
* Get the relation of the current user to a certain user.
* @param user The user that the current user needs relation to.
* @param userid The user that the current user needs relation to.
* @returns {string} - The relation, usually own if self, organ if part of organ, and all if there is no relation
*/
export function getRelation(user: BaseUserResponse): string {
const userStore = useUserStore();
export function getRelation(userid: number): string {
const authStore = useAuthStore();

if (userStore.current.user?.id == user.id) return 'own';
if (authStore.getUser?.id == userid) return 'own';

const roles = userStore.current.rolesWithPermissions.map(r => r.id);
if (roles.includes(user.id)) return 'organ';
const roles = authStore.getOrgans?.map(r => r.id);
if (!roles) return 'all';
if (roles.includes(userid)) return 'organ';
return 'all'; // No relation means the all relation
}
3 changes: 3 additions & 0 deletions lib/common/src/stores/auth.store.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,9 @@ export const useAuthStore = defineStore({
},
getUser(): UserResponse | null {
return this.user;
},
getOrgans(): UserResponse[] | null {
return this.organs;
}
},
actions: {
Expand Down

0 comments on commit bc7137a

Please sign in to comment.