You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

376 lines
14 KiB

import Knex from "knex";
import { Artist, ArtistDetails, Tag, Track, TagParentId, Id, Name, StoreLinks, Album, ArtistRefs } from "../../client/src/api/api";
import * as api from '../../client/src/api/api';
import asJson from "../lib/asJson";
import { DBError, DBErrorKind } from "../endpoints/types";
import { makeNotFoundError } from "./common";
var _ = require('lodash')
// Returns an artist with details, or null if not found.
export async function getArtist(id: number, userId: number, knex: Knex):
Promise<(Artist & ArtistDetails & Name & StoreLinks)> {
// Start transfers for tags and albums.
// Also request the artist itself.
const tagsPromise: Promise<(Tag & Name & Id & TagParentId)[]> =
knex.select('tagId')
.from('artists_tags')
.where({ 'artistId': id })
.then((tags: any) => tags.map((tag: any) => tag['tagId']))
.then((ids: number[]) =>
knex.select(['id', 'name', 'parentId'])
.from('tags')
.whereIn('id', ids)
.then((tags: (Id & Name & TagParentId)[]) =>
tags.map((tag : (Id & Name & TagParentId)) =>
{ return {...tag, mbApi_typename: "tag"}}
))
);
const albumsPromise: Promise<(Album & Name & Id & StoreLinks)[]> =
knex.select('albumId')
.from('artists_albums')
.where({ 'artistId': id })
.then((albums: any) => albums.map((album: any) => album['albumId']))
.then((ids: number[]) =>
knex.select(['id', 'name', 'storeLinks'])
.from('albums')
.whereIn('id', ids)
.then((albums: (Id & Name & StoreLinks)[]) =>
albums.map((tag : (Id & Name & StoreLinks)) =>
{ return {...tag, mbApi_typename: "album"}}
))
);
const tracksPromise: Promise<(Track & Id & Name & StoreLinks)[]> =
knex.select('trackId')
.from('tracks_artists')
.where({ 'artistId': id })
.then((tracks: any) => tracks.map((track: any) => track['trackId']))
.then((ids: number[]) =>
knex.select(['id', 'name', 'storeLinks'])
.from('tracks')
.whereIn('id', ids)
.then((tracks: (Id & Name & StoreLinks)[]) =>
tracks.map((tag : (Id & Name & StoreLinks)) =>
{ return {...tag, mbApi_typename: "track"}}
))
);
const artistPromise: Promise<(Artist & Name & StoreLinks) | undefined> =
knex.select('name', 'storeLinks')
.from('artists')
.where({ 'user': userId })
.where({ id: id })
.then((artists: any) => { return { ...artists[0], mbApi_typename: 'artist' } });
// Wait for the requests to finish.
const [artist, tags, albums, tracks] =
await Promise.all([artistPromise, tagsPromise, albumsPromise, tracksPromise]);
if (artist && artist['name']) {
return {
mbApi_typename: 'artist',
name: artist['name'],
albums: albums,
tags: tags,
tracks: tracks,
storeLinks: asJson(artist['storeLinks'] || []),
};
}
throw makeNotFoundError();
}
// Returns the id of the created artist.
export async function createArtist(userId: number, artist: (Artist & ArtistRefs & Name), knex: Knex): Promise<number> {
return await knex.transaction(async (trx) => {
// Start retrieving albums.
const albumIdsPromise: Promise<number[]> =
trx.select('id')
.from('albums')
.where({ 'user': userId })
.whereIn('id', artist.albumIds || [])
.then((as: any) => as.map((a: any) => a['id']));
// Start retrieving tracks.
const trackIdsPromise: Promise<number[]> =
trx.select('id')
.from('tracks')
.where({ 'user': userId })
.whereIn('id', artist.trackIds || [])
.then((as: any) => as.map((a: any) => a['id']));
// Start retrieving tags.
const tagIdsPromise: Promise<number[]> =
trx.select('id')
.from('tags')
.where({ 'user': userId })
.whereIn('id', artist.tagIds || [])
.then((as: any) => as.map((a: any) => a['id']));
// Wait for the requests to finish.
var [albums, tags, tracks] = await Promise.all([albumIdsPromise, tagIdsPromise, trackIdsPromise]);;
// Check that we found all artists and tags we need.
if (!_.isEqual(albums.sort(), (artist.albumIds || []).sort()) ||
!_.isEqual(tags.sort(), (artist.tagIds || []).sort()) ||
!_.isEqual(tracks.sort(), (artist.trackIds || []).sort())) {
throw makeNotFoundError();
}
// Create the artist.
const artistId = (await trx('artists')
.insert({
name: artist.name,
storeLinks: JSON.stringify(artist.storeLinks || []),
user: userId,
})
.returning('id') // Needed for Postgres
)[0];
// Link the albums via the linking table.
if (albums && albums.length) {
await trx('artists_albums').insert(
albums.map((albumId: number) => {
return {
albumId: albumId,
artistId: artistId,
}
})
)
}
// Link the tracks via the linking table.
if (tracks && tracks.length) {
await trx('tracks_artists').insert(
tracks.map((trackId: number) => {
return {
trackId: trackId,
artistId: artistId,
}
})
)
}
// Link the tags via the linking table.
if (tags && tags.length) {
await trx('artists_tags').insert(
tags.map((tagId: number) => {
return {
artistId: artistId,
tagId: tagId,
}
})
)
}
console.log('created artist', artist, ', ID ', artistId);
return artistId;
})
}
export async function modifyArtist(userId: number, artistId: number, artist: Artist, knex: Knex): Promise<void> {
await knex.transaction(async (trx) => {
// Start retrieving the artist itself.
const artistIdPromise: Promise<number | undefined | null> =
trx.select('id')
.from('artists')
.where({ 'user': userId })
.where({ id: artistId })
.then((r: any) => (r && r[0]) ? r[0]['id'] : null);
// Start retrieving albums if we are modifying those.
const albumIdsPromise: Promise<number[] | undefined | null> =
artist.albumIds ?
trx.select('id')
.from('albums')
.whereIn('id', artist.albumIds)
.then((as: any) => as.map((a: any) => a['id']))
: (async () => null)();
// Start retrieving tracks if we are modifying those.
const trackIdsPromise: Promise<number[] | undefined> =
artist.trackIds ?
trx.select('id')
.from('tracks')
.whereIn('id', artist.trackIds)
.then((as: any) => as.map((a: any) => a['id']))
: (async () => null)();
// Start retrieving tags if we are modifying those.
const tagIdsPromise =
artist.tagIds ?
trx.select('id')
.from('tags')
.whereIn('id', artist.tagIds)
.then((ts: any) => ts.map((t: any) => t['id'])) :
(async () => null)();
// Wait for the requests to finish.
var [oldArtist, albums, tags, tracks] = await Promise.all([artistIdPromise, albumIdsPromise, tagIdsPromise, trackIdsPromise]);;
// Check that we found all objects we need.
if ((albums === undefined || !_.isEqual((albums || []).sort(), (artist.albumIds || []).sort())) ||
(tags === undefined || !_.isEqual((tags || []).sort(), (artist.tagIds || []).sort())) ||
(tracks === undefined || !_.isEqual((tracks || []).sort(), (artist.trackIds || []).sort())) ||
!oldArtist) {
throw makeNotFoundError();
}
// Modify the artist.
var update: any = {};
if ("name" in artist) { update["name"] = artist.name; }
if ("storeLinks" in artist) { update["storeLinks"] = JSON.stringify(artist.storeLinks || []); }
const modifyArtistPromise = trx('artists')
.where({ 'user': userId })
.where({ 'id': artistId })
.update(update)
// Remove unlinked albums.
const removeUnlinkedAlbums = albums ? trx('artists_albums')
.where({ 'artistId': artistId })
.whereNotIn('albumId', artist.albumIds || [])
.delete() : undefined;
// Remove unlinked tracks.
const removeUnlinkedTracks = tracks ? trx('tracks_artists')
.where({ 'artistId': artistId })
.whereNotIn('trackId', artist.trackIds || [])
.delete() : undefined;
// Remove unlinked tags.
const removeUnlinkedTags = tags ? trx('artists_tags')
.where({ 'artistId': artistId })
.whereNotIn('tagId', artist.tagIds || [])
.delete() : undefined;
// Link new albums.
const addAlbums = albums ? trx('artists_albums')
.where({ 'artistId': artistId })
.then((as: any) => as.map((a: any) => a['albumId']))
.then((doneAlbumIds: number[]) => {
// Get the set of artists that are not yet linked
const toLink = (albums || []).filter((id: number) => {
return !doneAlbumIds.includes(id);
});
const insertObjects = toLink.map((albumId: number) => {
return {
artistId: artistId,
albumId: albumId,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('artists_albums').insert(obj)
)
);
}) : undefined;
// Link new tracks.
const addTracks = tracks ? trx('tracks_artists')
.where({ 'artistId': artistId })
.then((as: any) => as.map((a: any) => a['trackId']))
.then((doneTrackIds: number[]) => {
// Get the set of artists that are not yet linked
const toLink = (tracks || []).filter((id: number) => {
return !doneTrackIds.includes(id);
});
const insertObjects = toLink.map((trackId: number) => {
return {
artistId: artistId,
trackId: trackId,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('tracks_artists').insert(obj)
)
);
}) : undefined;
// Link new tags.
const addTags = tags ? trx('artists_tags')
.where({ 'artistId': artistId })
.then((ts: any) => ts.map((t: any) => t['tagId']))
.then((doneTagIds: number[]) => {
// Get the set of tags that are not yet linked
const toLink = tags.filter((id: number) => {
return !doneTagIds.includes(id);
});
const insertObjects = toLink.map((tagId: number) => {
return {
tagId: tagId,
artistId: artistId,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('artists_tags').insert(obj)
)
);
}) : undefined;
// Wait for all operations to finish.
await Promise.all([
modifyArtistPromise,
removeUnlinkedAlbums,
removeUnlinkedTags,
removeUnlinkedTracks,
addAlbums,
addTags,
addTracks,
]);
return;
})
}
export async function deleteArtist(userId: number, artistId: number, knex: Knex): Promise<void> {
await knex.transaction(async (trx) => {
// Start by retrieving the artist itself for sanity.
const confirmArtistId: number | undefined =
await trx.select('id')
.from('artists')
.where({ 'user': userId })
.where({ id: artistId })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined);
if (!confirmArtistId) {
throw makeNotFoundError();
}
// Start deleting artist associations with the artist.
const deleteAlbumsPromise: Promise<any> =
trx.delete()
.from('artists_albums')
.where({ 'artistId': artistId });
// Start deleting tag associations with the artist.
const deleteTagsPromise: Promise<any> =
trx.delete()
.from('artists_tags')
.where({ 'artistId': artistId });
// Start deleting track associations with the artist.
const deleteTracksPromise: Promise<any> =
trx.delete()
.from('tracks_artists')
.where({ 'artistId': artistId });
// Start deleting the artist.
const deleteArtistPromise: Promise<any> =
trx.delete()
.from('artists')
.where({ id: artistId });
// Wait for the requests to finish.
await Promise.all([deleteAlbumsPromise, deleteTagsPromise, deleteTracksPromise, deleteArtistPromise]);
})
}