Skip to content

Commit 2e5f9b6

Browse files
committed
Add ActorLoader & queries to handle both Users & SystemAgents
1 parent da8b382 commit 2e5f9b6

File tree

5 files changed

+77
-4
lines changed

5 files changed

+77
-4
lines changed
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import { ID } from '~/common';
2+
import { LoaderFactory, SessionAwareLoaderStrategy } from '~/core';
3+
import { Actor } from './dto';
4+
import { UserService } from './user.service';
5+
6+
@LoaderFactory(() => Actor)
7+
export class ActorLoader extends SessionAwareLoaderStrategy<Actor> {
8+
constructor(private readonly users: UserService) {
9+
super();
10+
}
11+
12+
async loadMany(ids: readonly ID[]) {
13+
return await this.users.readManyActors(ids, this.session);
14+
}
15+
}

src/components/user/user.edgedb.repository.ts

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { Injectable } from '@nestjs/common';
2-
import { NotImplementedException, PublicOf } from '~/common';
2+
import { ID, NotImplementedException, PublicOf } from '~/common';
33
import { e, RepoFor, ScopeOf } from '~/core/edgedb';
44
import {
55
AssignOrganizationToUser,
@@ -9,13 +9,35 @@ import {
99
} from './dto';
1010
import type { UserRepository } from './user.repository';
1111

12+
const hydrateUser = e.shape(e.User, (user) => ({
13+
...user['*'],
14+
__typename: e.str('User'),
15+
}));
16+
const hydrateSystemAgent = e.shape(e.SystemAgent, (agent) => ({
17+
...agent['*'],
18+
__typename: e.str('SystemAgent'),
19+
}));
20+
1221
@Injectable()
1322
export class UserEdgeDBRepository
14-
extends RepoFor(User, {
15-
hydrate: (user) => user['*'],
16-
})
23+
extends RepoFor(User, { hydrate: hydrateUser })
1724
implements PublicOf<UserRepository>
1825
{
26+
async readManyActors(ids: readonly ID[]) {
27+
const res = await this.db.run(this.readManyActorsQuery, { ids });
28+
return [...res.users, ...res.agents];
29+
}
30+
private readonly readManyActorsQuery = e.params(
31+
{ ids: e.array(e.uuid) },
32+
({ ids }) => {
33+
const actors = e.cast(e.Actor, e.array_unpack(ids));
34+
return e.select({
35+
users: e.select(actors.is(e.User), hydrateUser),
36+
agents: e.select(actors.is(e.SystemAgent), hydrateSystemAgent),
37+
});
38+
},
39+
);
40+
1941
async doesEmailAddressExist(email: string) {
2042
const query = e.select(e.User, () => ({
2143
filter_single: { email },

src/components/user/user.module.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { OrganizationModule } from '../organization/organization.module';
88
import { PartnerModule } from '../partner/partner.module';
99
import { TimeZoneModule } from '../timezone';
1010
import { ActorEdgeDBRepository } from './actor.edgedb.repository';
11+
import { ActorLoader } from './actor.loader';
1112
import { ActorNeo4jRepository } from './actor.neo4j.repository';
1213
import { ActorRepository } from './actor.repository';
1314
import { AssignableRolesResolver } from './assignable-roles.resolver';
@@ -39,6 +40,7 @@ import { UserService } from './user.service';
3940
UserResolver,
4041
AssignableRolesResolver,
4142
UserLoader,
43+
ActorLoader,
4244
UserService,
4345
splitDb(UserRepository, UserEdgeDBRepository),
4446
KnownLanguageRepository,

src/components/user/user.repository.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
AssignOrganizationToUser,
3030
CreatePerson,
3131
RemoveOrganizationFromUser,
32+
SystemAgent,
3233
UpdateUser,
3334
User,
3435
UserListInput,
@@ -38,6 +39,25 @@ import {
3839
export class UserRepository extends DtoRepository<typeof User, [Session | ID]>(
3940
User,
4041
) {
42+
async readManyActors(ids: readonly ID[], session: Session) {
43+
return await this.db
44+
.query()
45+
.raw('', { ids })
46+
.matchNode('user', 'User')
47+
.where({ 'user.id': inArray('ids', true) })
48+
.apply(this.hydrate(session))
49+
.union()
50+
.matchNode('agent', 'SystemAgent')
51+
.where({ 'agent.id': inArray('ids', true) })
52+
.return<{ dto: UnsecuredDto<User | SystemAgent> }>(
53+
merge('agent', {
54+
__typename: '"SystemAgent"',
55+
}).as('dto'),
56+
)
57+
.map('dto')
58+
.run();
59+
}
60+
4161
private readonly roleProperties = (roles?: readonly Role[]) =>
4262
(roles || []).flatMap((role) =>
4363
property('roles', role, 'node', `role${role}`),
@@ -116,6 +136,7 @@ export class UserRepository extends DtoRepository<typeof User, [Session | ID]>(
116136
.match(requestingUser(requestingUserId))
117137
.return<{ dto: UnsecuredDto<User> }>(
118138
merge({ email: null }, 'props', {
139+
__typename: '"User"',
119140
roles: 'roles',
120141
pinned: 'exists((requestingUser)-[:pinned]->(node))',
121142
}).as('dto'),

src/components/user/user.service.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import {
2929
CreatePerson,
3030
ModifyKnownLanguageArgs,
3131
RemoveOrganizationFromUser,
32+
SystemAgent,
3233
UpdateUser,
3334
User,
3435
UserListInput,
@@ -86,6 +87,18 @@ export class UserService {
8687
return users.map((dto) => this.secure(dto, session));
8788
}
8889

90+
async readManyActors(
91+
ids: readonly ID[],
92+
session: Session,
93+
): Promise<ReadonlyArray<User | SystemAgent>> {
94+
const users = await this.userRepo.readManyActors(ids, session);
95+
return users.map((dto) =>
96+
dto.__typename === 'User'
97+
? this.secure(dto, session)
98+
: (dto as SystemAgent),
99+
);
100+
}
101+
89102
secure(user: UnsecuredDto<User>, session: Session): User {
90103
return this.privileges.for(session, User).secure(user);
91104
}

0 commit comments

Comments
 (0)