[pinia] Added Pinia, replaced vuex on user and session
This commit is contained in:
parent
5153f074b5
commit
4b198b6472
|
@ -14,6 +14,7 @@
|
||||||
"axios": "^0.21.1",
|
"axios": "^0.21.1",
|
||||||
"cordova": "^10.0.0",
|
"cordova": "^10.0.0",
|
||||||
"core-js": "^3.7.0",
|
"core-js": "^3.7.0",
|
||||||
|
"pinia": "^2.0.0-alpha.7",
|
||||||
"quasar": "^2.0.0-beta.9"
|
"quasar": "^2.0.0-beta.9"
|
||||||
},
|
},
|
||||||
"prettier": {
|
"prettier": {
|
||||||
|
|
|
@ -30,7 +30,7 @@ module.exports = configure(function (/* ctx */) {
|
||||||
// app boot file (/src/boot)
|
// app boot file (/src/boot)
|
||||||
// --> boot files are part of "main.js"
|
// --> boot files are part of "main.js"
|
||||||
// https://quasar.dev/quasar-cli/boot-files
|
// https://quasar.dev/quasar-cli/boot-files
|
||||||
boot: ['axios', 'plugins', 'loading', 'login'],
|
boot: ['axios', 'store', 'plugins', 'loading', 'login'],
|
||||||
|
|
||||||
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
|
// https://quasar.dev/quasar-cli/quasar-conf-js#Property%3A-css
|
||||||
css: ['app.scss'],
|
css: ['app.scss'],
|
||||||
|
|
|
@ -1,27 +1,21 @@
|
||||||
import config from 'src/config';
|
import config from 'src/config';
|
||||||
import { boot } from 'quasar/wrappers';
|
import { boot } from 'quasar/wrappers';
|
||||||
import { LocalStorage, Notify } from 'quasar';
|
import { LocalStorage, Notify } from 'quasar';
|
||||||
import axios, { AxiosError, AxiosInstance } from 'axios';
|
import axios, { AxiosError } from 'axios';
|
||||||
import { UserSessionState } from 'src/plugins/user/store';
|
import { useMainStore } from 'src/store';
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
const api = axios.create();
|
||||||
interface ComponentCustomProperties {
|
|
||||||
$axios: AxiosInstance;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const api = axios.create({
|
export default boot(({ store, router }) => {
|
||||||
baseURL: <string | undefined>LocalStorage.getItem('baseURL') || config.baseURL,
|
api.defaults.baseURL = LocalStorage.getItem<string>('baseURL') || config.baseURL;
|
||||||
});
|
|
||||||
|
|
||||||
export default boot<UserSessionState>(({ app, store, router }) => {
|
|
||||||
/***
|
/***
|
||||||
* Intercept requests and insert Token if available
|
* Intercept requests and insert Token if available
|
||||||
*/
|
*/
|
||||||
api.interceptors.request.use((config) => {
|
api.interceptors.request.use((config) => {
|
||||||
const session = store.state.sessions.currentSession;
|
const store = useMainStore();
|
||||||
if (session?.token) {
|
if (store.session?.token) {
|
||||||
config.headers = { Authorization: 'Bearer ' + session.token };
|
config.headers = { Authorization: 'Bearer ' + store.session.token };
|
||||||
}
|
}
|
||||||
return config;
|
return config;
|
||||||
});
|
});
|
||||||
|
@ -31,7 +25,7 @@ export default boot<UserSessionState>(({ app, store, router }) => {
|
||||||
* - filter 401 --> logout
|
* - filter 401 --> logout
|
||||||
* - filter timeout or 502-504 --> backendOffline
|
* - filter timeout or 502-504 --> backendOffline
|
||||||
*/
|
*/
|
||||||
axios.interceptors.response.use(
|
api.interceptors.response.use(
|
||||||
(response) => response,
|
(response) => response,
|
||||||
(error) => {
|
(error) => {
|
||||||
if (error) {
|
if (error) {
|
||||||
|
@ -56,22 +50,13 @@ export default boot<UserSessionState>(({ app, store, router }) => {
|
||||||
return Promise.reject(error);
|
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, api };
|
export { api };
|
||||||
|
|
||||||
export const setBaseUrl = (url: string) => {
|
export const setBaseURL = (url: string) => {
|
||||||
LocalStorage.set('baseURL', url);
|
LocalStorage.set('baseURL', url);
|
||||||
axios.defaults.baseURL = url;
|
api.defaults.baseURL = url;
|
||||||
Notify.create({
|
Notify.create({
|
||||||
message: 'Serveraddresse gespeichert',
|
message: 'Serveraddresse gespeichert',
|
||||||
position: 'bottom',
|
position: 'bottom',
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import { boot } from 'quasar/wrappers';
|
import { boot } from 'quasar/wrappers';
|
||||||
import { UserSessionState } from 'src/plugins/user/store';
|
import { useMainStore } from 'src/store';
|
||||||
|
import { hasPermissions } from 'src/utils/permission';
|
||||||
import { RouteRecord } from 'vue-router';
|
import { RouteRecord } from 'vue-router';
|
||||||
|
|
||||||
export default boot<UserSessionState>(({ router, store }) => {
|
export default boot(({ router }) => {
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const session = store.state.sessions.currentSession;
|
console.log(`from ${from.fullPath} to ${to.fullPath}`);
|
||||||
|
const store = useMainStore();
|
||||||
|
|
||||||
if (to.path == from.path) {
|
if (to.path == from.path) {
|
||||||
return;
|
return;
|
||||||
|
@ -13,10 +15,9 @@ export default boot<UserSessionState>(({ router, store }) => {
|
||||||
if (to.path.startsWith('/main')) {
|
if (to.path.startsWith('/main')) {
|
||||||
// Secured area (LOGIN REQUIRED)
|
// Secured area (LOGIN REQUIRED)
|
||||||
// Check login is ok
|
// Check login is ok
|
||||||
if (!session || session.expires <= new Date()) {
|
if (!store.session || store.session.expires <= new Date()) {
|
||||||
store.dispatch('sessions/logout').catch((error) => {
|
console.log('Nope, logout');
|
||||||
console.warn(error);
|
void store.logout();
|
||||||
});
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,27 +25,28 @@ export default boot<UserSessionState>(({ router, store }) => {
|
||||||
if (
|
if (
|
||||||
to.matched.every((record: RouteRecord) => {
|
to.matched.every((record: RouteRecord) => {
|
||||||
if (!('meta' in record) || !('permissions' in record.meta)) return true;
|
if (!('meta' in record) || !('permissions' in record.meta)) return true;
|
||||||
if (record.meta) {
|
|
||||||
if ((<{ permissions: FG.Permission[] }>record.meta).permissions) {
|
if ((<{ permissions: FG.Permission[] }>record.meta).permissions) {
|
||||||
return (<{ permissions: FG.Permission[] }>record.meta).permissions.every(
|
console.log(record.meta);
|
||||||
(permission: string) => {
|
const h = hasPermissions((<{ permissions: FG.Permission[] }>record.meta).permissions);
|
||||||
return store.state.users.currentPermissions.includes(permission);
|
console.log(h);
|
||||||
}
|
return h;
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
) {
|
) {
|
||||||
|
console.log('ok next');
|
||||||
next();
|
next();
|
||||||
} else {
|
} else {
|
||||||
|
console.log('Back loggin');
|
||||||
next({ name: 'login', query: { redirect: to.fullPath } });
|
next({ name: 'login', query: { redirect: to.fullPath } });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (to.name == 'login' && store.state.users.currentUser && !to.params['logout']) {
|
if (to.name == 'login' && store.user && !to.params['logout']) {
|
||||||
// Called login while already logged in
|
// Called login while already logged in
|
||||||
|
console.log('Ok next');
|
||||||
void next({ name: 'dashboard' });
|
void next({ name: 'dashboard' });
|
||||||
} else {
|
} else {
|
||||||
// We are on the non secured area
|
// We are on the non secured area
|
||||||
|
console.log('Ok non sec');
|
||||||
next();
|
next();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,14 +0,0 @@
|
||||||
import { boot } from 'quasar/wrappers';
|
|
||||||
import { Notify } from 'quasar';
|
|
||||||
|
|
||||||
// "async" is optional;
|
|
||||||
// more info on params: https://quasar.dev/quasar-cli/boot-files
|
|
||||||
export default boot(() => {
|
|
||||||
Notify.registerType('error', {
|
|
||||||
color: 'negative',
|
|
||||||
icon: 'mdi-alert-circle',
|
|
||||||
progress: true,
|
|
||||||
position: 'bottom',
|
|
||||||
actions: [{ icon: 'mdi-close', color: 'white' }],
|
|
||||||
});
|
|
||||||
});
|
|
|
@ -5,7 +5,6 @@ import routes from 'src/router/routes';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
import { Router, RouteRecordRaw } from 'vue-router';
|
import { Router, RouteRecordRaw } from 'vue-router';
|
||||||
import { UserSessionState } from 'src/plugins/user/store';
|
|
||||||
|
|
||||||
const config: { [key: string]: Array<string> } = {
|
const config: { [key: string]: Array<string> } = {
|
||||||
// Do not change required Modules !!
|
// Do not change required Modules !!
|
||||||
|
@ -160,7 +159,7 @@ function loadPlugin(
|
||||||
modules: string[],
|
modules: string[],
|
||||||
backendpromise: Promise<Backend | null>,
|
backendpromise: Promise<Backend | null>,
|
||||||
plugins: FG_Plugin.Plugin[],
|
plugins: FG_Plugin.Plugin[],
|
||||||
store: Store<UserSessionState>,
|
store: Store<unknown>,
|
||||||
router: Router
|
router: Router
|
||||||
): FG_Plugin.Flaschengeist {
|
): FG_Plugin.Flaschengeist {
|
||||||
modules.forEach((requiredModule) => {
|
modules.forEach((requiredModule) => {
|
||||||
|
@ -218,7 +217,7 @@ async function getBackend(): Promise<Backend | null> {
|
||||||
|
|
||||||
// "async" is optional;
|
// "async" is optional;
|
||||||
// more info on params: https://quasar.dev/quasar-cli/cli-documentation/boot-files#Anatomy-of-a-boot-file
|
// more info on params: https://quasar.dev/quasar-cli/cli-documentation/boot-files#Anatomy-of-a-boot-file
|
||||||
export default boot<UserSessionState>(({ router, store, app }) => {
|
export default boot(({ router, app, store }) => {
|
||||||
const plugins: FG_Plugin.Plugin[] = [];
|
const plugins: FG_Plugin.Plugin[] = [];
|
||||||
|
|
||||||
const backendPromise = getBackend();
|
const backendPromise = getBackend();
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
import { createPinia } from 'pinia';
|
||||||
|
import { boot } from 'quasar/wrappers';
|
||||||
|
import { useMainStore } from 'src/store';
|
||||||
|
|
||||||
|
export default boot(({ app }) => {
|
||||||
|
app.use(createPinia());
|
||||||
|
|
||||||
|
const store = useMainStore();
|
||||||
|
void store.init();
|
||||||
|
});
|
|
@ -5,7 +5,7 @@
|
||||||
</q-item-section>
|
</q-item-section>
|
||||||
|
|
||||||
<q-item-section>
|
<q-item-section>
|
||||||
<q-item-label>{{ realTitle }}</q-item-label>
|
<q-item-label>{{ title }}</q-item-label>
|
||||||
<!--<q-item-label caption>
|
<!--<q-item-label caption>
|
||||||
{{ caption }}
|
{{ caption }}
|
||||||
</q-item-label>-->
|
</q-item-label>-->
|
||||||
|
@ -14,7 +14,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from 'vuex';
|
|
||||||
import { computed, defineComponent } from 'vue';
|
import { computed, defineComponent } from 'vue';
|
||||||
import { hasPermissions } from 'src/utils/permission';
|
import { hasPermissions } from 'src/utils/permission';
|
||||||
|
|
||||||
|
@ -44,22 +43,8 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props) {
|
setup(props) {
|
||||||
let title = computed<string>(() => {
|
|
||||||
if (props.title.includes('loadFromStore')) {
|
|
||||||
const store = useStore();
|
|
||||||
|
|
||||||
const startIndex = props.title.indexOf('(') + 1;
|
|
||||||
const endIndex = props.title.indexOf(')');
|
|
||||||
const substring = props.title.substring(startIndex, endIndex).replace(/"/g, '');
|
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
|
|
||||||
return <string>store.getters[substring];
|
|
||||||
}
|
|
||||||
return props.title;
|
|
||||||
});
|
|
||||||
|
|
||||||
const isGranted = computed(() => hasPermissions(props.permissions));
|
const isGranted = computed(() => hasPermissions(props.permissions));
|
||||||
|
return { isGranted };
|
||||||
return { realTitle: title, isGranted };
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
|
@ -13,10 +13,10 @@ declare namespace FG {
|
||||||
firstname: string;
|
firstname: string;
|
||||||
lastname: string;
|
lastname: string;
|
||||||
mail: string;
|
mail: string;
|
||||||
birthday?: any;
|
birthday?: Date;
|
||||||
roles: Array<string>;
|
roles: Array<string>;
|
||||||
permissions?: any;
|
permissions?: Array<Permission>;
|
||||||
avatar_url?: any;
|
avatar_url?: string;
|
||||||
}
|
}
|
||||||
type Permission = string;
|
type Permission = string;
|
||||||
interface Role {
|
interface Role {
|
||||||
|
@ -29,51 +29,16 @@ declare namespace FG {
|
||||||
time: Date;
|
time: Date;
|
||||||
amount: number;
|
amount: number;
|
||||||
reversal_id: number;
|
reversal_id: number;
|
||||||
sender_id?: any;
|
sender_id?: string;
|
||||||
receiver_id?: any;
|
receiver_id?: string;
|
||||||
author_id?: any;
|
author_id?: string;
|
||||||
original_id?: any;
|
original_id?: number;
|
||||||
}
|
|
||||||
interface Drink {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
volume: number;
|
|
||||||
cost_price: number;
|
|
||||||
discount: number;
|
|
||||||
extra_charge?: any;
|
|
||||||
prices: Array<DrinkPrice>;
|
|
||||||
ingredients: Array<Ingredient>;
|
|
||||||
tags: Array<any>;
|
|
||||||
}
|
|
||||||
interface DrinkPrice {
|
|
||||||
id: number;
|
|
||||||
volume: number;
|
|
||||||
price: number;
|
|
||||||
no_auto: boolean;
|
|
||||||
public: boolean;
|
|
||||||
description?: any;
|
|
||||||
round_step: number;
|
|
||||||
}
|
|
||||||
interface DrinkType {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
|
||||||
interface Ingredient {
|
|
||||||
id: number;
|
|
||||||
volume: number;
|
|
||||||
drink_parent_id: number;
|
|
||||||
drink_ingredient_id: number;
|
|
||||||
drink_ingredient?: any;
|
|
||||||
}
|
|
||||||
interface Tag {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
}
|
}
|
||||||
interface Event {
|
interface Event {
|
||||||
id: number;
|
id: number;
|
||||||
start: Date;
|
start: Date;
|
||||||
end: Date;
|
end: Date;
|
||||||
description?: any;
|
description?: string;
|
||||||
type: EventType;
|
type: EventType;
|
||||||
jobs: Array<Job>;
|
jobs: Array<Job>;
|
||||||
}
|
}
|
||||||
|
@ -84,7 +49,7 @@ declare namespace FG {
|
||||||
interface Job {
|
interface Job {
|
||||||
id: number;
|
id: number;
|
||||||
start: Date;
|
start: Date;
|
||||||
end?: any;
|
end?: Date;
|
||||||
comment: string;
|
comment: string;
|
||||||
type: JobType;
|
type: JobType;
|
||||||
services: Array<Service>;
|
services: Array<Service>;
|
||||||
|
|
|
@ -48,7 +48,7 @@
|
||||||
<essential-link
|
<essential-link
|
||||||
v-for="(link, index) in flaschengeist.mainLinks"
|
v-for="(link, index) in flaschengeist.mainLinks"
|
||||||
:key="'plugin' + index"
|
:key="'plugin' + index"
|
||||||
:title="link.title"
|
:title="typeof link.title === 'object' ? link.title.value : link.title"
|
||||||
:link="link.link"
|
:link="link.link"
|
||||||
:icon="link.icon"
|
:icon="link.icon"
|
||||||
:permissions="link.permissions"
|
:permissions="link.permissions"
|
||||||
|
@ -101,11 +101,11 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import EssentialLink from 'components/navigation/EssentialLink.vue';
|
import EssentialLink from 'components/navigation/EssentialLink.vue';
|
||||||
import ShortCutLink from 'components/navigation/ShortCutLink.vue';
|
import ShortCutLink from 'components/navigation/ShortCutLink.vue';
|
||||||
import { Screen, Loading } from 'quasar';
|
import { Screen } from 'quasar';
|
||||||
import { defineComponent, ref, inject, computed } from 'vue';
|
import { defineComponent, ref, inject, computed } from 'vue';
|
||||||
|
import { useMainStore } from 'src/store';
|
||||||
import { FG_Plugin } from 'src/plugins';
|
import { FG_Plugin } from 'src/plugins';
|
||||||
import { useRoute } from 'vue-router';
|
import { useRoute } from 'vue-router';
|
||||||
import { useStore } from 'vuex';
|
|
||||||
|
|
||||||
const links = [
|
const links = [
|
||||||
{
|
{
|
||||||
|
@ -136,7 +136,7 @@ export default defineComponent({
|
||||||
components: { EssentialLink, ShortCutLink },
|
components: { EssentialLink, ShortCutLink },
|
||||||
setup() {
|
setup() {
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const store = useStore();
|
const mainStore = useMainStore();
|
||||||
const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist');
|
const flaschengeist = inject<FG_Plugin.Flaschengeist>('flaschengeist');
|
||||||
const leftDrawer = ref(false);
|
const leftDrawer = ref(false);
|
||||||
|
|
||||||
|
@ -171,10 +171,7 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
function logout() {
|
function logout() {
|
||||||
Loading.show({ message: 'Session wird abgemeldet' });
|
void mainStore.logout();
|
||||||
store.dispatch('sessions/logout').finally(() => {
|
|
||||||
Loading.hide();
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -54,19 +54,19 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from 'vuex';
|
import { useMainStore } from 'src/store';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { Loading, Notify } from 'quasar';
|
import { Loading, Notify } from 'quasar';
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { setBaseUrl, api } from 'boot/axios';
|
import { setBaseURL, api } from 'boot/axios';
|
||||||
import { UserSessionState } from 'src/plugins/user/store';
|
import { useUserStore } from 'src/plugins/user/store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// name: 'PageName'
|
name: 'Login',
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<UserSessionState>();
|
const mainStore = useMainStore();
|
||||||
const router = useRouter();
|
|
||||||
const mainRoute = { name: 'dashboard' };
|
const mainRoute = { name: 'dashboard' };
|
||||||
|
const router = useRouter();
|
||||||
|
|
||||||
/* Stuff for the real login page */
|
/* Stuff for the real login page */
|
||||||
const userid = ref('');
|
const userid = ref('');
|
||||||
|
@ -81,26 +81,21 @@ export default defineComponent({
|
||||||
|
|
||||||
function changeUrl() {
|
function changeUrl() {
|
||||||
if (server.value) {
|
if (server.value) {
|
||||||
setBaseUrl(server.value);
|
setBaseURL(server.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
function doLogin() {
|
async function doLogin() {
|
||||||
Loading.show({
|
Loading.show({ message: 'Du wirst angemeldet' });
|
||||||
message: 'Du wirst angemeldet',
|
const status = await mainStore.login(userid.value, password.value);
|
||||||
});
|
|
||||||
store
|
if (status === true) {
|
||||||
.dispatch('sessions/login', {
|
mainStore.user = (await useUserStore().getUser(userid.value)) || undefined;
|
||||||
userid: userid.value,
|
|
||||||
password: password.value,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
const x = router.currentRoute.value.query['redirect'];
|
const x = router.currentRoute.value.query['redirect'];
|
||||||
void router.push(typeof x === 'string' ? { path: x } : mainRoute);
|
void router.push(typeof x === 'string' ? { path: x } : mainRoute);
|
||||||
})
|
} else {
|
||||||
.catch((error: { status: number } | undefined) => {
|
|
||||||
if (error && error.status === 401) {
|
|
||||||
password.value = '';
|
password.value = '';
|
||||||
|
if (status === 401) {
|
||||||
Notify.create({
|
Notify.create({
|
||||||
group: false,
|
group: false,
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
|
@ -110,13 +105,11 @@ export default defineComponent({
|
||||||
actions: [{ icon: 'mdi-close', color: 'white' }],
|
actions: [{ icon: 'mdi-close', color: 'white' }],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
.finally(() => {
|
|
||||||
Loading.hide();
|
Loading.hide();
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function doReset() {
|
async function doReset() {
|
||||||
if (userid.value == '') {
|
if (userid.value == '') {
|
||||||
Notify.create({
|
Notify.create({
|
||||||
group: false,
|
group: false,
|
||||||
|
@ -128,11 +121,8 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void store
|
|
||||||
.dispatch('sessions/requestPasswordReset', {
|
if (await mainStore.requestReset(userid.value)) {
|
||||||
userid: userid.value,
|
|
||||||
})
|
|
||||||
.then(() => {
|
|
||||||
userid.value = '';
|
userid.value = '';
|
||||||
password.value = '';
|
password.value = '';
|
||||||
Notify.create({
|
Notify.create({
|
||||||
|
@ -144,7 +134,7 @@ export default defineComponent({
|
||||||
progress: true,
|
progress: true,
|
||||||
actions: [{ icon: 'mdi-close', color: 'white' }],
|
actions: [{ icon: 'mdi-close', color: 'white' }],
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -47,8 +47,6 @@ export default defineComponent({
|
||||||
reload.value -= 1;
|
reload.value -= 1;
|
||||||
if (reload.value === 0) {
|
if (reload.value === 0) {
|
||||||
const path = router.currentRoute.value.query.redirect;
|
const path = router.currentRoute.value.query.redirect;
|
||||||
console.log('Offline: ');
|
|
||||||
console.log(path);
|
|
||||||
void router.replace(path ? { path: <string>path } : { name: 'login' });
|
void router.replace(path ? { path: <string>path } : { name: 'login' });
|
||||||
}
|
}
|
||||||
}, 1000);
|
}, 1000);
|
||||||
|
|
|
@ -34,17 +34,15 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from 'vuex';
|
|
||||||
import { AxiosResponse } from 'axios';
|
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { Loading, Notify } from 'quasar';
|
import { Loading, Notify } from 'quasar';
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { UserSessionState } from 'src/plugins/user/store';
|
import { useMainStore } from 'src/store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// name: 'PageName'
|
// name: 'PageName'
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<UserSessionState>();
|
const mainStore = useMainStore();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const password2 = ref('');
|
const password2 = ref('');
|
||||||
|
@ -68,16 +66,27 @@ export default defineComponent({
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const token = router.currentRoute.value.query.token;
|
||||||
|
if (token === null)
|
||||||
|
if (password.value !== password2.value) {
|
||||||
|
Notify.create({
|
||||||
|
group: false,
|
||||||
|
type: 'negative',
|
||||||
|
message: 'Der Link wurde nicht richtig geöffnet, Token nicht gefunden.',
|
||||||
|
timeout: 10000,
|
||||||
|
progress: true,
|
||||||
|
actions: [{ icon: 'mdi-close', color: 'white' }],
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Loading.show({
|
Loading.show({
|
||||||
message: 'Das Passwort wird zurückgesetzt',
|
message: 'Das Passwort wird zurückgesetzt',
|
||||||
});
|
});
|
||||||
store
|
mainStore
|
||||||
.dispatch('sessions/resetPassword', {
|
.resetPassword(<string>token, password.value)
|
||||||
password: password.value,
|
.catch((status) => {
|
||||||
token: router.currentRoute.value.query.token,
|
if (status == 403) {
|
||||||
})
|
|
||||||
.catch((error: AxiosResponse) => {
|
|
||||||
if (error.status == 403) {
|
|
||||||
Notify.create({
|
Notify.create({
|
||||||
group: false,
|
group: false,
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
import { Module } from 'vuex';
|
import { Module } from 'vuex';
|
||||||
import { Component } from 'vue';
|
import { Component, ComputedRef } from 'vue';
|
||||||
|
|
||||||
declare namespace FG_Plugin {
|
declare namespace FG_Plugin {
|
||||||
interface ShortCutLink {
|
interface ShortCutLink {
|
||||||
|
@ -10,7 +10,7 @@ declare namespace FG_Plugin {
|
||||||
}
|
}
|
||||||
|
|
||||||
interface PluginRouteConfig {
|
interface PluginRouteConfig {
|
||||||
title: string;
|
title: string | ComputedRef<string>;
|
||||||
icon: string;
|
icon: string;
|
||||||
route: RouteRecordRaw;
|
route: RouteRecordRaw;
|
||||||
shortcut?: boolean;
|
shortcut?: boolean;
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
|
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
|
||||||
import { StateInterface } from 'src/store';
|
import { StateInterface, useMainStore } from 'src/store';
|
||||||
import { api } from 'src/boot/axios';
|
import { api } from 'src/boot/axios';
|
||||||
import { AxiosResponse } from 'axios';
|
import { AxiosResponse } from 'axios';
|
||||||
|
|
||||||
|
@ -90,27 +90,28 @@ const mutations: MutationTree<BalanceInterface> = {
|
||||||
|
|
||||||
const actions: ActionTree<BalanceInterface, StateInterface> = {
|
const actions: ActionTree<BalanceInterface, StateInterface> = {
|
||||||
//const actions: ActionTree<BalanceInterface, any> = {
|
//const actions: ActionTree<BalanceInterface, any> = {
|
||||||
addShortcut({ commit, state, rootState }, shortcut) {
|
addShortcut({ commit, state }, shortcut) {
|
||||||
const sc = [...state.shortcuts, shortcut];
|
const sc = [...state.shortcuts, shortcut];
|
||||||
sc.sort();
|
sc.sort();
|
||||||
|
const mainStore = useMainStore();
|
||||||
const user = <FG.User>rootState.users.currentUser;
|
const user = mainStore.currentUser;
|
||||||
return api.put(`/users/${user.userid}/balance/shortcuts`, sc).then(() => {
|
return api.put(`/users/${user.userid}/balance/shortcuts`, sc).then(() => {
|
||||||
commit('setShortcuts', sc);
|
commit('setShortcuts', sc);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
removeShortcut({ commit, state, rootState }, shortcut) {
|
removeShortcut({ commit, state }, shortcut) {
|
||||||
const sc = state.shortcuts.filter((value: number) => value != shortcut);
|
const sc = state.shortcuts.filter((value: number) => value != shortcut);
|
||||||
|
const mainStore = useMainStore();
|
||||||
const user = <FG.User>rootState.users.currentUser;
|
const user = mainStore.currentUser;
|
||||||
return api.put(`/users/${user.userid}/balance/shortcuts`, sc).then(() => {
|
return api.put(`/users/${user.userid}/balance/shortcuts`, sc).then(() => {
|
||||||
commit('setShortcuts', sc);
|
commit('setShortcuts', sc);
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getShortcuts({ commit, state, rootState }, force = false) {
|
getShortcuts({ commit, state }, force = false) {
|
||||||
if (force || state.shortcuts.length == 0) {
|
if (force || state.shortcuts.length == 0) {
|
||||||
|
const mainStore = useMainStore();
|
||||||
commit('setLoading');
|
commit('setLoading');
|
||||||
const user = <FG.User>rootState.users.currentUser;
|
const user = mainStore.currentUser;
|
||||||
return api
|
return api
|
||||||
.get(`/users/${user.userid}/balance/shortcuts`)
|
.get(`/users/${user.userid}/balance/shortcuts`)
|
||||||
.then(({ data }: AxiosResponse<BalanceResponse>) => {
|
.then(({ data }: AxiosResponse<BalanceResponse>) => {
|
||||||
|
@ -120,9 +121,10 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
|
||||||
.finally(() => commit('setLoading', false));
|
.finally(() => commit('setLoading', false));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
getBalance({ commit, rootState }, user: FG.User | undefined = undefined) {
|
getBalance({ commit }, user: FG.User | undefined = undefined) {
|
||||||
commit('setLoading');
|
commit('setLoading');
|
||||||
if (!user) user = <FG.User>rootState.users.currentUser;
|
const mainStore = useMainStore();
|
||||||
|
if (!user) user = mainStore.currentUser;
|
||||||
return api
|
return api
|
||||||
.get(`/users/${user.userid}/balance`)
|
.get(`/users/${user.userid}/balance`)
|
||||||
.then(({ data }: AxiosResponse<BalanceResponse>) => {
|
.then(({ data }: AxiosResponse<BalanceResponse>) => {
|
||||||
|
@ -137,7 +139,7 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
getTransactions(
|
getTransactions(
|
||||||
{ commit, rootState },
|
{ commit },
|
||||||
payload: {
|
payload: {
|
||||||
userid?: string;
|
userid?: string;
|
||||||
filter?: {
|
filter?: {
|
||||||
|
@ -151,7 +153,8 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
commit('setLoading');
|
commit('setLoading');
|
||||||
if (!payload.userid) payload.userid = (<FG.User>rootState.users.currentUser).userid;
|
const mainStore = useMainStore();
|
||||||
|
if (!payload.userid) payload.userid = mainStore.currentUser.userid;
|
||||||
if (!payload.filter) payload.filter = { limit: 10 };
|
if (!payload.filter) payload.filter = { limit: 10 };
|
||||||
return api
|
return api
|
||||||
.get(`/users/${payload.userid}/balance/transactions`, { params: payload.filter || {} })
|
.get(`/users/${payload.userid}/balance/transactions`, { params: payload.filter || {} })
|
||||||
|
@ -162,11 +165,12 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
|
||||||
})
|
})
|
||||||
.finally(() => commit('setLoading', false));
|
.finally(() => commit('setLoading', false));
|
||||||
},
|
},
|
||||||
getLimit({ rootState, commit }) {
|
getLimit({ commit }) {
|
||||||
|
const mainStore = useMainStore();
|
||||||
commit('setLoading');
|
commit('setLoading');
|
||||||
api
|
api
|
||||||
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
/* eslint-disable-next-line @typescript-eslint/restrict-template-expressions */
|
||||||
.get(`/users/${rootState.users.currentUser?.userid}/balance/limit`)
|
.get(`/users/${mainStore.currentUser.userid}/balance/limit`)
|
||||||
.then(({ data }) => {
|
.then(({ data }) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
})
|
})
|
||||||
|
@ -184,19 +188,17 @@ const actions: ActionTree<BalanceInterface, StateInterface> = {
|
||||||
dispatch('getBalance').catch((err) => console.warn(err));
|
dispatch('getBalance').catch((err) => console.warn(err));
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
changeBalance(
|
changeBalance({ dispatch, commit }, data: { amount: number; user: string; sender?: string }) {
|
||||||
{ dispatch, commit, rootState },
|
|
||||||
data: { amount: number; user: string; sender?: string }
|
|
||||||
) {
|
|
||||||
commit('setLoading');
|
commit('setLoading');
|
||||||
return api
|
return api
|
||||||
.put(`/users/${data.user}/balance`, data)
|
.put(`/users/${data.user}/balance`, data)
|
||||||
.then((response: AxiosResponse<FG.Transaction>) => {
|
.then((response: AxiosResponse<FG.Transaction>) => {
|
||||||
|
const mainStore = useMainStore();
|
||||||
const transaction = response.data;
|
const transaction = response.data;
|
||||||
fixTransaction(transaction);
|
fixTransaction(transaction);
|
||||||
if (
|
if (
|
||||||
data.user == rootState.users.currentUser?.userid ||
|
data.user == mainStore.currentUser.userid ||
|
||||||
data.sender === rootState.users.currentUser?.userid
|
data.sender === mainStore.currentUser.userid
|
||||||
)
|
)
|
||||||
commit('addTransaction', transaction);
|
commit('addTransaction', transaction);
|
||||||
commit(state.balances.has(data.user) ? 'changeBalance' : 'setBalance', {
|
commit(state.balances.has(data.user) ? 'changeBalance' : 'setBalance', {
|
||||||
|
|
|
@ -12,14 +12,13 @@
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import MainUserSettings from 'src/plugins/user/components/settings/MainUserSettings.vue';
|
import MainUserSettings from 'src/plugins/user/components/settings/MainUserSettings.vue';
|
||||||
import { useStore } from 'vuex';
|
import { useUserStore } from '../store';
|
||||||
import { UserSessionState } from '../store';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'NewUser',
|
name: 'NewUser',
|
||||||
components: { MainUserSettings },
|
components: { MainUserSettings },
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<UserSessionState>();
|
const userStore = useUserStore();
|
||||||
const user = ref<FG.User>({
|
const user = ref<FG.User>({
|
||||||
userid: '',
|
userid: '',
|
||||||
display_name: '',
|
display_name: '',
|
||||||
|
@ -28,10 +27,9 @@ export default defineComponent({
|
||||||
mail: '',
|
mail: '',
|
||||||
roles: [],
|
roles: [],
|
||||||
});
|
});
|
||||||
function setUser(value: FG.User) {
|
|
||||||
store.dispatch('users/setUser', value).catch((error) => {
|
async function setUser(value: FG.User) {
|
||||||
console.warn(error);
|
await userStore.createUser(value);
|
||||||
});
|
|
||||||
}
|
}
|
||||||
return { user, setUser };
|
return { user, setUser };
|
||||||
},
|
},
|
||||||
|
|
|
@ -11,23 +11,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from 'vuex';
|
|
||||||
import { defineComponent, ref } from 'vue';
|
import { defineComponent, ref } from 'vue';
|
||||||
import { UserSessionState } from '../store';
|
|
||||||
import UserSelector from '../components/UserSelector.vue';
|
import UserSelector from '../components/UserSelector.vue';
|
||||||
import MainUserSettings from '../components/settings/MainUserSettings.vue';
|
import MainUserSettings from '../components/settings/MainUserSettings.vue';
|
||||||
|
import { useMainStore } from 'src/store';
|
||||||
|
import { useUserStore } from '../store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'UpdateUser',
|
name: 'UpdateUser',
|
||||||
components: { UserSelector, MainUserSettings },
|
components: { UserSelector, MainUserSettings },
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<UserSessionState>();
|
const mainStore = useMainStore();
|
||||||
const user = ref(<FG.User>store.state.users.currentUser);
|
const userStore = useUserStore();
|
||||||
|
const user = ref(mainStore.currentUser);
|
||||||
|
|
||||||
function updateUser(value: FG.User) {
|
async function updateUser(value: FG.User) {
|
||||||
store.dispatch('users/updateUser', value).catch((error) => {
|
await userStore.updateUser(value);
|
||||||
console.warn(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -12,8 +12,7 @@
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, PropType, onBeforeMount } from 'vue';
|
import { computed, defineComponent, PropType, onBeforeMount } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useUserStore } from '../store';
|
||||||
import { UserSessionState } from '../store';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'UserSelector',
|
name: 'UserSelector',
|
||||||
|
@ -23,15 +22,13 @@ export default defineComponent({
|
||||||
},
|
},
|
||||||
emits: { 'update:modelValue': (user: FG.User) => !!user },
|
emits: { 'update:modelValue': (user: FG.User) => !!user },
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const store = useStore<UserSessionState>();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
store.dispatch('users/getUsers').catch((error) => {
|
void userStore.getUsers(false);
|
||||||
console.error(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const users = computed(() => store.state.users.users);
|
const users = computed(() => userStore.users);
|
||||||
const selected = computed({
|
const selected = computed({
|
||||||
get: () => props.modelValue,
|
get: () => props.modelValue,
|
||||||
set: (value: FG.User | undefined) => (value ? emit('update:modelValue', value) : undefined),
|
set: (value: FG.User | undefined) => (value ? emit('update:modelValue', value) : undefined),
|
||||||
|
|
|
@ -26,19 +26,21 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
|
import { useMainStore } from 'src/store';
|
||||||
import { computed, defineComponent, onMounted, ref } from 'vue';
|
import { computed, defineComponent, onMounted, ref } from 'vue';
|
||||||
import { useStore } from 'vuex';
|
import { useUserStore } from '../store';
|
||||||
import { UserSessionState } from '../store';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Greeting',
|
name: 'Greeting',
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<UserSessionState>();
|
const mainStore = useMainStore();
|
||||||
onMounted(() => store.dispatch('users/getUsers', false));
|
const userStore = useUserStore();
|
||||||
|
|
||||||
const name = ref(store.state.users.currentUser?.display_name);
|
// Ensure users are loaded,so we can query birthdays
|
||||||
|
onMounted(() => userStore.getUsers(false));
|
||||||
|
|
||||||
const avatarLink = ref(store.state.users.currentUser?.avatar_url);
|
const name = ref(mainStore.currentUser.display_name);
|
||||||
|
const avatarLink = ref(mainStore.currentUser.avatar_url);
|
||||||
|
|
||||||
function userHasBirthday(user: FG.User) {
|
function userHasBirthday(user: FG.User) {
|
||||||
const today = new Date();
|
const today = new Date();
|
||||||
|
@ -50,13 +52,13 @@ export default defineComponent({
|
||||||
}
|
}
|
||||||
|
|
||||||
const hasBirthday = computed(() => {
|
const hasBirthday = computed(() => {
|
||||||
return userHasBirthday(<FG.User>store.state.users.currentUser);
|
return userHasBirthday(mainStore.currentUser);
|
||||||
});
|
});
|
||||||
|
|
||||||
const birthday = computed(() =>
|
const birthday = computed(() =>
|
||||||
store.state.users.users
|
userStore.users
|
||||||
.filter(userHasBirthday)
|
.filter(userHasBirthday)
|
||||||
.filter((user) => user.userid !== store.state.users.currentUser?.userid)
|
.filter((user) => user.userid !== mainStore.currentUser.userid)
|
||||||
);
|
);
|
||||||
|
|
||||||
return { avatarLink, name, hasBirthday, birthday };
|
return { avatarLink, name, hasBirthday, birthday };
|
||||||
|
|
|
@ -107,12 +107,12 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from 'vuex';
|
|
||||||
import { Notify } from 'quasar';
|
import { Notify } from 'quasar';
|
||||||
import { UserSessionState } from '../../store';
|
|
||||||
import { hasPermission } from 'src/utils/permission';
|
import { hasPermission } from 'src/utils/permission';
|
||||||
import IsoDateInput from 'src/components/utils/IsoDateInput.vue';
|
import IsoDateInput from 'src/components/utils/IsoDateInput.vue';
|
||||||
import { defineComponent, computed, ref, onBeforeMount, PropType } from 'vue';
|
import { defineComponent, computed, ref, onBeforeMount, PropType } from 'vue';
|
||||||
|
import { useUserStore } from '../../store';
|
||||||
|
import { useMainStore } from 'src/store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'MainUserSettings',
|
name: 'MainUserSettings',
|
||||||
|
@ -128,22 +128,20 @@ export default defineComponent({
|
||||||
'update:user': (payload: FG.User) => !!payload,
|
'update:user': (payload: FG.User) => !!payload,
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const store = useStore<UserSessionState>();
|
const userStore = useUserStore();
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
|
||||||
const user_model = ref(props.user);
|
const user_model = ref(props.user);
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
store.dispatch('users/getRoles', false).catch((error) => {
|
void userStore.getRoles(false);
|
||||||
console.warn(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const isCurrentUser = computed(
|
const isCurrentUser = computed(() => user_model.value.userid === mainStore.currentUser.userid);
|
||||||
() => user_model.value.userid === store.state.users.currentUser?.userid
|
|
||||||
);
|
|
||||||
|
|
||||||
const canSetRoles = computed(() => hasPermission('users_set_roles'));
|
const canSetRoles = computed(() => hasPermission('users_set_roles'));
|
||||||
|
|
||||||
const avatar = ref([]);
|
const avatar = ref([] as string[]);
|
||||||
function onAvatarRejected() {
|
function onAvatarRejected() {
|
||||||
Notify.create({
|
Notify.create({
|
||||||
group: false,
|
group: false,
|
||||||
|
@ -156,7 +154,7 @@ export default defineComponent({
|
||||||
avatar.value = [];
|
avatar.value = [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const allRoles = computed(() => store.state.users.roles.map((role) => role.name));
|
const allRoles = computed(() => userStore.roles.map((role) => role.name));
|
||||||
const password = ref('');
|
const password = ref('');
|
||||||
const new_password = ref('');
|
const new_password = ref('');
|
||||||
const new_password2 = ref('');
|
const new_password2 = ref('');
|
||||||
|
@ -176,12 +174,7 @@ export default defineComponent({
|
||||||
emit('update:user', changed);
|
emit('update:user', changed);
|
||||||
|
|
||||||
if (avatar.value && (avatar.value.length > 0 || avatar.value instanceof File))
|
if (avatar.value && (avatar.value.length > 0 || avatar.value instanceof File))
|
||||||
store
|
userStore.uploadAvatar(changed, avatar.value[0]).catch((response: Response) => {
|
||||||
.dispatch('users/uploadAvatar', {
|
|
||||||
user: changed,
|
|
||||||
file: avatar.value,
|
|
||||||
})
|
|
||||||
.catch((response: Response) => {
|
|
||||||
if (response && response.status == 400) {
|
if (response && response.status == 400) {
|
||||||
onAvatarRejected();
|
onAvatarRejected();
|
||||||
}
|
}
|
||||||
|
@ -212,7 +205,7 @@ export default defineComponent({
|
||||||
|
|
||||||
function isUseridUsed(val: string) {
|
function isUseridUsed(val: string) {
|
||||||
return (
|
return (
|
||||||
!store.state.users.users.find((user: FG.User) => {
|
!userStore.users.find((user: FG.User) => {
|
||||||
return user.userid == val;
|
return user.userid == val;
|
||||||
}) ||
|
}) ||
|
||||||
!props.newUser ||
|
!props.newUser ||
|
||||||
|
|
|
@ -45,28 +45,23 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from 'vuex';
|
|
||||||
import { UserSessionState } from '../../store';
|
|
||||||
import { computed, defineComponent, ref, onBeforeMount } from 'vue';
|
import { computed, defineComponent, ref, onBeforeMount } from 'vue';
|
||||||
|
import { useUserStore } from '../../store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'RoleSettings',
|
name: 'RoleSettings',
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<UserSessionState>();
|
const userStore = useUserStore();
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => {
|
||||||
store.dispatch('users/getRoles').catch((error) => {
|
void userStore.getRoles();
|
||||||
console.warn(error);
|
void userStore.getPermissions();
|
||||||
});
|
|
||||||
store.dispatch('users/getPermissions').catch((error) => {
|
|
||||||
console.warn(error);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const role = ref<FG.Role | null>(null);
|
const role = ref<FG.Role | null>(null);
|
||||||
const roles = computed(() => store.state.users.roles);
|
const roles = computed(() => userStore.roles);
|
||||||
const permissions = computed(() =>
|
const permissions = computed(() =>
|
||||||
store.state.users.permissions.map((perm) => {
|
userStore.permissions.map((perm) => {
|
||||||
return {
|
return {
|
||||||
value: perm,
|
value: perm,
|
||||||
label: perm,
|
label: perm,
|
||||||
|
@ -103,14 +98,14 @@ export default defineComponent({
|
||||||
function save() {
|
function save() {
|
||||||
if (role.value) {
|
if (role.value) {
|
||||||
if (role.value.id === -1)
|
if (role.value.id === -1)
|
||||||
void store.dispatch('users/newRole', role.value).then((createdRole: FG.Role) => {
|
void userStore.newRole(role.value).then((createdRole: FG.Role) => {
|
||||||
console.log(createdRole);
|
console.log(createdRole);
|
||||||
role.value = createdRole;
|
role.value = createdRole;
|
||||||
});
|
});
|
||||||
else {
|
else {
|
||||||
if (newRoleName.value !== '') role.value.name = newRoleName.value;
|
if (newRoleName.value !== '') role.value.name = newRoleName.value;
|
||||||
console.log(role.value);
|
console.log(role.value);
|
||||||
void store.dispatch('users/updateRole', role.value);
|
void userStore.updateRole(role.value);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -129,10 +124,7 @@ export default defineComponent({
|
||||||
if (role.value.id === -1) {
|
if (role.value.id === -1) {
|
||||||
role.value = null;
|
role.value = null;
|
||||||
} else {
|
} else {
|
||||||
store
|
void userStore.deleteRole(role.value).then(() => (role.value = null));
|
||||||
.dispatch('users/deleteRole', role.value)
|
|
||||||
.then(() => (role.value = null))
|
|
||||||
.catch((error) => console.warn(error));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -46,21 +46,22 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { useStore } from 'vuex';
|
import { defineComponent, ref, computed, PropType } from 'vue';
|
||||||
import { defineComponent, ref, computed } from 'vue';
|
|
||||||
import { UserSessionState } from '../../store';
|
|
||||||
import { formatDateTime } from 'src/utils/datetime';
|
import { formatDateTime } from 'src/utils/datetime';
|
||||||
|
import { useMainStore } from 'src/store';
|
||||||
|
import { useSessionStore } from '../../store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'Sessions',
|
name: 'Sessions',
|
||||||
props: {
|
props: {
|
||||||
session: {
|
session: {
|
||||||
required: true,
|
required: true,
|
||||||
type: Object,
|
type: Object as PropType<FG.Session>,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const store = useStore<UserSessionState>();
|
const sessionStore = useSessionStore();
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
|
||||||
const dateTime = (date: Date) => formatDateTime(date, true);
|
const dateTime = (date: Date) => formatDateTime(date, true);
|
||||||
|
|
||||||
|
@ -91,13 +92,11 @@ export default defineComponent({
|
||||||
: 'mdi-help';
|
: 'mdi-help';
|
||||||
}
|
}
|
||||||
|
|
||||||
function deleteSession(token: string) {
|
async function deleteSession(token: string) {
|
||||||
store.dispatch('sessions/deleteSession', token).catch((error) => {
|
await sessionStore.deleteSession(token);
|
||||||
console.warn(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
function isThisSession(token: string) {
|
function isThisSession(token: string) {
|
||||||
return store.state.sessions.currentSession?.token === token;
|
return mainStore.session?.token === token;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
|
@ -132,21 +131,14 @@ export default defineComponent({
|
||||||
});
|
});
|
||||||
|
|
||||||
function edit(value: boolean) {
|
function edit(value: boolean) {
|
||||||
lifetime.value = (<FG.Session>props.session).lifetime;
|
lifetime.value = props.session.lifetime;
|
||||||
isEdit.value = value;
|
isEdit.value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function save() {
|
async function save() {
|
||||||
console.log(lifetime.value);
|
console.log(lifetime.value);
|
||||||
isEdit.value = false;
|
isEdit.value = false;
|
||||||
void store
|
await sessionStore.updateSession(lifetime.value, props.session.token);
|
||||||
.dispatch('sessions/updateSession', {
|
|
||||||
lifetime: lifetime.value,
|
|
||||||
token: (<FG.Session>props.session).token,
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|
|
@ -14,36 +14,29 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { computed, defineComponent, onBeforeMount, ref } from 'vue';
|
import { defineComponent, onBeforeMount, ref } from 'vue';
|
||||||
import Sessions from '../components/settings/Sessions.vue';
|
import Sessions from '../components/settings/Sessions.vue';
|
||||||
import MainUserSettings from '../components/settings/MainUserSettings.vue';
|
import MainUserSettings from '../components/settings/MainUserSettings.vue';
|
||||||
import { useStore } from 'vuex';
|
import { useMainStore } from 'src/store';
|
||||||
import setLoadingBar from 'src/utils/loading';
|
import { useSessionStore } from '../store';
|
||||||
import { UserSessionState } from '../store';
|
import { useUserStore } from '../store';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
// name: 'PageName'
|
// name: 'PageName'
|
||||||
components: { Sessions, MainUserSettings },
|
components: { Sessions, MainUserSettings },
|
||||||
setup() {
|
setup() {
|
||||||
const store = useStore<UserSessionState>();
|
const mainStore = useMainStore();
|
||||||
|
const sessionStore = useSessionStore();
|
||||||
|
const userStore = useUserStore();
|
||||||
|
|
||||||
onBeforeMount(() => {
|
onBeforeMount(() => sessionStore.getSessions().then((s) => (sessions.value = s)));
|
||||||
store.dispatch('sessions/getSessions').catch((error) => {
|
const currentUser = ref(mainStore.currentUser);
|
||||||
console.warn(error);
|
const sessions = ref([] as FG.Session[]);
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const currentUser = ref(<FG.User>store.state.users.currentUser);
|
async function updateUser(value: FG.User) {
|
||||||
const sessions = computed(() => store.state.sessions.sessions);
|
await userStore.updateUser(value);
|
||||||
const loading = computed(() => store.state.sessions.loading || store.state.users.loading > 0);
|
|
||||||
function updateUser(value: FG.User) {
|
|
||||||
store.dispatch('users/updateUser', value).catch((error) => {
|
|
||||||
console.warn(error);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
setLoadingBar(loading);
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
currentUser,
|
currentUser,
|
||||||
sessions,
|
sessions,
|
||||||
|
|
|
@ -1,10 +1,6 @@
|
||||||
import { Module } from 'vuex';
|
|
||||||
import routes from './routes';
|
import routes from './routes';
|
||||||
import { FG_Plugin } from 'src/plugins';
|
import { FG_Plugin } from 'src/plugins';
|
||||||
import { defineAsyncComponent } from 'vue';
|
import { defineAsyncComponent } from 'vue';
|
||||||
import { UserSessionState } from './store';
|
|
||||||
import usersStore, { UserStateInterface } from './store/user';
|
|
||||||
import sessionsStorage, { SessionStateInterface } from './store/session';
|
|
||||||
|
|
||||||
const plugin: FG_Plugin.Plugin = {
|
const plugin: FG_Plugin.Plugin = {
|
||||||
name: 'User',
|
name: 'User',
|
||||||
|
@ -12,13 +8,6 @@ const plugin: FG_Plugin.Plugin = {
|
||||||
requiredModules: [],
|
requiredModules: [],
|
||||||
requiredBackendModules: ['auth'],
|
requiredBackendModules: ['auth'],
|
||||||
version: '0.0.1',
|
version: '0.0.1',
|
||||||
store: new Map<
|
|
||||||
string,
|
|
||||||
Module<UserStateInterface, UserSessionState> | Module<SessionStateInterface, UserSessionState>
|
|
||||||
>([
|
|
||||||
['users', usersStore],
|
|
||||||
['sessions', sessionsStorage],
|
|
||||||
]),
|
|
||||||
widgets: [
|
widgets: [
|
||||||
{
|
{
|
||||||
priority: 1,
|
priority: 1,
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
import { FG_Plugin } from 'src/plugins';
|
import { FG_Plugin } from 'src/plugins';
|
||||||
|
import { useMainStore } from 'src/store';
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
|
const mainRoutes: FG_Plugin.PluginRouteConfig[] = [
|
||||||
{
|
{
|
||||||
title: 'loadFromStore("users/displayName")',
|
get title() {
|
||||||
|
return computed(() => useMainStore().user?.display_name || 'Not loaded');
|
||||||
|
},
|
||||||
icon: 'mdi-account',
|
icon: 'mdi-account',
|
||||||
permissions: ['user'],
|
permissions: ['user'],
|
||||||
route: { path: 'user', name: 'user', component: () => import('../pages/MainPage.vue') },
|
route: { path: 'user', name: 'user', component: () => import('../pages/MainPage.vue') },
|
||||||
|
|
|
@ -0,0 +1,182 @@
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { api } from 'src/boot/axios';
|
||||||
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
|
import { useMainStore } from 'src/store';
|
||||||
|
|
||||||
|
export const useUserStore = defineStore({
|
||||||
|
id: 'users',
|
||||||
|
|
||||||
|
state: () => ({
|
||||||
|
roles: [] as FG.Role[],
|
||||||
|
users: [] as FG.User[],
|
||||||
|
permissions: [] as FG.Permission[],
|
||||||
|
_dirty_users: 0,
|
||||||
|
_dirty_roles: 0,
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
isDirty() {
|
||||||
|
return new Date().getTime() - this._dirty_users > 60000;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async getUser(userid: string, force = true) {
|
||||||
|
const idx = this.users.findIndex((user) => user.userid === userid);
|
||||||
|
if (force || idx == -1 || this.isDirty) {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get<FG.User>(`/users/${userid}`);
|
||||||
|
if (data.birthday) data.birthday = new Date(data.birthday);
|
||||||
|
if (idx === -1) this.users.push(data);
|
||||||
|
else this.users[idx] = data;
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
if (!error || !('response' in error) || (<AxiosError>error).response?.status !== 404)
|
||||||
|
throw error;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return this.users[idx];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async getUsers(force = true) {
|
||||||
|
if (force || this.isDirty) {
|
||||||
|
const { data } = await api.get<FG.User[]>('/users');
|
||||||
|
data.forEach((user) => {
|
||||||
|
if (user.birthday) user.birthday = new Date(user.birthday);
|
||||||
|
});
|
||||||
|
this.users = data;
|
||||||
|
this._dirty_users = new Date().getTime();
|
||||||
|
} else {
|
||||||
|
return this.users;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateUser(user: FG.User) {
|
||||||
|
await api.put(`/users/${user.userid}`, user);
|
||||||
|
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
if (user.userid === mainStore.user?.userid) mainStore.user = user;
|
||||||
|
this._dirty_users = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
async createUser(user: FG.User) {
|
||||||
|
const { data } = await api.post<FG.User>('/users', user);
|
||||||
|
this.users.push(data);
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async uploadAvatar(user: FG.User, file: string) {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', file);
|
||||||
|
await api.post(`/users/${user.userid}/avatar`, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async getPermissions(force = false) {
|
||||||
|
if (force || this.permissions.length === 0) {
|
||||||
|
const { data } = await api.get<FG.Permission[]>('/roles/permissions');
|
||||||
|
this.permissions = data;
|
||||||
|
}
|
||||||
|
return this.permissions;
|
||||||
|
},
|
||||||
|
|
||||||
|
async getRoles(force = false) {
|
||||||
|
if (force || new Date().getTime() - this._dirty_roles > 60000) {
|
||||||
|
const { data } = await api.get<FG.Role[]>('/roles');
|
||||||
|
this.roles = data;
|
||||||
|
this._dirty_roles = new Date().getTime();
|
||||||
|
}
|
||||||
|
return this.roles;
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateRole(role: FG.Role) {
|
||||||
|
await api.put(`/roles/${role.id}`, role);
|
||||||
|
const idx = this.roles.findIndex((r) => r.id === role.id);
|
||||||
|
if (idx != -1) this.roles[idx] = role;
|
||||||
|
this._dirty_roles = 0;
|
||||||
|
},
|
||||||
|
|
||||||
|
async newRole(role: FG.Role) {
|
||||||
|
const { data } = await api.post<FG.Role>('/roles', role);
|
||||||
|
this.roles.push(data);
|
||||||
|
this._dirty_roles = 0;
|
||||||
|
return data;
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteRole(role: FG.Role | number) {
|
||||||
|
await api.delete(`/roles/${typeof role === 'number' ? role : role.id}`);
|
||||||
|
this.roles = this.roles.filter((r) => r.id !== (typeof role == 'number' ? role : role.id));
|
||||||
|
this._dirty_roles = 0;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
export const useSessionStore = defineStore({
|
||||||
|
id: 'sessions',
|
||||||
|
|
||||||
|
state: () => ({}),
|
||||||
|
|
||||||
|
getters: {},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
async getSession(token: string) {
|
||||||
|
return await api
|
||||||
|
.get(`/auth/${token}`)
|
||||||
|
.then(({ data }: AxiosResponse<FG.Session>) => data)
|
||||||
|
.catch(() => null);
|
||||||
|
},
|
||||||
|
|
||||||
|
async getSessions() {
|
||||||
|
try {
|
||||||
|
const { data } = await api.get<FG.Session[]>('/auth');
|
||||||
|
data.forEach((session) => {
|
||||||
|
session.expires = new Date(session.expires);
|
||||||
|
});
|
||||||
|
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
const currentSession = data.find((session) => {
|
||||||
|
return session.token === mainStore.session?.token;
|
||||||
|
});
|
||||||
|
if (currentSession) {
|
||||||
|
mainStore.session = currentSession;
|
||||||
|
}
|
||||||
|
return data;
|
||||||
|
} catch (error) {
|
||||||
|
return [] as FG.Session[];
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async deleteSession(token: string) {
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
if (token === mainStore.session?.token) return mainStore.logout();
|
||||||
|
|
||||||
|
try {
|
||||||
|
await api.delete(`/auth/${token}`);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
if (!error || !('response' in error) || (<AxiosError>error).response?.status != 401)
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
},
|
||||||
|
|
||||||
|
async updateSession(lifetime: number, token: string) {
|
||||||
|
try {
|
||||||
|
const { data } = await api.put<FG.Session>(`auth/${token}`, { value: lifetime });
|
||||||
|
data.expires = new Date(data.expires);
|
||||||
|
|
||||||
|
const mainStore = useMainStore();
|
||||||
|
if (mainStore.session?.token == data.token) mainStore.session = data;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
|
@ -1,7 +0,0 @@
|
||||||
import { SessionStateInterface } from './session';
|
|
||||||
import { UserStateInterface } from './user';
|
|
||||||
|
|
||||||
export interface UserSessionState {
|
|
||||||
sessions: SessionStateInterface;
|
|
||||||
users: UserStateInterface;
|
|
||||||
}
|
|
|
@ -1,199 +0,0 @@
|
||||||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
|
|
||||||
import { LoginData, LoginResponse } from 'src/plugins/user/models';
|
|
||||||
import { api } from 'src/boot/axios';
|
|
||||||
import { AxiosError, AxiosResponse } from 'axios';
|
|
||||||
import { LocalStorage } from 'quasar';
|
|
||||||
import { UserSessionState } from '.';
|
|
||||||
import { Router } from 'src/router';
|
|
||||||
|
|
||||||
export interface SessionStateInterface {
|
|
||||||
currentSession?: FG.Session;
|
|
||||||
sessions: FG.Session[];
|
|
||||||
loading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Load current session from LocalStorage
|
|
||||||
* Used when we were already authenticated using this browser
|
|
||||||
*/
|
|
||||||
function loadCurrentSession() {
|
|
||||||
const session = LocalStorage.getItem<FG.Session>('currentSession');
|
|
||||||
if (session) session.expires = new Date(session.expires);
|
|
||||||
return session;
|
|
||||||
}
|
|
||||||
|
|
||||||
const state: SessionStateInterface = {
|
|
||||||
sessions: [],
|
|
||||||
currentSession: loadCurrentSession() || undefined,
|
|
||||||
loading: false,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mutations: MutationTree<SessionStateInterface> = {
|
|
||||||
setCurrentSession(state, session: FG.Session) {
|
|
||||||
LocalStorage.set('currentSession', session);
|
|
||||||
state.currentSession = session;
|
|
||||||
},
|
|
||||||
clearCurrentSession(state) {
|
|
||||||
LocalStorage.remove('currentSession');
|
|
||||||
state.currentSession = undefined;
|
|
||||||
},
|
|
||||||
setSessions(state, sessions: FG.Session[]) {
|
|
||||||
state.sessions = sessions;
|
|
||||||
},
|
|
||||||
setLoading(state, value: boolean) {
|
|
||||||
state.loading = value;
|
|
||||||
},
|
|
||||||
updateSession(state, session: FG.Session) {
|
|
||||||
const index = state.sessions.findIndex((x) => x.token == session.token);
|
|
||||||
if (index > -1) {
|
|
||||||
state.sessions[index] = session;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions: ActionTree<SessionStateInterface, UserSessionState> = {
|
|
||||||
/** Used to authenticate the user
|
|
||||||
* Setting current Session, User and Permissions.
|
|
||||||
* @param param0 Context
|
|
||||||
* @param data Credentitals
|
|
||||||
*/
|
|
||||||
login({ commit }, data: LoginData) {
|
|
||||||
return api
|
|
||||||
.post('/auth', data)
|
|
||||||
.then((response: AxiosResponse<LoginResponse>) => {
|
|
||||||
response.data.session.expires = new Date(response.data.session.expires);
|
|
||||||
commit('setCurrentSession', response.data.session);
|
|
||||||
commit('users/setCurrentUser', response.data.user, { root: true });
|
|
||||||
commit('users/setCurrentPermissions', response.data.permissions, {
|
|
||||||
root: true,
|
|
||||||
});
|
|
||||||
})
|
|
||||||
.catch((error: AxiosError) => {
|
|
||||||
return Promise.reject(error.response);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Logout from current session
|
|
||||||
* Alias of deleteSession with current session as target
|
|
||||||
*/
|
|
||||||
logout({ dispatch, rootState }) {
|
|
||||||
if (rootState.sessions.currentSession) {
|
|
||||||
dispatch('deleteSession', rootState.sessions.currentSession.token).catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
void dispatch('clearCurrent', false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
void dispatch('clearCurrent', false);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Delete a given session
|
|
||||||
*/
|
|
||||||
deleteSession({ commit, dispatch, rootState }, token: string) {
|
|
||||||
commit('setLoading', true);
|
|
||||||
api
|
|
||||||
.delete(`/auth/${token}`)
|
|
||||||
.then(() => {
|
|
||||||
if (token === rootState.sessions.currentSession?.token) {
|
|
||||||
void dispatch('clearCurrent', false);
|
|
||||||
} else {
|
|
||||||
dispatch('getSessions').catch((error) => {
|
|
||||||
throw error;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error: AxiosError) => {
|
|
||||||
if (!error.response || error.response.status != 401) throw error;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Clear current session and logged in user
|
|
||||||
*/
|
|
||||||
clearCurrent({ commit }, redirect = true) {
|
|
||||||
void Router.push({
|
|
||||||
name: 'login',
|
|
||||||
query: redirect ? { redirect: Router.currentRoute.value.fullPath } : {},
|
|
||||||
params: { logout: 'true' },
|
|
||||||
}).then(() => {
|
|
||||||
commit('clearCurrentSession');
|
|
||||||
commit('users/clearCurrentUser', null, { root: true });
|
|
||||||
// ensure also volatile store gets cleared by refreshing the site
|
|
||||||
Router.go(0);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
/**
|
|
||||||
* Get all sessions from current User
|
|
||||||
*/
|
|
||||||
getSessions({ commit, state }) {
|
|
||||||
commit('setLoading', true);
|
|
||||||
api
|
|
||||||
.get('/auth')
|
|
||||||
.then((response: AxiosResponse<FG.Session[]>) => {
|
|
||||||
response.data.forEach((session) => {
|
|
||||||
session.expires = new Date(session.expires);
|
|
||||||
});
|
|
||||||
commit('setSessions', response.data);
|
|
||||||
const currentSession = response.data.find((session: FG.Session) => {
|
|
||||||
return session.token === state.currentSession?.token;
|
|
||||||
});
|
|
||||||
if (currentSession) {
|
|
||||||
commit('setCurrentSession', currentSession);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
throw error;
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
updateSession({ commit, state }, data: { lifetime: number; token: string }) {
|
|
||||||
commit('setLoading', true);
|
|
||||||
api
|
|
||||||
.put(`auth/${data.token}`, { value: data.lifetime })
|
|
||||||
.then((response: AxiosResponse<FG.Session>) => {
|
|
||||||
response.data.expires = new Date(response.data.expires);
|
|
||||||
if (state.currentSession?.token == response.data.token) {
|
|
||||||
commit('setCurrentSession', response.data);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((err) => console.log(err))
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
console.log('updateSession', data);
|
|
||||||
},
|
|
||||||
requestPasswordReset({}, data) {
|
|
||||||
return api.post('/auth/reset', data);
|
|
||||||
},
|
|
||||||
resetPassword({}, data) {
|
|
||||||
return api.post('/auth/reset', data).catch((error: AxiosError) => {
|
|
||||||
return Promise.reject(error.response);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const getters: GetterTree<SessionStateInterface, UserSessionState> = {
|
|
||||||
currentSession(state) {
|
|
||||||
return state.currentSession;
|
|
||||||
},
|
|
||||||
sessions(state) {
|
|
||||||
return state.sessions;
|
|
||||||
},
|
|
||||||
loading(state) {
|
|
||||||
return state.loading;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const sessionsStore: Module<SessionStateInterface, UserSessionState> = {
|
|
||||||
namespaced: true,
|
|
||||||
state,
|
|
||||||
mutations,
|
|
||||||
actions,
|
|
||||||
getters,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default sessionsStore;
|
|
|
@ -1,275 +0,0 @@
|
||||||
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
||||||
import { Module, MutationTree, ActionTree, GetterTree } from 'vuex';
|
|
||||||
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;
|
|
||||||
currentPermissions: FG.Permission[];
|
|
||||||
users: FG.User[];
|
|
||||||
roles: FG.Role[];
|
|
||||||
permissions: FG.Permission[];
|
|
||||||
loading: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
function loadUserFromLocalStorage() {
|
|
||||||
const user = SessionStorage.getItem<FG.User>('currentUser') || undefined;
|
|
||||||
if (user && user.birthday && typeof user.birthday === 'string')
|
|
||||||
user.birthday = new Date(user.birthday);
|
|
||||||
return user;
|
|
||||||
}
|
|
||||||
const state: UserStateInterface = {
|
|
||||||
users: [],
|
|
||||||
roles: [],
|
|
||||||
permissions: [],
|
|
||||||
currentUser: loadUserFromLocalStorage(),
|
|
||||||
currentPermissions: SessionStorage.getItem<FG.Permission[]>('currentPermissions') || [],
|
|
||||||
loading: 0,
|
|
||||||
};
|
|
||||||
|
|
||||||
const mutations: MutationTree<UserStateInterface> = {
|
|
||||||
setCurrentUser(state, data: FG.User) {
|
|
||||||
if (typeof data.birthday === 'string') data.birthday = new Date(data.birthday);
|
|
||||||
SessionStorage.set('currentUser', data);
|
|
||||||
state.currentUser = data;
|
|
||||||
},
|
|
||||||
setCurrentPermissions(state, data: FG.Permission[]) {
|
|
||||||
SessionStorage.set('currentPermissions', data);
|
|
||||||
state.currentPermissions = data;
|
|
||||||
},
|
|
||||||
clearCurrentUser(state) {
|
|
||||||
SessionStorage.remove('currentUser');
|
|
||||||
SessionStorage.remove('currentPermissions');
|
|
||||||
state.currentUser = undefined;
|
|
||||||
state.currentPermissions = [];
|
|
||||||
},
|
|
||||||
setUsers(state, data: FG.User[]) {
|
|
||||||
state.users = data;
|
|
||||||
},
|
|
||||||
setUser(state, data: FG.User) {
|
|
||||||
const index = state.users.findIndex((x) => x.userid === data.userid);
|
|
||||||
if (index > -1) state.users[index] = data;
|
|
||||||
else state.users.push(data);
|
|
||||||
},
|
|
||||||
setRoles(state, data: FG.Role[]) {
|
|
||||||
state.roles = data;
|
|
||||||
},
|
|
||||||
addRole(state, data: FG.Role) {
|
|
||||||
state.roles.push(data);
|
|
||||||
},
|
|
||||||
updateRole(state, data: FG.Role) {
|
|
||||||
const idx = state.roles.findIndex((role) => role.id === data.id);
|
|
||||||
if (idx >= 0) {
|
|
||||||
state.roles[idx].name = data.name;
|
|
||||||
state.roles[idx].permissions = data.permissions;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
setPermissions(state, data: FG.Permission[]) {
|
|
||||||
state.permissions = data;
|
|
||||||
},
|
|
||||||
setLoading(state, data = true) {
|
|
||||||
if (data) state.loading += 1;
|
|
||||||
else state.loading -= 1;
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const actions: ActionTree<UserStateInterface, UserSessionState> = {
|
|
||||||
getCurrentUser({ commit, rootState }) {
|
|
||||||
if (rootState.sessions.currentSession) {
|
|
||||||
commit('setLoading');
|
|
||||||
api
|
|
||||||
.get(`/users/${rootState.sessions.currentSession.userid}`)
|
|
||||||
.then((response: AxiosResponse<CurrentUserResponse>) => {
|
|
||||||
commit('setCurrentUser', response.data);
|
|
||||||
commit('setCurrentPermissions', response.data.permissions);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.warn(err);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
console.debug('User not logged in, can not get current_user.');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getUsers({ commit }) {
|
|
||||||
commit('setLoading');
|
|
||||||
api
|
|
||||||
.get('/users')
|
|
||||||
.then((response: AxiosResponse<FG.User[]>) => {
|
|
||||||
response.data.forEach((user) => {
|
|
||||||
if (user.birthday) user.birthday = new Date(user.birthday);
|
|
||||||
});
|
|
||||||
commit('setUsers', response.data);
|
|
||||||
})
|
|
||||||
.catch((err) => {
|
|
||||||
console.warn(err);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateUser({ commit, state, dispatch }, data: FG.User) {
|
|
||||||
commit('setLoading');
|
|
||||||
api
|
|
||||||
.put(`/users/${data.userid}`, data)
|
|
||||||
.then(() => {
|
|
||||||
if (state.currentUser && state.currentUser.userid === data.userid)
|
|
||||||
void dispatch('getCurrentUser');
|
|
||||||
else void dispatch('getUsers');
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.log(error);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
uploadAvatar({ commit }, payload: { user: FG.User; file: string }) {
|
|
||||||
commit('setLoading');
|
|
||||||
const formData = new FormData();
|
|
||||||
formData.append('file', payload.file);
|
|
||||||
return api
|
|
||||||
.post(`/users/${payload.user.userid}/avatar`, formData, {
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'multipart/form-data',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
.catch((error: AxiosError) => {
|
|
||||||
return Promise.reject(error.response);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
setUser({ commit, state, dispatch }, data: FG.User) {
|
|
||||||
commit('setLoading');
|
|
||||||
api
|
|
||||||
.post('users', data)
|
|
||||||
.then(() => {
|
|
||||||
if (state.currentUser && state.currentUser.userid === data.userid)
|
|
||||||
void dispatch('getCurrentUser');
|
|
||||||
else void dispatch('getUsers');
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
console.warn(error);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getRoles({ commit, state }, force = false) {
|
|
||||||
if (!force && state.roles.length > 0) return;
|
|
||||||
commit('setLoading');
|
|
||||||
api
|
|
||||||
.get('/roles')
|
|
||||||
.then((response: AxiosResponse<FG.Role[]>) => {
|
|
||||||
commit('setRoles', response.data);
|
|
||||||
})
|
|
||||||
.finally(() => commit('setLoading', false));
|
|
||||||
},
|
|
||||||
|
|
||||||
updateRole({ commit }, data: FG.Role) {
|
|
||||||
commit('setLoading');
|
|
||||||
api
|
|
||||||
.put(`/roles/${data.id}`, data)
|
|
||||||
.then(() => {
|
|
||||||
commit('updateRole', data);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
newRole({ commit }, data: FG.Role) {
|
|
||||||
commit('setLoading');
|
|
||||||
return api
|
|
||||||
.post('/roles', data)
|
|
||||||
.then((response: AxiosResponse<FG.Role>) => {
|
|
||||||
commit('addRole', response.data);
|
|
||||||
return Promise.resolve(response.data);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
deleteRole({ commit, state }, data: FG.Role) {
|
|
||||||
commit('setLoading');
|
|
||||||
api
|
|
||||||
.delete(`/roles/${data.id}`)
|
|
||||||
.then(() => {
|
|
||||||
commit(
|
|
||||||
'setRoles',
|
|
||||||
state.roles.filter((value) => value.id !== data.id)
|
|
||||||
);
|
|
||||||
})
|
|
||||||
.finally(() => {
|
|
||||||
commit('setLoading', false);
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getPermissions({ commit, state }, force = false) {
|
|
||||||
if (!force && state.permissions.length > 0) return;
|
|
||||||
commit('setLoading');
|
|
||||||
api
|
|
||||||
.get('/roles/permissions')
|
|
||||||
.then((response: AxiosResponse<FG.Role[]>) => {
|
|
||||||
commit('setPermissions', response.data);
|
|
||||||
})
|
|
||||||
.finally(() => commit('setLoading', false));
|
|
||||||
},
|
|
||||||
getUser({ commit, getters }, data: { userid: string; force?: boolean }) {
|
|
||||||
// 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 api.get(`/users/${data.userid}`).then((response: AxiosResponse<FG.User>) => {
|
|
||||||
commit('setUser', response.data);
|
|
||||||
return response.data;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return Promise.resolve(user);
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
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;
|
|
||||||
},
|
|
||||||
currentUser({ currentUser }) {
|
|
||||||
return currentUser;
|
|
||||||
},
|
|
||||||
users({ users }) {
|
|
||||||
return users;
|
|
||||||
},
|
|
||||||
loading({ loading }) {
|
|
||||||
return loading > 0;
|
|
||||||
},
|
|
||||||
displayName({ currentUser }) {
|
|
||||||
return currentUser?.display_name;
|
|
||||||
},
|
|
||||||
roles({ roles }): string[] {
|
|
||||||
return roles.map((role) => role.name).flat();
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const usersStore: Module<UserStateInterface, UserSessionState> = {
|
|
||||||
namespaced: true,
|
|
||||||
actions,
|
|
||||||
getters,
|
|
||||||
mutations,
|
|
||||||
state,
|
|
||||||
};
|
|
||||||
|
|
||||||
export default usersStore;
|
|
|
@ -1,11 +1,7 @@
|
||||||
import { store } from 'quasar/wrappers';
|
import { store } from 'quasar/wrappers';
|
||||||
import { createStore } from 'vuex';
|
import { createStore } from 'vuex';
|
||||||
import { UserStateInterface } from 'src/plugins/user/store/user';
|
|
||||||
import { SessionStateInterface } from 'src/plugins/user/store/session';
|
|
||||||
|
|
||||||
export interface StateInterface {
|
export interface StateInterface {
|
||||||
users: UserStateInterface;
|
|
||||||
sessions: SessionStateInterface;
|
|
||||||
[key: string]: unknown;
|
[key: string]: unknown;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,3 +16,95 @@ export default store(function (/* { ssrContext } */) {
|
||||||
|
|
||||||
return Store;
|
return Store;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
import { defineStore } from 'pinia';
|
||||||
|
import { api } from 'src/boot/axios';
|
||||||
|
import { AxiosResponse } from 'axios';
|
||||||
|
import { LocalStorage } from 'quasar';
|
||||||
|
import { useUserStore, useSessionStore } from 'src/plugins/user/store';
|
||||||
|
|
||||||
|
function loadCurrentSession() {
|
||||||
|
const session = LocalStorage.getItem<FG.Session>('session');
|
||||||
|
if (session) session.expires = new Date(session.expires);
|
||||||
|
return session || undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useMainStore = defineStore({
|
||||||
|
id: 'main',
|
||||||
|
|
||||||
|
state: () => ({
|
||||||
|
session: loadCurrentSession(),
|
||||||
|
user: undefined as FG.User | undefined,
|
||||||
|
}),
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
loggedIn() {
|
||||||
|
return this.session !== undefined;
|
||||||
|
},
|
||||||
|
currentUser() {
|
||||||
|
if (this.user === undefined) throw 'Not logged in, this should not be called';
|
||||||
|
return this.user;
|
||||||
|
},
|
||||||
|
permissions() {
|
||||||
|
return this.user?.permissions || [];
|
||||||
|
},
|
||||||
|
},
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
/** Ininitalize store from saved session
|
||||||
|
* Updates session and loads current user
|
||||||
|
*/
|
||||||
|
async init() {
|
||||||
|
if (this.session) {
|
||||||
|
const sessionStore = useSessionStore();
|
||||||
|
const session = await sessionStore.getSession(this.session.token);
|
||||||
|
if (session) {
|
||||||
|
this.session = this.session;
|
||||||
|
const userStore = useUserStore();
|
||||||
|
const user = await userStore.getUser(this.session.userid);
|
||||||
|
if (user) this.user = user;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
async login(userid: string, password: string) {
|
||||||
|
try {
|
||||||
|
const { data } = await api.post<FG.Session>('/auth', { userid, password });
|
||||||
|
this.session = data;
|
||||||
|
this.session.expires = new Date(this.session.expires);
|
||||||
|
LocalStorage.set('session', this.session);
|
||||||
|
return true;
|
||||||
|
} catch ({ response }) {
|
||||||
|
return (<AxiosResponse | undefined>response)?.status || false;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
logout() {
|
||||||
|
if (!this.session) return false;
|
||||||
|
|
||||||
|
void api.delete(`/auth/${this.session.token}`);
|
||||||
|
this.$patch({
|
||||||
|
session: undefined,
|
||||||
|
user: undefined,
|
||||||
|
});
|
||||||
|
LocalStorage.remove('session');
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
|
||||||
|
async requestReset(userid: string) {
|
||||||
|
return await api
|
||||||
|
.post('/auth/reset', { userid })
|
||||||
|
.then(() => true)
|
||||||
|
.catch(() => false);
|
||||||
|
},
|
||||||
|
|
||||||
|
async resetPassword(token: string, password: string) {
|
||||||
|
return await api
|
||||||
|
.post('/auth/reset', { token, password })
|
||||||
|
.then(() => true)
|
||||||
|
.catch(({ response }) =>
|
||||||
|
response && 'status' in response ? (<AxiosResponse>response).status : false
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
|
@ -1,19 +1,16 @@
|
||||||
import { useStore } from 'vuex';
|
import { useMainStore } from 'src/store';
|
||||||
import { UserSessionState } from 'src/plugins/user/store';
|
|
||||||
|
|
||||||
export function hasPermission(permission: string) {
|
export function hasPermission(permission: string) {
|
||||||
const store = useStore<UserSessionState>();
|
const store = useMainStore();
|
||||||
return store.state.users.currentPermissions.includes(permission);
|
return store.permissions.includes(permission);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasPermissions(needed: string[]) {
|
export function hasPermissions(needed: string[]) {
|
||||||
const store = useStore<UserSessionState>();
|
const store = useMainStore();
|
||||||
const permissions = store.state.users.currentPermissions;
|
return needed.every((value) => store.permissions.includes(value));
|
||||||
return needed.every((value) => permissions.includes(value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hasSomePermissions(needed: string[]) {
|
export function hasSomePermissions(needed: string[]) {
|
||||||
const store = useStore<UserSessionState>();
|
const store = useMainStore();
|
||||||
const permissions = store.state.users.currentPermissions;
|
return needed.some((value) => store.permissions.includes(value));
|
||||||
return needed.some((value) => permissions.includes(value));
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7497,6 +7497,11 @@ pify@^5.0.0:
|
||||||
resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f"
|
resolved "https://registry.yarnpkg.com/pify/-/pify-5.0.0.tgz#1f5eca3f5e87ebec28cc6d54a0e4aaf00acc127f"
|
||||||
integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==
|
integrity sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==
|
||||||
|
|
||||||
|
pinia@^2.0.0-alpha.7:
|
||||||
|
version "2.0.0-alpha.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.0.0-alpha.7.tgz#3b55af0185a45e6e1a75ba90f6da3e1a61623767"
|
||||||
|
integrity sha512-IXPG+4elwC+0h5r3FZUhcH+XwywMN+f9uxoMxzapz5ywBfo7BeE48wpC1o+81qnil5m3FhBOjMKZeS7t0t6ljw==
|
||||||
|
|
||||||
pinkie-promise@^2.0.0:
|
pinkie-promise@^2.0.0:
|
||||||
version "2.0.1"
|
version "2.0.1"
|
||||||
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
resolved "https://registry.yarnpkg.com/pinkie-promise/-/pinkie-promise-2.0.1.tgz#2135d6dfa7a358c069ac9b178776288228450ffa"
|
||||||
|
|
Loading…
Reference in New Issue