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.
116 lines
4.0 KiB
116 lines
4.0 KiB
import { AlbumWithRefsWithId, ArtistWithRefsWithId, DBDataFormat, 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) => ensureInSet(toId, |
|
(data[fromObjectsType][fromId] 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 |
|
); |
|
} |
|
|
|
// 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); |
|
} |