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.
 
 
 
 

162 lines
5.3 KiB

import { AlbumWithRefsWithId, ArtistWithRefsWithId, DBDataFormat, PostAlbumRequest, PostArtistRequest, PostTagRequest, PostTrackRequest, TrackWithDetails, TrackWithRefsWithId } from "../../../client/src/api/api";
import { makeNotFoundError } from "../../db/common";
import filterInPlace from "../../lib/filterInPlace";
// The mock reference database is in the same format as
// the JSON import/export format, for multiple users.
export type ReferenceDatabase = Record<number, DBDataFormat>
type ObjectsType = "tracks" | "artists" | "tags" | "albums";
// Get a fresh ID for a new object.
function getNewId(db: ReferenceDatabase, objectsType: ObjectsType): number {
let highest: number = 1;
for (const data of Object.values(db)) {
data[objectsType].forEach((obj: any) => highest = Math.max(highest, obj.id));
}
return highest + 1;
}
// Check a (set of) IDs for presence in the objects array.
// All have to exist for it to return true.
function checkExists(objects: any[], ids: number[]) {
return ids.reduce((prev: boolean, id: number) => {
return prev && objects.find((object: any) => object.id === id);
}, true);
}
// If not in the array, put the number in the array.
function ensureInSet(n: number, s: number[]) {
if (!(n in s)) { s.push(n); }
}
// For a set of objects, ensure they point to another object.
function ensureLinked(fromObjects: number[], fromObjectsType: ObjectsType,
toId: number, toObjectsType: ObjectsType, data: DBDataFormat) {
if (toObjectsType === 'tracks') {
fromObjects.forEach((fromId: number) => {
let fromObject = (data[fromObjectsType] as any).find((o: any) => o.id === fromId);
ensureInSet(toId, (fromObject as AlbumWithRefsWithId | ArtistWithRefsWithId).trackIds)
})
}
}
// Create a new object.
export interface LinkField { field: string, otherObjectType: ObjectsType };
export function createObject(
userId: number,
object: any,
objectType: ObjectsType,
singularLinkFields: LinkField[],
pluralLinkFields: LinkField[],
db: ReferenceDatabase
): { id: number } {
// Existence checks
if (!(userId in db)) { throw makeNotFoundError() }
singularLinkFields.forEach((f: LinkField) => {
if (!checkExists(db[userId][f.otherObjectType], object[f.field] ? [object[f.field]] : [])) {
throw makeNotFoundError();
}
});
pluralLinkFields.forEach((f: LinkField) => {
if (!checkExists(db[userId][f.otherObjectType], object[f.field] || [])) {
throw makeNotFoundError();
}
});
// Create an ID and the object
let id = getNewId(db, objectType);
db[userId][objectType].push({
...object,
id: id,
})
// reverse links
singularLinkFields.forEach((f: LinkField) => {
ensureLinked(object[f.field] ? [object[f.field]] : [], f.otherObjectType, id, objectType, db[userId]);
});
pluralLinkFields.forEach((f: LinkField) => {
ensureLinked(object[f.field] || [], f.otherObjectType, id, objectType, db[userId]);
});
return { id: id };
}
// Create a new track.
export function createTrack(userId: number, track: PostTrackRequest, db: ReferenceDatabase): { id: number } {
return createObject(
userId,
track,
'tracks',
[{ field: 'albumId', otherObjectType: 'albums' }],
[
{ field: 'artistIds', otherObjectType: 'artists' },
{ field: 'tagIds', otherObjectType: 'tags' },
],
db
);
}
// Create a new album.
export function createAlbum(userId: number, album: PostAlbumRequest, db: ReferenceDatabase): { id: number } {
return createObject(
userId,
album,
'albums',
[],
[
{ field: 'artistIds', otherObjectType: 'artists' },
{ field: 'trackIds', otherObjectType: 'tracks' },
{ field: 'tagIds', otherObjectType: 'tags' },
],
db
);
}
// Create a new artist.
export function createArtist(userId: number, artist: PostArtistRequest, db: ReferenceDatabase): { id: number } {
return createObject(
userId,
artist,
'artists',
[],
[
{ field: 'albumIds', otherObjectType: 'albums' },
{ field: 'trackIds', otherObjectType: 'tracks' },
{ field: 'tagIds', otherObjectType: 'tags' },
],
db
);
}
// Create a new tag.
export function createTag(userId: number, tag: PostTagRequest, db: ReferenceDatabase): { id: number } {
return createObject(
userId,
tag,
'tags',
[{ field: 'parentId', otherObjectType: 'tags' }],
[],
db
);
}
// Delete a track.
export function deleteTrack(userId: number, id: number, db: ReferenceDatabase): void {
// Existence checks
if (!(userId in db)) { throw makeNotFoundError() }
// Find the object to delete.
let idx = db[userId].tracks.findIndex((track: TrackWithRefsWithId) => track.id === id);
if (idx < 0) {
// Not found
throw makeNotFoundError();
}
// Remove references
db[userId].albums.forEach((x: AlbumWithRefsWithId) => { filterInPlace(x.trackIds, (tid: number) => tid !== id); })
db[userId].artists.forEach((x: ArtistWithRefsWithId) => { filterInPlace(x.trackIds, (tid: number) => tid !== id); })
// Delete the object
db[userId].tracks.splice(idx, 1);
}