Skip to content

Commit a01637e

Browse files
authored
Replace parentIdMiddleware with expanded Grandparent concept (#3442)
1 parent fe4a1f3 commit a01637e

File tree

9 files changed

+72
-64
lines changed

9 files changed

+72
-64
lines changed
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
import type { FieldMiddleware } from '@nestjs/graphql';
2+
import { createGqlPipesParamDecorator } from '@nestjs/graphql/dist/decorators/param.utils.js';
3+
import { GqlParamtype } from '@nestjs/graphql/dist/enums/gql-paramtype.enum.js';
4+
import { isRegularObject } from '@seedcompany/common';
5+
import { ServerException } from './exceptions';
6+
7+
const store = new WeakMap<object, object>();
8+
9+
const get = <T>(value: object): T => {
10+
const grandparent = store.get(value);
11+
if (!grandparent) {
12+
throw new ServerException('Cannot find grandparent');
13+
}
14+
return grandparent as T;
15+
};
16+
17+
const middleware: FieldMiddleware = async ({ source, info }, next) => {
18+
const value = await next();
19+
if (value) {
20+
if (!isRegularObject(value)) {
21+
throw new ServerException(
22+
`Cannot store ${info.parentType.name}.${info.fieldName} for grandparent lookup because it is not an object.`,
23+
);
24+
}
25+
store.set(value, source);
26+
}
27+
return value;
28+
};
29+
30+
const PipeableParent = createGqlPipesParamDecorator(GqlParamtype.ROOT);
31+
32+
/**
33+
* @example
34+
* ```ts
35+
* class X {
36+
* @Field({ middleware: [Grandparent.store] })
37+
* y: SecuredY;
38+
* }
39+
* ```
40+
* ```ts
41+
* @Resolver(SecuredY)
42+
* class Resolver {
43+
* @ResolveField()
44+
* z(
45+
* @Grandparent() x: X
46+
* ) {}
47+
* }
48+
* ```
49+
*/
50+
export const Grandparent = () => PipeableParent({ transform: get });
51+
Grandparent.store = middleware;

src/common/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ export * from './optional-field';
3737
export * from './order.enum';
3838
export * from './pagination.input';
3939
export * from './pagination-list';
40-
export * from './parent-id.middleware';
40+
export * from './grandparent.middleware';
4141
export * from './parent-types';
4242
export * from './poll';
4343
export * from './resource.dto';

src/common/parent-id.middleware.ts

Lines changed: 0 additions & 37 deletions
This file was deleted.

src/components/engagement/dto/engagement.dto.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@ import {
77
DateTimeField,
88
DbLabel,
99
type DBNames,
10+
Grandparent,
1011
IntersectTypes,
11-
parentIdMiddleware,
1212
RequiredWhen,
1313
Resource,
1414
type ResourceRelationsShape,
@@ -91,7 +91,7 @@ class Engagement extends Interfaces {
9191
}>;
9292

9393
@Field(() => SecuredEngagementStatus, {
94-
middleware: [parentIdMiddleware],
94+
middleware: [Grandparent.store],
9595
})
9696
@DbLabel('EngagementStatus')
9797
readonly status: SecuredEngagementStatus;

src/components/engagement/engagement-status.resolver.ts

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
11
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
22
import { stripIndent } from 'common-tags';
3-
import { type ParentIdMiddlewareAdditions } from '~/common';
4-
import { EngagementStatusTransition, SecuredEngagementStatus } from './dto';
3+
import { Grandparent } from '~/common';
4+
import {
5+
type Engagement,
6+
EngagementStatusTransition,
7+
SecuredEngagementStatus,
8+
} from './dto';
59
import { EngagementRules } from './engagement.rules';
610

711
@Resolver(SecuredEngagementStatus)
@@ -12,15 +16,15 @@ export class EngagementStatusResolver {
1216
description: 'The available statuses a engagement can be transitioned to.',
1317
})
1418
async transitions(
15-
@Parent()
16-
status: SecuredEngagementStatus & ParentIdMiddlewareAdditions,
19+
@Grandparent() eng: Engagement,
20+
@Parent() status: SecuredEngagementStatus,
1721
): Promise<EngagementStatusTransition[]> {
1822
if (!status.canRead || !status.canEdit || !status.value) {
1923
return [];
2024
}
2125
return await this.engagementRules.getAvailableTransitions(
22-
status.parentId,
23-
status.changeset,
26+
eng.id,
27+
eng.changeset,
2428
);
2529
}
2630

src/components/progress-report/dto/progress-report.dto.ts

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import {
33
Calculated,
44
DbSort,
55
IntersectTypes,
6-
parentIdMiddleware,
76
Resource,
87
type ResourceRelationsShape,
98
SecuredProperty,
@@ -48,9 +47,7 @@ export class ProgressReport extends Interfaces {
4847
/** @deprecated */
4948
declare readonly reportFile: DefinedFile;
5049

51-
@Field(() => SecuredStatus, {
52-
middleware: [parentIdMiddleware],
53-
})
50+
@Field(() => SecuredStatus)
5451
@Calculated()
5552
@DbSort(sortingForEnumIndex(Status))
5653
readonly status: SecuredStatus;

src/components/progress-report/workflow/resolvers/progress-report-transitions.resolver.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import { Parent, ResolveField, Resolver } from '@nestjs/graphql';
22
import { stripIndent } from 'common-tags';
3-
import { type ParentIdMiddlewareAdditions } from '~/common';
43
import { SecuredProgressReportStatus } from '../../dto';
54
import { ProgressReportWorkflowTransition } from '../dto/workflow-transition.dto';
65
import { ProgressReportWorkflowService } from '../progress-report-workflow.service';
@@ -13,7 +12,7 @@ export class ProgressReportTransitionsResolver {
1312
description: 'The available statuses the report can be transitioned to.',
1413
})
1514
async transitions(
16-
@Parent() status: SecuredProgressReportStatus & ParentIdMiddlewareAdditions,
15+
@Parent() status: SecuredProgressReportStatus,
1716
): Promise<ProgressReportWorkflowTransition[]> {
1817
if (!status.canRead || !status.value) {
1918
return [];

src/components/project/dto/project.dto.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ import {
1212
DbUnique,
1313
Disabled,
1414
EnhancedResource,
15+
Grandparent,
1516
IntersectTypes,
1617
NameField,
17-
parentIdMiddleware,
1818
RequiredWhen,
1919
Resource,
2020
type ResourceRelationsShape,
@@ -119,7 +119,7 @@ class Project extends Interfaces {
119119
readonly departmentId: SecuredStringNullable;
120120

121121
@Field({
122-
middleware: [parentIdMiddleware],
122+
middleware: [Grandparent.store],
123123
})
124124
@DbLabel('ProjectStep')
125125
@DbSort(sortingForEnumIndex(ProjectStep))

src/components/project/workflow/resolvers/project-transitions.resolver.ts

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,8 @@
11
import { Parent, Query, ResolveField, Resolver } from '@nestjs/graphql';
2-
import { Loader, type LoaderOf } from '@seedcompany/data-loader';
32
import { stripIndent } from 'common-tags';
4-
import { type ParentIdMiddlewareAdditions, viewOfChangeset } from '~/common';
3+
import { Grandparent } from '~/common';
54
import { SerializedWorkflow } from '../../../workflow/dto';
6-
import { SecuredProjectStep } from '../../dto';
7-
import { ProjectLoader } from '../../project.loader';
5+
import { type Project, SecuredProjectStep } from '../../dto';
86
import { ProjectWorkflowTransition } from '../dto';
97
import { ProjectWorkflowService } from '../project-workflow.service';
108

@@ -22,16 +20,12 @@ export class ProjectTransitionsResolver {
2220
'The transitions currently available to execute for this project',
2321
})
2422
async transitions(
25-
@Parent() status: SecuredProjectStep & ParentIdMiddlewareAdditions,
26-
@Loader(ProjectLoader) projects: LoaderOf<ProjectLoader>,
23+
@Grandparent() project: Project,
24+
@Parent() status: SecuredProjectStep,
2725
): Promise<readonly ProjectWorkflowTransition[]> {
2826
if (!status.canRead || !status.value) {
2927
return [];
3028
}
31-
const project = await projects.load({
32-
id: status.parentId,
33-
view: viewOfChangeset(status.changeset),
34-
});
3529
return await this.workflow.getAvailableTransitions(project);
3630
}
3731

0 commit comments

Comments
 (0)