From 1c098ab2241b7ece1a88caf3d1def4fa27e99760 Mon Sep 17 00:00:00 2001 From: Sander Vocke Date: Tue, 29 Sep 2020 17:03:32 +0200 Subject: [PATCH] Starting to work on rendering tag changes. --- .../components/windows/album/AlbumWindow.tsx | 80 ++++---------- .../windows/artist/ArtistWindow.tsx | 80 ++++---------- .../windows/manage_tags/ManageTagsWindow.tsx | 55 +++------- .../windows/manage_tags/TagChange.tsx | 39 +++++++ .../components/windows/query/QueryWindow.tsx | 102 ++++++++++++------ .../components/windows/song/SongWindow.tsx | 41 ++----- .../src/components/windows/tag/TagWindow.tsx | 101 ++++++----------- .../lib/query/{Getters.tsx => Backend.tsx} | 64 +++++------ client/src/lib/query/Query.tsx | 14 ++- server/endpoints/QueryEndpointHandler.ts | 31 +++--- 10 files changed, 259 insertions(+), 348 deletions(-) create mode 100644 client/src/components/windows/manage_tags/TagChange.tsx rename client/src/lib/query/{Getters.tsx => Backend.tsx} (67%) diff --git a/client/src/components/windows/album/AlbumWindow.tsx b/client/src/components/windows/album/AlbumWindow.tsx index 2fd709c..0f4070e 100644 --- a/client/src/components/windows/album/AlbumWindow.tsx +++ b/client/src/components/windows/album/AlbumWindow.tsx @@ -8,6 +8,8 @@ import EditableText from '../../common/EditableText'; import SubmitChangesButton from '../../common/SubmitChangesButton'; import SongTable, { SongGetters } from '../../tables/ResultsTable'; import { saveAlbumChanges } from '../../../lib/saveChanges'; +import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query'; +import { queryAlbums, querySongs } from '../../../lib/query/Backend'; var _ = require('lodash'); export type AlbumMetadata = serverApi.AlbumDetails; @@ -50,38 +52,15 @@ export interface IProps { } export async function getAlbumMetadata(id: number) { - const query = { - prop: serverApi.QueryElemProperty.albumId, - propOperand: id, - propOperator: serverApi.QueryFilterOp.Eq, - }; - - var q: serverApi.QueryRequest = { - query: query, - offsetsLimits: { - albumOffset: 0, - albumLimit: 1, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, + return (await queryAlbums({ + query: { + a: QueryLeafBy.AlbumId, + b: id, + leafOp: QueryLeafOp.Equals, }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - - return (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); - let album = json.albums[0]; - return album; - })(); + offset: 0, + limit: 1, + }))[0]; } export default function AlbumWindow(props: IProps) { @@ -103,37 +82,20 @@ export default function AlbumWindow(props: IProps) { useEffect(() => { if (props.state.songsOnAlbum) { return; } - var q: serverApi.QueryRequest = { - query: { - prop: serverApi.QueryElemProperty.albumId, - propOperator: serverApi.QueryFilterOp.Eq, - propOperand: props.state.albumId, - }, - offsetsLimits: { - songOffset: 0, - songLimit: 100, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, - }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); + const songs = await querySongs({ + query: { + a: QueryLeafBy.AlbumId, + b: props.state.albumId, + leafOp: QueryLeafOp.Equals, + }, + offset: 0, + limit: -1, + }); props.dispatch({ type: AlbumWindowStateActions.SetSongs, - value: json.songs, - }); + value: songs, + }); })(); }, [props.state.songsOnAlbum]); diff --git a/client/src/components/windows/artist/ArtistWindow.tsx b/client/src/components/windows/artist/ArtistWindow.tsx index 263f6e3..9e0c3af 100644 --- a/client/src/components/windows/artist/ArtistWindow.tsx +++ b/client/src/components/windows/artist/ArtistWindow.tsx @@ -8,6 +8,8 @@ import EditableText from '../../common/EditableText'; import SubmitChangesButton from '../../common/SubmitChangesButton'; import SongTable, { SongGetters } from '../../tables/ResultsTable'; import { saveArtistChanges } from '../../../lib/saveChanges'; +import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query'; +import { queryArtists, querySongs } from '../../../lib/query/Backend'; var _ = require('lodash'); export type ArtistMetadata = serverApi.ArtistDetails; @@ -50,38 +52,15 @@ export interface IProps { } export async function getArtistMetadata(id: number) { - const query = { - prop: serverApi.QueryElemProperty.artistId, - propOperand: id, - propOperator: serverApi.QueryFilterOp.Eq, - }; - - var q: serverApi.QueryRequest = { - query: query, - offsetsLimits: { - artistOffset: 0, - artistLimit: 1, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, + return (await queryArtists({ + query: { + a: QueryLeafBy.ArtistId, + b: id, + leafOp: QueryLeafOp.Equals, }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - - return (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); - let artist = json.artists[0]; - return artist; - })(); + offset: 0, + limit: 1, + }))[0]; } export default function ArtistWindow(props: IProps) { @@ -103,37 +82,20 @@ export default function ArtistWindow(props: IProps) { useEffect(() => { if (props.state.songsByArtist) { return; } - var q: serverApi.QueryRequest = { - query: { - prop: serverApi.QueryElemProperty.artistId, - propOperator: serverApi.QueryFilterOp.Eq, - propOperand: props.state.artistId, - }, - offsetsLimits: { - songOffset: 0, - songLimit: 100, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, - }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); + const songs = await querySongs({ + query: { + a: QueryLeafBy.ArtistId, + b: props.state.artistId, + leafOp: QueryLeafOp.Equals, + }, + offset: 0, + limit: -1, + }); props.dispatch({ type: ArtistWindowStateActions.SetSongs, - value: json.songs, - }); + value: songs, + }); })(); }, [props.state.songsByArtist]); diff --git a/client/src/components/windows/manage_tags/ManageTagsWindow.tsx b/client/src/components/windows/manage_tags/ManageTagsWindow.tsx index d77468a..bae7ba5 100644 --- a/client/src/components/windows/manage_tags/ManageTagsWindow.tsx +++ b/client/src/components/windows/manage_tags/ManageTagsWindow.tsx @@ -6,24 +6,10 @@ import LoyaltyIcon from '@material-ui/icons/Loyalty'; import ArrowRightIcon from '@material-ui/icons/ArrowRight'; import ArrowDropDownIcon from '@material-ui/icons/ArrowDropDown'; import ManageTagMenu from './ManageTagMenu'; +import ControlTagChanges, { TagChange, TagChangeType } from './TagChange'; +import { queryTags } from '../../../lib/query/Backend'; var _ = require('lodash'); -export enum TagChangeType { - Delete = "Delete", - Create = "Create", - MoveTo = "MoveTo", - MergeTo = "MergeTo", - Rename = "Rename", -} - -export interface TagChange { - type: TagChangeType, - id: number, // MuDBase ID. If not in database yet, negative IDs will be used until submitted. - parent?: number | null, // MuDBase ID. If not in database yet, negative IDs will be used until submitted. - // null refers to the tags root. - name?: string, -} - export interface ManageTagsWindowState extends WindowState { fetchedTags: Record | null, pendingChanges: TagChange[], @@ -68,32 +54,14 @@ export function organiseTags(allTags: Record, fromId: number | null } export async function getAllTags() { - // Build a request to fetch all tags. - var q: serverApi.QueryRequest = { - query: {}, - offsetsLimits: { - tagOffset: 0, - tagLimit: 1000, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, - }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - return (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); var retval: Record = {}; - json.tags.forEach((tag: any) => { + const tags = await queryTags({ + query: undefined, + offset: 0, + limit: -1, + }); + tags.forEach((tag: any) => { retval[tag.tagId] = tag; }); return retval; @@ -305,6 +273,13 @@ export default function ManageTagsWindow(props: IProps) { > Manage Tags + + + ; + + switch (props.change.type) { + case TagChangeType.Delete: + return Delete {tag} + default: + throw new Error("Unhandled tag change type") + } +} + +export default function ControlTagChanges(props: { + changes: TagChange[], +}) { + return <> + {props.changes.map((change: any) => )} + +} \ No newline at end of file diff --git a/client/src/components/windows/query/QueryWindow.tsx b/client/src/components/windows/query/QueryWindow.tsx index 1a3fe75..2ec8f76 100644 --- a/client/src/components/windows/query/QueryWindow.tsx +++ b/client/src/components/windows/query/QueryWindow.tsx @@ -1,11 +1,11 @@ import React, { useEffect } from 'react'; import { createMuiTheme, Box, LinearProgress } from '@material-ui/core'; -import { QueryElem, toApiQuery } from '../../../lib/query/Query'; +import { QueryElem, toApiQuery, QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query'; import QueryBuilder from '../../querybuilder/QueryBuilder'; import * as serverApi from '../../../api'; import SongTable from '../../tables/ResultsTable'; import { songGetters } from '../../../lib/songGetters'; -import { getArtists, getSongTitles, getAlbums, getTags } from '../../../lib/query/Getters'; +import { queryArtists, querySongs, queryAlbums, queryTags } from '../../../lib/query/Backend'; import { grey } from '@material-ui/core/colors'; import { WindowState } from '../Windows'; var _ = require('lodash'); @@ -36,6 +36,56 @@ export enum QueryWindowStateActions { SetResultsForQuery = "setResultsForQuery", } +async function getArtistNames(filter: string) { + const artists = await queryArtists({ + query: filter.length > 0 ? { + a: QueryLeafBy.ArtistName, + b: '%' + filter + '%', + leafOp: QueryLeafOp.Like + } : undefined, + offset: 0, + limit: -1, + }); + + return [...(new Set([...(artists.map((a:any) => a.name))]))]; +} + +async function getAlbumNames(filter: string) { + const albums = await queryAlbums({ + query: filter.length > 0 ? { + a: QueryLeafBy.AlbumName, + b: '%' + filter + '%', + leafOp: QueryLeafOp.Like + } : undefined, + offset: 0, + limit: -1, + }); + + return [...(new Set([...(albums.map((a:any) => a.name))]))]; +} + +async function getSongTitles(filter: string) { + const songs = await querySongs({ + query: filter.length > 0 ? { + a: QueryLeafBy.SongTitle, + b: '%' + filter + '%', + leafOp: QueryLeafOp.Like + } : undefined, + offset: 0, + limit: -1, + }); + + return [...(new Set([...(songs.map((s:any) => s.title))]))]; +} + +async function getTagItems() { + return await queryTags({ + query: undefined, + offset: 0, + limit: -1, + }); +} + export function QueryWindowReducer(state: QueryWindowState, action: any) { switch (action.type) { case QueryWindowStateActions.SetQuery: @@ -73,36 +123,18 @@ export default function QueryWindow(props: IProps) { const showResults = (query && resultsFor && query == resultsFor.for) ? resultsFor.results : []; const doQuery = async (_query: QueryElem) => { - var q: serverApi.QueryRequest = { - query: toApiQuery(_query), - offsetsLimits: { - songOffset: 0, - songLimit: 100, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, - }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - - return (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); - if (_.isEqual(query, _query)) { - setResultsForQuery({ - for: _query, - results: json.songs, - }) - } - })(); + const songs = await querySongs({ + query: _query, + offset: 0, + limit: 100, //TODO: pagination + }); + + if (_.isEqual(query, _query)) { + setResultsForQuery({ + for: _query, + results: songs, + }) + } } useEffect(() => { @@ -124,10 +156,10 @@ export default function QueryWindow(props: IProps) { editing={editing} onChangeEditing={setEditingQuery} requestFunctions={{ - getArtists: getArtists, + getArtists: getArtistNames, getSongTitles: getSongTitles, - getAlbums: getAlbums, - getTags: getTags, + getAlbums: getAlbumNames, + getTags: getTagItems, }} /> diff --git a/client/src/components/windows/song/SongWindow.tsx b/client/src/components/windows/song/SongWindow.tsx index cae3a1f..ab304c1 100644 --- a/client/src/components/windows/song/SongWindow.tsx +++ b/client/src/components/windows/song/SongWindow.tsx @@ -11,6 +11,8 @@ import StoreLinkIcon, { whichStore } from '../../common/StoreLinkIcon'; import EditableText from '../../common/EditableText'; import SubmitChangesButton from '../../common/SubmitChangesButton'; import { saveSongChanges } from '../../../lib/saveChanges'; +import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query'; +import { querySongs } from '../../../lib/query/Backend'; export type SongMetadata = serverApi.SongDetails; export type SongMetadataChanges = serverApi.ModifySongRequest; @@ -47,38 +49,15 @@ export interface IProps { } export async function getSongMetadata(id: number) { - const query = { - prop: serverApi.QueryElemProperty.songId, - propOperand: id, - propOperator: serverApi.QueryFilterOp.Eq, - }; - - var q: serverApi.QueryRequest = { - query: query, - offsetsLimits: { - songOffset: 0, - songLimit: 1, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, + return (await querySongs({ + query: { + a: QueryLeafBy.SongId, + b: id, + leafOp: QueryLeafOp.Equals, }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - - return (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); - let song = json.songs[0]; - return song; - })(); + offset: 0, + limit: 1, + }))[0]; } export default function SongWindow(props: IProps) { diff --git a/client/src/components/windows/tag/TagWindow.tsx b/client/src/components/windows/tag/TagWindow.tsx index 8de6115..19499b8 100644 --- a/client/src/components/windows/tag/TagWindow.tsx +++ b/client/src/components/windows/tag/TagWindow.tsx @@ -8,6 +8,8 @@ import EditableText from '../../common/EditableText'; import SubmitChangesButton from '../../common/SubmitChangesButton'; import SongTable, { SongGetters } from '../../tables/ResultsTable'; import { saveTagChanges } from '../../../lib/saveChanges'; +import { queryTags, querySongs } from '../../../lib/query/Backend'; +import { QueryLeafBy, QueryLeafOp } from '../../../lib/query/Query'; var _ = require('lodash'); export interface FullTagMetadata extends serverApi.TagDetails { @@ -55,49 +57,27 @@ export interface IProps { } export async function getTagMetadata(id: number) { - const query = { - prop: serverApi.QueryElemProperty.tagId, - propOperand: id, - propOperator: serverApi.QueryFilterOp.Eq, - }; - - var q: serverApi.QueryRequest = { - query: query, - offsetsLimits: { - tagOffset: 0, - tagLimit: 1, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, + var tag = (await queryTags({ + query: { + a: QueryLeafBy.TagId, + b: id, + leafOp: QueryLeafOp.Equals, }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - - return (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); - let tag = json.tags[0]; - - // Recursively fetch parent tags to build the full metadata. - if (tag.parentId) { - const parent = await getTagMetadata(tag.parentId); - tag.fullName = [...parent.fullName, tag.name]; - tag.fullId = [...parent.fullId, tag.tagId]; - } else { - tag.fullName = [tag.name]; - tag.fullId = [tag.tagId]; - } + offset: 0, + limit: 1, + }))[0]; + + // Recursively fetch parent tags to build the full metadata. + if (tag.parentId) { + const parent = await getTagMetadata(tag.parentId); + tag.fullName = [...parent.fullName, tag.name]; + tag.fullId = [...parent.fullId, tag.tagId]; + } else { + tag.fullName = [tag.name]; + tag.fullId = [tag.tagId]; + } - return tag; - })(); + return tag; } export default function TagWindow(props: IProps) { @@ -119,37 +99,20 @@ export default function TagWindow(props: IProps) { useEffect(() => { if (props.state.songsWithTag) { return; } - var q: serverApi.QueryRequest = { - query: { - prop: serverApi.QueryElemProperty.tagId, - propOperator: serverApi.QueryFilterOp.Eq, - propOperand: props.state.tagId, - }, - offsetsLimits: { - songOffset: 0, - songLimit: 100, - }, - ordering: { - orderBy: { - type: serverApi.OrderByType.Name, - }, - ascending: true, - }, - }; - - const requestOpts = { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify(q), - }; - (async () => { - const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) - let json: any = await response.json(); + const songs = await querySongs({ + query: { + a: QueryLeafBy.TagId, + b: props.state.tagId, + leafOp: QueryLeafOp.Equals, + }, + offset: 0, + limit: -1, + }); props.dispatch({ type: TagWindowStateActions.SetSongs, - value: json.songs, - }); + value: songs, + }); })(); }, [props.state.songsWithTag]); diff --git a/client/src/lib/query/Getters.tsx b/client/src/lib/query/Backend.tsx similarity index 67% rename from client/src/lib/query/Getters.tsx rename to client/src/lib/query/Backend.tsx index 8d16129..21ad647 100644 --- a/client/src/lib/query/Getters.tsx +++ b/client/src/lib/query/Backend.tsx @@ -1,17 +1,18 @@ import * as serverApi from '../../api'; +import { QueryElem, toApiQuery } from './Query'; -export async function getArtists(filter: string) { - const query = filter.length > 0 ? { - prop: serverApi.QueryElemProperty.artistName, - propOperand: filter, - propOperator: serverApi.QueryFilterOp.Like, - } : {}; +export interface QueryArgs { + query?: QueryElem, + offset: number, + limit: number, +} +export async function queryArtists(args: QueryArgs) { var q: serverApi.QueryRequest = { - query: query, + query: args.query ? toApiQuery(args.query) : {}, offsetsLimits: { - artistOffset: 0, - artistLimit: 100, + artistOffset: args.offset, + artistLimit: args.limit, }, ordering: { orderBy: { @@ -30,23 +31,16 @@ export async function getArtists(filter: string) { return (async () => { const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) let json: any = await response.json(); - const names: string[] = json.artists.map((elem: any) => { return elem.name; }); - return [...new Set(names)]; + return json.artists; })(); } -export async function getAlbums(filter: string) { - const query = filter.length > 0 ? { - prop: serverApi.QueryElemProperty.albumName, - propOperand: filter, - propOperator: serverApi.QueryFilterOp.Like, - } : {}; - +export async function queryAlbums(args: QueryArgs) { var q: serverApi.QueryRequest = { - query: query, + query: args.query ? toApiQuery(args.query) : {}, offsetsLimits: { - albumOffset: 0, - albumLimit: 100, + albumOffset: args.offset, + albumLimit: args.limit, }, ordering: { orderBy: { @@ -65,23 +59,16 @@ export async function getAlbums(filter: string) { return (async () => { const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) let json: any = await response.json(); - const names: string[] = json.albums.map((elem: any) => { return elem.name; }); - return [...new Set(names)]; + return json.albums; })(); } -export async function getSongTitles(filter: string) { - const query = filter.length > 0 ? { - prop: serverApi.QueryElemProperty.songTitle, - propOperand: filter, - propOperator: serverApi.QueryFilterOp.Like, - } : {}; - +export async function querySongs(args: QueryArgs) { var q: serverApi.QueryRequest = { - query: query, + query: args.query ? toApiQuery(args.query) : {}, offsetsLimits: { - songOffset: 0, - songLimit: 100, + songOffset: args.offset, + songLimit: args.limit, }, ordering: { orderBy: { @@ -100,17 +87,16 @@ export async function getSongTitles(filter: string) { return (async () => { const response = await fetch((process.env.REACT_APP_BACKEND || "") + serverApi.QueryEndpoint, requestOpts) let json: any = await response.json(); - const titles: string[] = json.songs.map((elem: any) => { return elem.title; }); - return [...new Set(titles)]; + return json.songs; })(); } -export async function getTags() { +export async function queryTags(args: QueryArgs) { var q: serverApi.QueryRequest = { - query: {}, + query: args.query ? toApiQuery(args.query) : {}, offsetsLimits: { - tagOffset: 0, - tagLimit: 100, + tagOffset: args.offset, + tagLimit: args.limit, }, ordering: { orderBy: { diff --git a/client/src/lib/query/Query.tsx b/client/src/lib/query/Query.tsx index 9109cfd..225d118 100644 --- a/client/src/lib/query/Query.tsx +++ b/client/src/lib/query/Query.tsx @@ -2,9 +2,13 @@ import * as serverApi from '../../api'; export enum QueryLeafBy { ArtistName = 0, + ArtistId, AlbumName, + AlbumId, TagInfo, - SongTitle + TagId, + SongTitle, + SongId, } export enum QueryLeafOp { @@ -14,11 +18,11 @@ export enum QueryLeafOp { } export interface TagQueryInfo { - fullName: string[], matchIds: number[], + fullName: string[], } export function isTagQueryInfo(e: any): e is TagQueryInfo { - return (typeof e === 'object') && 'fullName' in e && 'matchIds' in e; + return (typeof e === 'object') && 'matchIds' in e && 'fullName' in e; } export type QueryLeafOperand = string | number | TagQueryInfo; @@ -166,6 +170,10 @@ export function toApiQuery(q: QueryElem) : serverApi.Query { [QueryLeafBy.SongTitle]: serverApi.QueryElemProperty.songTitle, [QueryLeafBy.ArtistName]: serverApi.QueryElemProperty.artistName, [QueryLeafBy.AlbumName]: serverApi.QueryElemProperty.albumName, + [QueryLeafBy.AlbumId]: serverApi.QueryElemProperty.albumId, + [QueryLeafBy.ArtistId]: serverApi.QueryElemProperty.artistId, + [QueryLeafBy.TagId]: serverApi.QueryElemProperty.tagId, + [QueryLeafBy.SongId]: serverApi.QueryElemProperty.songId, } const leafOpsMapping: any = { [QueryLeafOp.Equals]: serverApi.QueryFilterOp.Eq, diff --git a/server/endpoints/QueryEndpointHandler.ts b/server/endpoints/QueryEndpointHandler.ts index 00ad1d5..89fb35d 100644 --- a/server/endpoints/QueryEndpointHandler.ts +++ b/server/endpoints/QueryEndpointHandler.ts @@ -179,7 +179,7 @@ const objectColumns = { }; function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryElem, ordering: api.Ordering, - offset: number, limit: number) { + offset: number, limit: number | null) { const joinObjects = getRequiredDatabaseObjects(queryElem); joinObjects.delete(queryFor); // We are already querying this object in the base query. @@ -213,7 +213,12 @@ function constructQuery(knex: Knex, queryFor: ObjectType, queryElem: api.QueryEl (ordering.ascending ? 'asc' : 'desc')); // Apply limiting. - q = q.limit(limit).offset(offset); + if(limit !== null) { + q = q.limit(limit) + } + + // Apply offsetting. + q = q.offset(offset); return q; } @@ -271,43 +276,43 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, const albumLimit = reqObject.offsetsLimits.albumLimit; const albumOffset = reqObject.offsetsLimits.albumOffset; - const artistsPromise: Promise = (artistLimit && artistLimit > 0) ? + const artistsPromise: Promise = (artistLimit && artistLimit !== 0) ? constructQuery(knex, ObjectType.Artist, reqObject.query, reqObject.ordering, artistOffset || 0, - artistLimit + artistLimit >= 0 ? artistLimit : null, ) : (async () => [])(); - const albumsPromise: Promise = (albumLimit && albumLimit > 0) ? + const albumsPromise: Promise = (albumLimit && albumLimit !== 0) ? constructQuery(knex, ObjectType.Album, reqObject.query, reqObject.ordering, artistOffset || 0, - albumLimit + albumLimit >= 0 ? albumLimit : null, ) : (async () => [])(); - const songsPromise: Promise = (songLimit && songLimit > 0) ? + const songsPromise: Promise = (songLimit && songLimit !== 0) ? constructQuery(knex, ObjectType.Song, reqObject.query, reqObject.ordering, songOffset || 0, - songLimit + songLimit >= 0 ? songLimit : null, ) : (async () => [])(); - const tagsPromise: Promise = (tagLimit && tagLimit > 0) ? + const tagsPromise: Promise = (tagLimit && tagLimit !== 0) ? constructQuery(knex, ObjectType.Tag, reqObject.query, reqObject.ordering, tagOffset || 0, - tagLimit + tagLimit >= 0 ? tagLimit : null, ) : (async () => [])(); @@ -318,12 +323,12 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, const ids = songs.map((song: any) => song['songs.id']); return ids; })(); - const songsArtistsPromise: Promise> = (songLimit && songLimit > 0) ? + const songsArtistsPromise: Promise> = (songLimit && songLimit !== 0) ? (async () => { return await getLinkedObjects(knex, ObjectType.Song, ObjectType.Artist, await songIdsPromise); })() : (async () => { return {}; })(); - const songsTagsPromise: Promise> = (songLimit && songLimit > 0) ? + const songsTagsPromise: Promise> = (songLimit && songLimit !== 0) ? (async () => { const tagsPerSong: Record = await getLinkedObjects(knex, ObjectType.Song, ObjectType.Tag, await songIdsPromise); var result: Record = {}; @@ -338,7 +343,7 @@ export const QueryEndpointHandler: EndpointHandler = async (req: any, res: any, return result; })() : (async () => { return {}; })(); - const songsAlbumsPromise: Promise> = (songLimit && songLimit > 0) ? + const songsAlbumsPromise: Promise> = (songLimit && songLimit !== 0) ? (async () => { return await getLinkedObjects(knex, ObjectType.Song, ObjectType.Album, await songIdsPromise); })() :