Added proper music loading

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

@ -26,6 +26,8 @@ tags:
description: Endpoints related to retrieving movies and their details.
- name: tv
description: Endpoints related to retrieving TV series and their details.
- name: music
description: Endpoints related to retrieving music and details about artists,...
- name: other
description: Endpoints related to other TMDB data
- name: person
@ -666,6 +668,159 @@ components:
example: 19923-12-03
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:
type: object
properties:
@ -5932,6 +6087,26 @@ paths:
$ref: '#/components/schemas/CreditCrew'
id:
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:
get:
summary: Get media

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

@ -149,6 +149,7 @@ export interface ArtistResult {
beginDate?: string;
endDate?: string;
tags: string[];
mediaInfo?: Media;
}
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,
mediaType: 'artist',
name: artist.name,
@ -275,6 +279,7 @@ export const mapArtistResult = (artist: mbArtist): ArtistResult => ({
? artist.works.map((work) => mapWorkResult(work))
: [],
tags: artist.tags,
mediaInfo: media ?? undefined,
});
export const mapReleaseResult = (

@ -28,6 +28,7 @@ import issueRoutes from './issue';
import issueCommentRoutes from './issueComment';
import mediaRoutes from './media';
import movieRoutes from './movie';
import musicRoutes from './music';
import personRoutes from './person';
import requestRoutes from './request';
import searchRoutes from './search';
@ -143,6 +144,7 @@ router.use('/search', isAuthenticated(), searchRoutes);
router.use('/discover', isAuthenticated(), discoverRoutes);
router.use('/request', isAuthenticated(), requestRoutes);
router.use('/movie', isAuthenticated(), movieRoutes);
router.use('/music', isAuthenticated(), musicRoutes);
router.use('/tv', isAuthenticated(), tvRoutes);
router.use('/media', isAuthenticated(), mediaRoutes);
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 TitleCard from '@app/components/TitleCard';
import MusicTitleCard from '@app/components/TitleCard/MusicTitleCard';
import TmdbTitleCard from '@app/components/TitleCard/TmdbTitleCard';
import { Permission, useUser } from '@app/hooks/useUser';
import type { MediaResultsResponse } from '@server/interfaces/api/mediaInterfaces';
import { defineMessages, useIntl } from 'react-intl';
import useSWR from 'swr';
//import MusicBrainz from '@server/api/musicbrainz';
const messages = defineMessages({
@ -33,18 +32,6 @@ const RecentlyAddedSlider = () => {
const videoMedias = (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 (
<>
<div className="slider-header">
@ -55,15 +42,17 @@ const RecentlyAddedSlider = () => {
<Slider
sliderKey="media"
isLoading={!media}
items={videoMedias.map((item) => (
<TmdbTitleCard
key={`media-slider-item-${item.id}`}
id={item.id}
tmdbId={item.tmdbId as number}
tvdbId={item.tvdbId}
type={item.mediaType as 'movie' | 'tv'}
/>
))}
items={
videoMedias.map((item) => (
<TmdbTitleCard
key={`media-slider-item-${item.id}`}
id={item.id}
tmdbId={item.tmdbId as number}
tvdbId={item.tvdbId}
type={item.mediaType as 'movie' | 'tv'}
/>
)
)}
/>
<div className="slider-header">
<div className="slider-title">
@ -74,7 +63,16 @@ const RecentlyAddedSlider = () => {
<Slider
sliderKey="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 {
id: number;
tmdbId: number;
tmdbId?: number;
tvdbId?: number;
type: 'movie' | 'tv';
mbId?: string;
type: 'movie' | 'tv' | 'music';
canExpand?: boolean;
}
@ -17,6 +18,7 @@ const messages = defineMessages({
mediaerror: '{mediaType} Not Found',
tmdbid: 'TMDB ID',
tvdbid: 'TheTVDB ID',
mbId: 'MusicBrainz ID',
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={`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">
{type === 'movie'
? intl.formatMessage(globalMessages.movie)
: intl.formatMessage(globalMessages.tvshow)}
: type === 'tv'
? intl.formatMessage(globalMessages.tvshow)
: intl.formatMessage(globalMessages.music)}
</div>
</div>
<div className="pointer-events-none z-40">
@ -77,7 +83,9 @@ const Error = ({ id, tmdbId, tvdbId, type, canExpand }: ErrorCardProps) => {
mediaType: intl.formatMessage(
type === 'movie'
? globalMessages.movie
: globalMessages.tvshow
: type === 'tv'
? globalMessages.tvshow
: globalMessages.music
),
})}
</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