-
Notifications
You must be signed in to change notification settings - Fork 70
/
has-one-resolver.service.ts
155 lines (148 loc) · 4.43 KB
/
has-one-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
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
// 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 {
Entity,
Filter,
HasOneDefinition,
RelationType,
WhereBuilder,
} from '@loopback/repository';
import {asRestResolver} from '../../constants';
import {
IRestResolver,
ModifiedRestService,
RestLinkerParams,
RestRelationConfig,
RestResolverParams,
isConfigWithKey,
isConfigWithModelClass,
} from '../types';
@asRestResolver()
export class HasOneRestResolver<Source extends Entity, Target extends Entity>
implements IRestResolver<Source, Target>
{
type: RelationType = RelationType.hasOne;
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<Source, Target> & {
relationMetadata: HasOneDefinition;
}) {
const relatedService = await this.getService(relationConfig);
const keyFrom = this.getKeyFrom(relationMetadata);
const keyTo = (relationMetadata.keyTo ??
this.toPascalCase(relationMetadata.source.name) + 'Id') as keyof Target;
const filter = await this.addConditionToScope(
keyTo,
results.map(r => r[keyFrom]),
scope,
);
const relatedResults = await this.findRelatedData(
relatedService,
filter,
token,
relationConfig,
);
const relatedRecordMap = new Map<
Target[keyof Target] | Source[keyof Source],
Target
>();
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,
}: RestLinkerParams<Source, Target> & {
relationMetadata: HasOneDefinition;
}) {
const keyFrom = this.getKeyFrom(relationMetadata);
const relationName = relationMetadata.name as keyof Source;
parent[relationName] = resolvedDataMap.get(
parent[keyFrom],
) as Source[keyof Source];
return parent;
}
private async addConditionToScope(
keyTo: keyof Target,
ids: Source[keyof Source][],
scope?: Filter<Target>,
) {
const condition = {
[keyTo]: {
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 findRelatedData(
service: ModifiedRestService<Target>,
filter: Filter<Target>,
token?: string,
config?: RestRelationConfig,
) {
return service.find(filter, token, config);
}
private getKeyFrom(relationMetadata: HasOneDefinition) {
const sourceIdProp = relationMetadata.source.getIdProperties()?.[0];
if (!sourceIdProp) {
throw new Error('Source model does not have an id property');
}
const keyFrom = (relationMetadata.keyFrom ?? sourceIdProp) as keyof Source;
return keyFrom;
}
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<Target>>(config.serviceKey);
} else if (isConfigWithModelClass(config)) {
return this.context.get<ModifiedRestService<Target>>(
`services.${config.modelClass.name}Proxy`,
);
} else {
return this.context.get<ModifiedRestService<Target>>(
`services.${config.serviceClass.name}`,
);
}
}
}