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
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(); |
|
} |
|
}); |
|
} |