From 83df9e24b6f076ac509b32c9836c703abe0cb90d Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 4 Aug 2026 09:02:33 +0000 Subject: [PATCH 1/4] Show build server in build details Amp-Thread-ID: https://ampcode.com/threads/T-019fc6e3-ac3b-77e6-928a-10b57adca428 Co-authored-by: Arjun Komath --- .../[serviceId]/builds/[buildId]/page.tsx | 21 +++++++++--- web/app/api/builds/[buildId]/route.ts | 15 ++++++--- web/components/builds/build-details.tsx | 32 ++++++++++++++----- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/builds/[buildId]/page.tsx b/web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/builds/[buildId]/page.tsx index 8a816bc3..bd9f9de5 100644 --- a/web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/builds/[buildId]/page.tsx +++ b/web/app/(dashboard)/dashboard/projects/[slug]/[env]/services/[serviceId]/builds/[buildId]/page.tsx @@ -3,7 +3,13 @@ import { notFound } from "next/navigation"; import { BuildDetails } from "@/components/builds/build-details"; import { SetBreadcrumbs } from "@/components/core/breadcrumb-data"; import { db } from "@/db"; -import { builds, projects, serviceRevisions, services } from "@/db/schema"; +import { + builds, + projects, + servers, + serviceRevisions, + services, +} from "@/db/schema"; import { parseServiceRevisionSpec } from "@/lib/service-revision-changes"; async function getBuild( @@ -27,13 +33,18 @@ async function getBuild( if (!service) return null; - const build = await db - .select() + const buildData = await db + .select({ + build: builds, + server: { id: servers.id, name: servers.name }, + }) .from(builds) + .leftJoin(servers, eq(builds.claimedBy, servers.id)) .where(and(eq(builds.id, buildId), eq(builds.serviceId, serviceId))) .then((r) => r[0]); - if (!build) return null; + if (!buildData) return null; + const { build, server } = buildData; const revision = await db .select({ specification: serviceRevisions.specification }) @@ -63,6 +74,7 @@ async function getBuild( project, service, build, + server, githubRepo, }; } @@ -108,6 +120,7 @@ export default async function BuildPage({ envName={env} service={data.service} build={data.build} + server={data.server} githubRepo={data.githubRepo} /> diff --git a/web/app/api/builds/[buildId]/route.ts b/web/app/api/builds/[buildId]/route.ts index bbe0b677..420e93cf 100644 --- a/web/app/api/builds/[buildId]/route.ts +++ b/web/app/api/builds/[buildId]/route.ts @@ -1,7 +1,7 @@ import { eq } from "drizzle-orm"; import { type NextRequest, NextResponse } from "next/server"; import { db } from "@/db"; -import { builds } from "@/db/schema"; +import { builds, servers } from "@/db/schema"; export async function GET( _: NextRequest, @@ -9,11 +9,18 @@ export async function GET( ) { const { buildId } = await params; - const [build] = await db.select().from(builds).where(eq(builds.id, buildId)); + const [buildData] = await db + .select({ + build: builds, + server: { id: servers.id, name: servers.name }, + }) + .from(builds) + .leftJoin(servers, eq(builds.claimedBy, servers.id)) + .where(eq(builds.id, buildId)); - if (!build) { + if (!buildData) { return NextResponse.json({ error: "Build not found" }, { status: 404 }); } - return NextResponse.json({ build }); + return NextResponse.json(buildData); } diff --git a/web/components/builds/build-details.tsx b/web/components/builds/build-details.tsx index b80b335b..f8d4b060 100644 --- a/web/components/builds/build-details.tsx +++ b/web/components/builds/build-details.tsx @@ -42,6 +42,8 @@ type BuildWithDates = Omit< claimedAt: string | Date | null; }; +type BuildServer = { id: string; name: string }; + const STATUS_CONFIG: Record< BuildStatus, { @@ -97,28 +99,31 @@ export function BuildDetails({ envName, service, build: initialBuild, + server: initialServer, githubRepo, }: { projectSlug: string; envName: string; service: Pick; build: BuildWithDates; + server: BuildServer | null; githubRepo: { repoFullName: string } | null; }) { const router = useRouter(); const [isCancelling, setIsCancelling] = useState(false); const [isRetrying, setIsRetrying] = useState(false); - const { data } = useSWR<{ build: BuildWithDates }>( - `/api/builds/${initialBuild.id}`, - fetcher, - { - fallbackData: { build: initialBuild }, - refreshInterval: isActiveBuild(initialBuild.status) ? 1500 : 0, - }, - ); + const { data } = useSWR<{ + build: BuildWithDates; + server: BuildServer | null; + }>(`/api/builds/${initialBuild.id}`, fetcher, { + fallbackData: { build: initialBuild, server: initialServer }, + refreshInterval: (latestData) => + isActiveBuild(latestData?.build.status ?? initialBuild.status) ? 1500 : 0, + }); const build = data?.build || initialBuild; + const server = data ? data.server : initialServer; const config = STATUS_CONFIG[build.status]; const isAnimated = ["cloning", "building", "pushing"].includes(build.status); @@ -257,6 +262,17 @@ export function BuildDetails({ + {server && ( +
+ Build server + + {server.name} + +
+ )}
Created {formatRelativeTime(build.createdAt)} From bc8a34e7321c73bab1a21c20168f0075620c42fd Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 4 Aug 2026 09:15:02 +0000 Subject: [PATCH 2/4] Move build server into commit metadata Amp-Thread-ID: https://ampcode.com/threads/T-019fc6e3-ac3b-77e6-928a-10b57adca428 Co-authored-by: Arjun Komath --- web/components/builds/build-details.tsx | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/web/components/builds/build-details.tsx b/web/components/builds/build-details.tsx index f8d4b060..ca17f31d 100644 --- a/web/components/builds/build-details.tsx +++ b/web/components/builds/build-details.tsx @@ -249,30 +249,30 @@ export function BuildDetails({ {build.commitMessage?.split("\n")[0] || "No message"} -
+
{build.branch} {build.author && by {build.author}} -
- - - - - - {server && ( -
- Build server + + on{" "} {server.name} -
+ )} +
+ + + + + +
Created {formatRelativeTime(build.createdAt)} From e063e498726368781bbafa2e555117cf5f22cd51 Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 4 Aug 2026 09:16:42 +0000 Subject: [PATCH 3/4] Show build server in build rows Amp-Thread-ID: https://ampcode.com/threads/T-019fc6e3-ac3b-77e6-928a-10b57adca428 Co-authored-by: Arjun Komath --- web/app/api/services/[id]/builds/route.ts | 14 +++++++++----- web/components/builds/builds-viewer.tsx | 16 +++++++++++++++- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/web/app/api/services/[id]/builds/route.ts b/web/app/api/services/[id]/builds/route.ts index aabecb7f..cae4f41c 100644 --- a/web/app/api/services/[id]/builds/route.ts +++ b/web/app/api/services/[id]/builds/route.ts @@ -1,17 +1,21 @@ -import { NextRequest, NextResponse } from "next/server"; -import { desc, eq } from "drizzle-orm"; +import { desc, eq, getTableColumns } from "drizzle-orm"; +import { type NextRequest, NextResponse } from "next/server"; import { db } from "@/db"; -import { builds } from "@/db/schema"; +import { builds, servers } from "@/db/schema"; export async function GET( - request: NextRequest, + _request: NextRequest, { params }: { params: Promise<{ id: string }> }, ) { const { id: serviceId } = await params; const buildsList = await db - .select() + .select({ + ...getTableColumns(builds), + server: { id: servers.id, name: servers.name }, + }) .from(builds) + .leftJoin(servers, eq(builds.claimedBy, servers.id)) .where(eq(builds.serviceId, serviceId)) .orderBy(desc(builds.createdAt)) .limit(50); diff --git a/web/components/builds/builds-viewer.tsx b/web/components/builds/builds-viewer.tsx index 3c0aa718..dcf773d6 100644 --- a/web/components/builds/builds-viewer.tsx +++ b/web/components/builds/builds-viewer.tsx @@ -13,6 +13,7 @@ import { RotateCcw, XCircle, } from "lucide-react"; +import Link from "next/link"; import { useRouter } from "next/navigation"; import { useState } from "react"; import { toast } from "sonner"; @@ -61,6 +62,7 @@ type BuildListItem = Pick< createdAt: string; startedAt: string | null; completedAt: string | null; + server: { id: string; name: string } | null; }; type Commit = { @@ -411,10 +413,22 @@ export function BuildsViewer({ {build.branch} {build.author && ( - + by {build.author} )} + {build.server && ( + + on{" "} + event.stopPropagation()} + > + {build.server.name} + + + )} {formatRelativeTime(build.createdAt)} From c13a7b8e4421e61259adcd2643bef000742e502d Mon Sep 17 00:00:00 2001 From: Amp Date: Tue, 4 Aug 2026 09:33:53 +0000 Subject: [PATCH 4/4] Require authentication for build APIs Amp-Thread-ID: https://ampcode.com/threads/T-019fc6e3-ac3b-77e6-928a-10b57adca428 Co-authored-by: Arjun Komath --- web/app/api/builds/[buildId]/route.ts | 6 +++++- web/app/api/services/[id]/builds/route.ts | 6 +++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/web/app/api/builds/[buildId]/route.ts b/web/app/api/builds/[buildId]/route.ts index 420e93cf..16237543 100644 --- a/web/app/api/builds/[buildId]/route.ts +++ b/web/app/api/builds/[buildId]/route.ts @@ -2,11 +2,15 @@ import { eq } from "drizzle-orm"; import { type NextRequest, NextResponse } from "next/server"; import { db } from "@/db"; import { builds, servers } from "@/db/schema"; +import { requireRequestSession } from "@/lib/api-auth"; export async function GET( - _: NextRequest, + request: NextRequest, { params }: { params: Promise<{ buildId: string }> }, ) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) return sessionResult.response; + const { buildId } = await params; const [buildData] = await db diff --git a/web/app/api/services/[id]/builds/route.ts b/web/app/api/services/[id]/builds/route.ts index cae4f41c..2e9c617e 100644 --- a/web/app/api/services/[id]/builds/route.ts +++ b/web/app/api/services/[id]/builds/route.ts @@ -2,11 +2,15 @@ import { desc, eq, getTableColumns } from "drizzle-orm"; import { type NextRequest, NextResponse } from "next/server"; import { db } from "@/db"; import { builds, servers } from "@/db/schema"; +import { requireRequestSession } from "@/lib/api-auth"; export async function GET( - _request: NextRequest, + request: NextRequest, { params }: { params: Promise<{ id: string }> }, ) { + const sessionResult = await requireRequestSession(request); + if (!sessionResult.ok) return sessionResult.response; + const { id: serviceId } = await params; const buildsList = await db