Skip to content

Commit

Permalink
Merge pull request #2824 from SeedCompany/improve-dynamic-resources
Browse files Browse the repository at this point in the history
  • Loading branch information
CarsonF committed Jul 20, 2023
2 parents 180f5d1 + cf19398 commit f6ae866
Show file tree
Hide file tree
Showing 7 changed files with 72 additions and 22 deletions.
9 changes: 4 additions & 5 deletions src/components/comments/comment.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { Injectable } from '@nestjs/common';
import {
EnhancedResource,
ID,
InvalidIdForTypeException,
isIdLike,
Expand Down Expand Up @@ -93,10 +92,10 @@ export class CommentService {
? await this.resources.loadByBaseNode(parentNode)
: parentNode;

const parentType = await this.resourcesHost.getByName(parent.__typename);
const parentInterfaces = await this.resourcesHost.getInterfaces(parentType);
if (!parentInterfaces.includes(EnhancedResource.of(Commentable))) {
throw new NonCommentableType('Resource does not implement Commentable');
try {
await this.resourcesHost.verifyImplements(parent.__typename, Commentable);
} catch (e) {
throw new NonCommentableType(e.message);
}
return parent as Commentable;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/language/dto/language.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import {
import { SetChangeType } from '../../../core/database/changes';
import { Location } from '../../location/dto';
import { Pinnable } from '../../pin/dto';
import { Post, Postable } from '../../post/dto';
import { Postable } from '../../post/dto';
import { UpdateEthnologueLanguage } from './update-language.dto';

const Interfaces: Type<Resource & Pinnable & Postable> = IntersectionType(
Expand Down Expand Up @@ -87,7 +87,7 @@ export class Language extends Interfaces {
static readonly Relations = {
ethnologue: EthnologueLanguage,
locations: [Location], // a child list but not creating deleting...does it still count?
posts: [Post],
...Postable.Relations,
};

@NameField({
Expand Down
4 changes: 2 additions & 2 deletions src/components/partner/dto/partner.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
import { ScopedRole } from '../../authorization';
import { FinancialReportingType } from '../../partnership/dto/financial-reporting-type';
import { Pinnable } from '../../pin/dto';
import { Post, Postable } from '../../post/dto';
import { Postable } from '../../post/dto';
import { IProject } from '../../project/dto';
import { SecuredPartnerTypes } from './partner-type.enum';

Expand All @@ -43,7 +43,7 @@ export class Partner extends Interfaces {
static readonly SecuredProps = keysOf<SecuredProps<Partner>>();
static readonly Relations = {
projects: [IProject],
posts: [Post],
...Postable.Relations,
};

readonly organization: Secured<ID>;
Expand Down
19 changes: 18 additions & 1 deletion src/components/post/dto/postable.dto.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,32 @@
import { InterfaceType } from '@nestjs/graphql';
import { stripIndent } from 'common-tags';
import { ID, IdField } from '../../../common';
import { keys as keysOf } from 'ts-transformer-keys';
import { ID, IdField, SecuredProps } from '~/common';
import { RegisterResource } from '~/core/resources';
import { Post } from './post.dto';

@RegisterResource()
@InterfaceType({
description: stripIndent`
An object that can be used to enable Post discussions on a Node.
`,
})
export abstract class Postable {
static readonly Props: string[] = keysOf<Postable>();
static readonly SecuredProps: string[] = keysOf<SecuredProps<Postable>>();
static readonly Relations = {
posts: [Post],
};
static readonly Parent = 'dynamic';

@IdField({
description: "The object's ID",
})
readonly id: ID;
}

declare module '~/core/resources/map' {
interface ResourceMap {
Postable: typeof Postable;
}
}
4 changes: 3 additions & 1 deletion src/components/post/postable.resolver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ export class PostableResolver {
@LoggedInSession() session: Session,
@Loader(PostLoader) posts: LoaderOf<PostLoader>,
): Promise<SecuredPostList> {
const parentType = await this.resourcesHost.getByName(info.parentType.name);
const parentType = await this.resourcesHost.getByName(
info.parentType.name as 'Postable',
);
const list = await this.service.securedList(
parentType,
parent,
Expand Down
5 changes: 2 additions & 3 deletions src/components/project/dto/project.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@ import { Location } from '../../location/dto';
import { Partnership } from '../../partnership/dto';
import { SecuredReportPeriod } from '../../periodic-report/dto';
import { Pinnable } from '../../pin/dto';
import { Post } from '../../post/dto';
import { Postable } from '../../post/dto/postable.dto';
import { Postable } from '../../post/dto';
import { ProjectChangeRequest } from '../../project-change-request/dto';
import { ProjectMember } from '../project-member/dto';
import { ProjectStatus } from './status.enum';
Expand Down Expand Up @@ -81,7 +80,7 @@ class Project extends Interfaces {
engagement: [Engagement], // why singular
// edge case because it's writable for internships but not secured
sensitivity: Sensitivity,
posts: [Post], // from Postable interface
...Postable.Relations,
changeRequests: [ProjectChangeRequest],
...Commentable.Relations,
};
Expand Down
49 changes: 41 additions & 8 deletions src/core/resources/resources.host.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,13 @@ import { GraphQLSchemaHost } from '@nestjs/graphql';
import { CachedByArg } from '@seedcompany/common';
import { isObjectType } from 'graphql';
import { mapValues } from 'lodash';
import { ValueOf } from 'ts-essentials';
import { LiteralUnion } from 'type-fest';
import { EnhancedResource, ServerException } from '~/common';
import { LiteralUnion, ValueOf } from 'type-fest';
import {
EnhancedResource,
InvalidIdForTypeException,
ResourceShape,
ServerException,
} from '~/common';
import type { LegacyResourceMap } from '../../components/authorization/model/resource-map';
import { ResourceMap } from './map';
import { __privateDontUseThis } from './resource-map-holder';
Expand All @@ -14,6 +18,13 @@ export type EnhancedResourceMap = {
[K in keyof ResourceMap]: EnhancedResource<ResourceMap[K]>;
};

type LooseResourceName = LiteralUnion<keyof ResourceMap, string>;

type ResourceRef =
| ResourceShape<any>
| EnhancedResource<any>
| LooseResourceName;

@Injectable()
export class ResourcesHost {
constructor(private readonly gqlSchema: GraphQLSchemaHost) {}
Expand All @@ -40,11 +51,7 @@ export class ResourcesHost {

async getByName<K extends keyof ResourceMap>(
name: K,
): Promise<EnhancedResource<ValueOf<Pick<ResourceMap, K>>>>;
async getByName(
name: LiteralUnion<keyof ResourceMap, string>,
): Promise<EnhancedResource<ValueOf<ResourceMap>>>;
async getByName(name: keyof ResourceMap): Promise<EnhancedResource<any>> {
): Promise<EnhancedResource<ValueOf<Pick<ResourceMap, K>>>> {
const map = await this.getEnhancedMap();
const resource = map[name];
if (!resource) {
Expand All @@ -55,6 +62,32 @@ export class ResourcesHost {
return resource;
}

async getByDynamicName(
name: LooseResourceName,
): Promise<EnhancedResource<ValueOf<ResourceMap>>> {
return await this.getByName(name as any);
}

async verifyImplements(resource: ResourceRef, theInterface: ResourceRef) {
const iface = await this.enhance(theInterface);
if (!(await this.doesImplement(resource, iface))) {
throw new InvalidIdForTypeException(
`Resource does not implement ${iface.name}`,
);
}
}

async doesImplement(resource: ResourceRef, theInterface: ResourceRef) {
const interfaces = await this.getInterfaces(await this.enhance(resource));
return interfaces.includes(await this.enhance(theInterface));
}

private async enhance(ref: ResourceRef) {
return typeof ref === 'string'
? await this.getByDynamicName(ref)
: EnhancedResource.of(ref);
}

async getInterfaces(
resource: EnhancedResource<any>,
): Promise<ReadonlyArray<EnhancedResource<any>>> {
Expand Down

0 comments on commit f6ae866

Please sign in to comment.