Skip to content

Commit

Permalink
Merge pull request #3284 from SeedCompany/edgedb/polymorphic-queries-…
Browse files Browse the repository at this point in the history
…and-engagements

[EdgeDB] Polymorphic queries & Engagement Repo
  • Loading branch information
CarsonF authored Sep 5, 2024
2 parents c54e329 + 8919871 commit 303e5a3
Show file tree
Hide file tree
Showing 21 changed files with 345 additions and 83 deletions.
17 changes: 15 additions & 2 deletions dbschema/engagement.esdl
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ module default {
.<engagement[is Engagement::Ceremony]
));

completedDate: cal::local_date {
completeDate: cal::local_date {
annotation description := "Translation / Growth Plan complete date";
}
disbursementCompleteDate: cal::local_date;
Expand All @@ -51,7 +51,20 @@ module default {
property firstScripture := (
exists .language.firstScriptureEngagement
);


trigger denyDuplicateFirstScriptureBasedOnExternal after insert, update for each do (
assert(
not __new__.firstScripture or not exists __new__.language.hasExternalFirstScripture,
message := "First scripture has already been marked as having been done externally"
)
);
trigger denyDuplicateFirstScriptureBasedOnOtherEngagement after insert, update for each do (
assert(
not exists (select __new__.language.engagements filter .firstScripture),
message := "Another engagement has already been marked as having done the first scripture"
)
);

required lukePartnership: bool {
default := false;
};
Expand Down
21 changes: 21 additions & 0 deletions dbschema/migrations/00005-m1yeyjr.edgeql

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
"cypher-query-builder": "patch:cypher-query-builder@npm%3A6.0.4#~/.yarn/patches/cypher-query-builder-npm-6.0.4-e8707a5e8e.patch",
"dotenv": "^16.3.1",
"dotenv-expand": "^10.0.0",
"edgedb": "^1.6.0-canary.20240506T235920",
"edgedb": "^1.6.0-canary.20240827T111834",
"execa": "^8.0.1",
"express": "^4.18.2",
"extensionless": "^1.7.0",
Expand Down Expand Up @@ -107,7 +107,7 @@
"yaml": "^2.3.3"
},
"devDependencies": {
"@edgedb/generate": "^0.6.0-canary.20240506T235941",
"@edgedb/generate": "github:CarsonF/edgedb-js#workspace=@edgedb/generate&head=temp-host",
"@nestjs/cli": "^10.2.1",
"@nestjs/schematics": "^10.0.3",
"@nestjs/testing": "^10.2.7",
Expand Down
8 changes: 2 additions & 6 deletions src/common/id-field.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@ import { applyDecorators } from '@nestjs/common';
import { Field, FieldOptions, ID as IDType } from '@nestjs/graphql';
import { ValidationOptions } from 'class-validator';
import { IsAny, IsNever, Tagged } from 'type-fest';
import type {
AllResourceDBNames,
ResourceName,
ResourceNameLike,
} from '~/core';
import type { ResourceName, ResourceNameLike } from '~/core';
import { IsId } from './validators';

export const IdField = ({
Expand Down Expand Up @@ -40,4 +36,4 @@ type IDTag<Kind> = IsAny<Kind> extends true
: Kind
: never;

type IDKindLike = ResourceNameLike | AllResourceDBNames | object;
type IDKindLike = ResourceNameLike | object;
28 changes: 26 additions & 2 deletions src/common/resource.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ import { LazyGetter as Once } from 'lazy-get-decorator';
import { DateTime } from 'luxon';
import { keys as keysOf } from 'ts-transformer-keys';
import { inspect } from 'util';
import type { ResourceDBMap, ResourceName } from '~/core';
import type {
ResourceDBMap,
ResourceLike,
ResourceName,
ResourcesHost,
} from '~/core';
import { $, e } from '~/core/edgedb/reexports';
import { ScopedRole } from '../components/authorization/dto';
import { CalculatedSymbol } from './calculated.decorator';
Expand All @@ -27,7 +32,7 @@ const hasTypename = (value: unknown): value is { __typename: string } =>
export const resolveByTypename =
(interfaceName: string) => (value: unknown) => {
if (hasTypename(value)) {
return value.__typename;
return EnhancedResource.resolve(value.__typename).name;
}
throw new ServerException(`Cannot resolve ${interfaceName} type`);
};
Expand Down Expand Up @@ -87,13 +92,22 @@ export class EnhancedResource<T extends ResourceShape<any>> {
static readonly dbTypes = new WeakMap<ResourceShape<any>, $.$expr_PathNode>();
/** @internal */
static readonly dbSkipAccessPolicies = new Set<string>();
/** @internal */
static resourcesHost?: ResourcesHost;

private constructor(readonly type: T) {}
private static readonly refs = new WeakMap<
ResourceShape<any>,
EnhancedResource<any>
>();

static resolve(ref: ResourceLike) {
if (!EnhancedResource.resourcesHost) {
throw new ServerException('Cannot resolve without ResourcesHost');
}
return EnhancedResource.resourcesHost.enhance(ref);
}

static of<T extends ResourceShape<any>>(
resource: T | EnhancedResource<T>,
): EnhancedResource<T> {
Expand Down Expand Up @@ -305,7 +319,17 @@ export type DBType<TResourceStatic extends ResourceShape<any>> =
: never
: never;

/**
* The name of the EdgeDB type, it could be abstract.
*/
export type DBName<T extends $.TypeSet> = T['__element__']['__name__'];
/**
* The name(s) of the concrete EdgeDB types.
* If the type is abstract, then it is a string union of the concrete type's names.
* If the type is concrete, then it is just the name, just as {@link DBName}.
*/
export type DBNames<T extends $.ObjectTypeSet> =
T['__element__']['__polyTypenames__'];

export type MaybeUnsecuredInstance<TResourceStatic extends ResourceShape<any>> =
MaybeSecured<InstanceType<TResourceStatic>>;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { node, relation } from 'cypher-query-builder';
import { DateTime } from 'luxon';
import { ConfigService, EventsHandler, IEventHandler } from '~/core';
import { DatabaseService } from '~/core/database';
import { LanguageEngagement } from '../../engagement/dto';
import { EngagementCreatedEvent } from '../../engagement/events';
import { CeremonyService } from '../ceremony.service';
import { CeremonyType } from '../dto';
Expand All @@ -20,7 +21,7 @@ export class CreateEngagementDefaultCeremonyHandler
const { engagement } = event;
const input = {
type:
engagement.__typename === 'LanguageEngagement'
LanguageEngagement.resolve(engagement) === LanguageEngagement
? CeremonyType.Dedication
: CeremonyType.Certification,
};
Expand Down
15 changes: 11 additions & 4 deletions src/components/engagement/dto/engagement.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
DateInterval,
DateTimeField,
DbLabel,
DBNames,
IntersectTypes,
parentIdMiddleware,
Resource,
Expand Down Expand Up @@ -47,7 +48,7 @@ export type AnyEngagement = MergeExclusive<
const Interfaces = IntersectTypes(Resource, ChangesetAware);

export const resolveEngagementType = (val: Pick<AnyEngagement, '__typename'>) =>
val.__typename === 'LanguageEngagement'
val.__typename === 'default::LanguageEngagement'
? LanguageEngagement
: InternshipEngagement;

Expand All @@ -63,8 +64,9 @@ class Engagement extends Interfaces {
static readonly Props: string[] = keysOf<Engagement>();
static readonly SecuredProps: string[] = keysOf<SecuredProps<Engagement>>();
static readonly Parent = import('../../project/dto').then((m) => m.IProject);
static readonly resolve = resolveEngagementType;

declare readonly __typename: 'LanguageEngagement' | 'InternshipEngagement';
declare readonly __typename: DBNames<typeof e.Engagement>;

readonly project: LinkTo<'Project'> & Pick<IProject, 'status' | 'type'>;

Expand Down Expand Up @@ -154,7 +156,7 @@ export class LanguageEngagement extends Engagement {
(m) => m.TranslationProject,
);

declare readonly __typename: 'LanguageEngagement';
declare readonly __typename: DBNames<typeof e.LanguageEngagement>;

@Field(() => TranslationProject)
declare readonly parent: BaseNode;
Expand Down Expand Up @@ -195,7 +197,7 @@ export class InternshipEngagement extends Engagement {
(m) => m.InternshipProject,
);

declare readonly __typename: 'InternshipEngagement';
declare readonly __typename: DBNames<typeof e.InternshipEngagement>;

@Field(() => InternshipProject)
declare readonly parent: BaseNode;
Expand All @@ -220,6 +222,11 @@ export class InternshipEngagement extends Engagement {
export const engagementRange = (engagement: UnsecuredDto<Engagement>) =>
DateInterval.tryFrom(engagement.startDate, engagement.endDate);

export const EngagementConcretes = {
LanguageEngagement,
InternshipEngagement,
};

declare module '~/core/resources/map' {
interface ResourceMap {
Engagement: typeof Engagement;
Expand Down
Loading

0 comments on commit 303e5a3

Please sign in to comment.