refactor: front end

Signed-off-by: Sebastian Krupinski <krupinski01@gmail.com>
This commit is contained in:
2026-03-28 12:47:21 -04:00
parent 28e5cce23a
commit ccb781f933
38 changed files with 4909 additions and 2897 deletions

View File

@@ -1,131 +1,259 @@
/**
* Services Store
*/
import { ref, computed, readonly } from 'vue'
import { defineStore } from 'pinia'
import { ref, computed } from 'vue'
import type { Ref, ComputedRef } from 'vue'
import type { ServiceInterface, ServiceRecord } from '../types/service'
import type { SourceSelector } from '../types/common'
import { serviceService } from '../services/serviceService'
import { serviceService } from '../services'
import { ServiceObject } from '../models/service'
import type {
SourceSelector,
ServiceInterface,
} from '../types'
// Nested structure: provider -> service -> ServiceObject
type ServiceStore = Record<string, Record<string, ServiceObject>>
export const useServicesStore = defineStore('documentsServicesStore', () => {
// State
const _services = ref<Record<string, ServiceObject>>({})
const transceiving = ref(false)
export const useServicesStore = defineStore('fileServices', () => {
const services: Ref<ServiceStore> = ref({})
const loading = ref(false)
const error: Ref<string | null> = ref(null)
const initialized = ref(false)
/**
* Get count of services in store
*/
const count = computed(() => Object.keys(_services.value).length)
const serviceList: ComputedRef<ServiceObject[]> = computed(() => {
const result: ServiceObject[] = []
Object.values(services.value).forEach(providerServices => {
result.push(...Object.values(providerServices))
/**
* Check if any services are present in store
*/
const has = computed(() => count.value > 0)
/**
* Get all services present in store
*/
const services = computed(() => Object.values(_services.value))
/**
* Get all services present in store grouped by provider
*/
const servicesByProvider = computed(() => {
const groups: Record<string, ServiceObject[]> = {}
Object.values(_services.value).forEach((service) => {
const providerServices = (groups[service.provider] ??= [])
providerServices.push(service)
})
return result
return groups
})
const getService = (providerId: string, serviceId: string): ServiceObject | undefined => {
return services.value[providerId]?.[serviceId]
}
const hasService = (providerId: string, serviceId: string): boolean => {
return !!services.value[providerId]?.[serviceId]
}
const getProviderServices = (providerId: string): ServiceObject[] => {
return Object.values(services.value[providerId] || {})
}
const getRootId = (providerId: string, serviceId: string): string | undefined => {
return services.value[providerId]?.[serviceId]?.rootId
}
const setServices = (data: ServiceRecord) => {
const hydrated: ServiceStore = {}
for (const [id, serviceData] of Object.entries(data)) {
const providerId = serviceData.provider
if (!hydrated[providerId]) {
hydrated[providerId] = {}
}
hydrated[providerId][id] = new ServiceObject().fromJson(serviceData)
/**
* Get a specific service from store, with optional retrieval
*
* @param provider - provider identifier
* @param identifier - service identifier
* @param retrieve - Retrieve behavior: true = fetch if missing or refresh, false = cache only
*
* @returns Service object or null
*/
function service(provider: string, identifier: string | number, retrieve: boolean = false): ServiceObject | null {
const key = identifierKey(provider, identifier)
if (retrieve === true && !_services.value[key]) {
console.debug(`[Documents Manager][Store] - Force fetching service "${key}"`)
fetch(provider, identifier)
}
services.value = hydrated
initialized.value = true
return _services.value[key] || null
}
const addService = (providerId: string, serviceId: string, service: ServiceInterface) => {
if (!services.value[providerId]) {
services.value[providerId] = {}
}
services.value[providerId][serviceId] = new ServiceObject().fromJson(service)
/**
* Unique key for a service
*/
function identifierKey(provider: string, identifier: string | number | null): string {
return `${provider}:${identifier ?? ''}`
}
const removeService = (providerId: string, serviceId: string) => {
if (services.value[providerId]) {
delete services.value[providerId][serviceId]
}
}
const clearServices = () => {
services.value = {}
initialized.value = false
}
// API actions
const fetchServices = async (sources?: SourceSelector): Promise<void> => {
loading.value = true
error.value = null
// Actions
/**
* Retrieve all or specific services, optionally filtered by source selector
*
* @param sources - optional source selector
*
* @returns Promise with service object list keyed by provider and service identifier
*/
async function list(sources?: SourceSelector): Promise<Record<string, ServiceObject>> {
transceiving.value = true
try {
const data = await serviceService.list(sources)
setServices(data)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch services'
throw e
const response = await serviceService.list({ sources })
// Flatten nested structure: provider-id: { service-id: object } -> "provider-id:service-id": object
const services: Record<string, ServiceObject> = {}
Object.entries(response).forEach(([_providerId, providerServices]) => {
Object.entries(providerServices).forEach(([_serviceId, serviceObj]) => {
const key = identifierKey(serviceObj.provider, serviceObj.identifier)
services[key] = serviceObj
})
})
// Merge retrieved services into state
_services.value = { ..._services.value, ...services }
console.debug('[Documents Manager][Store] - Successfully retrieved', Object.keys(services).length, 'services')
return services
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to retrieve services:', error)
throw error
} finally {
loading.value = false
transceiving.value = false
}
}
const checkServiceExtant = async (sources: SourceSelector): Promise<Record<string, boolean>> => {
/**
* Retrieve a specific service by provider and identifier
*
* @param provider - provider identifier
* @param identifier - service identifier
*
* @returns Promise with service object
*/
async function fetch(provider: string, identifier: string | number): Promise<ServiceObject> {
transceiving.value = true
try {
return await serviceService.extant(sources)
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to check services'
throw e
const service = await serviceService.fetch({ provider, identifier })
// Merge fetched service into state
const key = identifierKey(service.provider, service.identifier)
_services.value[key] = service
console.debug('[Documents Manager][Store] - Successfully fetched service:', key)
return service
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to fetch service:', error)
throw error
} finally {
transceiving.value = false
}
}
const fetchService = async (providerId: string, serviceId: string): Promise<ServiceObject> => {
/**
* Retrieve service availability status for a given source selector
*
* @param sources - source selector to check availability for
*
* @returns Promise with service availability status
*/
async function extant(sources: SourceSelector) {
transceiving.value = true
try {
const data = await serviceService.fetch(providerId, serviceId)
addService(providerId, serviceId, data)
return services.value[providerId][serviceId]
} catch (e) {
error.value = e instanceof Error ? e.message : 'Failed to fetch service'
throw e
const response = await serviceService.extant({ sources })
console.debug('[Documents Manager][Store] - Successfully checked', sources ? Object.keys(sources).length : 0, 'services')
return response
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to check services:', error)
throw error
} finally {
transceiving.value = false
}
}
/**
* Create a new service with given provider and data
*
* @param provider - provider identifier for the new service
* @param data - partial service data for creation
*
* @returns Promise with created service object
*/
async function create(provider: string, data: Partial<ServiceInterface>): Promise<ServiceObject> {
transceiving.value = true
try {
const service = await serviceService.create({ provider, data })
// Merge created service into state
const key = identifierKey(service.provider, service.identifier)
_services.value[key] = service
console.debug('[Documents Manager][Store] - Successfully created service:', key)
return service
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to create service:', error)
throw error
} finally {
transceiving.value = false
}
}
/**
* Update an existing service with given provider, identifier, and data
*
* @param provider - provider identifier for the service to update
* @param identifier - service identifier for the service to update
* @param data - partial service data for update
*
* @returns Promise with updated service object
*/
async function update(provider: string, identifier: string | number, data: Partial<ServiceInterface>): Promise<ServiceObject> {
transceiving.value = true
try {
const service = await serviceService.update({ provider, identifier, data })
// Merge updated service into state
const key = identifierKey(service.provider, service.identifier)
_services.value[key] = service
console.debug('[Documents Manager][Store] - Successfully updated service:', key)
return service
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to update service:', error)
throw error
} finally {
transceiving.value = false
}
}
/**
* Delete a service by provider and identifier
*
* @param provider - provider identifier for the service to delete
* @param identifier - service identifier for the service to delete
*
* @returns Promise with deletion result
*/
async function remove(provider: string, identifier: string | number): Promise<any> {
transceiving.value = true
try {
await serviceService.delete({ provider, identifier })
// Remove deleted service from state
const key = identifierKey(provider, identifier)
delete _services.value[key]
console.debug('[Documents Manager][Store] - Successfully deleted service:', key)
} catch (error: any) {
console.error('[Documents Manager][Store] - Failed to delete service:', error)
throw error
} finally {
transceiving.value = false
}
}
// Return public API
return {
// State
services,
loading,
error,
initialized,
// Computed
serviceList,
// State (readonly)
transceiving: readonly(transceiving),
// Getters
getService,
hasService,
getProviderServices,
getRootId,
// Setters
setServices,
addService,
removeService,
clearServices,
count,
has,
services,
servicesByProvider,
// Actions
fetchServices,
checkServiceExtant,
fetchService,
service,
list,
fetch,
extant,
create,
update,
delete: remove,
}
})