Merge branch 'develop' into milestone/ff-admin-unit

This commit is contained in:
Julian Krauser 2025-05-09 11:25:31 +02:00
commit 4ebacc5f52
107 changed files with 4980 additions and 1739 deletions

View file

@ -11,21 +11,18 @@ export const useAbilityStore = defineStore("ability", {
getters: {
can:
(state) =>
(type: PermissionType | "admin", section: PermissionSection, module?: PermissionModule): boolean => {
(type: PermissionType | "admin", section: PermissionSection, module: PermissionModule): boolean => {
const permissions = state.permissions;
if (state.isOwner) return true;
if (type == "admin") return permissions?.admin ?? false;
if (permissions?.admin) return true;
if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false;
if (permissions?.admin || permissions?.adminByOwner) return true;
if (
(!module &&
permissions[section] != undefined &&
(permissions[section]?.all == "*" || permissions[section]?.all?.includes(type))) ||
permissions[section]?.all == "*" ||
permissions[section]?.all?.includes(type)
permissions[section]?.all?.includes(type) ||
permissions[section]?.[module] == "*" ||
permissions[section]?.[module]?.includes(type)
)
return true;
if (module && (permissions[section]?.[module] == "*" || permissions[section]?.[module]?.includes(type)))
return true;
return false;
},
canSection:
@ -33,8 +30,8 @@ export const useAbilityStore = defineStore("ability", {
(type: PermissionType | "admin", section: PermissionSection): boolean => {
const permissions = state.permissions;
if (state.isOwner) return true;
if (type == "admin") return permissions?.admin ?? false;
if (permissions?.admin) return true;
if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false;
if (permissions?.admin || permissions?.adminByOwner) return true;
if (
permissions[section]?.all == "*" ||
permissions[section]?.all?.includes(type) ||
@ -54,20 +51,31 @@ export const useAbilityStore = defineStore("ability", {
permissions: PermissionObject,
type: PermissionType | "admin",
section: PermissionSection,
module?: PermissionModule
module: PermissionModule
): boolean => {
// ignores ownership
if (type == "admin") return permissions?.admin ?? false;
if (permissions?.admin) return true;
if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false;
if (permissions?.admin || permissions?.adminByOwner) return true;
if (
(!module &&
permissions[section] != undefined &&
(permissions[section]?.all == "*" || permissions[section]?.all?.includes(type))) ||
permissions[section]?.all == "*" ||
permissions[section]?.all?.includes(type)
permissions[section]?.all?.includes(type) ||
permissions[section]?.[module] == "*" ||
permissions[section]?.[module]?.includes(type)
)
return true;
if (module && (permissions[section]?.[module] == "*" || permissions[section]?.[module]?.includes(type)))
return false;
},
_canSection:
() =>
(permissions: PermissionObject, type: PermissionType | "admin", section: PermissionSection): boolean => {
// ignores ownership
if (type == "admin") return permissions?.admin ?? permissions?.adminByOwner ?? false;
if (permissions?.admin || permissions?.adminByOwner) return true;
if (
permissions[section]?.all == "*" ||
permissions[section]?.all?.includes(type) ||
permissions[section] != undefined
)
return true;
return false;
},

View file

@ -87,6 +87,9 @@ export const useMemberStore = defineStore("member", {
})
.catch((err) => {});
},
fetchLastInternalId() {
return http.get(`/admin/member/last/internalId`);
},
async printMemberByActiveId() {
return http.get(`/admin/member/${this.activeMember}/print`, {
responseType: "blob",

View file

@ -1,9 +1,8 @@
import { defineStore } from "pinia";
import { http, newEventSource, streamingFetch } from "@/serverCom";
import { http, streamingFetch } from "@/serverCom";
import { useNewsletterStore } from "./newsletter";
import type { AxiosResponse } from "axios";
import type { EventSourcePolyfill } from "event-source-polyfill";
import { useNotificationStore, type NotificationType } from "../../../notification";
import { useNotificationStore, type NotificationType } from "@/stores/notification";
export const useNewsletterPrintoutStore = defineStore("newsletterPrintout", {
state: () => {

View file

@ -2,6 +2,7 @@ import { defineStore } from "pinia";
import { http } from "@/serverCom";
import type { TableMeta } from "@/viewmodels/admin/configuration/query.models";
import type { DynamicQueryStructure, FieldType } from "@/types/dynamicQueries";
import type { AxiosResponse } from "axios";
export const useQueryBuilderStore = defineStore("queryBuilder", {
state: () => {
@ -58,6 +59,16 @@ export const useQueryBuilderStore = defineStore("queryBuilder", {
this.loadingData = "failed";
});
},
async sendQueryByStoreId(
id: string,
offset = 0,
count = 25,
noLimit: boolean = false
): Promise<AxiosResponse<any, any>> {
return await http.post(
`/admin/querybuilder/query/${id}?` + (noLimit ? `noLimit=true` : `offset=${offset}&count=${count}`)
);
},
clearResults() {
this.data = [];
this.totalLength = 0;

View file

@ -6,7 +6,7 @@ import type {
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
export const useNewsletterConfigStore = defineStore("newsletterConfi", {
export const useNewsletterConfigStore = defineStore("newsletterConfig", {
state: () => {
return {
config: [] as Array<NewsletterConfigViewModel>,

View file

@ -6,10 +6,10 @@ import type {
} from "@/viewmodels/admin/configuration/query.models";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { useQueryBuilderStore } from "../club/queryBuilder";
import { useModalStore } from "../../modal";
import { useQueryBuilderStore } from "@/stores/admin/club/queryBuilder";
import { useModalStore } from "@/stores/modal";
import { defineAsyncComponent, markRaw } from "vue";
import { useAbilityStore } from "../../ability";
import { useAbilityStore } from "@/stores/ability";
export const useQueryStoreStore = defineStore("queryStore", {
state: () => {

View file

@ -1,7 +1,7 @@
import { defineStore } from "pinia";
import { http } from "@/serverCom";
import type { AxiosResponse, AxiosProgressEvent } from "axios";
import type { BackupRestoreViewModel } from "../../../viewmodels/admin/management/backup.models";
import type { BackupRestoreViewModel } from "@/viewmodels/admin/management/backup.models";
export const useBackupStore = defineStore("backup", {
state: () => {

View file

@ -0,0 +1,103 @@
import { defineStore } from "pinia";
import { http } from "@/serverCom";
import type { SettingString, SettingTopic, SettingValueMapping } from "@/types/settingTypes";
import type { AxiosResponse } from "axios";
export const useSettingStore = defineStore("setting", {
state: () => {
return {
settings: {} as { [key in SettingString]: SettingValueMapping[key] },
loading: "loading" as "loading" | "fetched" | "failed",
};
},
getters: {
readSetting:
(state) =>
<K extends SettingString>(key: K): SettingValueMapping[K] => {
return state.settings[key];
},
readByTopic:
(state) =>
<T extends SettingTopic>(
topic: T
): { [K in SettingString as K extends `${T}.${string}` ? K : never]: SettingValueMapping[K] } => {
return Object.entries(state.settings).reduce((acc, [key, value]) => {
const typedKey = key as SettingString;
if (key.startsWith(topic)) {
acc[typedKey] = value;
}
return acc;
}, {} as any);
},
},
actions: {
fetchSettings() {
this.loading = "loading";
http
.get("/admin/setting")
.then((result) => {
this.settings = result.data;
this.loading = "fetched";
})
.catch((err) => {
this.loading = "failed";
});
},
async getSetting(key: SettingString): Promise<AxiosResponse<any, any>> {
return await http.get(`/admin/setting/${key}`).then((res) => {
//@ts-expect-error
this.settings[key] = res.data;
return res;
});
},
async updateSetting<K extends SettingString>(
key: K,
value: SettingValueMapping[K]
): Promise<AxiosResponse<any, any>> {
return await http
.put("/admin/setting", {
setting: key,
value: value,
})
.then((res) => {
this.settings[key] = value;
return res;
});
},
async updateSettings<K extends SettingString>(
data: { key: K; value: SettingValueMapping[K] }[]
): Promise<AxiosResponse<any, any>> {
return await http.put("/admin/setting/multi", data).then((res) => {
for (const element of data) {
this.settings[element.key] = element.value;
}
return res;
});
},
async uploadImage(
data: { key: "club.logo" | "club.icon"; value?: File | "keep" }[]
): Promise<AxiosResponse<any, any>> {
const formData = new FormData();
for (let entry of data) {
if (entry.value) {
formData.append(typeof entry.value == "string" ? entry.key : entry.key.split(".")[1], entry.value);
}
}
return await http
.put("/admin/setting/images", formData, {
headers: {
"Content-Type": "multipart/form-data",
},
})
.then((res) => {
for (const element of data) {
this.settings[element.key] = element.value ? "configured" : "";
}
return res;
});
},
async resetSetting(key: SettingString): Promise<AxiosResponse<any, any>> {
return await http.delete(`/admin/setting/${key}`);
},
},
});

View file

@ -1,7 +1,7 @@
import { defineStore } from "pinia";
import { useAbilityStore } from "@/stores/ability";
import router from "@/router";
import type { PermissionSection } from "../../types/permissionTypes";
import type { PermissionSection } from "@/types/permissionTypes";
export type navigationModel = {
[key in topLevelNavigationType]: navigationSplitModel;
@ -146,6 +146,7 @@ export const useNavigationStore = defineStore("navigation", {
...(abilityStore.can("read", "management", "user") ? [{ key: "user", title: "Benutzer" }] : []),
...(abilityStore.can("read", "management", "role") ? [{ key: "role", title: "Rollen" }] : []),
...(abilityStore.can("read", "management", "webapi") ? [{ key: "webapi", title: "Webapi-Token" }] : []),
...(abilityStore.can("read", "management", "setting") ? [{ key: "setting", title: "Einstellungen" }] : []),
...(abilityStore.can("read", "management", "backup") ? [{ key: "backup", title: "Backups" }] : []),
...(abilityStore.isAdmin() ? [{ key: "version", title: "Version" }] : []),
],

View file

@ -0,0 +1,34 @@
import { defineStore } from "pinia";
import { http } from "@/serverCom";
export const useConfigurationStore = defineStore("configuration", {
state: () => {
return {
clubName: "",
clubImprint: "",
clubPrivacy: "",
clubWebsite: "",
appCustom_login_message: "",
appShow_link_to_calendar: false as boolean,
serverOffline: false as boolean,
};
},
actions: {
configure() {
http
.get("/public/configuration")
.then((res) => {
this.clubName = res.data["club.name"];
this.clubImprint = res.data["club.imprint"];
this.clubPrivacy = res.data["club.privacy"];
this.clubWebsite = res.data["club.website"];
this.appCustom_login_message = res.data["app.custom_login_message"];
this.appShow_link_to_calendar = res.data["app.show_link_to_calendar"];
})
.catch(() => {
this.serverOffline = true;
});
},
},
});

130
src/stores/setup.ts Normal file
View file

@ -0,0 +1,130 @@
import { defineStore } from "pinia";
import { http } from "@/serverCom";
import type { AxiosResponse } from "axios";
import { useConfigurationStore } from "./configuration";
export const useSetupStore = defineStore("setup", {
state: () => {
return {
dictionary: ["club", "clubImages", "app", "mail", "account", "finished"],
step: 0 as number,
successfull: 0 as number,
};
},
getters: {
stepIndex: (state) => (dict: string) => state.dictionary.findIndex((d) => d == dict),
},
actions: {
skip(dict: string) {
let myIndex = this.stepIndex(dict);
this.step += 1;
if (this.successfull <= myIndex) {
this.successfull = myIndex + 1;
}
},
async setClub(data: {
name?: string;
imprint?: string;
privacy?: string;
website?: string;
}): Promise<AxiosResponse<any, any>> {
let configStore = useConfigurationStore();
let myIndex = this.stepIndex("club");
const res = await http.post(`/setup/club`, {
name: data.name,
imprint: data.imprint,
privacy: data.privacy,
website: data.website,
});
configStore.configure();
this.step += 1;
if (this.successfull <= myIndex) {
this.successfull = myIndex;
}
return res;
},
async setClubImages(data: { icon?: File; logo?: File }): Promise<AxiosResponse<any, any>> {
let configStore = useConfigurationStore();
let myIndex = this.stepIndex("clubImages");
const formData = new FormData();
if (data.icon) {
formData.append("icon", data.icon);
}
if (data.logo) {
formData.append("logo", data.logo);
}
const res = await http.post(`/setup/club/images`, formData, {
headers: {
"Content-Type": "multipart/form-data",
},
});
configStore.configure();
this.step += 1;
if (this.successfull <= myIndex) {
this.successfull = myIndex;
}
return res;
},
async setApp(data: { login_message: string; show_cal_link: boolean }): Promise<AxiosResponse<any, any>> {
let myIndex = this.stepIndex("app");
const res = await http.post(`/setup/app`, {
custom_login_message: data.login_message,
show_link_to_calendar: data.show_cal_link,
});
this.step += 1;
if (this.successfull <= myIndex) {
this.successfull = myIndex;
}
return res;
},
async setMailConfig(data: {
host: string;
port: number;
secure: boolean;
mail: string;
username: string;
password: string;
}): Promise<AxiosResponse<any, any>> {
let myIndex = this.stepIndex("mail");
const res = await http.post(`/setup/mail`, {
host: data.host,
port: data.port,
secure: data.secure,
mail: data.mail,
username: data.username,
password: data.password,
});
this.step += 1;
if (this.successfull <= myIndex) {
this.successfull = myIndex;
}
return res;
},
async createAdmin(credentials: {
username: string;
mail: string;
firstname: string;
lastname: string;
}): Promise<AxiosResponse<any, any>> {
let myIndex = this.stepIndex("account");
const res = await http.post(`/setup/me`, {
username: credentials.username,
mail: credentials.mail,
firstname: credentials.firstname,
lastname: credentials.lastname,
});
this.step += 1;
if (this.successfull < myIndex) {
this.successfull = myIndex;
}
return res;
},
},
});