fix(regression): postpone the integration of openapi-client and instead manually type all requests #2398
This commit is contained in:
parent
910a6ab157
commit
43c1bee971
|
@ -1,7 +1,7 @@
|
|||
<script setup lang="ts">
|
||||
import type { BackendError, FileSystem, Library, PrivacyLevel } from '~/types'
|
||||
import type { paths } from '~/generated/types'
|
||||
import type { VueUploadItem } from 'vue-upload-component'
|
||||
import type { paths } from '~/generated/types'
|
||||
|
||||
import { computed, ref, reactive, watch, nextTick } from 'vue'
|
||||
import { useEventListener, useIntervalFn } from '@vueuse/core'
|
||||
|
@ -9,7 +9,6 @@ import { humanSize, truncate } from '~/utils/filters'
|
|||
import { useI18n } from 'vue-i18n'
|
||||
import { sortBy } from 'lodash-es'
|
||||
import { useStore } from '~/store'
|
||||
import { useClient } from '~/ui/composables/useClient'
|
||||
|
||||
import axios from 'axios'
|
||||
|
||||
|
@ -42,7 +41,6 @@ const props = withDefaults(defineProps<Props>(), {
|
|||
|
||||
const { t } = useI18n()
|
||||
const store = useStore()
|
||||
const { get, post } = useClient('libraries')
|
||||
|
||||
const upload = ref()
|
||||
const currentTab = ref('uploads')
|
||||
|
@ -86,31 +84,31 @@ const library = ref<Library>()
|
|||
|
||||
// New implementation with `useClient`:
|
||||
|
||||
watch(privacyLevel, (newValue) =>
|
||||
get({
|
||||
privacy_level: newValue,
|
||||
scope: 'me'
|
||||
})
|
||||
.then((data) =>
|
||||
library.value = data?.results.find(({name}) => name === privacyLevel.value)
|
||||
),
|
||||
{ immediate: true }
|
||||
)
|
||||
// watch(privacyLevel, (newValue) =>
|
||||
// get({
|
||||
// privacy_level: newValue,
|
||||
// scope: 'me'
|
||||
// })
|
||||
// .then((data) =>
|
||||
// library.value = data?.results.find(({name}) => name === privacyLevel.value)
|
||||
// ),
|
||||
// { immediate: true }
|
||||
// )
|
||||
|
||||
// Old implementation:
|
||||
|
||||
// watch(privacyLevel, async(newValue) => { try {
|
||||
// const response = await axios.get<paths['/api/v2/libraries/']['get']['responses']['200']['content']['application/json']>('libraries/', {
|
||||
// params: {
|
||||
// privacy_level: privacyLevel.value,
|
||||
// scope: 'me'
|
||||
// }
|
||||
// })
|
||||
watch(privacyLevel, async(newValue) => { try {
|
||||
const response = await axios.get<paths['/api/v2/libraries/']['get']['responses']['200']['content']['application/json']>('libraries/', {
|
||||
params: {
|
||||
privacy_level: privacyLevel.value,
|
||||
scope: 'me'
|
||||
}
|
||||
})
|
||||
|
||||
// library.value = response.data.results.find(({name})=>name===privacyLevel.value)
|
||||
// } catch (error) {
|
||||
// useErrorHandler(error as Error)
|
||||
// }}, { immediate: true })
|
||||
library.value = response.data.results.find(({name})=>name===privacyLevel.value)
|
||||
} catch (error) {
|
||||
useErrorHandler(error as Error)
|
||||
}}, { immediate: true })
|
||||
|
||||
|
||||
//
|
||||
|
|
|
@ -1,47 +1,139 @@
|
|||
import type { paths } from '~/generated/types.ts'
|
||||
import type { Simplify } from 'type-fest'
|
||||
import type { APIErrorResponse, BackendError, RateLimitStatus } from '~/types'
|
||||
|
||||
import axios from 'axios'
|
||||
import useErrorHandler from '~/composables/useErrorHandler'
|
||||
import createClient from 'openapi-fetch'
|
||||
|
||||
import { parseAPIErrors } from '~/utils'
|
||||
import { i18n } from '~/init/locale'
|
||||
|
||||
import moment from 'moment'
|
||||
|
||||
import useLogger from '~/composables/useLogger'
|
||||
import { useRouter } from 'vue-router'
|
||||
import { useStore } from '~/store'
|
||||
|
||||
// Note [WIP] that this module is Work in Progress!
|
||||
// TODO: Replace all `axios` calls with this client
|
||||
|
||||
const { t } = i18n.global
|
||||
const logger = useLogger()
|
||||
const store = useStore()
|
||||
const router = useRouter()
|
||||
|
||||
const prefix = "/api/v2/" as const;
|
||||
type Prefix = typeof prefix;
|
||||
|
||||
type RemovePrefixAndSuffix<T extends string> =
|
||||
T extends `${Prefix}${infer Infix}/` ? Infix : never
|
||||
const client = createClient<paths>({ baseUrl: `${store.state.instance.instanceUrl}${prefix}` });
|
||||
client.use({
|
||||
/*
|
||||
TODO: Check if we need these:
|
||||
axios.defaults.xsrfCookieName = 'csrftoken'
|
||||
axios.defaults.xsrfHeaderName = 'X-CSRFToken'
|
||||
*/
|
||||
|
||||
type Path = RemovePrefixAndSuffix<Simplify<keyof paths>>
|
||||
async onRequest({ request, options }) {
|
||||
if (store.state.auth.oauth.accessToken) {
|
||||
request.headers.set("Authorization", store.getters['auth/header'])
|
||||
}
|
||||
return request;
|
||||
},
|
||||
|
||||
type Get<TPath extends Path> = paths[`${Prefix}${TPath}/`]['get']
|
||||
type Post<TPath extends Path> = paths[`${Prefix}${TPath}/`]['post']
|
||||
async onResponse({ request, response, options }) {
|
||||
return response
|
||||
},
|
||||
|
||||
type GetRequestParameters<TPath extends Path> =
|
||||
Get<TPath> extends { parameters: { query? : any } }
|
||||
? Get<TPath>['parameters']['query']
|
||||
: Get<TPath> extends { parameters: any }
|
||||
? Get<TPath>['parameters']
|
||||
: never
|
||||
async onError({ error: unknownError }) {
|
||||
const error = unknownError as BackendError
|
||||
error.backendErrors = []
|
||||
error.isHandled = false
|
||||
|
||||
/**
|
||||
* 200
|
||||
*/
|
||||
type OK<TPath extends Path> =
|
||||
Get<TPath> extends { responses: { ['200'] : { content: { ['application/json']: any } } } }
|
||||
? Get<TPath>['responses']['200']['content']['application/json']
|
||||
: never
|
||||
if (store.state.auth.authenticated && !store.state.auth.oauth.accessToken && error.response?.status === 401) {
|
||||
store.commit('auth/authenticated', false)
|
||||
logger.warn('Received 401 response from API, redirecting to login form', router.currentRoute.value.fullPath)
|
||||
await router.push({ name: 'login', query: { next: router.currentRoute.value.fullPath } })
|
||||
}
|
||||
|
||||
type PostRequestJson<TPath extends Path> =
|
||||
Post<TPath> extends { requestBody: { content: { ['application/json']: any } } }
|
||||
? Post<TPath>['requestBody']['content']['application/json']
|
||||
: never
|
||||
switch (error.response?.status) {
|
||||
case 404:
|
||||
if (error.response?.data === 'Radio doesn\'t have more candidates') {
|
||||
error.backendErrors.push(error.response.data)
|
||||
break
|
||||
}
|
||||
|
||||
/**
|
||||
* 201
|
||||
*/
|
||||
type Created<TPath extends Path> =
|
||||
Post<TPath> extends { responses: { ['201']: { content: { ['application/json']: any } } } }
|
||||
? Post<TPath>['responses']['201']['content']['application/json']
|
||||
: never
|
||||
error.backendErrors.push('Resource not found')
|
||||
error.isHandled = true
|
||||
store.commit('ui/addMessage', {
|
||||
// @ts-expect-error TS does not know about .data structure
|
||||
content: error.response?.data?.detail ?? error.response?.data ?? 'Resource not found',
|
||||
class: 'error'
|
||||
})
|
||||
break
|
||||
|
||||
case 403:
|
||||
error.backendErrors.push('Permission denied')
|
||||
break
|
||||
|
||||
case 429: {
|
||||
let message
|
||||
const rateLimitStatus: RateLimitStatus = {
|
||||
limit: error.response?.headers['x-ratelimit-limit'],
|
||||
description: error.response?.headers['x-ratelimit-scope'],
|
||||
remaining: error.response?.headers['x-ratelimit-remaining'],
|
||||
duration: error.response?.headers['x-ratelimit-duration'],
|
||||
available_seconds: parseInt(error.response?.headers['retry-after'] ?? '60'),
|
||||
reset: error.response?.headers['x-ratelimit-reset'],
|
||||
reset_seconds: error.response?.headers['x-ratelimit-resetseconds'],
|
||||
/* The following threewere not defined. TODO: research correct values */
|
||||
id: '',
|
||||
available: 100,
|
||||
rate: ''
|
||||
}
|
||||
|
||||
if (rateLimitStatus.available_seconds) {
|
||||
const tryAgain = moment().add(rateLimitStatus.available_seconds, 's').toNow(true)
|
||||
message = t('init.axios.rateLimitDelay', { delay: tryAgain })
|
||||
} else {
|
||||
message = t('init.axios.rateLimitLater')
|
||||
}
|
||||
|
||||
error.backendErrors.push(message)
|
||||
error.isHandled = true
|
||||
store.commit('ui/addMessage', {
|
||||
content: message,
|
||||
date: new Date(),
|
||||
class: 'error'
|
||||
})
|
||||
|
||||
logger.error('This client is rate-limited!', rateLimitStatus)
|
||||
break
|
||||
}
|
||||
|
||||
case 500:
|
||||
error.backendErrors.push('A server error occurred')
|
||||
break
|
||||
|
||||
default:
|
||||
if (error.response?.data as object) {
|
||||
const data = error.response?.data as Record<string, unknown>
|
||||
if (data?.detail) {
|
||||
error.backendErrors.push(data.detail as string)
|
||||
} else {
|
||||
error.rawPayload = data as APIErrorResponse
|
||||
const parsedErrors = parseAPIErrors(data as APIErrorResponse)
|
||||
error.backendErrors = [...error.backendErrors, ...parsedErrors]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (error.backendErrors.length === 0) {
|
||||
error.backendErrors.push('An unknown error occurred, ensure your are connected to the internet and your funkwhale instance is up and running')
|
||||
}
|
||||
|
||||
// Do something with response error
|
||||
return Promise.reject(error)
|
||||
},
|
||||
|
||||
/* TODO: Check if we need to handle refreshAuth = async (failedRequest: AxiosError) */
|
||||
})
|
||||
|
||||
/**
|
||||
*
|
||||
|
@ -54,23 +146,19 @@ const result1 = get({ query: { q: 'test' } })
|
|||
```
|
||||
*
|
||||
* @param path The path to create a client for. Check the `paths` type in '~/generated/types.ts' to find all available paths.
|
||||
*/
|
||||
export const useClient = <TPath extends Path>( path: TPath ) => ({
|
||||
* @param variable
|
||||
*
|
||||
*/
|
||||
export const useClient = ({
|
||||
|
||||
get: async (parameters: GetRequestParameters<TPath>) =>
|
||||
await axios.get<OK<TPath>>(`${prefix}${path}/`, {
|
||||
params: parameters
|
||||
})
|
||||
.then(({ data }) => { return data })
|
||||
.catch (useErrorHandler),
|
||||
get: client.GET,
|
||||
|
||||
post: async (requestBody: PostRequestJson<TPath> ) =>
|
||||
await axios.post<Created<TPath>>(`${prefix}${path}/`, {
|
||||
params: requestBody
|
||||
})
|
||||
.then(({ data }) => { return data })
|
||||
.catch (useErrorHandler)
|
||||
post:client.POST,
|
||||
|
||||
// TODO: Add put, patch, delete, head, options, trace
|
||||
put: client.PUT,
|
||||
|
||||
patch: client.PATCH,
|
||||
|
||||
delete: client.DELETE,
|
||||
|
||||
})
|
||||
|
|
Loading…
Reference in New Issue