fix: 🐛 vercel no unique resource index

This commit is contained in:
amianthus
2026-05-28 13:35:50 +01:00
parent 3bccf848c0
commit 82508a7eed
6 changed files with 7038 additions and 21 deletions

View File

@@ -11,6 +11,7 @@ import { DrizzleError } from "drizzle-orm";
import { StatusCodes } from "http-status-codes";
import type Stripe from "stripe";
import { z } from "zod/v4";
import { isUniqueConstraintError } from "@/db/dbUtils.js";
import { createStripeCli } from "@/external/connect/createStripeCli.js";
import { sendCustomSvixEvent } from "@/external/svix/svixHelpers.js";
import { provisionVercelCusProduct } from "@/external/vercel/misc/vercelProvisioning.js";
@@ -185,18 +186,39 @@ export const handleCreateResource = createRoute({
}
const resourceId = generateId("vre");
await VercelResourceService.create({
db,
resource: {
id: resourceId,
org_id: orgId,
env: appEnv,
installation_id: integrationConfigurationId,
name,
status: "ready",
metadata: metadata ?? {},
},
});
try {
await VercelResourceService.create({
db,
resource: {
id: resourceId,
org_id: orgId,
env: appEnv,
installation_id: integrationConfigurationId,
name,
status: "ready",
metadata: metadata ?? {},
},
});
} catch (error) {
// Lost the create race against a concurrent request for the same
// (installation_id, name). Re-read and return the winner idempotently.
if (!isUniqueConstraintError(error)) throw error;
const winner =
await VercelResourceService.getByInstallationAndName({
db,
installationId: integrationConfigurationId,
name,
orgId,
env: appEnv,
});
if (!winner) throw error;
const product = installationCusProduct
? await loadProduct(installationCusProduct.product_id)
: await loadProduct(billingPlanId);
return c.json(
buildResourceResponse({ resourceId: winner.id, product }),
);
}
const product = installationCusProduct
? await loadProduct(installationCusProduct.product_id)

View File

@@ -1,6 +1,7 @@
import { type AppEnv, RecaseError, Scopes } from "@autumn/shared";
import { StatusCodes } from "http-status-codes";
import { z } from "zod/v4";
import { isUniqueConstraintError } from "@/db/dbUtils.js";
import { VercelResourceService } from "@/external/vercel/services/VercelResourceService.js";
import { createRoute } from "@/honoMiddlewares/routeHandler.js";
@@ -65,14 +66,26 @@ export const handleUpdateResource = createRoute({
console.log(`Vercel, updating resource: `, body);
const resource = await VercelResourceService.update({
db,
resourceId,
installationId: integrationConfigurationId,
orgId,
env: env as AppEnv,
updates,
});
let resource: Awaited<ReturnType<typeof VercelResourceService.update>>;
try {
resource = await VercelResourceService.update({
db,
resourceId,
installationId: integrationConfigurationId,
orgId,
env: env as AppEnv,
updates,
});
} catch (error) {
if (isUniqueConstraintError(error) && body.name) {
throw new RecaseError({
message: `A resource named "${body.name}" already exists for this installation`,
code: "vercel_resource_name_taken",
statusCode: StatusCodes.CONFLICT,
});
}
throw error;
}
return c.json({
id: resource.id,

View File

@@ -0,0 +1,25 @@
CREATE TABLE "passkey" (
"id" text PRIMARY KEY NOT NULL,
"name" text,
"public_key" text NOT NULL,
"user_id" text NOT NULL,
"credential_id" text NOT NULL,
"counter" integer NOT NULL,
"device_type" text NOT NULL,
"backed_up" boolean NOT NULL,
"transports" text,
"created_at" timestamp with time zone,
"aaguid" text,
CONSTRAINT "passkey_credential_id_unique" UNIQUE("credential_id")
);
--> statement-breakpoint
ALTER TABLE "passkey" ENABLE ROW LEVEL SECURITY;--> statement-breakpoint
ALTER TABLE "migration_runs" ADD COLUMN "target_limit" numeric;--> statement-breakpoint
ALTER TABLE "passkey" ADD CONSTRAINT "passkey_user_id_user_id_fk" FOREIGN KEY ("user_id") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "passkey_userId_idx" ON "passkey" USING btree ("user_id");--> statement-breakpoint
CREATE INDEX "passkey_credentialId_idx" ON "passkey" USING btree ("credential_id");--> statement-breakpoint
CREATE INDEX "idx_customer_products_revenuecat_processor" ON "customer_products" USING btree ("internal_customer_id") WHERE ("customer_products"."processor" ->> 'type') = 'revenuecat';--> statement-breakpoint
CREATE INDEX "idx_customers_cursor" ON "customers" USING btree ("org_id","env","created_at" DESC,"id" DESC);--> statement-breakpoint
CREATE INDEX "idx_entities_cursor" ON "entities" USING btree ("org_id","env","created_at" DESC,"id" DESC);--> statement-breakpoint
CREATE INDEX "idx_entitlements_internal_reward_id_c_partial" ON "entitlements" USING btree ("internal_reward_id" COLLATE "C") WHERE "entitlements"."internal_reward_id" IS NOT NULL;--> statement-breakpoint
CREATE UNIQUE INDEX "vercel_resources_installation_name_unique_idx" ON "vercel_resources" USING btree ("org_id","env","installation_id","name") WHERE status <> 'uninstalled';

File diff suppressed because it is too large Load Diff

View File

@@ -8,6 +8,13 @@
"when": 1779096275848,
"tag": "0000_bumpy_tinkerer",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1779971507895,
"tag": "0001_concerned_ravenous",
"breakpoints": true
}
]
}

View File

@@ -1,5 +1,12 @@
import { organizations } from "@models/orgModels/orgTable.js";
import { foreignKey, jsonb, pgTable, text } from "drizzle-orm/pg-core";
import { sql } from "drizzle-orm";
import {
foreignKey,
jsonb,
pgTable,
text,
uniqueIndex,
} from "drizzle-orm/pg-core";
export const vercelResources = pgTable(
"vercel_resources",
@@ -18,6 +25,11 @@ export const vercelResources = pgTable(
foreignColumns: [organizations.id],
name: "vercel_resources_org_id_fkey",
}).onDelete("cascade"),
// Partial unique index: enforces one live name per installation.
// Excludes soft-deleted rows so a name can be reused after uninstall.
uniqueIndex("vercel_resources_installation_name_unique_idx")
.on(table.org_id, table.env, table.installation_id, table.name)
.where(sql`status <> 'uninstalled'`),
],
);