Skip to content

Commit b62b638

Browse files
authored
Merge pull request #3053 from SeedCompany/edgedb/queries/field-zone
2 parents 9028ad7 + 47e725a commit b62b638

14 files changed

+170
-77
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -104,7 +104,7 @@
104104
"yaml": "^2.3.3"
105105
},
106106
"devDependencies": {
107-
"@edgedb/generate": "^0.5.0-canary.20240116T174045",
107+
"@edgedb/generate": "^0.5.0-canary.20240117T152712",
108108
"@nestjs/cli": "^10.2.1",
109109
"@nestjs/schematics": "^10.0.3",
110110
"@nestjs/testing": "^10.2.7",

src/common/pagination.input.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { PipeTransform, Type } from '@nestjs/common';
22
import { Args, ArgsOptions, Field, InputType, Int } from '@nestjs/graphql';
3+
import { setHas } from '@seedcompany/common';
34
import { Matches, Max, Min } from 'class-validator';
45
import { stripIndent } from 'common-tags';
56
import { DataObject } from './data-object';
@@ -28,6 +29,14 @@ export abstract class PaginationInput extends DataObject {
2829
readonly page: number = 1;
2930
}
3031

32+
export const isPaginationInput = (input: unknown): input is PaginationInput =>
33+
!!input &&
34+
typeof input === 'object' &&
35+
'count' in input &&
36+
typeof input.count === 'number' &&
37+
'page' in input &&
38+
typeof input.page === 'number';
39+
3140
@InputType({
3241
isAbstract: true,
3342
})
@@ -62,6 +71,16 @@ export interface SortablePaginationInput<SortKey extends string = string>
6271
order: Order;
6372
}
6473

74+
export const isSortablePaginationInput = (
75+
input: unknown,
76+
): input is SortablePaginationInput =>
77+
isPaginationInput(input) &&
78+
'sort' in input &&
79+
typeof input.sort === 'string' &&
80+
'order' in input &&
81+
typeof input.order === 'string' &&
82+
setHas(Order.values, input.order);
83+
6584
export const SortablePaginationInput = <SortKey extends string = string>({
6685
defaultSort,
6786
defaultOrder,

src/components/field-zone/dto/field-zone.dto.ts

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
import { ObjectType } from '@nestjs/graphql';
22
import { keys as keysOf } from 'ts-transformer-keys';
33
import { e } from '~/core/edgedb';
4-
import { RegisterResource } from '~/core/resources';
4+
import { LinkTo, RegisterResource } from '~/core/resources';
55
import {
66
DbUnique,
7-
ID,
87
NameField,
98
Resource,
109
Secured,
@@ -26,7 +25,7 @@ export class FieldZone extends Resource {
2625
@DbUnique()
2726
readonly name: SecuredString;
2827

29-
readonly director: Secured<ID>;
28+
readonly director: Secured<LinkTo<'User'>>;
3029
}
3130

3231
@ObjectType({
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
import { Injectable } from '@nestjs/common';
2+
import { PublicOf } from '~/common';
3+
import { ChangesOf } from '~/core/database/changes';
4+
import { e, RepoFor } from '~/core/edgedb';
5+
import { CreateFieldZone, FieldZone, UpdateFieldZone } from './dto';
6+
import { FieldZoneRepository } from './field-zone.repository';
7+
8+
@Injectable()
9+
export class FieldZoneEdgeDBRepository
10+
extends RepoFor(FieldZone, {
11+
hydrate: (zone) => ({
12+
...zone['*'],
13+
director: true,
14+
}),
15+
}).customize((cls) => {
16+
return class extends cls {
17+
async create(input: CreateFieldZone) {
18+
const created = e.insert(e.FieldZone, {
19+
name: input.name,
20+
director: e.cast(e.User, e.cast(e.uuid, input.directorId)),
21+
});
22+
const query = e.select(created, this.hydrate);
23+
return await this.db.run(query);
24+
}
25+
26+
async update(
27+
{ id }: Pick<FieldZone, 'id'>,
28+
changes: ChangesOf<FieldZone, UpdateFieldZone>,
29+
) {
30+
const zone = e.cast(e.FieldZone, e.cast(e.uuid, id));
31+
const updated = e.update(zone, () => ({
32+
set: {
33+
...(changes.name && { name: changes.name }),
34+
...(changes.directorId && {
35+
director: e.cast(e.User, e.cast(e.uuid, changes.directorId)),
36+
}),
37+
},
38+
}));
39+
const query = e.select(updated, this.hydrate);
40+
return await this.db.run(query);
41+
}
42+
};
43+
})
44+
implements PublicOf<FieldZoneRepository> {}

src/components/field-zone/field-zone.module.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
import { forwardRef, Module } from '@nestjs/common';
2+
import { splitDb } from '~/core';
23
import { AuthorizationModule } from '../authorization/authorization.module';
34
import { UserModule } from '../user/user.module';
5+
import { FieldZoneEdgeDBRepository } from './field-zone.edgedb.repository';
46
import { FieldZoneLoader } from './field-zone.loader';
57
import { FieldZoneRepository } from './field-zone.repository';
68
import { FieldZoneResolver } from './field-zone.resolver';
@@ -14,7 +16,7 @@ import { FieldZoneService } from './field-zone.service';
1416
providers: [
1517
FieldZoneResolver,
1618
FieldZoneService,
17-
FieldZoneRepository,
19+
splitDb(FieldZoneRepository, FieldZoneEdgeDBRepository),
1820
FieldZoneLoader,
1921
],
2022
exports: [FieldZoneService],

src/components/field-zone/field-zone.repository.ts

Lines changed: 28 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,14 @@ import { Injectable } from '@nestjs/common';
22
import { node, Query, relation } from 'cypher-query-builder';
33
import { DateTime } from 'luxon';
44
import { ChangesOf } from '~/core/database/changes';
5-
import { ID, Session, UnsecuredDto } from '../../common';
5+
import {
6+
DuplicateException,
7+
ID,
8+
SecuredList,
9+
ServerException,
10+
Session,
11+
UnsecuredDto,
12+
} from '../../common';
613
import { DtoRepository } from '../../core';
714
import {
815
ACTIVE,
@@ -25,6 +32,13 @@ import {
2532
@Injectable()
2633
export class FieldZoneRepository extends DtoRepository(FieldZone) {
2734
async create(input: CreateFieldZone, session: Session) {
35+
if (!(await this.isUnique(input.name))) {
36+
throw new DuplicateException(
37+
'fieldZone.name',
38+
'FieldZone with this name already exists.',
39+
);
40+
}
41+
2842
const initialProps = {
2943
name: input.name,
3044
canDelete: true,
@@ -42,7 +56,12 @@ export class FieldZoneRepository extends DtoRepository(FieldZone) {
4256
)
4357
.return<{ id: ID }>('node.id as id');
4458

45-
return await query.first();
59+
const result = await query.first();
60+
if (!result) {
61+
throw new ServerException('failed to create field zone');
62+
}
63+
64+
return await this.readOne(result.id);
4665
}
4766

4867
protected hydrate() {
@@ -56,13 +75,13 @@ export class FieldZoneRepository extends DtoRepository(FieldZone) {
5675
])
5776
.return<{ dto: UnsecuredDto<FieldZone> }>(
5877
merge('props', {
59-
director: 'director.id',
78+
director: 'director { .id }',
6079
}).as('dto'),
6180
);
6281
}
6382

6483
async update(
65-
existing: FieldZone,
84+
existing: Pick<FieldZone, 'id'>,
6685
changes: ChangesOf<FieldZone, UpdateFieldZone>,
6786
) {
6887
const { directorId, ...simpleChanges } = changes;
@@ -72,6 +91,8 @@ export class FieldZoneRepository extends DtoRepository(FieldZone) {
7291
}
7392

7493
await this.updateProperties(existing, simpleChanges);
94+
95+
return await this.readOne(existing.id);
7596
}
7697

7798
private async updateDirector(directorId: ID, id: ID) {
@@ -103,6 +124,9 @@ export class FieldZoneRepository extends DtoRepository(FieldZone) {
103124
}
104125

105126
async list({ filter, ...input }: FieldZoneListInput, session: Session) {
127+
if (!this.privileges.forUser(session).can('read')) {
128+
return SecuredList.Redacted;
129+
}
106130
const result = await this.db
107131
.query()
108132
.match(requestingUser(session))

src/components/field-zone/field-zone.resolver.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,9 @@ export class FieldZoneResolver {
6262
@Parent() fieldZone: FieldZone,
6363
@Loader(UserLoader) users: LoaderOf<UserLoader>,
6464
): Promise<SecuredUser> {
65-
return await mapSecuredValue(fieldZone.director, (id) => users.load(id));
65+
return await mapSecuredValue(fieldZone.director, ({ id }) =>
66+
users.load(id),
67+
);
6668
}
6769

6870
@Mutation(() => CreateFieldZoneOutput, {
Lines changed: 13 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import { Injectable } from '@nestjs/common';
22
import {
3-
DuplicateException,
43
ID,
54
ObjectView,
6-
SecuredList,
75
ServerException,
86
Session,
97
UnsecuredDto,
108
} from '../../common';
119
import { HandleIdLookup, ILogger, Logger } from '../../core';
12-
import { mapListResults } from '../../core/database/results';
1310
import { Privileges } from '../authorization';
1411
import {
1512
CreateFieldZone,
@@ -30,20 +27,8 @@ export class FieldZoneService {
3027

3128
async create(input: CreateFieldZone, session: Session): Promise<FieldZone> {
3229
this.privileges.for(session, FieldZone).verifyCan('create');
33-
if (!(await this.repo.isUnique(input.name))) {
34-
throw new DuplicateException(
35-
'fieldZone.name',
36-
'FieldZone with this name already exists.',
37-
);
38-
}
39-
const result = await this.repo.create(input, session);
40-
41-
if (!result) {
42-
throw new ServerException('failed to create field zone');
43-
}
44-
45-
this.logger.debug(`field zone created`, { id: result.id });
46-
return await this.readOne(result.id, session);
30+
const dto = await this.repo.create(input, session);
31+
return this.secure(dto, session);
4732
}
4833

4934
@HandleIdLookup(FieldZone)
@@ -58,32 +43,26 @@ export class FieldZoneService {
5843
});
5944

6045
const result = await this.repo.readOne(id);
61-
return await this.secure(result, session);
46+
return this.secure(result, session);
6247
}
6348

6449
async readMany(ids: readonly ID[], session: Session) {
6550
const fieldZones = await this.repo.readMany(ids);
66-
return await Promise.all(
67-
fieldZones.map((dto) => this.secure(dto, session)),
68-
);
51+
return fieldZones.map((dto) => this.secure(dto, session));
6952
}
7053

71-
private async secure(
72-
dto: UnsecuredDto<FieldZone>,
73-
session: Session,
74-
): Promise<FieldZone> {
54+
private secure(dto: UnsecuredDto<FieldZone>, session: Session) {
7555
return this.privileges.for(session, FieldZone).secure(dto);
7656
}
7757

7858
async update(input: UpdateFieldZone, session: Session): Promise<FieldZone> {
79-
const fieldZone = await this.readOne(input.id, session);
59+
const fieldZone = await this.repo.readOne(input.id);
8060

8161
const changes = this.repo.getActualChanges(fieldZone, input);
8262
this.privileges.for(session, FieldZone, fieldZone).verifyChanges(changes);
8363

84-
await this.repo.update(fieldZone, changes);
85-
86-
return await this.readOne(input.id, session);
64+
const updated = await this.repo.update(fieldZone, changes);
65+
return this.secure(updated, session);
8766
}
8867

8968
async delete(id: ID, session: Session): Promise<void> {
@@ -103,11 +82,10 @@ export class FieldZoneService {
10382
input: FieldZoneListInput,
10483
session: Session,
10584
): Promise<FieldZoneListOutput> {
106-
if (this.privileges.for(session, FieldZone).can('read')) {
107-
const results = await this.repo.list(input, session);
108-
return await mapListResults(results, (dto) => this.secure(dto, session));
109-
} else {
110-
return SecuredList.Redacted;
111-
}
85+
const results = await this.repo.list(input, session);
86+
return {
87+
...results,
88+
items: results.items.map((dto) => this.secure(dto, session)),
89+
};
11290
}
11391
}

src/components/file/file.repository.ts

Lines changed: 2 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,7 @@ import { Direction } from 'cypher-query-builder/dist/typings/clauses/order-by';
1414
import { AnyConditions } from 'cypher-query-builder/dist/typings/clauses/where-utils';
1515
import { DateTime } from 'luxon';
1616
import { ID, NotFoundException, ServerException, Session } from '../../common';
17-
import {
18-
CommonRepository,
19-
ILogger,
20-
Logger,
21-
OnIndex,
22-
ResourceRef,
23-
} from '../../core';
17+
import { CommonRepository, ILogger, LinkTo, Logger, OnIndex } from '../../core';
2418
import {
2519
ACTIVE,
2620
createNode,
@@ -407,7 +401,7 @@ export class FileRepository extends CommonRepository {
407401
public: isPublic,
408402
session,
409403
}: {
410-
resource: ResourceRef<any>;
404+
resource: LinkTo<any>;
411405
relation: string;
412406
name: string;
413407
public?: boolean;

src/components/prompts/dto/prompt-response.dto.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,14 +18,14 @@ import {
1818
UnsecuredDto,
1919
Variant,
2020
} from '~/common';
21-
import { ResourceRef } from '~/core';
21+
import { LinkToUnknown } from '~/core';
2222
import { BaseNode } from '~/core/database/results';
2323
import { User } from '../../user';
2424
import { Prompt, SecuredPrompt } from './prompt.dto';
2525

2626
@ObjectType()
2727
export class PromptResponse extends Resource {
28-
readonly parent: ResourceRef<any>;
28+
readonly parent: LinkToUnknown;
2929

3030
@Field()
3131
readonly prompt: SecuredPrompt;

0 commit comments

Comments
 (0)