Progress on advanced filters

This commit is contained in:
jeffvli
2022-11-02 22:04:46 -07:00
parent 73fff64a75
commit 94b40178aa
7 changed files with 764 additions and 148 deletions
+7 -1
View File
@@ -24,10 +24,15 @@ const getList = async (
res: Response
) => {
const { serverId } = req.params;
const { take, skip, serverUrlId } = req.query;
const { take, skip, serverUrlId, advancedFilters } = req.query;
const decodedAdvancedFilters = advancedFilters && decodeURI(advancedFilters);
const jsonAdvancedFilters =
decodedAdvancedFilters && JSON.parse(decodedAdvancedFilters);
const albums = await service.albums.findMany({
...req.query,
advancedFilters: jsonAdvancedFilters,
serverId,
skip: Number(skip),
take: Number(take),
@@ -66,6 +71,7 @@ const getDetailSongList = async (
const albums = await service.albums.findMany({
...req.query,
advancedFilters: undefined,
serverId,
skip: Number(skip),
take: Number(take),
+198 -1
View File
@@ -57,7 +57,7 @@ const sort = (sortBy: AlbumSort, orderBy: SortOrder) => {
break;
case AlbumSort.DATE_RELEASED:
order = { releaseDate: orderBy, year: orderBy };
order = { releaseDate: orderBy };
break;
case AlbumSort.DATE_RELEASED_YEAR:
@@ -80,7 +80,204 @@ const sort = (sortBy: AlbumSort, orderBy: SortOrder) => {
return order;
};
export enum FilterGroupType {
AND = 'AND',
OR = 'OR',
}
export type AdvancedFilterRule = {
field: string | null;
operator: string | null;
uniqueId: string;
value: string | number | Date | undefined | null | any;
};
export type AdvancedFilterGroup = {
group: AdvancedFilterGroup[];
rules: AdvancedFilterRule[];
type: FilterGroupType;
uniqueId: string;
};
const advancedFilterGroup = (
groups: AdvancedFilterGroup[],
user: AuthUser,
data: any[]
) => {
if (groups.length === 0) {
return data;
}
const filterGroups: any[] = [];
for (const group of groups) {
const rootType = group.type.toUpperCase();
const query: any = {
[rootType]: [],
};
for (const rule of group.rules) {
if (rule.field && rule.operator) {
const [table, field, relationField] = rule.field.split('.');
if (field === 'ratings') {
if (table === 'albums') {
query[rootType].push({
[field]: {
some: {
[relationField]: {
[rule.operator]: rule.value,
},
userId: user.id,
},
},
});
} else {
query[rootType].push({
[table]: {
some: {
[field]: {
some: {
[relationField]: {
[rule.operator]: rule.value,
},
userId: user.id,
},
},
},
},
});
}
} else if (table === 'albums') {
const obj = {
[field]: {
[rule.operator]: rule.value,
mode: 'insensitive',
},
};
query[rootType].push(obj);
} else {
const obj = {
[table]: {
some: {
[field]: {
[rule.operator]: rule.value,
mode: 'insensitive',
},
},
},
};
query[rootType].push(obj);
}
}
}
if (group.group.length > 0) {
const b = advancedFilterGroup(group.group, user, data);
b.forEach((c) => query[rootType].push(c));
}
data.push(query);
filterGroups.push(query);
}
return filterGroups;
};
const advancedFilter = (filter: AdvancedFilterGroup, user: AuthUser) => {
const rootQueryType = filter.type.toUpperCase();
const rootQuery = {
[rootQueryType]: [] as any[],
};
const operatorMap = {
'!=': 'not',
'!~': 'contains',
$: 'endsWith',
'<': 'lt',
'<=': 'lte',
'=': 'equals',
'>': 'gt',
'>=': 'gte',
'^': 'startsWith',
'~': 'contains',
};
for (const rule of filter.rules) {
if (rule.field && rule.operator) {
let [table, field, relationField] = rule.field.split('.');
const condition = rule.operator === '!~' ? 'none' : 'some';
const op = operatorMap[rule.operator as keyof typeof operatorMap];
switch (table) {
case 'albums':
if (field === 'ratings') {
rootQuery[rootQueryType].push({
[field]: {
[condition]: {
[relationField]: {
[op]: rule.value,
},
userId: user.id,
},
},
});
break;
}
rootQuery[rootQueryType].push({
[field]: {
mode: 'insensitive',
[op]: rule.value,
},
});
break;
default:
if (field === 'ratings') {
rootQuery[rootQueryType].push({
[table]: {
some: {
[field]: {
some: {
[relationField]: {
[op]: rule.value,
},
userId: user.id,
},
},
},
},
});
break;
}
rootQuery[rootQueryType].push({
[table]: {
[condition]: {
[field]: {
mode: 'insensitive',
[op]: rule.value,
},
},
},
});
break;
}
}
}
const groups = advancedFilterGroup(filter.group, user, []);
for (const group of groups) {
rootQuery[rootQueryType].push(group);
}
return rootQuery;
};
export const albumHelpers = {
advancedFilter,
include,
sort,
};
+28 -5
View File
@@ -1,7 +1,8 @@
import { Prisma } from '@prisma/client';
import { AuthUser } from '@/middleware';
import { OffsetPagination, SortOrder } from '@/types/types';
import { ApiError } from '@/utils';
import { AlbumSort } from '@helpers/albums.helpers';
import { AdvancedFilterGroup, AlbumSort } from '@helpers/albums.helpers';
import { helpers } from '@helpers/index';
import { prisma } from '@lib/prisma';
@@ -24,6 +25,7 @@ const findById = async (user: AuthUser, options: { id: string }) => {
};
export type AlbumFindManyOptions = {
advancedFilters?: AdvancedFilterGroup;
orderBy: SortOrder;
serverFolderId?: string[];
serverId: string;
@@ -32,8 +34,16 @@ export type AlbumFindManyOptions = {
} & OffsetPagination;
const findMany = async (options: AlbumFindManyOptions) => {
const { take, serverFolderId, skip, sortBy, orderBy, user, serverId } =
options;
const {
take,
serverFolderId,
skip,
sortBy,
orderBy,
user,
serverId,
advancedFilters,
} = options;
const serverFolderIds =
serverFolderId ||
@@ -42,6 +52,9 @@ const findMany = async (options: AlbumFindManyOptions) => {
let totalEntries = 0;
let albums;
const advancedFiltersQuery =
advancedFilters && helpers.albums.advancedFilter(advancedFilters, user);
if (sortBy === AlbumSort.RATING) {
const [count, result] = await prisma.$transaction([
prisma.albumRating.count({
@@ -92,14 +105,24 @@ const findMany = async (options: AlbumFindManyOptions) => {
} else {
[totalEntries, albums] = await prisma.$transaction([
prisma.album.count({
where: { OR: helpers.shared.serverFolderFilter(serverFolderIds) },
where: {
AND: [
helpers.shared.serverFolderFilter(serverFolderIds),
advancedFiltersQuery as Prisma.AlbumWhereInput,
],
},
}),
prisma.album.findMany({
include: helpers.albums.include(user, { songs: false }),
orderBy: [helpers.albums.sort(sortBy, orderBy)],
skip,
take,
where: { OR: helpers.shared.serverFolderFilter(serverFolderIds) },
where: {
AND: [
helpers.shared.serverFolderFilter(serverFolderIds),
advancedFiltersQuery as Prisma.AlbumWhereInput,
],
},
}),
]);
}
+1
View File
@@ -16,6 +16,7 @@ const list = {
...serverFolderIdValidation,
...orderByValidation,
...serverUrlIdValidation,
advancedFilters: z.optional(z.string()),
sortBy: z.nativeEnum(AlbumSort),
}),
};