Merge pull request 'refactor: improvemets' (#11) from refactor/improvements into main
Some checks failed
Renovate / renovate (push) Failing after 1m22s

Reviewed-on: #11
This commit was merged in pull request #11.
This commit is contained in:
2026-03-24 23:14:21 +00:00
2 changed files with 140 additions and 21 deletions

View File

@@ -5,10 +5,11 @@
*/ */
import { ref, onMounted, onUnmounted } from 'vue'; import { ref, onMounted, onUnmounted } from 'vue';
import type { Ref } from 'vue';
import { useEntitiesStore } from '../stores/entitiesStore'; import { useEntitiesStore } from '../stores/entitiesStore';
import { useCollectionsStore } from '../stores/collectionsStore'; import { useCollectionsStore } from '../stores/collectionsStore';
interface SyncSource { export interface SyncSource {
provider: string; provider: string;
service: string | number; service: string | number;
collections: (string | number)[]; collections: (string | number)[];
@@ -23,7 +24,21 @@ interface SyncOptions {
fetchDetails?: boolean; fetchDetails?: boolean;
} }
export function useMailSync(options: SyncOptions = {}) { export interface MailSyncController {
isRunning: Ref<boolean>;
lastSync: Ref<Date | null>;
error: Ref<string | null>;
sources: Ref<SyncSource[]>;
addSource: (source: SyncSource) => void;
removeSource: (source: SyncSource) => void;
clearSources: () => void;
sync: () => Promise<void>;
start: () => void;
stop: () => void;
restart: () => void;
}
export function useMailSync(options: SyncOptions = {}): MailSyncController {
const { const {
interval = 30000, interval = 30000,
autoStart = true, autoStart = true,

View File

@@ -9,8 +9,13 @@ import { CollectionObject, CollectionPropertiesObject } from '../models/collecti
import type { SourceSelector, ListFilter, ListSort } from '../types' import type { SourceSelector, ListFilter, ListSort } from '../types'
export const useCollectionsStore = defineStore('mailCollectionsStore', () => { export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
const ROOT_IDENTIFIER = '__root__'
const SERVICE_INDEX_IDENTIFIER = '__service__'
// State // State
const _collections = ref<Record<string, CollectionObject>>({}) const _collections = ref<Record<string, CollectionObject>>({})
const _collectionsByServiceIndex = ref<Record<string, string[]>>({})
const _collectionsByParentIndex = ref<Record<string, string[]>>({})
const transceiving = ref(false) const transceiving = ref(false)
/** /**
@@ -34,12 +39,19 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
const collectionsByService = computed(() => { const collectionsByService = computed(() => {
const groups: Record<string, CollectionObject[]> = {} const groups: Record<string, CollectionObject[]> = {}
Object.values(_collections.value).forEach((collection) => { Object.keys(_collectionsByServiceIndex.value).forEach(serviceIndexKey => {
const serviceKey = `${collection.provider}:${collection.service}` const collectionKeys = _collectionsByServiceIndex.value[serviceIndexKey] ?? []
if (!groups[serviceKey]) { const collectionsForKey = collectionKeys
groups[serviceKey] = [] .map(collectionKey => _collections.value[collectionKey])
.filter((collection): collection is CollectionObject => collection !== undefined)
if (collectionsForKey.length === 0) {
return
} }
groups[serviceKey].push(collection)
const firstCollection = collectionsForKey[0]
const serviceKey = `${firstCollection.provider}:${firstCollection.service}`
groups[serviceKey] = collectionsForKey
}) })
return groups return groups
@@ -75,10 +87,9 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
* @returns Array of collection objects * @returns Array of collection objects
*/ */
function collectionsForService(provider: string, service: string | number, retrieve: boolean = false): CollectionObject[] { function collectionsForService(provider: string, service: string | number, retrieve: boolean = false): CollectionObject[] {
const serviceKeyPrefix = `${provider}:${service}:` const serviceCollections = collectionObjectsForKeys(
const serviceCollections = Object.entries(_collections.value) _collectionsByServiceIndex.value[identifierKey(provider, service, SERVICE_INDEX_IDENTIFIER)] ?? [],
.filter(([key]) => key.startsWith(serviceKeyPrefix)) )
.map(([_, collection]) => collection)
if (retrieve === true && serviceCollections.length === 0) { if (retrieve === true && serviceCollections.length === 0) {
console.debug(`[Mail Manager][Store] - Force fetching collections for service "${provider}:${service}"`) console.debug(`[Mail Manager][Store] - Force fetching collections for service "${provider}:${service}"`)
@@ -93,19 +104,26 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
return serviceCollections return serviceCollections
} }
function collectionsInCollection(provider: string, service: string | number, collectionId: string | number, retrieve: boolean = false): CollectionObject[] { /**
const collectionKeyPrefix = `${provider}:${service}:${collectionId}:` * Get direct child collections for a parent collection, or root collections when parent is null.
const nestedCollections = Object.entries(_collections.value) *
.filter(([key]) => key.startsWith(collectionKeyPrefix)) * @param provider - provider identifier
.map(([_, collection]) => collection) * @param service - service identifier
* @param collectionId - parent collection identifier, or null for root-level collections
* @param retrieve - Retrieve behavior: true = fetch service collections if missing, false = cache only
*
* @returns Array of direct child collection objects
*/
function collectionsInCollection(provider: string, service: string | number, collectionId: string | number | null, retrieve: boolean = false): CollectionObject[] {
const nestedCollections = collectionObjectsForKeys(
_collectionsByParentIndex.value[identifierKey(provider, service, collectionId)] ?? [],
)
if (retrieve === true && nestedCollections.length === 0) { if (retrieve === true && nestedCollections.length === 0) {
console.debug(`[Mail Manager][Store] - Force fetching collections in collection "${provider}:${service}:${collectionId}"`) console.debug(`[Mail Manager][Store] - Force fetching collections in collection "${provider}:${service}:${collectionId}"`)
const sources: SourceSelector = { const sources: SourceSelector = {
[provider]: { [provider]: {
[String(service)]: { [String(service)]: true
[String(collectionId)]: true
}
} }
} }
list(sources) list(sources)
@@ -114,11 +132,66 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
return nestedCollections return nestedCollections
} }
function hasChildrenInCollection(provider: string, service: string | number, collectionId: string | number | null): boolean {
return (_collectionsByParentIndex.value[identifierKey(provider, service, collectionId)]?.length ?? 0) > 0
}
/** /**
* Create unique key for a collection * Create unique key for a collection
*/ */
function identifierKey(provider: string, service: string | number | null, identifier: string | number | null): string { function identifierKey(provider: string, service: string | number | null, identifier: string | number | null): string {
return `${provider}:${service ?? ''}:${identifier ?? ''}` return `${provider}:${String(service ?? ROOT_IDENTIFIER)}:${String(identifier ?? ROOT_IDENTIFIER)}`
}
function collectionObjectsForKeys(collectionKeys: string[]): CollectionObject[] {
return collectionKeys
.map(collectionKey => _collections.value[collectionKey])
.filter((collection): collection is CollectionObject => collection !== undefined)
}
function addIndexEntry(index: Record<string, string[]>, indexKey: string, collectionKey: string) {
const existing = index[indexKey] ?? []
if (existing.includes(collectionKey)) {
return
}
index[indexKey] = [...existing, collectionKey]
}
function removeIndexEntry(index: Record<string, string[]>, indexKey: string, collectionKey: string) {
const existing = index[indexKey]
if (!existing) {
return
}
const filtered = existing.filter(existingKey => existingKey !== collectionKey)
if (filtered.length === 0) {
delete index[indexKey]
return
}
index[indexKey] = filtered
}
function indexCollection(collection: CollectionObject) {
const collectionKey = identifierKey(collection.provider, collection.service, collection.identifier)
const serviceIndexKey = identifierKey(collection.provider, collection.service, SERVICE_INDEX_IDENTIFIER)
const parentIndexKey = identifierKey(collection.provider, collection.service, collection.collection)
addIndexEntry(_collectionsByServiceIndex.value, serviceIndexKey, collectionKey)
addIndexEntry(_collectionsByParentIndex.value, parentIndexKey, collectionKey)
}
function deindexCollection(collection: CollectionObject) {
const collectionKey = identifierKey(collection.provider, collection.service, collection.identifier)
const serviceIndexKey = identifierKey(collection.provider, collection.service, SERVICE_INDEX_IDENTIFIER)
const parentIndexKey = identifierKey(collection.provider, collection.service, collection.collection)
removeIndexEntry(_collectionsByServiceIndex.value, serviceIndexKey, collectionKey)
removeIndexEntry(_collectionsByParentIndex.value, parentIndexKey, collectionKey)
} }
// Actions // Actions
@@ -143,6 +216,12 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
Object.entries(providerServices).forEach(([_serviceId, serviceCollections]) => { Object.entries(providerServices).forEach(([_serviceId, serviceCollections]) => {
Object.entries(serviceCollections).forEach(([_collectionId, collectionObj]) => { Object.entries(serviceCollections).forEach(([_collectionId, collectionObj]) => {
const key = identifierKey(collectionObj.provider, collectionObj.service, collectionObj.identifier) const key = identifierKey(collectionObj.provider, collectionObj.service, collectionObj.identifier)
const previousCollection = _collections.value[key]
if (previousCollection) {
deindexCollection(previousCollection)
}
collections[key] = collectionObj collections[key] = collectionObj
}) })
}) })
@@ -150,6 +229,9 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
// Merge retrieved collections into state // Merge retrieved collections into state
_collections.value = { ..._collections.value, ...collections } _collections.value = { ..._collections.value, ...collections }
Object.values(collections).forEach(collectionObj => {
indexCollection(collectionObj)
})
console.debug('[Mail Manager][Store] - Successfully retrieved', Object.keys(collections).length, 'collections') console.debug('[Mail Manager][Store] - Successfully retrieved', Object.keys(collections).length, 'collections')
return collections return collections
@@ -177,7 +259,14 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
// Merge fetched collection into state // Merge fetched collection into state
const key = identifierKey(response.provider, response.service, response.identifier) const key = identifierKey(response.provider, response.service, response.identifier)
const previousCollection = _collections.value[key]
if (previousCollection) {
deindexCollection(previousCollection)
}
_collections.value[key] = response _collections.value[key] = response
indexCollection(response)
console.debug('[Mail Manager][Store] - Successfully fetched collection:', key) console.debug('[Mail Manager][Store] - Successfully fetched collection:', key)
return response return response
@@ -234,6 +323,7 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
// Merge created collection into state // Merge created collection into state
const key = identifierKey(response.provider, response.service, response.identifier) const key = identifierKey(response.provider, response.service, response.identifier)
_collections.value[key] = response _collections.value[key] = response
indexCollection(response)
console.debug('[Mail Manager][Store] - Successfully created collection:', key) console.debug('[Mail Manager][Store] - Successfully created collection:', key)
return response return response
@@ -267,7 +357,14 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
// Merge updated collection into state // Merge updated collection into state
const key = identifierKey(response.provider, response.service, response.identifier) const key = identifierKey(response.provider, response.service, response.identifier)
const previousCollection = _collections.value[key]
if (previousCollection) {
deindexCollection(previousCollection)
}
_collections.value[key] = response _collections.value[key] = response
indexCollection(response)
console.debug('[Mail Manager][Store] - Successfully updated collection:', key) console.debug('[Mail Manager][Store] - Successfully updated collection:', key)
return response return response
@@ -295,6 +392,12 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
// Remove deleted collection from state // Remove deleted collection from state
const key = identifierKey(provider, service, identifier) const key = identifierKey(provider, service, identifier)
const previousCollection = _collections.value[key]
if (previousCollection) {
deindexCollection(previousCollection)
}
delete _collections.value[key] delete _collections.value[key]
console.debug('[Mail Manager][Store] - Successfully deleted collection:', key) console.debug('[Mail Manager][Store] - Successfully deleted collection:', key)
@@ -317,6 +420,7 @@ export const useCollectionsStore = defineStore('mailCollectionsStore', () => {
collectionsByService, collectionsByService,
collectionsForService, collectionsForService,
collectionsInCollection, collectionsInCollection,
hasChildrenInCollection,
// Actions // Actions
collection, collection,
list, list,