UNPKG

@directus/api

Version:

Directus is a real-time API and App dashboard for managing SQL database content

125 lines (123 loc) 4.42 kB
import database_default from "../database/index.js"; import { transaction } from "../utils/transaction.js"; import { ItemsService } from "./items.js"; import { getDeploymentDriver } from "../deployment.js"; import { parseValue } from "../utils/parse-value.js"; import { InvalidPayloadError } from "@directus/errors"; //#region src/services/deployment-projects.ts var DeploymentProjectsService = class DeploymentProjectsService extends ItemsService { constructor(options) { super("directus_deployment_projects", options); } /** * Find a project by external ID within a deployment. Returns null if not tracked. * external_id is unique only per deployment, so the lookup must be scoped. */ async readByExternalId(deploymentId, externalId) { return (await this.readByQuery({ filter: { deployment: { _eq: deploymentId }, external_id: { _eq: externalId } }, limit: 1 }))?.[0] ?? null; } /** * List provider projects merged with DB selection, syncing metadata. */ async listWithSync(deploymentId, providerProjects) { const selectedProjects = await this.readByQuery({ filter: { deployment: { _eq: deploymentId } }, limit: -1 }); const selectedMap = new Map(selectedProjects.map((p) => [p.external_id, p])); const toUpdate = selectedProjects.map((dbProject) => { const providerProject = providerProjects.find((p) => p.id === dbProject.external_id); if (!providerProject) return null; return { id: dbProject.id, name: providerProject.name, deployable: providerProject.deployable }; }).filter((update) => update !== null); if (toUpdate.length > 0) await this.updateBatch(toUpdate); return providerProjects.map((project) => ({ id: selectedMap.get(project.id)?.id ?? null, external_id: project.id, name: project.name, deployable: project.deployable, framework: project.framework })); } /** * Validate that all projects to create are deployable. */ async validateDeployable(provider, projectsToCreate) { if (projectsToCreate.length === 0) return; const config = await this.readConfig(provider); const providerProjects = await this.createDriver(config).listProjects(); const projectsMap = new Map(providerProjects.map((p) => [p.id, p])); const nonDeployable = projectsToCreate.filter((p) => !projectsMap.get(p.external_id)?.deployable); if (nonDeployable.length > 0) throw new InvalidPayloadError({ reason: `Cannot add non-deployable projects: ${nonDeployable.map((p) => projectsMap.get(p.external_id)?.name || p.external_id).join(", ")}` }); } /** * Update project selection (create/delete) */ async updateSelection(provider, create, deleteIds) { const config = await this.readConfig(provider); const db = database_default(); const driver = this.createDriver(config); const enrichedCreate = await Promise.all(create.map(async (p) => { const details = await driver.getProject(p.external_id); return { external_id: p.external_id, name: p.name, url: details.url ?? null, framework: details.framework ?? null, deployable: details.deployable }; })); return transaction(db, async (trx) => { const trxService = new DeploymentProjectsService({ accountability: this.accountability, schema: this.schema, knex: trx }); if (deleteIds.length > 0) await trxService.deleteMany(deleteIds); if (enrichedCreate.length > 0) await trxService.createMany(enrichedCreate.map((p) => ({ deployment: config.id, external_id: p.external_id, name: p.name, url: p.url, framework: p.framework, deployable: p.deployable }))); return trxService.readByQuery({ filter: { deployment: { _eq: config.id } }, limit: -1 }); }); } /** * Read deployment config by provider (null accountability for internal use). */ async readConfig(provider) { const results = await new ItemsService("directus_deployments", { knex: this.knex, schema: this.schema, accountability: null }).readByQuery({ filter: { provider: { _eq: provider } }, limit: 1 }); if (!results || results.length === 0) throw new Error(`Deployment config for "${provider}" not found`); return results[0]; } createDriver(config) { const credentials = parseValue(config.credentials, {}); const options = parseValue(config.options, {}); return getDeploymentDriver(config.provider, credentials, options); } }; //#endregion export { DeploymentProjectsService };