mirror of
https://github.com/nowcommunity/nowdatabase.git
synced 2026-10-12 19:13:52 +03:00
169 lines
5.6 KiB
TypeScript
169 lines
5.6 KiB
TypeScript
import {
|
|
User,
|
|
CrossSearch,
|
|
Role,
|
|
CrossSearchRouteParameters,
|
|
ParsedCrossSearchRouteParameters,
|
|
SortingState,
|
|
ColumnFilter,
|
|
} from '../../../frontend/src/shared/types'
|
|
import { getCrossSearchFields, getFieldsOfTables, nowDb } from '../utils/db'
|
|
import { generateCrossSearchSql } from './queries/crossSearchQuery'
|
|
import { ValidationObject } from '../../../frontend/src/shared/validators/validator'
|
|
import { validateCrossSearchRouteParams } from '../../../frontend/src/shared/validators/crossSearch'
|
|
|
|
const getAllowedLocalities = async (user: User) => {
|
|
const usersProjects = await nowDb.now_proj_people.findMany({
|
|
where: { initials: user.initials },
|
|
select: { pid: true },
|
|
})
|
|
|
|
const projectsSet = new Set(usersProjects.map(({ pid }) => pid))
|
|
const projectIDs = Array.from(projectsSet)
|
|
|
|
const localities = await nowDb.now_plr.findMany({
|
|
where: { pid: { in: projectIDs } },
|
|
select: { lid: true },
|
|
})
|
|
|
|
const localitiesSet = new Set(localities.map(({ lid }) => lid))
|
|
const localityIDs = Array.from(localitiesSet)
|
|
return localityIDs
|
|
}
|
|
|
|
const convertFilterIdToFieldName = (id: string) => {
|
|
const aliasToFieldName: { [key: string]: string } = {
|
|
species_id_com_species: 'com_species.species_id',
|
|
body_mass_com_species: 'com_species.body_mass',
|
|
microwear_com_species: 'com_species.microwear',
|
|
mesowear_com_species: 'com_species.mesowear',
|
|
mw_or_high_com_species: 'com_species.mw_or_high',
|
|
mw_or_low_com_species: 'com_species.mw_or_low',
|
|
mw_cs_sharp_com_species: 'com_species.mw_cs_sharp',
|
|
mw_cs_round_com_species: 'com_species.mw_cs_round',
|
|
mw_cs_blunt_com_species: 'com_species.mw_cs_blunt',
|
|
mw_scale_min_com_species: 'com_species.mw_scale_min',
|
|
mw_scale_max_com_species: 'com_species.mw_scale_max',
|
|
mw_value_com_species: 'com_species.mw_value',
|
|
|
|
lid_now_loc: 'now_loc.lid',
|
|
species_id_now_ls: 'now_ls.species_id',
|
|
microwear_now_ls: 'now_ls.microwear',
|
|
body_mass_now_ls: 'now_ls.body_mass',
|
|
mesowear_now_ls: 'now_ls.mesowear',
|
|
mw_or_high_now_ls: 'now_ls.mw_or_high',
|
|
mw_or_low_now_ls: 'now_ls.mw_or_low',
|
|
mw_cs_sharp_now_ls: 'now_ls.mw_cs_sharp',
|
|
mw_cs_round_now_ls: 'now_ls.mw_cs_round',
|
|
mw_cs_blunt_now_ls: 'now_ls.mw_cs_blunt',
|
|
mw_scale_min_now_ls: 'now_ls.mw_scale_min',
|
|
mw_scale_max_now_ls: 'now_ls.mw_scale_max',
|
|
mw_value_now_ls: 'now_ls.mw_value',
|
|
}
|
|
|
|
if (Object.keys(aliasToFieldName).includes(id)) {
|
|
return aliasToFieldName[id]
|
|
}
|
|
|
|
if (getFieldsOfTables(['com_species']).includes(id)) {
|
|
return `com_species.${id}`
|
|
}
|
|
if (getFieldsOfTables(['now_ls']).includes(id)) {
|
|
return `now_ls.${id}`
|
|
}
|
|
if (getFieldsOfTables(['now_loc']).includes(id)) {
|
|
return `now_loc.${id}`
|
|
}
|
|
return id
|
|
}
|
|
|
|
export const getCrossSearchRawSql = async (
|
|
user: User | undefined,
|
|
limit?: number,
|
|
offset?: number,
|
|
columnFilters?: ColumnFilter[],
|
|
sorting?: SortingState[]
|
|
) => {
|
|
let orderBy: string | undefined
|
|
let descendingOrder: boolean = false
|
|
if (!sorting || sorting.length === 0) {
|
|
orderBy = undefined
|
|
} else {
|
|
orderBy = sorting[0].id
|
|
descendingOrder = sorting[0].desc
|
|
}
|
|
|
|
let convertedOrderBy = undefined
|
|
if (orderBy) {
|
|
convertedOrderBy = convertFilterIdToFieldName(orderBy)
|
|
if (!getCrossSearchFields().includes(convertedOrderBy)) throw new Error('orderBy was not a valid column id.')
|
|
}
|
|
|
|
const convertedColumnFilters = []
|
|
if (columnFilters) {
|
|
const allowedColumns = getCrossSearchFields()
|
|
for (const filter of columnFilters) {
|
|
const convertedId = convertFilterIdToFieldName(filter.id)
|
|
if (!allowedColumns.includes(convertedId)) throw new Error('columnFilters has an invalid column id.')
|
|
convertedColumnFilters.push({ id: convertedId, value: filter.value })
|
|
}
|
|
}
|
|
|
|
const showAll = user ? [Role.Admin, Role.EditUnrestricted].includes(user.role) : false
|
|
const allowedLocalities = user ? await getAllowedLocalities(user) : []
|
|
|
|
const sql = generateCrossSearchSql(
|
|
showAll,
|
|
allowedLocalities,
|
|
limit,
|
|
offset,
|
|
convertedColumnFilters,
|
|
convertedOrderBy,
|
|
descendingOrder
|
|
)
|
|
|
|
const result: Partial<CrossSearch>[] = await nowDb.$queryRaw(sql)
|
|
return result
|
|
}
|
|
|
|
export const parseAndValidateCrossSearchRouteParameters = (parameters: CrossSearchRouteParameters) => {
|
|
const { limit, offset, columnFilters, sorting } = parameters
|
|
let parsedLimit
|
|
let parsedOffset
|
|
if (limit) {
|
|
parsedLimit = parseInt(limit)
|
|
if (isNaN(parsedLimit)) throw new Error('Limit is not a number.')
|
|
}
|
|
if (offset) {
|
|
parsedOffset = parseInt(offset)
|
|
if (isNaN(parsedOffset)) throw new Error('Offset is not a number.')
|
|
}
|
|
const parsedColumnFilters = JSON.parse(columnFilters) as unknown
|
|
if (!Array.isArray(parsedColumnFilters)) throw new Error('ColumnFilters is not an array.')
|
|
const parsedSorting = JSON.parse(sorting) as unknown
|
|
if (!Array.isArray(parsedSorting)) throw new Error('Sorting is not an array.')
|
|
|
|
const parsedParameters = {
|
|
limit: parsedLimit,
|
|
offset: parsedOffset,
|
|
columnFilters: parsedColumnFilters,
|
|
sorting: parsedSorting,
|
|
}
|
|
const keys = Object.keys(parsedParameters)
|
|
const errors: ValidationObject[] = []
|
|
for (const key of keys) {
|
|
const error = validateCrossSearchRouteParams(parsedParameters, key as keyof ParsedCrossSearchRouteParameters)
|
|
if (error.error) errors.push(error)
|
|
}
|
|
const validatedColumnFilters = parsedColumnFilters as ColumnFilter[]
|
|
const validatedSorting = parsedSorting as SortingState[]
|
|
|
|
return {
|
|
validatedLimit: parsedLimit,
|
|
validatedOffset: parsedOffset,
|
|
validatedColumnFilters,
|
|
validatedSorting,
|
|
validationErrors: errors,
|
|
}
|
|
}
|