diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/deployments/components/table/components/domain_list.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/deployments/components/table/components/domain_list.tsx index 3e27dc6451..ba449b3e72 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/deployments/components/table/components/domain_list.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/deployments/components/table/components/domain_list.tsx @@ -3,24 +3,21 @@ import { useProjectLayout } from "../../../../layout-provider"; type Props = { deploymentId: string; - // I couldn't figure out how to make the domains revalidate on a rollback - // From my understanding it should already work, because we're using the - // .util.refetch() in the trpc mutation, but it doesn't. - // We need to investigate this later - hackyRevalidateDependency?: unknown; }; -export const DomainList = ({ deploymentId, hackyRevalidateDependency }: Props) => { +export const DomainList = ({ deploymentId }: Props) => { const { collections } = useProjectLayout(); - const domains = useLiveQuery( - (q) => - q - .from({ domain: collections.domains }) - .where(({ domain }) => eq(domain.deploymentId, deploymentId)) - .orderBy(({ domain }) => domain.domain, "asc"), - [hackyRevalidateDependency], + const domains = useLiveQuery((q) => + q + .from({ domain: collections.domains }) + .where(({ domain }) => eq(domain.deploymentId, deploymentId)) + .orderBy(({ domain }) => domain.domain, "asc"), ); + if (domains.isLoading || !domains.data.length) { + return ; + } + return ( ); }; + +const DomainListSkeleton = () => ( + +); diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/deployments/components/table/deployments-list.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/deployments/components/table/deployments-list.tsx index f4534cad6a..84777b79cb 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/deployments/components/table/deployments-list.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/deployments/components/table/deployments-list.tsx @@ -4,12 +4,13 @@ import type { Column } from "@/components/virtual-table/types"; import { useIsMobile } from "@/hooks/use-mobile"; import type { Deployment, Environment } from "@/lib/collections"; import { shortenId } from "@/lib/shorten-id"; -import { BookBookmark, Cloud, CodeBranch, Cube } from "@unkey/icons"; +import { BookBookmark, CodeBranch, Cube } from "@unkey/icons"; import { Button, Empty, TimestampInfo } from "@unkey/ui"; import { cn } from "@unkey/ui/src/lib/utils"; import dynamic from "next/dynamic"; import { useMemo, useState } from "react"; import { Avatar } from "../../../details/active-deployment-card/git-avatar"; +import { StatusIndicator } from "../../../details/active-deployment-card/status-indicator"; import { useDeployments } from "../../hooks/use-deployments"; import { DeploymentStatusBadge } from "./components/deployment-status-badge"; import { DomainList } from "./components/domain_list"; @@ -64,18 +65,7 @@ export const DeploymentsList = () => { headerClassName: "pl-[18px]", render: ({ deployment, environment }) => { const isLive = liveDeployment?.id === deployment.id; - const isSelected = deployment.id === selectedDeployment?.deployment.id; - const iconContainer = ( -
- -
- ); + const iconContainer = ; return (
@@ -127,7 +117,6 @@ export const DeploymentsList = () => { ), }, @@ -237,13 +226,13 @@ export const DeploymentsList = () => {
- {deployment.gitCommitAuthorUsername} + {deployment.gitCommitAuthorHandle}
@@ -283,10 +272,10 @@ export const DeploymentsList = () => {
- {deployment.gitCommitAuthorName} + {deployment.gitCommitAuthorHandle}
); diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/git-avatar.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/git-avatar.tsx index 72c9d0ed49..b7b9ca5c3d 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/git-avatar.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/git-avatar.tsx @@ -1,3 +1,4 @@ +import { cn } from "@/lib/utils"; import { User } from "@unkey/icons"; import { useState } from "react"; @@ -7,12 +8,17 @@ type AvatarProps = { className?: string; }; -export function Avatar({ src, alt, className = "size-5" }: AvatarProps) { +export function Avatar({ src, alt, className }: AvatarProps) { const [hasError, setHasError] = useState(false); if (!src || hasError) { return ( -
+
); @@ -22,7 +28,7 @@ export function Avatar({ src, alt, className = "size-5" }: AvatarProps) { {alt} setHasError(true)} /> ); diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/index.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/index.tsx index f82e7f4dec..d471fdf012 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/index.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/active-deployment-card/index.tsx @@ -135,7 +135,7 @@ export const ActiveDeploymentCard = ({ deploymentId }: Props) => { Created by - {deployment.gitCommitAuthorName} + {deployment.gitCommitAuthorHandle}
diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/project-details-expandables/index.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/project-details-expandables/index.tsx index 972ef07cb6..4eb0b5cce6 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/project-details-expandables/index.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/project-details-expandables/index.tsx @@ -33,12 +33,31 @@ export const ProjectDetailsExpandable = ({ ); const data = query.data.at(0); + const { data: domainsData } = useLiveQuery( + (q) => + q + .from({ domain: collections.domains }) + .where(({ domain }) => eq(domain.deploymentId, data?.project.liveDeploymentId)) + .select(({ domain }) => ({ + domain: domain.domain, + environment: domain.sticky, + })) + .orderBy(({ domain }) => domain.id, "asc"), + [data?.project.liveDeploymentId], + ); if (!data?.deployment) { return null; } - const detailSections = createDetailSections(data.deployment); + const detailSections = createDetailSections({ + ...data.deployment, + repository: data.project.gitRepositoryUrl, + }); + + // This "environment" domain never changes even when you do a rollback this one stays stable. + const mainDomain = domainsData.find((d) => d.environment === "environment")?.domain; + const gitShaAndBranchNameDomains = domainsData.filter((d) => d.environment !== "environment"); return (
@@ -101,35 +120,38 @@ export const ProjectDetailsExpandable = ({
- dashboard -
+ + {data.project.name} + +
+ {/* # is okay. This section is not accessible without deploy*/} - api.gateway.com + {mainDomain} - {["staging.gateway.com", "dev.gateway.com"].map((region) => ( +
+ {gitShaAndBranchNameDomains.map((d) => (
-
+ ))} @@ -137,7 +159,7 @@ export const ProjectDetailsExpandable = ({ } >
- +2 + +{gitShaAndBranchNameDomains.length}
diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/project-details-expandables/sections.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/project-details-expandables/sections.tsx index 8a497e61d7..8148319503 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/project-details-expandables/sections.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/[projectId]/details/project-details-expandables/sections.tsx @@ -16,6 +16,7 @@ import { } from "@unkey/icons"; import { Badge, TimestampInfo } from "@unkey/ui"; import type { ReactNode } from "react"; +import { RepoDisplay } from "../../../_components/list/repo-display"; import { Avatar } from "../active-deployment-card/git-avatar"; export type DetailItem = { @@ -30,7 +31,9 @@ export type DetailSection = { items: DetailItem[]; }; -export const createDetailSections = (details: Deployment): DetailSection[] => [ +export const createDetailSections = ( + details: Deployment & { repository: string | null }, +): DetailSection[] => [ { title: "Active deployment", items: [ @@ -38,9 +41,11 @@ export const createDetailSections = (details: Deployment): DetailSection[] => [ icon: , label: "Repository", content: ( -
- TODO/ TODO -
+ ), }, { @@ -75,9 +80,9 @@ export const createDetailSections = (details: Deployment): DetailSection[] => [
- {details.gitCommitAuthorUsername} + {details.gitCommitAuthorHandle}
), }, diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/create-project/create-project-dialog.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/create-project/create-project-dialog.tsx index 4323f0ea1e..949976e6cc 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/create-project/create-project-dialog.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/create-project/create-project-dialog.tsx @@ -44,6 +44,7 @@ export const CreateProjectDialog = () => { updatedAt: null, id: "will-be-replace-by-server", author: "will-be-replace-by-server", + authorAvatar: "will-be-replace-by-server", branch: "will-be-replace-by-server", commitTimestamp: Date.now(), commitTitle: "will-be-replace-by-server", diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/index.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/index.tsx index b4ee4ef028..5d72463ba3 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/index.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/index.tsx @@ -85,6 +85,7 @@ export const ProjectsList = () => { commitTimestamp={project.commitTimestamp} branch={project.branch} author={project.author} + authorAvatar={project.authorAvatar} regions={project.regions} repository={project.gitRepositoryUrl || undefined} actions={ diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/projects-card.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/projects-card.tsx index b19979c8a7..6b275fb142 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/projects-card.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/projects-card.tsx @@ -1,18 +1,20 @@ import { useWorkspaceNavigation } from "@/hooks/use-workspace-navigation"; -import { CodeBranch, Cube, User } from "@unkey/icons"; +import { CodeBranch, Cube } from "@unkey/icons"; import { InfoTooltip, Loading, TimestampInfo } from "@unkey/ui"; import Link from "next/link"; import type { ReactNode } from "react"; import { useCallback, useState } from "react"; +import { Avatar } from "../../[projectId]/details/active-deployment-card/git-avatar"; import { RegionBadges } from "./region-badges"; type ProjectCardProps = { name: string; - domain: string; - commitTitle: string; + domain: string | null; + commitTitle: string | null; commitTimestamp?: number | null; branch: string; - author: string; + author: string | null; + authorAvatar: string | null; regions: string[]; repository?: string; actions: ReactNode; @@ -26,6 +28,7 @@ export const ProjectCard = ({ commitTimestamp, branch, author, + authorAvatar, regions, repository, actions, @@ -69,47 +72,60 @@ export const ProjectCard = ({ {/*Top Section > Domains/Hostnames*/} - - - {domain} - - + {domain && ( + + + {domain} + + + )}
{/*Top Section > Project actions*/}
{actions}
{/*Middle Section > Last commit title*/}
- - - {commitTitle} - - + {commitTitle ? ( + + + {commitTitle} + + + ) : ( +
No commit info
+ )} +
{commitTimestamp ? ( ) : ( - No deployments + + No deployments + )} {branch} - by -
- -
- - {author} - + {authorAvatar && ( + <> + by + + + + {author} + + + + )}
{/*Bottom Section > Regions*/} diff --git a/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/region-badges.tsx b/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/region-badges.tsx index 72b0cc00ca..4b704a3c56 100644 --- a/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/region-badges.tsx +++ b/apps/dashboard/app/(app)/[workspaceSlug]/projects/_components/list/region-badges.tsx @@ -13,7 +13,7 @@ export const RegionBadges = ({ regions, repository }: RegionBadgesProps) => { const remainingCount = remainingRegions.length; return ( -
+
{visibleRegions.map((region) => (
+ {showIcon && } {children || {repoName}} diff --git a/apps/dashboard/lib/collections/deploy/deployments.ts b/apps/dashboard/lib/collections/deploy/deployments.ts index 5bc7498674..3fa9c178c1 100644 --- a/apps/dashboard/lib/collections/deploy/deployments.ts +++ b/apps/dashboard/lib/collections/deploy/deployments.ts @@ -8,18 +8,12 @@ const schema = z.object({ id: z.string(), projectId: z.string(), environmentId: z.string(), - // Git information - // TEMP: Git fields as non-nullable for UI development with mock data - // TODO: Convert to nullable (.nullable()) when real git integration is added - // In production, deployments may not have git metadata if triggered manually - gitCommitSha: z.string(), + gitCommitSha: z.string().nullable(), gitBranch: z.string(), - gitCommitMessage: z.string(), - gitCommitAuthorName: z.string(), - gitCommitAuthorEmail: z.string(), - gitCommitAuthorUsername: z.string(), + gitCommitMessage: z.string().nullable(), + gitCommitAuthorHandle: z.string().nullable(), gitCommitAuthorAvatarUrl: z.string(), - gitCommitTimestamp: z.number().int(), + gitCommitTimestamp: z.number().int().nullable(), // Immutable configuration snapshot runtimeConfig: z.object({ regions: z.array( diff --git a/apps/dashboard/lib/collections/deploy/projects.ts b/apps/dashboard/lib/collections/deploy/projects.ts index 49bb31fd6b..fb34785e5f 100644 --- a/apps/dashboard/lib/collections/deploy/projects.ts +++ b/apps/dashboard/lib/collections/deploy/projects.ts @@ -13,13 +13,14 @@ const schema = z.object({ liveDeploymentId: z.string().nullable(), isRolledBack: z.boolean(), // Flattened deployment fields for UI - commitTitle: z.string(), + commitTitle: z.string().nullable(), branch: z.string(), - author: z.string(), + author: z.string().nullable(), + authorAvatar: z.string().nullable(), commitTimestamp: z.number().int().nullable(), regions: z.array(z.string()), // Domain field - domain: z.string(), + domain: z.string().nullable(), }); export const createProjectRequestSchema = z.object({ diff --git a/apps/dashboard/lib/trpc/routers/deploy/deployment/list.ts b/apps/dashboard/lib/trpc/routers/deploy/deployment/list.ts index 12ffe8657e..df2c6b237b 100644 --- a/apps/dashboard/lib/trpc/routers/deploy/deployment/list.ts +++ b/apps/dashboard/lib/trpc/routers/deploy/deployment/list.ts @@ -20,9 +20,7 @@ export const listDeployments = t.procedure gitCommitSha: true, gitBranch: true, gitCommitMessage: true, - gitCommitAuthorName: true, - gitCommitAuthorEmail: true, - gitCommitAuthorUsername: true, + gitCommitAuthorHandle: true, gitCommitAuthorAvatarUrl: true, gitCommitTimestamp: true, runtimeConfig: true, @@ -34,16 +32,10 @@ export const listDeployments = t.procedure return deployments.map((deployment) => ({ ...deployment, - // Replace NULL git fields with dummy data that clearly indicates it's fake - gitCommitSha: deployment.gitCommitSha ?? "abc123ef456789012345678901234567890abcdef", gitBranch: deployment.gitBranch ?? "main", - gitCommitMessage: deployment.gitCommitMessage ?? "[DUMMY] Initial commit", - gitCommitAuthorName: deployment.gitCommitAuthorName ?? "[DUMMY] Unknown Author", - gitCommitAuthorEmail: deployment.gitCommitAuthorEmail ?? "dummy@example.com", - gitCommitAuthorUsername: deployment.gitCommitAuthorUsername ?? "dummy-user", gitCommitAuthorAvatarUrl: deployment.gitCommitAuthorAvatarUrl ?? "https://github.com/identicons/dummy-user.png", - gitCommitTimestamp: deployment.gitCommitTimestamp ?? Date.now() - 86400000, + gitCommitTimestamp: deployment.gitCommitTimestamp, })); } catch (_error) { throw new TRPCError({ diff --git a/apps/dashboard/lib/trpc/routers/deploy/project/list.ts b/apps/dashboard/lib/trpc/routers/deploy/project/list.ts index 61c56f8e88..59ea4c6f7c 100644 --- a/apps/dashboard/lib/trpc/routers/deploy/project/list.ts +++ b/apps/dashboard/lib/trpc/routers/deploy/project/list.ts @@ -14,7 +14,8 @@ type ProjectRow = { is_rolled_back: boolean; git_commit_message: string | null; git_branch: string | null; - git_commit_author_name: string | null; + git_commit_author_handle: string | null; + git_commit_author_avatar_url: string | null; git_commit_timestamp: number | null; runtime_config: Deployment["runtimeConfig"] | null; domain: string | null; @@ -36,7 +37,8 @@ export const listProjects = t.procedure ${projects.isRolledBack}, ${deployments.gitCommitMessage}, ${deployments.gitBranch}, - ${deployments.gitCommitAuthorName}, + ${deployments.gitCommitAuthorHandle}, + ${deployments.gitCommitAuthorAvatarUrl}, ${deployments.gitCommitTimestamp}, ${deployments.runtimeConfig}, ${domains.domain} @@ -60,12 +62,13 @@ export const listProjects = t.procedure gitRepositoryUrl: row.git_repository_url, liveDeploymentId: row.live_deployment_id, isRolledBack: row.is_rolled_back, - commitTitle: row.git_commit_message ?? "[DUMMY] Initial commit", + commitTitle: row.git_commit_message, branch: row.git_branch ?? "main", - author: row.git_commit_author_name ?? "[DUMMY] Unknown Author", - commitTimestamp: row.git_commit_timestamp ?? Date.now() - 86400000, + author: row.git_commit_author_handle, + commitTimestamp: Number(row.git_commit_timestamp), + authorAvatar: row.git_commit_author_avatar_url, regions: row.runtime_config?.regions?.map((r) => r.region) ?? ["us-east-1"], - domain: row.domain ?? "project-temp.unkey.app", + domain: row.domain, }), ); }); diff --git a/go/apps/ctrl/services/deployment/create_deployment.go b/go/apps/ctrl/services/deployment/create_deployment.go index 83902e344e..60c1bb524b 100644 --- a/go/apps/ctrl/services/deployment/create_deployment.go +++ b/go/apps/ctrl/services/deployment/create_deployment.go @@ -105,8 +105,7 @@ func (s *Service) CreateDeployment( // Sanitize input values before persisting gitCommitSha := req.Msg.GetGitCommitSha() gitCommitMessage := trimLength(req.Msg.GetGitCommitMessage(), 10240) - gitCommitAuthorName := trimLength(strings.TrimSpace(req.Msg.GetGitCommitAuthorName()), 256) - gitCommitAuthorUsername := trimLength(strings.TrimSpace(req.Msg.GetGitCommitAuthorUsername()), 256) + gitCommitAuthorHandle := trimLength(strings.TrimSpace(req.Msg.GetGitCommitAuthorHandle()), 256) gitCommitAuthorAvatarUrl := trimLength(strings.TrimSpace(req.Msg.GetGitCommitAuthorAvatarUrl()), 512) // Insert deployment into database @@ -120,16 +119,14 @@ func (s *Service) CreateDeployment( "cpus": 2, "memory": 2048 }`), - OpenapiSpec: sql.NullString{String: "", Valid: false}, - Status: db.DeploymentsStatusPending, - CreatedAt: now, - UpdatedAt: sql.NullInt64{Int64: now, Valid: true}, - GitCommitSha: sql.NullString{String: gitCommitSha, Valid: gitCommitSha != ""}, - GitBranch: sql.NullString{String: gitBranch, Valid: true}, - GitCommitMessage: sql.NullString{String: gitCommitMessage, Valid: req.Msg.GetGitCommitMessage() != ""}, - GitCommitAuthorName: sql.NullString{String: gitCommitAuthorName, Valid: req.Msg.GetGitCommitAuthorName() != ""}, - // TODO: Use email to lookup GitHub username/avatar via GitHub API instead of persisting PII - GitCommitAuthorUsername: sql.NullString{String: gitCommitAuthorUsername, Valid: req.Msg.GetGitCommitAuthorUsername() != ""}, + OpenapiSpec: sql.NullString{String: "", Valid: false}, + Status: db.DeploymentsStatusPending, + CreatedAt: now, + UpdatedAt: sql.NullInt64{Int64: now, Valid: true}, + GitCommitSha: sql.NullString{String: gitCommitSha, Valid: gitCommitSha != ""}, + GitBranch: sql.NullString{String: gitBranch, Valid: true}, + GitCommitMessage: sql.NullString{String: gitCommitMessage, Valid: req.Msg.GetGitCommitMessage() != ""}, + GitCommitAuthorHandle: sql.NullString{String: gitCommitAuthorHandle, Valid: req.Msg.GetGitCommitAuthorHandle() != ""}, GitCommitAuthorAvatarUrl: sql.NullString{String: gitCommitAuthorAvatarUrl, Valid: req.Msg.GetGitCommitAuthorAvatarUrl() != ""}, GitCommitTimestamp: sql.NullInt64{Int64: req.Msg.GetGitCommitTimestamp(), Valid: req.Msg.GetGitCommitTimestamp() != 0}, }) diff --git a/go/apps/ctrl/services/deployment/create_deployment_simple_test.go b/go/apps/ctrl/services/deployment/create_deployment_simple_test.go index 0619420862..18b1cec9b4 100644 --- a/go/apps/ctrl/services/deployment/create_deployment_simple_test.go +++ b/go/apps/ctrl/services/deployment/create_deployment_simple_test.go @@ -70,27 +70,23 @@ func TestGitFieldValidation_SpecialCharacters(t *testing.T) { // Test that special characters are preserved in protobuf req := &ctrlv1.CreateDeploymentRequest{ GitCommitMessage: tt.input, - GitCommitAuthorName: tt.input, - GitCommitAuthorUsername: tt.input, + GitCommitAuthorHandle: tt.input, GitCommitAuthorAvatarUrl: tt.input, } require.Equal(t, tt.expected, req.GetGitCommitMessage()) - require.Equal(t, tt.expected, req.GetGitCommitAuthorName()) - require.Equal(t, tt.expected, req.GetGitCommitAuthorUsername()) + require.Equal(t, tt.expected, req.GetGitCommitAuthorHandle()) require.Equal(t, tt.expected, req.GetGitCommitAuthorAvatarUrl()) // Test that special characters are preserved in database model deployment := db.Deployment{ GitCommitMessage: sql.NullString{String: tt.input, Valid: true}, - GitCommitAuthorName: sql.NullString{String: tt.input, Valid: true}, - GitCommitAuthorUsername: sql.NullString{String: tt.input, Valid: true}, + GitCommitAuthorHandle: sql.NullString{String: tt.input, Valid: true}, GitCommitAuthorAvatarUrl: sql.NullString{String: tt.input, Valid: true}, } require.Equal(t, tt.expected, deployment.GitCommitMessage.String) - require.Equal(t, tt.expected, deployment.GitCommitAuthorName.String) - require.Equal(t, tt.expected, deployment.GitCommitAuthorUsername.String) + require.Equal(t, tt.expected, deployment.GitCommitAuthorHandle.String) require.Equal(t, tt.expected, deployment.GitCommitAuthorAvatarUrl.String) }) } @@ -105,32 +101,27 @@ func TestGitFieldValidation_NullHandling(t *testing.T) { WorkspaceId: "ws_test", ProjectId: "proj_test", GitCommitMessage: "", - GitCommitAuthorName: "", - GitCommitAuthorUsername: "", GitCommitAuthorAvatarUrl: "", GitCommitTimestamp: 0, } // Empty strings should be returned as-is require.Equal(t, "", req.GetGitCommitMessage()) - require.Equal(t, "", req.GetGitCommitAuthorName()) - require.Equal(t, "", req.GetGitCommitAuthorUsername()) + require.Equal(t, "", req.GetGitCommitAuthorHandle()) require.Equal(t, "", req.GetGitCommitAuthorAvatarUrl()) require.Equal(t, int64(0), req.GetGitCommitTimestamp()) // Test NULL database fields deployment := db.Deployment{ GitCommitMessage: sql.NullString{Valid: false}, - GitCommitAuthorName: sql.NullString{Valid: false}, - GitCommitAuthorUsername: sql.NullString{Valid: false}, + GitCommitAuthorHandle: sql.NullString{Valid: false}, GitCommitAuthorAvatarUrl: sql.NullString{Valid: false}, GitCommitTimestamp: sql.NullInt64{Valid: false}, } // NULL fields should be invalid require.False(t, deployment.GitCommitMessage.Valid) - require.False(t, deployment.GitCommitAuthorName.Valid) - require.False(t, deployment.GitCommitAuthorUsername.Valid) + require.False(t, deployment.GitCommitAuthorHandle.Valid) require.False(t, deployment.GitCommitAuthorAvatarUrl.Valid) require.False(t, deployment.GitCommitTimestamp.Valid) } @@ -290,8 +281,7 @@ func TestCreateVersionFieldMapping(t *testing.T) { SourceType: ctrlv1.SourceType_SOURCE_TYPE_GIT, GitCommitSha: "abc123def456789", GitCommitMessage: "feat: implement new feature", - GitCommitAuthorName: "Jane Doe", - GitCommitAuthorUsername: "janedoe", + GitCommitAuthorHandle: "janedoe", GitCommitAuthorAvatarUrl: "https://github.com/janedoe.png", GitCommitTimestamp: 1724251845123, // Fixed millisecond timestamp }, @@ -336,8 +326,7 @@ func TestCreateVersionFieldMapping(t *testing.T) { SourceType: ctrlv1.SourceType_SOURCE_TYPE_GIT, GitCommitSha: "", GitCommitMessage: "", - GitCommitAuthorName: "", - GitCommitAuthorUsername: "", + GitCommitAuthorHandle: "", GitCommitAuthorAvatarUrl: "", GitCommitTimestamp: 0, }, @@ -382,8 +371,7 @@ func TestCreateVersionFieldMapping(t *testing.T) { SourceType: ctrlv1.SourceType_SOURCE_TYPE_GIT, GitCommitSha: "xyz789abc123", GitCommitMessage: "fix: critical security issue", - GitCommitAuthorName: "", // Empty - GitCommitAuthorUsername: "", // Empty + GitCommitAuthorHandle: "", // Empty GitCommitAuthorAvatarUrl: "", // Empty GitCommitTimestamp: 1724251845999, }, @@ -436,8 +424,7 @@ func TestCreateVersionFieldMapping(t *testing.T) { GitCommitSha: sql.NullString{String: tt.request.GetGitCommitSha(), Valid: tt.request.GetGitCommitSha() != ""}, GitBranch: sql.NullString{String: tt.request.GetBranch(), Valid: true}, GitCommitMessage: sql.NullString{String: tt.request.GetGitCommitMessage(), Valid: tt.request.GetGitCommitMessage() != ""}, - GitCommitAuthorName: sql.NullString{String: tt.request.GetGitCommitAuthorName(), Valid: tt.request.GetGitCommitAuthorName() != ""}, - GitCommitAuthorUsername: sql.NullString{String: tt.request.GetGitCommitAuthorUsername(), Valid: tt.request.GetGitCommitAuthorUsername() != ""}, + GitCommitAuthorHandle: sql.NullString{String: tt.request.GetGitCommitAuthorHandle(), Valid: tt.request.GetGitCommitAuthorHandle() != ""}, GitCommitAuthorAvatarUrl: sql.NullString{String: tt.request.GetGitCommitAuthorAvatarUrl(), Valid: tt.request.GetGitCommitAuthorAvatarUrl() != ""}, GitCommitTimestamp: sql.NullInt64{Int64: tt.request.GetGitCommitTimestamp(), Valid: tt.request.GetGitCommitTimestamp() != 0}, RuntimeConfig: []byte("{}"), @@ -457,11 +444,8 @@ func TestCreateVersionFieldMapping(t *testing.T) { require.Equal(t, tt.expected.gitCommitMessage, params.GitCommitMessage.String, "GitCommitMessage string mismatch") require.Equal(t, tt.expected.gitCommitMessageValid, params.GitCommitMessage.Valid, "GitCommitMessage valid flag mismatch") - require.Equal(t, tt.expected.gitCommitAuthorName, params.GitCommitAuthorName.String, "GitCommitAuthorName string mismatch") - require.Equal(t, tt.expected.gitCommitAuthorNameValid, params.GitCommitAuthorName.Valid, "GitCommitAuthorName valid flag mismatch") - - require.Equal(t, tt.expected.gitCommitAuthorUsername, params.GitCommitAuthorUsername.String, "GitCommitAuthorUsername string mismatch") - require.Equal(t, tt.expected.gitCommitAuthorUsernameValid, params.GitCommitAuthorUsername.Valid, "GitCommitAuthorUsername valid flag mismatch") + require.Equal(t, tt.expected.gitCommitAuthorUsername, params.GitCommitAuthorHandle.String, "GitCommitAuthorUsername string mismatch") + require.Equal(t, tt.expected.gitCommitAuthorUsernameValid, params.GitCommitAuthorHandle.Valid, "GitCommitAuthorUsername valid flag mismatch") require.Equal(t, tt.expected.gitCommitAuthorAvatarUrl, params.GitCommitAuthorAvatarUrl.String, "GitCommitAuthorAvatarUrl string mismatch") require.Equal(t, tt.expected.gitCommitAuthorAvatarUrlValid, params.GitCommitAuthorAvatarUrl.Valid, "GitCommitAuthorAvatarUrl valid flag mismatch") diff --git a/go/apps/ctrl/services/deployment/get_deployment.go b/go/apps/ctrl/services/deployment/get_deployment.go index d34e0ffb29..03229fc9b0 100644 --- a/go/apps/ctrl/services/deployment/get_deployment.go +++ b/go/apps/ctrl/services/deployment/get_deployment.go @@ -53,12 +53,10 @@ func (s *Service) GetDeployment( if deployment.GitCommitMessage.Valid { protoDeployment.GitCommitMessage = deployment.GitCommitMessage.String } - if deployment.GitCommitAuthorName.Valid { - protoDeployment.GitCommitAuthorName = deployment.GitCommitAuthorName.String - } + // Email removed to avoid storing PII - TODO: implement GitHub API lookup - if deployment.GitCommitAuthorUsername.Valid { - protoDeployment.GitCommitAuthorUsername = deployment.GitCommitAuthorUsername.String + if deployment.GitCommitAuthorHandle.Valid { + protoDeployment.GitCommitAuthorHandle = deployment.GitCommitAuthorHandle.String } if deployment.GitCommitAuthorAvatarUrl.Valid { protoDeployment.GitCommitAuthorAvatarUrl = deployment.GitCommitAuthorAvatarUrl.String diff --git a/go/cmd/deploy/control_plane.go b/go/cmd/deploy/control_plane.go index 78f29a75eb..405e381152 100644 --- a/go/cmd/deploy/control_plane.go +++ b/go/cmd/deploy/control_plane.go @@ -13,6 +13,7 @@ import ( "github.com/unkeyed/unkey/go/gen/proto/ctrl/v1/ctrlv1connect" "github.com/unkeyed/unkey/go/pkg/codes" "github.com/unkeyed/unkey/go/pkg/fault" + "github.com/unkeyed/unkey/go/pkg/git" "github.com/unkeyed/unkey/go/pkg/otel/logging" ) @@ -50,15 +51,21 @@ func NewControlPlaneClient(opts DeployOptions) *ControlPlaneClient { // CreateDeployment creates a new deployment in the control plane func (c *ControlPlaneClient) CreateDeployment(ctx context.Context, dockerImage string) (string, error) { + // Get git commit information + commitInfo := git.GetInfo() createReq := connect.NewRequest(&ctrlv1.CreateDeploymentRequest{ - WorkspaceId: c.opts.WorkspaceID, - ProjectId: c.opts.ProjectID, - KeyspaceId: &c.opts.KeyspaceID, - Branch: c.opts.Branch, - SourceType: ctrlv1.SourceType_SOURCE_TYPE_CLI_UPLOAD, - GitCommitSha: c.opts.Commit, - EnvironmentSlug: c.opts.Environment, - DockerImage: dockerImage, + WorkspaceId: c.opts.WorkspaceID, + ProjectId: c.opts.ProjectID, + KeyspaceId: &c.opts.KeyspaceID, + Branch: c.opts.Branch, + SourceType: ctrlv1.SourceType_SOURCE_TYPE_CLI_UPLOAD, + EnvironmentSlug: c.opts.Environment, + DockerImage: dockerImage, + GitCommitSha: commitInfo.CommitSHA, + GitCommitMessage: commitInfo.Message, + GitCommitAuthorHandle: commitInfo.AuthorHandle, + GitCommitAuthorAvatarUrl: commitInfo.AuthorAvatarURL, + GitCommitTimestamp: commitInfo.CommitTimestamp, }) // Use API key for authentication if provided, fallback to auth token diff --git a/go/gen/proto/ctrl/v1/deployment.pb.go b/go/gen/proto/ctrl/v1/deployment.pb.go index 77c7766bdd..6640f96c5d 100644 --- a/go/gen/proto/ctrl/v1/deployment.pb.go +++ b/go/gen/proto/ctrl/v1/deployment.pb.go @@ -143,15 +143,14 @@ type CreateDeploymentRequest struct { SourceType SourceType `protobuf:"varint,5,opt,name=source_type,json=sourceType,proto3,enum=ctrl.v1.SourceType" json:"source_type,omitempty"` DockerImage string `protobuf:"bytes,6,opt,name=docker_image,json=dockerImage,proto3" json:"docker_image,omitempty"` // Extended git information - GitCommitSha string `protobuf:"bytes,7,opt,name=git_commit_sha,json=gitCommitSha,proto3" json:"git_commit_sha,omitempty"` // For git sources - GitCommitMessage string `protobuf:"bytes,8,opt,name=git_commit_message,json=gitCommitMessage,proto3" json:"git_commit_message,omitempty"` - GitCommitAuthorName string `protobuf:"bytes,9,opt,name=git_commit_author_name,json=gitCommitAuthorName,proto3" json:"git_commit_author_name,omitempty"` + GitCommitSha string `protobuf:"bytes,7,opt,name=git_commit_sha,json=gitCommitSha,proto3" json:"git_commit_sha,omitempty"` // For git sources + GitCommitMessage string `protobuf:"bytes,8,opt,name=git_commit_message,json=gitCommitMessage,proto3" json:"git_commit_message,omitempty"` // TODO: Add GitHub API integration to lookup username/avatar from email - GitCommitAuthorUsername string `protobuf:"bytes,10,opt,name=git_commit_author_username,json=gitCommitAuthorUsername,proto3" json:"git_commit_author_username,omitempty"` - GitCommitAuthorAvatarUrl string `protobuf:"bytes,11,opt,name=git_commit_author_avatar_url,json=gitCommitAuthorAvatarUrl,proto3" json:"git_commit_author_avatar_url,omitempty"` - GitCommitTimestamp int64 `protobuf:"varint,12,opt,name=git_commit_timestamp,json=gitCommitTimestamp,proto3" json:"git_commit_timestamp,omitempty"` // Unix epoch milliseconds + GitCommitAuthorHandle string `protobuf:"bytes,9,opt,name=git_commit_author_handle,json=gitCommitAuthorHandle,proto3" json:"git_commit_author_handle,omitempty"` + GitCommitAuthorAvatarUrl string `protobuf:"bytes,10,opt,name=git_commit_author_avatar_url,json=gitCommitAuthorAvatarUrl,proto3" json:"git_commit_author_avatar_url,omitempty"` + GitCommitTimestamp int64 `protobuf:"varint,11,opt,name=git_commit_timestamp,json=gitCommitTimestamp,proto3" json:"git_commit_timestamp,omitempty"` // Unix epoch milliseconds // Keyspace ID for authentication - KeyspaceId *string `protobuf:"bytes,13,opt,name=keyspace_id,json=keyspaceId,proto3,oneof" json:"keyspace_id,omitempty"` + KeyspaceId *string `protobuf:"bytes,12,opt,name=keyspace_id,json=keyspaceId,proto3,oneof" json:"keyspace_id,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -242,16 +241,9 @@ func (x *CreateDeploymentRequest) GetGitCommitMessage() string { return "" } -func (x *CreateDeploymentRequest) GetGitCommitAuthorName() string { +func (x *CreateDeploymentRequest) GetGitCommitAuthorHandle() string { if x != nil { - return x.GitCommitAuthorName - } - return "" -} - -func (x *CreateDeploymentRequest) GetGitCommitAuthorUsername() string { - if x != nil { - return x.GitCommitAuthorUsername + return x.GitCommitAuthorHandle } return "" } @@ -444,12 +436,11 @@ type Deployment struct { // Deployment steps Steps []*DeploymentStep `protobuf:"bytes,16,rep,name=steps,proto3" json:"steps,omitempty"` // Extended git information - GitCommitMessage string `protobuf:"bytes,17,opt,name=git_commit_message,json=gitCommitMessage,proto3" json:"git_commit_message,omitempty"` - GitCommitAuthorName string `protobuf:"bytes,18,opt,name=git_commit_author_name,json=gitCommitAuthorName,proto3" json:"git_commit_author_name,omitempty"` + GitCommitMessage string `protobuf:"bytes,17,opt,name=git_commit_message,json=gitCommitMessage,proto3" json:"git_commit_message,omitempty"` // Removed: email is PII and not stored - GitCommitAuthorUsername string `protobuf:"bytes,20,opt,name=git_commit_author_username,json=gitCommitAuthorUsername,proto3" json:"git_commit_author_username,omitempty"` - GitCommitAuthorAvatarUrl string `protobuf:"bytes,21,opt,name=git_commit_author_avatar_url,json=gitCommitAuthorAvatarUrl,proto3" json:"git_commit_author_avatar_url,omitempty"` - GitCommitTimestamp int64 `protobuf:"varint,22,opt,name=git_commit_timestamp,json=gitCommitTimestamp,proto3" json:"git_commit_timestamp,omitempty"` // Unix epoch milliseconds + GitCommitAuthorHandle string `protobuf:"bytes,18,opt,name=git_commit_author_handle,json=gitCommitAuthorHandle,proto3" json:"git_commit_author_handle,omitempty"` + GitCommitAuthorAvatarUrl string `protobuf:"bytes,19,opt,name=git_commit_author_avatar_url,json=gitCommitAuthorAvatarUrl,proto3" json:"git_commit_author_avatar_url,omitempty"` + GitCommitTimestamp int64 `protobuf:"varint,20,opt,name=git_commit_timestamp,json=gitCommitTimestamp,proto3" json:"git_commit_timestamp,omitempty"` // Unix epoch milliseconds unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -603,16 +594,9 @@ func (x *Deployment) GetGitCommitMessage() string { return "" } -func (x *Deployment) GetGitCommitAuthorName() string { - if x != nil { - return x.GitCommitAuthorName - } - return "" -} - -func (x *Deployment) GetGitCommitAuthorUsername() string { +func (x *Deployment) GetGitCommitAuthorHandle() string { if x != nil { - return x.GitCommitAuthorUsername + return x.GitCommitAuthorHandle } return "" } @@ -1017,7 +1001,7 @@ var File_ctrl_v1_deployment_proto protoreflect.FileDescriptor const file_ctrl_v1_deployment_proto_rawDesc = "" + "\n" + - "\x18ctrl/v1/deployment.proto\x12\actrl.v1\"\xe5\x04\n" + + "\x18ctrl/v1/deployment.proto\x12\actrl.v1\"\xac\x04\n" + "\x17CreateDeploymentRequest\x12!\n" + "\fworkspace_id\x18\x01 \x01(\tR\vworkspaceId\x12\x1d\n" + "\n" + @@ -1028,13 +1012,12 @@ const file_ctrl_v1_deployment_proto_rawDesc = "" + "sourceType\x12!\n" + "\fdocker_image\x18\x06 \x01(\tR\vdockerImage\x12$\n" + "\x0egit_commit_sha\x18\a \x01(\tR\fgitCommitSha\x12,\n" + - "\x12git_commit_message\x18\b \x01(\tR\x10gitCommitMessage\x123\n" + - "\x16git_commit_author_name\x18\t \x01(\tR\x13gitCommitAuthorName\x12;\n" + - "\x1agit_commit_author_username\x18\n" + - " \x01(\tR\x17gitCommitAuthorUsername\x12>\n" + - "\x1cgit_commit_author_avatar_url\x18\v \x01(\tR\x18gitCommitAuthorAvatarUrl\x120\n" + - "\x14git_commit_timestamp\x18\f \x01(\x03R\x12gitCommitTimestamp\x12$\n" + - "\vkeyspace_id\x18\r \x01(\tH\x00R\n" + + "\x12git_commit_message\x18\b \x01(\tR\x10gitCommitMessage\x127\n" + + "\x18git_commit_author_handle\x18\t \x01(\tR\x15gitCommitAuthorHandle\x12>\n" + + "\x1cgit_commit_author_avatar_url\x18\n" + + " \x01(\tR\x18gitCommitAuthorAvatarUrl\x120\n" + + "\x14git_commit_timestamp\x18\v \x01(\x03R\x12gitCommitTimestamp\x12$\n" + + "\vkeyspace_id\x18\f \x01(\tH\x00R\n" + "keyspaceId\x88\x01\x01B\x0e\n" + "\f_keyspace_id\"r\n" + "\x18CreateDeploymentResponse\x12#\n" + @@ -1045,7 +1028,7 @@ const file_ctrl_v1_deployment_proto_rawDesc = "" + "\x15GetDeploymentResponse\x123\n" + "\n" + "deployment\x18\x01 \x01(\v2\x13.ctrl.v1.DeploymentR\n" + - "deployment\"\xde\a\n" + + "deployment\"\xa5\a\n" + "\n" + "Deployment\x12\x0e\n" + "\x02id\x18\x01 \x01(\tR\x02id\x12!\n" + @@ -1069,11 +1052,10 @@ const file_ctrl_v1_deployment_proto_rawDesc = "" + "\x0frootfs_image_id\x18\x0e \x01(\tR\rrootfsImageId\x12\x19\n" + "\bbuild_id\x18\x0f \x01(\tR\abuildId\x12-\n" + "\x05steps\x18\x10 \x03(\v2\x17.ctrl.v1.DeploymentStepR\x05steps\x12,\n" + - "\x12git_commit_message\x18\x11 \x01(\tR\x10gitCommitMessage\x123\n" + - "\x16git_commit_author_name\x18\x12 \x01(\tR\x13gitCommitAuthorName\x12;\n" + - "\x1agit_commit_author_username\x18\x14 \x01(\tR\x17gitCommitAuthorUsername\x12>\n" + - "\x1cgit_commit_author_avatar_url\x18\x15 \x01(\tR\x18gitCommitAuthorAvatarUrl\x120\n" + - "\x14git_commit_timestamp\x18\x16 \x01(\x03R\x12gitCommitTimestamp\x1aG\n" + + "\x12git_commit_message\x18\x11 \x01(\tR\x10gitCommitMessage\x127\n" + + "\x18git_commit_author_handle\x18\x12 \x01(\tR\x15gitCommitAuthorHandle\x12>\n" + + "\x1cgit_commit_author_avatar_url\x18\x13 \x01(\tR\x18gitCommitAuthorAvatarUrl\x120\n" + + "\x14git_commit_timestamp\x18\x14 \x01(\x03R\x12gitCommitTimestamp\x1aG\n" + "\x19EnvironmentVariablesEntry\x12\x10\n" + "\x03key\x18\x01 \x01(\tR\x03key\x12\x14\n" + "\x05value\x18\x02 \x01(\tR\x05value:\x028\x01\"\x86\x01\n" + diff --git a/go/pkg/db/bulk_deployment_insert.sql_generated.go b/go/pkg/db/bulk_deployment_insert.sql_generated.go index fb8b1f66fb..465a6610ea 100644 --- a/go/pkg/db/bulk_deployment_insert.sql_generated.go +++ b/go/pkg/db/bulk_deployment_insert.sql_generated.go @@ -9,7 +9,7 @@ import ( ) // bulkInsertDeployment is the base query for bulk insert -const bulkInsertDeployment = `INSERT INTO ` + "`" + `deployments` + "`" + ` ( id, workspace_id, project_id, environment_id, git_commit_sha, git_branch, runtime_config, git_commit_message, git_commit_author_name, git_commit_author_username, git_commit_author_avatar_url, git_commit_timestamp, openapi_spec, status, created_at, updated_at ) VALUES %s` +const bulkInsertDeployment = `INSERT INTO ` + "`" + `deployments` + "`" + ` ( id, workspace_id, project_id, environment_id, git_commit_sha, git_branch, runtime_config, git_commit_message, git_commit_author_handle, git_commit_author_avatar_url, git_commit_timestamp, openapi_spec, status, created_at, updated_at ) VALUES %s` // InsertDeployments performs bulk insert in a single query func (q *BulkQueries) InsertDeployments(ctx context.Context, db DBTX, args []InsertDeploymentParams) error { @@ -21,7 +21,7 @@ func (q *BulkQueries) InsertDeployments(ctx context.Context, db DBTX, args []Ins // Build the bulk insert query valueClauses := make([]string, len(args)) for i := range args { - valueClauses[i] = "( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" + valueClauses[i] = "( ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? )" } bulkQuery := fmt.Sprintf(bulkInsertDeployment, strings.Join(valueClauses, ", ")) @@ -37,8 +37,7 @@ func (q *BulkQueries) InsertDeployments(ctx context.Context, db DBTX, args []Ins allArgs = append(allArgs, arg.GitBranch) allArgs = append(allArgs, arg.RuntimeConfig) allArgs = append(allArgs, arg.GitCommitMessage) - allArgs = append(allArgs, arg.GitCommitAuthorName) - allArgs = append(allArgs, arg.GitCommitAuthorUsername) + allArgs = append(allArgs, arg.GitCommitAuthorHandle) allArgs = append(allArgs, arg.GitCommitAuthorAvatarUrl) allArgs = append(allArgs, arg.GitCommitTimestamp) allArgs = append(allArgs, arg.OpenapiSpec) diff --git a/go/pkg/db/deployment_find_by_id.sql_generated.go b/go/pkg/db/deployment_find_by_id.sql_generated.go index c3f70b786f..5ec188df08 100644 --- a/go/pkg/db/deployment_find_by_id.sql_generated.go +++ b/go/pkg/db/deployment_find_by_id.sql_generated.go @@ -21,8 +21,7 @@ SELECT git_branch, runtime_config, git_commit_message, - git_commit_author_name, - git_commit_author_username, + git_commit_author_handle, git_commit_author_avatar_url, git_commit_timestamp, openapi_spec, @@ -42,8 +41,7 @@ type FindDeploymentByIdRow struct { GitBranch sql.NullString `db:"git_branch"` RuntimeConfig json.RawMessage `db:"runtime_config"` GitCommitMessage sql.NullString `db:"git_commit_message"` - GitCommitAuthorName sql.NullString `db:"git_commit_author_name"` - GitCommitAuthorUsername sql.NullString `db:"git_commit_author_username"` + GitCommitAuthorHandle sql.NullString `db:"git_commit_author_handle"` GitCommitAuthorAvatarUrl sql.NullString `db:"git_commit_author_avatar_url"` GitCommitTimestamp sql.NullInt64 `db:"git_commit_timestamp"` OpenapiSpec sql.NullString `db:"openapi_spec"` @@ -63,8 +61,7 @@ type FindDeploymentByIdRow struct { // git_branch, // runtime_config, // git_commit_message, -// git_commit_author_name, -// git_commit_author_username, +// git_commit_author_handle, // git_commit_author_avatar_url, // git_commit_timestamp, // openapi_spec, @@ -85,8 +82,7 @@ func (q *Queries) FindDeploymentById(ctx context.Context, db DBTX, id string) (F &i.GitBranch, &i.RuntimeConfig, &i.GitCommitMessage, - &i.GitCommitAuthorName, - &i.GitCommitAuthorUsername, + &i.GitCommitAuthorHandle, &i.GitCommitAuthorAvatarUrl, &i.GitCommitTimestamp, &i.OpenapiSpec, diff --git a/go/pkg/db/deployment_insert.sql_generated.go b/go/pkg/db/deployment_insert.sql_generated.go index 7fb05a9714..2e5d78412a 100644 --- a/go/pkg/db/deployment_insert.sql_generated.go +++ b/go/pkg/db/deployment_insert.sql_generated.go @@ -21,8 +21,7 @@ INSERT INTO ` + "`" + `deployments` + "`" + ` ( git_branch, runtime_config, git_commit_message, - git_commit_author_name, - git_commit_author_username, + git_commit_author_handle, git_commit_author_avatar_url, git_commit_timestamp, -- Unix epoch milliseconds openapi_spec, @@ -45,7 +44,6 @@ VALUES ( ?, ?, ?, - ?, ? ) ` @@ -59,8 +57,7 @@ type InsertDeploymentParams struct { GitBranch sql.NullString `db:"git_branch"` RuntimeConfig json.RawMessage `db:"runtime_config"` GitCommitMessage sql.NullString `db:"git_commit_message"` - GitCommitAuthorName sql.NullString `db:"git_commit_author_name"` - GitCommitAuthorUsername sql.NullString `db:"git_commit_author_username"` + GitCommitAuthorHandle sql.NullString `db:"git_commit_author_handle"` GitCommitAuthorAvatarUrl sql.NullString `db:"git_commit_author_avatar_url"` GitCommitTimestamp sql.NullInt64 `db:"git_commit_timestamp"` OpenapiSpec sql.NullString `db:"openapi_spec"` @@ -80,8 +77,7 @@ type InsertDeploymentParams struct { // git_branch, // runtime_config, // git_commit_message, -// git_commit_author_name, -// git_commit_author_username, +// git_commit_author_handle, // git_commit_author_avatar_url, // git_commit_timestamp, -- Unix epoch milliseconds // openapi_spec, @@ -104,7 +100,6 @@ type InsertDeploymentParams struct { // ?, // ?, // ?, -// ?, // ? // ) func (q *Queries) InsertDeployment(ctx context.Context, db DBTX, arg InsertDeploymentParams) error { @@ -117,8 +112,7 @@ func (q *Queries) InsertDeployment(ctx context.Context, db DBTX, arg InsertDeplo arg.GitBranch, arg.RuntimeConfig, arg.GitCommitMessage, - arg.GitCommitAuthorName, - arg.GitCommitAuthorUsername, + arg.GitCommitAuthorHandle, arg.GitCommitAuthorAvatarUrl, arg.GitCommitTimestamp, arg.OpenapiSpec, diff --git a/go/pkg/db/models_generated.go b/go/pkg/db/models_generated.go index d961d08d53..ad35be5799 100644 --- a/go/pkg/db/models_generated.go +++ b/go/pkg/db/models_generated.go @@ -574,9 +574,7 @@ type Deployment struct { GitCommitSha sql.NullString `db:"git_commit_sha"` GitBranch sql.NullString `db:"git_branch"` GitCommitMessage sql.NullString `db:"git_commit_message"` - GitCommitAuthorName sql.NullString `db:"git_commit_author_name"` - GitCommitAuthorEmail sql.NullString `db:"git_commit_author_email"` - GitCommitAuthorUsername sql.NullString `db:"git_commit_author_username"` + GitCommitAuthorHandle sql.NullString `db:"git_commit_author_handle"` GitCommitAuthorAvatarUrl sql.NullString `db:"git_commit_author_avatar_url"` GitCommitTimestamp sql.NullInt64 `db:"git_commit_timestamp"` RuntimeConfig json.RawMessage `db:"runtime_config"` diff --git a/go/pkg/db/querier_generated.go b/go/pkg/db/querier_generated.go index 9ab7a23a09..f166bc877b 100644 --- a/go/pkg/db/querier_generated.go +++ b/go/pkg/db/querier_generated.go @@ -149,8 +149,7 @@ type Querier interface { // git_branch, // runtime_config, // git_commit_message, - // git_commit_author_name, - // git_commit_author_username, + // git_commit_author_handle, // git_commit_author_avatar_url, // git_commit_timestamp, // openapi_spec, @@ -884,8 +883,7 @@ type Querier interface { // git_branch, // runtime_config, // git_commit_message, - // git_commit_author_name, - // git_commit_author_username, + // git_commit_author_handle, // git_commit_author_avatar_url, // git_commit_timestamp, -- Unix epoch milliseconds // openapi_spec, @@ -908,7 +906,6 @@ type Querier interface { // ?, // ?, // ?, - // ?, // ? // ) InsertDeployment(ctx context.Context, db DBTX, arg InsertDeploymentParams) error diff --git a/go/pkg/db/queries/deployment_find_by_id.sql b/go/pkg/db/queries/deployment_find_by_id.sql index b617fcd757..ee2b438b83 100644 --- a/go/pkg/db/queries/deployment_find_by_id.sql +++ b/go/pkg/db/queries/deployment_find_by_id.sql @@ -8,8 +8,7 @@ SELECT git_branch, runtime_config, git_commit_message, - git_commit_author_name, - git_commit_author_username, + git_commit_author_handle, git_commit_author_avatar_url, git_commit_timestamp, openapi_spec, diff --git a/go/pkg/db/queries/deployment_insert.sql b/go/pkg/db/queries/deployment_insert.sql index 7415304ad7..5d4659f329 100644 --- a/go/pkg/db/queries/deployment_insert.sql +++ b/go/pkg/db/queries/deployment_insert.sql @@ -8,8 +8,7 @@ INSERT INTO `deployments` ( git_branch, runtime_config, git_commit_message, - git_commit_author_name, - git_commit_author_username, + git_commit_author_handle, git_commit_author_avatar_url, git_commit_timestamp, -- Unix epoch milliseconds openapi_spec, @@ -26,8 +25,7 @@ VALUES ( sqlc.arg(git_branch), sqlc.arg(runtime_config), sqlc.arg(git_commit_message), - sqlc.arg(git_commit_author_name), - sqlc.arg(git_commit_author_username), + sqlc.arg(git_commit_author_handle), sqlc.arg(git_commit_author_avatar_url), sqlc.arg(git_commit_timestamp), sqlc.arg(openapi_spec), diff --git a/go/pkg/db/schema.sql b/go/pkg/db/schema.sql index 3b7177fd95..a1f23ad252 100644 --- a/go/pkg/db/schema.sql +++ b/go/pkg/db/schema.sql @@ -331,9 +331,7 @@ CREATE TABLE `deployments` ( `git_commit_sha` varchar(40), `git_branch` varchar(256), `git_commit_message` text, - `git_commit_author_name` varchar(256), - `git_commit_author_email` varchar(256), - `git_commit_author_username` varchar(256), + `git_commit_author_handle` varchar(256), `git_commit_author_avatar_url` varchar(512), `git_commit_timestamp` bigint, `runtime_config` json NOT NULL, @@ -420,4 +418,3 @@ CREATE INDEX `project_idx` ON `domains` (`project_id`); CREATE INDEX `deployment_idx` ON `domains` (`deployment_id`); CREATE INDEX `workspace_idx` ON `acme_challenges` (`workspace_id`); CREATE INDEX `status_idx` ON `acme_challenges` (`status`); - diff --git a/go/pkg/git/git.go b/go/pkg/git/git.go index 10d5666f9b..2f2614e8e1 100644 --- a/go/pkg/git/git.go +++ b/go/pkg/git/git.go @@ -1,28 +1,50 @@ package git import ( + "encoding/json" + "fmt" + "io" + "net/http" "os/exec" + "strconv" "strings" + "time" ) -// Info contains Git repository information +// Info contains comprehensive Git repo and commit information type Info struct { - Branch string // Current branch name (e.g., "main", "feature/auth") + // Basic repo status + Branch string // Current branch name (e.g., "main", "feature/auth") + IsDirty bool // Whether there are uncommitted changes + IsRepo bool // Whether we're in a Git repo + + // Commit identification CommitSHA string // Full commit SHA (e.g., "abc123def456...") ShortSHA string // Short commit SHA (e.g., "abc123d") - IsDirty bool // Whether there are uncommitted changes - IsRepo bool // Whether we're in a Git repository + + // Commit details + Message string // Commit message (first line only) + AuthorHandle string // Author's GitHub handle + AuthorAvatarURL string // URL to author's avatar image + CommitTimestamp int64 // Unix timestamp of the commit in milliseconds +} + +// githubCommitResponse represents the GitHub API commit response +type githubCommitResponse struct { + Author struct { + Login string `json:"login"` + AvatarURL string `json:"avatar_url"` + } `json:"author"` } // GetInfo safely extracts Git information from the current directory. // It never fails - returns sensible defaults if Git is unavailable or we're not in a repo. +// Extended commit details (message, author, timestamp) are populated when available. func GetInfo() Info { info := Info{ - Branch: "main", // Default branch - CommitSHA: "", // Empty if not available - ShortSHA: "", // Empty if not available - IsDirty: false, // Assume clean if unknown - IsRepo: false, // Assume not a repo until proven otherwise + Branch: "main", // Default branch + IsDirty: false, // Assume clean if unknown + IsRepo: false, // Assume not a repo until proven otherwise } // Check if we're in a Git repository @@ -47,37 +69,134 @@ func GetInfo() Info { // Check if working directory is dirty info.IsDirty = isWorkingDirDirty() + // Get extended commit details (best effort - ignore errors) + if info.CommitSHA != "" { + // Get commit message (first line only) + if message, err := execGitCommand("git", "log", "-1", "--pretty=%s"); err == nil { + info.Message = message + } + + // Get commit timestamp + if timestampStr, err := execGitCommand("git", "log", "-1", "--pretty=%ct"); err == nil { + if timestamp, err := strconv.ParseInt(timestampStr, 10, 64); err == nil { + info.CommitTimestamp = timestamp * 1000 // Convert to milliseconds + } + } + + // Get remote URL to determine if it's a GitHub repo + if remoteURL, err := execGitCommand("git", "config", "--get", "remote.origin.url"); err == nil && isGitHubURL(remoteURL) { + // Extract owner and repo from GitHub URL + owner, repo := parseGitHubURL(remoteURL) + if owner != "" && repo != "" { + // Fetch author info from GitHub API (best effort) + handle, avatarURL := fetchGitHubAuthorInfo(owner, repo, info.CommitSHA) + info.AuthorHandle = handle + info.AuthorAvatarURL = avatarURL + } + } + } + return info } +// isGitHubURL checks if the URL is a GitHub repository URL +func isGitHubURL(url string) bool { + return strings.Contains(url, "github.com") +} + +// parseGitHubURL extracts owner and repo name from GitHub URL +// Supports both HTTPS and SSH formats: +// - https://github.com/owner/repo.git +// - git@github.com:owner/repo.git +func parseGitHubURL(url string) (owner, repo string) { + url = strings.TrimSpace(url) + + // Remove .git suffix + url = strings.TrimSuffix(url, ".git") + + // Handle SSH format: git@github.com:owner/repo + if after, ok := strings.CutPrefix(url, "git@github.com:"); ok { + path := after + parts := strings.Split(path, "/") + if len(parts) == 2 { + return parts[0], parts[1] + } + } + + // Handle HTTPS format: https://github.com/owner/repo + if strings.Contains(url, "github.com/") { + parts := strings.Split(url, "github.com/") + if len(parts) == 2 { + pathParts := strings.Split(parts[1], "/") + if len(pathParts) >= 2 { + return pathParts[0], pathParts[1] + } + } + } + + return "", "" +} + +// TODO: We'll have something smarter after demo. As long as we are demoing in a pushed repo we are good. +// fetchGitHubAuthorInfo fetches the commit author's GitHub handle and avatar from GitHub API +func fetchGitHubAuthorInfo(owner, repo, sha string) (string, string) { + url := fmt.Sprintf("https://api.github.com/repos/%s/%s/commits/%s", owner, repo, sha) + + client := &http.Client{ + Timeout: 5 * time.Second, + } + + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return "", "" + } + + // Set User-Agent header (required by GitHub API) + req.Header.Set("User-Agent", "unkey-cli") + + resp, err := client.Do(req) + if err != nil { + return "", "" + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return "", "" + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return "", "" + } + + var commitData githubCommitResponse + if err := json.Unmarshal(body, &commitData); err != nil { + return "", "" + } + + return commitData.Author.Login, commitData.Author.AvatarURL +} + // isGitRepo checks if we're in a Git repository func isGitRepo() bool { - cmd := exec.Command("git", "rev-parse", "--git-dir") - err := cmd.Run() + _, err := execGitCommand("git", "rev-parse", "--git-dir") return err == nil } // getCurrentBranch gets the current branch name func getCurrentBranch() string { // Try to get branch name from HEAD - cmd := exec.Command("git", "rev-parse", "--abbrev-ref", "HEAD") - output, err := cmd.Output() + branch, err := execGitCommand("git", "rev-parse", "--abbrev-ref", "HEAD") if err != nil { return "" } - - branch := strings.TrimSpace(string(output)) - // If we're in detached HEAD state, try to get branch from describe if branch == "HEAD" { - cmd = exec.Command("git", "describe", "--contains", "--all", "HEAD") - describeOutput, describeErr := cmd.Output() + describeBranch, describeErr := execGitCommand("git", "describe", "--contains", "--all", "HEAD") if describeErr != nil { return "" } - branch = strings.TrimSpace(string(describeOutput)) - - // Clean up the output (remove refs/heads/ prefix if present) + branch = describeBranch branch = strings.TrimPrefix(branch, "heads/") branch = strings.TrimPrefix(branch, "remotes/origin/") } @@ -87,34 +206,43 @@ func getCurrentBranch() string { // getCommitSHA gets the current commit SHA func getCommitSHA() string { - cmd := exec.Command("git", "rev-parse", "HEAD") - output, err := cmd.Output() + sha, err := execGitCommand("git", "rev-parse", "HEAD") if err != nil { return "" } - return strings.TrimSpace(string(output)) + return sha } // isWorkingDirDirty checks if there are uncommitted changes func isWorkingDirDirty() bool { // Check for staged changes - cmd := exec.Command("git", "diff-index", "--quiet", "--cached", "HEAD") - if err := cmd.Run(); err != nil { - return true // Has staged changes + _, err := execGitCommand("git", "diff-index", "--quiet", "--cached", "HEAD") + if err != nil { + return true } - // Check for unstaged changes - cmd = exec.Command("git", "diff-files", "--quiet") - if err := cmd.Run(); err != nil { - return true // Has unstaged changes + _, err = execGitCommand("git", "diff-files", "--quiet") + if err != nil { + return true } - // Check for untracked files - cmd = exec.Command("git", "ls-files", "--others", "--exclude-standard") - untrackedOutput, untrackedErr := cmd.Output() + untrackedOutput, untrackedErr := execGitCommand("git", "ls-files", "--others", "--exclude-standard") if untrackedErr != nil { - return false // Assume clean if we can't check + return false } - return strings.TrimSpace(string(untrackedOutput)) != "" + return untrackedOutput != "" +} + +// execGitCommand executes a git command and returns trimmed output +func execGitCommand(name string, args ...string) (string, error) { + cmd := exec.Command(name, args...) + output, err := cmd.Output() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + return "", fmt.Errorf("command failed: %s, stderr: %s", err, string(exitErr.Stderr)) + } + return "", err + } + return strings.TrimSpace(string(output)), nil } diff --git a/go/proto/ctrl/v1/deployment.proto b/go/proto/ctrl/v1/deployment.proto index 552e213b81..7d72c5b507 100644 --- a/go/proto/ctrl/v1/deployment.proto +++ b/go/proto/ctrl/v1/deployment.proto @@ -36,14 +36,13 @@ message CreateDeploymentRequest { // Extended git information string git_commit_sha = 7; // For git sources string git_commit_message = 8; - string git_commit_author_name = 9; // TODO: Add GitHub API integration to lookup username/avatar from email - string git_commit_author_username = 10; - string git_commit_author_avatar_url = 11; - int64 git_commit_timestamp = 12; // Unix epoch milliseconds + string git_commit_author_handle = 9; + string git_commit_author_avatar_url = 10; + int64 git_commit_timestamp = 11; // Unix epoch milliseconds // Keyspace ID for authentication - optional string keyspace_id = 13; + optional string keyspace_id = 12; } message CreateDeploymentResponse { @@ -95,11 +94,10 @@ message Deployment { // Extended git information string git_commit_message = 17; - string git_commit_author_name = 18; // Removed: email is PII and not stored - string git_commit_author_username = 20; - string git_commit_author_avatar_url = 21; - int64 git_commit_timestamp = 22; // Unix epoch milliseconds + string git_commit_author_handle = 18; + string git_commit_author_avatar_url = 19; + int64 git_commit_timestamp = 20; // Unix epoch milliseconds } message DeploymentStep { diff --git a/internal/db/src/schema/deployments.ts b/internal/db/src/schema/deployments.ts index b532a77960..95071a228a 100644 --- a/internal/db/src/schema/deployments.ts +++ b/internal/db/src/schema/deployments.ts @@ -20,9 +20,7 @@ export const deployments = mysqlTable( gitCommitSha: varchar("git_commit_sha", { length: 40 }), gitBranch: varchar("git_branch", { length: 256 }), gitCommitMessage: text("git_commit_message"), - gitCommitAuthorName: varchar("git_commit_author_name", { length: 256 }), - gitCommitAuthorEmail: varchar("git_commit_author_email", { length: 256 }), - gitCommitAuthorUsername: varchar("git_commit_author_username", { + gitCommitAuthorHandle: varchar("git_commit_author_handle", { length: 256, }), gitCommitAuthorAvatarUrl: varchar("git_commit_author_avatar_url", { diff --git a/internal/proto/generated/ctrl/v1/deployment_pb.ts b/internal/proto/generated/ctrl/v1/deployment_pb.ts index 6c36d1898b..6be0682d4a 100644 --- a/internal/proto/generated/ctrl/v1/deployment_pb.ts +++ b/internal/proto/generated/ctrl/v1/deployment_pb.ts @@ -12,7 +12,7 @@ import { enumDesc, fileDesc, messageDesc, serviceDesc } from "@bufbuild/protobuf export const file_ctrl_v1_deployment: GenFile = /*@__PURE__*/ fileDesc( - "ChhjdHJsL3YxL2RlcGxveW1lbnQucHJvdG8SB2N0cmwudjEikwMKF0NyZWF0ZURlcGxveW1lbnRSZXF1ZXN0EhQKDHdvcmtzcGFjZV9pZBgBIAEoCRISCgpwcm9qZWN0X2lkGAIgASgJEg4KBmJyYW5jaBgDIAEoCRIYChBlbnZpcm9ubWVudF9zbHVnGAQgASgJEigKC3NvdXJjZV90eXBlGAUgASgOMhMuY3RybC52MS5Tb3VyY2VUeXBlEhQKDGRvY2tlcl9pbWFnZRgGIAEoCRIWCg5naXRfY29tbWl0X3NoYRgHIAEoCRIaChJnaXRfY29tbWl0X21lc3NhZ2UYCCABKAkSHgoWZ2l0X2NvbW1pdF9hdXRob3JfbmFtZRgJIAEoCRIiChpnaXRfY29tbWl0X2F1dGhvcl91c2VybmFtZRgKIAEoCRIkChxnaXRfY29tbWl0X2F1dGhvcl9hdmF0YXJfdXJsGAsgASgJEhwKFGdpdF9jb21taXRfdGltZXN0YW1wGAwgASgDEhgKC2tleXNwYWNlX2lkGA0gASgJSACIAQFCDgoMX2tleXNwYWNlX2lkIlwKGENyZWF0ZURlcGxveW1lbnRSZXNwb25zZRIVCg1kZXBsb3ltZW50X2lkGAEgASgJEikKBnN0YXR1cxgCIAEoDjIZLmN0cmwudjEuRGVwbG95bWVudFN0YXR1cyItChRHZXREZXBsb3ltZW50UmVxdWVzdBIVCg1kZXBsb3ltZW50X2lkGAEgASgJIkAKFUdldERlcGxveW1lbnRSZXNwb25zZRInCgpkZXBsb3ltZW50GAEgASgLMhMuY3RybC52MS5EZXBsb3ltZW50IqoFCgpEZXBsb3ltZW50EgoKAmlkGAEgASgJEhQKDHdvcmtzcGFjZV9pZBgCIAEoCRISCgpwcm9qZWN0X2lkGAMgASgJEhYKDmVudmlyb25tZW50X2lkGAQgASgJEhYKDmdpdF9jb21taXRfc2hhGAUgASgJEhIKCmdpdF9icmFuY2gYBiABKAkSKQoGc3RhdHVzGAcgASgOMhkuY3RybC52MS5EZXBsb3ltZW50U3RhdHVzEhUKDWVycm9yX21lc3NhZ2UYCCABKAkSTAoVZW52aXJvbm1lbnRfdmFyaWFibGVzGAkgAygLMi0uY3RybC52MS5EZXBsb3ltZW50LkVudmlyb25tZW50VmFyaWFibGVzRW50cnkSIwoIdG9wb2xvZ3kYCiABKAsyES5jdHJsLnYxLlRvcG9sb2d5EhIKCmNyZWF0ZWRfYXQYCyABKAMSEgoKdXBkYXRlZF9hdBgMIAEoAxIRCglob3N0bmFtZXMYDSADKAkSFwoPcm9vdGZzX2ltYWdlX2lkGA4gASgJEhAKCGJ1aWxkX2lkGA8gASgJEiYKBXN0ZXBzGBAgAygLMhcuY3RybC52MS5EZXBsb3ltZW50U3RlcBIaChJnaXRfY29tbWl0X21lc3NhZ2UYESABKAkSHgoWZ2l0X2NvbW1pdF9hdXRob3JfbmFtZRgSIAEoCRIiChpnaXRfY29tbWl0X2F1dGhvcl91c2VybmFtZRgUIAEoCRIkChxnaXRfY29tbWl0X2F1dGhvcl9hdmF0YXJfdXJsGBUgASgJEhwKFGdpdF9jb21taXRfdGltZXN0YW1wGBYgASgDGjsKGUVudmlyb25tZW50VmFyaWFibGVzRW50cnkSCwoDa2V5GAEgASgJEg0KBXZhbHVlGAIgASgJOgI4ASJcCg5EZXBsb3ltZW50U3RlcBIOCgZzdGF0dXMYASABKAkSDwoHbWVzc2FnZRgCIAEoCRIVCg1lcnJvcl9tZXNzYWdlGAMgASgJEhIKCmNyZWF0ZWRfYXQYBCABKAMipgEKCFRvcG9sb2d5EhYKDmNwdV9taWxsaWNvcmVzGAEgASgFEhEKCW1lbW9yeV9tYhgCIAEoBRIoCgdyZWdpb25zGAMgAygLMhcuY3RybC52MS5SZWdpb25hbENvbmZpZxIcChRpZGxlX3RpbWVvdXRfc2Vjb25kcxgEIAEoBRIZChFoZWFsdGhfY2hlY2tfcGF0aBgFIAEoCRIMCgRwb3J0GAYgASgFIk4KDlJlZ2lvbmFsQ29uZmlnEg4KBnJlZ2lvbhgBIAEoCRIVCg1taW5faW5zdGFuY2VzGAIgASgFEhUKDW1heF9pbnN0YW5jZXMYAyABKAUiTQoPUm9sbGJhY2tSZXF1ZXN0EhwKFHNvdXJjZV9kZXBsb3ltZW50X2lkGAEgASgJEhwKFHRhcmdldF9kZXBsb3ltZW50X2lkGAIgASgJIhIKEFJvbGxiYWNrUmVzcG9uc2UiLgoOUHJvbW90ZVJlcXVlc3QSHAoUdGFyZ2V0X2RlcGxveW1lbnRfaWQYASABKAkiEQoPUHJvbW90ZVJlc3BvbnNlKu8BChBEZXBsb3ltZW50U3RhdHVzEiEKHURFUExPWU1FTlRfU1RBVFVTX1VOU1BFQ0lGSUVEEAASHQoZREVQTE9ZTUVOVF9TVEFUVVNfUEVORElORxABEh4KGkRFUExPWU1FTlRfU1RBVFVTX0JVSUxESU5HEAISHwobREVQTE9ZTUVOVF9TVEFUVVNfREVQTE9ZSU5HEAMSHQoZREVQTE9ZTUVOVF9TVEFUVVNfTkVUV09SSxAEEhsKF0RFUExPWU1FTlRfU1RBVFVTX1JFQURZEAUSHAoYREVQTE9ZTUVOVF9TVEFUVVNfRkFJTEVEEAYqWgoKU291cmNlVHlwZRIbChdTT1VSQ0VfVFlQRV9VTlNQRUNJRklFRBAAEhMKD1NPVVJDRV9UWVBFX0dJVBABEhoKFlNPVVJDRV9UWVBFX0NMSV9VUExPQUQQAjLDAgoRRGVwbG95bWVudFNlcnZpY2USWQoQQ3JlYXRlRGVwbG95bWVudBIgLmN0cmwudjEuQ3JlYXRlRGVwbG95bWVudFJlcXVlc3QaIS5jdHJsLnYxLkNyZWF0ZURlcGxveW1lbnRSZXNwb25zZSIAElAKDUdldERlcGxveW1lbnQSHS5jdHJsLnYxLkdldERlcGxveW1lbnRSZXF1ZXN0Gh4uY3RybC52MS5HZXREZXBsb3ltZW50UmVzcG9uc2UiABJBCghSb2xsYmFjaxIYLmN0cmwudjEuUm9sbGJhY2tSZXF1ZXN0GhkuY3RybC52MS5Sb2xsYmFja1Jlc3BvbnNlIgASPgoHUHJvbW90ZRIXLmN0cmwudjEuUHJvbW90ZVJlcXVlc3QaGC5jdHJsLnYxLlByb21vdGVSZXNwb25zZSIAQjZaNGdpdGh1Yi5jb20vdW5rZXllZC91bmtleS9nby9nZW4vcHJvdG8vY3RybC92MTtjdHJsdjFiBnByb3RvMw", + "ChhjdHJsL3YxL2RlcGxveW1lbnQucHJvdG8SB2N0cmwudjEi8QIKF0NyZWF0ZURlcGxveW1lbnRSZXF1ZXN0EhQKDHdvcmtzcGFjZV9pZBgBIAEoCRISCgpwcm9qZWN0X2lkGAIgASgJEg4KBmJyYW5jaBgDIAEoCRIYChBlbnZpcm9ubWVudF9zbHVnGAQgASgJEigKC3NvdXJjZV90eXBlGAUgASgOMhMuY3RybC52MS5Tb3VyY2VUeXBlEhQKDGRvY2tlcl9pbWFnZRgGIAEoCRIWCg5naXRfY29tbWl0X3NoYRgHIAEoCRIaChJnaXRfY29tbWl0X21lc3NhZ2UYCCABKAkSIAoYZ2l0X2NvbW1pdF9hdXRob3JfaGFuZGxlGAkgASgJEiQKHGdpdF9jb21taXRfYXV0aG9yX2F2YXRhcl91cmwYCiABKAkSHAoUZ2l0X2NvbW1pdF90aW1lc3RhbXAYCyABKAMSGAoLa2V5c3BhY2VfaWQYDCABKAlIAIgBAUIOCgxfa2V5c3BhY2VfaWQiXAoYQ3JlYXRlRGVwbG95bWVudFJlc3BvbnNlEhUKDWRlcGxveW1lbnRfaWQYASABKAkSKQoGc3RhdHVzGAIgASgOMhkuY3RybC52MS5EZXBsb3ltZW50U3RhdHVzIi0KFEdldERlcGxveW1lbnRSZXF1ZXN0EhUKDWRlcGxveW1lbnRfaWQYASABKAkiQAoVR2V0RGVwbG95bWVudFJlc3BvbnNlEicKCmRlcGxveW1lbnQYASABKAsyEy5jdHJsLnYxLkRlcGxveW1lbnQiiAUKCkRlcGxveW1lbnQSCgoCaWQYASABKAkSFAoMd29ya3NwYWNlX2lkGAIgASgJEhIKCnByb2plY3RfaWQYAyABKAkSFgoOZW52aXJvbm1lbnRfaWQYBCABKAkSFgoOZ2l0X2NvbW1pdF9zaGEYBSABKAkSEgoKZ2l0X2JyYW5jaBgGIAEoCRIpCgZzdGF0dXMYByABKA4yGS5jdHJsLnYxLkRlcGxveW1lbnRTdGF0dXMSFQoNZXJyb3JfbWVzc2FnZRgIIAEoCRJMChVlbnZpcm9ubWVudF92YXJpYWJsZXMYCSADKAsyLS5jdHJsLnYxLkRlcGxveW1lbnQuRW52aXJvbm1lbnRWYXJpYWJsZXNFbnRyeRIjCgh0b3BvbG9neRgKIAEoCzIRLmN0cmwudjEuVG9wb2xvZ3kSEgoKY3JlYXRlZF9hdBgLIAEoAxISCgp1cGRhdGVkX2F0GAwgASgDEhEKCWhvc3RuYW1lcxgNIAMoCRIXCg9yb290ZnNfaW1hZ2VfaWQYDiABKAkSEAoIYnVpbGRfaWQYDyABKAkSJgoFc3RlcHMYECADKAsyFy5jdHJsLnYxLkRlcGxveW1lbnRTdGVwEhoKEmdpdF9jb21taXRfbWVzc2FnZRgRIAEoCRIgChhnaXRfY29tbWl0X2F1dGhvcl9oYW5kbGUYEiABKAkSJAocZ2l0X2NvbW1pdF9hdXRob3JfYXZhdGFyX3VybBgTIAEoCRIcChRnaXRfY29tbWl0X3RpbWVzdGFtcBgUIAEoAxo7ChlFbnZpcm9ubWVudFZhcmlhYmxlc0VudHJ5EgsKA2tleRgBIAEoCRINCgV2YWx1ZRgCIAEoCToCOAEiXAoORGVwbG95bWVudFN0ZXASDgoGc3RhdHVzGAEgASgJEg8KB21lc3NhZ2UYAiABKAkSFQoNZXJyb3JfbWVzc2FnZRgDIAEoCRISCgpjcmVhdGVkX2F0GAQgASgDIqYBCghUb3BvbG9neRIWCg5jcHVfbWlsbGljb3JlcxgBIAEoBRIRCgltZW1vcnlfbWIYAiABKAUSKAoHcmVnaW9ucxgDIAMoCzIXLmN0cmwudjEuUmVnaW9uYWxDb25maWcSHAoUaWRsZV90aW1lb3V0X3NlY29uZHMYBCABKAUSGQoRaGVhbHRoX2NoZWNrX3BhdGgYBSABKAkSDAoEcG9ydBgGIAEoBSJOCg5SZWdpb25hbENvbmZpZxIOCgZyZWdpb24YASABKAkSFQoNbWluX2luc3RhbmNlcxgCIAEoBRIVCg1tYXhfaW5zdGFuY2VzGAMgASgFIk0KD1JvbGxiYWNrUmVxdWVzdBIcChRzb3VyY2VfZGVwbG95bWVudF9pZBgBIAEoCRIcChR0YXJnZXRfZGVwbG95bWVudF9pZBgCIAEoCSISChBSb2xsYmFja1Jlc3BvbnNlIi4KDlByb21vdGVSZXF1ZXN0EhwKFHRhcmdldF9kZXBsb3ltZW50X2lkGAEgASgJIhEKD1Byb21vdGVSZXNwb25zZSrvAQoQRGVwbG95bWVudFN0YXR1cxIhCh1ERVBMT1lNRU5UX1NUQVRVU19VTlNQRUNJRklFRBAAEh0KGURFUExPWU1FTlRfU1RBVFVTX1BFTkRJTkcQARIeChpERVBMT1lNRU5UX1NUQVRVU19CVUlMRElORxACEh8KG0RFUExPWU1FTlRfU1RBVFVTX0RFUExPWUlORxADEh0KGURFUExPWU1FTlRfU1RBVFVTX05FVFdPUksQBBIbChdERVBMT1lNRU5UX1NUQVRVU19SRUFEWRAFEhwKGERFUExPWU1FTlRfU1RBVFVTX0ZBSUxFRBAGKloKClNvdXJjZVR5cGUSGwoXU09VUkNFX1RZUEVfVU5TUEVDSUZJRUQQABITCg9TT1VSQ0VfVFlQRV9HSVQQARIaChZTT1VSQ0VfVFlQRV9DTElfVVBMT0FEEAIywwIKEURlcGxveW1lbnRTZXJ2aWNlElkKEENyZWF0ZURlcGxveW1lbnQSIC5jdHJsLnYxLkNyZWF0ZURlcGxveW1lbnRSZXF1ZXN0GiEuY3RybC52MS5DcmVhdGVEZXBsb3ltZW50UmVzcG9uc2UiABJQCg1HZXREZXBsb3ltZW50Eh0uY3RybC52MS5HZXREZXBsb3ltZW50UmVxdWVzdBoeLmN0cmwudjEuR2V0RGVwbG95bWVudFJlc3BvbnNlIgASQQoIUm9sbGJhY2sSGC5jdHJsLnYxLlJvbGxiYWNrUmVxdWVzdBoZLmN0cmwudjEuUm9sbGJhY2tSZXNwb25zZSIAEj4KB1Byb21vdGUSFy5jdHJsLnYxLlByb21vdGVSZXF1ZXN0GhguY3RybC52MS5Qcm9tb3RlUmVzcG9uc2UiAEI2WjRnaXRodWIuY29tL3Vua2V5ZWQvdW5rZXkvZ28vZ2VuL3Byb3RvL2N0cmwvdjE7Y3RybHYxYgZwcm90bzM", ); /** @@ -65,34 +65,29 @@ export type CreateDeploymentRequest = Message<"ctrl.v1.CreateDeploymentRequest"> */ gitCommitMessage: string; - /** - * @generated from field: string git_commit_author_name = 9; - */ - gitCommitAuthorName: string; - /** * TODO: Add GitHub API integration to lookup username/avatar from email * - * @generated from field: string git_commit_author_username = 10; + * @generated from field: string git_commit_author_handle = 9; */ - gitCommitAuthorUsername: string; + gitCommitAuthorHandle: string; /** - * @generated from field: string git_commit_author_avatar_url = 11; + * @generated from field: string git_commit_author_avatar_url = 10; */ gitCommitAuthorAvatarUrl: string; /** * Unix epoch milliseconds * - * @generated from field: int64 git_commit_timestamp = 12; + * @generated from field: int64 git_commit_timestamp = 11; */ gitCommitTimestamp: bigint; /** * Keyspace ID for authentication * - * @generated from field: optional string keyspace_id = 13; + * @generated from field: optional string keyspace_id = 12; */ keyspaceId?: string; }; @@ -275,27 +270,22 @@ export type Deployment = Message<"ctrl.v1.Deployment"> & { */ gitCommitMessage: string; - /** - * @generated from field: string git_commit_author_name = 18; - */ - gitCommitAuthorName: string; - /** * Removed: email is PII and not stored * - * @generated from field: string git_commit_author_username = 20; + * @generated from field: string git_commit_author_handle = 18; */ - gitCommitAuthorUsername: string; + gitCommitAuthorHandle: string; /** - * @generated from field: string git_commit_author_avatar_url = 21; + * @generated from field: string git_commit_author_avatar_url = 19; */ gitCommitAuthorAvatarUrl: string; /** * Unix epoch milliseconds * - * @generated from field: int64 git_commit_timestamp = 22; + * @generated from field: int64 git_commit_timestamp = 20; */ gitCommitTimestamp: bigint; };