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.
 
 
 
 

204 lines
7.7 KiB

import Knex from "knex";
import { TrackWithRefsWithId, AlbumWithRefsWithId, ArtistWithRefsWithId, TagWithRefsWithId, TrackWithRefs } from "../../client/src/api/api";
import * as api from '../../client/src/api/api';
import asJson from "../lib/asJson";
import { createArtist } from "./Artist";
import { createTag } from "./Tag";
import { createAlbum } from "./Album";
import { createTrack } from "./Track";
// This interface describes a JSON format in which the "interesting part"
// of the entire database for a user can be imported/exported.
// Worth noting is that the IDs used in this format only exist for cross-
// referencing between objects. They do not correspond to IDs in the actual
// database.
// Upon import, they might be replaced, and upon export, they might be randomly
// generated.
interface DBImportExportFormat {
tracks: TrackWithRefsWithId[],
albums: AlbumWithRefsWithId[],
artists: ArtistWithRefsWithId[],
tags: TagWithRefsWithId[],
}
export async function exportDB(userId: number, knex: Knex): Promise<DBImportExportFormat> {
// First, retrieve all the objects without taking linking tables into account.
// Fetch the links separately.
let tracksPromise: Promise<api.TrackWithRefsWithId[]> =
knex.select('id', 'name', 'storeLinks', 'albumId')
.from('tracks')
.where({ 'user': userId })
.then((ts: any[]) => ts.map((t: any) => {
return {
mbApi_typename: 'track',
name: t.name,
id: t.id,
storeLinks: asJson(t.storeLinks),
albumId: t.albumId,
artistIds: [],
tagIds: [],
}
}));
let albumsPromise: Promise<api.AlbumWithRefsWithId[]> =
knex.select('name', 'storeLinks', 'id')
.from('albums')
.where({ 'user': userId })
.then((ts: any[]) => ts.map((t: any) => {
return {
mbApi_typename: 'album',
id: t.id,
name: t.name,
storeLinks: asJson(t.storeLinks),
artistIds: [],
tagIds: [],
trackIds: [],
}
}));
let artistsPromise: Promise<api.ArtistWithRefsWithId[]> =
knex.select('name', 'storeLinks', 'id')
.from('artists')
.where({ 'user': userId })
.then((ts: any[]) => ts.map((t: any) => {
return {
mbApi_typename: 'artist',
id: t.id,
name: t.name,
storeLinks: asJson(t.storeLinks),
albumIds: [],
tagIds: [],
trackIds: [],
}
}));
let tagsPromise: Promise<api.TagWithRefsWithId[]> =
knex.select('name', 'parentId', 'id')
.from('tags')
.where({ 'user': userId })
.then((ts: any[]) => ts.map((t: any) => {
return {
mbApi_typename: 'tag',
id: t.id,
name: t.name,
parentId: t.parentId,
}
}));
let tracksArtistsPromise: Promise<[number, number][]> =
knex.select('trackId', 'artistId')
.from('tracks_artists')
.then((rs: any) => rs.map((r: any) => [r.trackId, r.artistId]));
let tracksTagsPromise: Promise<[number, number][]> =
knex.select('trackId', 'tagId')
.from('tracks_tags')
.then((rs: any) => rs.map((r: any) => [r.trackId, r.tagId]));
let artistsTagsPromise: Promise<[number, number][]> =
knex.select('artistId', 'tagId')
.from('artists_tags')
.then((rs: any) => rs.map((r: any) => [r.artistId, r.tagId]));
let albumsTagsPromise: Promise<[number, number][]> =
knex.select('albumId', 'tagId')
.from('albums_tags')
.then((rs: any) => rs.map((r: any) => [r.albumId, r.tagId]));
let artistsAlbumsPromise: Promise<[number, number][]> =
knex.select('albumId', 'artistId')
.from('artists_albums')
.then((rs: any) => rs.map((r: any) => [r.albumId, r.artistId]));
let [
tracks,
albums,
artists,
tags,
tracksArtists,
tracksTags,
artistsTags,
albumsTags,
artistsAlbums,
] = await Promise.all([
tracksPromise,
albumsPromise,
artistsPromise,
tagsPromise,
tracksArtistsPromise,
tracksTagsPromise,
artistsTagsPromise,
albumsTagsPromise,
artistsAlbumsPromise,
]);
// Now store the links inside the resource objects.
tracksArtists.forEach((v: [number, number]) => {
let [trackId, artistId] = v;
tracks.find((t: TrackWithRefsWithId) => t.id === trackId)?.artistIds.push(artistId);
})
tracksTags.forEach((v: [number, number]) => {
let [trackId, tagId] = v;
tracks.find((t: TrackWithRefsWithId) => t.id === trackId)?.tagIds.push(tagId);
})
artistsTags.forEach((v: [number, number]) => {
let [artistId, tagId] = v;
artists.find((t: ArtistWithRefsWithId) => t.id === artistId)?.tagIds.push(tagId);
})
albumsTags.forEach((v: [number, number]) => {
let [albumId, tagId] = v;
albums.find((t: AlbumWithRefsWithId) => t.id === albumId)?.tagIds.push(tagId);
})
artistsAlbums.forEach((v: [number, number]) => {
let [artistId, albumId] = v;
artists.find((t: ArtistWithRefsWithId) => t.id === artistId)?.albumIds.push(albumId);
albums.find((t: AlbumWithRefsWithId) => t.id === albumId)?.artistIds.push(artistId);
})
return {
tracks: tracks,
albums: albums,
artists: artists,
tags: tags,
}
}
export async function importDB(userId: number, db: DBImportExportFormat, knex: Knex): Promise<void> {
return await knex.transaction(async (trx) => {
// Store the ID mappings in this record.
let tagIdMaps: Record<number, number> = {};
let artistIdMaps: Record<number, number> = {};
let albumIdMaps: Record<number, number> = {};
let trackIdMaps: Record<number, number> = {};
try {
// Insert items one by one, remapping the IDs as we go.
await Promise.all(db.tags.map((tag: TagWithRefsWithId) => async () => {
tagIdMaps[tag.id] = await createTag(userId, tag, knex);
}));
await Promise.all(db.artists.map((artist: ArtistWithRefsWithId) => async () => {
artistIdMaps[artist.id] = await createArtist(userId, {
...artist,
tagIds: artist.tagIds.map((id: number) => tagIdMaps[id]),
}, knex);
}))
await Promise.all(db.albums.map((album: AlbumWithRefsWithId) => async () => {
albumIdMaps[album.id] = await createAlbum(userId, {
...album,
tagIds: album.tagIds.map((id: number) => tagIdMaps[id]),
artistIds: album.artistIds.map((id: number) => artistIdMaps[id]),
}, knex);
}))
await Promise.all(db.tracks.map((track: TrackWithRefsWithId) => async () => {
trackIdMaps[track.id] = await createTrack(userId, {
...track,
tagIds: track.tagIds.map((id: number) => tagIdMaps[id]),
artistIds: track.artistIds.map((id: number) => artistIdMaps[id]),
albumId: track.albumId ? albumIdMaps[track.albumId] : null,
}, knex);
}))
} catch (e) {
trx.rollback();
}
});
}