Migrate data management to OpenAPI-based approach

Replaced manual data handling and filtering logic with an auto-generated OpenAPI client. Introduced new modular Pinia stores for stats and packages, improving maintainability and decoupling data management. Removed outdated custom implementations to streamline the codebase.
This commit is contained in:
2025-05-04 22:16:00 +02:00
parent 555feddabf
commit 075c246710
24 changed files with 860 additions and 825 deletions

View File

@@ -0,0 +1,212 @@
import { defineStore } from 'pinia'
import { reactive } from 'vue'
import { components, getPackages } from '@/api'
export interface PackageFilters {
status?: components['schemas']['Package']['status'][]
pkgbase?: components['schemas']['Package']['pkgbase']
exact?: boolean | undefined
repo?: components['schemas']['Package']['repo']
}
export const usePackagesStore = defineStore('packages', () => {
const state = reactive({
packages: [] as components['schemas']['Package'][],
currentlyBuildingPackages: [] as components['schemas']['Package'][],
total: 0,
offset: 0,
limit: Number(import.meta.env.VITE_LIMIT) || 50,
loading: false,
error: null as string | null,
lastUpdated: Date.now(),
filters: {
status: undefined as components['schemas']['Package']['status'][] | undefined,
pkgbase: undefined as components['schemas']['Package']['pkgbase'] | undefined,
exact: undefined as boolean | undefined,
repo: undefined as components['schemas']['Package']['repo'] | undefined
} as PackageFilters
})
// Actions
const fetchPackages = (init = false) => {
state.loading = true
state.error = null
if (init) {
initFromUrl()
}
const filter = {}
if (state.filters.status && state.filters.status.length > 0) {
filter['status'] = state.filters.status
}
if (state.filters.pkgbase) {
filter['pkgbase'] = state.filters.pkgbase
}
if (state.filters.exact === true) {
filter['exact'] = ''
}
if (state.filters.repo) {
filter['repo'] = state.filters.repo
}
getPackages({
limit: state.limit,
offset: state.offset,
...filter
})
.then((response) => {
if (!response) throw new Error('No response from API')
state.packages = response.packages || []
state.total = response.total || 0
state.offset = response.offset || 0
state.limit = response.limit || state.limit
})
.catch((err) => {
if (err.statusCode === 404) {
state.packages = []
state.total = 0
state.offset = 0
state.limit = Number(import.meta.env.VITE_LIMIT) || 50
} else {
state.error = err instanceof Error ? err.message : 'Failed to fetch packages'
console.error('Error fetching packages:', err)
}
})
.finally(() => {
state.loading = false
})
}
const fetchCurrentlyBuilding = () => {
getPackages({
limit: 0,
offset: 0,
status: ['queued', 'building', 'built']
})
.then((response) => {
state.currentlyBuildingPackages = response?.packages || []
})
.catch((err) => {
if (err.statusCode === 404) {
state.currentlyBuildingPackages = []
} else {
state.error =
err instanceof Error ? err.message : 'Failed to fetch currently building packages'
console.error('Error fetching queued packages:', err)
}
})
.finally(() => {
state.loading = false
})
}
const goToPage = (page: number) => {
state.offset = (page - 1) * state.limit
updateUrlParams()
fetchPackages()
}
const setFilters = (newFilters: PackageFilters, page?: number) => {
state.filters = JSON.parse(JSON.stringify(newFilters))
if (state.filters.exact === false) {
state.filters.exact = undefined
}
if (page) {
state.offset = (page - 1) * state.limit
}
updateUrlParams()
fetchPackages()
}
const resetFilters = () => {
state.filters = {
status: undefined,
pkgbase: undefined,
exact: undefined,
repo: undefined
}
state.offset = 0
state.limit = Number(import.meta.env.VITE_LIMIT) || 50
updateUrlParams()
fetchPackages()
}
const updateUrlParams = () => {
const params = new URLSearchParams()
let page = state.offset / state.limit + 1
// Only add page parameter if it's not the first page
if (page > 1) {
params.set('page', page.toString())
}
if (state.filters.status && state.filters.status.length > 0) {
state.filters.status.forEach((status) => {
params.append('status', status)
})
}
if (state.filters.pkgbase) {
params.set('pkgbase', state.filters.pkgbase.toLowerCase())
}
if (state.filters.repo) {
params.set('repo', state.filters.repo)
}
if (state.filters.exact === true) {
params.set('exact', '')
} else {
params.delete('exact')
}
const paramsString = params.toString()
if (paramsString) {
window.history.pushState(null, '', `${window.location.pathname}?${paramsString}`)
} else {
window.history.pushState(null, '', window.location.pathname)
}
}
const initFromUrl = () => {
const urlParams = new URLSearchParams(window.location.search)
if (urlParams.has('page')) {
const pageParam = urlParams.get('page') || '1'
const parsedPage = parseInt(pageParam, 10)
const page = !isNaN(parsedPage) && parsedPage > 0 ? parsedPage : 1
state.offset = (page - 1) * state.limitq
}
if (urlParams.has('status')) {
state.filters.status = urlParams.getAll('status')
}
if (urlParams.has('pkgbase')) {
state.filters.pkgbase = urlParams.get('pkgbase')
}
if (urlParams.has('repo')) {
state.filters.repo = urlParams.get('repo')
}
if (urlParams.has('exact')) {
state.filters.exact = true
}
}
return {
state,
// Actions
fetchPackages,
fetchCurrentlyBuilding,
goToPage,
setFilters,
resetFilters
}
})