Fix tag queries.

master
Sander Vocke 4 years ago
parent 4b45610648
commit e8c043b08d
  1. 6
      client/src/api/endpoints/data.ts
  2. 4
      client/src/api/endpoints/query.ts
  3. 12
      client/src/api/endpoints/resources.ts
  4. 9
      client/src/api/types/resources.ts
  5. 38
      client/src/components/querybuilder/QBAddElemMenu.tsx
  6. 4
      client/src/components/querybuilder/QBNodeElem.tsx
  7. 1
      client/src/components/querybuilder/QBPlaceholder.tsx
  8. 10
      client/src/components/querybuilder/QueryBuilder.tsx
  9. 19
      client/src/components/windows/query/QueryWindow.tsx
  10. 50
      client/src/lib/query/Query.tsx
  11. 8
      server/db/Album.ts
  12. 8
      server/db/Artist.ts
  13. 4
      server/db/Data.ts
  14. 12
      server/db/Tag.ts
  15. 8
      server/db/Track.ts

@ -7,7 +7,7 @@
// Upon import, they might be replaced, and upon export, they might be randomly // Upon import, they might be replaced, and upon export, they might be randomly
// generated. // generated.
import { Album, Id, AlbumRefs, Artist, ArtistRefs, Tag, TagRefs, Track, TrackRefs, isTrackRefs, isAlbumRefs, isArtistRefs, isTagRefs } from "../types/resources"; import { Album, Id, AlbumRefs, Artist, ArtistRefs, Tag, TagParentId, Track, TrackRefs, isTrackRefs, isAlbumRefs, isArtistRefs, isTagParentId } from "../types/resources";
// The import/export DB format is just a set of lists of objects. // The import/export DB format is just a set of lists of objects.
// Each object has an ID and references others by ID. // Each object has an ID and references others by ID.
@ -17,7 +17,7 @@ export interface DBDataFormat {
tracks: (Track & Id & TrackRefs)[], tracks: (Track & Id & TrackRefs)[],
albums: (Album & Id & AlbumRefs)[], albums: (Album & Id & AlbumRefs)[],
artists: (Artist & Id & ArtistRefs)[], artists: (Artist & Id & ArtistRefs)[],
tags: (Tag & Id & TagRefs)[], tags: (Tag & Id & TagParentId)[],
} }
// Get a full export of a user's database (GET). // Get a full export of a user's database (GET).
@ -49,7 +49,7 @@ export const checkDBImportRequest: (v: any) => boolean = (v: any) => {
return prev && isArtistRefs(cur); return prev && isArtistRefs(cur);
}, true) && }, true) &&
v.tags.reduce((prev: boolean, cur: any) => { v.tags.reduce((prev: boolean, cur: any) => {
return prev && isTagRefs(cur); return prev && isTagParentId(cur);
}, true); }, true);
} }

@ -1,6 +1,6 @@
// Query for items (POST). // Query for items (POST).
import { Album, Id, Artist, Tag, Track, Name, StoreLinks, TagRefs, AlbumRefs, TrackDetails, ArtistDetails } from "../types/resources"; import { Album, Id, Artist, Tag, Track, Name, StoreLinks, TagParentId, AlbumRefs, TrackDetails, ArtistDetails } from "../types/resources";
export const QueryEndpoint = '/query'; export const QueryEndpoint = '/query';
@ -79,7 +79,7 @@ export interface QueryRequest {
// Query response structure // Query response structure
export type QueryResponseTrackDetails = (Track & Name & StoreLinks & TrackDetails & Id); export type QueryResponseTrackDetails = (Track & Name & StoreLinks & TrackDetails & Id);
export type QueryResponseArtistDetails = (Artist & Name & StoreLinks & Id); export type QueryResponseArtistDetails = (Artist & Name & StoreLinks & Id);
export type QueryResponseTagDetails = (Tag & Name & TagRefs & Id); export type QueryResponseTagDetails = (Tag & Name & TagParentId & Id);
export type QueryResponseAlbumDetails = (Album & Name & StoreLinks & Id); export type QueryResponseAlbumDetails = (Album & Name & StoreLinks & Id);
export interface QueryResponse { export interface QueryResponse {
tracks: QueryResponseTrackDetails[] | number[] | number, // Details | IDs | count, depending on QueryResponseType tracks: QueryResponseTrackDetails[] | number[] | number, // Details | IDs | count, depending on QueryResponseType

@ -12,11 +12,11 @@ import {
TrackRefs, TrackRefs,
ArtistRefs, ArtistRefs,
AlbumRefs, AlbumRefs,
TagRefs, TagParentId,
isTrackRefs, isTrackRefs,
isAlbumRefs, isAlbumRefs,
isArtistRefs, isArtistRefs,
isTagRefs, isTagParentId,
isName, isName,
Name, Name,
isTrack, isTrack,
@ -83,9 +83,9 @@ export const checkPostAlbumRequest: (v: any) => boolean = (v: any) => isAlbumRef
// Post new tag (POST). // Post new tag (POST).
export const PostTagEndpoint = "/tag"; export const PostTagEndpoint = "/tag";
export type PostTagRequest = (Tag & TagRefs & Name); export type PostTagRequest = (Tag & TagParentId & Name);
export interface PostTagResponse { id: number }; export interface PostTagResponse { id: number };
export const checkPostTagRequest: (v: any) => boolean = (v: any) => isTagRefs(v) && isName(v); export const checkPostTagRequest: (v: any) => boolean = (v: any) => isTagParentId(v) && isName(v);
// Post new integration (POST). // Post new integration (POST).
export const PostIntegrationEndpoint = "/integration"; export const PostIntegrationEndpoint = "/integration";
@ -113,9 +113,9 @@ export const checkPutAlbumRequest: (v: any) => boolean = (v: any) => isAlbumRefs
// Replace tag (PUT). // Replace tag (PUT).
export const PutTagEndpoint = "/tag/:id"; export const PutTagEndpoint = "/tag/:id";
export type PutTagRequest = (Tag & TagRefs); export type PutTagRequest = (Tag & TagParentId);
export type PutTagResponse = void; export type PutTagResponse = void;
export const checkPutTagRequest: (v: any) => boolean = (v: any) => isTagRefs(v) && isName(v);; export const checkPutTagRequest: (v: any) => boolean = (v: any) => isTagParentId(v) && isName(v);;
// Replace integration (PUT). // Replace integration (PUT).
export const PutIntegrationEndpoint = "/integration/:id"; export const PutIntegrationEndpoint = "/integration/:id";

@ -147,12 +147,17 @@ export interface Tag {
id?: number, id?: number,
parentId?: number | null, parentId?: number | null,
parent?: (Tag & Id) | null, parent?: (Tag & Id) | null,
childIds?: number[],
} }
export interface TagRefs { export interface TagParentId {
parentId: number | null, parentId: number | null,
} }
export interface TagChildIds {
childIds: number[],
}
export interface TagDetails { export interface TagDetails {
parent: (Tag & Id) | null, parent: (Tag & Id) | null,
} }
@ -161,7 +166,7 @@ export function isTag(q: any): q is Tag {
return q.mbApi_typename && q.mbApi_typename === "tag"; return q.mbApi_typename && q.mbApi_typename === "tag";
} }
export function isTagRefs(q: any): q is TagRefs { export function isTagParentId(q: any): q is TagParentId {
return isTag(q) && 'parentId' in q; return isTag(q) && 'parentId' in q;
} }

@ -3,7 +3,7 @@ import { Menu, MenuItem } from '@material-ui/core';
import NestedMenuItem from "material-ui-nested-menu-item"; import NestedMenuItem from "material-ui-nested-menu-item";
import { QueryElem, QueryLeafBy, QueryLeafOp, TagQueryInfo } from '../../lib/query/Query'; import { QueryElem, QueryLeafBy, QueryLeafOp, TagQueryInfo } from '../../lib/query/Query';
import QBSelectWithRequest from './QBSelectWithRequest'; import QBSelectWithRequest from './QBSelectWithRequest';
import { Requests } from './QueryBuilder'; import { Requests, QueryBuilderTag } from './QueryBuilder';
export interface MenuProps { export interface MenuProps {
anchorEl: null | HTMLElement, anchorEl: null | HTMLElement,
@ -12,19 +12,19 @@ export interface MenuProps {
requestFunctions: Requests, requestFunctions: Requests,
} }
export function createTagInfo(tag: any, allTags: any[]): TagQueryInfo { export function createTagInfo(tag: QueryBuilderTag, allTags: QueryBuilderTag[]): TagQueryInfo {
const resolveName: (t: any) => string[] = (t: any) => { const resolveName: (t: QueryBuilderTag) => string[] = (t: QueryBuilderTag) => {
if (t.parentId) { if (t.parentId) {
const parent = allTags.filter((o: any) => o.tagId === t.parentId)[0]; const parent = allTags.filter((o: QueryBuilderTag) => o.id === t.parentId)[0];
return [resolveName(parent), t.name]; return resolveName(parent).concat(t.name);
} }
return [t.name]; return [t.name];
} }
const resolveChildren: (t: any) => Set<number> = (t: any) => { const resolveChildren: (t: QueryBuilderTag) => Set<number> = (t: QueryBuilderTag) => {
if (t.childIds.length > 0) { if (t.childIds.length > 0) {
const childSets: Set<number>[] = allTags.filter((o: any) => t.childIds.includes(o.tagId)) const childSets: Set<number>[] = allTags.filter((o: QueryBuilderTag) => t.childIds.includes(o.id))
.map((child: any) => resolveChildren(child)); .map((child: QueryBuilderTag) => resolveChildren(child));
var r = new Set<number>(); var r = new Set<number>();
childSets.forEach((c: any) => { childSets.forEach((c: any) => {
@ -33,7 +33,7 @@ export function createTagInfo(tag: any, allTags: any[]): TagQueryInfo {
return r; return r;
} }
return new Set([t.tagId]); return new Set([t.id]);
} }
return { return {
@ -47,13 +47,14 @@ export function QBAddElemMenu(props: MenuProps) {
let onClose = props.onClose; let onClose = props.onClose;
interface TagItemProps { interface TagItemProps {
tag: any, tag: QueryBuilderTag,
allTags: any[], allTags: QueryBuilderTag[],
} }
const TagItem = (_props: TagItemProps) => { const TagItem = (_props: TagItemProps) => {
if (_props.tag.childIds.length > 0) { if (_props.tag.childIds.length > 0) {
const children = _props.allTags.filter( const children = _props.allTags.filter(
(tag: any) => _props.tag.childIds.includes(tag.tagId) (tag: QueryBuilderTag) =>
_props.tag.childIds.includes(tag.id)
); );
return <NestedMenuItem return <NestedMenuItem
@ -68,12 +69,19 @@ export function QBAddElemMenu(props: MenuProps) {
}); });
}} }}
> >
{children.map((child: any) => <TagItem tag={child} allTags={_props.allTags} />)} {children.map((child: QueryBuilderTag) => <TagItem tag={child} allTags={_props.allTags} />)}
</NestedMenuItem> </NestedMenuItem>
} }
return <MenuItem return <MenuItem
onClick={() => { onClick={() => {
console.log("onCreateQuery: adding:",{
a: QueryLeafBy.TagInfo,
leafOp: QueryLeafOp.Equals,
b: createTagInfo(_props.tag, _props.allTags),
} );
onClose(); onClose();
props.onCreateQuery({ props.onCreateQuery({
a: QueryLeafBy.TagInfo, a: QueryLeafBy.TagInfo,
@ -87,7 +95,7 @@ export function QBAddElemMenu(props: MenuProps) {
} }
const BaseTagsItem = (_props: any) => { const BaseTagsItem = (_props: any) => {
const [tags, setTags] = useState<any[] | null>(null); const [tags, setTags] = useState<QueryBuilderTag[] | null>(null);
useEffect(() => { useEffect(() => {
(async () => { (async () => {
@ -97,7 +105,7 @@ export function QBAddElemMenu(props: MenuProps) {
return tags ? return tags ?
<> <>
{tags.filter((tag: any) => !tag.parentId).map((tag: any) => { {tags.filter((tag: QueryBuilderTag) => !tag.parentId).map((tag: QueryBuilderTag) => {
return <TagItem tag={tag} allTags={tags} /> return <TagItem tag={tag} allTags={tags} />
})} })}
</> </>

@ -22,7 +22,9 @@ export function QBNodeElem(props: NodeProps) {
} else { } else {
ops.splice(idx, 1); ops.splice(idx, 1);
} }
let newNode = simplify({ operands: ops, nodeOp: e.nodeOp }, null); let newq = { operands: ops, nodeOp: e.nodeOp };
console.log("onReplace:", newq, simplify(newq, null));
let newNode = simplify(newq, null);
props.onReplace(newNode); props.onReplace(newNode);
} }

@ -19,6 +19,7 @@ export function QBPlaceholder(props: IProps & any) {
setAnchorEl(null); setAnchorEl(null);
}; };
const onCreate = (q: QueryElem) => { const onCreate = (q: QueryElem) => {
console.log("Replacing placeholder by:", q);
props.onReplace(q); props.onReplace(q);
}; };

@ -3,19 +3,15 @@ import { Box } from '@material-ui/core';
import QBQueryButton from './QBEditButton'; import QBQueryButton from './QBEditButton';
import { QBQueryElem } from './QBQueryElem'; import { QBQueryElem } from './QBQueryElem';
import { QueryElem, addPlaceholders, removePlaceholders, simplify } from '../../lib/query/Query'; import { QueryElem, addPlaceholders, removePlaceholders, simplify } from '../../lib/query/Query';
import { Tag, TagChildIds, TagParentId, Name, Id } from '../../api/api';
export interface TagItem { export type QueryBuilderTag = (Tag & TagChildIds & TagParentId & Name & Id);
name: string,
id: number,
childIds: number[],
parentId?: number,
}
export interface Requests { export interface Requests {
getArtists: (filter: string) => Promise<string[]>, getArtists: (filter: string) => Promise<string[]>,
getAlbums: (filter: string) => Promise<string[]>, getAlbums: (filter: string) => Promise<string[]>,
getTrackNames: (filter: string) => Promise<string[]>, getTrackNames: (filter: string) => Promise<string[]>,
getTags: () => Promise<TagItem[]>, getTags: () => Promise<QueryBuilderTag[]>,
} }
export interface IProps { export interface IProps {

@ -1,7 +1,7 @@
import React, { useEffect, useReducer, useCallback } from 'react'; import React, { useEffect, useReducer, useCallback } from 'react';
import { Box, LinearProgress, Typography } from '@material-ui/core'; import { Box, LinearProgress, Typography } from '@material-ui/core';
import { QueryElem, QueryLeafBy, QueryLeafElem, QueryLeafOp } from '../../../lib/query/Query'; import { QueryElem, QueryLeafBy, QueryLeafElem, QueryLeafOp } from '../../../lib/query/Query';
import QueryBuilder from '../../querybuilder/QueryBuilder'; import QueryBuilder, { QueryBuilderTag } from '../../querybuilder/QueryBuilder';
import { AlbumsTable, ArtistsTable, ColumnType, ItemsTable, TracksTable } from '../../tables/ResultsTable'; import { AlbumsTable, ArtistsTable, ColumnType, ItemsTable, TracksTable } from '../../tables/ResultsTable';
import { queryArtists, queryTracks, queryAlbums, queryTags } from '../../../lib/backend/queries'; import { queryArtists, queryTracks, queryAlbums, queryTags } from '../../../lib/backend/queries';
import { WindowState } from '../Windows'; import { WindowState } from '../Windows';
@ -87,13 +87,22 @@ async function getTrackNames(filter: string) {
return [...(new Set([...(tracks.map((s: any) => s.name))]))]; return [...(new Set([...(tracks.map((s: any) => s.name))]))];
} }
async function getTagItems(): Promise<any> { async function getTagItems(): Promise<QueryBuilderTag[]> {
let tags: any = await queryTags( let tags: QueryResponseTagDetails[] = (await queryTags(
undefined, undefined,
0, -1, QueryResponseType.Details 0, -1, QueryResponseType.Details
); )) as QueryResponseTagDetails[];
// We need to resolve the child ids.
let tags_with_children : QueryBuilderTag[] = tags.map((t: QueryResponseTagDetails) => {
return {
...t,
childIds: tags.filter((t2: QueryResponseTagDetails) => t2.parentId === t.id)
.map((t2: QueryResponseTagDetails) => t2.id)
}
})
return tags; return tags_with_children;
} }
export interface FireNewQueriesData { export interface FireNewQueriesData {

@ -1,28 +1,29 @@
import * as serverApi from '../../api/api'; import * as serverApi from '../../api/api';
export enum QueryFor { export enum QueryFor {
Artists = 0, Artists = "artists",
Albums, Albums = "albums",
Tags, Tags = "tags",
Tracks, Tracks = "tracks",
} }
export enum QueryLeafBy { export enum QueryLeafBy {
ArtistName = 0, ArtistName = "artistName",
ArtistId, ArtistId = "artistId",
AlbumName, AlbumName = "albumName",
AlbumId, AlbumId = "albumId",
TagInfo, TagInfo = "tagInfo",
TagId, TagId = "tagId",
TrackName, TrackName = "trackName",
TrackId, TrackId = "trackId",
StoreLinks, StoreLinks = "storeLinks",
NotApplicable = "n/a", // Some query nodes don't need an operand.
} }
export enum QueryLeafOp { export enum QueryLeafOp {
Equals = 0, Equals = "equals",
Like, Like = "like",
Placeholder, // Special op which indicates that this leaf is not filled in yet. Placeholder = "placeholder", // Special op which indicates that this leaf is not filled in yet.
} }
export interface TagQueryInfo { export interface TagQueryInfo {
@ -98,6 +99,7 @@ function mapToServerProperty(l: QueryLeafBy, queryFor: QueryFor | null) :
(queryFor == QueryFor.Tracks) ? serverApi.QueryElemProperty.trackStoreLinks : (queryFor == QueryFor.Tracks) ? serverApi.QueryElemProperty.trackStoreLinks :
null, null,
[QueryLeafBy.TagInfo]: null, [QueryLeafBy.TagInfo]: null,
[QueryLeafBy.NotApplicable]: null,
}[l]; }[l];
} }
@ -129,9 +131,9 @@ export function addPlaceholders(
inNode: null | QueryNodeOp, inNode: null | QueryNodeOp,
): QueryElem { ): QueryElem {
const makePlaceholder = () => { const makePlaceholder : () => QueryElem = () => {
return { return {
a: 0, a: QueryLeafBy.NotApplicable,
leafOp: QueryLeafOp.Placeholder, leafOp: QueryLeafOp.Placeholder,
b: "" b: ""
} }
@ -226,12 +228,12 @@ export function simplify(q: QueryElem | null, queryFor: QueryFor | null): QueryE
} }
// This shouldn't be part of simplification. // This shouldn't be part of simplification.
if (q && isLeafElem(q)) { // if (q && isLeafElem(q)) {
if (mapToServerLeafOp(q.leafOp, queryFor) === null || // if (mapToServerLeafOp(q.leafOp, queryFor) === null ||
mapToServerProperty(q.a, queryFor) === null) { // mapToServerProperty(q.a, queryFor) === null) {
return null; // return null;
} // }
} // }
return q; return q;
} }

@ -1,5 +1,5 @@
import Knex from "knex"; import Knex from "knex";
import { Album, AlbumRefs, Id, Name, AlbumDetails, StoreLinks, Tag, TagRefs, Track, Artist } from "../../client/src/api/api"; import { Album, AlbumRefs, Id, Name, AlbumDetails, StoreLinks, Tag, TagParentId, Track, Artist } from "../../client/src/api/api";
import * as api from '../../client/src/api/api'; import * as api from '../../client/src/api/api';
import asJson from "../lib/asJson"; import asJson from "../lib/asJson";
import { DBError, DBErrorKind } from "../endpoints/types"; import { DBError, DBErrorKind } from "../endpoints/types";
@ -14,7 +14,7 @@ export async function getAlbum(id: number, userId: number, knex: Knex):
// Start transfers for tracks, tags and artists. // Start transfers for tracks, tags and artists.
// Also request the album itself. // Also request the album itself.
const tagsPromise: Promise<(Tag & Id & Name & TagRefs)[]> = const tagsPromise: Promise<(Tag & Id & Name & TagParentId)[]> =
knex.select('tagId') knex.select('tagId')
.from('albums_tags') .from('albums_tags')
.where({ 'albumId': id }) .where({ 'albumId': id })
@ -23,8 +23,8 @@ export async function getAlbum(id: number, userId: number, knex: Knex):
knex.select(['id', 'name', 'parentId']) knex.select(['id', 'name', 'parentId'])
.from('tags') .from('tags')
.whereIn('id', ids) .whereIn('id', ids)
.then((tags: (Id & Name & TagRefs)[]) => .then((tags: (Id & Name & TagParentId)[]) =>
tags.map((tag : (Id & Name & TagRefs)) => tags.map((tag : (Id & Name & TagParentId)) =>
{ return {...tag, mbApi_typename: "tag"}} { return {...tag, mbApi_typename: "tag"}}
)) ))
); );

@ -1,5 +1,5 @@
import Knex from "knex"; import Knex from "knex";
import { Artist, ArtistDetails, Tag, Track, TagRefs, Id, Name, StoreLinks, Album, ArtistRefs } from "../../client/src/api/api"; import { Artist, ArtistDetails, Tag, Track, TagParentId, Id, Name, StoreLinks, Album, ArtistRefs } from "../../client/src/api/api";
import * as api from '../../client/src/api/api'; import * as api from '../../client/src/api/api';
import asJson from "../lib/asJson"; import asJson from "../lib/asJson";
import { DBError, DBErrorKind } from "../endpoints/types"; import { DBError, DBErrorKind } from "../endpoints/types";
@ -11,7 +11,7 @@ export async function getArtist(id: number, userId: number, knex: Knex):
Promise<(Artist & ArtistDetails & Name & StoreLinks)> { Promise<(Artist & ArtistDetails & Name & StoreLinks)> {
// Start transfers for tags and albums. // Start transfers for tags and albums.
// Also request the artist itself. // Also request the artist itself.
const tagsPromise: Promise<(Tag & Name & Id & TagRefs)[]> = const tagsPromise: Promise<(Tag & Name & Id & TagParentId)[]> =
knex.select('tagId') knex.select('tagId')
.from('artists_tags') .from('artists_tags')
.where({ 'artistId': id }) .where({ 'artistId': id })
@ -20,8 +20,8 @@ export async function getArtist(id: number, userId: number, knex: Knex):
knex.select(['id', 'name', 'parentId']) knex.select(['id', 'name', 'parentId'])
.from('tags') .from('tags')
.whereIn('id', ids) .whereIn('id', ids)
.then((tags: (Id & Name & TagRefs)[]) => .then((tags: (Id & Name & TagParentId)[]) =>
tags.map((tag : (Id & Name & TagRefs)) => tags.map((tag : (Id & Name & TagParentId)) =>
{ return {...tag, mbApi_typename: "tag"}} { return {...tag, mbApi_typename: "tag"}}
)) ))
); );

@ -1,5 +1,5 @@
import Knex from "knex"; import Knex from "knex";
import { Track, TrackRefs, Id, Name, StoreLinks, Album, AlbumRefs, Artist, ArtistRefs, Tag, TagRefs, isTrackRefs, isAlbumRefs, DBImportResponse, IDMappings } from "../../client/src/api/api"; import { Track, TrackRefs, Id, Name, StoreLinks, Album, AlbumRefs, Artist, ArtistRefs, Tag, TagParentId, isTrackRefs, isAlbumRefs, DBImportResponse, IDMappings } from "../../client/src/api/api";
import * as api from '../../client/src/api/api'; import * as api from '../../client/src/api/api';
import asJson from "../lib/asJson"; import asJson from "../lib/asJson";
import { createArtist } from "./Artist"; import { createArtist } from "./Artist";
@ -60,7 +60,7 @@ export async function exportDB(userId: number, knex: Knex): Promise<api.DBDataFo
} }
})); }));
let tagsPromise: Promise<(Tag & Id & Name & TagRefs)[]> = let tagsPromise: Promise<(Tag & Id & Name & TagParentId)[]> =
knex.select('name', 'parentId', 'id') knex.select('name', 'parentId', 'id')
.from('tags') .from('tags')
.where({ 'user': userId }) .where({ 'user': userId })

@ -1,7 +1,7 @@
import Knex from "knex"; import Knex from "knex";
import { isConstructorDeclaration } from "typescript"; import { isConstructorDeclaration } from "typescript";
import * as api from '../../client/src/api/api'; import * as api from '../../client/src/api/api';
import { Tag, TagRefs, TagDetails, Id, Name } from "../../client/src/api/api"; import { Tag, TagParentId, TagDetails, Id, Name } from "../../client/src/api/api";
import { DBError, DBErrorKind } from "../endpoints/types"; import { DBError, DBErrorKind } from "../endpoints/types";
import { makeNotFoundError } from "./common"; import { makeNotFoundError } from "./common";
@ -35,7 +35,7 @@ export async function getTagChildrenRecursive(id: number,
} }
// Returns the id of the created tag. // Returns the id of the created tag.
export async function createTag(userId: number, tag: (Tag & Name & TagRefs), knex: Knex): Promise<number> { export async function createTag(userId: number, tag: (Tag & Name & TagParentId), knex: Knex): Promise<number> {
return await knex.transaction(async (trx) => { return await knex.transaction(async (trx) => {
// If applicable, retrieve the parent tag. // If applicable, retrieve the parent tag.
const maybeMatches: any[] | null = const maybeMatches: any[] | null =
@ -124,13 +124,13 @@ export async function deleteTag(userId: number, tagId: number, knex: Knex) {
} }
export async function getTag(userId: number, tagId: number, knex: Knex): Promise<(Tag & TagDetails & Name)> { export async function getTag(userId: number, tagId: number, knex: Knex): Promise<(Tag & TagDetails & Name)> {
const tagPromise: Promise<(Tag & Id & Name & TagRefs) | null> = const tagPromise: Promise<(Tag & Id & Name & TagParentId) | null> =
knex.select(['id', 'name', 'parentId']) knex.select(['id', 'name', 'parentId'])
.from('tags') .from('tags')
.where({ 'user': userId }) .where({ 'user': userId })
.where({ 'id': tagId }) .where({ 'id': tagId })
.then((r: (Id & Name & TagRefs)[] | undefined) => r ? r[0] : null) .then((r: (Id & Name & TagParentId)[] | undefined) => r ? r[0] : null)
.then((r: (Id & Name & TagRefs) | null) => { .then((r: (Id & Name & TagParentId) | null) => {
if (r) { if (r) {
return { ...r, mbApi_typename: 'tag'}; return { ...r, mbApi_typename: 'tag'};
} }
@ -139,7 +139,7 @@ export async function getTag(userId: number, tagId: number, knex: Knex): Promise
const parentPromise: Promise<(Tag & Id & Name & TagDetails) | null> = const parentPromise: Promise<(Tag & Id & Name & TagDetails) | null> =
tagPromise tagPromise
.then((r: (Tag & Id & Name & TagRefs) | null) => .then((r: (Tag & Id & Name & TagParentId) | null) =>
(r && r.parentId) ? ( (r && r.parentId) ? (
getTag(userId, r.parentId, knex) getTag(userId, r.parentId, knex)
.then((rr: (Tag & Name & TagDetails) | null) => .then((rr: (Tag & Name & TagDetails) | null) =>

@ -1,5 +1,5 @@
import Knex from "knex"; import Knex from "knex";
import { Track, TrackRefs, TrackDetails, Id, Name, StoreLinks, Tag, Album, Artist, TagRefs } from "../../client/src/api/api"; import { Track, TrackRefs, TrackDetails, Id, Name, StoreLinks, Tag, Album, Artist, TagParentId } from "../../client/src/api/api";
import * as api from '../../client/src/api/api'; import * as api from '../../client/src/api/api';
import asJson from "../lib/asJson"; import asJson from "../lib/asJson";
import { DBError, DBErrorKind } from "../endpoints/types"; import { DBError, DBErrorKind } from "../endpoints/types";
@ -11,7 +11,7 @@ export async function getTrack(id: number, userId: number, knex: Knex):
Promise<Track & Name & StoreLinks & TrackDetails> { Promise<Track & Name & StoreLinks & TrackDetails> {
// Start transfers for tracks, tags and artists. // Start transfers for tracks, tags and artists.
// Also request the track itself. // Also request the track itself.
const tagsPromise: Promise<(Tag & Id & Name & TagRefs)[]> = const tagsPromise: Promise<(Tag & Id & Name & TagParentId)[]> =
knex.select('tagId') knex.select('tagId')
.from('tracks_tags') .from('tracks_tags')
.where({ 'trackId': id }) .where({ 'trackId': id })
@ -20,8 +20,8 @@ export async function getTrack(id: number, userId: number, knex: Knex):
knex.select(['id', 'name', 'parentId']) knex.select(['id', 'name', 'parentId'])
.from('tags') .from('tags')
.whereIn('id', ids) .whereIn('id', ids)
.then((tags: (Id & Name & TagRefs)[]) => .then((tags: (Id & Name & TagParentId)[]) =>
tags.map((tag : (Id & Name & TagRefs)) => tags.map((tag : (Id & Name & TagParentId)) =>
{ return {...tag, mbApi_typename: "tag"}} { return {...tag, mbApi_typename: "tag"}}
)) ))
); );

Loading…
Cancel
Save