Instant song query.

pull/7/head
Sander Vocke 5 years ago
parent ffb82d6784
commit 7a137d7012
  1. 123
      client/src/App.tsx
  2. 39
      client/src/api.ts
  3. 88
      client/src/components/FilterControl.tsx
  4. 4
      client/src/components/ItemListLoadedArtistItem.tsx
  5. 4
      client/src/components/ItemListLoadedSongItem.tsx
  6. 2
      client/src/types/DisplayItem.tsx
  7. 32
      client/src/types/Query.tsx
  8. 13
      scripts/gpm_retrieve/gpm_retrieve.py
  9. 24
      server/endpoints/CreateArtistEndpointHandler.ts
  10. 9
      server/endpoints/QueryArtistsEndpointHandler.ts
  11. 40
      server/endpoints/QuerySongsEndpointHandler.ts

@ -7,6 +7,8 @@ import * as serverApi from './api';
import AppBar, { ActiveTab as AppBarActiveTab } from './components/AppBar'; import AppBar, { ActiveTab as AppBarActiveTab } from './components/AppBar';
import ItemList from './components/ItemList'; import ItemList from './components/ItemList';
import ItemListItem from './components/ItemListItem'; import ItemListItem from './components/ItemListItem';
import FilterControl from './components/FilterControl';
import { SongQuery, toApiQuery } from './types/Query';
import { SongDisplayItem, ArtistDisplayItem } from './types/DisplayItem'; import { SongDisplayItem, ArtistDisplayItem } from './types/DisplayItem';
import { ReactComponent as GooglePlayIcon } from './assets/googleplaymusic_icon.svg'; import { ReactComponent as GooglePlayIcon } from './assets/googleplaymusic_icon.svg';
@ -17,9 +19,10 @@ import {
useHistory, useHistory,
Redirect Redirect
} from "react-router-dom"; } from "react-router-dom";
import { timeLog } from 'console';
interface SongItemProps { interface SongItemProps {
id: Number, song: serverApi.SongDetails,
} }
interface ArtistItemProps { interface ArtistItemProps {
@ -28,70 +31,41 @@ interface ArtistItemProps {
const getStoreIcon = (url: String) => { const getStoreIcon = (url: String) => {
if (url.includes('play.google.com')) { if (url.includes('play.google.com')) {
return <GooglePlayIcon height='30px' width='30px'/>; return <GooglePlayIcon height='30px' width='30px' />;
} }
return <StoreIcon/>; return <StoreIcon />;
} }
function SongItem(props: SongItemProps) { function SongItem(props: SongItemProps) {
const [songDisplayItem, setSongDisplayItem] = React.useState<SongDisplayItem | undefined>(undefined);
const updateSong = async () => { const displayItem: SongDisplayItem = {
const response: any = await fetch(serverApi.SongDetailsEndpoint.replace(':id', props.id.toString())); title: props.song.title,
const json: any = await response.json(); artistNames: props.song.artists && props.song.artists.map((artist: serverApi.ArtistDetails) => {
const title: String | undefined = json.title; return artist.name;
const artistIds: Number[] | undefined = json.artistIds; }) || ['Unknown'],
const artistNamesPromises: Promise<String>[] | undefined = artistIds && artistIds.map((id: Number) => { tagNames: props.song.tags && props.song.tags.map((tag: serverApi.TagDetails) => {
return fetch(serverApi.ArtistDetailsEndpoint.replace(':id', id.toString())) return tag.name;
.then((response: any) => response.json()) }) || [],
.then((json: any) => json.name); storeLinks: []
}); // json.storeLinks.map((url: String) => {
const artistNames: String[] | undefined = artistNamesPromises && await Promise.all(artistNamesPromises); // return {
// icon: getStoreIcon(url),
return { // url: url
title: title ? title : "Unknown", // }
artistNames: artistNames ? artistNames : [], // })
storeLinks: json.storeLinks.map((url: String) => {
return {
icon: getStoreIcon(url),
url: url
} }
}),
};
};
useEffect(() => {
updateSong().then((song: SongDisplayItem) => { setSongDisplayItem(song); });
}, []);
return <ItemListItem item={songDisplayItem ? songDisplayItem : { return <ItemListItem item={displayItem} />;
loadingSong: true
}} />;
} }
function SongList() { interface SongListProps {
const [songs, setSongs] = useState<Number[]>([]); songs: serverApi.SongDetails[]
}
React.useEffect(() => { function SongList(props: SongListProps) {
const request: serverApi.QuerySongsRequest = {
query: {}
}
const requestOpts = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
};
fetch(serverApi.QuerySongsEndpoint, requestOpts)
.then((response: any) => response.json())
.then((json: any) => {
'ids' in json && setSongs(json.ids);
});
}, []);
return <Paper> return <Paper>
<ItemList> <ItemList>
{songs.map((song: any) => { {props.songs.map((song: any) => {
return <SongItem id={song} />; return <SongItem song={song} />;
})} })}
</ItemList> </ItemList>
</Paper>; </Paper>;
@ -103,9 +77,17 @@ function ArtistItem(props: ArtistItemProps) {
const updateArtist = async () => { const updateArtist = async () => {
const response: any = await fetch(serverApi.ArtistDetailsEndpoint.replace(':id', props.id.toString())); const response: any = await fetch(serverApi.ArtistDetailsEndpoint.replace(':id', props.id.toString()));
const json: any = await response.json(); const json: any = await response.json();
const tagIds: Number[] | undefined = json.tagIds;
const tagNamesPromises: Promise<String>[] | undefined = tagIds && tagIds.map((id: Number) => {
return fetch(serverApi.TagDetailsEndpoint.replace(':id', id.toString()))
.then((response: any) => response.json())
.then((json: any) => json.name);
});
const tagNames: String[] | undefined = tagNamesPromises && await Promise.all(tagNamesPromises);
return { return {
name: json.name ? json.name : "Unknown", name: json.name ? json.name : "Unknown",
tagNames: tagNames ? tagNames : [],
storeLinks: json.storeLinks.map((url: String) => { storeLinks: json.storeLinks.map((url: String) => {
return { return {
icon: getStoreIcon(url), icon: getStoreIcon(url),
@ -129,7 +111,8 @@ function ArtistList() {
React.useEffect(() => { React.useEffect(() => {
const request: serverApi.QueryArtistsRequest = { const request: serverApi.QueryArtistsRequest = {
query: {} offset: 0,
limit: 20,
} }
const requestOpts = { const requestOpts = {
method: 'POST', method: 'POST',
@ -154,6 +137,30 @@ function ArtistList() {
function AppBody() { function AppBody() {
const history = useHistory(); const history = useHistory();
const [songQuery, setSongQuery] = useState<SongQuery>({
'titleLike': ''
});
const [songs, setSongs] = useState<serverApi.SongDetails[]>([]);
React.useEffect(() => {
const query = songQuery;
setSongs([]);
const request: serverApi.QuerySongsRequest = {
query: toApiQuery(query),
offset: 0,
limit: 20,
}
const requestOpts = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(request)
};
fetch(serverApi.QuerySongsEndpoint, requestOpts)
.then((response: any) => response.json())
.then((json: any) => {
'songs' in json && query === songQuery && setSongs(json.songs);
});
}, [songQuery]);
const onAppBarTabChange = (value: AppBarActiveTab) => { const onAppBarTabChange = (value: AppBarActiveTab) => {
switch (value) { switch (value) {
@ -174,8 +181,12 @@ function AppBody() {
<Redirect exact from='/' to='/songs' /> <Redirect exact from='/' to='/songs' />
<Route path='/songs'> <Route path='/songs'>
<AppBar activeTab={AppBarActiveTab.Songs} onActiveTabChange={onAppBarTabChange} /> <AppBar activeTab={AppBarActiveTab.Songs} onActiveTabChange={onAppBarTabChange} />
<FilterControl
query={songQuery}
onChangeQuery={(query: SongQuery) => { setSongQuery(query); }}
/>
<Paper> <Paper>
<SongList /> <SongList songs={songs} />
</Paper> </Paper>
</Route> </Route>
<Route path='/artists'> <Route path='/artists'>

@ -7,6 +7,22 @@
// a request structure, a response structure and // a request structure, a response structure and
// a checking function which determines request validity. // a checking function which determines request validity.
export interface ArtistDetails {
id: Number,
name: String,
}
export interface TagDetails {
id: Number,
name: String,
parent?: TagDetails,
}
export interface SongDetails {
id: Number,
title: String,
artists?: ArtistDetails[],
tags?: TagDetails[],
}
// Query for songs (POST). // Query for songs (POST).
export const QuerySongsEndpoint = '/song/query'; export const QuerySongsEndpoint = '/song/query';
export enum SongQueryElemOp { export enum SongQueryElemOp {
@ -17,9 +33,11 @@ export enum SongQueryFilterOp {
Eq = "EQ", Eq = "EQ",
Ne = "NE", Ne = "NE",
In = "IN", In = "IN",
NotIn = "NOTIN" NotIn = "NOTIN",
Like = "LIKE",
} }
export enum SongQueryElemProperty { export enum SongQueryElemProperty {
title = "title",
id = "id", id = "id",
artistIds = "artistIds", artistIds = "artistIds",
albumIds = "albumIds", albumIds = "albumIds",
@ -33,10 +51,12 @@ export interface SongQueryElem {
} }
export interface SongQuery extends SongQueryElem { } export interface SongQuery extends SongQueryElem { }
export interface QuerySongsRequest { export interface QuerySongsRequest {
query: SongQuery query: SongQuery,
offset: Number,
limit: Number,
} }
export interface QuerySongsResponse { export interface QuerySongsResponse {
ids: Number[] songs: SongDetails[]
} }
export function checkQuerySongsElem(elem: any): boolean { export function checkQuerySongsElem(elem: any): boolean {
if (elem.childrenOperator && elem.children) { if (elem.childrenOperator && elem.children) {
@ -51,7 +71,10 @@ export function checkQuerySongsElem(elem: any): boolean {
Object.keys(elem).length == 0; Object.keys(elem).length == 0;
} }
export function checkQuerySongsRequest(req: any): boolean { export function checkQuerySongsRequest(req: any): boolean {
return "query" in req && checkQuerySongsElem(req.query); return 'query' in req
&& 'offset' in req
&& 'limit' in req
&& checkQuerySongsElem(req.query);
} }
// Get song details (GET). // Get song details (GET).
@ -70,12 +93,16 @@ export function checkSongDetailsRequest(req: any): boolean {
// Query for artists. // Query for artists.
export const QueryArtistsEndpoint = '/artist/query'; export const QueryArtistsEndpoint = '/artist/query';
export interface QueryArtistsRequest { } export interface QueryArtistsRequest {
offset: Number,
limit: Number,
}
export interface QueryArtistsResponse { export interface QueryArtistsResponse {
ids: Number[] ids: Number[]
} }
export function checkQueryArtistsRequest(req: any): boolean { export function checkQueryArtistsRequest(req: any): boolean {
return true; return 'offset' in req
&& 'limit' in req;
} }
// Get artist details (GET). // Get artist details (GET).

@ -0,0 +1,88 @@
import React from 'react';
import {
TextField,
Paper,
Select,
MenuItem,
Typography
} from '@material-ui/core';
import {
TitleQuery,
ArtistQuery,
isTitleQuery,
isArtistQuery,
SongQuery
} from '../types/Query';
interface TitleFilterControlProps {
query: TitleQuery,
onChangeQuery: (q: SongQuery) => void,
}
function TitleFilterControl(props: TitleFilterControlProps) {
return <TextField
label="Title"
value={props.query.titleLike}
onChange={(i: any) => props.onChangeQuery({
titleLike: i.target.value
})}
/>
}
interface ArtistFilterControlProps {
query: ArtistQuery,
onChangeQuery: (q: SongQuery) => void,
}
function ArtistFilterControl(props: ArtistFilterControlProps) {
return <TextField
label="Name"
value={props.query.artistLike}
onChange={(i: any) => props.onChangeQuery({
artistLike: i.target.value
})}
/>
}
export interface IProps {
query: SongQuery,
onChangeQuery: (query: SongQuery) => void,
}
export default function FilterControl(props: IProps) {
const selectOptions: string[] = ['Title', 'Artist'];
const selectOption: string = (isTitleQuery(props.query) && 'Title') ||
(isArtistQuery(props.query) && 'Artist') ||
"Unknown";
const handleQueryOnChange = (event: any) => {
switch (event.target.value) {
case 'Title': {
props.onChangeQuery({
titleLike: ''
})
break;
}
case 'Artist': {
props.onChangeQuery({
artistLike: ''
})
break;
}
}
}
return <Paper>
<Select
value={selectOption}
onChange={handleQueryOnChange}
>
{selectOptions.map((option: string) => {
return <MenuItem value={option}>{option}</MenuItem>
})}
</Select>
{isTitleQuery(props.query) && <TitleFilterControl query={props.query} onChangeQuery={props.onChangeQuery} />}
{isArtistQuery(props.query) && <ArtistFilterControl query={props.query} onChangeQuery={props.onChangeQuery} />}
</Paper>;
}

@ -3,6 +3,7 @@ import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from '@material-ui/core/ListItemText';
import GroupIcon from '@material-ui/icons/Group'; import GroupIcon from '@material-ui/icons/Group';
import Chip from '@material-ui/core/Chip';
import { ArtistDisplayItem } from '../types/DisplayItem'; import { ArtistDisplayItem } from '../types/DisplayItem';
@ -19,6 +20,9 @@ export default function ItemListLoadedArtistItem(props: IProps) {
<ListItemText <ListItemText
primary={props.item.name} primary={props.item.name}
/> />
{props.item.tagNames.map((tag: any) => {
return <Chip label={tag}/>
})}
{props.item.storeLinks.map((link: any) => { {props.item.storeLinks.map((link: any) => {
return <a href={link.url} target="_blank"> return <a href={link.url} target="_blank">
<ListItemIcon> <ListItemIcon>

@ -3,6 +3,7 @@ import ListItem from '@material-ui/core/ListItem';
import ListItemIcon from '@material-ui/core/ListItemIcon'; import ListItemIcon from '@material-ui/core/ListItemIcon';
import ListItemText from '@material-ui/core/ListItemText'; import ListItemText from '@material-ui/core/ListItemText';
import MusicNoteIcon from '@material-ui/icons/MusicNote'; import MusicNoteIcon from '@material-ui/icons/MusicNote';
import Chip from '@material-ui/core/Chip';
import { SongDisplayItem } from '../types/DisplayItem'; import { SongDisplayItem } from '../types/DisplayItem';
@ -25,6 +26,9 @@ export default function ItemListLoadedSongItem(props: IProps) {
primary={props.item.title} primary={props.item.title}
secondary={artists} secondary={artists}
/> />
{props.item.tagNames.map((tag: any) => {
return <Chip label={tag}/>
})}
{props.item.storeLinks.map((link: any) => { {props.item.storeLinks.map((link: any) => {
return <a href={link.url} target="_blank"> return <a href={link.url} target="_blank">
<ListItemIcon> <ListItemIcon>

@ -1,6 +1,7 @@
export interface SongDisplayItem { export interface SongDisplayItem {
title:String, title:String,
artistNames:String[], artistNames:String[],
tagNames:String[],
storeLinks: { storeLinks: {
icon: JSX.Element, icon: JSX.Element,
url: String, url: String,
@ -13,6 +14,7 @@ export interface LoadingSongDisplayItem {
export interface ArtistDisplayItem { export interface ArtistDisplayItem {
name:String, name:String,
tagNames:String[],
storeLinks: { storeLinks: {
icon: JSX.Element, icon: JSX.Element,
url: String, url: String,

@ -0,0 +1,32 @@
import { SongQueryElemProperty, SongQueryFilterOp } from '../api';
export interface TitleQuery {
titleLike: String
};
export function isTitleQuery(q: SongQuery): q is TitleQuery {
return "titleLike" in q;
}
export function TitleToApiQuery(q: TitleQuery) {
return {
'prop': SongQueryElemProperty.title,
'propOperand': '%' + q.titleLike + '%',
'propOperator': SongQueryFilterOp.Like,
}
}
export interface ArtistQuery {
artistLike: String
};
export function isArtistQuery(q: SongQuery): q is ArtistQuery {
return "artistLike" in q;
}
export function ArtistToApiQuery(q: ArtistQuery) {
return {
}
}
export type SongQuery = TitleQuery | ArtistQuery;
export function toApiQuery(q: SongQuery) {
return (isTitleQuery(q) && TitleToApiQuery(q)) ||
(isArtistQuery(q) && ArtistToApiQuery(q)) || {};
}

@ -35,6 +35,12 @@ def transferLibrary(gpm_api, mudbase_api):
return []; return [];
artistStoreIds = [ [ getArtistStoreIds(song) for song in songs if song['artist'] == artist ][0] for artist in artists ] artistStoreIds = [ [ getArtistStoreIds(song) for song in songs if song['artist'] == artist ][0] for artist in artists ]
# Create GPM import tag
gpmTagIdResponse = requests.post(mudbase_api + '/tag', data = {
'name': 'GPM Import'
}).json()
print(f"Created tag \"GPM Import\", response: {gpmTagIdResponse}")
# Create genres and store their mudbase Ids # Create genres and store their mudbase Ids
genreRootResponse = requests.post(mudbase_api + '/tag', data = { genreRootResponse = requests.post(mudbase_api + '/tag', data = {
'name': 'Genre' 'name': 'Genre'
@ -52,9 +58,10 @@ def transferLibrary(gpm_api, mudbase_api):
# Create artists and store their mudbase Ids # Create artists and store their mudbase Ids
artistMudbaseIds = [] artistMudbaseIds = []
for idx,artist in enumerate(artists): for idx,artist in enumerate(artists):
response = requests.post(mudbase_api + '/artist', data = { response = requests.post(mudbase_api + '/artist', json = {
'name': artist, 'name': artist,
'storeLinks': [ 'https://play.google.com/music/m/' + id for id in artistStoreIds[idx] ] 'storeLinks': [ 'https://play.google.com/music/m/' + id for id in artistStoreIds[idx] ],
'tagIds': [ gpmTagIdResponse['id'] ]
}).json() }).json()
print(f"Created artist \"{artist}\", response: {response}") print(f"Created artist \"{artist}\", response: {response}")
artistMudbaseIds.append(response['id']) artistMudbaseIds.append(response['id'])
@ -70,7 +77,7 @@ def transferLibrary(gpm_api, mudbase_api):
response = requests.post(mudbase_api + '/song', json = { response = requests.post(mudbase_api + '/song', json = {
'title': song['title'], 'title': song['title'],
'artistIds': [ artistMudbaseId ], 'artistIds': [ artistMudbaseId ],
'tagIds' : [ genreMudbaseId ], 'tagIds' : [ genreMudbaseId, gpmTagIdResponse['id'] ],
'storeLinks': [ 'https://play.google.com/music/m/' + id for id in getSongStoreIds(song) ], 'storeLinks': [ 'https://play.google.com/music/m/' + id for id in getSongStoreIds(song) ],
}).json() }).json()
print(f"Created song \"{song['title']}\" with artist ID {artistMudbaseId}, response: {response}") print(f"Created song \"{song['title']}\" with artist ID {artistMudbaseId}, response: {response}")

@ -13,8 +13,12 @@ export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res
} }
const reqObject: api.CreateArtistRequest = req.body; const reqObject: api.CreateArtistRequest = req.body;
console.log("Create artist:", reqObject)
try {
// Start retrieving the tag instances to link the artist to. // Start retrieving the tag instances to link the artist to.
var tagInstancesPromise = reqObject.tagIds && models.Tag.findAll({ const tags = reqObject.tagIds && await models.Tag.findAll({
where: { where: {
id: { id: {
[Op.in]: reqObject.tagIds [Op.in]: reqObject.tagIds
@ -22,14 +26,11 @@ export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res
} }
}); });
// Upon finish retrieving artists and albums, create the artist and associate it. console.log("Found artist tags:", tags)
await Promise.all([tagInstancesPromise])
.then((values: any) => {
var [tags] = values;
if (reqObject.tagIds && tags.length !== reqObject.tagIds.length) { if (reqObject.tagIds && tags.length !== reqObject.tagIds.length) {
const e: EndpointError = { const e: EndpointError = {
internalMessage: 'Not all atags exist for CreateArtist request: ' + JSON.stringify(req.body), internalMessage: 'Not all tags exist for CreateArtist request: ' + JSON.stringify(req.body),
httpStatus: 400 httpStatus: 400
}; };
throw e; throw e;
@ -40,13 +41,12 @@ export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res
storeLinks: reqObject.storeLinks || [], storeLinks: reqObject.storeLinks || [],
}); });
tags && artist.addTags(tags); tags && artist.addTags(tags);
return artist.save(); await artist.save();
})
.then((artist: any) => {
const responseObject: api.CreateSongResponse = { const responseObject: api.CreateSongResponse = {
id: artist.id id: artist.id
}; };
res.status(200).send(responseObject); await res.status(200).send(responseObject);
}) } catch (e) {
.catch(catchUnhandledErrors); catchUnhandledErrors(e);
}
} }

@ -3,14 +3,19 @@ import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types'; import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
export const QueryArtistsEndpointHandler: EndpointHandler = async (req: any, res: any) => { export const QueryArtistsEndpointHandler: EndpointHandler = async (req: any, res: any) => {
if (!api.checkQueryArtistsRequest(req)) { if (!api.checkQueryArtistsRequest(req.body)) {
const e: EndpointError = { const e: EndpointError = {
internalMessage: 'Invalid QueryArtists request: ' + JSON.stringify(req.body), internalMessage: 'Invalid QueryArtists request: ' + JSON.stringify(req.body),
httpStatus: 400 httpStatus: 400
}; };
throw e; throw e;
} }
await models.Artist.findAll() const reqObject: api.QueryArtistsRequest = req.body;
await models.Artist.findAll({
offset: reqObject.offset,
limit: reqObject.limit,
})
.then((artists: any[]) => { .then((artists: any[]) => {
const response: api.QueryArtistsResponse = { const response: api.QueryArtistsResponse = {
ids: artists.map((artist: any) => { ids: artists.map((artist: any) => {

@ -8,11 +8,13 @@ const sequelizeOps: any = {
[api.SongQueryFilterOp.Ne]: Op.ne, [api.SongQueryFilterOp.Ne]: Op.ne,
[api.SongQueryFilterOp.In]: Op.in, [api.SongQueryFilterOp.In]: Op.in,
[api.SongQueryFilterOp.NotIn]: Op.notIn, [api.SongQueryFilterOp.NotIn]: Op.notIn,
[api.SongQueryFilterOp.Like]: Op.like,
[api.SongQueryElemOp.And]: Op.and, [api.SongQueryElemOp.And]: Op.and,
[api.SongQueryElemOp.Or]: Op.or, [api.SongQueryElemOp.Or]: Op.or,
}; };
const sequelizeProps: any = { const sequelizeProps: any = {
[api.SongQueryElemProperty.title]: "title",
[api.SongQueryElemProperty.id]: "id", [api.SongQueryElemProperty.id]: "id",
[api.SongQueryElemProperty.artistIds]: "$Artists.id$", [api.SongQueryElemProperty.artistIds]: "$Artists.id$",
[api.SongQueryElemProperty.albumIds]: "$Albums.id$", [api.SongQueryElemProperty.albumIds]: "$Albums.id$",
@ -45,7 +47,7 @@ const getSequelizeWhere = (queryElem: api.SongQueryElem) => {
} }
export const QuerySongsEndpointHandler: EndpointHandler = async (req: any, res: any) => { export const QuerySongsEndpointHandler: EndpointHandler = async (req: any, res: any) => {
if (!api.checkQuerySongsRequest(req)) { if (!api.checkQuerySongsRequest(req.body)) {
const e: EndpointError = { const e: EndpointError = {
internalMessage: 'Invalid QuerySongs request: ' + JSON.stringify(req.body), internalMessage: 'Invalid QuerySongs request: ' + JSON.stringify(req.body),
httpStatus: 400 httpStatus: 400
@ -54,17 +56,39 @@ export const QuerySongsEndpointHandler: EndpointHandler = async (req: any, res:
} }
const reqObject: api.QuerySongsRequest = req.body; const reqObject: api.QuerySongsRequest = req.body;
await models.Song.findAll({ try {
const songs = await models.Song.findAll({
where: getSequelizeWhere(reqObject.query), where: getSequelizeWhere(reqObject.query),
include: [models.Artist, models.Album] include: [models.Artist, models.Album, models.Tag],
limit: reqObject.limit,
offset: reqObject.offset,
}) })
.then((songs: any[]) => {
const response: api.QuerySongsResponse = { const response: api.QuerySongsResponse = {
ids: songs.map((song: any) => { songs: await Promise.all(songs.map(async (song: any) => {
return song.id; console.log("Song:", song, "artists:", song.getArtists());
const artists = await song.getArtists();
const tags = await song.getTags();
return <api.SongDetails>{
id: song.id,
title: song.title,
artists: artists.map((artist: any) => {
return <api.ArtistDetails>{
id: artist.id,
name: artist.name,
}
}),
tags: tags.map((tag: any) => {
return <api.TagDetails>{
id: tag.id,
name: tag.name,
}
}) })
}; };
}))
};
res.send(response); res.send(response);
}) } catch (e) {
.catch(catchUnhandledErrors); catchUnhandledErrors(e);
}
} }
Loading…
Cancel
Save