import { customAlphabet } from "nanoid"; import { $transaction, prisma, PrismaClient } from "~/db.server"; import { env } from "~/env.server"; import { AuthenticatedEnvironment } from "../apiAuth.server"; import { workerQueue } from "../worker.server"; import { CreateEndpointError } from "./createEndpoint.server"; import { EndpointApi } from "../endpointApi.server"; import { RuntimeEnvironmentType } from "~/database-types"; const indexingHookIdentifier = customAlphabet("0123456789abcdefghijklmnopqrstuvxyz", 10); export class ValidateCreateEndpointService { #prismaClient: PrismaClient; constructor(prismaClient: PrismaClient = prisma) { this.#prismaClient = prismaClient; } public async call({ environment, url }: { environment: AuthenticatedEnvironment; url: string }) { const endpointUrl = this.#normalizeEndpointUrl(url); const client = new EndpointApi(environment.apiKey, endpointUrl); const validationResult = await client.validate(); if (!validationResult.ok) { throw new Error(validationResult.error); } try { const result = await $transaction(this.#prismaClient, async (tx) => { const endpoint = await tx.endpoint.upsert({ where: { environmentId_slug: { environmentId: environment.id, slug: validationResult.endpointId, }, }, include: { environment: true, }, create: { environment: { connect: { id: environment.id, }, }, organization: { connect: { id: environment.organizationId, }, }, project: { connect: { id: environment.projectId, }, }, slug: validationResult.endpointId, url: endpointUrl, indexingHookIdentifier: indexingHookIdentifier(), version: validationResult.triggerVersion, }, update: { url: endpointUrl, version: validationResult.triggerVersion, }, }); const index = await tx.endpointIndex.create({ data: { endpointId: endpoint.id, status: "PENDING", source: "INTERNAL" }, }); // Kick off process to fetch the jobs for this index await workerQueue.enqueue( "performEndpointIndexing", { id: index.id, }, { tx, maxAttempts: endpoint.environment.type === RuntimeEnvironmentType.DEVELOPMENT ? 1 : undefined, } ); return endpoint; }); return result; } catch (error) { if (error instanceof Error) { throw new CreateEndpointError("FAILED_UPSERT", error.message); } else { throw new CreateEndpointError("FAILED_UPSERT", "Something went wrong"); } } } // If the endpoint URL points to localhost, and the RUNTIME_PLATFORM is docker-compose, then we need to rewrite the host to host.docker.internal // otherwise we shouldn't change anything #normalizeEndpointUrl(url: string) { if (env.RUNTIME_PLATFORM === "docker-compose") { const urlObj = new URL(url); if (urlObj.hostname === "localhost") { urlObj.hostname = "host.docker.internal"; return urlObj.toString(); } } return url; } }