release v2.0.0 #4

Merged
crimsen merged 481 commits from develop into master 2024-01-18 15:15:08 +00:00
11 changed files with 123 additions and 108 deletions
Showing only changes of commit b7db5ea3a6 - Show all commits

View File

@ -21,7 +21,7 @@ module.exports = configure(function (/* ctx */) {
files: './src/**/*.{ts,tsx,js,jsx,vue}',
},
}
}
},
// https://quasar.dev/quasar-cli/prefetch-feature
// preFetch: true,

View File

@ -1,45 +1,25 @@
import axios, { AxiosInstance, AxiosError } from 'axios';
import config from 'src/config';
import { boot } from 'quasar/wrappers';
import config from '../config';
import { Store } from 'vuex';
import { StateInterface } from 'src/store';
import { LocalStorage } from 'quasar';
import { Notify } from 'quasar';
import { LocalStorage, Notify } from 'quasar';
import axios, { AxiosError, AxiosInstance } from 'axios';
import { UserSessionState } from 'src/plugins/user/store';
declare module 'vue/types/vue' {
interface Vue {
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$axios: AxiosInstance;
}
}
export const setBaseUrl = (url: string) => {
LocalStorage.set('baseURL', url);
axios.defaults.baseURL = url;
Notify.create({
message: 'Serveraddresse gespeichert',
position: 'bottom',
caption: `${url}`,
color: 'positive',
const api = axios.create({
baseURL: <string | undefined>LocalStorage.getItem('baseURL') || config.baseURL,
});
setTimeout(() => {
window.location.reload();
}, 5000);
};
export default boot<Store<StateInterface>>(({ Vue, store, router }) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
Vue.prototype.$axios = axios;
const baseURL = <string | undefined>LocalStorage.getItem('baseURL');
if (baseURL) {
axios.defaults.baseURL = baseURL;
} else {
axios.defaults.baseURL = config.baseURL;
}
export default boot<UserSessionState>(({ app, store, router }) => {
/***
* Intercept requests and insert Token if available
*/
axios.interceptors.request.use((config) => {
const session = store.state.session.currentSession;
api.interceptors.request.use((config) => {
const session = store.state.sessions.currentSession;
if (session?.token) {
config.headers = { Authorization: 'Bearer ' + session.token };
}
@ -62,15 +42,39 @@ export default boot<Store<StateInterface>>(({ Vue, store, router }) => {
) {
return router.push({
name: 'offline',
query: { redirect: router.currentRoute.fullPath },
query: { redirect: router.currentRoute.value.fullPath },
});
} else if (e.response && e.response.status == 401) {
if (router.currentRoute.name !== 'login') return store.dispatch('session/clearCurrent');
if (router.currentRoute.value.name !== 'login')
return store.dispatch('session/clearCurrent');
}
}
return Promise.reject(error);
}
);
// for use inside Vue files (Options API) through this.$axios and this.$api
app.config.globalProperties.$axios = axios;
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
// so you won't necessarily have to import axios in each vue file
app.config.globalProperties.$api = api;
// ^ ^ ^ this will allow you to use this.$api (for Vue Options API form)
// so you can easily perform requests against your app's API
});
export { axios };
export { axios, api };
export const setBaseUrl = (url: string) => {
LocalStorage.set('baseURL', url);
axios.defaults.baseURL = url;
Notify.create({
message: 'Serveraddresse gespeichert',
position: 'bottom',
caption: `${url}`,
color: 'positive',
});
setTimeout(() => {
window.location.reload();
}, 5000);
};

View File

@ -1,10 +0,0 @@
import { boot } from 'quasar/wrappers';
import { formatDateTime } from 'src/utils/datetime';
export default boot(({ Vue }) => {
Vue.filter('date', formatDateTime);
Vue.filter('time', (date: Date, seconds = false) => formatDateTime(date, false, true, seconds));
Vue.filter('dateTime', (date: Date, seconds = false, weekday = false) =>
formatDateTime(date, true, true, seconds, weekday)
);
});

View File

@ -4,7 +4,7 @@ import DarkCircularProgress from 'components/loading/DarkCircularProgress.vue';
// "async" is optional;
// more info on params: https://quasar.dev/quasar-cli/cli-documentation/boot-files#Anatomy-of-a-boot-file
export default boot((/* { app, router, Vue ... } */) => {
export default boot(() => {
Loading.setDefaults({
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore

View File

@ -1,11 +1,10 @@
import { boot } from 'quasar/wrappers';
import { StateInterface } from 'src/store';
import { UserSessionState } from 'src/plugins/user/store';
import { RouteRecord } from 'vue-router';
import { Store } from 'vuex';
export default boot<Store<StateInterface>>(({ router, store }) => {
export default boot<UserSessionState>(({ router, store }) => {
router.beforeEach((to, from, next) => {
const session = store.state.session.currentSession;
const session = store.state.sessions.currentSession;
if (to.path == from.path) {
return;
@ -29,7 +28,7 @@ export default boot<Store<StateInterface>>(({ router, store }) => {
if ((<{ permissions: FG.Permission[] }>record.meta).permissions) {
return (<{ permissions: FG.Permission[] }>record.meta).permissions.every(
(permission: string) => {
return store.state.user.currentPermissions.includes(permission);
return store.state.users.currentPermissions.includes(permission);
}
);
}
@ -41,7 +40,7 @@ export default boot<Store<StateInterface>>(({ router, store }) => {
next({ name: 'login', query: { redirect: to.fullPath } });
}
} else {
if (to.name == 'login' && store.state.user.currentUser && !to.params['logout']) {
if (to.name == 'login' && store.state.users.currentUser && !to.params['logout']) {
// Called login while already logged in
void next({ name: 'dashboard' });
} else {

View File

@ -3,7 +3,7 @@ import { Notify } from 'quasar';
// "async" is optional;
// more info on params: https://quasar.dev/quasar-cli/boot-files
export default boot((/* { app, router, Vue ... } */) => {
export default boot(() => {
Notify.registerType('error', {
color: 'negative',
icon: 'mdi-alert-circle',

View File

@ -1,11 +1,11 @@
import { boot } from 'quasar/wrappers';
import { Store } from 'vuex';
import { StateInterface } from 'src/store';
import { FG_Plugin } from 'src/plugins';
import routes from 'src/router/routes';
import { axios } from 'boot/axios';
import { AxiosResponse } from 'axios';
import { Router, RouteRecordRaw } from 'vue-router';
import { UserSessionState } from 'src/plugins/user/store';
const config = {
// Do not change required Modules !!
@ -154,7 +154,7 @@ function loadPlugin(
modules: string[],
backendpromise: Promise<Backend | null>,
plugins: FG_Plugin.Plugin[],
store: Store<any>,
store: Store<UserSessionState>,
router: Router
): FG_Plugin.LoadedPlugins {
modules.forEach((requiredModule) => {
@ -211,7 +211,7 @@ async function getBackend(): Promise<Backend | null> {
// "async" is optional;
// more info on params: https://quasar.dev/quasar-cli/cli-documentation/boot-files#Anatomy-of-a-boot-file
export default boot<Store<StateInterface>>(({ router, store, app }) => {
export default boot<UserSessionState>(({ router, store, app }) => {
const plugins: FG_Plugin.Plugin[] = [];
const backendPromise = getBackend();

View File

@ -0,0 +1,7 @@
import { SessionStateInterface } from './session';
import { UserStateInterface } from './user';
export interface UserSessionState {
sessions: SessionStateInterface;
users: UserStateInterface;
}

View File

@ -1,12 +1,12 @@
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
import { LoginData, LoginResponse } from 'src/plugins/user/models';
import { StateInterface } from 'src/store';
import { axios } from 'src/boot/axios';
import { api } from 'src/boot/axios';
import { AxiosError, AxiosResponse } from 'axios';
import { Router } from 'src/router';
import { LocalStorage } from 'quasar';
import { UserSessionState } from '.';
import { useRouter } from 'vue-router';
export interface SessionInterface {
export interface SessionStateInterface {
currentSession?: FG.Session;
sessions: FG.Session[];
loading: boolean;
@ -22,13 +22,13 @@ function loadCurrentSession() {
return session;
}
const state: SessionInterface = {
const state: SessionStateInterface = {
sessions: [],
currentSession: loadCurrentSession() || undefined,
loading: false,
};
const mutations: MutationTree<SessionInterface> = {
const mutations: MutationTree<SessionStateInterface> = {
setCurrentSession(state, session: FG.Session) {
LocalStorage.set('currentSession', session);
state.currentSession = session;
@ -51,19 +51,22 @@ const mutations: MutationTree<SessionInterface> = {
},
};
const actions: ActionTree<SessionInterface, StateInterface> = {
const actions: ActionTree<SessionStateInterface, UserSessionState> = {
/** Used to authenticate the user
* Setting current Session, User and Permissions.
* @param param0 Context
* @param data Credentitals
*/
login({ commit, dispatch }, data: LoginData) {
return axios
login({ commit }, data: LoginData) {
return api
.post('/auth', data)
.then(async (response: AxiosResponse<FG.Session>) => {
response.data.expires = new Date(response.data.expires);
commit('setCurrentSession', response.data);
await dispatch('user/getCurrentUser', undefined, { root: true });
.then((response: AxiosResponse<LoginResponse>) => {
response.data.session.expires = new Date(response.data.session.expires);
commit('setCurrentSession', response.data.session);
commit('user/setCurrentUser', response.data.user, { root: true });
commit('user/setCurrentPermissions', response.data.permissions, {
root: true,
});
})
.catch((error: AxiosError) => {
return Promise.reject(error.response);
@ -74,8 +77,8 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
* Alias of deleteSession with current session as target
*/
logout({ dispatch, rootState }) {
if (rootState.session.currentSession) {
dispatch('deleteSession', rootState.session.currentSession.token).catch((error) => {
if (rootState.sessions.currentSession) {
dispatch('deleteSession', rootState.sessions.currentSession.token).catch((error) => {
console.log(error);
void dispatch('clearCurrent', false);
});
@ -88,10 +91,10 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
*/
deleteSession({ commit, dispatch, rootState }, token: string) {
commit('setLoading', true);
axios
api
.delete(`/auth/${token}`)
.then(() => {
if (token === rootState.session.currentSession?.token) {
if (token === rootState.sessions.currentSession?.token) {
void dispatch('clearCurrent', false);
} else {
dispatch('getSessions').catch((error) => {
@ -110,15 +113,18 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
* Clear current session and logged in user
*/
clearCurrent({ commit }, redirect = true) {
void Router.push({
const router = useRouter();
void router
.push({
name: 'login',
query: redirect ? { redirect: Router.currentRoute.fullPath } : {},
query: redirect ? { redirect: router.currentRoute.value.fullPath } : {},
params: { logout: 'true' },
}).then(() => {
})
.then(() => {
commit('clearCurrentSession');
commit('user/clearCurrentUser', null, { root: true });
// ensure also volatile store gets cleared by refreshing the site
Router.go(0);
router.go(0);
});
},
/**
@ -126,7 +132,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
*/
getSessions({ commit, state }) {
commit('setLoading', true);
axios
api
.get('/auth')
.then((response: AxiosResponse<FG.Session[]>) => {
response.data.forEach((session) => {
@ -149,7 +155,7 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
},
updateSession({ commit, state }, data: { lifetime: number; token: string }) {
commit('setLoading', true);
axios
api
.put(`auth/${data.token}`, { value: data.lifetime })
.then((response: AxiosResponse<FG.Session>) => {
response.data.expires = new Date(response.data.expires);
@ -164,16 +170,16 @@ const actions: ActionTree<SessionInterface, StateInterface> = {
console.log('updateSession', data);
},
requestPasswordReset({}, data) {
return axios.post('/auth/reset', data);
return api.post('/auth/reset', data);
},
resetPassword({}, data) {
return axios.post('/auth/reset', data).catch((error: AxiosError) => {
return api.post('/auth/reset', data).catch((error: AxiosError) => {
return Promise.reject(error.response);
});
},
};
const getters: GetterTree<SessionInterface, StateInterface> = {
const getters: GetterTree<SessionStateInterface, UserSessionState> = {
currentSession(state) {
return state.currentSession;
},
@ -185,7 +191,7 @@ const getters: GetterTree<SessionInterface, StateInterface> = {
},
};
const sessions: Module<SessionInterface, StateInterface> = {
const sessions: Module<SessionStateInterface, UserSessionState> = {
namespaced: true,
state,
mutations,

View File

@ -1,10 +1,10 @@
/* eslint-disable @typescript-eslint/no-unsafe-call */
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
import { StateInterface } from 'src/store';
import { axios } from 'boot/axios';
import { api } from 'boot/axios';
import { AxiosError, AxiosResponse } from 'axios';
import { SessionStorage } from 'quasar';
import { CurrentUserResponse } from 'src/plugins/user/models';
import { UserSessionState } from '.';
export interface UserStateInterface {
currentUser?: FG.User;
@ -76,12 +76,12 @@ const mutations: MutationTree<UserStateInterface> = {
},
};
const actions: ActionTree<UserStateInterface, StateInterface> = {
const actions: ActionTree<UserStateInterface, UserSessionState> = {
getCurrentUser({ commit, rootState }) {
if (rootState.session.currentSession) {
if (rootState.sessions.currentSession) {
commit('setLoading');
axios
.get(`/users/${rootState.session.currentSession.userid}`)
api
.get(`/users/${rootState.sessions.currentSession.userid}`)
.then((response: AxiosResponse<CurrentUserResponse>) => {
commit('setCurrentUser', response.data);
commit('setCurrentPermissions', response.data.permissions);
@ -99,7 +99,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
getUsers({ commit }) {
commit('setLoading');
axios
api
.get('/users')
.then((response: AxiosResponse<FG.User[]>) => {
response.data.forEach((user) => {
@ -117,7 +117,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
updateUser({ commit, state, dispatch }, data: FG.User) {
commit('setLoading');
axios
api
.put(`/users/${data.userid}`, data)
.then(() => {
if (state.currentUser && state.currentUser.userid === data.userid)
@ -136,7 +136,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
commit('setLoading');
const formData = new FormData();
formData.append('file', payload.file);
return axios
return api
.post(`/users/${payload.user.userid}/avatar`, formData, {
headers: {
'Content-Type': 'multipart/form-data',
@ -152,7 +152,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
setUser({ commit, state, dispatch }, data: FG.User) {
commit('setLoading');
axios
api
.post('users', data)
.then(() => {
if (state.currentUser && state.currentUser.userid === data.userid)
@ -170,7 +170,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
getRoles({ commit, state }, force = false) {
if (!force && state.roles.length > 0) return;
commit('setLoading');
axios
api
.get('/roles')
.then((response: AxiosResponse<FG.Role[]>) => {
commit('setRoles', response.data);
@ -180,7 +180,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
updateRole({ commit }, data: FG.Role) {
commit('setLoading');
axios
api
.put(`/roles/${data.id}`, data)
.then(() => {
commit('updateRole', data);
@ -192,7 +192,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
newRole({ commit }, data: FG.Role) {
commit('setLoading');
return axios
return api
.post('/roles', data)
.then((response: AxiosResponse<FG.Role>) => {
commit('addRole', response.data);
@ -205,7 +205,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
deleteRole({ commit, state }, data: FG.Role) {
commit('setLoading');
axios
api
.delete(`/roles/${data.id}`)
.then(() => {
commit(
@ -221,7 +221,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
getPermissions({ commit, state }, force = false) {
if (!force && state.permissions.length > 0) return;
commit('setLoading');
axios
api
.get('/roles/permissions')
.then((response: AxiosResponse<FG.Role[]>) => {
commit('setPermissions', response.data);
@ -232,7 +232,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
const user = <FG.User | undefined>getters['getUser'](data.userid);
if (user === undefined || data.force === true) {
return axios.get(`/users/${data.userid}`).then((response: AxiosResponse<FG.User>) => {
return api.get(`/users/${data.userid}`).then((response: AxiosResponse<FG.User>) => {
commit('setUser', response.data);
return response.data;
});
@ -242,7 +242,7 @@ const actions: ActionTree<UserStateInterface, StateInterface> = {
},
};
const getters: GetterTree<UserStateInterface, StateInterface> = {
const getters: GetterTree<UserStateInterface, UserSessionState> = {
getUser: (state) => (userid: string) => {
const user = state.users.filter((usr) => usr.userid === userid);
return user.length > 0 ? user[0] : undefined;
@ -264,7 +264,7 @@ const getters: GetterTree<UserStateInterface, StateInterface> = {
},
};
const userStore: Module<UserStateInterface, StateInterface> = {
const userStore: Module<UserStateInterface, UserSessionState> = {
namespaced: true,
actions,
getters,

9
src/store/store-flag.d.ts vendored Normal file
View File

@ -0,0 +1,9 @@
// THIS FEATURE-FLAG FILE IS AUTOGENERATED,
// REMOVAL OR CHANGES WILL CAUSE RELATED TYPES TO STOP WORKING
import 'quasar/dist/types/feature-flag';
declare module 'quasar/dist/types/feature-flag' {
interface QuasarFeatureFlags {
store: true;
}
}