-
Notifications
You must be signed in to change notification settings - Fork 70
/
belongs-to-resolver.service.ts
128 lines (123 loc) · 3.79 KB
/
belongs-to-resolver.service.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
// Copyright (c) 2023 Sourcefuse Technologies
//
// This software is released under the MIT License.
// https://opensource.org/licenses/MIT
import {Context, inject} from '@loopback/core';
import {
BelongsToDefinition,
Entity,
Filter,
RelationType,
WhereBuilder,
} from '@loopback/repository';
import {asRestResolver} from '../../constants';
import {
IRestResolver,
ModifiedRestService,
RestLinkerParams,
RestRelationConfig,
RestResolverParams,
isConfigWithKey,
isConfigWithModelClass,
} from '../types';
@asRestResolver()
export class BelongsToRestResolver<T extends Entity, S extends Entity>
implements IRestResolver<T, S>
{
type: RelationType = RelationType.belongsTo;
constructor(
@inject.context()
private context: Context,
) {}
/**
* It takes the results of the query, finds the foreign key in each result, and then uses that key to
* find the related record in the related service
* @param - `relationConfig` is the configuration object for the relation.
* @returns A map of related records.
*/
async resolve({
relationConfig,
relationMetadata,
results,
scope,
token,
}: RestResolverParams<T, S> & {
relationMetadata: BelongsToDefinition;
}) {
const relatedService = await this.getService(relationConfig);
const keyFrom = (relationMetadata.keyFrom ??
this.toPascalCase(relationMetadata.source.name) + 'Id') as keyof T;
const keyTo = (relationMetadata.keyTo ?? 'id') as keyof S;
const filter = await this.addConditionToScope(
results.map(r => r[keyFrom]),
scope,
);
const relatedResults = await relatedService.find(
filter ?? {},
token,
relationConfig,
);
const relatedRecordMap = new Map<S[keyof S] | T[keyof T], S>();
for (const item of relatedResults) {
const key = item[keyTo];
if (!relatedRecordMap.has(key)) {
relatedRecordMap.set(key, item);
}
}
return relatedRecordMap;
}
/**
* It takes a parent object, looks up the foreign key on the parent, and then uses that foreign key to
* look up and link the related object in the resolvedDataMap
* @param - `relationMetadata` is the metadata for the relation being linked.
* @returns The parent object with the relationName property set to the
* resolvedDataMap.get(parent[keyFrom])
*/
async link({
relationMetadata,
parent,
resolvedDataMap,
token,
}: RestLinkerParams<T, S> & {
relationMetadata: BelongsToDefinition;
}) {
const keyFrom = (relationMetadata.keyFrom ??
this.toPascalCase(relationMetadata.source.name) + 'Id') as keyof T;
const relationName = relationMetadata.name as keyof T;
parent[relationName] = resolvedDataMap.get(parent[keyFrom]) as T[keyof T];
return parent;
}
private async addConditionToScope(ids: T[keyof T][], scope?: Filter<S>) {
const condition = {
id: {
inq: ids.filter(id => id), //filter out valid IDs
},
};
const whereBuilder = new WhereBuilder();
whereBuilder.and([condition, scope?.where].filter(w => !!w));
return {
...scope,
where: whereBuilder.build(),
};
}
private toPascalCase(str: string) {
return str
.replace(/(?:^\w|[A-Z]|\b\w)/g, function (word, index) {
return index === 0 ? word.toLowerCase() : word.toUpperCase();
})
.replace(/\s+/g, '');
}
private async getService(config: RestRelationConfig) {
if (isConfigWithKey(config)) {
return this.context.get<ModifiedRestService<S>>(config.serviceKey);
} else if (isConfigWithModelClass(config)) {
return this.context.get<ModifiedRestService<S>>(
`services.${config.modelClass.name}Proxy`,
);
} else {
return this.context.get<ModifiedRestService<S>>(
`services.${config.serviceClass.name}`,
);
}
}
}