Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[EdgeDB] Polymorphic queries & Engagement Repo #3284

Merged
merged 11 commits into from
Sep 5, 2024
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
Loading