From 29e71c8a26080568d2fc0a78d9577d275a4b8bbe Mon Sep 17 00:00:00 2001 From: Henry Jameson Date: Fri, 13 Feb 2026 13:59:00 +0200 Subject: [PATCH] sss -> sc --- src/components/mobile_nav/mobile_nav.js | 8 +- src/components/nav_panel/nav_panel.js | 14 +-- src/components/navigation/navigation_entry.js | 10 +- src/components/navigation/navigation_pins.js | 4 +- .../settings_modal/tabs/clutter_tab.js | 18 +-- .../settings_modal/tabs/filtering_tab.js | 22 ++-- src/components/status/status.js | 6 +- .../status_action_buttons.js | 12 +- .../update_notification.js | 16 +-- src/modules/notifications.js | 6 +- src/modules/users.js | 21 ++-- .../{serverSideStorage.js => sync_config.js} | 114 ++++++++++++------ .../sync_config.spec.js} | 81 +++++++++---- 13 files changed, 204 insertions(+), 128 deletions(-) rename src/stores/{serverSideStorage.js => sync_config.js} (87%) rename test/unit/specs/{modules/serverSideStorage.spec.js => stores/sync_config.spec.js} (87%) diff --git a/src/components/mobile_nav/mobile_nav.js b/src/components/mobile_nav/mobile_nav.js index c5b1d66f6..6242344ce 100644 --- a/src/components/mobile_nav/mobile_nav.js +++ b/src/components/mobile_nav/mobile_nav.js @@ -13,7 +13,7 @@ import SideDrawer from '../side_drawer/side_drawer.vue' import { useAnnouncementsStore } from 'src/stores/announcements.js' import { useInstanceStore } from 'src/stores/instance.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -75,15 +75,15 @@ const MobileNav = { return this.$route.name === 'chat' }, ...mapState(useAnnouncementsStore, ['unreadAnnouncementCount']), - ...mapState(useServerSideStorageStore, { + ...mapState(useSyncConfigStore, { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems).has('chats'), }), shouldConfirmLogout() { - return this.$store.getters.mergedConfig.modalOnLogout + return useSyncConfigStore().mergedConfig.modalOnLogout }, closingDrawerMarksAsSeen() { - return this.$store.getters.mergedConfig.closingDrawerMarksAsSeen + return useSyncConfigStore().mergedConfig.closingDrawerMarksAsSeen }, ...mapGetters(['unreadChatCount']), }, diff --git a/src/components/nav_panel/nav_panel.js b/src/components/nav_panel/nav_panel.js index 8aff31f02..9e5901f24 100644 --- a/src/components/nav_panel/nav_panel.js +++ b/src/components/nav_panel/nav_panel.js @@ -12,7 +12,7 @@ import NavigationPins from 'src/components/navigation/navigation_pins.vue' import { useAnnouncementsStore } from 'src/stores/announcements' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -84,28 +84,28 @@ const NavPanel = { this.editMode = !this.editMode }, toggleCollapse() { - useServerSideStorageStore().setPreference({ + useSyncConfigStore().setPreference({ path: 'simple.collapseNav', value: !this.collapsed, }) - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, isPinned(item) { return this.pinnedItems.has(item) }, togglePin(item) { if (this.isPinned(item)) { - useServerSideStorageStore().removeCollectionPreference({ + useSyncConfigStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value: item, }) } else { - useServerSideStorageStore().addCollectionPreference({ + useSyncConfigStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value: item, }) } - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, }, computed: { @@ -122,7 +122,7 @@ const NavPanel = { ...mapPiniaState(useInstanceStore, { privateMode: (store) => store.private, }), - ...mapPiniaState(useServerSideStorageStore, { + ...mapPiniaState(useSyncConfigStore, { collapsed: (store) => store.prefsStorage.simple.collapseNav, pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), diff --git a/src/components/navigation/navigation_entry.js b/src/components/navigation/navigation_entry.js index 3384534be..7a43000ce 100644 --- a/src/components/navigation/navigation_entry.js +++ b/src/components/navigation/navigation_entry.js @@ -5,7 +5,7 @@ import { routeTo } from 'src/components/navigation/navigation.js' import OptionalRouterLink from 'src/components/optional_router_link/optional_router_link.vue' import { useAnnouncementsStore } from 'src/stores/announcements.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { faThumbtack } from '@fortawesome/free-solid-svg-icons' @@ -23,17 +23,17 @@ const NavigationEntry = { }, togglePin(value) { if (this.isPinned(value)) { - useServerSideStorageStore().removeCollectionPreference({ + useSyncConfigStore().removeCollectionPreference({ path: 'collections.pinnedNavItems', value, }) } else { - useServerSideStorageStore().addCollectionPreference({ + useSyncConfigStore().addCollectionPreference({ path: 'collections.pinnedNavItems', value, }) } - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, }, computed: { @@ -47,7 +47,7 @@ const NavigationEntry = { ...mapState({ currentUser: (state) => state.users.currentUser, }), - ...mapPiniaState(useServerSideStorageStore, { + ...mapPiniaState(useSyncConfigStore, { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), }), diff --git a/src/components/navigation/navigation_pins.js b/src/components/navigation/navigation_pins.js index 698bf5d59..85f6fafee 100644 --- a/src/components/navigation/navigation_pins.js +++ b/src/components/navigation/navigation_pins.js @@ -18,7 +18,7 @@ import { useBookmarkFoldersStore } from 'src/stores/bookmark_folders' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useListsStore } from 'src/stores/lists' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { library } from '@fortawesome/fontawesome-svg-core' import { @@ -70,7 +70,7 @@ const NavPanel = { ...mapPiniaState(useBookmarkFoldersStore, { bookmarks: getBookmarkFolderEntries, }), - ...mapPiniaState(useServerSideStorageStore, { + ...mapPiniaState(useSyncConfigStore, { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedNavItems), }), diff --git a/src/components/settings_modal/tabs/clutter_tab.js b/src/components/settings_modal/tabs/clutter_tab.js index 23e069d21..f81ad7040 100644 --- a/src/components/settings_modal/tabs/clutter_tab.js +++ b/src/components/settings_modal/tabs/clutter_tab.js @@ -12,7 +12,7 @@ import UnitSetting from '../helpers/unit_setting.vue' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useSyncConfigStore } from 'src/stores/sync_config.js' const ClutterTab = { components: { @@ -33,7 +33,7 @@ const ClutterTab = { store.instanceIdentity.showInstanceSpecificPanel && store.instanceIdentity.instanceSpecificPanelContent, }), - ...mapState(useServerSideStorageStore, { + ...mapState(useSyncConfigStore, { muteFilters: (store) => Object.entries(store.prefsStorage.simple.muteFilters), muteFiltersObject: (store) => store.prefsStorage.simple.muteFilters, @@ -89,10 +89,10 @@ const ClutterTab = { }, }, methods: { - ...mapActions(useServerSideStorageStore, [ + ...mapActions(useSyncConfigStore, [ 'setPreference', 'unsetPreference', - 'pushServerSideStorage', + 'pushSyncConfig', ]), getDatetimeLocal(timestamp) { const date = new Date(timestamp) @@ -139,7 +139,7 @@ const ClutterTab = { filter.order = this.muteFilters.length + 2 this.muteFiltersDraftObject[newId] = filter this.setPreference({ path: 'simple.muteFilters.' + newId, value: filter }) - this.pushServerSideStorage() + this.pushSyncConfig() }, exportFilter(id) { this.exportedFilter = { ...this.muteFiltersDraftObject[id] } @@ -155,19 +155,19 @@ const ClutterTab = { this.muteFiltersDraftObject[newId] = filter this.setPreference({ path: 'simple.muteFilters.' + newId, value: filter }) - this.pushServerSideStorage() + this.pushSyncConfig() }, deleteFilter(id) { delete this.muteFiltersDraftObject[id] this.unsetPreference({ path: 'simple.muteFilters.' + id, value: null }) - this.pushServerSideStorage() + this.pushSyncConfig() }, purgeExpiredFilters() { this.muteFiltersExpired.forEach(([id]) => { delete this.muteFiltersDraftObject[id] this.unsetPreference({ path: 'simple.muteFilters.' + id, value: null }) }) - this.pushServerSideStorage() + this.pushSyncConfig() }, updateFilter(id, field, value) { const filter = { ...this.muteFiltersDraftObject[id] } @@ -193,7 +193,7 @@ const ClutterTab = { path: 'simple.muteFilters.' + id, value: this.muteFiltersDraftObject[id], }) - this.pushServerSideStorage() + this.pushSyncConfig() this.muteFiltersDraftDirty[id] = false }, }, diff --git a/src/components/settings_modal/tabs/filtering_tab.js b/src/components/settings_modal/tabs/filtering_tab.js index 68efbe762..a0544dd30 100644 --- a/src/components/settings_modal/tabs/filtering_tab.js +++ b/src/components/settings_modal/tabs/filtering_tab.js @@ -13,7 +13,7 @@ import UnitSetting from '../helpers/unit_setting.vue' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { newExporter, @@ -36,11 +36,11 @@ const FilteringTab = { label: this.$t(`user_card.mute_block_${mode}`), })), muteFiltersDraftObject: cloneDeep( - useServerSideStorageStore().prefsStorage.simple.muteFilters, + useSyncConfigStore().prefsStorage.simple.muteFilters, ), muteFiltersDraftDirty: Object.fromEntries( Object.entries( - useServerSideStorageStore().prefsStorage.simple.muteFilters, + useSyncConfigStore().prefsStorage.simple.muteFilters, ).map(([k]) => [k, false]), ), exportedFilter: null, @@ -92,7 +92,7 @@ const FilteringTab = { }, computed: { ...SharedComputedObject(), - ...mapState(useServerSideStorageStore, { + ...mapState(useSyncConfigStore, { muteFilters: (store) => Object.entries(store.prefsStorage.simple.muteFilters), muteFiltersObject: (store) => store.prefsStorage.simple.muteFilters, @@ -149,10 +149,10 @@ const FilteringTab = { }, }, methods: { - ...mapActions(useServerSideStorageStore, [ + ...mapActions(useSyncConfigStore, [ 'setPreference', 'unsetPreference', - 'pushServerSideStorage', + 'pushSyncConfig', ]), getDatetimeLocal(timestamp) { const date = new Date(timestamp) @@ -199,7 +199,7 @@ const FilteringTab = { filter.order = this.muteFilters.length + 2 this.muteFiltersDraftObject[newId] = filter this.setPreference({ path: 'simple.muteFilters.' + newId, value: filter }) - this.pushServerSideStorage() + this.pushSyncConfig() }, exportFilter(id) { this.exportedFilter = { ...this.muteFiltersDraftObject[id] } @@ -215,19 +215,19 @@ const FilteringTab = { this.muteFiltersDraftObject[newId] = filter this.setPreference({ path: 'simple.muteFilters.' + newId, value: filter }) - this.pushServerSideStorage() + this.pushSyncConfig() }, deleteFilter(id) { delete this.muteFiltersDraftObject[id] this.unsetPreference({ path: 'simple.muteFilters.' + id, value: null }) - this.pushServerSideStorage() + this.pushSyncConfig() }, purgeExpiredFilters() { this.muteFiltersExpired.forEach(([id]) => { delete this.muteFiltersDraftObject[id] this.unsetPreference({ path: 'simple.muteFilters.' + id, value: null }) }) - this.pushServerSideStorage() + this.pushSyncConfig() }, updateFilter(id, field, value) { const filter = { ...this.muteFiltersDraftObject[id] } @@ -253,7 +253,7 @@ const FilteringTab = { path: 'simple.muteFilters.' + id, value: this.muteFiltersDraftObject[id], }) - this.pushServerSideStorage() + this.pushSyncConfig() this.muteFiltersDraftDirty[id] = false }, }, diff --git a/src/components/status/status.js b/src/components/status/status.js index fbea34b4b..786864244 100644 --- a/src/components/status/status.js +++ b/src/components/status/status.js @@ -22,7 +22,7 @@ import UserPopover from '../user_popover/user_popover.vue' import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import generateProfileLink from 'src/services/user_profile_link_generator/user_profile_link_generator' @@ -259,9 +259,7 @@ const Status = { }, muteFilterHits() { return muteFilterHits( - Object.values( - useServerSideStorageStore().prefsStorage.simple.muteFilters, - ), + Object.values(useSyncConfigStore().prefsStorage.simple.muteFilters), this.status, ) }, diff --git a/src/components/status_action_buttons/status_action_buttons.js b/src/components/status_action_buttons/status_action_buttons.js index 2e08797c8..9938e6382 100644 --- a/src/components/status_action_buttons/status_action_buttons.js +++ b/src/components/status_action_buttons/status_action_buttons.js @@ -5,7 +5,7 @@ import Popover from 'src/components/popover/popover.vue' import ActionButtonContainer from './action_button_container.vue' import { BUTTONS } from './buttons_definitions.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import genRandomSeed from 'src/services/random_seed/random_seed.service.js' @@ -36,7 +36,7 @@ const StatusActionButtons = { ActionButtonContainer, }, computed: { - ...mapState(useServerSideStorageStore, { + ...mapState(useSyncConfigStore, { pinnedItems: (store) => new Set(store.prefsStorage.collections.pinnedStatusActions), }), @@ -111,18 +111,18 @@ const StatusActionButtons = { return this.pinnedItems.has(button.name) }, unpin(button) { - useServerSideStorageStore().removeCollectionPreference({ + useSyncConfigStore().removeCollectionPreference({ path: 'collections.pinnedStatusActions', value: button.name, }) - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, pin(button) { - useServerSideStorageStore().addCollectionPreference({ + useSyncConfigStore().addCollectionPreference({ path: 'collections.pinnedStatusActions', value: button.name, }) - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, getComponent(button) { if (!this.$store.state.users.currentUser && button.anonLink) { diff --git a/src/components/update_notification/update_notification.js b/src/components/update_notification/update_notification.js index a8bc60676..78aaa79e8 100644 --- a/src/components/update_notification/update_notification.js +++ b/src/components/update_notification/update_notification.js @@ -1,7 +1,7 @@ import Modal from 'src/components/modal/modal.vue' import { useInstanceStore } from 'src/stores/instance.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import pleromaTanFoxMask from 'src/assets/pleromatan_apology_fox_mask.png' import pleromaTanMask from 'src/assets/pleromatan_apology_mask.png' @@ -41,9 +41,9 @@ const UpdateNotification = { return ( !useInstanceStore().disableUpdateNotification && this.$store.state.users.currentUser && - useServerSideStorageStore().flagStorage.updateCounter < + useSyncConfigStore().flagStorage.updateCounter < CURRENT_UPDATE_COUNTER && - !useServerSideStorageStore().prefsStorage.simple.dontShowUpdateNotifs + !useSyncConfigStore().prefsStorage.simple.dontShowUpdateNotifs ) }, }, @@ -53,22 +53,22 @@ const UpdateNotification = { }, neverShowAgain() { this.toggleShow() - useServerSideStorageStore().setFlag({ + useSyncConfigStore().setFlag({ flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER, }) - useServerSideStorageStore().setPreference({ + useSyncConfigStore().setPreference({ path: 'simple.dontShowUpdateNotifs', value: true, }) - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, dismiss() { - useServerSideStorageStore().setFlag({ + useSyncConfigStore().setFlag({ flag: 'updateCounter', value: CURRENT_UPDATE_COUNTER, }) - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }, }, mounted() { diff --git a/src/modules/notifications.js b/src/modules/notifications.js index cb3d430db..9507177a2 100644 --- a/src/modules/notifications.js +++ b/src/modules/notifications.js @@ -10,7 +10,7 @@ import { } from '../services/notification_utils/notification_utils.js' import { useReportsStore } from 'src/stores/reports.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage.js' +import { useSyncConfigStore } from 'src/stores/sync_config.js' const emptyNotifications = () => ({ desktopNotificationSilence: true, @@ -119,9 +119,7 @@ export const notifications = { maybeShowNotification( store, - Object.values( - useServerSideStorageStore().prefsStorage.simple.muteFilters, - ), + Object.values(useSyncConfigStore().prefsStorage.simple.muteFilters), notification, ) } else if (notification.seen) { diff --git a/src/modules/users.js b/src/modules/users.js index b0febcd3a..ea2a0ccb4 100644 --- a/src/modules/users.js +++ b/src/modules/users.js @@ -26,7 +26,7 @@ import { useInstanceStore } from 'src/stores/instance.js' import { useInstanceCapabilitiesStore } from 'src/stores/instance_capabilities.js' import { useInterfaceStore } from 'src/stores/interface.js' import { useOAuthStore } from 'src/stores/oauth.js' -import { useServerSideStorageStore } from 'src/stores/serverSideStorage' +import { useSyncConfigStore } from 'src/stores/sync_config.js' import { declarations } from 'src/modules/config_declaration' @@ -682,7 +682,7 @@ const users = { useInterfaceStore().setLastTimeline('public-timeline') useInterfaceStore().setLayoutWidth(windowWidth()) useInterfaceStore().setLayoutHeight(windowHeight()) - store.commit('clearServerSideStorage') + //useSyncConfigStore().clearSyncConfig() }) }, loginUser(store, accessToken) { @@ -702,7 +702,7 @@ const users = { user.domainMutes = [] commit('setCurrentUser', user) - useServerSideStorageStore().setServerSideStorage(user) + useSyncConfigStore().setSyncConfig(user) commit('addNewUsers', [user]) useEmojiStore().fetchEmoji() @@ -723,17 +723,16 @@ const users = { /* // Reset wordfilter Object.keys( - useServerSideStorageStore().prefsStorage.simple.muteFilters + useSyncConfigStore().prefsStorage.simple.muteFilters ).forEach(key => { - useServerSideStorageStore().unsetPreference({ path: 'simple.muteFilters.' + key, value: null }) + useSyncConfigStore().unsetPreference({ path: 'simple.muteFilters.' + key, value: null }) }) // Reset flag to 0 to re-run migrations - useServerSideStorageStore().setFlag({ flag: 'configMigration', value: 0 }) + useSyncConfigStore().setFlag({ flag: 'configMigration', value: 0 }) /**/ - const { configMigration } = - useServerSideStorageStore().flagStorage + const { configMigration } = useSyncConfigStore().flagStorage declarations .filter((x) => { return ( @@ -744,12 +743,12 @@ const users = { }) .toSorted((a, b) => a.configMigration - b.configMigration) .forEach((value) => { - value.migration(useServerSideStorageStore(), store.rootState) - useServerSideStorageStore().setFlag({ + value.migration(useSyncConfigStore(), store.rootState) + useSyncConfigStore().setFlag({ flag: 'configMigration', value: value.migrationNum, }) - useServerSideStorageStore().pushServerSideStorage() + useSyncConfigStore().pushSyncConfig() }) if (user.token) { diff --git a/src/stores/serverSideStorage.js b/src/stores/sync_config.js similarity index 87% rename from src/stores/serverSideStorage.js rename to src/stores/sync_config.js index 99f5045f7..009ccbce4 100644 --- a/src/stores/serverSideStorage.js +++ b/src/stores/sync_config.js @@ -17,7 +17,11 @@ import { toRaw } from 'vue' import { CURRENT_UPDATE_COUNTER } from 'src/components/update_notification/update_notification.js' -export const VERSION = 1 +import { useInstanceStore } from 'src/stores/instance' + +import { defaultState as configDefaultState } from 'src/modules/default_config_state' + +export const VERSION = 2 export const NEW_USER_DATE = new Date('2022-08-04') // date of writing this, basically export const COMMAND_TRIM_FLAGS = 1000 @@ -41,6 +45,7 @@ export const defaultState = { dontShowUpdateNotifs: false, collapseNav: false, muteFilters: {}, + ...configDefaultState, }, collections: { pinnedStatusActions: ['reply', 'retweet', 'favorite', 'emoji'], @@ -128,13 +133,13 @@ export const _getRecentData = (cache, live, isTest) => { live._version === cache._version ) { console.debug( - 'Same version/timestamp on both source, source of truth irrelevant', + 'Same version/timestamp on both sources, source of truth irrelevant', ) result.recent = cache result.stale = live } else { console.debug( - 'Different timestamp, figuring out which one is more recent', + 'Different timestamp or version, figuring out which one is more recent', ) if (live._timestamp < cache._timestamp) { result.recent = cache @@ -208,7 +213,7 @@ const _mergeJournal = (...journals) => { // side effect journal.sort((a, b) => (a.timestamp > b.timestamp ? 1 : -1)) - if (path.startsWith('collections')) { + if (path.startsWith('collections') || path.startsWith('objectCollection')) { const lastRemoveIndex = findLastIndex( journal, ({ operation }) => operation === 'removeFromCollection', @@ -229,6 +234,7 @@ const _mergeJournal = (...journals) => { return false } if (a.operation === 'addToCollection') { + // TODO check how objectCollections behaves here return a.args[0] === b.args[0] } return false @@ -368,21 +374,21 @@ export const _resetFlags = ( return result } -export const _doMigrations = (cache) => { - if (!cache) return cache +export const _doMigrations = (cache, live) => { + const data = cache ?? live - if (cache._version < VERSION) { + if (data._version < VERSION) { console.debug( - 'Local cached data has older version, seeing if there any migrations that can be applied', + 'Data has older version, seeing if there any migrations that can be applied', ) // no migrations right now since we only have one version console.debug('No migrations found') } - if (cache._version > VERSION) { + if (data._version > VERSION) { console.debug( - 'Local cached data has newer version, seeing if there any reverse migrations that can be applied', + 'Data has newer version, seeing if there any reverse migrations that can be applied', ) // no reverse migrations right now but we leave a possibility of loading a hotpatch if need be @@ -391,9 +397,9 @@ export const _doMigrations = (cache) => { console.debug('Found hotpatch migration, applying') return window._PLEROMA_HOTPATCH.reverseMigrations.call( {}, - 'serverSideStorage', - { from: cache._version, to: VERSION }, - cache, + 'syncConfigStore', + { from: data._version, to: VERSION }, + data, ) } } @@ -402,7 +408,7 @@ export const _doMigrations = (cache) => { return cache } -export const useServerSideStorageStore = defineStore('serverSideStorage', { +export const useSyncConfigStore = defineStore('sync_config', { state() { return cloneDeep(defaultState) }, @@ -510,19 +516,40 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', { `tried to edit internal (starts with _) field '${path}', ignoring.`, ) } - const collection = new Set(get(this.prefsStorage, path)) - collection.delete(value) - set(this.prefsStorage, path, [...collection]) - this.prefsStorage._journal = [ - ...this.prefsStorage._journal, - { - operation: 'removeFromCollection', - path, - args: [value], - timestamp: Date.now(), - }, - ] - this.dirty = true + + const { _key } = value + if (path.startsWith('collection')) { + const collection = new Set(get(this.prefsStorage, path)) + collection.delete(value) + set(this.prefsStorage, path, [...collection]) + + this.prefsStorage._journal = [ + ...this.prefsStorage._journal, + { + operation: 'removeFromCollection', + path, + args: [value], + timestamp: Date.now(), + }, + ] + this.dirty = true + } else if (path.startsWith('objectCollection')) { + const collection = new Set(get(this.prefsStorage, path + '.index')) + collection.delete(_key) + set(this.prefsStorage, path + '.index', [...collection]) + const data = get(this.prefsStorage, path + '.data') + delete data[_key] + + this.prefsStorage._journal = [ + ...this.prefsStorage._journal, + { + operation: 'removeFromCollection', + path, + args: [{ _key }], + timestamp: Date.now(), + }, + ] + } }, reorderCollectionPreference({ path, value, movement }) { if (path.startsWith('_')) { @@ -554,24 +581,23 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', { username, ) }, - clearServerSideStorage() { + clearSyncConfig() { const blankState = { ...cloneDeep(defaultState) } Object.keys(this).forEach((k) => { this[k] = blankState[k] }) }, - setServerSideStorage(userData) { + setSyncConfig(userData) { const live = userData.storage this.raw = live let cache = this.cache - if (cache && cache._user !== userData.fqn) { + if (cache?._user !== userData.fqn) { console.warn( 'Cache belongs to another user! reinitializing local cache!', ) cache = null } - - cache = _doMigrations(cache) + console.log(cache, live) let { recent, stale, needUpload } = _getRecentData(cache, live) @@ -589,6 +615,9 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', { }) } + recent = recent && _doMigrations(recent) + stale = stale && _doMigrations(stale) + if (!needUpload && recent && stale) { console.debug('Checking if data needs merging...') // discarding timestamps and versions @@ -627,7 +656,7 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', { this.flagStorage = this.cache.flagStorage this.prefsStorage = this.cache.prefsStorage }, - pushServerSideStorage({ force = false } = {}) { + pushSyncConfig({ force = false } = {}) { const needPush = this.dirty || force if (!needPush) return this.updateCache({ username: window.vuex.state.users.currentUser.fqn }) @@ -635,9 +664,26 @@ export const useServerSideStorageStore = defineStore('serverSideStorage', { window.vuex.state.api.backendInteractor .updateProfileJSON({ params }) .then((user) => { - this.setServerSideStorage(user) + this.setSyncConfig(user) this.dirty = false }) }, }, + getters: { + mergedConfig: (state) => { + const instancePrefs = useInstanceStore().prefsStorage + const result = Object.fromEntries( + Object.entries(state.prefsStorage.simple).map(([k, v]) => [ + k, + v ?? instancePrefs[k], + ]), + ) + return result + }, + }, + persist: { + afterLoad(state) { + return state + }, + }, }) diff --git a/test/unit/specs/modules/serverSideStorage.spec.js b/test/unit/specs/stores/sync_config.spec.js similarity index 87% rename from test/unit/specs/modules/serverSideStorage.spec.js rename to test/unit/specs/stores/sync_config.spec.js index bd2028ea3..abcddbeb1 100644 --- a/test/unit/specs/modules/serverSideStorage.spec.js +++ b/test/unit/specs/stores/sync_config.spec.js @@ -12,25 +12,25 @@ import { COMMAND_TRIM_FLAGS_AND_RESET, defaultState, newUserFlags, - useServerSideStorageStore, + useSyncConfigStore, VERSION, -} from 'src/stores/serverSideStorage.js' +} from 'src/stores/sync_config.js' -describe('The serverSideStorage module', () => { +describe('The SyncConfig module', () => { beforeEach(() => { setActivePinia(createPinia()) }) describe('mutations', () => { - describe('setServerSideStorage', () => { + describe('setSyncConfig', () => { const user = { created_at: new Date('1999-02-09'), storage: {}, } it('should initialize storage if none present', () => { - const store = useServerSideStorageStore() - store.setServerSideStorage(store, user) + const store = useSyncConfigStore() + store.setSyncConfig({ ...user }) expect(store.cache._version).to.eql(VERSION) expect(store.cache._timestamp).to.be.a('number') expect(store.cache.flagStorage).to.eql(defaultState.flagStorage) @@ -38,8 +38,8 @@ describe('The serverSideStorage module', () => { }) it('should initialize storage with proper flags for new users if none present', () => { - const store = useServerSideStorageStore() - store.setServerSideStorage({ ...user, created_at: new Date() }) + const store = useSyncConfigStore() + store.setSyncConfig({ ...user, created_at: new Date() }) expect(store.cache._version).to.eql(VERSION) expect(store.cache._timestamp).to.be.a('number') expect(store.cache.flagStorage).to.eql(newUserFlags) @@ -47,14 +47,14 @@ describe('The serverSideStorage module', () => { }) it('should merge flags even if remote timestamp is older', () => { - const store = useServerSideStorageStore() + const store = useSyncConfigStore() store.cache = { _timestamp: Date.now(), _version: VERSION, ...cloneDeep(defaultState), } - store.setServerSideStorage({ + store.setSyncConfig({ ...user, storage: { _timestamp: 123, @@ -76,10 +76,10 @@ describe('The serverSideStorage module', () => { }) it('should reset local timestamp to remote if contents are the same', () => { - const store = useServerSideStorageStore() + const store = useSyncConfigStore() store.cache = null - store.setServerSideStorage({ + store.setSyncConfig({ ...user, storage: { _timestamp: 123, @@ -95,9 +95,9 @@ describe('The serverSideStorage module', () => { expect(store.cache.flagStorage.updateCounter).to.eql(999) }) - it('should remote version if local missing', () => { - const store = useServerSideStorageStore() - store.setServerSideStorage(store, user) + it('should use remote version if local missing', () => { + const store = useSyncConfigStore() + store.setSyncConfig(store, user) expect(store.cache._version).to.eql(VERSION) expect(store.cache._timestamp).to.be.a('number') expect(store.cache.flagStorage).to.eql(defaultState.flagStorage) @@ -105,7 +105,7 @@ describe('The serverSideStorage module', () => { }) describe('setPreference', () => { it('should set preference and update journal log accordingly', () => { - const store = useServerSideStorageStore() + const store = useSyncConfigStore() store.setPreference({ path: 'simple.testing', value: 1 }) expect(store.prefsStorage.simple.testing).to.eql(1) expect(store.prefsStorage._journal.length).to.eql(1) @@ -119,18 +119,34 @@ describe('The serverSideStorage module', () => { }) it('should keep journal to a minimum', () => { - const store = useServerSideStorageStore() + const store = useSyncConfigStore() store.setPreference({ path: 'simple.testing', value: 1 }) store.setPreference({ path: 'simple.testing', value: 2 }) store.addCollectionPreference({ path: 'collections.testing', value: 2 }) + store.addCollectionPreference({ + path: 'objectCollections.testing', + value: { _key: 'a', foo: 1 }, + }) + expect(store.prefsStorage.objectCollections.testing).to.eql({ + data: { a: { _key: 'a', foo: 1 } }, + index: ['a'], + }) store.removeCollectionPreference({ path: 'collections.testing', value: 2, }) + store.removeCollectionPreference({ + path: 'objectCollections.testing', + value: { _key: 'a' }, + }) store.updateCache({ username: 'test' }) expect(store.prefsStorage.simple.testing).to.eql(2) expect(store.prefsStorage.collections.testing).to.eql([]) - expect(store.prefsStorage._journal.length).to.eql(2) + expect(store.prefsStorage.objectCollections.testing).to.eql({ + data: {}, + index: [], + }) + expect(store.prefsStorage._journal.length).to.eql(3) expect(store.prefsStorage._journal[0]).to.eql({ path: 'simple.testing', operation: 'set', @@ -145,22 +161,41 @@ describe('The serverSideStorage module', () => { // should have A timestamp, we don't really care what it is timestamp: store.prefsStorage._journal[1].timestamp, }) + expect(store.prefsStorage._journal[2]).to.eql({ + path: 'objectCollections.testing', + operation: 'removeFromCollection', + args: [{ _key: 'a' }], + // should have A timestamp, we don't really care what it is + timestamp: store.prefsStorage._journal[2].timestamp, + }) }) it('should remove duplicate entries from journal', () => { - const store = useServerSideStorageStore() + const store = useSyncConfigStore() store.setPreference({ path: 'simple.testing', value: 1 }) store.setPreference({ path: 'simple.testing', value: 1 }) store.addCollectionPreference({ path: 'collections.testing', value: 2 }) store.addCollectionPreference({ path: 'collections.testing', value: 2 }) + store.addCollectionPreference({ + path: 'objectCollections.testing', + value: { _key: 'a', foo: 1 }, + }) + store.addCollectionPreference({ + path: 'objectCollections.testing', + value: { _key: 'a', foo: 1 }, + }) store.updateCache({ username: 'test' }) expect(store.prefsStorage.simple.testing).to.eql(1) expect(store.prefsStorage.collections.testing).to.eql([2]) - expect(store.prefsStorage._journal.length).to.eql(2) + expect(store.prefsStorage.objectCollections.testing).to.eql({ + data: { a: { _key: 'a', foo: 1 } }, + index: ['a'], + }) + expect(store.prefsStorage._journal.length).to.eql(4) }) it('should remove depth = 3 set/unset entries from journal', () => { - const store = useServerSideStorageStore() + const store = useSyncConfigStore() store.setPreference({ path: 'simple.object.foo', value: 1 }) store.unsetPreference({ path: 'simple.object.foo' }) store.updateCache(store, { username: 'test' }) @@ -169,7 +204,7 @@ describe('The serverSideStorage module', () => { }) it('should not allow unsetting depth <= 2', () => { - const store = useServerSideStorageStore() + const store = useSyncConfigStore() store.setPreference({ path: 'simple.object.foo', value: 1 }) expect(() => store.unsetPreference({ path: 'simple' })).to.throw() expect(() => @@ -178,7 +213,7 @@ describe('The serverSideStorage module', () => { }) it('should not allow (un)setting depth > 3', () => { - const store = useServerSideStorageStore() + const store = useSyncConfigStore() store.setPreference({ path: 'simple.object', value: {} }) expect(() => store.setPreference({ path: 'simple.object.lv3', value: 1 }),