diff --git a/src/components/field-region/dto/list-field-region.dto.ts b/src/components/field-region/dto/list-field-region.dto.ts index f531132077..200ac4564a 100644 --- a/src/components/field-region/dto/list-field-region.dto.ts +++ b/src/components/field-region/dto/list-field-region.dto.ts @@ -2,15 +2,25 @@ import { InputType, ObjectType } from '@nestjs/graphql'; import { FilterField, type ID, + IdField, PaginatedList, SecuredList, SortablePaginationInput, } from '~/common'; +import { FieldZoneFilters } from '../../field-zone/dto'; +import { UserFilters } from '../../user/dto'; import { FieldRegion } from './field-region.dto'; @InputType() export abstract class FieldRegionFilters { - readonly fieldZoneId?: ID; + @IdField({ optional: true }) + readonly id?: ID<'FieldRegion'>; + + @FilterField(() => UserFilters) + readonly director?: UserFilters & {}; + + @FilterField(() => FieldZoneFilters) + readonly fieldZone?: FieldZoneFilters & {}; } @InputType() @@ -19,7 +29,7 @@ export class FieldRegionListInput extends SortablePaginationInput< >({ defaultSort: 'name', }) { - @FilterField(() => FieldRegionFilters, { internal: true }) + @FilterField(() => FieldRegionFilters) readonly filter?: FieldRegionFilters; } diff --git a/src/components/field-region/field-region.repository.ts b/src/components/field-region/field-region.repository.ts index dbc1285764..da34e81823 100644 --- a/src/components/field-region/field-region.repository.ts +++ b/src/components/field-region/field-region.repository.ts @@ -14,14 +14,22 @@ import { ACTIVE, createNode, createRelationships, + defineSorters, + filter, matchProps, merge, paginate, - sorting, + sortWith, } from '~/core/database/query'; +import { + fieldZoneFilters, + fieldZoneSorters, +} from '../field-zone/field-zone.repository'; +import { userFilters, userSorters } from '../user/user.repository'; import { type CreateFieldRegion, FieldRegion, + FieldRegionFilters, type FieldRegionListInput, type UpdateFieldRegion, } from './dto'; @@ -102,14 +110,15 @@ export class FieldRegionRepository extends DtoRepository(FieldRegion) { ); } - async list({ filter, ...input }: FieldRegionListInput) { + async list(input: FieldRegionListInput) { if (!this.privileges.can('read')) { return SecuredList.Redacted; } const result = await this.db .query() .match(node('node', 'FieldRegion')) - .apply(sorting(FieldRegion, input)) + .apply(fieldRegionFilters(input.filter)) + .apply(sortWith(fieldRegionSorters, input)) .apply(paginate(input, this.hydrate())) .first(); return result!; // result from paginate() will always have 1 row. @@ -128,3 +137,44 @@ export class FieldRegionRepository extends DtoRepository(FieldRegion) { .run(); } } + +export const fieldRegionFilters = filter.define(() => FieldRegionFilters, { + id: filter.baseNodeProp(), + director: filter.sub(() => userFilters)((sub) => + sub.match([ + node('outer'), + relation('out', '', 'director', ACTIVE), + node('node', 'User'), + ]), + ), + fieldZone: filter.sub(() => fieldZoneFilters)((sub) => + sub.match([ + node('outer'), + relation('out', '', 'zone', ACTIVE), + node('node', 'FieldZone'), + ]), + ), +}); + +export const fieldRegionSorters = defineSorters(FieldRegion, { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'director.*': (query, input) => + query + .with('node as region') + .match([ + node('region'), + relation('out', '', 'director', ACTIVE), + node('node', 'User'), + ]) + .apply(sortWith(userSorters, input)), + // eslint-disable-next-line @typescript-eslint/naming-convention + 'fieldZone.*': (query, input) => + query + .with('node as region') + .match([ + node('region'), + relation('out', '', 'zone', ACTIVE), + node('node', 'FieldZone'), + ]) + .apply(sortWith(fieldZoneSorters, input)), +}); diff --git a/src/components/field-zone/dto/list-field-zone.dto.ts b/src/components/field-zone/dto/list-field-zone.dto.ts index 5201ef0588..2a768fc53b 100644 --- a/src/components/field-zone/dto/list-field-zone.dto.ts +++ b/src/components/field-zone/dto/list-field-zone.dto.ts @@ -2,15 +2,21 @@ import { InputType, ObjectType } from '@nestjs/graphql'; import { FilterField, type ID, + IdField, PaginatedList, SecuredList, SortablePaginationInput, } from '~/common'; +import { UserFilters } from '../../user/dto'; import { FieldZone } from './field-zone.dto'; @InputType() export abstract class FieldZoneFilters { - readonly fieldZoneId?: ID; + @IdField({ optional: true }) + readonly id?: ID<'FieldZone'>; + + @FilterField(() => UserFilters) + readonly director?: UserFilters & {}; } @InputType() @@ -19,7 +25,7 @@ export class FieldZoneListInput extends SortablePaginationInput< >({ defaultSort: 'name', }) { - @FilterField(() => FieldZoneFilters, { internal: true }) + @FilterField(() => FieldZoneFilters) readonly filter?: FieldZoneFilters; } diff --git a/src/components/field-zone/field-zone.repository.ts b/src/components/field-zone/field-zone.repository.ts index ca5ad476ba..78c9e441e4 100644 --- a/src/components/field-zone/field-zone.repository.ts +++ b/src/components/field-zone/field-zone.repository.ts @@ -15,14 +15,18 @@ import { ACTIVE, createNode, createRelationships, + defineSorters, + filter, matchProps, merge, paginate, - sorting, + sortWith, } from '~/core/database/query'; +import { userFilters, userSorters } from '../user/user.repository'; import { type CreateFieldZone, FieldZone, + FieldZoneFilters, type FieldZoneListInput, type UpdateFieldZone, } from './dto'; @@ -121,14 +125,15 @@ export class FieldZoneRepository extends DtoRepository(FieldZone) { await query.run(); } - async list({ filter, ...input }: FieldZoneListInput) { + async list(input: FieldZoneListInput) { if (!this.privileges.can('read')) { return SecuredList.Redacted; } const result = await this.db .query() .match(node('node', 'FieldZone')) - .apply(sorting(FieldZone, input)) + .apply(fieldZoneFilters(input.filter)) + .apply(sortWith(fieldZoneSorters, input)) .apply(paginate(input, this.hydrate())) .first(); return result!; // result from paginate() will always have 1 row. @@ -147,3 +152,27 @@ export class FieldZoneRepository extends DtoRepository(FieldZone) { .run(); } } + +export const fieldZoneFilters = filter.define(() => FieldZoneFilters, { + id: filter.baseNodeProp(), + director: filter.sub(() => userFilters)((sub) => + sub.match([ + node('outer'), + relation('out', '', 'director', ACTIVE), + node('node', 'User'), + ]), + ), +}); + +export const fieldZoneSorters = defineSorters(FieldZone, { + // eslint-disable-next-line @typescript-eslint/naming-convention + 'director.*': (query, input) => + query + .with('node as zone') + .match([ + node('zone'), + relation('out', '', 'director', ACTIVE), + node('node', 'User'), + ]) + .apply(sortWith(userSorters, input)), +}); diff --git a/src/components/project/dto/list-projects.dto.ts b/src/components/project/dto/list-projects.dto.ts index 7df7b76ed9..e71c2cfe36 100644 --- a/src/components/project/dto/list-projects.dto.ts +++ b/src/components/project/dto/list-projects.dto.ts @@ -16,6 +16,7 @@ import { SortablePaginationInput, } from '~/common'; import { Transform } from '~/common/transform.decorator'; +import { FieldRegionFilters } from '../../field-region/dto'; import { LocationFilters } from '../../location/dto'; import { PartnershipFilters } from '../../partnership/dto'; import { ProjectMemberFilters } from '../project-member/dto'; @@ -134,6 +135,9 @@ export abstract class ProjectFilters { @FilterField(() => LocationFilters) readonly primaryLocation?: LocationFilters & {}; + + @FilterField(() => FieldRegionFilters) + readonly fieldRegion?: FieldRegionFilters & {}; } Object.defineProperty(ProjectFilters.prototype, 'mine', { diff --git a/src/components/project/project-filters.query.ts b/src/components/project/project-filters.query.ts index 268c940430..d3e7997f95 100644 --- a/src/components/project/project-filters.query.ts +++ b/src/components/project/project-filters.query.ts @@ -13,6 +13,7 @@ import { path, variable, } from '~/core/database/query'; +import { fieldRegionFilters } from '../field-region/field-region.repository'; import { locationFilters } from '../location/location.repository'; import { partnershipFilters } from '../partnership/partnership.repository'; import { ProjectFilters } from './dto'; @@ -107,6 +108,13 @@ export const projectFilters = filter.define(() => ProjectFilters, { node('node', 'Location'), ]), ), + fieldRegion: filter.sub(() => fieldRegionFilters)((sub) => + sub.match([ + node('outer'), + relation('out', '', 'fieldRegion', ACTIVE), + node('node', 'FieldRegion'), + ]), + ), sensitivity: ({ value, query }) => query .apply(matchProjectSens('node')) diff --git a/src/components/project/project.repository.ts b/src/components/project/project.repository.ts index 893405a9de..ef6eb021c0 100644 --- a/src/components/project/project.repository.ts +++ b/src/components/project/project.repository.ts @@ -34,6 +34,7 @@ import { variable, } from '~/core/database/query'; import { Privileges } from '../authorization'; +import { fieldRegionSorters } from '../field-region/field-region.repository'; import { locationSorters } from '../location/location.repository'; import { partnershipSorters } from '../partnership/partnership.repository'; import { @@ -456,4 +457,21 @@ export const projectSorters = defineSorters(IProject, { .where(not(path(getPath(true)))) .return('null as sortValue'); }, + // eslint-disable-next-line @typescript-eslint/naming-convention + 'fieldRegion.*': (query, input) => { + const getPath = (anon = false) => [ + node('project'), + relation('out', '', 'fieldRegion', ACTIVE), + node(anon ? '' : 'node', 'FieldRegion'), + ]; + return query + .with('node as project') + .match(getPath()) + .apply(sortWith(fieldRegionSorters, input)) + .union() + .with('node') + .with('node as project') + .where(not(path(getPath(true)))) + .return('null as sortValue'); + }, });