Query endpoint is up again, but no filtering yet.

pull/10/head
Sander Vocke 5 years ago
parent ddbfb3a52c
commit 4471c27b4c
  1. 151
      server/endpoints/QueryEndpointHandler.ts
  2. 4
      server/knex/knex.ts
  3. 2
      server/knexfile.ts
  4. 7
      server/server.ts

@ -1,10 +1,98 @@
import * as api from '../../client/src/api';
import { EndpointError, EndpointHandler, catchUnhandledErrors } from './types';
import Knex from 'knex';
enum QueryType {
enum ObjectType {
Song = 0,
Artist,
Tag,
Album,
}
// To keep track of which database objects are needed to filter on
// certain properties.
const propertyObjects: Record<api.QueryElemProperty, ObjectType> = {
[api.QueryElemProperty.albumName]: ObjectType.Album,
[api.QueryElemProperty.artistId]: ObjectType.Artist,
[api.QueryElemProperty.artistName]: ObjectType.Artist,
[api.QueryElemProperty.songId]: ObjectType.Song,
[api.QueryElemProperty.songTitle]: ObjectType.Song,
}
// To keep track of the tables in which objects are stored.
const objectTables: Record<ObjectType, string> = {
[ObjectType.Album]: 'albums',
[ObjectType.Artist]: 'artists',
[ObjectType.Song]: 'songs',
[ObjectType.Tag]: 'tags',
}
// To keep track of linking tables between objects.
const linkingTables: any = [
[[ObjectType.Song, ObjectType.Album], 'songs_albums'],
[[ObjectType.Song, ObjectType.Artist], 'songs_artists'],
[[ObjectType.Song, ObjectType.Tag], 'songs_tags'],
[[ObjectType.Artist, ObjectType.Album], 'artists_albums'],
[[ObjectType.Artist, ObjectType.Tag], 'artists_tags'],
[[ObjectType.Album, ObjectType.Tag], 'albums_tags'],
]
function getLinkingTable(a: ObjectType, b: ObjectType): string {
var res: string | undefined = undefined;
linkingTables.forEach((row: any) => {
if (row[0].includes(a) && row[0].includes(b)) {
res = row[1];
}
})
if (res) return res;
throw "Could not find linking table for objects: " + JSON.stringify(a) + ", " + JSON.stringify(b);
}
// To keep track of ID fields used in linking tables.
const linkingTableIdNames: Record<ObjectType, string> = {
[ObjectType.Album]: 'albumId',
[ObjectType.Artist]: 'artistId',
[ObjectType.Song]: 'songId',
[ObjectType.Tag]: 'tagId',
}
function getRequiredDatabaseObjects(queryElem: api.QueryElem): Set<ObjectType> {
if (queryElem.prop) {
// Leaf node.
return new Set([propertyObjects[queryElem.prop]]);
} else if (queryElem.children) {
// Branch node.
var r = new Set<ObjectType>();
queryElem.children.forEach((child: api.QueryElem) => {
getRequiredDatabaseObjects(child).forEach(object => r.add(object));
});
return r;
}
return new Set([]);
}
function addJoin(knexQuery: any, base: ObjectType, other: ObjectType) {
const linkTable = getLinkingTable(base, other);
const baseTable = objectTables[base];
const otherTable = objectTables[other];
return knexQuery
.join(linkTable, { [baseTable + '.id']: linkTable + '.' + linkingTableIdNames[base] })
.join(otherTable, { [otherTable + '.id']: linkTable + '.' + linkingTableIdNames[other] });
}
function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryElem) {
// First, we create a base query for the type of object we need to yield.
var q = knex.select('*').distinct().from(objectTables[queryFor]);
// Now, we need to add join statements for other objects we want to filter on.
const allObjects = getRequiredDatabaseObjects(queryElem);
allObjects.delete(queryFor); // We are already querying this object in the base query.
allObjects.forEach((object: ObjectType) => {
q = addJoin(q, queryFor, object);
})
// TODO: apply filters.
return q;
}
// const sequelizeOps: any = {
@ -89,7 +177,7 @@ enum QueryType {
// ];
// }
export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any) => {
export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, knex: Knex) => {
if (!api.checkQueryRequest(req.body)) {
const e: EndpointError = {
internalMessage: 'Invalid Query request: ' + JSON.stringify(req.body),
@ -99,6 +187,63 @@ 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;
const artistsPromise: Promise<any> = (artistLimit && artistLimit > 0) ?
constructQuery(knex, ObjectType.Artist, reqObject.query)
.offset(artistOffset || 0).limit(artistLimit) :
(async () => [])();
const songsPromise: Promise<any> = (songLimit && songLimit > 0) ?
constructQuery(knex, ObjectType.Song, reqObject.query)
.offset(songOffset || 0).limit(songLimit) :
(async () => [])();
const tagsPromise: Promise<any> = (tagLimit && tagLimit > 0) ?
constructQuery(knex, ObjectType.Tag, reqObject.query)
.offset(tagOffset || 0).limit(tagLimit) :
(async () => [])();
const [songs, artists, tags] = await Promise.all([songsPromise, artistsPromise, tagsPromise]);
const response: api.QueryResponse = {
songs: songs.map((song: any) => {
return <api.SongDetails>{
songId: song['id'],
title: song['title'],
storeLinks: JSON.parse(song['storeLinks']),
artists: [], //FIXME
tags: [], //FIXME
albums: [], //FIXME
}
}),
artists: artists.map((artist: any) => {
return <api.ArtistDetails>{
artistId: artist['id'],
name: artist['name'],
storeLinks: JSON.parse(artist['storeLinks']),
}
}),
tags: tags.map((tag: any) => {
return <api.TagDetails>{
tagId: tag['id'],
name: tag['name'],
parentId: tag['parentId'],
}
}),
}
res.send(response);
} catch (e) {
catchUnhandledErrors(e);
}
// try {
// const songLimit = reqObject.offsetsLimits.songLimit;
// const songOffset = reqObject.offsetsLimits.songOffset;
@ -188,6 +333,4 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any)
// } catch (e) {
// catchUnhandledErrors(e);
// }
throw "NOTIMPLEMENTED";
}

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

@ -1,6 +1,6 @@
// Update with your config settings.
export default {
export default <Record<string,any>> {
development: {
client: "sqlite3",

@ -1,16 +1,15 @@
const express = require('express');
const environment = process.env.ENVIRONMENT || 'development'
const knexConfig = require('knexfile.js')[environment];
const knex = require('knex')(knexConfig);
import knex from './knex/knex';
import { SetupApp } from './app';
const app = express();
knex.migrate.latest().then(() => {
SetupApp(app, knex);
const port = process.env.PORT || 5000;
app.listen(port, () => console.log(`Listening on port ${port}`));
})
export { }
Loading…
Cancel
Save