@backstage/plugin-catalog-backend 3.4.0-next.2 → 3.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +55 -0
- package/config.d.ts +43 -0
- package/dist/providers/DefaultLocationStore.cjs.js +340 -1
- package/dist/providers/DefaultLocationStore.cjs.js.map +1 -1
- package/dist/providers/GenericScmEventRefreshProvider.cjs.js +83 -0
- package/dist/providers/GenericScmEventRefreshProvider.cjs.js.map +1 -0
- package/dist/schema/openapi/generated/router.cjs.js +75 -0
- package/dist/schema/openapi/generated/router.cjs.js.map +1 -1
- package/dist/service/AuthorizedLocationService.cjs.js +10 -0
- package/dist/service/AuthorizedLocationService.cjs.js.map +1 -1
- package/dist/service/CatalogBuilder.cjs.js +17 -3
- package/dist/service/CatalogBuilder.cjs.js.map +1 -1
- package/dist/service/CatalogPlugin.cjs.js +6 -3
- package/dist/service/CatalogPlugin.cjs.js.map +1 -1
- package/dist/service/DefaultLocationService.cjs.js +7 -0
- package/dist/service/DefaultLocationService.cjs.js.map +1 -1
- package/dist/service/createRouter.cjs.js +35 -0
- package/dist/service/createRouter.cjs.js.map +1 -1
- package/dist/service/request/parseLocationQuery.cjs.js +75 -0
- package/dist/service/request/parseLocationQuery.cjs.js.map +1 -0
- package/dist/util/readScmEventHandlingConfig.cjs.js +28 -0
- package/dist/util/readScmEventHandlingConfig.cjs.js.map +1 -0
- package/package.json +22 -20
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,60 @@
|
|
|
1
1
|
# @backstage/plugin-catalog-backend
|
|
2
2
|
|
|
3
|
+
## 3.4.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- f1d29b4: Failures to connect catalog providers are now attributed to the module that provided the failing provider. This means that such failures will be reported as module startup failures rather than a failure to start the catalog plugin, and will therefore respect `onPluginModuleBootFailure` configuration instead.
|
|
8
|
+
- 34cc520: Implemented handling of events from the newly introduced alpha
|
|
9
|
+
`catalogScmEventsServiceRef` service, in the builtin entity providers. This
|
|
10
|
+
allows entities to get refreshed, and locations updated or removed, as a
|
|
11
|
+
response to incoming events. In its first iteration, only the GitHub module
|
|
12
|
+
implements such event handling however.
|
|
13
|
+
|
|
14
|
+
This is not yet enabled by default, but this fact may change in a future
|
|
15
|
+
release. To try it out, ensure that you have the latest catalog GitHub module
|
|
16
|
+
installed, and set the following in your app-config:
|
|
17
|
+
|
|
18
|
+
```yaml
|
|
19
|
+
catalog:
|
|
20
|
+
scmEvents: true
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Or if you want to pick and choose from the various features:
|
|
24
|
+
|
|
25
|
+
```yaml
|
|
26
|
+
catalog:
|
|
27
|
+
scmEvents:
|
|
28
|
+
# refresh (reprocess) upon events?
|
|
29
|
+
refresh: true
|
|
30
|
+
# automatically unregister locations based on events? (files deleted, repos archived, etc)
|
|
31
|
+
unregister: true
|
|
32
|
+
# automatically move locations based on events? (repo transferred, file renamed, etc)
|
|
33
|
+
move: true
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
- b4e8249: Implemented the `POST /locations/by-query` endpoint which allows paginated, filtered location queries
|
|
37
|
+
|
|
38
|
+
### Patch Changes
|
|
39
|
+
|
|
40
|
+
- cfd8103: Updated imports to use stable catalog extension points from `@backstage/plugin-catalog-node` instead of the deprecated alpha exports.
|
|
41
|
+
- 7455dae: Use node prefix on native imports
|
|
42
|
+
- 5e3ef57: Added `peerModules` metadata declaring recommended modules for cross-plugin integrations.
|
|
43
|
+
- 08a5813: Fixed O(n²) performance bottleneck in `buildEntitySearch` `traverse()` by replacing `Array.some()` linear scan with a `Set` for O(1) duplicate path key detection.
|
|
44
|
+
- 1e669cc: Migrate audit events reference docs to http://backstage.io/docs.
|
|
45
|
+
- 69d880e: Bump to latest zod to ensure it has the latest features
|
|
46
|
+
- Updated dependencies
|
|
47
|
+
- @backstage/integration@1.20.0
|
|
48
|
+
- @backstage/plugin-catalog-node@2.0.0
|
|
49
|
+
- @backstage/backend-openapi-utils@0.6.6
|
|
50
|
+
- @backstage/backend-plugin-api@1.7.0
|
|
51
|
+
- @backstage/catalog-client@1.13.0
|
|
52
|
+
- @backstage/filter-predicates@0.1.0
|
|
53
|
+
- @backstage/plugin-permission-common@0.9.6
|
|
54
|
+
- @backstage/plugin-permission-node@0.10.10
|
|
55
|
+
- @backstage/plugin-catalog-common@1.1.8
|
|
56
|
+
- @backstage/plugin-events-node@0.4.19
|
|
57
|
+
|
|
3
58
|
## 3.4.0-next.2
|
|
4
59
|
|
|
5
60
|
### Patch Changes
|
package/config.d.ts
CHANGED
|
@@ -257,5 +257,48 @@ export interface Config {
|
|
|
257
257
|
priority?: number;
|
|
258
258
|
};
|
|
259
259
|
};
|
|
260
|
+
|
|
261
|
+
/**
|
|
262
|
+
* Settings that control what to do when receiving messages from the SCM
|
|
263
|
+
* events service.
|
|
264
|
+
*
|
|
265
|
+
* @defaultValue false
|
|
266
|
+
* @remarks
|
|
267
|
+
*
|
|
268
|
+
* This is primarily meant to affect builtin providers in the catalog
|
|
269
|
+
* backend such as the location handler, but other providers and processors
|
|
270
|
+
* may also read this configuration.
|
|
271
|
+
*
|
|
272
|
+
* If set to false, disable all handling of SCM events.
|
|
273
|
+
*
|
|
274
|
+
* If set to true, enable all default handling of SCM events. Note that the
|
|
275
|
+
* set of default handling can change over time.
|
|
276
|
+
*
|
|
277
|
+
* You can also configure individual handlers one by one.
|
|
278
|
+
*/
|
|
279
|
+
scmEvents?:
|
|
280
|
+
| boolean
|
|
281
|
+
| {
|
|
282
|
+
/**
|
|
283
|
+
* Trigger refreshes (reprocessing) of entities that are affected by an
|
|
284
|
+
* SCM event. This may include source control file content changes,
|
|
285
|
+
* repository status changes, etc.
|
|
286
|
+
*
|
|
287
|
+
* @defaultValue true
|
|
288
|
+
*/
|
|
289
|
+
refresh?: boolean;
|
|
290
|
+
/**
|
|
291
|
+
* Unregister entities that are deleted as a result of an SCM event.
|
|
292
|
+
*
|
|
293
|
+
* @defaultValue true
|
|
294
|
+
*/
|
|
295
|
+
unregister?: boolean;
|
|
296
|
+
/**
|
|
297
|
+
* Move entities that are moved as a result of an SCM event.
|
|
298
|
+
*
|
|
299
|
+
* @defaultValue true
|
|
300
|
+
*/
|
|
301
|
+
move?: boolean;
|
|
302
|
+
};
|
|
260
303
|
};
|
|
261
304
|
}
|
|
@@ -5,12 +5,22 @@ var uuid = require('uuid');
|
|
|
5
5
|
var util = require('../processing/util.cjs.js');
|
|
6
6
|
var conversion = require('../util/conversion.cjs.js');
|
|
7
7
|
var catalogModel = require('@backstage/catalog-model');
|
|
8
|
+
var lodash = require('lodash');
|
|
9
|
+
var parseGitUrl = require('git-url-parse');
|
|
10
|
+
|
|
11
|
+
function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
|
|
12
|
+
|
|
13
|
+
var parseGitUrl__default = /*#__PURE__*/_interopDefaultCompat(parseGitUrl);
|
|
8
14
|
|
|
9
15
|
class DefaultLocationStore {
|
|
10
16
|
_connection;
|
|
11
17
|
db;
|
|
12
|
-
|
|
18
|
+
scmEvents;
|
|
19
|
+
scmEventHandlingConfig;
|
|
20
|
+
constructor(db, scmEvents, scmEventHandlingConfig) {
|
|
13
21
|
this.db = db;
|
|
22
|
+
this.scmEvents = scmEvents;
|
|
23
|
+
this.scmEventHandlingConfig = scmEventHandlingConfig;
|
|
14
24
|
}
|
|
15
25
|
getProviderName() {
|
|
16
26
|
return "DefaultLocationStore";
|
|
@@ -45,6 +55,36 @@ class DefaultLocationStore {
|
|
|
45
55
|
async listLocations() {
|
|
46
56
|
return await this.locations();
|
|
47
57
|
}
|
|
58
|
+
async queryLocations(options) {
|
|
59
|
+
let itemsQuery = this.db("locations").whereNot(
|
|
60
|
+
"type",
|
|
61
|
+
"bootstrap"
|
|
62
|
+
);
|
|
63
|
+
if (options.query) {
|
|
64
|
+
itemsQuery = applyLocationFilterToQuery(
|
|
65
|
+
this.db.client.config.client,
|
|
66
|
+
itemsQuery,
|
|
67
|
+
options.query
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const countQuery = itemsQuery.clone().count("*", { as: "count" });
|
|
71
|
+
itemsQuery = itemsQuery.orderBy("id", "asc");
|
|
72
|
+
if (options.afterId !== void 0) {
|
|
73
|
+
itemsQuery = itemsQuery.where("id", ">", options.afterId);
|
|
74
|
+
}
|
|
75
|
+
if (options.limit !== void 0) {
|
|
76
|
+
itemsQuery = itemsQuery.limit(options.limit);
|
|
77
|
+
}
|
|
78
|
+
const [items, [{ count }]] = await Promise.all([itemsQuery, countQuery]);
|
|
79
|
+
return {
|
|
80
|
+
items: items.map((item) => ({
|
|
81
|
+
id: item.id,
|
|
82
|
+
target: item.target,
|
|
83
|
+
type: item.type
|
|
84
|
+
})),
|
|
85
|
+
totalItems: Number(count)
|
|
86
|
+
};
|
|
87
|
+
}
|
|
48
88
|
async getLocation(id) {
|
|
49
89
|
const items = await this.db("locations").where({ id }).select();
|
|
50
90
|
if (!items.length) {
|
|
@@ -112,6 +152,9 @@ class DefaultLocationStore {
|
|
|
112
152
|
type: "full",
|
|
113
153
|
entities
|
|
114
154
|
});
|
|
155
|
+
if (this.scmEventHandlingConfig.unregister || this.scmEventHandlingConfig.move) {
|
|
156
|
+
this.scmEvents.subscribe({ onEvents: this.#onScmEvents.bind(this) });
|
|
157
|
+
}
|
|
115
158
|
}
|
|
116
159
|
async locations(dbOrTx = this.db) {
|
|
117
160
|
const locations = await dbOrTx("locations").select();
|
|
@@ -121,6 +164,302 @@ class DefaultLocationStore {
|
|
|
121
164
|
type: item.type
|
|
122
165
|
}));
|
|
123
166
|
}
|
|
167
|
+
// #region SCM event handling
|
|
168
|
+
async #onScmEvents(events) {
|
|
169
|
+
const exactLocationsToDelete = /* @__PURE__ */ new Set();
|
|
170
|
+
const locationPrefixesToDelete = /* @__PURE__ */ new Set();
|
|
171
|
+
const exactLocationsToCreate = /* @__PURE__ */ new Set();
|
|
172
|
+
const locationPrefixesToMove = /* @__PURE__ */ new Map();
|
|
173
|
+
for (const event of events) {
|
|
174
|
+
if (event.type === "location.deleted" && this.scmEventHandlingConfig.unregister) {
|
|
175
|
+
exactLocationsToDelete.add(event.url);
|
|
176
|
+
} else if (event.type === "location.moved" && this.scmEventHandlingConfig.move) {
|
|
177
|
+
exactLocationsToDelete.add(event.fromUrl);
|
|
178
|
+
exactLocationsToCreate.add(event.toUrl);
|
|
179
|
+
} else if (event.type === "repository.deleted" && this.scmEventHandlingConfig.unregister) {
|
|
180
|
+
locationPrefixesToDelete.add(event.url);
|
|
181
|
+
} else if (event.type === "repository.moved" && this.scmEventHandlingConfig.move) {
|
|
182
|
+
locationPrefixesToMove.set(event.fromUrl, event.toUrl);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (exactLocationsToDelete.size > 0) {
|
|
186
|
+
await this.#deleteLocationsByExactUrl(exactLocationsToDelete);
|
|
187
|
+
}
|
|
188
|
+
if (locationPrefixesToDelete.size > 0) {
|
|
189
|
+
await this.#deleteLocationsByUrlPrefix(locationPrefixesToDelete);
|
|
190
|
+
}
|
|
191
|
+
if (exactLocationsToCreate.size > 0) {
|
|
192
|
+
await this.#createLocationsByExactUrl(exactLocationsToCreate);
|
|
193
|
+
}
|
|
194
|
+
if (locationPrefixesToMove.size > 0) {
|
|
195
|
+
await this.#moveLocationsByUrlPrefix(locationPrefixesToMove);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
async #createLocationsByExactUrl(urls) {
|
|
199
|
+
let count = 0;
|
|
200
|
+
for (const batch of lodash.chunk(Array.from(urls), 100)) {
|
|
201
|
+
const existingUrls = await this.db("locations").where("type", "=", "url").whereIn("target", batch).select().then((rows) => new Set(rows.map((row) => row.target)));
|
|
202
|
+
const newLocations = batch.filter((url) => !existingUrls.has(url)).map((url) => ({ id: uuid.v4(), type: "url", target: url }));
|
|
203
|
+
if (newLocations.length) {
|
|
204
|
+
await this.db("locations").insert(newLocations);
|
|
205
|
+
await this.connection.applyMutation({
|
|
206
|
+
type: "delta",
|
|
207
|
+
added: newLocations.map((location) => {
|
|
208
|
+
const entity = conversion.locationSpecToLocationEntity({ location });
|
|
209
|
+
return { entity, locationKey: util.getEntityLocationRef(entity) };
|
|
210
|
+
}),
|
|
211
|
+
removed: []
|
|
212
|
+
});
|
|
213
|
+
count += newLocations.length;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return count;
|
|
217
|
+
}
|
|
218
|
+
async #deleteLocationsByExactUrl(urls) {
|
|
219
|
+
let count = 0;
|
|
220
|
+
for (const batch of lodash.chunk(Array.from(urls), 100)) {
|
|
221
|
+
const rows = await this.db("locations").where("type", "=", "url").whereIn("target", batch).select();
|
|
222
|
+
if (rows.length) {
|
|
223
|
+
await this.db("locations").whereIn(
|
|
224
|
+
"id",
|
|
225
|
+
rows.map((row) => row.id)
|
|
226
|
+
).delete();
|
|
227
|
+
await this.connection.applyMutation({
|
|
228
|
+
type: "delta",
|
|
229
|
+
added: [],
|
|
230
|
+
removed: rows.map((row) => ({
|
|
231
|
+
entity: conversion.locationSpecToLocationEntity({ location: row })
|
|
232
|
+
}))
|
|
233
|
+
});
|
|
234
|
+
count += rows.length;
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
return count;
|
|
238
|
+
}
|
|
239
|
+
async #deleteLocationsByUrlPrefix(urls) {
|
|
240
|
+
const matches = await this.#findLocationsByPrefixOrExactMatch(urls);
|
|
241
|
+
if (matches.length) {
|
|
242
|
+
await this.#deleteLocations(matches.map((l) => l.row));
|
|
243
|
+
}
|
|
244
|
+
return matches.length;
|
|
245
|
+
}
|
|
246
|
+
async #moveLocationsByUrlPrefix(urlPrefixes) {
|
|
247
|
+
let count = 0;
|
|
248
|
+
for (const [fromPrefix, toPrefix] of urlPrefixes) {
|
|
249
|
+
if (fromPrefix === toPrefix) {
|
|
250
|
+
continue;
|
|
251
|
+
}
|
|
252
|
+
if (fromPrefix.match(/[?#]/) || toPrefix.match(/[?#]/)) {
|
|
253
|
+
continue;
|
|
254
|
+
}
|
|
255
|
+
const matches = await this.#findLocationsByPrefixOrExactMatch([
|
|
256
|
+
fromPrefix
|
|
257
|
+
]);
|
|
258
|
+
if (matches.length) {
|
|
259
|
+
await this.#deleteLocations(matches.map((m) => m.row));
|
|
260
|
+
await this.#createLocationsByExactUrl(
|
|
261
|
+
matches.map((m) => {
|
|
262
|
+
const remainder = m.row.target.slice(fromPrefix.length).replace(/^\/+/, "");
|
|
263
|
+
if (!remainder) {
|
|
264
|
+
return toPrefix;
|
|
265
|
+
}
|
|
266
|
+
return `${toPrefix.replace(/\/+$/, "")}/${remainder}`;
|
|
267
|
+
})
|
|
268
|
+
);
|
|
269
|
+
count += matches.length;
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
return count;
|
|
273
|
+
}
|
|
274
|
+
async #deleteLocations(rows) {
|
|
275
|
+
for (const ids of lodash.chunk(
|
|
276
|
+
rows.map((l) => l.id),
|
|
277
|
+
100
|
|
278
|
+
)) {
|
|
279
|
+
await this.db("locations").whereIn("id", ids).delete();
|
|
280
|
+
}
|
|
281
|
+
await this.connection.applyMutation({
|
|
282
|
+
type: "delta",
|
|
283
|
+
added: [],
|
|
284
|
+
removed: rows.map((l) => ({
|
|
285
|
+
entity: conversion.locationSpecToLocationEntity({ location: l })
|
|
286
|
+
}))
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
/**
|
|
290
|
+
* Given a "base" URL prefix, find all locations that are for paths at or
|
|
291
|
+
* below it.
|
|
292
|
+
*
|
|
293
|
+
* For example, given a base URL prefix of
|
|
294
|
+
* "https://github.com/backstage/backstage/blob/master/plugins", it will match
|
|
295
|
+
* locations inside the plugins directory, and nowhere else.
|
|
296
|
+
*/
|
|
297
|
+
async #findLocationsByPrefixOrExactMatch(urls) {
|
|
298
|
+
const result = new Array();
|
|
299
|
+
for (const url of urls) {
|
|
300
|
+
let base;
|
|
301
|
+
try {
|
|
302
|
+
base = parseGitUrl__default.default(url);
|
|
303
|
+
} catch (error) {
|
|
304
|
+
throw new Error(`Invalid URL prefix, could not parse: ${url}`);
|
|
305
|
+
}
|
|
306
|
+
if (!base.owner || !base.name) {
|
|
307
|
+
throw new Error(
|
|
308
|
+
`Invalid URL prefix, missing owner or repository: ${url}`
|
|
309
|
+
);
|
|
310
|
+
}
|
|
311
|
+
const pathPrefix = base.filepath === "" || base.filepath.endsWith("/") ? base.filepath : `${base.filepath}/`;
|
|
312
|
+
const rows = await this.db("locations").where("type", "=", "url").where("target", "like", `%${base.owner}%`).where("target", "like", `%${base.name}%`).select();
|
|
313
|
+
result.push(
|
|
314
|
+
...rows.flatMap((row) => {
|
|
315
|
+
try {
|
|
316
|
+
const candidate = parseGitUrl__default.default(row.target);
|
|
317
|
+
if (candidate.protocol === base.protocol && candidate.resource === base.resource && candidate.port === base.port && candidate.organization === base.organization && candidate.owner === base.owner && candidate.name === base.name && // If the base has no ref (for example didn't have the "/blob/master"
|
|
318
|
+
// part and therefore targeted an entire repository) then we match any
|
|
319
|
+
// ref below that
|
|
320
|
+
(!base.ref || candidate.ref === base.ref) && // Match both on exact equality and any subpath with a slash between
|
|
321
|
+
(candidate.filepath === base.filepath || candidate.filepath.startsWith(pathPrefix))) {
|
|
322
|
+
return [{ row, parsed: candidate }];
|
|
323
|
+
}
|
|
324
|
+
return [];
|
|
325
|
+
} catch {
|
|
326
|
+
return [];
|
|
327
|
+
}
|
|
328
|
+
})
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return lodash.uniqBy(result, (entry) => entry.row.id);
|
|
332
|
+
}
|
|
333
|
+
// #endregion
|
|
334
|
+
}
|
|
335
|
+
function applyLocationFilterToQuery(clientType, inputQuery, query) {
|
|
336
|
+
let result = inputQuery;
|
|
337
|
+
if (!query || typeof query !== "object" || Array.isArray(query)) {
|
|
338
|
+
throw new errors.InputError("Invalid filter predicate, expected an object");
|
|
339
|
+
}
|
|
340
|
+
if ("$all" in query) {
|
|
341
|
+
if (query.$all.length === 0) {
|
|
342
|
+
return result.whereRaw("1 = 0");
|
|
343
|
+
}
|
|
344
|
+
return result.where((outer) => {
|
|
345
|
+
for (const subQuery of query.$all) {
|
|
346
|
+
outer.andWhere((inner) => {
|
|
347
|
+
applyLocationFilterToQuery(clientType, inner, subQuery);
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
if ("$any" in query) {
|
|
353
|
+
if (query.$any.length === 0) {
|
|
354
|
+
return result.whereRaw("1 = 0");
|
|
355
|
+
}
|
|
356
|
+
return result.where((outer) => {
|
|
357
|
+
for (const subQuery of query.$any) {
|
|
358
|
+
outer.orWhere((inner) => {
|
|
359
|
+
applyLocationFilterToQuery(clientType, inner, subQuery);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
}
|
|
364
|
+
if ("$not" in query) {
|
|
365
|
+
return result.whereNot((inner) => {
|
|
366
|
+
applyLocationFilterToQuery(clientType, inner, query.$not);
|
|
367
|
+
});
|
|
368
|
+
}
|
|
369
|
+
const entries = Object.entries(query);
|
|
370
|
+
const keys = entries.map((e) => e[0]);
|
|
371
|
+
if (keys.some((k) => k.startsWith("$"))) {
|
|
372
|
+
throw new errors.InputError(
|
|
373
|
+
`Invalid filter predicate, unknown logic operator '${keys.join(", ")}'`
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
for (const [keyAnyCase, value] of entries) {
|
|
377
|
+
const key = keyAnyCase.toLocaleLowerCase("en-US");
|
|
378
|
+
if (!["id", "type", "target"].includes(key)) {
|
|
379
|
+
throw new errors.InputError(
|
|
380
|
+
`Invalid filter predicate, expected key to be 'id', 'type', or 'target', got '${keyAnyCase}'`
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
result = applyFilterValueToQuery(clientType, result, key, value);
|
|
384
|
+
}
|
|
385
|
+
return result;
|
|
386
|
+
}
|
|
387
|
+
function applyFilterValueToQuery(clientType, result, key, value) {
|
|
388
|
+
if (["string", "number", "boolean"].includes(typeof value)) {
|
|
389
|
+
if (clientType === "pg") {
|
|
390
|
+
return result.whereRaw(`UPPER(??::text) = UPPER(?::text)`, [key, value]);
|
|
391
|
+
}
|
|
392
|
+
if (clientType.includes("mysql")) {
|
|
393
|
+
return result.whereRaw(
|
|
394
|
+
`UPPER(CAST(?? AS CHAR)) = UPPER(CAST(? AS CHAR))`,
|
|
395
|
+
[key, value]
|
|
396
|
+
);
|
|
397
|
+
}
|
|
398
|
+
return result.whereRaw(`UPPER(??) = UPPER(?)`, [key, value]);
|
|
399
|
+
}
|
|
400
|
+
if (typeof value === "object") {
|
|
401
|
+
if (!value || Array.isArray(value)) {
|
|
402
|
+
throw new errors.InputError(
|
|
403
|
+
`Invalid filter predicate, got unknown matcher object '${JSON.stringify(
|
|
404
|
+
value
|
|
405
|
+
)}'`
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
if ("$exists" in value) {
|
|
409
|
+
return value.$exists ? result.whereNotNull(key) : result.whereNull(key);
|
|
410
|
+
}
|
|
411
|
+
if ("$in" in value) {
|
|
412
|
+
if (value.$in.length === 0) {
|
|
413
|
+
return result.whereRaw("1 = 0");
|
|
414
|
+
}
|
|
415
|
+
if (key === "id") {
|
|
416
|
+
return result.whereIn(key, value.$in);
|
|
417
|
+
}
|
|
418
|
+
if (clientType === "pg") {
|
|
419
|
+
const rhs2 = value.$in.map(() => "UPPER(?::text)").join(", ");
|
|
420
|
+
return result.whereRaw(`UPPER(??::text) IN (${rhs2})`, [
|
|
421
|
+
key,
|
|
422
|
+
...value.$in
|
|
423
|
+
]);
|
|
424
|
+
}
|
|
425
|
+
if (clientType.includes("mysql")) {
|
|
426
|
+
const rhs2 = value.$in.map(() => "UPPER(CAST(? AS CHAR))").join(", ");
|
|
427
|
+
return result.whereRaw(`UPPER(CAST(?? AS CHAR)) IN (${rhs2})`, [
|
|
428
|
+
key,
|
|
429
|
+
...value.$in
|
|
430
|
+
]);
|
|
431
|
+
}
|
|
432
|
+
const rhs = value.$in.map(() => "UPPER(?)").join(", ");
|
|
433
|
+
return result.whereRaw(`UPPER(??) IN (${rhs})`, [key, ...value.$in]);
|
|
434
|
+
}
|
|
435
|
+
if ("$hasPrefix" in value) {
|
|
436
|
+
const escaped = value.$hasPrefix.replace(/([\\%_])/g, "\\$1");
|
|
437
|
+
if (clientType === "pg") {
|
|
438
|
+
return result.whereRaw("?? ilike ? escape '\\'", [key, `${escaped}%`]);
|
|
439
|
+
}
|
|
440
|
+
if (clientType.includes("mysql")) {
|
|
441
|
+
return result.whereRaw("UPPER(??) like UPPER(?) escape '\\\\'", [
|
|
442
|
+
key,
|
|
443
|
+
`${escaped}%`
|
|
444
|
+
]);
|
|
445
|
+
}
|
|
446
|
+
return result.whereRaw("UPPER(??) like UPPER(?) escape '\\'", [
|
|
447
|
+
key,
|
|
448
|
+
`${escaped}%`
|
|
449
|
+
]);
|
|
450
|
+
}
|
|
451
|
+
if ("$contains" in value) {
|
|
452
|
+
return result.whereRaw("1 = 0");
|
|
453
|
+
}
|
|
454
|
+
throw new errors.InputError(
|
|
455
|
+
`Invalid filter predicate, got unknown matcher object '${JSON.stringify(
|
|
456
|
+
value
|
|
457
|
+
)}'`
|
|
458
|
+
);
|
|
459
|
+
}
|
|
460
|
+
throw new errors.InputError(
|
|
461
|
+
`Invalid filter predicate, expected value to be a primitive value or a matcher object, got '${typeof value}'`
|
|
462
|
+
);
|
|
124
463
|
}
|
|
125
464
|
|
|
126
465
|
exports.DefaultLocationStore = DefaultLocationStore;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DefaultLocationStore.cjs.js","sources":["../../src/providers/DefaultLocationStore.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Location } from '@backstage/catalog-client';\nimport { ConflictError, NotFoundError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport {\n DbLocationsRow,\n DbRefreshStateRow,\n DbSearchRow,\n} from '../database/tables';\nimport { getEntityLocationRef } from '../processing/util';\nimport {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\nimport { locationSpecToLocationEntity } from '../util/conversion';\nimport { LocationInput, LocationStore } from '../service/types';\nimport {\n ANNOTATION_ORIGIN_LOCATION,\n CompoundEntityRef,\n parseLocationRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\n\nexport class DefaultLocationStore implements LocationStore, EntityProvider {\n private _connection: EntityProviderConnection | undefined;\n private readonly db: Knex;\n\n constructor(db: Knex) {\n this.db = db;\n }\n\n getProviderName(): string {\n return 'DefaultLocationStore';\n }\n\n async createLocation(input: LocationInput): Promise<Location> {\n const location = await this.db.transaction(async tx => {\n // Attempt to find a previous location matching the input\n const previousLocations = await this.locations(tx);\n // TODO: when location id's are a compilation of input target we can remove this full\n // lookup of locations first and just grab the by that instead.\n const previousLocation = previousLocations.some(\n l => input.type === l.type && input.target === l.target,\n );\n if (previousLocation) {\n throw new ConflictError(\n `Location ${input.type}:${input.target} already exists`,\n );\n }\n\n const inner: DbLocationsRow = {\n id: uuid(),\n type: input.type,\n target: input.target,\n };\n\n await tx<DbLocationsRow>('locations').insert(inner);\n\n return inner;\n });\n const entity = locationSpecToLocationEntity({ location });\n await this.connection.applyMutation({\n type: 'delta',\n added: [{ entity, locationKey: getEntityLocationRef(entity) }],\n removed: [],\n });\n\n return location;\n }\n\n async listLocations(): Promise<Location[]> {\n return await this.locations();\n }\n\n async getLocation(id: string): Promise<Location> {\n const items = await this.db<DbLocationsRow>('locations')\n .where({ id })\n .select();\n\n if (!items.length) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n return items[0];\n }\n\n async deleteLocation(id: string): Promise<void> {\n if (!this.connection) {\n throw new Error('location store is not initialized');\n }\n\n const deleted = await this.db.transaction(async tx => {\n const [location] = await tx<DbLocationsRow>('locations')\n .where({ id })\n .select();\n\n if (!location) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n\n await tx<DbLocationsRow>('locations').where({ id }).del();\n return location;\n });\n const entity = locationSpecToLocationEntity({ location: deleted });\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: [{ entity, locationKey: getEntityLocationRef(entity) }],\n });\n }\n\n async getLocationByEntity(entityRef: CompoundEntityRef): Promise<Location> {\n const entityRefString = stringifyEntityRef(entityRef);\n\n const [entityRow] = await this.db<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRefString })\n .select('entity_id')\n .limit(1);\n if (!entityRow) {\n throw new NotFoundError(`found no entity for ref ${entityRefString}`);\n }\n\n const [searchRow] = await this.db<DbSearchRow>('search')\n .where({\n entity_id: entityRow.entity_id,\n key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`,\n })\n .select('original_value')\n .limit(1);\n if (!searchRow?.original_value) {\n throw new NotFoundError(\n `found no origin annotation for ref ${entityRefString}`,\n );\n }\n\n const { type, target } = parseLocationRef(searchRow.original_value);\n const [locationRow] = await this.db<DbLocationsRow>('locations')\n .where({ type, target })\n .select()\n .limit(1);\n\n if (!locationRow) {\n throw new NotFoundError(\n `Found no location with type ${type} and target ${target}`,\n );\n }\n\n return locationRow;\n }\n\n private get connection(): EntityProviderConnection {\n if (!this._connection) {\n throw new Error('location store is not initialized');\n }\n\n return this._connection;\n }\n\n async connect(connection: EntityProviderConnection): Promise<void> {\n this._connection = connection;\n\n const locations = await this.locations();\n\n const entities = locations.map(location => {\n const entity = locationSpecToLocationEntity({ location });\n return { entity, locationKey: getEntityLocationRef(entity) };\n });\n\n await this.connection.applyMutation({\n type: 'full',\n entities,\n });\n }\n\n private async locations(dbOrTx: Knex.Transaction | Knex = this.db) {\n const locations = await dbOrTx<DbLocationsRow>('locations').select();\n return (\n locations\n // TODO(blam): We should create a mutation to remove this location for everyone\n // eventually when it's all done and dusted\n .filter(({ type }) => type !== 'bootstrap')\n .map(item => ({\n id: item.id,\n target: item.target,\n type: item.type,\n }))\n );\n }\n}\n"],"names":["ConflictError","uuid","locationSpecToLocationEntity","getEntityLocationRef","NotFoundError","stringifyEntityRef","ANNOTATION_ORIGIN_LOCATION","parseLocationRef"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"DefaultLocationStore.cjs.js","sources":["../../src/providers/DefaultLocationStore.ts"],"sourcesContent":["/*\n * Copyright 2021 The Backstage Authors\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { Location } from '@backstage/catalog-client';\nimport { ConflictError, InputError, NotFoundError } from '@backstage/errors';\nimport { Knex } from 'knex';\nimport { v4 as uuid } from 'uuid';\nimport {\n DbLocationsRow,\n DbRefreshStateRow,\n DbSearchRow,\n} from '../database/tables';\nimport { getEntityLocationRef } from '../processing/util';\nimport {\n EntityProvider,\n EntityProviderConnection,\n} from '@backstage/plugin-catalog-node';\nimport { locationSpecToLocationEntity } from '../util/conversion';\nimport { LocationInput, LocationStore } from '../service/types';\nimport {\n ANNOTATION_ORIGIN_LOCATION,\n CompoundEntityRef,\n parseLocationRef,\n stringifyEntityRef,\n} from '@backstage/catalog-model';\nimport {\n CatalogScmEvent,\n CatalogScmEventsService,\n} from '@backstage/plugin-catalog-node/alpha';\nimport { chunk, uniqBy } from 'lodash';\nimport parseGitUrl, { type GitUrl } from 'git-url-parse';\nimport { ScmEventHandlingConfig } from '../util/readScmEventHandlingConfig';\nimport {\n FilterPredicate,\n FilterPredicateValue,\n} from '@backstage/filter-predicates';\n\nexport class DefaultLocationStore implements LocationStore, EntityProvider {\n private _connection: EntityProviderConnection | undefined;\n private readonly db: Knex;\n private readonly scmEvents: CatalogScmEventsService;\n private readonly scmEventHandlingConfig: ScmEventHandlingConfig;\n\n constructor(\n db: Knex,\n scmEvents: CatalogScmEventsService,\n scmEventHandlingConfig: ScmEventHandlingConfig,\n ) {\n this.db = db;\n this.scmEvents = scmEvents;\n this.scmEventHandlingConfig = scmEventHandlingConfig;\n }\n\n getProviderName(): string {\n return 'DefaultLocationStore';\n }\n\n async createLocation(input: LocationInput): Promise<Location> {\n const location = await this.db.transaction(async tx => {\n // Attempt to find a previous location matching the input\n const previousLocations = await this.locations(tx);\n // TODO: when location id's are a compilation of input target we can remove this full\n // lookup of locations first and just grab the by that instead.\n const previousLocation = previousLocations.some(\n l => input.type === l.type && input.target === l.target,\n );\n if (previousLocation) {\n throw new ConflictError(\n `Location ${input.type}:${input.target} already exists`,\n );\n }\n\n const inner: DbLocationsRow = {\n id: uuid(),\n type: input.type,\n target: input.target,\n };\n\n await tx<DbLocationsRow>('locations').insert(inner);\n\n return inner;\n });\n const entity = locationSpecToLocationEntity({ location });\n await this.connection.applyMutation({\n type: 'delta',\n added: [{ entity, locationKey: getEntityLocationRef(entity) }],\n removed: [],\n });\n\n return location;\n }\n\n async listLocations(): Promise<Location[]> {\n return await this.locations();\n }\n\n async queryLocations(options: {\n limit: number;\n afterId?: string;\n query?: FilterPredicate;\n }): Promise<{ items: Location[]; totalItems: number }> {\n let itemsQuery = this.db<DbLocationsRow>('locations').whereNot(\n 'type',\n 'bootstrap',\n );\n\n if (options.query) {\n itemsQuery = applyLocationFilterToQuery(\n this.db.client.config.client,\n itemsQuery,\n options.query,\n );\n }\n\n const countQuery = itemsQuery.clone().count('*', { as: 'count' });\n\n itemsQuery = itemsQuery.orderBy('id', 'asc');\n if (options.afterId !== undefined) {\n itemsQuery = itemsQuery.where('id', '>', options.afterId);\n }\n if (options.limit !== undefined) {\n itemsQuery = itemsQuery.limit(options.limit);\n }\n\n const [items, [{ count }]] = await Promise.all([itemsQuery, countQuery]);\n\n return {\n items: items.map(item => ({\n id: item.id,\n target: item.target,\n type: item.type,\n })),\n totalItems: Number(count),\n };\n }\n\n async getLocation(id: string): Promise<Location> {\n const items = await this.db<DbLocationsRow>('locations')\n .where({ id })\n .select();\n\n if (!items.length) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n return items[0];\n }\n\n async deleteLocation(id: string): Promise<void> {\n if (!this.connection) {\n throw new Error('location store is not initialized');\n }\n\n const deleted = await this.db.transaction(async tx => {\n const [location] = await tx<DbLocationsRow>('locations')\n .where({ id })\n .select();\n\n if (!location) {\n throw new NotFoundError(`Found no location with ID ${id}`);\n }\n\n await tx<DbLocationsRow>('locations').where({ id }).del();\n return location;\n });\n const entity = locationSpecToLocationEntity({ location: deleted });\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: [{ entity, locationKey: getEntityLocationRef(entity) }],\n });\n }\n\n async getLocationByEntity(entityRef: CompoundEntityRef): Promise<Location> {\n const entityRefString = stringifyEntityRef(entityRef);\n\n const [entityRow] = await this.db<DbRefreshStateRow>('refresh_state')\n .where({ entity_ref: entityRefString })\n .select('entity_id')\n .limit(1);\n if (!entityRow) {\n throw new NotFoundError(`found no entity for ref ${entityRefString}`);\n }\n\n const [searchRow] = await this.db<DbSearchRow>('search')\n .where({\n entity_id: entityRow.entity_id,\n key: `metadata.annotations.${ANNOTATION_ORIGIN_LOCATION}`,\n })\n .select('original_value')\n .limit(1);\n if (!searchRow?.original_value) {\n throw new NotFoundError(\n `found no origin annotation for ref ${entityRefString}`,\n );\n }\n\n const { type, target } = parseLocationRef(searchRow.original_value);\n const [locationRow] = await this.db<DbLocationsRow>('locations')\n .where({ type, target })\n .select()\n .limit(1);\n\n if (!locationRow) {\n throw new NotFoundError(\n `Found no location with type ${type} and target ${target}`,\n );\n }\n\n return locationRow;\n }\n\n private get connection(): EntityProviderConnection {\n if (!this._connection) {\n throw new Error('location store is not initialized');\n }\n\n return this._connection;\n }\n\n async connect(connection: EntityProviderConnection): Promise<void> {\n this._connection = connection;\n\n const locations = await this.locations();\n\n const entities = locations.map(location => {\n const entity = locationSpecToLocationEntity({ location });\n return { entity, locationKey: getEntityLocationRef(entity) };\n });\n\n await this.connection.applyMutation({\n type: 'full',\n entities,\n });\n\n if (\n this.scmEventHandlingConfig.unregister ||\n this.scmEventHandlingConfig.move\n ) {\n this.scmEvents.subscribe({ onEvents: this.#onScmEvents.bind(this) });\n }\n }\n\n private async locations(dbOrTx: Knex.Transaction | Knex = this.db) {\n const locations = await dbOrTx<DbLocationsRow>('locations').select();\n return (\n locations\n // TODO(blam): We should create a mutation to remove this location for everyone\n // eventually when it's all done and dusted\n .filter(({ type }) => type !== 'bootstrap')\n .map(item => ({\n id: item.id,\n target: item.target,\n type: item.type,\n }))\n );\n }\n\n // #region SCM event handling\n\n async #onScmEvents(events: CatalogScmEvent[]): Promise<void> {\n const exactLocationsToDelete = new Set<string>();\n const locationPrefixesToDelete = new Set<string>();\n const exactLocationsToCreate = new Set<string>();\n const locationPrefixesToMove = new Map<string, string>();\n\n for (const event of events) {\n if (\n event.type === 'location.deleted' &&\n this.scmEventHandlingConfig.unregister\n ) {\n exactLocationsToDelete.add(event.url);\n } else if (\n event.type === 'location.moved' &&\n this.scmEventHandlingConfig.move\n ) {\n // Since Location entities are named after their target URL, these\n // unfortunately have to be translated into deletion and creation\n exactLocationsToDelete.add(event.fromUrl);\n exactLocationsToCreate.add(event.toUrl);\n } else if (\n event.type === 'repository.deleted' &&\n this.scmEventHandlingConfig.unregister\n ) {\n locationPrefixesToDelete.add(event.url);\n } else if (\n event.type === 'repository.moved' &&\n this.scmEventHandlingConfig.move\n ) {\n // These also have to be handled with deletions and creations\n locationPrefixesToMove.set(event.fromUrl, event.toUrl);\n }\n }\n\n if (exactLocationsToDelete.size > 0) {\n await this.#deleteLocationsByExactUrl(exactLocationsToDelete);\n }\n if (locationPrefixesToDelete.size > 0) {\n await this.#deleteLocationsByUrlPrefix(locationPrefixesToDelete);\n }\n if (exactLocationsToCreate.size > 0) {\n await this.#createLocationsByExactUrl(exactLocationsToCreate);\n }\n if (locationPrefixesToMove.size > 0) {\n await this.#moveLocationsByUrlPrefix(locationPrefixesToMove);\n }\n }\n\n async #createLocationsByExactUrl(urls: Iterable<string>): Promise<number> {\n let count = 0;\n\n for (const batch of chunk(Array.from(urls), 100)) {\n const existingUrls = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n .whereIn('target', batch)\n .select()\n .then(rows => new Set(rows.map(row => row.target)));\n\n const newLocations = batch\n .filter(url => !existingUrls.has(url))\n .map(url => ({ id: uuid(), type: 'url', target: url }));\n\n if (newLocations.length) {\n await this.db<DbLocationsRow>('locations').insert(newLocations);\n\n await this.connection.applyMutation({\n type: 'delta',\n added: newLocations.map(location => {\n const entity = locationSpecToLocationEntity({ location });\n return { entity, locationKey: getEntityLocationRef(entity) };\n }),\n removed: [],\n });\n\n count += newLocations.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocationsByExactUrl(urls: Iterable<string>): Promise<number> {\n let count = 0;\n\n for (const batch of chunk(Array.from(urls), 100)) {\n const rows = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n .whereIn('target', batch)\n .select();\n\n if (rows.length) {\n await this.db<DbLocationsRow>('locations')\n .whereIn(\n 'id',\n rows.map(row => row.id),\n )\n .delete();\n\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: rows.map(row => ({\n entity: locationSpecToLocationEntity({ location: row }),\n })),\n });\n\n count += rows.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocationsByUrlPrefix(urls: Iterable<string>): Promise<number> {\n const matches = await this.#findLocationsByPrefixOrExactMatch(urls);\n if (matches.length) {\n await this.#deleteLocations(matches.map(l => l.row));\n }\n\n return matches.length;\n }\n\n async #moveLocationsByUrlPrefix(\n urlPrefixes: Map<string, string>,\n ): Promise<number> {\n let count = 0;\n\n for (const [fromPrefix, toPrefix] of urlPrefixes) {\n if (fromPrefix === toPrefix) {\n continue;\n }\n\n if (fromPrefix.match(/[?#]/) || toPrefix.match(/[?#]/)) {\n // TODO(freben): We can't yet support complex URL locations where e.g.\n // the path can be anywhere in the URL including in the query or hash\n // part. The code below currently assumes that we can use simple\n // substring operations.\n continue;\n }\n\n const matches = await this.#findLocationsByPrefixOrExactMatch([\n fromPrefix,\n ]);\n if (matches.length) {\n await this.#deleteLocations(matches.map(m => m.row));\n\n await this.#createLocationsByExactUrl(\n matches.map(m => {\n const remainder = m.row.target\n .slice(fromPrefix.length)\n .replace(/^\\/+/, '');\n if (!remainder) {\n return toPrefix;\n }\n return `${toPrefix.replace(/\\/+$/, '')}/${remainder}`;\n }),\n );\n\n count += matches.length;\n }\n }\n\n return count;\n }\n\n async #deleteLocations(rows: DbLocationsRow[]): Promise<void> {\n // Delete the location table entries (in chunks so as not to overload the\n // knex query builder)\n for (const ids of chunk(\n rows.map(l => l.id),\n 100,\n )) {\n await this.db<DbLocationsRow>('locations').whereIn('id', ids).delete();\n }\n\n // Delete the corresponding Location kind entities (this is efficiently\n // chunked internally in the catalog)\n await this.connection.applyMutation({\n type: 'delta',\n added: [],\n removed: rows.map(l => ({\n entity: locationSpecToLocationEntity({ location: l }),\n })),\n });\n }\n\n /**\n * Given a \"base\" URL prefix, find all locations that are for paths at or\n * below it.\n *\n * For example, given a base URL prefix of\n * \"https://github.com/backstage/backstage/blob/master/plugins\", it will match\n * locations inside the plugins directory, and nowhere else.\n */\n async #findLocationsByPrefixOrExactMatch(\n urls: Iterable<string>,\n ): Promise<Array<{ row: DbLocationsRow; parsed: GitUrl }>> {\n const result = new Array<{ row: DbLocationsRow; parsed: GitUrl }>();\n\n for (const url of urls) {\n let base: GitUrl;\n try {\n base = parseGitUrl(url);\n } catch (error) {\n throw new Error(`Invalid URL prefix, could not parse: ${url}`);\n }\n\n if (!base.owner || !base.name) {\n throw new Error(\n `Invalid URL prefix, missing owner or repository: ${url}`,\n );\n }\n\n const pathPrefix =\n base.filepath === '' || base.filepath.endsWith('/')\n ? base.filepath\n : `${base.filepath}/`;\n\n const rows = await this.db<DbLocationsRow>('locations')\n .where('type', '=', 'url')\n // Initial rough pruning to not have to go through them all\n .where('target', 'like', `%${base.owner}%`)\n .where('target', 'like', `%${base.name}%`)\n .select();\n\n result.push(\n ...rows.flatMap(row => {\n try {\n // We do this pretty explicit set of checks because we want to support\n // providers that have a URL format where the path isn't necessarily at\n // the end of the URL string (e.g. in the query part). Some of these may\n // be empty strings etc, but that's fine as long as they parse to the\n // same thing as above.\n const candidate = parseGitUrl(row.target);\n\n if (\n candidate.protocol === base.protocol &&\n candidate.resource === base.resource &&\n candidate.port === base.port &&\n candidate.organization === base.organization &&\n candidate.owner === base.owner &&\n candidate.name === base.name &&\n // If the base has no ref (for example didn't have the \"/blob/master\"\n // part and therefore targeted an entire repository) then we match any\n // ref below that\n (!base.ref || candidate.ref === base.ref) &&\n // Match both on exact equality and any subpath with a slash between\n (candidate.filepath === base.filepath ||\n candidate.filepath.startsWith(pathPrefix))\n ) {\n return [{ row, parsed: candidate }];\n }\n return [];\n } catch {\n return [];\n }\n }),\n );\n }\n\n return uniqBy(result, entry => entry.row.id);\n }\n\n // #endregion\n}\n\n/**\n * Recursively builds up the SQL expression corresponding to the given filter\n * predicate.\n *\n * @remarks\n *\n * Design note: The code prefers to let the SQL engine achieve case\n * insensitivity. We could attempt to use `.toUpperCase` etc on the client\n * side, but that would only work for the values being passed in, not the column\n * side of the expression. If we let the database perform UPPER on both, we know\n * that they will always be locale consistent etc as well.\n *\n * This does come at a runtime cost. However, the data set is typically rather\n * small in the grand scheme of things, and we can add the proper indices in the\n * future if needed. At this point I considered it not worth the effort.\n */\nfunction applyLocationFilterToQuery(\n clientType: string,\n inputQuery: Knex.QueryBuilder,\n query: FilterPredicate,\n): Knex.QueryBuilder {\n let result = inputQuery;\n\n if (!query || typeof query !== 'object' || Array.isArray(query)) {\n throw new InputError('Invalid filter predicate, expected an object');\n }\n\n if ('$all' in query) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (query.$all.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n return result.where(outer => {\n for (const subQuery of query.$all) {\n outer.andWhere(inner => {\n applyLocationFilterToQuery(clientType, inner, subQuery);\n });\n }\n });\n }\n\n if ('$any' in query) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (query.$any.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n return result.where(outer => {\n for (const subQuery of query.$any) {\n outer.orWhere(inner => {\n applyLocationFilterToQuery(clientType, inner, subQuery);\n });\n }\n });\n }\n\n if ('$not' in query) {\n return result.whereNot(inner => {\n applyLocationFilterToQuery(clientType, inner, query.$not);\n });\n }\n\n const entries = Object.entries(query);\n const keys = entries.map(e => e[0]);\n if (keys.some(k => k.startsWith('$'))) {\n throw new InputError(\n `Invalid filter predicate, unknown logic operator '${keys.join(', ')}'`,\n );\n }\n\n for (const [keyAnyCase, value] of entries) {\n const key = keyAnyCase.toLocaleLowerCase('en-US');\n if (!['id', 'type', 'target'].includes(key)) {\n throw new InputError(\n `Invalid filter predicate, expected key to be 'id', 'type', or 'target', got '${keyAnyCase}'`,\n );\n }\n\n result = applyFilterValueToQuery(clientType, result, key, value);\n }\n\n return result;\n}\n\nfunction applyFilterValueToQuery(\n clientType: string,\n result: Knex.QueryBuilder,\n key: string,\n value: FilterPredicateValue,\n): Knex.QueryBuilder {\n // Is it a primitive value?\n if (['string', 'number', 'boolean'].includes(typeof value)) {\n if (clientType === 'pg') {\n return result.whereRaw(`UPPER(??::text) = UPPER(?::text)`, [key, value]);\n }\n\n if (clientType.includes('mysql')) {\n return result.whereRaw(\n `UPPER(CAST(?? AS CHAR)) = UPPER(CAST(? AS CHAR))`,\n [key, value],\n );\n }\n\n return result.whereRaw(`UPPER(??) = UPPER(?)`, [key, value]);\n }\n\n // Is it a matcher object?\n if (typeof value === 'object') {\n if (!value || Array.isArray(value)) {\n throw new InputError(\n `Invalid filter predicate, got unknown matcher object '${JSON.stringify(\n value,\n )}'`,\n );\n }\n\n // Technically existence checks do not make much sense in the context of\n // this table at the time of writing (values are always present), but\n // there's nothing gained by prohibiting it.\n if ('$exists' in value) {\n return value.$exists ? result.whereNotNull(key) : result.whereNull(key);\n }\n\n if ('$in' in value) {\n // Explicitly handle the empty case to avoid malformed SQL\n if (value.$in.length === 0) {\n return result.whereRaw('1 = 0');\n }\n\n // The id is matched with plain equality; it's of UUID type and case\n // insensitivity does not apply.\n if (key === 'id') {\n return result.whereIn(key, value.$in);\n }\n\n if (clientType === 'pg') {\n const rhs = value.$in.map(() => 'UPPER(?::text)').join(', ');\n return result.whereRaw(`UPPER(??::text) IN (${rhs})`, [\n key,\n ...value.$in,\n ]);\n }\n\n if (clientType.includes('mysql')) {\n const rhs = value.$in.map(() => 'UPPER(CAST(? AS CHAR))').join(', ');\n return result.whereRaw(`UPPER(CAST(?? AS CHAR)) IN (${rhs})`, [\n key,\n ...value.$in,\n ]);\n }\n\n const rhs = value.$in.map(() => 'UPPER(?)').join(', ');\n return result.whereRaw(`UPPER(??) IN (${rhs})`, [key, ...value.$in]);\n }\n\n if ('$hasPrefix' in value) {\n const escaped = value.$hasPrefix.replace(/([\\\\%_])/g, '\\\\$1');\n\n if (clientType === 'pg') {\n return result.whereRaw(\"?? ilike ? escape '\\\\'\", [key, `${escaped}%`]);\n }\n\n if (clientType.includes('mysql')) {\n return result.whereRaw(\"UPPER(??) like UPPER(?) escape '\\\\\\\\'\", [\n key,\n `${escaped}%`,\n ]);\n }\n\n return result.whereRaw(\"UPPER(??) like UPPER(?) escape '\\\\'\", [\n key,\n `${escaped}%`,\n ]);\n }\n\n // There are no array shaped values for location queries, so we just always\n // fail here\n if ('$contains' in value) {\n return result.whereRaw('1 = 0');\n }\n\n throw new InputError(\n `Invalid filter predicate, got unknown matcher object '${JSON.stringify(\n value,\n )}'`,\n );\n }\n\n throw new InputError(\n `Invalid filter predicate, expected value to be a primitive value or a matcher object, got '${typeof value}'`,\n );\n}\n"],"names":["ConflictError","uuid","locationSpecToLocationEntity","getEntityLocationRef","NotFoundError","stringifyEntityRef","ANNOTATION_ORIGIN_LOCATION","parseLocationRef","chunk","parseGitUrl","uniqBy","InputError","rhs"],"mappings":"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"}
|