Refactor: use types derived from API schema
addresses #2366 #2371 #2381 #2386 #2391 closes #2388 Co-Authored-By: ArneBo <arne@ecobasa.org> Co-Authored-By: Flupsi <upsiflu@gmail.com> Co-Authored-By: jon r <jon@allmende.io>
This commit is contained in:
parent
5997a0f0bb
commit
3e9d75d089
|
@ -16,10 +16,13 @@
|
||||||
"test": "vitest run",
|
"test": "vitest run",
|
||||||
"test:unit": "vitest run --coverage",
|
"test:unit": "vitest run --coverage",
|
||||||
"test:generate-mock-server": "msw-auto-mock ../docs/schema.yml -o test/msw-server.ts --node",
|
"test:generate-mock-server": "msw-auto-mock ../docs/schema.yml -o test/msw-server.ts --node",
|
||||||
"lint": "eslint --cache --cache-strategy content --ext .ts,.js,.vue,.json,.html src test cypress public/embed.html",
|
"lint": "yarn lint:es && yarn lint:tsc",
|
||||||
"lint:tsc": "vue-tsc --noEmit --incremental && tsc --noEmit --incremental -p cypress",
|
"lint:es": "eslint --max-warnings 0 --cache --cache-strategy content --ext .ts,.js,.vue,.json,.html,.cjs . cypress public/embed.html src test ui-docs",
|
||||||
"fix-fomantic-css": "scripts/fix-fomantic-css.sh",
|
"lint:tsc": "vue-tsc --noEmit --incremental && tsc --noEmit --incremental --project tsconfig.json",
|
||||||
"postinstall": "yarn run fix-fomantic-css"
|
"generate-types-from-local-schema": "yarn run openapi-typescript ../api/funkwhale_api/common/schema.yml -o src/generated/types.ts",
|
||||||
|
"generate-types-from-remote-schema": "yarn run openapi-typescript https://docs.funkwhale.audio/develop/swagger/schema.yml -o src/generated/types.ts",
|
||||||
|
"fmt:es": "yarn lint:es --fix",
|
||||||
|
"fmt:html": "node --experimental-strip-types node_modules/prettier/bin/prettier.cjs index.html public/embed.html --write"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@sentry/tracing": "7.47.0",
|
"@sentry/tracing": "7.47.0",
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
../../../api/funkwhale_api/common/schema.yml
|
File diff suppressed because it is too large
Load Diff
|
@ -1,11 +1,7 @@
|
||||||
import type { InitModule } from '~/types'
|
import type { InitModule } from '~/types'
|
||||||
|
|
||||||
import { setupDropdown } from '~/utils/fomantic'
|
|
||||||
|
|
||||||
export const install: InitModule = ({ app, store }) => {
|
export const install: InitModule = ({ app, store }) => {
|
||||||
app.directive('title', function (el, binding) {
|
app.directive('title', function (el, binding) {
|
||||||
store.commit('ui/pageTitle', binding.value)
|
store.commit('ui/pageTitle', binding.value)
|
||||||
})
|
})
|
||||||
|
|
||||||
app.directive('dropdown', (element) => setupDropdown(element))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import type { NodeInfo } from '~/store/instance'
|
import type { components } from '~/generated/types'
|
||||||
import type { InitModule } from '~/types'
|
import type { InitModule } from '~/types'
|
||||||
|
|
||||||
import { whenever } from '@vueuse/core'
|
import { whenever } from '@vueuse/core'
|
||||||
|
@ -15,7 +15,7 @@ export const install: InitModule = async ({ store, router }) => {
|
||||||
const fetchNodeInfo = async () => {
|
const fetchNodeInfo = async () => {
|
||||||
try {
|
try {
|
||||||
const [{ data }] = await Promise.all([
|
const [{ data }] = await Promise.all([
|
||||||
axios.get<NodeInfo>('instance/nodeinfo/2.1/'),
|
axios.get<components['schemas']['NodeInfo21']>('instance/nodeinfo/2.1/'),
|
||||||
store.dispatch('instance/fetchSettings')
|
store.dispatch('instance/fetchSettings')
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
|
@ -4,6 +4,9 @@ import type { Router } from 'vue-router'
|
||||||
import type { AxiosError } from 'axios'
|
import type { AxiosError } from 'axios'
|
||||||
import type { RootState } from '~/store'
|
import type { RootState } from '~/store'
|
||||||
|
|
||||||
|
// App types are synced from the backend. Run `yarn update-schema`.
|
||||||
|
import { type components } from '~/generated/types.ts'
|
||||||
|
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
import type { ComponentPublicInstance } from '@vue/runtime-core'
|
import type { ComponentPublicInstance } from '@vue/runtime-core'
|
||||||
import type { QueueTrack } from '~/composables/audio/queue'
|
import type { QueueTrack } from '~/composables/audio/queue'
|
||||||
|
@ -41,190 +44,46 @@ export interface ThemeEntry {
|
||||||
// Track stuff
|
// Track stuff
|
||||||
export type ContentCategory = 'podcast' | 'music'
|
export type ContentCategory = 'podcast' | 'music'
|
||||||
|
|
||||||
export interface Artist {
|
// Use backend-defined schema types
|
||||||
id: number
|
|
||||||
fid: string
|
|
||||||
mbid?: string
|
|
||||||
|
|
||||||
name: string
|
export type Actor = components['schemas']['FullActor']
|
||||||
description: Content
|
export type Activity = components['schemas']['Activity']
|
||||||
cover?: Cover
|
export type Album = components['schemas']['Album']
|
||||||
channel?: Channel
|
export type ArtistCredit = components['schemas']['ArtistCredit']
|
||||||
// TODO (wvffle): Check if it's Tag[] or string[]
|
export type Channel = components['schemas']['Channel']
|
||||||
tags: string[]
|
export type Library = components['schemas']['Library']
|
||||||
|
export type License = components['schemas']['License']
|
||||||
|
export type Listening = components['schemas']['Listening']
|
||||||
|
export type Playlist = components['schemas']['Playlist']
|
||||||
|
export type PlaylistTrack = components['schemas']['PlaylistTrack']
|
||||||
|
export type PrivacyLevelEnum = components['schemas']['PrivacyLevelEnum']
|
||||||
|
export type Radio = components['schemas']['Radio']
|
||||||
|
export type SearchResult = components['schemas']['SearchResult']
|
||||||
|
export type Tag = components['schemas']['Tag']
|
||||||
|
export type Track = components['schemas']['Track']
|
||||||
|
export type Usage = components['schemas']['Usage']
|
||||||
|
export type LibraryScan = components['schemas']['LibraryScan']
|
||||||
|
export type LibraryFollow = components['schemas']['LibraryFollow']
|
||||||
|
export type Cover = components['schemas']['CoverField']
|
||||||
|
export type RateLimitStatus = components['schemas']['RateLimit']['scopes'][number]
|
||||||
|
export type PaginatedAlbumList = components['schemas']['PaginatedAlbumList']
|
||||||
|
export type PaginatedChannelList = components['schemas']['PaginatedChannelList']
|
||||||
|
|
||||||
content_category: ContentCategory
|
export type Artist = components['schemas']['Artist']
|
||||||
albums: Album[]
|
|
||||||
tracks_count: number
|
|
||||||
attributed_to: Actor
|
|
||||||
is_local: boolean
|
|
||||||
is_playable: boolean
|
|
||||||
modification_date?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface ArtistCredit {
|
export type PrivacyLevel = components['schemas']['LibraryPrivacyLevelEnum']
|
||||||
artist: Artist
|
|
||||||
credit: string
|
|
||||||
joinphrase: string
|
|
||||||
index: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Album {
|
export type ImportStatus = components['schemas']['ImportStatusEnum']
|
||||||
id: number
|
|
||||||
fid: string
|
|
||||||
mbid?: string
|
|
||||||
|
|
||||||
title: string
|
// TODO: Find out which type: `Follow` or `LibraryFollow`
|
||||||
description: Content
|
// export interface UserFollow {
|
||||||
release_date?: string
|
// uuid: string
|
||||||
cover?: Cover
|
// approved: boolean
|
||||||
tags: string[]
|
|
||||||
|
|
||||||
artist_credit: ArtistCredit[]
|
// name: string
|
||||||
tracks_count: number
|
// type?: 'federation.Actor' | 'federation.UserFollow'
|
||||||
tracks: Track[]
|
// target?: Actor
|
||||||
|
// }
|
||||||
is_playable: boolean
|
|
||||||
is_local: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Track {
|
|
||||||
id: number
|
|
||||||
fid: string
|
|
||||||
mbid?: string
|
|
||||||
|
|
||||||
title: string
|
|
||||||
description: Content
|
|
||||||
cover?: Cover
|
|
||||||
position?: number
|
|
||||||
copyright?: string
|
|
||||||
license?: License
|
|
||||||
tags: string[]
|
|
||||||
uploads: Upload[]
|
|
||||||
downloads_count: number
|
|
||||||
|
|
||||||
album?: Album
|
|
||||||
artist_credit: ArtistCredit[]
|
|
||||||
disc_number: number
|
|
||||||
|
|
||||||
listen_url: string
|
|
||||||
creation_date: string
|
|
||||||
attributed_to: Actor
|
|
||||||
|
|
||||||
is_playable: boolean
|
|
||||||
is_local: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Channel {
|
|
||||||
id: number
|
|
||||||
uuid: string
|
|
||||||
artist?: Artist
|
|
||||||
actor: Actor
|
|
||||||
attributed_to: Actor
|
|
||||||
url?: string
|
|
||||||
rss_url: string
|
|
||||||
subscriptions_count: number
|
|
||||||
downloads_count: number
|
|
||||||
content_category: ContentCategory
|
|
||||||
|
|
||||||
metadata?: {
|
|
||||||
itunes_category?: unknown
|
|
||||||
itunes_subcategory?: unknown
|
|
||||||
language?: string
|
|
||||||
owner_name?: string
|
|
||||||
owner_email?: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type PrivacyLevel = 'everyone' | 'instance' | 'me'
|
|
||||||
|
|
||||||
export interface Library {
|
|
||||||
id: number
|
|
||||||
uuid: string
|
|
||||||
fid?: string
|
|
||||||
name: string
|
|
||||||
actor: Actor
|
|
||||||
uploads_count: number
|
|
||||||
size: number
|
|
||||||
description: string
|
|
||||||
privacy_level: PrivacyLevel
|
|
||||||
creation_date: string
|
|
||||||
follow?: LibraryFollow
|
|
||||||
latest_scan: LibraryScan
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ImportStatus = 'scanning' | 'pending' | 'finished' | 'errored' | 'draft' | 'skipped'
|
|
||||||
export interface LibraryScan {
|
|
||||||
processed_files: number
|
|
||||||
total_files: number
|
|
||||||
status: ImportStatus
|
|
||||||
errored_files: number
|
|
||||||
modification_date: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface LibraryFollow {
|
|
||||||
uuid: string
|
|
||||||
approved: boolean
|
|
||||||
|
|
||||||
name: string
|
|
||||||
type?: 'music.Library' | 'federation.LibraryFollow'
|
|
||||||
target: Library
|
|
||||||
}
|
|
||||||
export interface UserFollow {
|
|
||||||
uuid: string
|
|
||||||
approved: boolean
|
|
||||||
|
|
||||||
name: string
|
|
||||||
type?: 'federation.Actor' | 'federation.UserFollow'
|
|
||||||
target?: Actor
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Cover {
|
|
||||||
uuid: string
|
|
||||||
urls: {
|
|
||||||
original: string
|
|
||||||
medium_square_crop: string
|
|
||||||
large_square_crop: string
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface License {
|
|
||||||
code: string
|
|
||||||
name: string
|
|
||||||
url: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Playlist {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
modification_date: string
|
|
||||||
actor: Actor
|
|
||||||
privacy_level: PrivacyLevel
|
|
||||||
tracks_count: number
|
|
||||||
duration: number
|
|
||||||
album_covers: string[]
|
|
||||||
|
|
||||||
is_playable: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface PlaylistTrack {
|
|
||||||
track: Track
|
|
||||||
position?: number
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Radio {
|
|
||||||
id: number
|
|
||||||
name: string
|
|
||||||
user: User
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface Listening {
|
|
||||||
id: number
|
|
||||||
track: Track
|
|
||||||
user: User
|
|
||||||
actor: Actor
|
|
||||||
creation_date: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// API stuff
|
// API stuff
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
|
@ -236,21 +95,14 @@ export interface BackendError extends AxiosError {
|
||||||
rawPayload?: APIErrorResponse
|
rawPayload?: APIErrorResponse
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Backend response now contains pagination fields.
|
||||||
|
// Example: PaginatedArtistWithAlbumsList
|
||||||
|
// Example: PaginatedAlbumsList
|
||||||
export interface BackendResponse<T> {
|
export interface BackendResponse<T> {
|
||||||
count: number
|
count: number
|
||||||
results: T[]
|
results: T[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface RateLimitStatus {
|
|
||||||
limit?: string
|
|
||||||
scope?: string
|
|
||||||
remaining?: string
|
|
||||||
duration?: string
|
|
||||||
availableSeconds: number
|
|
||||||
reset?: string
|
|
||||||
resetSeconds?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket stuff
|
// WebSocket stuff
|
||||||
|
|
||||||
// FS Browser
|
// FS Browser
|
||||||
|
@ -312,17 +164,17 @@ export interface Upload {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Profile stuff
|
// Profile stuff
|
||||||
export interface Actor {
|
// export interface Actor {
|
||||||
id: number
|
// id: number
|
||||||
fid?: string
|
// fid?: string
|
||||||
name?: string
|
// name?: string
|
||||||
icon?: Cover
|
// icon?: Cover
|
||||||
summary: string
|
// summary: string
|
||||||
preferred_username: string
|
// preferred_username: string
|
||||||
full_username: string
|
// full_username: string
|
||||||
is_local: boolean
|
// is_local: boolean
|
||||||
domain: string
|
// domain: string
|
||||||
}
|
// }
|
||||||
|
|
||||||
export interface User {
|
export interface User {
|
||||||
id: number
|
id: number
|
||||||
|
@ -486,24 +338,12 @@ export interface UserRequest {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Notification stuff
|
// Notification stuff
|
||||||
export type Activity = {
|
|
||||||
actor: Actor
|
|
||||||
creation_date: string
|
|
||||||
related_object: LibraryFollow | UserFollow
|
|
||||||
type: 'Follow' | 'Accept'
|
|
||||||
object: LibraryFollow | UserFollow
|
|
||||||
}
|
|
||||||
export interface Notification {
|
export interface Notification {
|
||||||
id: number
|
id: number
|
||||||
is_read: boolean
|
is_read: boolean
|
||||||
activity: Activity
|
activity: Activity
|
||||||
}
|
}
|
||||||
|
|
||||||
// Tags stuff
|
|
||||||
export interface Tag {
|
|
||||||
name: string
|
|
||||||
}
|
|
||||||
|
|
||||||
// Application stuff
|
// Application stuff
|
||||||
export interface Application {
|
export interface Application {
|
||||||
client_id: string
|
client_id: string
|
||||||
|
|
Loading…
Reference in New Issue