import * as api from '../../client/src/api'; import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types'; import Knex from 'knex'; export const PostTag: EndpointHandler = async (req: any, res: any, knex: Knex) => { if (!api.checkCreateTagRequest(req)) { const e: EndpointError = { internalMessage: 'Invalid PostTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } const reqObject: api.CreateTagRequest = req.body; const { id: userId } = req.user; console.log("User ", userId, ": Post Tag ", reqObject); await knex.transaction(async (trx) => { try { // If applicable, retrieve the parent tag. const maybeParent: number | undefined = reqObject.parentId ? (await trx.select('id') .from('tags') .where({ 'user': userId }) .where({ 'id': reqObject.parentId }))[0]['id'] : undefined; // Check if the parent was found, if applicable. if (reqObject.parentId && maybeParent !== reqObject.parentId) { const e: EndpointError = { internalMessage: 'Could not find parent tag for CreateTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } // Create the new tag. var tag: any = { name: reqObject.name, user: userId, }; if (maybeParent) { tag['parentId'] = maybeParent; } const tagId = (await trx('tags') .insert(tag) .returning('id') // Needed for Postgres )[0]; // Respond to the request. const responseObject: api.CreateTagResponse = { id: tagId }; res.status(200).send(responseObject); } catch (e) { catchUnhandledErrors(e); trx.rollback(); } }) } async function getChildrenRecursive(id: number, userId: number, trx: any) { const directChildren = (await trx.select('id') .from('tags') .where({ 'user': userId }) .where({ 'parentId': id })).map((r: any) => r.id); const indirectChildrenPromises = directChildren.map( (child: number) => getChildrenRecursive(child, userId, trx) ); const indirectChildrenNested = await Promise.all(indirectChildrenPromises); const indirectChildren = indirectChildrenNested.flat(); return [ ...directChildren, ...indirectChildren, ] } export const DeleteTag: EndpointHandler = async (req: any, res: any, knex: Knex) => { if (!api.checkDeleteTagRequest(req)) { const e: EndpointError = { internalMessage: 'Invalid DeleteTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } const reqObject: api.DeleteTagRequest = req.body; const { id: userId } = req.user; console.log("User ", userId, ": Delete Tag ", reqObject); await knex.transaction(async (trx) => { try { // Start retrieving any child tags. const childTagsPromise = getChildrenRecursive(req.params.id, userId, trx); // Start retrieving the tag itself. const tagPromise = trx.select('id') .from('tags') .where({ 'user': userId }) .where({ id: req.params.id }) .then((r: any) => (r && r[0]) ? r[0]['id'] : undefined) // Wait for the requests to finish. var [tag, children] = await Promise.all([tagPromise, childTagsPromise]); // Merge all IDs. const toDelete = [ tag, ...children ]; // Check that we found all objects we need. if (!tag) { const e: EndpointError = { internalMessage: 'Tag or parent does not exist for DeleteTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } // Delete the tag and its children. await trx('tags') .where({ 'user': userId }) .whereIn('id', toDelete) .del(); // Respond to the request. res.status(200).send(); } catch (e) { catchUnhandledErrors(e); trx.rollback(); } }) } export const GetTag: EndpointHandler = async (req: any, res: any, knex: Knex) => { if (!api.checkTagDetailsRequest(req)) { const e: EndpointError = { internalMessage: 'Invalid GetTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } const { id: userId } = req.user; try { const results = await knex.select(['id', 'name', 'parentId']) .from('tags') .where({ 'user': userId }) .where({ 'id': req.params.id }); if (results[0]) { const response: api.TagDetailsResponse = { name: results[0].name, parentId: results[0].parentId || undefined, } await res.send(response); } else { await res.status(404).send({}); } } catch (e) { catchUnhandledErrors(e) } } export const PutTag: EndpointHandler = async (req: any, res: any, knex: Knex) => { if (!api.checkModifyTagRequest(req)) { const e: EndpointError = { internalMessage: 'Invalid PutTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } const reqObject: api.ModifyTagRequest = req.body; const { id: userId } = req.user; console.log("User ", userId, ": Put Tag ", reqObject); await knex.transaction(async (trx) => { try { // Start retrieving the parent tag. const parentTagPromise = reqObject.parentId ? trx.select('id') .from('tags') .where({ 'user': userId }) .where({ 'id': reqObject.parentId }) .then((ts: any) => ts.map((t: any) => t['tagId'])) : (async () => { return [] })(); // Start retrieving the tag itself. const tagPromise = trx.select('id') .from('tags') .where({ 'user': userId }) .where({ id: req.params.id }) .then((r: any) => (r && r[0]) ? r[0]['id'] : undefined) // Wait for the requests to finish. var [tag, parent] = await Promise.all([tagPromise, parentTagPromise]);; // Check that we found all objects we need. if ((reqObject.parentId && !parent) || !tag) { const e: EndpointError = { internalMessage: 'Tag or parent does not exist for ModifyTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } // Modify the tag. await trx('tags') .where({ 'user': userId }) .where({ 'id': req.params.id }) .update({ name: reqObject.name, parentId: reqObject.parentId || null, }) // Respond to the request. res.status(200).send(); } catch (e) { catchUnhandledErrors(e); trx.rollback(); } }) } export const MergeTag: EndpointHandler = async (req: any, res: any, knex: Knex) => { if (!api.checkMergeTagRequest(req)) { const e: EndpointError = { internalMessage: 'Invalid MergeTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } const reqObject: api.DeleteTagRequest = req.body; const { id: userId } = req.user; console.log("User ", userId, ": Merge Tag ", reqObject); const fromId = req.params.id; const toId = req.params.toId; await knex.transaction(async (trx) => { try { // Start retrieving the "from" tag. const fromTagPromise = trx.select('id') .from('tags') .where({ 'user': userId }) .where({ id: fromId }) .then((r: any) => (r && r[0]) ? r[0]['id'] : undefined) // Start retrieving the "to" tag. const toTagPromise = trx.select('id') .from('tags') .where({ 'user': userId }) .where({ id: toId }) .then((r: any) => (r && r[0]) ? r[0]['id'] : undefined) // Wait for the requests to finish. var [fromTag, toTag] = await Promise.all([fromTagPromise, toTagPromise]); // Check that we found all objects we need. if (!fromTag || !toTag) { const e: EndpointError = { internalMessage: 'Source or target tag does not exist for MergeTag request: ' + JSON.stringify(req.body), httpStatus: 400 }; throw e; } // Assign new tag ID to any objects referencing the to-be-merged tag. const cPromise = trx('tags') .where({ 'user': userId }) .where({ 'parentId': fromId }) .update({ 'parentId': toId }); const sPromise = trx('songs_tags') .where({ 'tagId': fromId }) .update({ 'tagId': toId }); const arPromise = trx('artists_tags') .where({ 'tagId': fromId }) .update({ 'tagId': toId }); const alPromise = trx('albums_tags') .where({ 'tagId': fromId }) .update({ 'tagId': toId }); await Promise.all([sPromise, arPromise, alPromise, cPromise]); // Delete the original tag. await trx('tags') .where({ 'user': userId }) .where({ 'id': fromId }) .del(); // Respond to the request. res.status(200).send(); } catch (e) { catchUnhandledErrors(e); trx.rollback(); } }) }