Added proper music loading

pull/3800/merge^2
Anatole Sot 5 months ago
parent 8941fef04d
commit 55dd935302

@ -26,6 +26,8 @@ tags:
description: Endpoints related to retrieving movies and their details. description: Endpoints related to retrieving movies and their details.
- name: tv - name: tv
description: Endpoints related to retrieving TV series and their details. description: Endpoints related to retrieving TV series and their details.
- name: music
description: Endpoints related to retrieving music and details about artists,...
- name: other - name: other
description: Endpoints related to other TMDB data description: Endpoints related to other TMDB data
- name: person - name: person
@ -666,6 +668,159 @@ components:
example: 19923-12-03 example: 19923-12-03
mediaInfo: mediaInfo:
$ref: '#/components/schemas/MediaInfo' $ref: '#/components/schemas/MediaInfo'
ArtistResult:
type: object
properties:
id:
type: string
example: 87f17f8a-c0e2-406c-a149-8c8e311bf330
mediaType:
type: string
example: artist
title:
type: string
example: Album Name
mediaInfo:
$ref: '#/components/schemas/MediaInfo'
name:
type: string
type:
type: string
enum:
- mbArtistType
releases:
type: array
items:
$ref: '#/components/schemas/ReleaseResult'
recordings:
type: array
items:
$ref: '#/components/schemas/RecordingResult'
releaseGroups:
type: array
items:
$ref: '#/components/schemas/ReleaseGroupResult'
works:
type: array
items:
$ref: '#/components/schemas/WorkResult'
gender:
type: string
area:
type: string
beginDate:
type: string
endDate:
type: string
tags:
type: array
items:
type: string
WorkResult:
type: object
properties:
id:
type: string
example: 87f17f8a-c0e2-406c-a149-8c8e311bf330
mediaType:
type: string
example: work
title:
type: string
artist:
type: array
items:
$ref: '#/components/schemas/ArtistResult'
tags:
type: array
items:
type: string
RecordingResult:
type: object
properties:
id:
type: string
example: 87f17f8a-c0e2-406c-a149-8c8e311bf330
mediaType:
type: string
example: recording
title:
type: string
artist:
type: array
items:
$ref: '#/components/schemas/ArtistResult'
length:
type: number
firstReleased:
type: string
format: date-time
tags:
type: array
items:
type: string
ReleaseResult:
type: object
properties:
id:
type: string
example: 87f17f8a-c0e2-406c-a149-8c8e311bf330
mediaType:
type: string
example: release
title:
type: string
artist:
type: array
items:
$ref: '#/components/schemas/ArtistResult'
posterPath:
type: string
date:
type: string
format: date
tracks:
type: array
items:
$ref: '#/components/schemas/RecordingResult'
tags:
type: array
items:
type: string
mediaInfo:
$ref: '#/components/schemas/MediaInfo'
ReleaseGroupResult:
type: object
properties:
id:
type: string
example: 87f17f8a-c0e2-406c-a149-8c8e311bf330
mediaType:
type: string
example: release-group
type:
type: string
enum:
- mbReleaseGroupType
posterPath:
type: string
title:
type: string
artist:
type: array
items:
$ref: '#/components/schemas/ArtistResult'
tags:
type: array
items:
type: string
mediaInfo:
$ref: '#/components/schemas/MediaInfo'
Genre: Genre:
type: object type: object
properties: properties:
@ -5932,6 +6087,26 @@ paths:
$ref: '#/components/schemas/CreditCrew' $ref: '#/components/schemas/CreditCrew'
id: id:
type: number type: number
/music/artist/{artistId}:
get:
summary: Get artist details
description: Returns full artist details in a JSON object.
tags:
- music
parameters:
- in: path
name: artistId
required: true
schema:
type: string
example: 87f17f8a-c0e2-406c-a149-8c8e311bf330
responses:
'200':
description: Artist details
content:
application/json:
schema:
$ref: '#/components/schemas/ArtistResult'
/media: /media:
get: get:
summary: Get media summary: Get media

@ -60,17 +60,24 @@ class Media {
} }
public static async getMedia( public static async getMedia(
id: number, id: number | string,
mediaType: MediaType mediaType: MediaType
): Promise<Media | undefined> { ): Promise<Media | undefined> {
const mediaRepository = getRepository(Media); const mediaRepository = getRepository(Media);
try { try {
const media = await mediaRepository.findOne({ let media: Media | null = null;
where: { tmdbId: id, mediaType }, if (mediaType === MediaType.MOVIE || mediaType === MediaType.TV) {
relations: { requests: true, issues: true }, media = await mediaRepository.findOne({
}); where: { tmdbId: Number(id), mediaType },
relations: { requests: true, issues: true },
});
} else if (mediaType === MediaType.MUSIC) {
media = await mediaRepository.findOne({
where: { mbId: String(id), mediaType },
relations: { requests: true, issues: true },
});
}
return media ?? undefined; return media ?? undefined;
} catch (e) { } catch (e) {
logger.error(e.message); logger.error(e.message);

@ -149,6 +149,7 @@ export interface ArtistResult {
beginDate?: string; beginDate?: string;
endDate?: string; endDate?: string;
tags: string[]; tags: string[];
mediaInfo?: Media;
} }
export type Results = export type Results =
@ -255,7 +256,10 @@ export const mapReleaseGroupResult = (
}; };
}; };
export const mapArtistResult = (artist: mbArtist): ArtistResult => ({ export const mapArtistResult = (
artist: mbArtist,
media?: Media
): ArtistResult => ({
id: artist.id, id: artist.id,
mediaType: 'artist', mediaType: 'artist',
name: artist.name, name: artist.name,
@ -275,6 +279,7 @@ export const mapArtistResult = (artist: mbArtist): ArtistResult => ({
? artist.works.map((work) => mapWorkResult(work)) ? artist.works.map((work) => mapWorkResult(work))
: [], : [],
tags: artist.tags, tags: artist.tags,
mediaInfo: media ?? undefined,
}); });
export const mapReleaseResult = ( export const mapReleaseResult = (

@ -28,6 +28,7 @@ import issueRoutes from './issue';
import issueCommentRoutes from './issueComment'; import issueCommentRoutes from './issueComment';
import mediaRoutes from './media'; import mediaRoutes from './media';
import movieRoutes from './movie'; import movieRoutes from './movie';
import musicRoutes from './music';
import personRoutes from './person'; import personRoutes from './person';
import requestRoutes from './request'; import requestRoutes from './request';
import searchRoutes from './search'; import searchRoutes from './search';
@ -143,6 +144,7 @@ router.use('/search', isAuthenticated(), searchRoutes);
router.use('/discover', isAuthenticated(), discoverRoutes); router.use('/discover', isAuthenticated(), discoverRoutes);
router.use('/request', isAuthenticated(), requestRoutes); router.use('/request', isAuthenticated(), requestRoutes);
router.use('/movie', isAuthenticated(), movieRoutes); router.use('/movie', isAuthenticated(), movieRoutes);
router.use('/music', isAuthenticated(), musicRoutes);
router.use('/tv', isAuthenticated(), tvRoutes); router.use('/tv', isAuthenticated(), tvRoutes);
router.use('/media', isAuthenticated(), mediaRoutes); router.use('/media', isAuthenticated(), mediaRoutes);
router.use('/person', isAuthenticated(), personRoutes); router.use('/person', isAuthenticated(), personRoutes);

@ -0,0 +1,32 @@
import MusicBrainz from '@server/api/musicbrainz';
import { MediaType } from '@server/constants/media';
import Media from '@server/entity/Media';
import logger from '@server/logger';
import { mapArtistResult } from '@server/models/Search';
import { Router } from 'express';
const musicRoutes = Router();
musicRoutes.get('/artist/:id', async (req, res, next) => {
const mb = new MusicBrainz();
try {
const artist = await mb.getArtist(req.params.id);
const media = await Media.getMedia(artist.id, MediaType.MUSIC);
return res.status(200).json(mapArtistResult(artist, media));
} catch (e) {
logger.debug('Something went wrong retrieving movie', {
label: 'API',
errorMessage: e.message,
movieId: req.params.id,
});
return next({
status: 500,
message: 'Unable to retrieve movie.',
});
}
});
export default musicRoutes;

@ -1,11 +1,10 @@
import Slider from '@app/components/Slider'; import Slider from '@app/components/Slider';
import TitleCard from '@app/components/TitleCard'; import MusicTitleCard from '@app/components/TitleCard/MusicTitleCard';
import TmdbTitleCard from '@app/components/TitleCard/TmdbTitleCard'; import TmdbTitleCard from '@app/components/TitleCard/TmdbTitleCard';
import { Permission, useUser } from '@app/hooks/useUser'; import { Permission, useUser } from '@app/hooks/useUser';
import type { MediaResultsResponse } from '@server/interfaces/api/mediaInterfaces'; import type { MediaResultsResponse } from '@server/interfaces/api/mediaInterfaces';
import { defineMessages, useIntl } from 'react-intl'; import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr'; import useSWR from 'swr';
//import MusicBrainz from '@server/api/musicbrainz';
const messages = defineMessages({ const messages = defineMessages({
@ -33,18 +32,6 @@ const RecentlyAddedSlider = () => {
const videoMedias = (media?.results ?? []).filter((item) => ["movie", "tv"].includes(item.mediaType)) const videoMedias = (media?.results ?? []).filter((item) => ["movie", "tv"].includes(item.mediaType))
const musicMedias = (media?.results ?? []).filter((item) => !["movie", "tv"].includes(item.mediaType)) const musicMedias = (media?.results ?? []).filter((item) => !["movie", "tv"].includes(item.mediaType))
//const musicBrainz = new MusicBrainz();
//const artistNames = musicMedias.map(async (item) => {return item.mbId ? (await musicBrainz.getArtist(item.mbId)).name: "Unknown"});
const musicItems = musicMedias.map((item) => (
<TitleCard
key={`media-slider-item-${item.id}`}
id={item.id}
title={"Unknown"}
mediaType={item.mediaType as 'music'}
/>
));
return ( return (
<> <>
<div className="slider-header"> <div className="slider-header">
@ -55,15 +42,17 @@ const RecentlyAddedSlider = () => {
<Slider <Slider
sliderKey="media" sliderKey="media"
isLoading={!media} isLoading={!media}
items={videoMedias.map((item) => ( items={
<TmdbTitleCard videoMedias.map((item) => (
key={`media-slider-item-${item.id}`} <TmdbTitleCard
id={item.id} key={`media-slider-item-${item.id}`}
tmdbId={item.tmdbId as number} id={item.id}
tvdbId={item.tvdbId} tmdbId={item.tmdbId as number}
type={item.mediaType as 'movie' | 'tv'} tvdbId={item.tvdbId}
/> type={item.mediaType as 'movie' | 'tv'}
))} />
)
)}
/> />
<div className="slider-header"> <div className="slider-header">
<div className="slider-title"> <div className="slider-title">
@ -74,7 +63,16 @@ const RecentlyAddedSlider = () => {
<Slider <Slider
sliderKey="media" sliderKey="media"
isLoading={!media} isLoading={!media}
items={musicItems} items={
musicMedias.map((item) => (
<MusicTitleCard
key={`media-slider-item-${item.id}`}
id={item.id}
mbId={item.mbId ?? ''}
mediaType={item.mediaType as 'music'}
/>
)
)}
/> />
</> </>
); );

@ -7,9 +7,10 @@ import { mutate } from 'swr';
interface ErrorCardProps { interface ErrorCardProps {
id: number; id: number;
tmdbId: number; tmdbId?: number;
tvdbId?: number; tvdbId?: number;
type: 'movie' | 'tv'; mbId?: string;
type: 'movie' | 'tv' | 'music';
canExpand?: boolean; canExpand?: boolean;
} }
@ -17,6 +18,7 @@ const messages = defineMessages({
mediaerror: '{mediaType} Not Found', mediaerror: '{mediaType} Not Found',
tmdbid: 'TMDB ID', tmdbid: 'TMDB ID',
tvdbid: 'TheTVDB ID', tvdbid: 'TheTVDB ID',
mbId: 'MusicBrainz ID',
cleardata: 'Clear Data', cleardata: 'Clear Data',
}); });
@ -44,13 +46,17 @@ const Error = ({ id, tmdbId, tvdbId, type, canExpand }: ErrorCardProps) => {
<div className="absolute left-0 right-0 flex items-center justify-between p-2"> <div className="absolute left-0 right-0 flex items-center justify-between p-2">
<div <div
className={`pointer-events-none z-40 rounded-full shadow ${ className={`pointer-events-none z-40 rounded-full shadow ${
type === 'movie' ? 'bg-blue-500' : 'bg-purple-600' type === 'movie' ? 'bg-blue-500' :
type === 'tv' ? 'bg-purple-600'
: 'bg-green-600'
}`} }`}
> >
<div className="flex h-4 items-center px-2 py-2 text-center text-xs font-medium uppercase tracking-wider text-white sm:h-5"> <div className="flex h-4 items-center px-2 py-2 text-center text-xs font-medium uppercase tracking-wider text-white sm:h-5">
{type === 'movie' {type === 'movie'
? intl.formatMessage(globalMessages.movie) ? intl.formatMessage(globalMessages.movie)
: intl.formatMessage(globalMessages.tvshow)} : type === 'tv'
? intl.formatMessage(globalMessages.tvshow)
: intl.formatMessage(globalMessages.music)}
</div> </div>
</div> </div>
<div className="pointer-events-none z-40"> <div className="pointer-events-none z-40">
@ -77,7 +83,9 @@ const Error = ({ id, tmdbId, tvdbId, type, canExpand }: ErrorCardProps) => {
mediaType: intl.formatMessage( mediaType: intl.formatMessage(
type === 'movie' type === 'movie'
? globalMessages.movie ? globalMessages.movie
: globalMessages.tvshow : type === 'tv'
? globalMessages.tvshow
: globalMessages.music
), ),
})} })}
</h1> </h1>

@ -0,0 +1,81 @@
import TitleCard from '@app/components/TitleCard';
import { Permission, useUser } from '@app/hooks/useUser';
import type { ArtistResult,
ReleaseGroupResult,
ReleaseResult,
WorkResult,
RecordingResult } from '@server/models/Search';
import { da } from 'date-fns/locale';
import e from 'express';
import { has } from 'lodash';
import { useInView } from 'react-intersection-observer';
import useSWR from 'swr';
export interface MusicBrainTitleCardProps {
id: number;
mbId: string;
mediaType: 'music';
type?: 'artist' | 'release-group' | 'release' | 'recording' | 'work';
canExpand?: boolean;
}
const TmdbTitleCard = ({
id,
mbId,
mediaType,
canExpand,
type='artist'
}: MusicBrainTitleCardProps) => {
const { hasPermission } = useUser();
const { ref, inView } = useInView({
triggerOnce: true,
});
const url = `/api/v1/music/${type}/${mbId}`;
const { data, error } = useSWR<ArtistResult | ReleaseGroupResult | ReleaseResult | WorkResult | RecordingResult>(
inView ? `${url}` : null
);
if (!data && !error) {
return (
<div ref={ref}>
<TitleCard.Placeholder canExpand={canExpand} />
</div>
);
}
if (!data) {
return hasPermission(Permission.ADMIN) ? (
<TitleCard.ErrorCard
id={id}
mbId={mbId}
type='music'
/>
) : null;
}
if (data.mediaType === 'artist') {
const newData = data as ArtistResult;
return (
<TitleCard
id={id}
status={newData.mediaInfo?.status}
title={newData.name}
mediaType={mediaType}
canExpand={canExpand}
/>
);
} else if (data.mediaType === 'release-group' || data.mediaType === 'release') {
return (<TitleCard
id={data.id}
image={data.posterPath}
status={data.mediaInfo?.status}
title={data.title}
mediaType={data.mediaType}
canExpand={canExpand}
/>)
}
return null;
};
export default TmdbTitleCard;
Loading…
Cancel
Save