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:
jon r 2025-04-18 10:57:34 +02:00
parent 5997a0f0bb
commit 3e9d75d089
6 changed files with 23411 additions and 221 deletions

View File

@ -16,10 +16,13 @@
"test": "vitest run",
"test:unit": "vitest run --coverage",
"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:tsc": "vue-tsc --noEmit --incremental && tsc --noEmit --incremental -p cypress",
"fix-fomantic-css": "scripts/fix-fomantic-css.sh",
"postinstall": "yarn run fix-fomantic-css"
"lint": "yarn lint:es && yarn lint:tsc",
"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",
"lint:tsc": "vue-tsc --noEmit --incremental && tsc --noEmit --incremental --project tsconfig.json",
"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": {
"@sentry/tracing": "7.47.0",

View File

@ -0,0 +1 @@
../../../api/funkwhale_api/common/schema.yml

23350
front/src/generated/types.ts Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,11 +1,7 @@
import type { InitModule } from '~/types'
import { setupDropdown } from '~/utils/fomantic'
export const install: InitModule = ({ app, store }) => {
app.directive('title', function (el, binding) {
store.commit('ui/pageTitle', binding.value)
})
app.directive('dropdown', (element) => setupDropdown(element))
}

View File

@ -1,4 +1,4 @@
import type { NodeInfo } from '~/store/instance'
import type { components } from '~/generated/types'
import type { InitModule } from '~/types'
import { whenever } from '@vueuse/core'
@ -15,7 +15,7 @@ export const install: InitModule = async ({ store, router }) => {
const fetchNodeInfo = async () => {
try {
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')
])

View File

@ -4,6 +4,9 @@ import type { Router } from 'vue-router'
import type { AxiosError } from 'axios'
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
import type { ComponentPublicInstance } from '@vue/runtime-core'
import type { QueueTrack } from '~/composables/audio/queue'
@ -41,190 +44,46 @@ export interface ThemeEntry {
// Track stuff
export type ContentCategory = 'podcast' | 'music'
export interface Artist {
id: number
fid: string
mbid?: string
// Use backend-defined schema types
name: string
description: Content
cover?: Cover
channel?: Channel
// TODO (wvffle): Check if it's Tag[] or string[]
tags: string[]
export type Actor = components['schemas']['FullActor']
export type Activity = components['schemas']['Activity']
export type Album = components['schemas']['Album']
export type ArtistCredit = components['schemas']['ArtistCredit']
export type Channel = components['schemas']['Channel']
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
albums: Album[]
tracks_count: number
attributed_to: Actor
is_local: boolean
is_playable: boolean
modification_date?: string
}
export type Artist = components['schemas']['Artist']
export interface ArtistCredit {
artist: Artist
credit: string
joinphrase: string
index: number
}
export type PrivacyLevel = components['schemas']['LibraryPrivacyLevelEnum']
export interface Album {
id: number
fid: string
mbid?: string
export type ImportStatus = components['schemas']['ImportStatusEnum']
title: string
description: Content
release_date?: string
cover?: Cover
tags: string[]
// TODO: Find out which type: `Follow` or `LibraryFollow`
// export interface UserFollow {
// uuid: string
// approved: boolean
artist_credit: ArtistCredit[]
tracks_count: number
tracks: Track[]
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
}
// name: string
// type?: 'federation.Actor' | 'federation.UserFollow'
// target?: Actor
// }
// API stuff
// eslint-disable-next-line
@ -236,21 +95,14 @@ export interface BackendError extends AxiosError {
rawPayload?: APIErrorResponse
}
// Backend response now contains pagination fields.
// Example: PaginatedArtistWithAlbumsList
// Example: PaginatedAlbumsList
export interface BackendResponse<T> {
count: number
results: T[]
}
export interface RateLimitStatus {
limit?: string
scope?: string
remaining?: string
duration?: string
availableSeconds: number
reset?: string
resetSeconds?: string
}
// WebSocket stuff
// FS Browser
@ -312,17 +164,17 @@ export interface Upload {
}
// Profile stuff
export interface Actor {
id: number
fid?: string
name?: string
icon?: Cover
summary: string
preferred_username: string
full_username: string
is_local: boolean
domain: string
}
// export interface Actor {
// id: number
// fid?: string
// name?: string
// icon?: Cover
// summary: string
// preferred_username: string
// full_username: string
// is_local: boolean
// domain: string
// }
export interface User {
id: number
@ -486,24 +338,12 @@ export interface UserRequest {
}
// Notification stuff
export type Activity = {
actor: Actor
creation_date: string
related_object: LibraryFollow | UserFollow
type: 'Follow' | 'Accept'
object: LibraryFollow | UserFollow
}
export interface Notification {
id: number
is_read: boolean
activity: Activity
}
// Tags stuff
export interface Tag {
name: string
}
// Application stuff
export interface Application {
client_id: string