Skip to content

Commit 38826e6

Browse files
committed
feat: add unpublish functionality for domains
- Add unpublish button for project domain and custom domains - Call edge-api deployment.unpublish to delete worker - Remove domain from all builds in DB (not just latest) - Disable domain renaming only when that specific domain is published - Add PUBLISHER_HOST to deployment context for domain suffix handling - Add error details to unpublish error messages
1 parent 939b94f commit 38826e6

File tree

3 files changed

+80
-48
lines changed

3 files changed

+80
-48
lines changed

packages/domain/src/trpc/domain.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -185,19 +185,20 @@ export const domainRouter = router({
185185
if (result.success === false && result.error !== "NOT_IMPLEMENTED") {
186186
return {
187187
success: false,
188-
message: `Failed to unpublish ${input.domain}`,
189-
} as const;
188+
message: `Failed to unpublish ${input.domain}: ${result.error}`,
189+
};
190190
}
191191

192192
return {
193193
success: true,
194194
message: `${input.domain} unpublished`,
195-
} as const;
195+
};
196196
} catch (error) {
197+
console.error("Unpublish failed:", error);
197198
return {
198199
success: false,
199-
message: `Failed to unpublish ${input.domain}`,
200-
} as const;
200+
message: `Failed to unpublish ${input.domain}: ${error instanceof Error ? error.message : "Unknown error"}`,
201+
};
201202
}
202203
}),
203204
/**

packages/project-build/src/db/build.ts

Lines changed: 40 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,7 @@ export const unpublishBuild = async (
247247
);
248248
}
249249

250-
// Find the build that has this domain in its deployment
250+
// Find all builds that have this domain in their deployment
251251
const buildsResult = await context.postgrest.client
252252
.from("Build")
253253
.select("id, deployment")
@@ -259,8 +259,8 @@ export const unpublishBuild = async (
259259
throw buildsResult.error;
260260
}
261261

262-
// Find the build with this specific domain in deployment.domains
263-
const targetBuild = buildsResult.data.find((build) => {
262+
// Find all builds with this specific domain in deployment.domains
263+
const targetBuilds = buildsResult.data.filter((build) => {
264264
const deployment = parseDeployment(build.deployment);
265265
if (deployment === undefined) {
266266
return false;
@@ -271,44 +271,49 @@ export const unpublishBuild = async (
271271
return deployment.domains.includes(props.domain);
272272
});
273273

274-
if (targetBuild === undefined) {
274+
if (targetBuilds.length === 0) {
275275
throw new Error(`Domain ${props.domain} is not published`);
276276
}
277277

278-
const deployment = parseDeployment(targetBuild.deployment);
278+
// Process all builds that contain this domain
279+
for (const targetBuild of targetBuilds) {
280+
const deployment = parseDeployment(targetBuild.deployment);
279281

280-
if (deployment === undefined || deployment.destination !== "saas") {
281-
throw new Error("Build is not published to SaaS");
282-
}
283-
284-
// Remove the domain from the deployment
285-
const remainingDomains = deployment.domains.filter((d) => d !== props.domain);
282+
if (deployment === undefined || deployment.destination !== "saas") {
283+
continue;
284+
}
286285

287-
if (remainingDomains.length === 0) {
288-
// Delete the production build entirely when no domains remain
289-
// Don't set deployment=null as that would create a duplicate "dev build"
290-
const result = await context.postgrest.client
291-
.from("Build")
292-
.delete()
293-
.eq("id", targetBuild.id);
286+
// Remove the domain from the deployment
287+
const remainingDomains = deployment.domains.filter(
288+
(d) => d !== props.domain
289+
);
294290

295-
if (result.error) {
296-
throw result.error;
297-
}
298-
} else {
299-
// Update with remaining domains
300-
const newDeployment = JSON.stringify({
301-
...deployment,
302-
domains: remainingDomains,
303-
});
304-
305-
const result = await context.postgrest.client
306-
.from("Build")
307-
.update({ deployment: newDeployment })
308-
.eq("id", targetBuild.id);
309-
310-
if (result.error) {
311-
throw result.error;
291+
if (remainingDomains.length === 0) {
292+
// Delete the production build entirely when no domains remain
293+
// Don't set deployment=null as that would create a duplicate "dev build"
294+
const result = await context.postgrest.client
295+
.from("Build")
296+
.delete()
297+
.eq("id", targetBuild.id);
298+
299+
if (result.error) {
300+
throw result.error;
301+
}
302+
} else {
303+
// Update with remaining domains
304+
const newDeployment = JSON.stringify({
305+
...deployment,
306+
domains: remainingDomains,
307+
});
308+
309+
const result = await context.postgrest.client
310+
.from("Build")
311+
.update({ deployment: newDeployment })
312+
.eq("id", targetBuild.id);
313+
314+
if (result.error) {
315+
throw result.error;
316+
}
312317
}
313318
}
314319
};

packages/project/src/db/project.ts

Lines changed: 34 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -269,20 +269,46 @@ export const updateDomain = async (
269269

270270
await assertEditPermission(input.id, context);
271271

272-
// Check if project has been published - forbid renaming wstd domain after publishing
273-
const projectData = await context.postgrest.client
274-
.from("DashboardProject")
275-
.select("isPublished")
272+
// Check if the current wstd domain is published - forbid renaming while published
273+
// Get current domain first
274+
const currentProject = await context.postgrest.client
275+
.from("Project")
276+
.select("domain")
276277
.eq("id", input.id)
277278
.single();
278279

279-
if (projectData.error) {
280-
throw projectData.error;
280+
if (currentProject.error) {
281+
throw currentProject.error;
282+
}
283+
284+
// Check if any build has this domain in deployment.domains
285+
const buildsWithDomain = await context.postgrest.client
286+
.from("Build")
287+
.select("id, deployment")
288+
.eq("projectId", input.id)
289+
.not("deployment", "is", null);
290+
291+
if (buildsWithDomain.error) {
292+
throw buildsWithDomain.error;
281293
}
282294

283-
if (projectData.data.isPublished) {
295+
const isDomainPublished = buildsWithDomain.data.some((build) => {
296+
const deployment = build.deployment as {
297+
destination?: string;
298+
domains?: string[];
299+
} | null;
300+
if (deployment === null) {
301+
return false;
302+
}
303+
if (deployment.destination === "static") {
304+
return false;
305+
}
306+
return deployment.domains?.includes(currentProject.data.domain) ?? false;
307+
});
308+
309+
if (isDomainPublished) {
284310
throw new Error(
285-
"Cannot change domain after the project has been published"
311+
"Cannot change domain while it is published. Unpublish first."
286312
);
287313
}
288314

0 commit comments

Comments
 (0)