Skip to content

Commit

Permalink
Merge pull request #2709 from SeedCompany/filter-nulls
Browse files Browse the repository at this point in the history
  • Loading branch information
CarsonF committed Dec 20, 2022
2 parents ac67d80 + 8ef713f commit 128a523
Show file tree
Hide file tree
Showing 28 changed files with 143 additions and 199 deletions.
17 changes: 17 additions & 0 deletions src/common/default-value.ts
@@ -0,0 +1,17 @@
const get = (type: any) => Reflect.getMetadata(key, type) ?? {};

const set =
(value: any): PropertyDecorator =>
({ constructor: type }, propertyKey) =>
Reflect.defineMetadata(key, { ...get(type), [propertyKey]: value }, type);

/**
* A helper to get/set default values for classes.
* Usage of this is discouraged in favor or more common practices.
* This is mostly useful with abstractions.
* Note that the defaults aren't automatically applied, this just holds
* a container for them - They need to be fetched explicitly.
*/
export const DefaultValue = { Get: get, Set: set };

const key = 'DefaultValue';
34 changes: 34 additions & 0 deletions src/common/filter-field.ts
@@ -0,0 +1,34 @@
import { applyDecorators } from '@nestjs/common';
import { Field } from '@nestjs/graphql';
import { Transform, Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { HasRequiredKeys } from 'type-fest';
import { DefaultValue } from './default-value';
import { AbstractClassType } from './types';

/**
* A field that is a filter object probably for input on a list query.
*/
export const FilterField = <T extends object>(
type: HasRequiredKeys<T> extends true ? never : AbstractClassType<T>,
options?: {
/**
* There are no external fields on the filter, so don't expose to GQL.
*/
internal?: boolean;
}
): PropertyDecorator =>
applyDecorators(
...(options?.internal
? []
: [
Field(() => type, {
nullable: true,
defaultValue: {}, // Only for GQL schema & not always applied in TS
}),
]),
Type(() => type),
ValidateNested(),
DefaultValue.Set({}), // Default when omitted
Transform(({ value }) => value || {}) // null -> {}
);
1 change: 1 addition & 0 deletions src/common/index.ts
Expand Up @@ -14,6 +14,7 @@ export * from './mutation-placeholder.output';
export * from './exceptions';
export * from './expose-enum-order.helper';
export * from './fields.pipe';
export * from './filter-field';
export * from './firstLettersOfWords';
export * from './fiscal-year';
export * from './generate-id';
Expand Down
3 changes: 2 additions & 1 deletion src/common/pagination.input.ts
Expand Up @@ -3,6 +3,7 @@ import { Args, ArgsOptions, Field, InputType, Int } from '@nestjs/graphql';
import { Matches, Max, Min } from 'class-validator';
import { stripIndent } from 'common-tags';
import { DataObject } from './data-object';
import { DefaultValue } from './default-value';
import { Order } from './order.enum';
import { AbstractClassType } from './types';

Expand Down Expand Up @@ -106,7 +107,7 @@ export const ListArg = <T extends PaginationInput>(
name: 'input',
type: () => input,
nullable: true,
defaultValue: DataObject.defaultValue(input),
defaultValue: DataObject.defaultValue(input, DefaultValue.Get(input)),
...opts,
},
...pipes
Expand Down
10 changes: 4 additions & 6 deletions src/components/budget/dto/list-budget.dto.ts
Expand Up @@ -2,11 +2,12 @@ import { InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import {
FilterField,
ID,
PaginatedList,
SecuredList,
SortablePaginationInput,
} from '../../../common';
} from '~/common';
import { BudgetRecord } from './budget-record.dto';
import { Budget } from './budget.dto';

Expand All @@ -15,15 +16,12 @@ export abstract class BudgetFilters {
readonly projectId?: ID;
}

const defaultFilters = {};

@InputType()
export class BudgetListInput extends SortablePaginationInput<keyof Budget>({
defaultSort: 'status',
}) {
@Type(() => BudgetFilters)
@ValidateNested()
readonly filter: BudgetFilters = defaultFilters;
@FilterField(BudgetFilters, { internal: true })
readonly filter: BudgetFilters;
}

@ObjectType()
Expand Down
12 changes: 3 additions & 9 deletions src/components/ceremony/dto/list-ceremony.dto.ts
@@ -1,7 +1,5 @@
import { Field, InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { PaginatedList, SortablePaginationInput } from '../../../common';
import { FilterField, PaginatedList, SortablePaginationInput } from '~/common';
import { Ceremony } from './ceremony.dto';
import { CeremonyType } from './type.enum';

Expand All @@ -14,18 +12,14 @@ export abstract class CeremonyFilters {
readonly type?: CeremonyType;
}

const defaultFilters = {};

@InputType()
export class CeremonyListInput extends SortablePaginationInput<
keyof Ceremony | 'projectName' | 'languageName'
>({
defaultSort: 'projectName',
}) {
@Field({ nullable: true })
@Type(() => CeremonyFilters)
@ValidateNested()
readonly filter: CeremonyFilters = defaultFilters;
@FilterField(CeremonyFilters)
readonly filter: CeremonyFilters;
}

@ObjectType()
Expand Down
13 changes: 4 additions & 9 deletions src/components/engagement/dto/list-engagements.dto.ts
@@ -1,12 +1,11 @@
import { Field, InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import {
FilterField,
ID,
PaginatedList,
SecuredList,
SortablePaginationInput,
} from '../../../common';
} from '~/common';
import {
Engagement,
IEngagement,
Expand All @@ -25,18 +24,14 @@ export abstract class EngagementFilters {
readonly projectId?: ID;
}

const defaultFilters = {};

@InputType()
export class EngagementListInput extends SortablePaginationInput<
keyof Engagement
>({
defaultSort: 'createdAt',
}) {
@Field({ nullable: true })
@Type(() => EngagementFilters)
@ValidateNested()
readonly filter: EngagementFilters = defaultFilters;
@FilterField(EngagementFilters)
readonly filter: EngagementFilters;
}

@ObjectType()
Expand Down
11 changes: 3 additions & 8 deletions src/components/ethno-art/dto/list-ethno-art.dto.ts
@@ -1,21 +1,16 @@
import { InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { PaginatedList, SortablePaginationInput } from '../../../common';
import { FilterField, PaginatedList, SortablePaginationInput } from '~/common';
import { EthnoArt } from './ethno-art.dto';

@InputType()
export abstract class EthnoArtFilters {}

const defaultFilters = {};

@InputType()
export class EthnoArtListInput extends SortablePaginationInput<keyof EthnoArt>({
defaultSort: 'name',
}) {
@Type(() => EthnoArtFilters)
@ValidateNested()
readonly filter: EthnoArtFilters = defaultFilters;
@FilterField(EthnoArtFilters, { internal: true })
readonly filter: EthnoArtFilters;
}

@ObjectType()
Expand Down
12 changes: 4 additions & 8 deletions src/components/field-region/dto/list-field-region.dto.ts
@@ -1,30 +1,26 @@
import { InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import {
FilterField,
ID,
PaginatedList,
SecuredList,
SortablePaginationInput,
} from '../../../common';
} from '~/common';
import { FieldRegion } from './field-region.dto';

@InputType()
export abstract class FieldRegionFilters {
readonly fieldZoneId?: ID;
}

const defaultFilters = {};

@InputType()
export class FieldRegionListInput extends SortablePaginationInput<
keyof FieldRegion
>({
defaultSort: 'name',
}) {
@Type(() => FieldRegionFilters)
@ValidateNested()
readonly filter: FieldRegionFilters = defaultFilters;
@FilterField(FieldRegionFilters, { internal: true })
readonly filter: FieldRegionFilters;
}

@ObjectType()
Expand Down
12 changes: 4 additions & 8 deletions src/components/field-zone/dto/list-field-zone.dto.ts
@@ -1,30 +1,26 @@
import { InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import {
FilterField,
ID,
PaginatedList,
SecuredList,
SortablePaginationInput,
} from '../../../common';
} from '~/common';
import { FieldZone } from './field-zone.dto';

@InputType()
export abstract class FieldZoneFilters {
readonly fieldZoneId?: ID;
}

const defaultFilters = {};

@InputType()
export class FieldZoneListInput extends SortablePaginationInput<
keyof FieldZone
>({
defaultSort: 'name',
}) {
@Type(() => FieldZoneFilters)
@ValidateNested()
readonly filter: FieldZoneFilters = defaultFilters;
@FilterField(FieldZoneFilters, { internal: true })
readonly filter: FieldZoneFilters;
}

@ObjectType()
Expand Down
12 changes: 3 additions & 9 deletions src/components/file/dto/list.ts
@@ -1,7 +1,5 @@
import { Field, InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { PaginatedList, SortablePaginationInput } from '../../../common';
import { FilterField, PaginatedList, SortablePaginationInput } from '~/common';
import { Directory, File, FileNode, IFileNode } from './node';
import { FileNodeType } from './type';

Expand All @@ -20,18 +18,14 @@ export abstract class FileFilters {
readonly type?: FileNodeType;
}

const defaultFilters = {};

@InputType()
export class FileListInput extends SortablePaginationInput<
keyof File | keyof Directory
>({
defaultSort: 'name',
}) {
@Field({ nullable: true })
@Type(() => FileFilters)
@ValidateNested()
readonly filter?: FileFilters = defaultFilters;
@FilterField(FileFilters)
readonly filter?: FileFilters;
}

@ObjectType()
Expand Down
11 changes: 3 additions & 8 deletions src/components/film/dto/list-film.dto.ts
@@ -1,21 +1,16 @@
import { InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { PaginatedList, SortablePaginationInput } from '../../../common';
import { FilterField, PaginatedList, SortablePaginationInput } from '~/common';
import { Film } from './film.dto';

@InputType()
export abstract class FilmFilters {}

const defaultFilters = {};

@InputType()
export class FilmListInput extends SortablePaginationInput<keyof Film>({
defaultSort: 'name',
}) {
@Type(() => FilmFilters)
@ValidateNested()
readonly filter: FilmFilters = defaultFilters;
@FilterField(FilmFilters, { internal: true })
readonly filter: FilmFilters;
}

@ObjectType()
Expand Down
13 changes: 4 additions & 9 deletions src/components/language/dto/list-language.dto.ts
@@ -1,13 +1,12 @@
import { Field, InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import {
FilterField,
PaginatedList,
SecuredList,
SensitivitiesFilter,
Sensitivity,
SortablePaginationInput,
} from '../../../common';
} from '~/common';
import { Language } from './language.dto';

@InputType()
Expand Down Expand Up @@ -49,16 +48,12 @@ export abstract class LanguageFilters {
readonly pinned?: boolean;
}

const defaultFilters = {};

@InputType()
export class LanguageListInput extends SortablePaginationInput<keyof Language>({
defaultSort: 'name',
}) {
@Field({ nullable: true })
@Type(() => LanguageFilters)
@ValidateNested()
readonly filter: LanguageFilters = defaultFilters;
@FilterField(LanguageFilters)
readonly filter: LanguageFilters;
}

@ObjectType()
Expand Down
@@ -1,23 +1,18 @@
import { InputType, ObjectType } from '@nestjs/graphql';
import { Type } from 'class-transformer';
import { ValidateNested } from 'class-validator';
import { PaginatedList, SortablePaginationInput } from '../../../common';
import { FilterField, PaginatedList, SortablePaginationInput } from '~/common';
import { LiteracyMaterial } from './literacy-material.dto';

@InputType()
export abstract class LiteracyMaterialFilters {}

const defaultFilters = {};

@InputType()
export class LiteracyMaterialListInput extends SortablePaginationInput<
keyof LiteracyMaterial
>({
defaultSort: 'name',
}) {
@Type(() => LiteracyMaterialFilters)
@ValidateNested()
readonly filter: LiteracyMaterialFilters = defaultFilters;
@FilterField(LiteracyMaterialFilters, { internal: true })
readonly filter: LiteracyMaterialFilters;
}

@ObjectType()
Expand Down

0 comments on commit 128a523

Please sign in to comment.