fix: 🐛 vercel no unique resource index
This commit is contained in:
@@ -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)
|
||||
|
||||
@@ -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,
|
||||
|
||||
25
shared/drizzle/0001_concerned_ravenous.sql
Normal file
25
shared/drizzle/0001_concerned_ravenous.sql
Normal 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';
|
||||
6938
shared/drizzle/meta/0001_snapshot.json
Normal file
6938
shared/drizzle/meta/0001_snapshot.json
Normal file
File diff suppressed because it is too large
Load Diff
@@ -8,6 +8,13 @@
|
||||
"when": 1779096275848,
|
||||
"tag": "0000_bumpy_tinkerer",
|
||||
"breakpoints": true
|
||||
},
|
||||
{
|
||||
"idx": 1,
|
||||
"version": "7",
|
||||
"when": 1779971507895,
|
||||
"tag": "0001_concerned_ravenous",
|
||||
"breakpoints": true
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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'`),
|
||||
],
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user