Got all APIs working in Knex, except query.

pull/10/head
Sander Vocke 5 years ago
parent 5dcc8451bf
commit ddbfb3a52c
  1. 118
      client/src/api.ts
  2. 2
      server/.gitignore
  3. 37
      server/app.ts
  4. 20
      server/config/config.json
  5. 61
      server/endpoints/AlbumDetailsEndpointHandler.ts
  6. 38
      server/endpoints/ArtistDetailsEndpointHandler.ts
  7. 98
      server/endpoints/CreateAlbumEndpointHandler.ts
  8. 78
      server/endpoints/CreateArtistEndpointHandler.ts
  9. 130
      server/endpoints/CreateSongEndpointHandler.ts
  10. 70
      server/endpoints/CreateTagEndpointHandler.ts
  11. 164
      server/endpoints/ModifyAlbumEndpointHandler.ts
  12. 105
      server/endpoints/ModifyArtistEndpointHandler.ts
  13. 217
      server/endpoints/ModifySongEndpointHandler.ts
  14. 85
      server/endpoints/ModifyTagEndpointHandler.ts
  15. 324
      server/endpoints/QueryEndpointHandler.ts
  16. 63
      server/endpoints/SongDetailsEndpointHandler.ts
  17. 43
      server/endpoints/TagDetailsEndpointHandler.ts
  18. 4
      server/endpoints/types.ts
  19. 3
      server/knex/knex.ts
  20. 44
      server/knexfile.ts
  21. 118
      server/migrations/20200828124218_init_db.ts
  22. 14
      server/models/album.js
  23. 14
      server/models/artist.js
  24. 37
      server/models/index.js
  25. 13
      server/models/ranking.js
  26. 15
      server/models/song.js
  27. 14
      server/models/tag.js
  28. 3
      server/package.json
  29. 15
      server/server.ts
  30. 4
      server/test/integration/flows/AlbumFlow.js
  31. 27
      server/test/integration/flows/ArtistFlow.js
  32. 4
      server/test/integration/flows/QueryFlow.js
  33. 4
      server/test/integration/flows/SongFlow.js
  34. 4
      server/test/integration/flows/TagFlow.js
  35. 6
      server/test/integration/flows/helpers.js
  36. 1326
      server/yarn.lock

@ -15,38 +15,38 @@ export enum ItemType {
}
export interface ArtistDetails {
artistId: Number,
name: String,
storeLinks?: String[],
artistId: number,
name: string,
storeLinks?: string[],
}
export function isArtistDetails(q: any): q is ArtistDetails {
return 'artistId' in q;
}
export interface TagDetails {
tagId: Number,
name: String,
tagId: number,
name: string,
parent?: TagDetails,
storeLinks?: String[],
storeLinks?: string[],
}
export function isTagDetails(q: any): q is TagDetails {
return 'tagId' in q;
}
export interface RankingDetails {
rankingId: Number,
rankingId: number,
type: ItemType, // The item type being ranked
rankedId: Number, // The item being ranked
rankedId: number, // The item being ranked
context: ArtistDetails | TagDetails,
value: Number, // The ranking (higher = better)
value: number, // The ranking (higher = better)
}
export function isRankingDetails(q: any): q is RankingDetails {
return 'rankingId' in q;
}
export interface SongDetails {
songId: Number,
title: String,
songId: number,
title: string,
artists?: ArtistDetails[],
tags?: TagDetails[],
storeLinks?: String[],
storeLinks?: string[],
rankings?: RankingDetails[],
}
export function isSongDetails(q: any): q is SongDetails {
@ -134,11 +134,11 @@ export function checkQueryRequest(req: any): boolean {
export const SongDetailsEndpoint = '/song/:id';
export interface SongDetailsRequest { }
export interface SongDetailsResponse {
title: String,
storeLinks: String[],
artistIds: Number[],
albumIds: Number[],
tagIds: Number[],
title: string,
storeLinks: string[],
artistIds: number[],
albumIds: number[],
tagIds: number[],
}
export function checkSongDetailsRequest(req: any): boolean {
return true;
@ -148,9 +148,9 @@ export function checkSongDetailsRequest(req: any): boolean {
export const ArtistDetailsEndpoint = '/artist/:id';
export interface ArtistDetailsRequest { }
export interface ArtistDetailsResponse {
name: String,
tagIds: Number[],
storeLinks: String[],
name: string,
tagIds: number[],
storeLinks: string[],
}
export function checkArtistDetailsRequest(req: any): boolean {
return true;
@ -159,14 +159,14 @@ export function checkArtistDetailsRequest(req: any): boolean {
// Create a new song (POST).
export const CreateSongEndpoint = '/song';
export interface CreateSongRequest {
title: String;
artistIds?: Number[];
albumIds?: Number[];
tagIds?: Number[];
storeLinks?: String[];
title: string;
artistIds?: number[];
albumIds?: number[];
tagIds?: number[];
storeLinks?: string[];
}
export interface CreateSongResponse {
id: Number;
id: number;
}
export function checkCreateSongRequest(req: any): boolean {
return "body" in req &&
@ -176,11 +176,11 @@ export function checkCreateSongRequest(req: any): boolean {
// Modify an existing song (PUT).
export const ModifySongEndpoint = '/song/:id';
export interface ModifySongRequest {
title?: String;
artistIds?: Number[];
albumIds?: Number[];
tagIds?: Number[];
storeLinks?: String[];
title?: string;
artistIds?: number[];
albumIds?: number[];
tagIds?: number[];
storeLinks?: string[];
}
export interface ModifySongResponse { }
export function checkModifySongRequest(req: any): boolean {
@ -190,13 +190,13 @@ export function checkModifySongRequest(req: any): boolean {
// Create a new album (POST).
export const CreateAlbumEndpoint = '/album';
export interface CreateAlbumRequest {
name: String;
tagIds?: Number[];
artistIds?: Number[];
storeLinks?: String[];
name: string;
tagIds?: number[];
artistIds?: number[];
storeLinks?: string[];
}
export interface CreateAlbumResponse {
id: Number;
id: number;
}
export function checkCreateAlbumRequest(req: any): boolean {
return "body" in req &&
@ -206,10 +206,10 @@ export function checkCreateAlbumRequest(req: any): boolean {
// Modify an existing album (PUT).
export const ModifyAlbumEndpoint = '/album/:id';
export interface ModifyAlbumRequest {
name?: String;
tagIds?: Number[];
artistIds?: Number[];
storeLinks?: String[];
name?: string;
tagIds?: number[];
artistIds?: number[];
storeLinks?: string[];
}
export interface ModifyAlbumResponse { }
export function checkModifyAlbumRequest(req: any): boolean {
@ -220,11 +220,11 @@ export function checkModifyAlbumRequest(req: any): boolean {
export const AlbumDetailsEndpoint = '/album/:id';
export interface AlbumDetailsRequest { }
export interface AlbumDetailsResponse {
name: String;
tagIds: Number[];
artistIds: Number[];
songIds: Number[];
storeLinks: String[];
name: string;
tagIds: number[];
artistIds: number[];
songIds: number[];
storeLinks: string[];
}
export function checkAlbumDetailsRequest(req: any): boolean {
return true;
@ -233,12 +233,12 @@ export function checkAlbumDetailsRequest(req: any): boolean {
// Create a new artist (POST).
export const CreateArtistEndpoint = '/artist';
export interface CreateArtistRequest {
name: String;
tagIds?: Number[];
storeLinks?: String[];
name: string;
tagIds?: number[];
storeLinks?: string[];
}
export interface CreateArtistResponse {
id: Number;
id: number;
}
export function checkCreateArtistRequest(req: any): boolean {
return "body" in req &&
@ -248,9 +248,9 @@ export function checkCreateArtistRequest(req: any): boolean {
// Modify an existing artist (PUT).
export const ModifyArtistEndpoint = '/artist/:id';
export interface ModifyArtistRequest {
name?: String,
tagIds?: Number[];
storeLinks?: String[],
name?: string,
tagIds?: number[];
storeLinks?: string[],
}
export interface ModifyArtistResponse { }
export function checkModifyArtistRequest(req: any): boolean {
@ -260,11 +260,11 @@ export function checkModifyArtistRequest(req: any): boolean {
// Create a new tag (POST).
export const CreateTagEndpoint = '/tag';
export interface CreateTagRequest {
name: String;
parentId?: Number;
name: string;
parentId?: number;
}
export interface CreateTagResponse {
id: Number;
id: number;
}
export function checkCreateTagRequest(req: any): boolean {
return "body" in req &&
@ -274,8 +274,8 @@ export function checkCreateTagRequest(req: any): boolean {
// Modify an existing tag (PUT).
export const ModifyTagEndpoint = '/tag/:id';
export interface ModifyTagRequest {
name?: String,
parentId?: Number;
name?: string,
parentId?: number;
}
export interface ModifyTagResponse { }
export function checkModifyTagRequest(req: any): boolean {
@ -286,8 +286,8 @@ export function checkModifyTagRequest(req: any): boolean {
export const TagDetailsEndpoint = '/tag/:id';
export interface TagDetailsRequest { }
export interface TagDetailsResponse {
name: String,
parentId?: Number,
name: string,
parentId?: number,
}
export function checkTagDetailsRequest(req: any): boolean {
return true;

2
server/.gitignore vendored

@ -21,4 +21,4 @@
npm-debug.log*
yarn-debug.log*
yarn-error.log*
db_dev.sqlite3
dev.sqlite3

@ -1,5 +1,6 @@
const bodyParser = require('body-parser');
import * as api from '../client/src/api';
import Knex from 'knex';
import { CreateSongEndpointHandler } from './endpoints/CreateSongEndpointHandler';
import { CreateArtistEndpointHandler } from './endpoints/CreateArtistEndpointHandler';
@ -16,10 +17,10 @@ import { ModifyAlbumEndpointHandler } from './endpoints/ModifyAlbumEndpointHandl
import { AlbumDetailsEndpointHandler } from './endpoints/AlbumDetailsEndpointHandler';
import * as endpointTypes from './endpoints/types';
const invokeHandler = (handler:endpointTypes.EndpointHandler) => {
const invokeHandler = (handler:endpointTypes.EndpointHandler, knex: Knex) => {
return async (req: any, res: any) => {
console.log("Incoming", req.method, " @ ", req.url);
await handler(req, res)
await handler(req, res, knex)
.catch(endpointTypes.catchUnhandledErrors)
.catch((_e:endpointTypes.EndpointError) => {
let e:endpointTypes.EndpointError = _e;
@ -30,24 +31,28 @@ const invokeHandler = (handler:endpointTypes.EndpointHandler) => {
};
}
const SetupApp = (app: any) => {
const SetupApp = (app: any, knex: Knex) => {
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const invokeWithKnex = (handler: endpointTypes.EndpointHandler) => {
return invokeHandler(handler, knex);
}
// Set up REST API endpoints
app.post(api.CreateSongEndpoint, invokeHandler(CreateSongEndpointHandler));
app.post(api.QueryEndpoint, invokeHandler(QueryEndpointHandler));
app.post(api.CreateArtistEndpoint, invokeHandler(CreateArtistEndpointHandler));
app.put(api.ModifyArtistEndpoint, invokeHandler(ModifyArtistEndpointHandler));
app.put(api.ModifySongEndpoint, invokeHandler(ModifySongEndpointHandler));
app.get(api.SongDetailsEndpoint, invokeHandler(SongDetailsEndpointHandler));
app.get(api.ArtistDetailsEndpoint, invokeHandler(ArtistDetailsEndpointHandler));
app.post(api.CreateTagEndpoint, invokeHandler(CreateTagEndpointHandler));
app.put(api.ModifyTagEndpoint, invokeHandler(ModifyTagEndpointHandler));
app.get(api.TagDetailsEndpoint, invokeHandler(TagDetailsEndpointHandler));
app.post(api.CreateAlbumEndpoint, invokeHandler(CreateAlbumEndpointHandler));
app.put(api.ModifyAlbumEndpoint, invokeHandler(ModifyAlbumEndpointHandler));
app.get(api.AlbumDetailsEndpoint, invokeHandler(AlbumDetailsEndpointHandler));
app.post(api.CreateSongEndpoint, invokeWithKnex(CreateSongEndpointHandler));
app.post(api.QueryEndpoint, invokeWithKnex(QueryEndpointHandler));
app.post(api.CreateArtistEndpoint, invokeWithKnex(CreateArtistEndpointHandler));
app.put(api.ModifyArtistEndpoint, invokeWithKnex(ModifyArtistEndpointHandler));
app.put(api.ModifySongEndpoint, invokeWithKnex(ModifySongEndpointHandler));
app.get(api.SongDetailsEndpoint, invokeWithKnex(SongDetailsEndpointHandler));
app.get(api.ArtistDetailsEndpoint, invokeWithKnex(ArtistDetailsEndpointHandler));
app.post(api.CreateTagEndpoint, invokeWithKnex(CreateTagEndpointHandler));
app.put(api.ModifyTagEndpoint, invokeWithKnex(ModifyTagEndpointHandler));
app.get(api.TagDetailsEndpoint, invokeWithKnex(TagDetailsEndpointHandler));
app.post(api.CreateAlbumEndpoint, invokeWithKnex(CreateAlbumEndpointHandler));
app.put(api.ModifyAlbumEndpoint, invokeWithKnex(ModifyAlbumEndpointHandler));
app.get(api.AlbumDetailsEndpoint, invokeWithKnex(AlbumDetailsEndpointHandler));
}
export { SetupApp }

@ -1,20 +0,0 @@
{
"development": {
"storage": "db_dev.sqlite3",
"dialect": "sqlite"
},
"test": {
"username": "root",
"password": null,
"database": "database_test",
"host": "127.0.0.1",
"dialect": "mysql"
},
"production": {
"username": "root",
"password": null,
"database": "database_production",
"host": "127.0.0.1",
"dialect": "mysql"
}
}

@ -1,8 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
export const AlbumDetailsEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const AlbumDetailsEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkAlbumDetailsRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid AlbumDetails request: ' + JSON.stringify(req.body),
@ -12,27 +12,44 @@ export const AlbumDetailsEndpointHandler: EndpointHandler = async (req: any, res
}
try {
const albums = await models.Album.findAll({
include: [models.Artist, models.Tag, models.Song],
where: {
id: req.params.id
}
});
if (albums.length != 1) {
const e: EndpointError = {
internalMessage: 'There is no album with id ' + req.params.id + '.',
httpStatus: 400
};
throw e;
}
let album = albums[0];
// Start transfers for songs, tags and artists.
// Also request the album itself.
const tagIdsPromise = knex.select('tagId')
.from('albums_tags')
.where({ 'albumId': req.params.id })
.then((tags: any) => {
return tags.map((tag: any) => tag['tagId'])
});
const songIdsPromise = knex.select('songId')
.from('songs_albums')
.where({ 'albumId': req.params.id })
.then((songs: any) => {
return songs.map((song: any) => song['songId'])
});
const artistIdsPromise = knex.select('artistId')
.from('artists_albums')
.where({ 'albumId': req.params.id })
.then((artists: any) => {
return artists.map((artist: any) => artist['artistId'])
});
const albumPromise = knex.select('name', 'storeLinks')
.from('albums')
.where({ id: req.params.id })
.then((albums: any) => albums[0]);
// Wait for the requests to finish.
const [album, tags, songs, artists] =
await Promise.all([albumPromise, tagIdsPromise, songIdsPromise, artistIdsPromise]);
// Respond to the request.
const response: api.AlbumDetailsResponse = {
name: album.name,
artistIds: album.Artists.map((artist: any) => artist.id),
tagIds: album.Tags.map((tag: any) => tag.id),
songIds: album.Songs.map((song: any) => song.id),
storeLinks: album.storeLinks,
}
name: album['name'],
artistIds: artists,
tagIds: tags,
songIds: songs,
storeLinks: JSON.parse(album['storeLinks']),
};
await res.send(response);
} catch (e) {
catchUnhandledErrors(e);

@ -1,8 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
export const ArtistDetailsEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const ArtistDetailsEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkArtistDetailsRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid ArtistDetails request: ' + JSON.stringify(req.body),
@ -12,27 +12,21 @@ export const ArtistDetailsEndpointHandler: EndpointHandler = async (req: any, re
}
try {
const artists: any[] = await models.Artist.findAll({
where: {
id: req.params.id
},
include: [models.Tag]
});
if (artists.length != 1) {
const e: EndpointError = {
internalMessage: 'There is no artist with id ' + req.params.id + '.',
httpStatus: 400
};
throw e;
}
let artist = artists[0];
const storeLinks = Array.isArray(artist.storeLinks) ? artist.storeLinks :
(artist.storeLinks ? [artist.storeLinks] : []);
const tagIds = Array.from(new Set((await knex.select('tagId')
.from('artists_tags')
.where({ 'artistId': req.params.id })
).map((tag: any) => tag['tagId'])));
const results = await knex.select(['id', 'name', 'storeLinks'])
.from('artists')
.where({ 'id': req.params.id });
const response: api.ArtistDetailsResponse = {
name: artist.name,
tagIds: artist.Tags.map((tag: any) => tag.id),
storeLinks: storeLinks,
};
name: results[0].name,
tagIds: tagIds,
storeLinks: JSON.parse(results[0].storeLinks),
}
await res.send(response);
} catch (e) {
catchUnhandledErrors(e)

@ -1,9 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
const { Op } = require("sequelize");
import Knex from 'knex';
export const CreateAlbumEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const CreateAlbumEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkCreateAlbumRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid CreateAlbum request: ' + JSON.stringify(req.body),
@ -13,29 +12,30 @@ export const CreateAlbumEndpointHandler: EndpointHandler = async (req: any, res:
}
const reqObject: api.CreateAlbumRequest = req.body;
// Start retrieving the artist instances to link the album to.
var artistInstancesPromise = reqObject.artistIds && models.Artist.findAll({
where: {
id: {
[Op.in]: reqObject.artistIds
}
}
});
console.log("Create Album:", reqObject);
// Start retrieving the tag instances to link the album to.
var tagInstancesPromise = reqObject.tagIds && models.Tag.findAll({
where: {
id: {
[Op.in]: reqObject.tagIds
}
}
});
await knex.transaction(async (trx) => {
try {
// Start retrieving artists.
const artistIdsPromise = reqObject.artistIds ?
trx.select('id')
.from('artists')
.whereIn('id', reqObject.artistIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
// Start retrieving tags.
const tagIdsPromise = reqObject.tagIds ?
trx.select('id')
.from('tags')
.whereIn('id', reqObject.tagIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
// Upon finish retrieving artists and tags, create the album and associate it.
await Promise.all([artistInstancesPromise, tagInstancesPromise])
.then((values: any) => {
var [artists, tags] = values;
// Wait for the requests to finish.
var [artists, tags] = await Promise.all([artistIdsPromise, tagIdsPromise]);;
// Check that we found all artists and tags we need.
if ((reqObject.artistIds && artists.length !== reqObject.artistIds.length) ||
(reqObject.tagIds && tags.length !== reqObject.tagIds.length)) {
const e: EndpointError = {
@ -45,19 +45,47 @@ export const CreateAlbumEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
var album = models.Album.build({
name: reqObject.name,
storeLinks: reqObject.storeLinks || [],
});
artists && album.addArtists(artists);
tags && album.addTags(tags);
return album.save();
})
.then((album: any) => {
// Create the album.
const albumId = (await trx('albums')
.insert({
name: reqObject.name,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
})
)[0];
// Link the artists via the linking table.
if (artists && artists.length) {
await trx('artists_albums').insert(
artists.map((artistId: number) => {
return {
artistId: artistId,
albumId: albumId,
}
})
)
}
// Link the tags via the linking table.
if (tags && tags.length) {
await trx('albums_tags').insert(
tags.map((tagId: number) => {
return {
albumId: albumId,
tagId: tagId,
}
})
)
}
// Respond to the request.
const responseObject: api.CreateSongResponse = {
id: album.id
id: albumId
};
res.status(200).send(responseObject);
})
.catch(catchUnhandledErrors);
} catch (e) {
catchUnhandledErrors(e);
trx.rollback();
}
})
}

@ -1,9 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
const { Op } = require("sequelize");
import Knex from 'knex';
export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkCreateArtistRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid CreateArtist request: ' + JSON.stringify(req.body),
@ -15,38 +14,55 @@ export const CreateArtistEndpointHandler: EndpointHandler = async (req: any, res
console.log("Create artist:", reqObject)
try {
await knex.transaction(async (trx) => {
try {
// Retrieve tag instances to link the artist to.
const tags: number[] = reqObject.tagIds ?
Array.from(new Set(
(await trx.select('id').from('tags')
.whereIn('id', reqObject.tagIds))
.map((tag: any) => tag['id'])
))
: [];
// Start retrieving the tag instances to link the artist to.
const tags = reqObject.tagIds && await models.Tag.findAll({
where: {
id: {
[Op.in]: reqObject.tagIds
}
console.log("Found artist tags:", tags)
if (reqObject.tagIds && tags && tags.length !== reqObject.tagIds.length) {
const e: EndpointError = {
internalMessage: 'Not all tags exist for CreateArtist request: ' + JSON.stringify(req.body),
httpStatus: 400
};
throw e;
}
});
console.log("Found artist tags:", tags)
// Create the artist.
const artistId = (await trx('artists')
.insert({
name: reqObject.name,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
})
)[0];
// Link the tags via the linking table.
if (tags && tags.length) {
await trx('artists_tags').insert(
tags.map((tagId: number) => {
return {
artistId: artistId,
tagId: tagId,
}
})
)
}
if (reqObject.tagIds && tags.length !== reqObject.tagIds.length) {
const e: EndpointError = {
internalMessage: 'Not all tags exist for CreateArtist request: ' + JSON.stringify(req.body),
httpStatus: 400
const responseObject: api.CreateSongResponse = {
id: artistId
};
throw e;
}
await res.status(200).send(responseObject);
var artist = models.Artist.build({
name: reqObject.name,
storeLinks: reqObject.storeLinks || [],
});
tags && artist.addTags(tags);
await artist.save();
const responseObject: api.CreateSongResponse = {
id: artist.id
};
await res.status(200).send(responseObject);
} catch (e) {
catchUnhandledErrors(e);
}
} catch (e) {
catchUnhandledErrors(e);
trx.rollback();
}
});
}

@ -1,9 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
const { Op } = require("sequelize");
import Knex from 'knex';
export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkCreateSongRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid CreateSong request: ' + JSON.stringify(req.body),
@ -13,41 +12,41 @@ export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res:
}
const reqObject: api.CreateSongRequest = req.body;
// Start retrieving the artist instances to link the song to.
var artistInstancesPromise = reqObject.artistIds && models.Artist.findAll({
where: {
id: {
[Op.in]: reqObject.artistIds
}
}
});
console.log("Create Song:", reqObject);
// Start retrieving the album instances to link the song to.
var albumInstancesPromise = reqObject.albumIds && models.Album.findAll({
where: {
id: {
[Op.in]: reqObject.albumIds
}
}
});
await knex.transaction(async (trx) => {
try {
// Start retrieving artists.
const artistIdsPromise = reqObject.artistIds ?
trx.select('id')
.from('artists')
.whereIn('id', reqObject.artistIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
// Start retrieving the tag instances to link the song to.
var tagInstancesPromise = reqObject.tagIds && models.Tag.findAll({
where: {
id: {
[Op.in]: reqObject.tagIds
}
}
});
// Start retrieving tags.
const tagIdsPromise = reqObject.tagIds ?
trx.select('id')
.from('tags')
.whereIn('id', reqObject.tagIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
// Upon finish retrieving dependents, create the song and associate it.
await Promise.all([artistInstancesPromise, albumInstancesPromise, tagInstancesPromise])
.then((values: any) => {
var [artists, albums, tags] = values;
// Start retrieving albums.
const albumIdsPromise = reqObject.albumIds ?
trx.select('id')
.from('albums')
.whereIn('id', reqObject.albumIds)
.then((as: any) => as.map((a: any) => a['id'])) :
(async () => { return [] })();
// Wait for the requests to finish.
var [artists, tags, albums] = await Promise.all([artistIdsPromise, tagIdsPromise, albumIdsPromise]);;
// Check that we found all objects we need.
if ((reqObject.artistIds && artists.length !== reqObject.artistIds.length) ||
(reqObject.albumIds && albums.length !== reqObject.albumIds.length) ||
(reqObject.tagIds && tags.length !== reqObject.tagIds.length)) {
(reqObject.tagIds && tags.length !== reqObject.tagIds.length) ||
(reqObject.albumIds && albums.length !== reqObject.albumIds.length)) {
const e: EndpointError = {
internalMessage: 'Not all albums and/or artists and/or tags exist for CreateSong request: ' + JSON.stringify(req.body),
httpStatus: 400
@ -55,20 +54,59 @@ export const CreateSongEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
var song = models.Song.build({
title: reqObject.title,
storeLinks: reqObject.storeLinks || [],
});
artists && song.addArtists(artists);
albums && song.addAlbums(albums);
tags && song.addTags(tags);
return song.save();
})
.then((song: any) => {
// Create the song.
const songId = (await trx('songs')
.insert({
title: reqObject.title,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
})
)[0];
// Link the artists via the linking table.
if (artists && artists.length) {
await Promise.all(
artists.map((artistId: number) => {
return trx('songs_artists').insert({
artistId: artistId,
songId: songId,
})
})
)
}
// Link the tags via the linking table.
if (tags && tags.length) {
await Promise.all(
tags.map((tagId: number) => {
return trx('songs_tags').insert({
songId: songId,
tagId: tagId,
})
})
)
}
// Link the albums via the linking table.
if (albums && albums.length) {
await Promise.all(
albums.map((albumId: number) => {
return trx('songs_albums').insert({
songId: songId,
albumId: albumId,
})
})
)
}
// Respond to the request.
const responseObject: api.CreateSongResponse = {
id: song.id
id: songId
};
res.status(200).send(responseObject);
})
.catch(catchUnhandledErrors);
} catch (e) {
catchUnhandledErrors(e);
trx.rollback();
}
})
}

@ -1,8 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
export const CreateTagEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const CreateTagEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkCreateTagRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid CreateTag request: ' + JSON.stringify(req.body),
@ -12,37 +12,45 @@ export const CreateTagEndpointHandler: EndpointHandler = async (req: any, res: a
}
const reqObject: api.CreateTagRequest = req.body;
const getTag = async (id: Number) => {
const tag = await models.Tag.findAll({
where: {
id: id
console.log("Create 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({ '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;
}
});
if (tag.length != 1) {
const e: EndpointError = {
internalMessage: 'There is no tag with id ' + id + '.',
httpStatus: 400
// Create the new tag.
var tag: any = {
name: reqObject.name
};
throw e;
}
return tag[0];
}
if (maybeParent) {
tag['parentId'] = maybeParent;
}
const tagId = (await trx('tags').insert(tag))[0];
// If applicable, start retrieving the new parent tag.
const maybeNewParentPromise: Promise<any> | Promise<undefined> =
(reqObject.parentId) ? getTag(reqObject.parentId) : (async () => { return undefined })();
// Respond to the request.
const responseObject: api.CreateTagResponse = {
id: tagId
};
res.status(200).send(responseObject);
(async () => {
const maybeParent = await maybeNewParentPromise;
const tag = await models.Tag.create({
name: reqObject.name
});
reqObject.parentId && await tag.setParent(maybeParent);
await tag.save();
const responseObject: api.CreateTagResponse = {
id: tag.id
};
res.status(200).send(responseObject);
})()
.catch(catchUnhandledErrors);
} catch (e) {
catchUnhandledErrors(e);
trx.rollback();
}
})
}

@ -1,9 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
const { Op } = require("sequelize");
import Knex from 'knex';
export const ModifyAlbumEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const ModifyAlbumEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkModifyAlbumRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid ModifyAlbum request: ' + JSON.stringify(req.body),
@ -13,50 +12,133 @@ export const ModifyAlbumEndpointHandler: EndpointHandler = async (req: any, res:
}
const reqObject: api.ModifyAlbumRequest = req.body;
// Start retrieving the artist instances to link the album to.
var artistInstancesPromise = reqObject.artistIds && models.Artist.findAll({
where: {
id: {
[Op.in]: reqObject.artistIds
}
}
});
console.log("Modify Album:", reqObject);
// Start retrieving the tag instances to link the album to.
var tagInstancesPromise = reqObject.tagIds && models.Tag.findAll({
where: {
id: {
[Op.in]: reqObject.tagIds
}
}
});
await knex.transaction(async (trx) => {
try {
// Start retrieving artists.
const artistIdsPromise = reqObject.artistIds ?
trx.select('artistId')
.from('artists_albums')
.whereIn('id', reqObject.artistIds)
.then((as: any) => as.map((a: any) => a['artistId'])) :
(async () => { return [] })();
// Start retrieving the album to modify.
var albumInstancePromise = models.Album.findOne({
where: {
id: req.params.id
}
});
// Start retrieving tags.
const tagIdsPromise = reqObject.tagIds ?
trx.select('id')
.from('albums_tags')
.whereIn('id', reqObject.tagIds)
.then((ts: any) => ts.map((t: any) => t['tagId'])) :
(async () => { return [] })();
// Upon finish retrieving artists and albums, modify the album.
await Promise.all([artistInstancesPromise, tagInstancesPromise, albumInstancePromise])
.then(async (values: any) => {
var [artists, tags, album] = values;
if (!album) {
// Start retrieving the album itself.
const albumPromise = trx.select('id')
.from('albums')
.where({ id: req.params.id })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
// Wait for the requests to finish.
var [album, artists, tags] = await Promise.all([albumPromise, artistIdsPromise, tagIdsPromise]);;
// Check that we found all objects we need.
if ((reqObject.artistIds && artists.length !== reqObject.artistIds.length) ||
(reqObject.tagIds && tags.length !== reqObject.tagIds.length) ||
!album) {
const e: EndpointError = {
internalMessage: 'There is no album with id ' + req.params.id + '.',
internalMessage: 'Not all albums and/or artists and/or tags exist for ModifyAlbum request: ' + JSON.stringify(req.body),
httpStatus: 400
};
throw e;
}
if (reqObject.artistIds) { album.setArtists(artists) };
if (reqObject.tagIds) { album.setTags(tags) };
if (reqObject.name) { album.name = reqObject.name };
if (reqObject.storeLinks) { album.setStoreIds(reqObject.storeLinks) };
await album.save();
})
.then(() => {
res.status(200).send({});
})
.catch(catchUnhandledErrors);
// Modify the album.
const modifyAlbumPromise = trx('albums')
.where({ 'id': req.params.id })
.update({
name: reqObject.name,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
})
// Remove unlinked artists.
// TODO: test this!
const removeUnlinkedArtists = trx('artists_albums')
.where({ 'albumId': req.params.id })
.whereNotIn('artistId', reqObject.artistIds || [])
.delete();
// Remove unlinked tags.
// TODO: test this!
const removeUnlinkedTags = trx('albums_tags')
.where({ 'albumId': req.params.id })
.whereNotIn('tagId', reqObject.tagIds || [])
.delete();
// Link new artists.
// TODO: test this!
const addArtists = trx('artists_albums')
.where({ 'albumId': req.params.id })
.then((as: any) => as.map((a: any) => a['artistId']))
.then((doneArtistIds: number[]) => {
// Get the set of artists that are not yet linked
const toLink = artists.filter((id: number) => {
return !doneArtistIds.includes(id);
});
const insertObjects = toLink.map((artistId: number) => {
return {
artistId: artistId,
albumId: req.params.id,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('artists_albums').insert(obj)
)
);
})
// Link new tags.
// TODO: test this!
const addTags = trx('albums_tags')
.where({ 'albumId': req.params.id })
.then((ts: any) => ts.map((t: any) => t['tagId']))
.then((doneTagIds: number[]) => {
// Get the set of tags that are not yet linked
const toLink = tags.filter((id: number) => {
return !doneTagIds.includes(id);
});
const insertObjects = toLink.map((tagId: number) => {
return {
tagId: tagId,
albumId: req.params.id,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('albums_tags').insert(obj)
)
);
})
// Wait for all operations to finish.
await Promise.all([
modifyAlbumPromise,
removeUnlinkedArtists,
removeUnlinkedTags,
addArtists,
addTags
]);
// Respond to the request.
res.status(200).send();
} catch (e) {
catchUnhandledErrors(e);
trx.rollback();
}
})
}

@ -1,8 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
export const ModifyArtistEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const ModifyArtistEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkModifyArtistRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid ModifyArtist request: ' + JSON.stringify(req.body),
@ -10,26 +10,95 @@ export const ModifyArtistEndpointHandler: EndpointHandler = async (req: any, res
};
throw e;
}
const reqObject:api.ModifyArtistRequest = req.body;
const reqObject: api.ModifyArtistRequest = req.body;
console.log("Modify Artist:", reqObject);
await models.Artist.findAll({
where: { id: req.params.id }
})
.then(async (artists: any[]) => {
if (artists.length != 1) {
await knex.transaction(async (trx) => {
try {
const artistId = parseInt(req.params.id);
// Start retrieving tags.
const tagIdsPromise = reqObject.tagIds ?
trx.select('id')
.from('artists_tags')
.whereIn('id', reqObject.tagIds)
.then((ts: any) => ts.map((t: any) => t['tagId'])) :
(async () => { return [] })();
// Start retrieving the artist itself.
const artistPromise = trx.select('id')
.from('artists')
.where({ id: artistId })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
// Wait for the requests to finish.
var [artist, tags] = await Promise.all([artistPromise, tagIdsPromise]);;
// Check that we found all objects we need.
if ((reqObject.tagIds && tags.length !== reqObject.tagIds.length) ||
!artist) {
const e: EndpointError = {
internalMessage: 'There is no artist with id ' + req.params.id + '.',
internalMessage: 'Not all artists and/or tags exist for ModifyArtist request: ' + JSON.stringify(req.body),
httpStatus: 400
};
throw e;
}
let artist = artists[0];
artist.name = reqObject.name;
if(reqObject.storeLinks) { artist.setStoreLinks(reqObject.storeLinks) };
await artist.save();
})
.then(() => {
res.status(200).send({});
})
.catch(catchUnhandledErrors);
// Modify the artist.
const modifyArtistPromise = trx('artists')
.where({ 'id': artistId })
.update({
name: reqObject.name,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
})
// Remove unlinked tags.
// TODO: test this!
const removeUnlinkedTags = reqObject.tagIds ?
trx('artists_tags')
.where({ 'artistId': artistId })
.whereNotIn('tagId', reqObject.tagIds || [])
.delete() :
(async () => undefined)();
// Link new tags.
// TODO: test this!
const addTags = trx('artists_tags')
.where({ 'artistId': artistId })
.then((ts: any) => ts.map((t: any) => t['tagId']))
.then((doneTagIds: number[]) => {
// Get the set of tags that are not yet linked
const toLink = tags.filter((id: number) => {
return !doneTagIds.includes(id);
});
const insertObjects = toLink.map((tagId: number) => {
return {
tagId: tagId,
artistId: artistId,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('artists_tags').insert(obj)
)
);
});
// Wait for all operations to finish.
await Promise.all([
modifyArtistPromise,
removeUnlinkedTags,
addTags
]);
// Respond to the request.
res.status(200).send();
} catch (e) {
catchUnhandledErrors(e);
trx.rollback();
}
})
}

@ -1,9 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
const { Op } = require("sequelize");
import Knex from 'knex';
export const ModifySongEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const ModifySongEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkModifySongRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid ModifySong request: ' + JSON.stringify(req.body),
@ -13,53 +12,177 @@ export const ModifySongEndpointHandler: EndpointHandler = async (req: any, res:
}
const reqObject: api.ModifySongRequest = req.body;
// Start retrieving the artist instances to link the song to.
var artistInstancesPromise = reqObject.artistIds && models.Artist.findAll({
where: {
id: {
[Op.in]: reqObject.artistIds
}
}
});
console.log("Modify Song:", reqObject);
// Start retrieving the album instances to link the song to.
var albumInstancesPromise = reqObject.albumIds && models.Album.findAll({
where: {
id: {
[Op.in]: reqObject.albumIds
}
}
});
await knex.transaction(async (trx) => {
try {
// Start retrieving artists.
const artistIdsPromise = reqObject.artistIds ?
trx.select('artistId')
.from('songs_artists')
.whereIn('id', reqObject.artistIds)
.then((as: any) => as.map((a: any) => a['artistId'])) :
(async () => { return [] })();
// Start retrieving tags.
const tagIdsPromise = reqObject.tagIds ?
trx.select('id')
.from('songs_tags')
.whereIn('id', reqObject.tagIds)
.then((ts: any) => ts.map((t: any) => t['tagId'])) :
(async () => { return [] })();
// Start retrieving albums.
const albumIdsPromise = reqObject.albumIds ?
trx.select('id')
.from('songs_albums')
.whereIn('id', reqObject.albumIds)
.then((as: any) => as.map((a: any) => a['albumId'])) :
(async () => { return [] })();
// Start retrieving the tag instances to link the song to.
var tagInstancesPromise = reqObject.tagIds && models.Tag.findAll({
where: {
id: {
[Op.in]: reqObject.tagIds
// Start retrieving the song itself.
const songPromise = trx.select('id')
.from('songs')
.where({ id: req.params.id })
.then((r: any) => (r && r[0]) ? r[0]['id'] : undefined)
// Wait for the requests to finish.
var [song, artists, tags, albums] =
await Promise.all([songPromise, artistIdsPromise, tagIdsPromise, albumIdsPromise]);;
// Check that we found all objects we need.
if ((reqObject.artistIds && artists.length !== reqObject.artistIds.length) ||
(reqObject.tagIds && tags.length !== reqObject.tagIds.length) ||
(reqObject.albumIds && albums.length !== reqObject.albumIds.length) ||
!song) {
const e: EndpointError = {
internalMessage: 'Not all albums and/or artists and/or tags exist for ModifySong request: ' + JSON.stringify(req.body),
httpStatus: 400
};
throw e;
}
}
});
// Start retrieving the song to modify.
var songInstancePromise = models.Song.findAll({
where: {
id: req.params.id
// Modify the song.
const modifySongPromise = trx('songs')
.where({ 'id': req.params.id })
.update({
title: reqObject.title,
storeLinks: JSON.stringify(reqObject.storeLinks || []),
})
// Remove unlinked artists.
// TODO: test this!
const removeUnlinkedArtists = trx('artists_songs')
.where({ 'songId': req.params.id })
.whereNotIn('artistId', reqObject.artistIds || [])
.delete();
// Remove unlinked tags.
// TODO: test this!
const removeUnlinkedTags = trx('songs_tags')
.where({ 'songId': req.params.id })
.whereNotIn('tagId', reqObject.tagIds || [])
.delete();
// Remove unlinked albums.
// TODO: test this!
const removeUnlinkedAlbums = trx('songs_albums')
.where({ 'songId': req.params.id })
.whereNotIn('albumId', reqObject.albumIds || [])
.delete();
// Link new artists.
// TODO: test this!
const addArtists = trx('artists_songs')
.where({ 'songId': req.params.id })
.then((as: any) => as.map((a: any) => a['artistId']))
.then((doneArtistIds: number[]) => {
// Get the set of artists that are not yet linked
const toLink = artists.filter((id: number) => {
return !doneArtistIds.includes(id);
});
const insertObjects = toLink.map((artistId: number) => {
return {
artistId: artistId,
songId: req.params.id,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('artists_songs').insert(obj)
)
);
})
// Link new tags.
// TODO: test this!
const addTags = trx('songs_tags')
.where({ 'songId': req.params.id })
.then((ts: any) => ts.map((t: any) => t['tagId']))
.then((doneTagIds: number[]) => {
// Get the set of tags that are not yet linked
const toLink = tags.filter((id: number) => {
return !doneTagIds.includes(id);
});
const insertObjects = toLink.map((tagId: number) => {
return {
tagId: tagId,
songId: req.params.id,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('songs_tags').insert(obj)
)
);
})
// Link new albums.
// TODO: test this!
const addAlbums = trx('songs_albums')
.where({ 'albumId': req.params.id })
.then((as: any) => as.map((a: any) => a['albumId']))
.then((doneAlbumIds: number[]) => {
// Get the set of albums that are not yet linked
const toLink = albums.filter((id: number) => {
return !doneAlbumIds.includes(id);
});
const insertObjects = toLink.map((albumId: number) => {
return {
albumId: albumId,
songId: req.params.id,
}
})
// Link them
return Promise.all(
insertObjects.map((obj: any) =>
trx('songs_albums').insert(obj)
)
);
})
// Wait for all operations to finish.
await Promise.all([
modifySongPromise,
removeUnlinkedArtists,
removeUnlinkedTags,
removeUnlinkedAlbums,
addArtists,
addTags,
addAlbums,
]);
// Respond to the request.
res.status(200).send();
} catch (e) {
catchUnhandledErrors(e);
trx.rollback();
}
});
// Upon finish retrieving artists and albums, modify the song.
await Promise.all([artistInstancesPromise, albumInstancesPromise, tagInstancesPromise, songInstancePromise])
.then(async (values: any) => {
var [artists, albums, tags, song] = values;
if (reqObject.artistIds) { song.setArtists(artists) };
if (reqObject.albumIds) { song.setAlbums(albums) };
if (reqObject.tagIds) { song.setTags(tags) };
if (reqObject.title) { song.setTitle(reqObject.title) };
if (reqObject.storeLinks) { song.setStoreIds(reqObject.storeLinks) };
await song.save();
})
.then(() => {
res.status(200).send({});
})
.catch(catchUnhandledErrors);
})
}

@ -1,10 +1,9 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import tag from '../models/tag';
import Knex from 'knex';
export const ModifyTagEndpointHandler: EndpointHandler = async (req: any, res: any) => {
if (!api.checkModifySongRequest(req)) {
export const ModifyTagEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkModifyTagRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid ModifyTag request: ' + JSON.stringify(req.body),
httpStatus: 400
@ -13,39 +12,51 @@ export const ModifyTagEndpointHandler: EndpointHandler = async (req: any, res: a
}
const reqObject: api.ModifyTagRequest = req.body;
const getTag = async (id:Number) => {
const tag = await models.Tag.findAll({
where: {
id: id
console.log("Modify Tag:", reqObject);
await knex.transaction(async (trx) => {
try {
// Start retrieving the parent tag.
const parentTagPromise = reqObject.parentId ?
trx.select('id')
.from('tags')
.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({ 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;
}
});
if(tag.length != 1) {
const e: EndpointError = {
internalMessage: 'There is no tag with id ' + id + '.',
httpStatus: 400
};
throw e;
}
return tag[0];
}
// If applicable, start retrieving the new parent tag.
const maybeNewParentPromise:Promise<any>|Promise<undefined> =
(reqObject.parentId) ? getTag(reqObject.parentId) : (async () => { return undefined })();
// Start retrieving the tag to modify.
var tagInstancePromise: Promise<any> = getTag(req.params.id);
// Upon finish retrieving artists and albums, modify the song.
await Promise.all([maybeNewParentPromise, tagInstancePromise])
.then(async (values: any) => {
var [maybeParent, tag] = values;
if(reqObject.name) { tag.setName(reqObject.name) };
if(reqObject.parentId) { tag.setParent(maybeParent) };
await tag.save();
})
.then(() => {
res.status(200).send({});
})
.catch(catchUnhandledErrors);
// Modify the tag.
await trx('tags')
.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();
}
})
}

@ -1,5 +1,3 @@
const models = require('../models');
const { Op } = require("sequelize");
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
@ -9,87 +7,87 @@ enum QueryType {
Tag,
}
const sequelizeOps: any = {
[api.QueryFilterOp.Eq]: Op.eq,
[api.QueryFilterOp.Ne]: Op.ne,
[api.QueryFilterOp.In]: Op.in,
[api.QueryFilterOp.NotIn]: Op.notIn,
[api.QueryFilterOp.Like]: Op.like,
[api.QueryElemOp.And]: Op.and,
[api.QueryElemOp.Or]: Op.or,
};
// const sequelizeOps: any = {
// [api.QueryFilterOp.Eq]: Op.eq,
// [api.QueryFilterOp.Ne]: Op.ne,
// [api.QueryFilterOp.In]: Op.in,
// [api.QueryFilterOp.NotIn]: Op.notIn,
// [api.QueryFilterOp.Like]: Op.like,
// [api.QueryElemOp.And]: Op.and,
// [api.QueryElemOp.Or]: Op.or,
// };
const sequelizeProps: any = {
[QueryType.Song]: {
[api.QueryElemProperty.songTitle]: "title",
[api.QueryElemProperty.songId]: "id",
[api.QueryElemProperty.artistName]: "$Artists.name$",
[api.QueryElemProperty.artistId]: "$Artists.id$",
[api.QueryElemProperty.albumName]: "$Albums.name$",
},
[QueryType.Artist]: {
[api.QueryElemProperty.songTitle]: "$Songs.title$",
[api.QueryElemProperty.songId]: "$Songs.id$",
[api.QueryElemProperty.artistName]: "name",
[api.QueryElemProperty.artistId]: "id",
[api.QueryElemProperty.albumName]: "$Albums.name$",
},
[QueryType.Tag]: {
[api.QueryElemProperty.songTitle]: "$Songs.title$",
[api.QueryElemProperty.songId]: "$Songs.id$",
[api.QueryElemProperty.artistName]: "$Artists.name$",
[api.QueryElemProperty.artistId]: "$Artists.id$",
[api.QueryElemProperty.albumName]: "$Albums.name$",
}
};
// const sequelizeProps: any = {
// [QueryType.Song]: {
// [api.QueryElemProperty.songTitle]: "title",
// [api.QueryElemProperty.songId]: "id",
// [api.QueryElemProperty.artistName]: "$Artists.name$",
// [api.QueryElemProperty.artistId]: "$Artists.id$",
// [api.QueryElemProperty.albumName]: "$Albums.name$",
// },
// [QueryType.Artist]: {
// [api.QueryElemProperty.songTitle]: "$Songs.title$",
// [api.QueryElemProperty.songId]: "$Songs.id$",
// [api.QueryElemProperty.artistName]: "name",
// [api.QueryElemProperty.artistId]: "id",
// [api.QueryElemProperty.albumName]: "$Albums.name$",
// },
// [QueryType.Tag]: {
// [api.QueryElemProperty.songTitle]: "$Songs.title$",
// [api.QueryElemProperty.songId]: "$Songs.id$",
// [api.QueryElemProperty.artistName]: "$Artists.name$",
// [api.QueryElemProperty.artistId]: "$Artists.id$",
// [api.QueryElemProperty.albumName]: "$Albums.name$",
// }
// };
const sequelizeOrderColumns: any = {
[QueryType.Song]: {
[api.OrderByType.Name]: 'title',
[api.OrderByType.ArtistRanking]: '$Rankings.rank$',
[api.OrderByType.TagRanking]: '$Rankings.rank$',
},
[QueryType.Artist]: {
[api.OrderByType.Name]: 'name'
},
[QueryType.Tag]: {
[api.OrderByType.Name]: 'name'
},
}
// const sequelizeOrderColumns: any = {
// [QueryType.Song]: {
// [api.OrderByType.Name]: 'title',
// [api.OrderByType.ArtistRanking]: '$Rankings.rank$',
// [api.OrderByType.TagRanking]: '$Rankings.rank$',
// },
// [QueryType.Artist]: {
// [api.OrderByType.Name]: 'name'
// },
// [QueryType.Tag]: {
// [api.OrderByType.Name]: 'name'
// },
// }
// Returns the "where" clauses for Sequelize, per object type.
const getSequelizeWhere = (queryElem: api.QueryElem, type: QueryType) => {
var where: any = {
[Op.and]: []
};
// // Returns the "where" clauses for Sequelize, per object type.
// const getSequelizeWhere = (queryElem: api.QueryElem, type: QueryType) => {
// var where: any = {
// [Op.and]: []
// };
if (queryElem.prop && queryElem.propOperator && queryElem.propOperand) {
// Visit a filter-like subquery leaf.
where[Op.and].push({
[sequelizeProps[type][queryElem.prop]]: {
[sequelizeOps[queryElem.propOperator]]: queryElem.propOperand
}
});
}
if (queryElem.childrenOperator && queryElem.children) {
// Recursively visit a nested subquery.
// if (queryElem.prop && queryElem.propOperator && queryElem.propOperand) {
// // Visit a filter-like subquery leaf.
// where[Op.and].push({
// [sequelizeProps[type][queryElem.prop]]: {
// [sequelizeOps[queryElem.propOperator]]: queryElem.propOperand
// }
// });
// }
// if (queryElem.childrenOperator && queryElem.children) {
// // Recursively visit a nested subquery.
const children = queryElem.children.map((child: api.QueryElem) => getSequelizeWhere(child, type));
where[Op.and].push({
[sequelizeOps[queryElem.childrenOperator]]: children
});
}
// const children = queryElem.children.map((child: api.QueryElem) => getSequelizeWhere(child, type));
// where[Op.and].push({
// [sequelizeOps[queryElem.childrenOperator]]: children
// });
// }
return where;
}
// return where;
// }
function getSequelizeOrder(order: api.Ordering, type: QueryType) {
const ascstring = order.ascending ? 'ASC' : 'DESC';
// function getSequelizeOrder(order: api.Ordering, type: QueryType) {
// const ascstring = order.ascending ? 'ASC' : 'DESC';
return [
[ sequelizeOrderColumns[type][order.orderBy.type], ascstring ]
];
}
// return [
// [ sequelizeOrderColumns[type][order.orderBy.type], ascstring ]
// ];
// }
export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any) => {
if (!api.checkQueryRequest(req.body)) {
@ -101,93 +99,95 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any)
}
const reqObject: api.QueryRequest = req.body;
try {
const songLimit = reqObject.offsetsLimits.songLimit;
const songOffset = reqObject.offsetsLimits.songOffset;
const tagLimit = reqObject.offsetsLimits.tagLimit;
const tagOffset = reqObject.offsetsLimits.tagOffset;
const artistLimit = reqObject.offsetsLimits.artistLimit;
const artistOffset = reqObject.offsetsLimits.artistOffset;
// try {
// const songLimit = reqObject.offsetsLimits.songLimit;
// const songOffset = reqObject.offsetsLimits.songOffset;
// const tagLimit = reqObject.offsetsLimits.tagLimit;
// const tagOffset = reqObject.offsetsLimits.tagOffset;
// const artistLimit = reqObject.offsetsLimits.artistLimit;
// const artistOffset = reqObject.offsetsLimits.artistOffset;
const songs = (songLimit && songLimit > 0) && await models.Song.findAll({
// NOTE: have to disable limit and offset because of bug: https://github.com/sequelize/sequelize/issues/11938.
// Custom pagination is implemented before responding.
where: getSequelizeWhere(reqObject.query, QueryType.Song),
order: getSequelizeOrder(reqObject.ordering, QueryType.Song),
include: [ models.Artist, models.Album, models.Tag, models.Ranking ],
//limit: reqObject.limit,
//offset: reqObject.offset,
})
const artists = (artistLimit && artistLimit > 0) && await models.Artist.findAll({
// NOTE: have to disable limit and offset because of bug: https://github.com/sequelize/sequelize/issues/11938.
// Custom pagination is implemented before responding.
where: getSequelizeWhere(reqObject.query, QueryType.Artist),
order: getSequelizeOrder(reqObject.ordering, QueryType.Artist),
include: [models.Song, models.Album, models.Tag],
//limit: reqObject.limit,
//offset: reqObject.offset,
})
const tags = (tagLimit && tagLimit > 0) && await models.Tag.findAll({
// NOTE: have to disable limit and offset because of bug: https://github.com/sequelize/sequelize/issues/11938.
// Custom pagination is implemented before responding.
where: getSequelizeWhere(reqObject.query, QueryType.Tag),
order: getSequelizeOrder(reqObject.ordering, QueryType.Tag),
include: [models.Song, models.Album, models.Artist],
//limit: reqObject.limit,
//offset: reqObject.offset,
})
// const songs = (songLimit && songLimit > 0) && await models.Song.findAll({
// // NOTE: have to disable limit and offset because of bug: https://github.com/sequelize/sequelize/issues/11938.
// // Custom pagination is implemented before responding.
// where: getSequelizeWhere(reqObject.query, QueryType.Song),
// order: getSequelizeOrder(reqObject.ordering, QueryType.Song),
// include: [ models.Artist, models.Album, models.Tag, models.Ranking ],
// //limit: reqObject.limit,
// //offset: reqObject.offset,
// })
// const artists = (artistLimit && artistLimit > 0) && await models.Artist.findAll({
// // NOTE: have to disable limit and offset because of bug: https://github.com/sequelize/sequelize/issues/11938.
// // Custom pagination is implemented before responding.
// where: getSequelizeWhere(reqObject.query, QueryType.Artist),
// order: getSequelizeOrder(reqObject.ordering, QueryType.Artist),
// include: [models.Song, models.Album, models.Tag],
// //limit: reqObject.limit,
// //offset: reqObject.offset,
// })
// const tags = (tagLimit && tagLimit > 0) && await models.Tag.findAll({
// // NOTE: have to disable limit and offset because of bug: https://github.com/sequelize/sequelize/issues/11938.
// // Custom pagination is implemented before responding.
// where: getSequelizeWhere(reqObject.query, QueryType.Tag),
// order: getSequelizeOrder(reqObject.ordering, QueryType.Tag),
// include: [models.Song, models.Album, models.Artist],
// //limit: reqObject.limit,
// //offset: reqObject.offset,
// })
const response: api.QueryResponse = {
songs: ((songLimit || -1) <= 0) ? [] : await Promise.all(songs.map(async (song: any) => {
const artists = song.getArtists();
const tags = song.getTags();
const rankings = song.getRankings();
return <api.SongDetails>{
songId: song.id,
title: song.title,
storeLinks: song.storeLinks,
artists: (await artists).map((artist: any) => {
return <api.ArtistDetails>{
artistId: artist.id,
name: artist.name,
}
}),
tags: (await tags).map((tag: any) => {
return <api.TagDetails>{
tagId: tag.id,
name: tag.name,
}
}),
rankings: await (await rankings).map(async (ranking: any) => {
const maybeTagContext: api.TagDetails | undefined = await ranking.getTagContext();
const maybeArtistContext: api.ArtistDetails | undefined = await ranking.getArtistContext();
const maybeContext = maybeTagContext || maybeArtistContext;
return <api.RankingDetails>{
rankingId: ranking.id,
type: api.ItemType.Song,
rankedId: song.id,
context: maybeContext,
value: ranking.value,
}
})
};
}).slice(songOffset || 0, (songOffset || 0) + (songLimit || 10))),
// TODO: custom pagination due to bug mentioned above
artists: ((artistLimit || -1) <= 0) ? [] : await Promise.all(artists.map(async (artist: any) => {
return <api.ArtistDetails>{
artistId: artist.id,
name: artist.name,
};
}).slice(artistOffset || 0, (artistOffset || 0) + (artistLimit || 10))),
tags: ((tagLimit || -1) <= 0) ? [] : await Promise.all(tags.map(async (tag: any) => {
return <api.TagDetails>{
tagId: tag.id,
name: tag.name,
};
}).slice(tagOffset || 0, (tagOffset || 0) + (tagLimit || 10))),
};
res.send(response);
} catch (e) {
catchUnhandledErrors(e);
}
// const response: api.QueryResponse = {
// songs: ((songLimit || -1) <= 0) ? [] : await Promise.all(songs.map(async (song: any) => {
// const artists = song.getArtists();
// const tags = song.getTags();
// const rankings = song.getRankings();
// return <api.SongDetails>{
// songId: song.id,
// title: song.title,
// storeLinks: song.storeLinks,
// artists: (await artists).map((artist: any) => {
// return <api.ArtistDetails>{
// artistId: artist.id,
// name: artist.name,
// }
// }),
// tags: (await tags).map((tag: any) => {
// return <api.TagDetails>{
// tagId: tag.id,
// name: tag.name,
// }
// }),
// rankings: await (await rankings).map(async (ranking: any) => {
// const maybeTagContext: api.TagDetails | undefined = await ranking.getTagContext();
// const maybeArtistContext: api.ArtistDetails | undefined = await ranking.getArtistContext();
// const maybeContext = maybeTagContext || maybeArtistContext;
// return <api.RankingDetails>{
// rankingId: ranking.id,
// type: api.ItemType.Song,
// rankedId: song.id,
// context: maybeContext,
// value: ranking.value,
// }
// })
// };
// }).slice(songOffset || 0, (songOffset || 0) + (songLimit || 10))),
// // TODO: custom pagination due to bug mentioned above
// artists: ((artistLimit || -1) <= 0) ? [] : await Promise.all(artists.map(async (artist: any) => {
// return <api.ArtistDetails>{
// artistId: artist.id,
// name: artist.name,
// };
// }).slice(artistOffset || 0, (artistOffset || 0) + (artistLimit || 10))),
// tags: ((tagLimit || -1) <= 0) ? [] : await Promise.all(tags.map(async (tag: any) => {
// return <api.TagDetails>{
// tagId: tag.id,
// name: tag.name,
// };
// }).slice(tagOffset || 0, (tagOffset || 0) + (tagLimit || 10))),
// };
// res.send(response);
// } catch (e) {
// catchUnhandledErrors(e);
// }
throw "NOTIMPLEMENTED";
}

@ -1,8 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
export const SongDetailsEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const SongDetailsEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkSongDetailsRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid SongDetails request: ' + JSON.stringify(req.body),
@ -12,29 +12,50 @@ export const SongDetailsEndpointHandler: EndpointHandler = async (req: any, res:
}
try {
const songs = await models.Song.findAll({
include: [models.Artist, models.Album, models.Tag],
where: {
id: req.params.id
}
});
if (songs.length != 1) {
const e: EndpointError = {
internalMessage: 'There is no song with id ' + req.params.id + '.',
httpStatus: 400
};
throw e;
}
let song = songs[0];
const tagIdsPromise: Promise<number[]> = knex.select('tagId')
.from('songs_tags')
.where({ 'songId': req.params.id })
.then((ts: any) => {
return Array.from(new Set(
ts.map((tag: any) => tag['tagId'])
));
})
const albumIdsPromise: Promise<number[]> = knex.select('albumId')
.from('songs_albums')
.where({ 'songId': req.params.id })
.then((as: any) => {
return Array.from(new Set(
as.map((album: any) => album['albumId'])
));
})
const artistIdsPromise: Promise<number[]> = knex.select('artistId')
.from('songs_artists')
.where({ 'songId': req.params.id })
.then((as: any) => {
return Array.from(new Set(
as.map((artist: any) => artist['artistId'])
));
})
const songPromise = await knex.select(['id', 'title', 'storeLinks'])
.from('songs')
.where({ 'id': req.params.id })
.then((ss: any) => ss[0])
const [tags, albums, artists, song] =
await Promise.all([tagIdsPromise, albumIdsPromise, artistIdsPromise, songPromise]);
const response: api.SongDetailsResponse = {
title: song.title,
artistIds: song.Artists.map((artist: any) => artist.id),
albumIds: song.Albums.map((album: any) => album.id),
tagIds: song.Tags.map((tag: any) => tag.id),
storeLinks: song.storeLinks,
tagIds: tags,
artistIds: artists,
albumIds: albums,
storeLinks: JSON.parse(song.storeLinks),
}
await res.send(response);
} catch (e) {
catchUnhandledErrors(e);
catchUnhandledErrors(e)
}
}

@ -1,8 +1,8 @@
const models = require('../models');
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
export const TagDetailsEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const TagDetailsEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkTagDetailsRequest(req)) {
const e: EndpointError = {
internalMessage: 'Invalid TagDetails request: ' + JSON.stringify(req.body),
@ -11,29 +11,18 @@ export const TagDetailsEndpointHandler: EndpointHandler = async (req: any, res:
throw e;
}
await models.Tag.findAll({
where: {
id: req.params.id
},
include: [{
model: models.Tag,
as: 'parent'
}]
})
.then((tags: any[]) => {
if (tags.length != 1) {
const e: EndpointError = {
internalMessage: 'There is no tag with id ' + req.params.id + '.',
httpStatus: 400
};
throw e;
}
let tag = tags[0];
var response: api.TagDetailsResponse = {
name: tag.name,
};
if(tag.parent) { response['parentId'] = tag.parent.id; }
res.send(response);
})
.catch(catchUnhandledErrors);
try {
const results = await knex.select(['id', 'name', 'parentId'])
.from('tags')
.where({ 'id': req.params.id });
const response: api.TagDetailsResponse = {
name: results[0].name,
parentId: results[0].parentId,
}
await res.send(response);
} catch (e) {
catchUnhandledErrors(e)
}
}

@ -1,4 +1,6 @@
export type EndpointHandler = (req: any, res: any) => Promise<void>;
import Knex from 'knex';
export type EndpointHandler = (req: any, res: any, knex: Knex) => Promise<void>;
export interface EndpointError {
internalMessage: String;

@ -0,0 +1,3 @@
const environment = process.env.ENVIRONMENT || 'development'
const config = require('../knexfile.js')[environment];
export default require('knex')(config);

@ -0,0 +1,44 @@
// Update with your config settings.
export default {
development: {
client: "sqlite3",
connection: {
filename: "./dev.sqlite3"
}
},
// staging: {
// client: "postgresql",
// connection: {
// database: "my_db",
// user: "username",
// password: "password"
// },
// pool: {
// min: 2,
// max: 10
// },
// migrations: {
// tableName: "knex_migrations"
// }
// },
// production: {
// client: "postgresql",
// connection: {
// database: "my_db",
// user: "username",
// password: "password"
// },
// pool: {
// min: 2,
// max: 10
// },
// migrations: {
// tableName: "knex_migrations"
// }
// }
};

@ -0,0 +1,118 @@
import * as Knex from "knex";
export async function up(knex: Knex): Promise<void> {
// Songs table.
await knex.schema.createTable(
'songs',
(table: any) => {
table.increments('id');
table.string('title');
table.json('storeLinks')
}
)
// Artists table.
await knex.schema.createTable(
'artists',
(table: any) => {
table.increments('id');
table.string('name');
table.json('storeLinks');
}
)
// Albums table.
await knex.schema.createTable(
'albums',
(table: any) => {
table.increments('id');
table.string('name');
table.json('storeLinks');
}
)
// Tags table.
await knex.schema.createTable(
'tags',
(table: any) => {
table.increments('id');
table.string('name');
table.integer('parentId');
}
)
// Songs <-> Artists
await knex.schema.createTable(
'songs_artists',
(table: any) => {
table.increments('id');
table.integer('songId');
table.integer('artistId');
}
)
// Songs <-> Albums
await knex.schema.createTable(
'songs_albums',
(table: any) => {
table.increments('id');
table.integer('songId');
table.integer('albumId');
}
)
// Songs <-> Tags
await knex.schema.createTable(
'songs_tags',
(table: any) => {
table.increments('id');
table.integer('songId');
table.integer('tagId');
}
)
// Artists <-> Tags
await knex.schema.createTable(
'artists_tags',
(table: any) => {
table.increments('id');
table.integer('artistId');
table.integer('tagId');
}
)
// Albums <-> Tags
await knex.schema.createTable(
'albums_tags',
(table: any) => {
table.increments('id');
table.integer('tagId');
table.integer('albumId');
}
)
// Artists <-> Albums
await knex.schema.createTable(
'artists_albums',
(table: any) => {
table.increments('id');
table.integer('artistId');
table.integer('albumId');
}
)
}
export async function down(knex: Knex): Promise<void> {
await knex.schema.dropTable('songs');
await knex.schema.dropTable('artists');
await knex.schema.dropTable('albums');
await knex.schema.dropTable('tags');
await knex.schema.dropTable('songs_artists');
await knex.schema.dropTable('songs_albums');
await knex.schema.dropTable('songs_tags');
await knex.schema.dropTable('artists_tags');
await knex.schema.dropTable('albums_tags');
}

@ -1,14 +0,0 @@
module.exports = (sequelize, DataTypes) => {
var Album = sequelize.define('Album', {
name: DataTypes.STRING,
storeLinks: DataTypes.JSON,
});
Album.associate = function (models) {
models.Album.belongsToMany(models.Song, { through: "SongAlbums" });
models.Album.belongsToMany(models.Artist, { through: "AlbumArtists" });
models.Album.belongsToMany(models.Tag, { through: 'AlbumTags' });
};
return Album;
};

@ -1,14 +0,0 @@
module.exports = (sequelize, DataTypes) => {
var Artist = sequelize.define('Artist', {
name: DataTypes.STRING,
storeLinks: DataTypes.JSON,
});
Artist.associate = function (models) {
models.Artist.belongsToMany(models.Song, { through: "SongArtists" });
models.Artist.belongsToMany(models.Album, { through: "AlbumArtists" });
models.Artist.belongsToMany(models.Tag, { through: 'ArtistTags' });
};
return Artist;
};

@ -1,37 +0,0 @@
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = require(path.join(__dirname, file))(sequelize, Sequelize.DataTypes);
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

@ -1,13 +0,0 @@
module.exports = (sequelize, DataTypes) => {
var Ranking = sequelize.define('Ranking', {
rank: DataTypes.DOUBLE
});
Ranking.associate = function (models) {
models.Ranking.hasOne(models.Tag, { as: 'tagContext' });
models.Ranking.hasOne(models.Artist, { as: 'artistContext' });
models.Ranking.belongsToMany(models.Song, { through: 'SongRankings' });
};
return Ranking;
};

@ -1,15 +0,0 @@
module.exports = (sequelize, DataTypes) => {
var Song = sequelize.define('Song', {
title: DataTypes.STRING,
storeLinks: DataTypes.JSON,
});
Song.associate = function (models) {
models.Song.belongsToMany(models.Artist, { through: "SongArtists" });
models.Song.belongsToMany(models.Album, { through: "SongAlbums" });
models.Song.belongsToMany(models.Tag, { through: 'SongTags' });
models.Song.belongsToMany(models.Ranking, { through: 'SongRankings'});
};
return Song;
};

@ -1,14 +0,0 @@
module.exports = (sequelize, DataTypes) => {
var Tag = sequelize.define('Tag', {
name: DataTypes.STRING,
});
Tag.associate = function (models) {
models.Tag.hasOne(models.Tag, { as: 'parent' });
models.Tag.belongsToMany(models.Artist, { through: 'ArtistTags' });
models.Tag.belongsToMany(models.Album, { through: 'AlbumTags' });
models.Tag.belongsToMany(models.Song, { through: 'SongTags' });
};
return Tag;
};

@ -12,8 +12,7 @@
"chai-http": "^4.3.0",
"express": "^4.16.4",
"jasmine": "^3.5.0",
"sequelize": "^6.3.0",
"sequelize-cli": "^6.2.0",
"knex": "^0.21.5",
"sqlite3": "^5.0.0",
"ts-node": "^8.10.2",
"typescript": "~3.7.2"

@ -1,17 +1,16 @@
const express = require('express');
const bodyParser = require('body-parser');
const models = require('./models');
const environment = process.env.ENVIRONMENT || 'development'
const knexConfig = require('knexfile.js')[environment];
const knex = require('knex')(knexConfig);
import { SetupApp } from './app';
const app = express();
SetupApp(app);
SetupApp(app, knex);
models.sequelize.sync().then(() => {
// TODO: configurable port
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));
})
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));
export { }

@ -1,7 +1,6 @@
const chai = require('chai');
const chaiHttp = require('chai-http');
const express = require('express');
const models = require('../../../models');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
@ -9,8 +8,7 @@ import * as helpers from './helpers';
async function init() {
chai.use(chaiHttp);
const app = express();
SetupApp(app);
await models.sequelize.sync({ force: true });
SetupApp(app, await helpers.initTestDB());
return app;
}

@ -1,7 +1,6 @@
const chai = require('chai');
const chaiHttp = require('chai-http');
const express = require('express');
const models = require('../../../models');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
@ -9,8 +8,7 @@ import * as helpers from './helpers';
async function init() {
chai.use(chaiHttp);
const app = express();
SetupApp(app);
await models.sequelize.sync({ force: true });
SetupApp(app, await helpers.initTestDB());
return app;
}
@ -32,32 +30,23 @@ describe('POST /artist with no name', () => {
describe('POST /artist with a correct request', () => {
it('should succeed', done => {
init().then((app) => {
chai
.request(app)
.post('/artist')
.send({
name: "MyArtist"
})
.then((res) => {
expect(res).to.have.status(200);
expect(res.body).to.deep.equal({
id: 1
});
done();
});
var req = chai.request(app).keepOpen();
helpers.createArtist(req, { name: "MyArtist" }, 200, { id: 1 })
.then(() => helpers.checkArtist(req, 1, 200, { name: "MyArtist", storeLinks: [], tagIds: [] }))
.then(req.close)
.then(done);
});
});
});
describe('PUT /artist on nonexistent artist', () => {
it('should fail', done => {
init().then((app) => {
chai
.request(app)
.put('/artist/1')
.put('/artist/0')
.send({
id: 1,
id: 0,
name: "NewArtistName"
})
.then((res) => {

@ -1,7 +1,6 @@
const chai = require('chai');
const chaiHttp = require('chai-http');
const express = require('express');
const models = require('../../../models');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
@ -9,8 +8,7 @@ import * as helpers from './helpers';
async function init() {
chai.use(chaiHttp);
const app = express();
SetupApp(app);
await models.sequelize.sync({ force: true });
SetupApp(app, await helpers.initTestDB());;
return app;
}

@ -1,7 +1,6 @@
const chai = require('chai');
const chaiHttp = require('chai-http');
const express = require('express');
const models = require('../../../models');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
@ -9,8 +8,7 @@ import * as helpers from './helpers';
async function init() {
chai.use(chaiHttp);
const app = express();
SetupApp(app);
await models.sequelize.sync({ force: true });
SetupApp(app, await helpers.initTestDB());
return app;
}

@ -1,7 +1,6 @@
const chai = require('chai');
const chaiHttp = require('chai-http');
const express = require('express');
const models = require('../../../models');
import { SetupApp } from '../../../app';
import { expect } from 'chai';
import * as helpers from './helpers';
@ -9,8 +8,7 @@ import * as helpers from './helpers';
async function init() {
chai.use(chaiHttp);
const app = express();
SetupApp(app);
await models.sequelize.sync({ force: true });
SetupApp(app, await helpers.initTestDB());
return app;
}

@ -1,5 +1,11 @@
import { expect } from "chai";
export async function initTestDB() {
const knex = await require('knex')({ client: 'sqlite3', connection: ':memory:'})
await knex.migrate.latest();
return knex;
}
export async function createSong(
req,
props = { title: "Song" },

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save