diff options
author | vnugent <public@vaughnnugent.com> | 2024-01-20 23:49:29 -0500 |
---|---|---|
committer | vnugent <public@vaughnnugent.com> | 2024-01-20 23:49:29 -0500 |
commit | 6cb7da37824d02a1898d08d0f9495c77fde4dd1d (patch) | |
tree | 95e37ea3c20f416d6a205ee4ab050c307b18eafe /front-end/src/store |
inital commit
Diffstat (limited to 'front-end/src/store')
-rw-r--r-- | front-end/src/store/bookmarks.ts | 249 | ||||
-rw-r--r-- | front-end/src/store/index.ts | 94 | ||||
-rw-r--r-- | front-end/src/store/mfaSettingsPlugin.ts | 80 | ||||
-rw-r--r-- | front-end/src/store/oauthAppsPlugin.ts | 174 | ||||
-rw-r--r-- | front-end/src/store/socialMfaPlugin.ts | 85 | ||||
-rw-r--r-- | front-end/src/store/userProfile.ts | 97 |
6 files changed, 779 insertions, 0 deletions
diff --git a/front-end/src/store/bookmarks.ts b/front-end/src/store/bookmarks.ts new file mode 100644 index 0000000..a49937b --- /dev/null +++ b/front-end/src/store/bookmarks.ts @@ -0,0 +1,249 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'pinia' +import { MaybeRef, shallowRef, watch, computed, Ref, ref } from 'vue'; +import { apiCall, useAxios, WebMessage } from '@vnuge/vnlib.browser'; +import { useToggle, get, set, useOffsetPagination, watchDebounced, syncRef } from '@vueuse/core'; +import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia' +import { isArray, join, map, split, sortBy } from 'lodash-es'; +import { useQuery } from './index'; + +export interface Bookmark{ + readonly Id: string + Name: string + Url: string + Tags: string[] + Description: string + Created: string + LastModified: string +} + +export interface BatchUploadResult{ + readonly invalid: BookmarkError[] +} + +export interface BookmarkError{ + readonly subject: Bookmark + readonly errors: Array<{ + readonly message: string + readonly property: string + }> +} + +export interface BookmarkApi{ + list: (page: number, limit: number, search: BookmarkSearch) => Promise<Bookmark[]> + add: (bookmark: Bookmark) => Promise<void> + addMany: (bookmarks: Bookmark[], failOnValidationError: boolean) => Promise<BatchUploadResult | string> + set: (bookmark: Bookmark) => Promise<void> + getTags: () => Promise<string[]> + delete: (bookmark: Bookmark | Bookmark[]) => Promise<void> + count: () => Promise<number> +} + +export interface BookmarkSearch{ + query: string | undefined | null + tags: string[] +} + +declare module 'pinia' { + export interface PiniaCustomProperties { + bookmarks:{ + api: BookmarkApi + query: string + tags: string[] + allTags: string[] + list: Bookmark[] + pages: ReturnType<typeof useOffsetPagination> + refresh: () => void + } + } +} + +const useBookmarkApi = (endpoint: MaybeRef<string>): BookmarkApi => { + + const axios = useAxios(null) + + const listBookmarks = async (page: number, limit: number, search: BookmarkSearch) => { + const query = get(search.query) + const tagQuery = join(get(search.tags), ' ') + + const params = new URLSearchParams() + params.append('page', (page - 1).toString()) + params.append('limit', limit.toString()) + params.append('t', tagQuery) + + //Add query if defined + if(query){ + params.append('q', query) + } + + const { data } = await axios.get<Bookmark[]>(`${get(endpoint)}?${params.toString()}`) + return data; + } + + const addBookmark = async (bookmark: Bookmark) => { + const { data } = await axios.post<WebMessage>(`${get(endpoint)}`, bookmark) + data.getResultOrThrow(); + } + + const setBookmark = async (bookmark: Bookmark) => { + const { data } = await axios.patch<WebMessage<Bookmark>>(`${get(endpoint)}`, bookmark) + data.getResultOrThrow(); + } + + const deleteBookmark = async (bookmark: Bookmark | Bookmark[]) => { + if(isArray(bookmark)){ + //Delete multiple bookmarks with comma separated ids + const bookmarIds = join(map(bookmark, b => b.Id), ',') + const { data } = await axios.delete<WebMessage<Bookmark>>(`${get(endpoint)}?ids=${bookmarIds}`) + data.getResultOrThrow(); + } + else { + //Delete a single bookmark + const { data } = await axios.delete<WebMessage<Bookmark>>(`${get(endpoint)}?id=${bookmark.Id}`) + data.getResultOrThrow(); + } + } + + const getItemsCount = async () => { + const { data } = await axios.get<WebMessage<number>>(`${get(endpoint)}?count=true`) + return data.getResultOrThrow(); + } + + const getTags = async () => { + const { data } = await axios.get<string[]>(`${get(endpoint)}?getTags=true`) + return sortBy(data); + } + + const addMany = async (bookmarks: Bookmark[], failOnValidationError: boolean): Promise<BatchUploadResult | string> => { + let params = '' + + if(failOnValidationError){ + params = '?failOnInvalid=true' + } + + //Exec request, ignore a validation error on a 20x response + const { data } = await axios.put<WebMessage<BatchUploadResult>>(`${get(endpoint)}${params}`, bookmarks); + return data.result; + } + + return { + list: listBookmarks, + add: addBookmark, + set: setBookmark, + delete: deleteBookmark, + count: getItemsCount, + addMany, + getTags + } +} + +const urlPagiation = (p: Ref<number>, l: Ref<number>) => { + const page = useQuery('page') + const limit = useQuery('limit') + + const currentPage = computed({ + get: () => page.value ? parseInt(page.value) : 1, + set: (value) => set(page, value.toString()) + }) + + const currentPageSize = computed({ + get: () => limit.value ? parseInt(limit.value) : 20, + set: (value) => set(limit, value.toString()) + }) + + //Sync current page and limit with the provided refs + syncRef(currentPage, p, { immediate: true }) + syncRef(currentPageSize, l, { immediate: true }) +} + +const searchQuery = (search: Ref<string | null>, tags: Ref<string[]>) => { + + const query = useQuery('q') + const tagQuery = useQuery('t') + + const currentTags = computed({ + get: () => split(tagQuery.value, ' '), + set: (value) => set(tagQuery, join(value, ' ')) + }) + + //Sync current page and limit with the provided refs + syncRef(query, search, { immediate: true }) + syncRef(currentTags, tags, { immediate: true }) +} + +export const bookmarkPlugin = (bookmarkEndpoint: MaybeRef<string>): PiniaPlugin => { + + return ({ store }: PiniaPluginContext) => { + + const { loggedIn } = storeToRefs(store) + const [onRefresh, refresh] = useToggle() + + const totalBookmarks = shallowRef(0) + const bookmarks = shallowRef<Bookmark[]>() + const allTags = shallowRef<string[]>([]) + + const pages = useOffsetPagination({ page: 1, pageSize: 20 }) + const { currentPage, currentPageSize } = pages; + //Sync url query params with the pagination + urlPagiation(currentPage, currentPageSize) + + //sync search query and tags + const query = ref<string | null>(null) + const tags = ref<string[]>([]) + searchQuery(query, tags) + + //Init api + const bookmarkApi = useBookmarkApi(bookmarkEndpoint) + + watch([loggedIn, onRefresh], ([ li ]) => { + if(!li){ + //Clear the bookmarks + set(totalBookmarks, 0) + set(bookmarks, []) + return + } + + //Update the total bookmarks + apiCall(async () => totalBookmarks.value = await bookmarkApi.count()) + apiCall(async () => allTags.value = await bookmarkApi.getTags()) + }) + + //Watch for serach query changes + watchDebounced([currentPage, currentPageSize, totalBookmarks, tags, query, allTags], + ([ page, pageSize, _, tags, query ]) => { + apiCall(async () => bookmarks.value = await bookmarkApi.list(page, pageSize, { tags, query })) + }, { debounce: 10 }) + + //Watch for page changes and scroll to top + watch([currentPage], () => window.scrollTo({ top: 0, behavior: 'smooth' })) + + //reset current page when tags change or query changes + watch([tags, query], () => set(currentPage, 1)) + + return { + bookmarks:{ + list: bookmarks, + pages, + refresh, + query, + tags, + allTags, + api: bookmarkApi + } + } + } +}
\ No newline at end of file diff --git a/front-end/src/store/index.ts b/front-end/src/store/index.ts new file mode 100644 index 0000000..c13edcb --- /dev/null +++ b/front-end/src/store/index.ts @@ -0,0 +1,94 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import { useSession, useAutoHeartbeat } from "@vnuge/vnlib.browser"; +import { toRefs, set, watchDebounced, useLocalStorage } from "@vueuse/core"; +import { noop, toSafeInteger, toString, defaults } from "lodash-es"; +import { defineStore } from "pinia"; +import { computed, shallowRef, watch } from "vue"; + +export const useQuery = (name: string) => { + + const getQuery = () => { + const url = new URL(window.location.href); + return url.searchParams.get(name) as string | null | undefined; + } + + const setQuery = (value: string | null | undefined) => { + const url = new URL(window.location.href); + + if(value){ + url.searchParams.set(name, value); + + } + else{ + url.searchParams.delete(name); + } + + window.history.replaceState({}, '', url.toString()); + } + + //configure reactive state + const value = shallowRef(getQuery()); + watchDebounced(value, (value) => setQuery(value), { debounce: 20 }); + + return value; +} + +export enum TabId{ + Bookmarks, + Profile, + Settings, + Login +} + +/** + * Loads the main store for the application + */ +export const useStore = defineStore('main', () => { + + const { loggedIn, isLocalAccount } = useSession(); + + //MANAGED STATE + const siteTitle = shallowRef(""); + const setSiteTitle = (title: string) => set(siteTitle, title); + + //Get shared global state storage + const mainState = useLocalStorage("vn-state", { autoHeartbeat: false }); + //Set defaults to avoid undefined errors from toRefs + defaults(mainState.value, { autoHeartbeat: false }) + const { autoHeartbeat } = toRefs(mainState) + + const tab = useQuery('tab'); + const activeTab = computed<TabId>({ + get: () => tab.value ? toSafeInteger(tab.value) : TabId.Bookmarks, + set: (value) => tab.value = toString(value) + }) + + //If not logged in, redirect to login tab + watch(loggedIn, (loggedIn) => loggedIn ? noop() : set(activeTab, TabId.Login), { immediate: true }) + + //Setup heartbeat for 5 minutes + useAutoHeartbeat(shallowRef(5 * 60 * 1000), autoHeartbeat) + + return{ + autoHeartbeat, + loggedIn, + isLocalAccount, + siteTitle, + setSiteTitle, + activeTab + } +}) diff --git a/front-end/src/store/mfaSettingsPlugin.ts b/front-end/src/store/mfaSettingsPlugin.ts new file mode 100644 index 0000000..77a145b --- /dev/null +++ b/front-end/src/store/mfaSettingsPlugin.ts @@ -0,0 +1,80 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'pinia' +import { MaybeRef, shallowRef, watch } from 'vue'; +import { MfaMethod, PkiPublicKey, apiCall, useMfaConfig, usePkiConfig, usePkiAuth } from '@vnuge/vnlib.browser'; +import { useToggle, get } from '@vueuse/core'; +import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia' +import { defer, includes } from 'lodash-es'; + +declare module 'pinia' { + export interface PiniaCustomProperties { + mfaEndabledMethods: MfaMethod[] + mfaConfig: ReturnType<typeof useMfaConfig> + pkiConfig: ReturnType<typeof usePkiConfig> + pkiAuth: ReturnType<typeof usePkiAuth> + pkiPublicKeys: PkiPublicKey[] + mfaRefreshMethods: () => void + } +} + +export const mfaSettingsPlugin = (mfaEndpoint: MaybeRef<string>, pkiEndpoint?:MaybeRef<string>): PiniaPlugin => { + + return ({ store }: PiniaPluginContext) => { + + const { loggedIn } = storeToRefs(store) + const mfaConfig = useMfaConfig(mfaEndpoint) + const pkiConfig = usePkiConfig(pkiEndpoint || '/') + const pkiAuth = usePkiAuth(pkiEndpoint || '/') + const [onRefresh, mfaRefreshMethods] = useToggle() + + const mfaEndabledMethods = shallowRef<MfaMethod[]>([]) + const pkiPublicKeys = shallowRef<PkiPublicKey[]>([]) + + watch([loggedIn, onRefresh], ([ li ]) => { + if(!li){ + mfaEndabledMethods.value = [] + return + } + + //load the mfa methods if the user is logged in + apiCall(async () => mfaEndabledMethods.value = await mfaConfig.getMethods()) + }) + + //Watch for changes to mfa methods (refresh) and update the pki keys + watch([mfaEndabledMethods], ([ methods ]) => { + if(!includes(methods, 'pki' as MfaMethod) || !get(pkiEndpoint)){ + pkiPublicKeys.value = [] + return + } + + //only load the pki keys if pki is enabled on the server + apiCall(async () => pkiPublicKeys.value = await pkiConfig.getAllKeys()) + }) + + //Initial load when page loads + defer(mfaRefreshMethods) + + return{ + mfaRefreshMethods, + mfaEndabledMethods, + mfaConfig, + pkiConfig, + pkiAuth, + pkiPublicKeys + } + } +}
\ No newline at end of file diff --git a/front-end/src/store/oauthAppsPlugin.ts b/front-end/src/store/oauthAppsPlugin.ts new file mode 100644 index 0000000..d12eed2 --- /dev/null +++ b/front-end/src/store/oauthAppsPlugin.ts @@ -0,0 +1,174 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'pinia' +import { MaybeRef, computed, shallowRef, watch } from 'vue'; +import { apiCall, useAxios } from '@vnuge/vnlib.browser'; +import { get, set, useToggle } from '@vueuse/core'; +import { PiniaPlugin, PiniaPluginContext, storeToRefs } from 'pinia' +import { map, sortBy, isArray, defer } from 'lodash-es'; + +export interface OAuth2Application { + readonly Id: string, + readonly name: string, + readonly description: string, + readonly permissions: string[], + readonly client_id: string, + Created: Date, + readonly LastModified: Date, +} + +export interface NewAppResponse { + readonly secret: string + readonly app: OAuth2Application +} + +export interface OAuth2Api { + create(app: OAuth2Application): Promise<NewAppResponse> + updateSecret(app: OAuth2Application, password: string): Promise<string> + updateMeta(app: OAuth2Application): Promise<void> + deleteApp(app: OAuth2Application, password: string): Promise<void> +} + +declare module 'pinia' { + export interface PiniaCustomProperties { + oauth2:{ + apps: OAuth2Application[], + scopes: string[], + refresh(): void + } & OAuth2Api + } +} + +export const oauth2AppsPlugin = (o2EndpointUrl: MaybeRef<string>, scopeEndpoint: MaybeRef<string>): PiniaPlugin => { + + return ({ store }: PiniaPluginContext) => { + + const axios = useAxios(null); + const { loggedIn } = storeToRefs(store) + + const [onRefresh, refresh] = useToggle() + + const _oauth2Apps = shallowRef<OAuth2Application[]>([]) + const scopes = shallowRef<string[]>([]) + + /** + * Updates an Oauth2 application's metadata + */ + const updateMeta = async (app: OAuth2Application): Promise<void> => { + //Update the app metadata + await axios.put(get(o2EndpointUrl), app) + } + + /** + * Gets all of the user's oauth2 applications from the server + * @returns The user's oauth2 applications + */ + const getApps = async () => { + // Get all apps + const { data } = await axios.get<OAuth2Application[]>(get(o2EndpointUrl)); + + if (!isArray(data)) { + throw new Error("Invalid response from server") + } + + return map(data, (appData) => { + //Store the created time as a date object + appData.Created = new Date(appData?.Created ?? 0) + //create a new state manager for the user's profile + return appData; + }) + } + + /** + * Creates a new application from the given data + * @param param0 The application server buffer + * @returns The newly created application + */ + const create = async ({ name, description, permissions }: OAuth2Application): Promise<NewAppResponse> => { + + // make the post request, response is the new app data with a secret + const { data } = await axios.post<OAuth2Application & { raw_secret: string }>(`${get(o2EndpointUrl)}?action=create`, { name, description, permissions }) + + // Store secret + const secret = data.raw_secret + + // remove secre tfrom the response + delete (data as any).raw_secret + + return { secret, app: data } + } + + /** + * Requets a new secret for an application from the server + * @param app The app to request a new secret for + * @param password The user's password + * @returns The new secret + */ + const updateSecret = async (app: OAuth2Application, password: string): Promise<string> => { + const { data } = await axios.post(`${o2EndpointUrl}?action=secret`, { Id: app.Id, password }) + return data.raw_secret + } + + /** + * Deletes an application from the server + * @param app The application to delete + * @param password The user's password + * @returns The response from the server + */ + const deleteApp = async (app: OAuth2Application, password: string): Promise<void> => { + await axios.post(`${o2EndpointUrl}?action=delete`, { password, Id: app.Id }); + } + + const apps = computed(() => sortBy(_oauth2Apps.value, a => a.Created)) + + watch([loggedIn, onRefresh], async ([li]) => { + if (!li) { + set(_oauth2Apps, []) + return; + } + + //Load the user's oauth2 apps after scopes load. to make sure the plugin is enabled + apiCall(async () => { + try { + //Load the oauth2 scopes + const { data } = await axios.get<string[]>(get(scopeEndpoint)) + set(scopes, data) + + _oauth2Apps.value = await getApps(); + } + catch (e: any) { + if (e.response?.status === 404) { + console.warn("OAuth2 plugin is not configured"); + } + } + }) + }) + + defer(refresh) + + return { + oauth2:{ + scopes, + apps, + deleteApp, + updateSecret, + updateMeta, + create, + refresh + } + } + } +}
\ No newline at end of file diff --git a/front-end/src/store/socialMfaPlugin.ts b/front-end/src/store/socialMfaPlugin.ts new file mode 100644 index 0000000..d8d7bb1 --- /dev/null +++ b/front-end/src/store/socialMfaPlugin.ts @@ -0,0 +1,85 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'pinia' +import { MaybeRef } from 'vue'; +import { useSocialOauthLogin, useUser, SocialOAuthPortal, fromPortals, useAxios } from '@vnuge/vnlib.browser' +import { get } from '@vueuse/core'; +import { PiniaPluginContext, PiniaPlugin, storeToRefs } from 'pinia' +import { defer } from 'lodash-es'; + +type SocialMfaPlugin = ReturnType<typeof useSocialOauthLogin> + +declare module 'pinia' { + export interface PiniaCustomProperties { + socialOauth(): Promise<SocialMfaPlugin> + } +} + +export const socialMfaPlugin = (portalEndpoint?: MaybeRef<string>): PiniaPlugin => { + + return ({ store }: PiniaPluginContext) => { + + const { } = storeToRefs(store) + const { logout } = useUser() + + /** + * Override the logout function to default to a social logout, + * if the social logout fails, then we will logout the user + */ + const setLogoutMethod = (socialOauth: SocialMfaPlugin) => { + const logoutFunc = socialOauth.logout; + + (socialOauth as any).logout = async () => { + if (await logoutFunc() === false) { + await logout() + } + } + } + + const _loadPromise = new Promise<SocialMfaPlugin>((resolve, reject) => { + + if(get(portalEndpoint) == null) { + const socialOauth = useSocialOauthLogin([]) + setLogoutMethod(socialOauth) + return resolve(socialOauth) + } + + defer(async () => { + try { + //Get axios instance + const axios = useAxios(null) + + //Get all enabled portals + const { data } = await axios.get<SocialOAuthPortal[]>(get(portalEndpoint)!); + //Setup social providers from server portals + const socialOauth = useSocialOauthLogin(fromPortals(data)); + setLogoutMethod(socialOauth); + + resolve(socialOauth) + + } catch (error) { + reject(error) + } + }) + }) + + const socialOauth = () => _loadPromise + + return { + socialOauth, + } + } +}
\ No newline at end of file diff --git a/front-end/src/store/userProfile.ts b/front-end/src/store/userProfile.ts new file mode 100644 index 0000000..ed796d9 --- /dev/null +++ b/front-end/src/store/userProfile.ts @@ -0,0 +1,97 @@ +// Copyright (C) 2024 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import 'pinia' +import { MaybeRef, watch } from 'vue'; +import { ServerDataBuffer, ServerObjectBuffer, UserProfile, WebMessage, apiCall, useAxios, useDataBuffer, useUser } from '@vnuge/vnlib.browser'; +import { get, useToggle } from '@vueuse/core'; +import { PiniaPlugin, PiniaPluginContext, storeToRefs } from 'pinia' +import { defer } from 'lodash-es'; + +export interface OAuth2Application { + readonly Id: string, + readonly name: string, + readonly description: string, + readonly permissions: string[], + readonly client_id: string, + readonly Created: Date, + readonly LastModified: Date, +} + +export interface NewAppResponse { + readonly secret: string + readonly app: ServerDataBuffer<OAuth2Application, WebMessage<string>> +} + +interface ExUserProfile extends UserProfile { + created: string | Date +} + +declare module 'pinia' { + export interface PiniaCustomProperties { + userProfile: ServerDataBuffer<ExUserProfile, WebMessage<string>> + userName: string | undefined + refreshProfile(): void; + } +} + +export const profilePlugin = (accountsUrl:MaybeRef<string>) :PiniaPlugin => { + + return ({ store }: PiniaPluginContext) => { + + const { loggedIn } = storeToRefs(store) + const { getProfile, userName } = useUser() + const axios = useAxios(null) + + const [onRefresh, refreshProfile] = useToggle() + + const updateUserProfile = async (profile: ServerObjectBuffer<ExUserProfile>) => { + // Apply the buffer to the profile + const { data } = await axios.post<WebMessage<string>>(get(accountsUrl), profile.buffer) + + //Get the new profile from the server + const newProfile = await getProfile() as ExUserProfile + + //Apply the new profile to the buffer + profile.apply(newProfile) + + return data; + } + + const userProfile = useDataBuffer({} as any, updateUserProfile) + + const loadProfile = async () => { + //Get the user profile + const profile = await getProfile() as ExUserProfile + //Apply the profile to the buffer + userProfile.apply(profile) + } + + watch([loggedIn, onRefresh], ([li]) => { + //If the user is logged in, load the profile buffer + if (li) { + apiCall(loadProfile) + } + }) + + defer(refreshProfile); + + return { + userProfile, + refreshProfile, + userName + } + } +}
\ No newline at end of file |