Skip to content

Commit 22ba014

Browse files
authored
Merge pull request #3257 from SeedCompany/primary-partner
2 parents e8b6f6d + f139701 commit 22ba014

13 files changed

+239
-63
lines changed

src/components/organization/dto/list-organization.dto.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { InputType, ObjectType } from '@nestjs/graphql';
1+
import { Field, InputType, ObjectType } from '@nestjs/graphql';
22
import {
33
FilterField,
44
ID,
@@ -10,6 +10,11 @@ import { Organization } from './organization.dto';
1010

1111
@InputType()
1212
export abstract class OrganizationFilters {
13+
@Field({
14+
nullable: true,
15+
})
16+
readonly name?: string;
17+
1318
readonly userId?: ID;
1419
}
1520

src/components/organization/organization.repository.ts

Lines changed: 41 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,13 @@ import {
77
Session,
88
UnsecuredDto,
99
} from '~/common';
10-
import { DtoRepository } from '~/core/database';
10+
import { DtoRepository, OnIndex } from '~/core/database';
1111
import {
1212
ACTIVE,
1313
createNode,
14+
defineSorters,
15+
filter,
16+
FullTextIndex,
1417
matchProjectScopedRoles,
1518
matchProjectSens,
1619
matchProps,
@@ -19,11 +22,12 @@ import {
1922
paginate,
2023
rankSens,
2124
requestingUser,
22-
sorting,
25+
sortWith,
2326
} from '~/core/database/query';
2427
import {
2528
CreateOrganization,
2629
Organization,
30+
OrganizationFilters,
2731
OrganizationListInput,
2832
UpdateOrganization,
2933
} from './dto';
@@ -111,20 +115,12 @@ export class OrganizationRepository extends DtoRepository<
111115
);
112116
}
113117

114-
async list({ filter, ...input }: OrganizationListInput, session: Session) {
118+
async list(input: OrganizationListInput, session: Session) {
115119
const query = this.db
116120
.query()
117121
.matchNode('node', 'Organization')
118-
.match([
119-
...(filter?.userId && session.userId
120-
? [
121-
node('node'),
122-
relation('in', '', 'organization', ACTIVE),
123-
node('user', 'User', { id: filter.userId }),
124-
]
125-
: []),
126-
])
127122
.match(requestingUser(session))
123+
.apply(organizationFilters(input.filter))
128124
.apply(
129125
this.privileges.forUser(session).filterToReadable({
130126
wrapContext: (inner) => (query) =>
@@ -141,8 +137,40 @@ export class OrganizationRepository extends DtoRepository<
141137
.apply(oncePerProject(inner)),
142138
}),
143139
)
144-
.apply(sorting(Organization, input))
140+
.apply(sortWith(organizationSorters, input))
145141
.apply(paginate(input, this.hydrate(session)));
146142
return (await query.first())!; // result from paginate() will always have 1 row.
147143
}
144+
145+
@OnIndex('schema')
146+
private async createSchemaIndexes() {
147+
await this.db.query().apply(OrgNameIndex.create()).run();
148+
}
148149
}
150+
151+
export const organizationFilters = filter.define(() => OrganizationFilters, {
152+
userId: filter.pathExists((id) => [
153+
node('node'),
154+
relation('in', '', 'organization', ACTIVE),
155+
node('', 'User', { id }),
156+
]),
157+
name: filter.fullText({
158+
index: () => OrgNameIndex,
159+
matchToNode: (q) =>
160+
q.match([
161+
node('node', 'Organization'),
162+
relation('out', '', 'name', ACTIVE),
163+
node('match'),
164+
]),
165+
minScore: 0.8,
166+
}),
167+
});
168+
169+
export const organizationSorters = defineSorters(Organization, {});
170+
171+
const OrgNameIndex = FullTextIndex({
172+
indexName: 'OrganizationName',
173+
labels: 'OrgName',
174+
properties: 'value',
175+
analyzer: 'standard-folding',
176+
});

src/components/partner/dto/list-partner.dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
SecuredList,
77
SortablePaginationInput,
88
} from '~/common';
9+
import { OrganizationFilters } from '../../organization/dto';
910
import { Partner } from './partner.dto';
1011

1112
@InputType()
@@ -18,6 +19,9 @@ export abstract class PartnerFilters {
1819
nullable: true,
1920
})
2021
readonly pinned?: boolean;
22+
23+
@FilterField(() => OrganizationFilters)
24+
readonly organization?: OrganizationFilters & {};
2125
}
2226

2327
@InputType()

src/components/partner/partner.repository.ts

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ import {
1616
collect,
1717
createNode,
1818
createRelationships,
19+
defineSorters,
20+
filter,
1921
filter as filters,
2022
matchProjectScopedRoles,
2123
matchProjectSens,
@@ -25,9 +27,19 @@ import {
2527
paginate,
2628
rankSens,
2729
requestingUser,
28-
sorting,
30+
sortWith,
2931
} from '~/core/database/query';
30-
import { CreatePartner, Partner, PartnerListInput, UpdatePartner } from './dto';
32+
import {
33+
organizationFilters,
34+
organizationSorters,
35+
} from '../organization/organization.repository';
36+
import {
37+
CreatePartner,
38+
Partner,
39+
PartnerFilters,
40+
PartnerListInput,
41+
UpdatePartner,
42+
} from './dto';
3143

3244
@Injectable()
3345
export class PartnerRepository extends DtoRepository<
@@ -262,28 +274,12 @@ export class PartnerRepository extends DtoRepository<
262274
);
263275
}
264276

265-
async list({ filter, ...input }: PartnerListInput, session: Session) {
277+
async list(input: PartnerListInput, session: Session) {
266278
const result = await this.db
267279
.query()
268280
.matchNode('node', 'Partner')
269-
.match([
270-
...(filter?.userId && session.userId
271-
? [
272-
node('node'),
273-
relation('out', '', 'organization', ACTIVE),
274-
node('', 'Organization'),
275-
relation('in', '', 'organization', ACTIVE),
276-
node('user', 'User', { id: filter.userId }),
277-
]
278-
: []),
279-
])
280281
.match(requestingUser(session))
281-
.apply(
282-
filters.builder(filter ?? {}, {
283-
pinned: filters.isPinned,
284-
userId: filters.skip, // already applied above
285-
}),
286-
)
282+
.apply(partnerFilters(input.filter))
287283
.apply(
288284
this.privileges.forUser(session).filterToReadable({
289285
wrapContext: (inner) => (query) =>
@@ -298,22 +294,52 @@ export class PartnerRepository extends DtoRepository<
298294
.apply(oncePerProject(inner)),
299295
}),
300296
)
301-
.apply(
302-
sorting(Partner, input, {
303-
name: (query) =>
304-
query
305-
.match([
306-
node('node'),
307-
relation('out', '', 'organization', ACTIVE),
308-
node('organization', 'Organization'),
309-
relation('out', '', 'name', ACTIVE),
310-
node('prop', 'Property'),
311-
])
312-
.return<{ sortValue: string }>('prop.value as sortValue'),
313-
}),
314-
)
297+
.apply(sortWith(partnerSorters, input))
315298
.apply(paginate(input, this.hydrate(session)))
316299
.first();
317300
return result!; // result from paginate() will always have 1 row.
318301
}
319302
}
303+
304+
export const partnerFilters = filters.define(() => PartnerFilters, {
305+
pinned: filters.isPinned,
306+
userId: filters.pathExists((id) => [
307+
node('node'),
308+
relation('out', '', 'organization', ACTIVE),
309+
node('', 'Organization'),
310+
relation('in', '', 'organization', ACTIVE),
311+
node('', 'User', { id }),
312+
]),
313+
organization: filter.sub(() => organizationFilters)((sub) =>
314+
sub
315+
.with('node as partner')
316+
.match([
317+
node('partner'),
318+
relation('out', '', 'organization'),
319+
node('node', 'Organization'),
320+
]),
321+
),
322+
});
323+
324+
export const partnerSorters = defineSorters(Partner, {
325+
name: (query) =>
326+
query
327+
.match([
328+
node('node'),
329+
relation('out', '', 'organization', ACTIVE),
330+
node('organization', 'Organization'),
331+
relation('out', '', 'name', ACTIVE),
332+
node('prop', 'Property'),
333+
])
334+
.return<{ sortValue: string }>('prop.value as sortValue'),
335+
// eslint-disable-next-line @typescript-eslint/naming-convention
336+
'organization.*': (query, input) =>
337+
query
338+
.with('node as partner')
339+
.match([
340+
node('partner'),
341+
relation('out', '', 'organization'),
342+
node('node', 'Organization'),
343+
])
344+
.apply(sortWith(organizationSorters, input)),
345+
});

src/components/partnership/dto/list-partnership.dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,15 @@ import {
66
SecuredList,
77
SortablePaginationInput,
88
} from '~/common';
9+
import { PartnerFilters } from '../../partner/dto';
910
import { Partnership } from './partnership.dto';
1011

1112
@InputType()
1213
export abstract class PartnershipFilters {
1314
readonly projectId?: ID;
15+
16+
@FilterField(() => PartnerFilters)
17+
readonly partner?: PartnerFilters & {};
1418
}
1519

1620
@InputType()

src/components/partnership/dto/partnership.dto.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import {
88
Secured,
99
SecuredBoolean,
1010
SecuredDateNullable,
11+
SecuredProperty,
1112
SecuredProps,
1213
Sensitivity,
1314
SensitivityField,
@@ -92,3 +93,8 @@ declare module '~/core/resources/map' {
9293
Partnership: typeof e.default.Partnership;
9394
}
9495
}
96+
97+
@ObjectType({
98+
description: SecuredProperty.descriptionFor('a partnership'),
99+
})
100+
export class SecuredPartnership extends SecuredProperty(Partnership) {}

src/components/partnership/partnership.repository.ts

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@ import {
2020
coalesce,
2121
createNode,
2222
createRelationships,
23+
defineSorters,
24+
filter,
2325
INACTIVE,
2426
matchChangesetAndChangedProps,
2527
matchProps,
@@ -28,15 +30,17 @@ import {
2830
oncePerProject,
2931
paginate,
3032
requestingUser,
31-
sorting,
33+
sortWith,
3234
whereNotDeletedInChangeset,
3335
} from '~/core/database/query';
3436
import { FileService } from '../file';
3537
import { FileId } from '../file/dto';
38+
import { partnerFilters, partnerSorters } from '../partner/partner.repository';
3639
import {
3740
CreatePartnership,
3841
Partnership,
3942
PartnershipAgreementStatus,
43+
PartnershipFilters,
4044
PartnershipListInput,
4145
UpdatePartnership,
4246
} from './dto';
@@ -233,12 +237,13 @@ export class PartnershipRepository extends DtoRepository<
233237
),
234238
)
235239
.match(requestingUser(session))
240+
.apply(partnershipFilters(input.filter))
236241
.apply(
237242
this.privileges.forUser(session).filterToReadable({
238243
wrapContext: oncePerProject,
239244
}),
240245
)
241-
.apply(sorting(Partnership, input))
246+
.apply(sortWith(partnershipSorters, input))
242247
.apply(paginate(input, this.hydrate(session, viewOfChangeset(changeset))))
243248
.first();
244249
return result!; // result from paginate() will always have 1 row.
@@ -398,3 +403,29 @@ export class PartnershipRepository extends DtoRepository<
398403
};
399404
}
400405
}
406+
407+
export const partnershipFilters = filter.define(() => PartnershipFilters, {
408+
projectId: filter.skip,
409+
partner: filter.sub(() => partnerFilters)((sub) =>
410+
sub
411+
.with('node as partnership')
412+
.match([
413+
node('partnership'),
414+
relation('out', '', 'partner'),
415+
node('node', 'Partner'),
416+
]),
417+
),
418+
});
419+
420+
export const partnershipSorters = defineSorters(Partnership, {
421+
// eslint-disable-next-line @typescript-eslint/naming-convention
422+
'partner.*': (query, input) =>
423+
query
424+
.with('node as partnership')
425+
.match([
426+
node('partnership'),
427+
relation('out', '', 'partner'),
428+
node('node', 'Partner'),
429+
])
430+
.apply(sortWith(partnerSorters, input)),
431+
});

src/components/project/dto/list-projects.dto.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import {
1111
Sensitivity,
1212
SortablePaginationInput,
1313
} from '~/common';
14+
import { PartnershipFilters } from '../../partnership/dto';
1415
import { ProjectStatus } from './project-status.enum';
1516
import { ProjectStep } from './project-step.enum';
1617
import { ProjectType } from './project-type.enum';
@@ -99,6 +100,9 @@ export abstract class ProjectFilters {
99100
readonly partnerId?: ID;
100101

101102
readonly userId?: ID;
103+
104+
@FilterField(() => PartnershipFilters)
105+
readonly primaryPartnership?: PartnershipFilters & {};
102106
}
103107

104108
@InputType()

src/components/project/dto/project.dto.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,8 @@ class Project extends Interfaces {
124124
@Calculated()
125125
readonly status: ProjectStatus;
126126

127+
readonly primaryPartnership: Secured<LinkTo<'Partnership'> | null>;
128+
127129
readonly primaryLocation: Secured<LinkTo<'Location'> | null>;
128130

129131
readonly marketingLocation: Secured<LinkTo<'Location'> | null>;

0 commit comments

Comments
 (0)