@red-hat-developer-hub/backstage-plugin-bulk-import-backend 6.3.0 → 6.4.1

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/README.md +28 -0
  3. package/config.d.ts +9 -1
  4. package/dist/catalog/catalogHttpClient.cjs.js +2 -0
  5. package/dist/catalog/catalogHttpClient.cjs.js.map +1 -1
  6. package/dist/catalog/catalogInfoGenerator.cjs.js +1 -0
  7. package/dist/catalog/catalogInfoGenerator.cjs.js.map +1 -1
  8. package/dist/database/migration.cjs.js +20 -0
  9. package/dist/database/migration.cjs.js.map +1 -0
  10. package/dist/database/repositoryDao.cjs.js +117 -0
  11. package/dist/database/repositoryDao.cjs.js.map +1 -0
  12. package/dist/generated/openapidocument.cjs.js +321 -72
  13. package/dist/generated/openapidocument.cjs.js.map +1 -1
  14. package/dist/github/githubApiService.cjs.js +217 -91
  15. package/dist/github/githubApiService.cjs.js.map +1 -1
  16. package/dist/github/utils/orgUtils.cjs.js +1 -0
  17. package/dist/github/utils/orgUtils.cjs.js.map +1 -1
  18. package/dist/github/utils/prUtils.cjs.js +2 -0
  19. package/dist/github/utils/prUtils.cjs.js.map +1 -1
  20. package/dist/github/utils/repoUtils.cjs.js +1 -38
  21. package/dist/github/utils/repoUtils.cjs.js.map +1 -1
  22. package/dist/github/utils/utils.cjs.js +39 -1
  23. package/dist/github/utils/utils.cjs.js.map +1 -1
  24. package/dist/gitlab/gitlabApiService.cjs.js +102 -0
  25. package/dist/gitlab/gitlabApiService.cjs.js.map +1 -1
  26. package/dist/gitlab/utils/prUtils.cjs.js +1 -0
  27. package/dist/gitlab/utils/prUtils.cjs.js.map +1 -1
  28. package/dist/gitlab/utils/utils.cjs.js +1 -0
  29. package/dist/gitlab/utils/utils.cjs.js.map +1 -1
  30. package/dist/helpers/utils.cjs.js +21 -0
  31. package/dist/helpers/utils.cjs.js.map +1 -1
  32. package/dist/index.d.ts +2 -1
  33. package/dist/plugin.cjs.js +6 -3
  34. package/dist/plugin.cjs.js.map +1 -1
  35. package/dist/service/handlers/import/bulkImports.cjs.js +155 -2
  36. package/dist/service/handlers/import/bulkImports.cjs.js.map +1 -1
  37. package/dist/service/handlers/import/execute-template.cjs.js +264 -0
  38. package/dist/service/handlers/import/execute-template.cjs.js.map +1 -0
  39. package/dist/service/handlers/import/importStatus.cjs.js +1 -1
  40. package/dist/service/handlers/import/importStatus.cjs.js.map +1 -1
  41. package/dist/service/handlers/repository/repositories.cjs.js +1 -0
  42. package/dist/service/handlers/repository/repositories.cjs.js.map +1 -1
  43. package/dist/service/router.cjs.js +179 -24
  44. package/dist/service/router.cjs.js.map +1 -1
  45. package/migrations/20250808122501_migrations.js +53 -0
  46. package/package.json +8 -3
package/CHANGELOG.md CHANGED
@@ -1,5 +1,25 @@
1
1
  ### Dependencies
2
2
 
3
+ ## 6.4.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 4c5967e: Make bulkImport configuration optional again to be backward compatible
8
+
9
+ ## 6.4.0
10
+
11
+ ### Minor Changes
12
+
13
+ - 2a31b02: Implement scaffolder template execution for the bulk-import plugin.
14
+
15
+ ### Patch Changes
16
+
17
+ - bd09be8: Automatically select templates from default namespace for bulkImport.importTemplate configuration.
18
+ - b5ce9fc: Fix dependencies: Replace @ai-zen/node-fetch-event-source with @microsoft/fetch-event-source and remove unused dependency @roadiehq/scaffolder-backend-module-utils
19
+ - e11b586: Fix an sql issue when filtering added repositories on SQlite
20
+ - Updated dependencies [2a31b02]
21
+ - @red-hat-developer-hub/backstage-plugin-bulk-import-common@1.10.0
22
+
3
23
  ## 6.3.0
4
24
 
5
25
  ### Minor Changes
package/README.md CHANGED
@@ -89,6 +89,34 @@ backend.add(BulkImportPermissionBackendModule);
89
89
  backend.add(import('@backstage/plugin-permission-backend/alpha'));
90
90
  ```
91
91
 
92
+ #### Scaffolder Template Execution
93
+
94
+ The Bulk Import plugin allows for the execution of a scaffolder template on multiple selected repositories. An administrator can create a scaffolder template specifically for the Bulk Import plugin and provide it within the application's configuration.
95
+
96
+ ##### Plugin Configuration
97
+
98
+ The Bulk Import plugin has a specific configuration for the scaffolder template:
99
+
100
+ ```
101
+ bulkImport:
102
+ importTemplate: your-template-entity-reference-or-template-name
103
+ importAPI: 'scaffolder'
104
+ ```
105
+
106
+ importAPI: This field defines the import workflow. It currently supports two options:
107
+
108
+ - open-pull-requests: This is the default import workflow, which includes the logic for creating pull requests for every selected repository.
109
+ - scaffolder: This workflow uses an import scenario defined in the scaffolder template. The import steps depend on the template's content, allowing for various scenarios. These can include importing existing catalog entities from a repository, creating pull requests, calling webhooks, and more. This method offers greater flexibility.
110
+
111
+ > Important Note
112
+ > The scaffolder template must be generic and not specific to a single repository to be successfully executed for every repository in the bulk list.
113
+
114
+ For the RHDH instance to use the scaffolder functionality, it must be run with the following environment variable enabled:
115
+
116
+ ```
117
+ export NODE_OPTIONS=--no-node-snapshot
118
+ ```
119
+
92
120
  ### Audit Logging
93
121
 
94
122
  Audit logging is backed by the [`@backstage/backend-plugin-api`](https://www.npmjs.com/package/@backstage/backend-plugin-api) package.
package/config.d.ts CHANGED
@@ -14,4 +14,12 @@
14
14
  * limitations under the License.
15
15
  */
16
16
 
17
- export interface Config {}
17
+ export interface Config {
18
+ bulkImport?: {
19
+ /**
20
+ * The name of the scaffolder template to execute for importing a repository.
21
+ * @visibility backend
22
+ */
23
+ importTemplate?: string;
24
+ };
25
+ }
@@ -3,6 +3,8 @@
3
3
  var fetch = require('node-fetch');
4
4
  var auth = require('../helpers/auth.cjs.js');
5
5
  var loggingUtils = require('../helpers/loggingUtils.cjs.js');
6
+ require('@backstage/catalog-model');
7
+ require('@backstage/errors');
6
8
  require('git-url-parse');
7
9
  var catalogUtils = require('./catalogUtils.cjs.js');
8
10
 
@@ -1 +1 @@
1
- {"version":3,"file":"catalogHttpClient.cjs.js","sources":["../../src/catalog/catalogHttpClient.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 type {\n AuthService,\n DiscoveryService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogApi } from '@backstage/catalog-client';\nimport type { LocationEntity } from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\n\nimport fetch from 'node-fetch';\n\nimport { getTokenForPlugin, logErrorIfNeeded } from '../helpers';\nimport { filterLocations, getCatalogUrl } from './catalogUtils';\nimport { CatalogLocation } from './types';\n\nexport class CatalogHttpClient {\n private readonly logger: LoggerService;\n private readonly config: Config;\n private readonly discovery: DiscoveryService;\n private readonly auth: AuthService;\n private readonly catalogApi: CatalogApi;\n\n constructor(deps: {\n logger: LoggerService;\n config: Config;\n discovery: DiscoveryService;\n auth: AuthService;\n catalogApi: CatalogApi;\n }) {\n this.logger = deps.logger;\n this.config = deps.config;\n this.discovery = deps.discovery;\n this.auth = deps.auth;\n this.catalogApi = deps.catalogApi;\n }\n\n // Wrapper for https://backstage.io/docs/features/software-catalog/software-catalog-api/#post-analyze-location\n async analyzeLocation(repoUrl: string): Promise<any[]> {\n this.logger.debug(`Forwarding request to analyze location: ${repoUrl}`);\n const response = await fetch(\n `${await this.discovery.getBaseUrl('catalog')}/analyze-location`,\n {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'POST',\n body: JSON.stringify({\n location: {\n type: 'github',\n target: repoUrl,\n },\n }),\n },\n );\n return (await response.json()).generateEntities ?? [];\n }\n\n async listCatalogUrlLocations(\n search?: string,\n pageNumber?: number,\n pageSize?: number,\n ): Promise<{\n uniqueCatalogUrlLocations: Map<string, CatalogLocation>;\n totalCount?: number;\n }> {\n // byId order: config, locations, other\n const byId = await this.listCatalogUrlLocationsById(\n search,\n pageNumber,\n pageSize,\n );\n const result = new Map<string, CatalogLocation>();\n\n for (const l of byId.locations) {\n if (!result.has(l.target)) {\n result.set(l.target, l);\n }\n }\n return {\n uniqueCatalogUrlLocations: result,\n totalCount: byId.totalCount,\n };\n }\n\n async listCatalogUrlLocationsById(\n search?: string,\n pageNumber?: number,\n pageSize?: number,\n ): Promise<{\n locations: CatalogLocation[];\n totalCount?: number;\n }> {\n const result = await Promise.all([\n this.listCatalogUrlLocationsFromConfig(search),\n this.listCatalogUrlLocationsByIdFromLocationsEndpoint(search),\n this.listCatalogUrlLocationEntitiesById(search, pageNumber, pageSize),\n ]);\n const locations = result.flatMap(u => u.locations);\n // we might have duplicate elements here\n const totalCount = result\n .map(l => l.totalCount ?? 0)\n .reduce((accumulator, currentValue) => accumulator + currentValue, 0);\n return {\n locations,\n totalCount,\n };\n }\n\n async listCatalogUrlLocationsByIdFromLocationsEndpoint(\n search?: string,\n ): Promise<{\n locations: CatalogLocation[];\n totalCount?: number;\n }> {\n const url = `${await this.discovery.getBaseUrl('catalog')}/locations`;\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'GET',\n });\n const locations = (await response.json()) as {\n data: { id: string; target: string; type: string };\n }[];\n if (!Array.isArray(locations)) {\n return { locations: [] };\n }\n const res = locations\n .filter(\n location => location.data?.target && location.data?.type === 'url',\n )\n .map(location => {\n return {\n id: location.data?.id,\n target: location.data.target,\n source: 'location',\n } as CatalogLocation;\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n listCatalogUrlLocationsFromConfig(search?: string): {\n locations: CatalogLocation[];\n totalCount?: number;\n } {\n const locationConfigs =\n this.config.getOptionalConfigArray('catalog.locations') ?? [];\n const res = locationConfigs\n .filter(\n location =>\n location.getOptionalString('target') &&\n location.getOptionalString('type') === 'url',\n )\n .map(location => {\n const target = location.getString('target');\n return {\n id: `app-config-location--${target}`,\n target,\n source: 'config',\n } as CatalogLocation;\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n async listCatalogUrlLocationEntitiesById(\n search?: string,\n _pageNumber?: number,\n _pageSize?: number,\n ): Promise<{\n locations: CatalogLocation[];\n totalCount?: number;\n }> {\n const result = await this.catalogApi.getEntities(\n {\n filter: {\n kind: 'Location',\n },\n // There is no query parameter to find entities with target URLs containing a string.\n // The existing filter does an exact matching. That's why we are retrieving this hard-coded high number of Locations.\n limit: 9999,\n offset: 0,\n order: { field: 'metadata.name', order: 'desc' },\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n const locations = (result?.items ?? []) as LocationEntity[];\n const res = locations\n .filter(\n location => location.spec?.target && location.spec?.type === 'url',\n )\n .map(location => {\n return {\n id: location.metadata.uid,\n target: location.spec.target!,\n source: 'integration',\n } as CatalogLocation;\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n /**\n * verifyLocationExistence checks for the existence of the Location target.\n * Under the hood, it attempts to read the target URL and will return false if the target could not be found\n * and even if there is already a Location row in the database.\n * @param repoCatalogUrl\n */\n async verifyLocationExistence(repoCatalogUrl: string): Promise<boolean> {\n try {\n const result = await this.catalogApi.addLocation(\n {\n type: 'url',\n target: repoCatalogUrl,\n dryRun: true,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n // The `result.exists` field is only filled in dryRun mode\n return result.exists as boolean;\n } catch (error: any) {\n if (\n error.message?.includes('NotFoundError') ||\n error.body?.error?.message?.includes('NotFoundError')\n ) {\n return false;\n }\n throw error;\n }\n }\n\n async hasEntityInCatalog(entityName: string) {\n return this.catalogApi\n .queryEntities(\n {\n filter: {\n 'metadata.name': entityName,\n },\n limit: 1,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n )\n .then(resp => resp.items?.length > 0);\n }\n\n async possiblyCreateLocation(repoCatalogUrl: string) {\n try {\n await this.catalogApi.addLocation(\n {\n type: 'url',\n target: repoCatalogUrl,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n } catch (error: any) {\n if (\n !(\n error.message?.includes('ConflictError') ||\n error.body?.error?.name?.includes('ConflictError')\n )\n ) {\n throw error;\n }\n // Location already exists, which is fine\n }\n }\n\n async deleteCatalogLocationById(locationId: string): Promise<void> {\n try {\n const url = `${await this.discovery.getBaseUrl(\n 'catalog',\n )}/locations/${locationId}`;\n await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'DELETE',\n });\n } catch (error: any) {\n logErrorIfNeeded(\n this.logger,\n `Could not delete location ${locationId}`,\n error,\n );\n }\n }\n\n async deleteCatalogLocationEntityById(locationUid: string): Promise<void> {\n await this.catalogApi.removeEntityByUid(locationUid, {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n });\n }\n\n async findLocationEntitiesByRepoUrl(repoUrl: string, defaultBranch?: string) {\n return this.findLocationEntitiesByTargetUrl(\n getCatalogUrl(this.config, repoUrl, defaultBranch),\n );\n }\n\n async findLocationEntitiesByTargetUrl(targetUrl: string, limit?: number) {\n return this.catalogApi\n .queryEntities(\n {\n filter: [\n { kind: 'Location', 'spec.type': 'url', 'spec.target': targetUrl },\n ],\n fields: ['metadata.namespace', 'metadata.name', 'metadata.uid'],\n limit,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n )\n .then(resp => resp.items);\n }\n\n async refreshLocationByRepoUrl(repoUrl: string, defaultBranch?: string) {\n const promises: Promise<void>[] = [];\n this.findLocationEntitiesByRepoUrl(repoUrl, defaultBranch).then(\n entities => {\n const nbEntities = entities.length;\n if (nbEntities === 0) {\n this.logger.debug(`No Location Entity found for repo: ${repoUrl}`);\n return;\n }\n this.logger.debug(\n `Refreshing ${nbEntities} Location(s) for repo: ${repoUrl}`,\n );\n entities.forEach(ent =>\n promises.push(\n this.refreshEntity(\n 'location',\n ent.metadata.name,\n ent.metadata.namespace,\n ),\n ),\n );\n },\n );\n await Promise.all(promises);\n }\n\n async refreshEntity(\n kind: string,\n name: string,\n namespace: string = 'default',\n ) {\n const entityRef = `${kind}:${namespace}/${name}`;\n this.logger.debug(`Refreshing entityRef: ${entityRef}`);\n await this.catalogApi.refreshEntity(entityRef, {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n });\n }\n}\n"],"names":["fetch","getTokenForPlugin","filterLocations","logErrorIfNeeded","getCatalogUrl"],"mappings":"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"}
1
+ {"version":3,"file":"catalogHttpClient.cjs.js","sources":["../../src/catalog/catalogHttpClient.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 type {\n AuthService,\n DiscoveryService,\n LoggerService,\n} from '@backstage/backend-plugin-api';\nimport type { CatalogApi } from '@backstage/catalog-client';\nimport type { LocationEntity } from '@backstage/catalog-model';\nimport type { Config } from '@backstage/config';\n\nimport fetch from 'node-fetch';\n\nimport { getTokenForPlugin, logErrorIfNeeded } from '../helpers';\nimport { filterLocations, getCatalogUrl } from './catalogUtils';\nimport { CatalogLocation } from './types';\n\nexport class CatalogHttpClient {\n private readonly logger: LoggerService;\n private readonly config: Config;\n private readonly discovery: DiscoveryService;\n private readonly auth: AuthService;\n private readonly catalogApi: CatalogApi;\n\n constructor(deps: {\n logger: LoggerService;\n config: Config;\n discovery: DiscoveryService;\n auth: AuthService;\n catalogApi: CatalogApi;\n }) {\n this.logger = deps.logger;\n this.config = deps.config;\n this.discovery = deps.discovery;\n this.auth = deps.auth;\n this.catalogApi = deps.catalogApi;\n }\n\n // Wrapper for https://backstage.io/docs/features/software-catalog/software-catalog-api/#post-analyze-location\n async analyzeLocation(repoUrl: string): Promise<any[]> {\n this.logger.debug(`Forwarding request to analyze location: ${repoUrl}`);\n const response = await fetch(\n `${await this.discovery.getBaseUrl('catalog')}/analyze-location`,\n {\n headers: {\n 'Content-Type': 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'POST',\n body: JSON.stringify({\n location: {\n type: 'github',\n target: repoUrl,\n },\n }),\n },\n );\n return (await response.json()).generateEntities ?? [];\n }\n\n async listCatalogUrlLocations(\n search?: string,\n pageNumber?: number,\n pageSize?: number,\n ): Promise<{\n uniqueCatalogUrlLocations: Map<string, CatalogLocation>;\n totalCount?: number;\n }> {\n // byId order: config, locations, other\n const byId = await this.listCatalogUrlLocationsById(\n search,\n pageNumber,\n pageSize,\n );\n const result = new Map<string, CatalogLocation>();\n\n for (const l of byId.locations) {\n if (!result.has(l.target)) {\n result.set(l.target, l);\n }\n }\n return {\n uniqueCatalogUrlLocations: result,\n totalCount: byId.totalCount,\n };\n }\n\n async listCatalogUrlLocationsById(\n search?: string,\n pageNumber?: number,\n pageSize?: number,\n ): Promise<{\n locations: CatalogLocation[];\n totalCount?: number;\n }> {\n const result = await Promise.all([\n this.listCatalogUrlLocationsFromConfig(search),\n this.listCatalogUrlLocationsByIdFromLocationsEndpoint(search),\n this.listCatalogUrlLocationEntitiesById(search, pageNumber, pageSize),\n ]);\n const locations = result.flatMap(u => u.locations);\n // we might have duplicate elements here\n const totalCount = result\n .map(l => l.totalCount ?? 0)\n .reduce((accumulator, currentValue) => accumulator + currentValue, 0);\n return {\n locations,\n totalCount,\n };\n }\n\n async listCatalogUrlLocationsByIdFromLocationsEndpoint(\n search?: string,\n ): Promise<{\n locations: CatalogLocation[];\n totalCount?: number;\n }> {\n const url = `${await this.discovery.getBaseUrl('catalog')}/locations`;\n const response = await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'GET',\n });\n const locations = (await response.json()) as {\n data: { id: string; target: string; type: string };\n }[];\n if (!Array.isArray(locations)) {\n return { locations: [] };\n }\n const res = locations\n .filter(\n location => location.data?.target && location.data?.type === 'url',\n )\n .map(location => {\n return {\n id: location.data?.id,\n target: location.data.target,\n source: 'location',\n } as CatalogLocation;\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n listCatalogUrlLocationsFromConfig(search?: string): {\n locations: CatalogLocation[];\n totalCount?: number;\n } {\n const locationConfigs =\n this.config.getOptionalConfigArray('catalog.locations') ?? [];\n const res = locationConfigs\n .filter(\n location =>\n location.getOptionalString('target') &&\n location.getOptionalString('type') === 'url',\n )\n .map(location => {\n const target = location.getString('target');\n return {\n id: `app-config-location--${target}`,\n target,\n source: 'config',\n } as CatalogLocation;\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n async listCatalogUrlLocationEntitiesById(\n search?: string,\n _pageNumber?: number,\n _pageSize?: number,\n ): Promise<{\n locations: CatalogLocation[];\n totalCount?: number;\n }> {\n const result = await this.catalogApi.getEntities(\n {\n filter: {\n kind: 'Location',\n },\n // There is no query parameter to find entities with target URLs containing a string.\n // The existing filter does an exact matching. That's why we are retrieving this hard-coded high number of Locations.\n limit: 9999,\n offset: 0,\n order: { field: 'metadata.name', order: 'desc' },\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n const locations = (result?.items ?? []) as LocationEntity[];\n const res = locations\n .filter(\n location => location.spec?.target && location.spec?.type === 'url',\n )\n .map(location => {\n return {\n id: location.metadata.uid,\n target: location.spec.target!,\n source: 'integration',\n } as CatalogLocation;\n });\n const filtered = filterLocations(res, search);\n return { locations: filtered, totalCount: filtered.length };\n }\n\n /**\n * verifyLocationExistence checks for the existence of the Location target.\n * Under the hood, it attempts to read the target URL and will return false if the target could not be found\n * and even if there is already a Location row in the database.\n * @param repoCatalogUrl\n */\n async verifyLocationExistence(repoCatalogUrl: string): Promise<boolean> {\n try {\n const result = await this.catalogApi.addLocation(\n {\n type: 'url',\n target: repoCatalogUrl,\n dryRun: true,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n // The `result.exists` field is only filled in dryRun mode\n return result.exists as boolean;\n } catch (error: any) {\n if (\n error.message?.includes('NotFoundError') ||\n error.body?.error?.message?.includes('NotFoundError')\n ) {\n return false;\n }\n throw error;\n }\n }\n\n async hasEntityInCatalog(entityName: string) {\n return this.catalogApi\n .queryEntities(\n {\n filter: {\n 'metadata.name': entityName,\n },\n limit: 1,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n )\n .then(resp => resp.items?.length > 0);\n }\n\n async possiblyCreateLocation(repoCatalogUrl: string) {\n try {\n await this.catalogApi.addLocation(\n {\n type: 'url',\n target: repoCatalogUrl,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n );\n } catch (error: any) {\n if (\n !(\n error.message?.includes('ConflictError') ||\n error.body?.error?.name?.includes('ConflictError')\n )\n ) {\n throw error;\n }\n // Location already exists, which is fine\n }\n }\n\n async deleteCatalogLocationById(locationId: string): Promise<void> {\n try {\n const url = `${await this.discovery.getBaseUrl(\n 'catalog',\n )}/locations/${locationId}`;\n await fetch(url, {\n headers: {\n Accept: 'application/json',\n Authorization: `Bearer ${await getTokenForPlugin(\n this.auth,\n 'catalog',\n )}`,\n },\n method: 'DELETE',\n });\n } catch (error: any) {\n logErrorIfNeeded(\n this.logger,\n `Could not delete location ${locationId}`,\n error,\n );\n }\n }\n\n async deleteCatalogLocationEntityById(locationUid: string): Promise<void> {\n await this.catalogApi.removeEntityByUid(locationUid, {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n });\n }\n\n async findLocationEntitiesByRepoUrl(repoUrl: string, defaultBranch?: string) {\n return this.findLocationEntitiesByTargetUrl(\n getCatalogUrl(this.config, repoUrl, defaultBranch),\n );\n }\n\n async findLocationEntitiesByTargetUrl(targetUrl: string, limit?: number) {\n return this.catalogApi\n .queryEntities(\n {\n filter: [\n { kind: 'Location', 'spec.type': 'url', 'spec.target': targetUrl },\n ],\n fields: ['metadata.namespace', 'metadata.name', 'metadata.uid'],\n limit,\n },\n {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n },\n )\n .then(resp => resp.items);\n }\n\n async refreshLocationByRepoUrl(repoUrl: string, defaultBranch?: string) {\n const promises: Promise<void>[] = [];\n this.findLocationEntitiesByRepoUrl(repoUrl, defaultBranch).then(\n entities => {\n const nbEntities = entities.length;\n if (nbEntities === 0) {\n this.logger.debug(`No Location Entity found for repo: ${repoUrl}`);\n return;\n }\n this.logger.debug(\n `Refreshing ${nbEntities} Location(s) for repo: ${repoUrl}`,\n );\n entities.forEach(ent =>\n promises.push(\n this.refreshEntity(\n 'location',\n ent.metadata.name,\n ent.metadata.namespace,\n ),\n ),\n );\n },\n );\n await Promise.all(promises);\n }\n\n async refreshEntity(\n kind: string,\n name: string,\n namespace: string = 'default',\n ) {\n const entityRef = `${kind}:${namespace}/${name}`;\n this.logger.debug(`Refreshing entityRef: ${entityRef}`);\n await this.catalogApi.refreshEntity(entityRef, {\n token: await getTokenForPlugin(this.auth, 'catalog'),\n });\n }\n}\n"],"names":["fetch","getTokenForPlugin","filterLocations","logErrorIfNeeded","getCatalogUrl"],"mappings":"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"}
@@ -7,6 +7,7 @@ require('@backstage/plugin-permission-common');
7
7
  require('@red-hat-developer-hub/backstage-plugin-bulk-import-common');
8
8
  require('just-kebab-case');
9
9
  var loggingUtils = require('../helpers/loggingUtils.cjs.js');
10
+ require('@backstage/catalog-model');
10
11
 
11
12
  function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e : { default: e }; }
12
13
 
@@ -1 +1 @@
1
- {"version":3,"file":"catalogInfoGenerator.cjs.js","sources":["../../src/catalog/catalogInfoGenerator.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 type { LoggerService } from '@backstage/backend-plugin-api';\n\nimport gitUrlParse from 'git-url-parse';\nimport jsYaml from 'js-yaml';\n\nimport { logErrorIfNeeded } from '../helpers';\nimport { CatalogHttpClient } from './catalogHttpClient';\n\nexport class CatalogInfoGenerator {\n private readonly logger: LoggerService;\n private readonly catalogHttpClient: CatalogHttpClient;\n\n constructor(logger: LoggerService, catalogHttpClient: CatalogHttpClient) {\n this.logger = logger;\n this.catalogHttpClient = catalogHttpClient;\n }\n\n async generateDefaultCatalogInfoContent(\n repoUrl: string,\n analyzeLocation: boolean = true,\n ): Promise<string> {\n const gitUrl = gitUrlParse(repoUrl);\n const defaultCatalogInfo = `---\napiVersion: backstage.io/v1alpha1\nkind: Component\nmetadata:\n name: ${gitUrl.name}\n annotations:\n github.com/project-slug: ${gitUrl.organization}/${gitUrl.name}\nspec:\n type: other\n lifecycle: unknown\n owner: ${gitUrl.organization}\n---`;\n if (!analyzeLocation) {\n return defaultCatalogInfo;\n }\n\n let generatedEntities: any[] = [];\n try {\n generatedEntities = await this.catalogHttpClient.analyzeLocation(repoUrl);\n } catch (error: any) {\n // fallback to the default catalog-info value\n logErrorIfNeeded(\n this.logger,\n `Could not analyze location ${repoUrl}`,\n error,\n );\n }\n\n if (generatedEntities.length === 0) {\n return defaultCatalogInfo;\n }\n\n return generatedEntities\n .map(\n generatedEntity => `---\n${jsYaml.dump(generatedEntity.entity)}`,\n )\n .join('\\n');\n }\n}\n"],"names":["gitUrlParse","logErrorIfNeeded","jsYaml"],"mappings":";;;;;;;;;;;;;;;AAwBO,MAAM,oBAAqB,CAAA;AAAA,EACf,MAAA;AAAA,EACA,iBAAA;AAAA,EAEjB,WAAA,CAAY,QAAuB,iBAAsC,EAAA;AACvE,IAAA,IAAA,CAAK,MAAS,GAAA,MAAA;AACd,IAAA,IAAA,CAAK,iBAAoB,GAAA,iBAAA;AAAA;AAC3B,EAEA,MAAM,iCAAA,CACJ,OACA,EAAA,eAAA,GAA2B,IACV,EAAA;AACjB,IAAM,MAAA,MAAA,GAASA,6BAAY,OAAO,CAAA;AAClC,IAAA,MAAM,kBAAqB,GAAA,CAAA;AAAA;AAAA;AAAA;AAAA,QAAA,EAIrB,OAAO,IAAI;AAAA;AAAA,6BAAA,EAEU,MAAO,CAAA,YAAY,CAAI,CAAA,EAAA,MAAA,CAAO,IAAI;AAAA;AAAA;AAAA;AAAA,SAAA,EAItD,OAAO,YAAY;AAAA,GAAA,CAAA;AAE1B,IAAA,IAAI,CAAC,eAAiB,EAAA;AACpB,MAAO,OAAA,kBAAA;AAAA;AAGT,IAAA,IAAI,oBAA2B,EAAC;AAChC,IAAI,IAAA;AACF,MAAA,iBAAA,GAAoB,MAAM,IAAA,CAAK,iBAAkB,CAAA,eAAA,CAAgB,OAAO,CAAA;AAAA,aACjE,KAAY,EAAA;AAEnB,MAAAC,6BAAA;AAAA,QACE,IAAK,CAAA,MAAA;AAAA,QACL,8BAA8B,OAAO,CAAA,CAAA;AAAA,QACrC;AAAA,OACF;AAAA;AAGF,IAAI,IAAA,iBAAA,CAAkB,WAAW,CAAG,EAAA;AAClC,MAAO,OAAA,kBAAA;AAAA;AAGT,IAAA,OAAO,iBACJ,CAAA,GAAA;AAAA,MACC,CAAmB,eAAA,KAAA,CAAA;AAAA,EACzBC,uBAAO,CAAA,IAAA,CAAK,eAAgB,CAAA,MAAM,CAAC,CAAA;AAAA,KAC/B,CACC,KAAK,IAAI,CAAA;AAAA;AAEhB;;;;"}
1
+ {"version":3,"file":"catalogInfoGenerator.cjs.js","sources":["../../src/catalog/catalogInfoGenerator.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 type { LoggerService } from '@backstage/backend-plugin-api';\n\nimport gitUrlParse from 'git-url-parse';\nimport jsYaml from 'js-yaml';\n\nimport { logErrorIfNeeded } from '../helpers';\nimport { CatalogHttpClient } from './catalogHttpClient';\n\nexport class CatalogInfoGenerator {\n private readonly logger: LoggerService;\n private readonly catalogHttpClient: CatalogHttpClient;\n\n constructor(logger: LoggerService, catalogHttpClient: CatalogHttpClient) {\n this.logger = logger;\n this.catalogHttpClient = catalogHttpClient;\n }\n\n async generateDefaultCatalogInfoContent(\n repoUrl: string,\n analyzeLocation: boolean = true,\n ): Promise<string> {\n const gitUrl = gitUrlParse(repoUrl);\n const defaultCatalogInfo = `---\napiVersion: backstage.io/v1alpha1\nkind: Component\nmetadata:\n name: ${gitUrl.name}\n annotations:\n github.com/project-slug: ${gitUrl.organization}/${gitUrl.name}\nspec:\n type: other\n lifecycle: unknown\n owner: ${gitUrl.organization}\n---`;\n if (!analyzeLocation) {\n return defaultCatalogInfo;\n }\n\n let generatedEntities: any[] = [];\n try {\n generatedEntities = await this.catalogHttpClient.analyzeLocation(repoUrl);\n } catch (error: any) {\n // fallback to the default catalog-info value\n logErrorIfNeeded(\n this.logger,\n `Could not analyze location ${repoUrl}`,\n error,\n );\n }\n\n if (generatedEntities.length === 0) {\n return defaultCatalogInfo;\n }\n\n return generatedEntities\n .map(\n generatedEntity => `---\n${jsYaml.dump(generatedEntity.entity)}`,\n )\n .join('\\n');\n }\n}\n"],"names":["gitUrlParse","logErrorIfNeeded","jsYaml"],"mappings":";;;;;;;;;;;;;;;;AAwBO,MAAM,oBAAqB,CAAA;AAAA,EACf,MAAA;AAAA,EACA,iBAAA;AAAA,EAEjB,WAAA,CAAY,QAAuB,iBAAsC,EAAA;AACvE,IAAA,IAAA,CAAK,MAAS,GAAA,MAAA;AACd,IAAA,IAAA,CAAK,iBAAoB,GAAA,iBAAA;AAAA;AAC3B,EAEA,MAAM,iCAAA,CACJ,OACA,EAAA,eAAA,GAA2B,IACV,EAAA;AACjB,IAAM,MAAA,MAAA,GAASA,6BAAY,OAAO,CAAA;AAClC,IAAA,MAAM,kBAAqB,GAAA,CAAA;AAAA;AAAA;AAAA;AAAA,QAAA,EAIrB,OAAO,IAAI;AAAA;AAAA,6BAAA,EAEU,MAAO,CAAA,YAAY,CAAI,CAAA,EAAA,MAAA,CAAO,IAAI;AAAA;AAAA;AAAA;AAAA,SAAA,EAItD,OAAO,YAAY;AAAA,GAAA,CAAA;AAE1B,IAAA,IAAI,CAAC,eAAiB,EAAA;AACpB,MAAO,OAAA,kBAAA;AAAA;AAGT,IAAA,IAAI,oBAA2B,EAAC;AAChC,IAAI,IAAA;AACF,MAAA,iBAAA,GAAoB,MAAM,IAAA,CAAK,iBAAkB,CAAA,eAAA,CAAgB,OAAO,CAAA;AAAA,aACjE,KAAY,EAAA;AAEnB,MAAAC,6BAAA;AAAA,QACE,IAAK,CAAA,MAAA;AAAA,QACL,8BAA8B,OAAO,CAAA,CAAA;AAAA,QACrC;AAAA,OACF;AAAA;AAGF,IAAI,IAAA,iBAAA,CAAkB,WAAW,CAAG,EAAA;AAClC,MAAO,OAAA,kBAAA;AAAA;AAGT,IAAA,OAAO,iBACJ,CAAA,GAAA;AAAA,MACC,CAAmB,eAAA,KAAA,CAAA;AAAA,EACzBC,uBAAO,CAAA,IAAA,CAAK,eAAgB,CAAA,MAAM,CAAC,CAAA;AAAA,KAC/B,CACC,KAAK,IAAI,CAAA;AAAA;AAEhB;;;;"}
@@ -0,0 +1,20 @@
1
+ 'use strict';
2
+
3
+ var backendPluginApi = require('@backstage/backend-plugin-api');
4
+
5
+ const migrationsDir = backendPluginApi.resolvePackagePath(
6
+ "@red-hat-developer-hub/backstage-plugin-bulk-import-backend",
7
+ "migrations"
8
+ );
9
+ async function migrate(databaseManager) {
10
+ const knex = await databaseManager.getClient();
11
+ if (!databaseManager.migrations?.skip) {
12
+ await knex.migrate.latest({
13
+ directory: migrationsDir
14
+ });
15
+ }
16
+ return knex;
17
+ }
18
+
19
+ exports.migrate = migrate;
20
+ //# sourceMappingURL=migration.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migration.cjs.js","sources":["../../src/database/migration.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 {\n DatabaseService,\n resolvePackagePath,\n} from '@backstage/backend-plugin-api';\n\nimport { Knex } from 'knex';\n\nconst migrationsDir = resolvePackagePath(\n '@red-hat-developer-hub/backstage-plugin-bulk-import-backend',\n 'migrations',\n);\n\nexport async function migrate(databaseManager: DatabaseService): Promise<Knex> {\n const knex = await databaseManager.getClient();\n\n if (!databaseManager.migrations?.skip) {\n await knex.migrate.latest({\n directory: migrationsDir,\n });\n }\n return knex;\n}\n"],"names":["resolvePackagePath"],"mappings":";;;;AAuBA,MAAM,aAAgB,GAAAA,mCAAA;AAAA,EACpB,6DAAA;AAAA,EACA;AACF,CAAA;AAEA,eAAsB,QAAQ,eAAiD,EAAA;AAC7E,EAAM,MAAA,IAAA,GAAO,MAAM,eAAA,CAAgB,SAAU,EAAA;AAE7C,EAAI,IAAA,CAAC,eAAgB,CAAA,UAAA,EAAY,IAAM,EAAA;AACrC,IAAM,MAAA,IAAA,CAAK,QAAQ,MAAO,CAAA;AAAA,MACxB,SAAW,EAAA;AAAA,KACZ,CAAA;AAAA;AAEH,EAAO,OAAA,IAAA;AACT;;;;"}
@@ -0,0 +1,117 @@
1
+ 'use strict';
2
+
3
+ async function paginateQuery(queryBuilder, page = 1, size = 10, search) {
4
+ const offset = (page - 1) * size;
5
+ let baseQuery = queryBuilder.clone();
6
+ if (search?.term) {
7
+ if (queryBuilder.client.dialect === "postgres") {
8
+ baseQuery = baseQuery.whereILike(search.column, `%${search.term}%`);
9
+ } else {
10
+ baseQuery = baseQuery.whereLike(search.column, `%${search.term}%`);
11
+ }
12
+ }
13
+ const countQuery = baseQuery.clone().clearSelect().clearOrder().count("* as count");
14
+ const [rows, [{ count }]] = await Promise.all([
15
+ baseQuery.clone().limit(size).offset(offset),
16
+ countQuery
17
+ ]);
18
+ const total = Number(count);
19
+ return {
20
+ data: rows,
21
+ page,
22
+ size,
23
+ total,
24
+ totalPages: Math.ceil(total / size)
25
+ };
26
+ }
27
+ class RepositoryDao {
28
+ constructor(knex, logger) {
29
+ this.knex = knex;
30
+ this.logger = logger;
31
+ }
32
+ async findRepositories(page = 1, size = 10, search) {
33
+ this.logger.debug(
34
+ `Fetching repositories page=${page}, size=${size}, search=${search}`
35
+ );
36
+ const query = this.knex("repositories").select("id", "url", "approvalTool");
37
+ const searchParam = { column: "url", term: search };
38
+ return paginateQuery(query, page, size, searchParam);
39
+ }
40
+ async insertRepository(repoUrl, taskId, approvalTool) {
41
+ this.logger.debug(
42
+ `Saving task ${taskId} for repo ${repoUrl} to database..`
43
+ );
44
+ const repository = await this.knex("repositories").where({ url: repoUrl }).first();
45
+ let repositoryId;
46
+ if (repository) {
47
+ repositoryId = repository.id;
48
+ } else {
49
+ const [newRepository] = await this.knex("repositories").insert({ url: repoUrl, approvalTool }).returning("id");
50
+ repositoryId = newRepository.id;
51
+ }
52
+ return repositoryId;
53
+ }
54
+ async findRepositoryByUrl(url) {
55
+ this.logger.debug(`Fetching repository from database by url ${url}...`);
56
+ return await this.knex("repositories").where({ url }).first();
57
+ }
58
+ async deleteRepository(url) {
59
+ this.logger.debug(`Deleting repository from database by url ${url}...`);
60
+ const repository = await this.knex("repositories").where({ url }).first();
61
+ if (repository) {
62
+ await this.knex("repositories").where({ id: repository.id }).del();
63
+ }
64
+ }
65
+ }
66
+ class ScaffolderTaskDao {
67
+ constructor(knex) {
68
+ this.knex = knex;
69
+ }
70
+ async findAllTasks() {
71
+ return await this.knex("scaffolder_tasks").select(
72
+ "taskId",
73
+ "repositoryId",
74
+ "scaffolderOptions"
75
+ );
76
+ }
77
+ async insertTask(task) {
78
+ const result = await this.knex("scaffolder_tasks").insert({
79
+ taskId: task.taskId,
80
+ repositoryId: task.repositoryId,
81
+ scaffolderOptions: JSON.stringify(task.scaffolderOptions),
82
+ executedAt: task.executedAt
83
+ }).returning("taskId");
84
+ return result[0].taskId;
85
+ }
86
+ async findTasksByRepositoryId(repositoryId) {
87
+ return await this.knex("scaffolder_tasks").where({ repositoryId }).select("taskId", "repositoryId", "scaffolderOptions", "executedAt");
88
+ }
89
+ async lastExecutedTaskByRepoId(repositoryId) {
90
+ return await this.knex("scaffolder_tasks").where({ repositoryId }).orderBy("executedAt", "desc").first();
91
+ }
92
+ }
93
+ class TaskLocationsDao {
94
+ constructor(knex) {
95
+ this.knex = knex;
96
+ }
97
+ async addTaskLocation(taskId, location, type = "component") {
98
+ await this.knex("task_locations").insert({ taskId, location, type });
99
+ }
100
+ async findLocationsByTaskId(taskId) {
101
+ return await this.knex("task_locations").where({ taskId }).select("id", "taskId", "location", "type");
102
+ }
103
+ async findAllLocations() {
104
+ return await this.knex("task_locations").select(
105
+ "id",
106
+ "taskId",
107
+ "location",
108
+ "type"
109
+ );
110
+ }
111
+ }
112
+
113
+ exports.RepositoryDao = RepositoryDao;
114
+ exports.ScaffolderTaskDao = ScaffolderTaskDao;
115
+ exports.TaskLocationsDao = TaskLocationsDao;
116
+ exports.paginateQuery = paginateQuery;
117
+ //# sourceMappingURL=repositoryDao.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"repositoryDao.cjs.js","sources":["../../src/database/repositoryDao.ts"],"sourcesContent":["/*\n * Copyright Red Hat, Inc.\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 type { LoggerService } from '@backstage/backend-plugin-api';\n\nimport { Knex } from 'knex';\n\nimport { Components } from '../generated/openapi';\n\nexport interface Repository {\n id: number;\n url: string;\n approvalTool: Components.Schemas.ApprovalTool;\n}\n\nexport interface ScaffolderTask {\n taskId: string;\n scaffolderOptions: any;\n repositoryId: number;\n executedAt?: Date;\n}\n\nexport interface PaginatedResult<T> {\n data: T[];\n page: number;\n size: number;\n total: number;\n totalPages: number;\n}\n\nexport async function paginateQuery<T>(\n queryBuilder: Knex.QueryBuilder<any, T[]>,\n page: number = 1,\n size: number = 10,\n search?: { column: string; term?: string },\n): Promise<PaginatedResult<T>> {\n const offset = (page - 1) * size;\n\n let baseQuery = queryBuilder.clone();\n\n if (search?.term) {\n if (queryBuilder.client.dialect === 'postgres') {\n baseQuery = baseQuery.whereILike(search.column, `%${search.term}%`);\n } else {\n baseQuery = baseQuery.whereLike(search.column, `%${search.term}%`);\n }\n }\n\n const countQuery = baseQuery\n .clone()\n .clearSelect()\n .clearOrder()\n .count<{ count: string }[]>('* as count');\n\n const [rows, [{ count }]] = await Promise.all([\n baseQuery.clone().limit(size).offset(offset),\n countQuery,\n ]);\n\n const total = Number(count);\n\n return {\n data: rows as T[],\n page,\n size,\n total,\n totalPages: Math.ceil(total / size),\n };\n}\n\n// @internal\nexport class RepositoryDao {\n constructor(\n private readonly knex: Knex<any, any[]>,\n private readonly logger: LoggerService,\n ) {}\n\n async findRepositories(\n page: number = 1,\n size: number = 10,\n search?: string,\n ): Promise<PaginatedResult<Repository>> {\n this.logger.debug(\n `Fetching repositories page=${page}, size=${size}, search=${search}`,\n );\n const query = this.knex('repositories').select('id', 'url', 'approvalTool');\n const searchParam = { column: 'url', term: search };\n return paginateQuery<Repository>(query, page, size, searchParam);\n }\n\n async insertRepository(\n repoUrl: string,\n taskId: string,\n approvalTool: string,\n ): Promise<number> {\n this.logger.debug(\n `Saving task ${taskId} for repo ${repoUrl} to database..`,\n );\n const repository = await this.knex('repositories')\n .where({ url: repoUrl })\n .first();\n\n let repositoryId;\n if (repository) {\n repositoryId = repository.id;\n } else {\n const [newRepository] = await this.knex('repositories')\n .insert({ url: repoUrl, approvalTool: approvalTool })\n .returning('id');\n repositoryId = newRepository.id;\n }\n return repositoryId;\n }\n\n async findRepositoryByUrl(url: string): Promise<Repository | undefined> {\n this.logger.debug(`Fetching repository from database by url ${url}...`);\n return await this.knex('repositories').where({ url: url }).first();\n }\n\n async deleteRepository(url: string): Promise<void> {\n this.logger.debug(`Deleting repository from database by url ${url}...`);\n const repository = await this.knex('repositories')\n .where({ url: url })\n .first();\n\n if (repository) {\n await this.knex('repositories').where({ id: repository.id }).del();\n }\n }\n}\n\n// @internal\nexport class ScaffolderTaskDao {\n constructor(private readonly knex: Knex<any, any[]>) {}\n\n async findAllTasks(): Promise<ScaffolderTask[]> {\n return await this.knex('scaffolder_tasks').select<ScaffolderTask[]>(\n 'taskId',\n 'repositoryId',\n 'scaffolderOptions',\n );\n }\n\n async insertTask(task: ScaffolderTask): Promise<string> {\n const result = await this.knex('scaffolder_tasks')\n .insert<ScaffolderTask>({\n taskId: task.taskId,\n repositoryId: task.repositoryId,\n scaffolderOptions: JSON.stringify(task.scaffolderOptions),\n executedAt: task.executedAt,\n })\n .returning<{ taskId: string }[]>('taskId');\n\n return result[0].taskId;\n }\n\n async findTasksByRepositoryId(\n repositoryId: number,\n ): Promise<ScaffolderTask[]> {\n return await this.knex('scaffolder_tasks')\n .where({ repositoryId })\n .select<\n ScaffolderTask[]\n >('taskId', 'repositoryId', 'scaffolderOptions', 'executedAt');\n }\n\n async lastExecutedTaskByRepoId(\n repositoryId: number,\n ): Promise<ScaffolderTask | undefined> {\n return await this.knex('scaffolder_tasks')\n .where({ repositoryId })\n .orderBy('executedAt', 'desc')\n .first<ScaffolderTask>();\n }\n}\n\nexport interface TaskLocation {\n id: number;\n taskId: string;\n location: string;\n type: string;\n}\n\n// @internal\nexport class TaskLocationsDao {\n constructor(private readonly knex: Knex<any, any[]>) {}\n\n async addTaskLocation(\n taskId: string,\n location: string,\n type: string = 'component',\n ): Promise<void> {\n await this.knex('task_locations').insert({ taskId, location, type });\n }\n\n async findLocationsByTaskId(taskId: string): Promise<TaskLocation[]> {\n return await this.knex('task_locations')\n .where({ taskId })\n .select<TaskLocation[]>('id', 'taskId', 'location', 'type');\n }\n\n async findAllLocations(): Promise<TaskLocation[]> {\n return await this.knex('task_locations').select<TaskLocation[]>(\n 'id',\n 'taskId',\n 'location',\n 'type',\n );\n }\n}\n"],"names":[],"mappings":"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"}