From c28db21b188714be732036855eb8119242eb09af Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Thu, 26 Nov 2020 10:59:25 +0100 Subject: [PATCH] Expand query to support different levels of response detail. --- client/src/api.ts | 17 ++-- server/endpoints/Query.ts | 63 +++++++++++---- server/test/integration/flows/QueryFlow.js | 93 ++++++++++++++++++---- 3 files changed, 139 insertions(+), 34 deletions(-) diff --git a/client/src/api.ts b/client/src/api.ts index 99733cb..01ce335 100644 --- a/client/src/api.ts +++ b/client/src/api.ts @@ -88,7 +88,12 @@ export enum QueryElemProperty { albumStoreLinks = "albumStoreLinks", //Note: treated as a JSON string for filter operations } export enum OrderByType { - Name = 0, + Name = 'name', +} +export enum QueryResponseType { + Details = 'details', // Returns detailed result items. + Ids = 'ids', // Returns IDs only. + Count = 'count', // Returns an item count only. } export interface QueryElem { prop?: QueryElemProperty, @@ -108,12 +113,13 @@ export interface QueryRequest { query: Query, offsetsLimits: OffsetsLimits, ordering: Ordering, + responseType: QueryResponseType } export interface QueryResponse { - songs: SongDetails[], - artists: ArtistDetails[], - tags: TagDetails[], - albums: AlbumDetails[], + songs: SongDetails[] | number[] | number, // Details | IDs | count, depending on QueryResponseType + artists: ArtistDetails[] | number[] | number, + tags: TagDetails[] | number[] | number, + albums: AlbumDetails[] | number[] | number, } export interface OffsetsLimits { songOffset?: number, @@ -141,6 +147,7 @@ export function checkQueryRequest(req: any): boolean { return 'query' in req && 'offsetsLimits' in req && 'ordering' in req + && 'responseType' in req && checkQueryElem(req.query); } diff --git a/server/endpoints/Query.ts b/server/endpoints/Query.ts index 36715ec..13caa2d 100644 --- a/server/endpoints/Query.ts +++ b/server/endpoints/Query.ts @@ -220,7 +220,7 @@ function constructQuery(knex: Knex, userId: number, queryFor: ObjectType, queryE (ordering.ascending ? 'asc' : 'desc')); // Apply limiting. - if(limit !== null) { + if (limit !== null) { q = q.limit(limit) } @@ -383,20 +383,53 @@ export const Query: EndpointHandler = async (req: any, res: any, knex: Knex) => songsAlbumsPromise, ]); - const response: api.QueryResponse = { - songs: songs.map((song: any) => { - const id = song['songs.id']; - return toApiSong(song, songsArtists[id], songsTags[id], songsAlbums[id]); - }), - artists: artists.map((artist: any) => { - return toApiArtist(artist); - }), - albums: albums.map((album: any) => { - return toApiAlbum(album); - }), - tags: tags.map((tag: any) => { - return toApiTag(tag); - }), + var response: api.QueryResponse = { + songs: [], + artists: [], + albums: [], + tags: [], + }; + + switch (reqObject.responseType) { + case api.QueryResponseType.Details: { + response = { + songs: songs.map((song: any) => { + const id = song['songs.id']; + return toApiSong(song, songsArtists[id], songsTags[id], songsAlbums[id]); + }), + artists: artists.map((artist: any) => { + return toApiArtist(artist); + }), + albums: albums.map((album: any) => { + return toApiAlbum(album); + }), + tags: tags.map((tag: any) => { + return toApiTag(tag); + }), + }; + break; + } + case api.QueryResponseType.Ids: { + response = { + songs: songs.map((song: any) => song['songs.id']), + artists: artists.map((artist: any) => artist['artists.id']), + albums: albums.map((album: any) => album['albums.id']), + tags: tags.map((tag: any) => tag['tags.id']), + }; + break; + } + case api.QueryResponseType.Count: { + response = { + songs: songs.length, + artists: artists.length, + albums: albums.length, + tags: tags.length, + }; + break; + } + default: { + throw new Error("Unimplemented response type.") + } } console.log("Query repsonse", response); diff --git a/server/test/integration/flows/QueryFlow.js b/server/test/integration/flows/QueryFlow.js index c98d037..9da8da6 100644 --- a/server/test/integration/flows/QueryFlow.js +++ b/server/test/integration/flows/QueryFlow.js @@ -39,10 +39,11 @@ describe('POST /query with no songs', () => { }, 'ordering': { 'orderBy': { - 'type': 0, + 'type': 'name', }, 'ascending': true - } + }, + 'responseType': 'details', }) expect(res).to.have.status(200); expect(res.body).to.deep.equal({ @@ -114,10 +115,11 @@ describe('POST /query with several songs and filters', () => { }, 'ordering': { 'orderBy': { - 'type': 0, + 'type': 'name', }, 'ascending': true - } + }, + 'responseType': 'details', }) .then((res) => { expect(res).to.have.status(200); @@ -145,10 +147,11 @@ describe('POST /query with several songs and filters', () => { }, 'ordering': { 'orderBy': { - 'type': 0, + 'type': 'name', }, 'ascending': true - } + }, + 'responseType': 'details', }) .then((res) => { expect(res).to.have.status(200); @@ -176,10 +179,11 @@ describe('POST /query with several songs and filters', () => { }, 'ordering': { 'orderBy': { - 'type': 0, + 'type': 'name', }, 'ascending': true - } + }, + 'responseType': 'details', }) .then((res) => { expect(res).to.have.status(200); @@ -207,10 +211,11 @@ describe('POST /query with several songs and filters', () => { }, 'ordering': { 'orderBy': { - 'type': 0, + 'type': 'name', }, 'ascending': true - } + }, + 'responseType': 'details', }) .then((res) => { expect(res).to.have.status(200); @@ -248,10 +253,11 @@ describe('POST /query with several songs and filters', () => { }, 'ordering': { 'orderBy': { - 'type': 0, + 'type': 'name', }, 'ascending': true - } + }, + 'responseType': 'details', }) .then((res) => { expect(res).to.have.status(200); @@ -279,10 +285,11 @@ describe('POST /query with several songs and filters', () => { }, 'ordering': { 'orderBy': { - 'type': 0, + 'type': 'name', }, 'ascending': true - } + }, + 'responseType': 'details', }) .then((res) => { expect(res).to.have.status(200); @@ -295,6 +302,62 @@ describe('POST /query with several songs and filters', () => { }); } + async function checkResponseTypeIds(req) { + await req + .post('/query') + .send({ + "query": {}, + 'offsetsLimits': { + 'songOffset': 0, + 'songLimit': 10, + }, + 'ordering': { + 'orderBy': { + 'type': 'name', + }, + 'ascending': true + }, + 'responseType': 'ids', + }) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.deep.equal({ + songs: [song1.songId, song2.songId, song3.songId], + artists: [], + tags: [], + albums: [], + }); + }); + } + + async function checkResponseTypeCount(req) { + await req + .post('/query') + .send({ + "query": {}, + 'offsetsLimits': { + 'songOffset': 0, + 'songLimit': 10, + }, + 'ordering': { + 'orderBy': { + 'type': 'name', + }, + 'ascending': true + }, + 'responseType': 'count', + }) + .then((res) => { + expect(res).to.have.status(200); + expect(res.body).to.deep.equal({ + songs: 3, + artists: 0, + tags: 0, + albums: 0, + }); + }); + } + let agent = await init(); let req = agent.keepOpen(); try { @@ -309,6 +372,8 @@ describe('POST /query with several songs and filters', () => { await checkArtistIdIn(req); await checkOrRelation(req); await checkStoreLinksLike(req); + await checkResponseTypeCount(req); + await checkResponseTypeIds(req); } finally { req.close(); agent.close();