diff --git a/QUICKSTART-DEPLOY.md b/QUICKSTART-DEPLOY.md new file mode 100644 index 0000000000..5e34c271ad --- /dev/null +++ b/QUICKSTART-DEPLOY.md @@ -0,0 +1,102 @@ +# Quickstart Guide + +This guide will help you get the Unkey deployment platform up and running locally for development and testing. + +## Prerequisites + +- Docker and Docker Compose +- A terminal/command line + +## Step 1: Start the Platform + +1. Start all services using Docker Compose: + +```bash +docker-compose up -d +``` + +This will start: + +- MySQL database (port 3306) +- Dashboard (port 3000) +- Control plane services +- Supporting infrastructure + +2. Wait for all services to be healthy (this may take 1-2 minutes): + +```bash +docker-compose ps +``` + +## Step 2: Set Up Your Workspace + +1. Open your browser and navigate to the dashboard: + +``` +http://localhost:3000 +``` + +2. Sign in or create an account through the authentication flow + +3. Once logged in, you'll automatically have a workspace created. Navigate to: + +``` +http://localhost:3000/projects +``` + +4. Create a new project by filling out the form: + + - **Name**: Choose any name (e.g., "My Test App") + - **Slug**: This will auto-generate based on the name + - **Git URL**: Optional, leave blank for testing + +5. After creating the project, **copy the Project ID** from the project details. It will look like: + +``` +proj_xxxxxxxxxxxxxxxxxx +``` + +6. Also note your **Workspace ID** (you can find this settings). It will look like: + +``` +ws_xxxxxxxxxxxxxxxxxx +``` + +## Step 3: Deploy a Version + +1. Navigate to the go directory: + +```bash +cd go +``` + +2. Create a version using the CLI with your copied IDs: + +```bash +go run . version create \ + --context=./demo_api \ + --workspace-id=YOUR_WORKSPACE_ID \ + --project-id=YOUR_PROJECT_ID +``` + +Keep the context as shown, there's a demo api in that folder. +Replace `YOUR_WORKSPACE_ID` and `YOUR_PROJECT_ID` with the actual values you copied from the dashboard. + +3. The CLI will show real-time progress as your deployment goes through these stages: + - Downloading Docker image + - Building rootfs + - Uploading rootfs + - Creating VM + - Booting VM + - Assigning domains + - Completed + +## Step 4: View Your Deployment + +1. Return to the dashboard and navigate to: + +``` +http://localhost:3000/versions +http://localhost:3000/deployments +``` + diff --git a/apps/dashboard/Dockerfile b/apps/dashboard/Dockerfile new file mode 100644 index 0000000000..e8f39874c3 --- /dev/null +++ b/apps/dashboard/Dockerfile @@ -0,0 +1,24 @@ +FROM node:lts + +WORKDIR /unkey + +# Install pnpm +RUN npm install -g pnpm + +# Copy everything +COPY . . + +# Install dependencies +RUN pnpm install -r + +# Move to dashboard directory +WORKDIR /unkey/apps/dashboard +RUN pnpm build +EXPOSE 3000 + +# Set hostname to 0.0.0.0 to allow external connections +ENV HOSTNAME="0.0.0.0" +ENV PORT=3000 + +# Run in development mode for now +CMD ["pnpm","start", "--hostname", "0.0.0.0"] diff --git a/apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx b/apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx new file mode 100644 index 0000000000..704b796ea2 --- /dev/null +++ b/apps/dashboard/app/(app)/projects/[projectId]/branches/page.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { trpc } from "@/lib/trpc/client"; +import { useParams } from "next/navigation"; + +export default function ProjectBranchesPage() { + const params = useParams(); + const projectId = params?.projectId as string; + + const { data, isLoading, error } = trpc.project.branches.useQuery( + { + projectId, + }, + { + enabled: !!projectId, // Only run query if projectId exists + }, + ); + + if (!projectId) { + return ( +
+

Invalid project ID

+
+ ); + } + + if (isLoading) { + return ( +
+

Loading...

+
+ ); + } + + if (error) { + return ( +
+

Error

+

Failed to load branches: {error.message}

+
+ ); + } + + return ( +
+

Branches for {data?.project.name}

+

+ Project: {data?.project.slug} ({data?.project.id}) +

+ +

All Branches

+ {data?.branches && data.branches.length > 0 ? ( +
{JSON.stringify(data.branches, null, 2)}
+ ) : ( +

No branches found for this project.

+ )} +
+ ); +} diff --git a/apps/dashboard/app/(app)/projects/page.tsx b/apps/dashboard/app/(app)/projects/page.tsx new file mode 100644 index 0000000000..a3887f0710 --- /dev/null +++ b/apps/dashboard/app/(app)/projects/page.tsx @@ -0,0 +1,137 @@ +"use client"; + +import { trpc } from "@/lib/trpc/client"; +import { useState } from "react"; + +export default function ProjectsPage() { + const [name, setName] = useState(""); + const [slug, setSlug] = useState(""); + const [gitUrl, setGitUrl] = useState(""); + + const { data, isLoading, refetch } = trpc.project.list.useQuery(); + const createProject = trpc.project.create.useMutation({ + onSuccess: () => { + refetch(); + setName(""); + setSlug(""); + setGitUrl(""); + }, + }); + + const handleSubmit = (e: React.FormEvent) => { + e.preventDefault(); + if (!name || !slug) { + return; + } + + createProject.mutate({ + name, + slug, + gitRepositoryUrl: gitUrl || undefined, + }); + }; + + return ( +
+

Projects

+ +

Existing Projects

+ {isLoading ? ( +

Loading...

+ ) : data?.projects && data.projects.length > 0 ? ( +
+ {data.projects.map((project) => ( +
+

{project.name}

+

Slug: {project.slug}

+

ID: {project.id}

+ {project.gitRepositoryUrl &&

Git: {project.gitRepositoryUrl}

} +

Created: {new Date(project.createdAt).toLocaleString()}

+ + View Branches → + +
+ ))} +
+ ) : ( +

No projects found.

+ )} + +

Create New Project

+
+
+ +
+ +
+ +
+ +
+ +
+ + +
+ + {createProject.error &&

Error: {createProject.error.message}

} +
+ ); +} diff --git a/apps/dashboard/app/(app)/versions/page.tsx b/apps/dashboard/app/(app)/versions/page.tsx new file mode 100644 index 0000000000..7074f8f88e --- /dev/null +++ b/apps/dashboard/app/(app)/versions/page.tsx @@ -0,0 +1,100 @@ +"use client"; + +import { trpc } from "@/lib/trpc/client"; + +export default function VersionsPage() { + const { data, isLoading, error } = trpc.version.list.useQuery(); + + if (isLoading) { + return ( +
+

Loading...

+
+ ); + } + + if (error) { + return ( +
+

Error

+

Failed to load versions: {error.message}

+
+ ); + } + + return ( +
+

All Versions

+ + {data?.versions && data.versions.length > 0 ? ( +
+ {data.versions.map((version) => ( +
+

Version {version.id}

+

+ Status: {version.status} +

+ {version.gitCommitSha && ( +

+ Git Commit: {version.gitCommitSha} +

+ )} + {version.gitBranch && ( +

+ Git Branch: {version.gitBranch} +

+ )} + {version.rootfsImageId && ( +

+ Rootfs Image: {version.rootfsImageId} +

+ )} + {version.buildId && ( +

+ Build ID: {version.buildId} +

+ )} + {version.project && ( +

+ Project: {version.project.name} ({version.project.slug}) +

+ )} + {version.branch && ( +

+ Branch: {version.branch.name} +

+ )} +

+ Created: {new Date(version.createdAt).toLocaleString()} +

+ {version.updatedAt && ( +

+ Updated: {new Date(version.updatedAt).toLocaleString()} +

+ )} +
+ ))} +
+ ) : ( +
+

No versions found.

+

Create a version using the CLI:

+ + ./bin/unkey create --workspace-id=ws_local_root --project-id=YOUR_PROJECT_ID + --branch=main + +
+ )} +
+ ); +} diff --git a/apps/dashboard/lib/db.ts b/apps/dashboard/lib/db.ts index 32b25f3470..631d3d0ebd 100644 --- a/apps/dashboard/lib/db.ts +++ b/apps/dashboard/lib/db.ts @@ -13,8 +13,8 @@ export const db = drizzle( // biome-ignore lint/suspicious/noExplicitAny: safe to leave (init as any).cache = undefined; // Remove cache header const u = new URL(url); - // set protocol to http if localhost for CI testing - if (u.host.includes("localhost")) { + // set protocol to http if localhost or docker planetscale service for CI testing + if (u.host.includes("localhost") || u.host === "planetscale:3900") { u.protocol = "http"; } return fetch(u, init); diff --git a/apps/dashboard/lib/trpc/routers/index.ts b/apps/dashboard/lib/trpc/routers/index.ts index e0cf974086..825af4385d 100644 --- a/apps/dashboard/lib/trpc/routers/index.ts +++ b/apps/dashboard/lib/trpc/routers/index.ts @@ -73,6 +73,7 @@ import { updateMembership, } from "./org"; import { createPlainIssue } from "./plain"; +import { projectRouter } from "./project"; import { createNamespace } from "./ratelimit/createNamespace"; import { createOverride } from "./ratelimit/createOverride"; import { deleteNamespace } from "./ratelimit/deleteNamespace"; @@ -109,6 +110,7 @@ import { uncancelSubscription } from "./stripe/uncancelSubscription"; import { updateSubscription } from "./stripe/updateSubscription"; import { getCurrentUser, listMemberships, switchOrg } from "./user"; import { vercelRouter } from "./vercel"; +import { versionRouter } from "./version"; import { changeWorkspaceName } from "./workspace/changeName"; import { createWorkspace } from "./workspace/create"; import { optWorkspaceIntoBeta } from "./workspace/optIntoBeta"; @@ -302,6 +304,8 @@ export const router = t.router({ query: queryIdentities, search: searchIdentities, }), + project: projectRouter, + version: versionRouter, }); // export type definition of API diff --git a/apps/dashboard/lib/trpc/routers/project/branches.ts b/apps/dashboard/lib/trpc/routers/project/branches.ts new file mode 100644 index 0000000000..18f5562a36 --- /dev/null +++ b/apps/dashboard/lib/trpc/routers/project/branches.ts @@ -0,0 +1,58 @@ +import { db } from "@/lib/db"; +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { requireUser, requireWorkspace, t } from "../../trpc"; + +export const listProjectBranches = t.procedure + .use(requireUser) + .use(requireWorkspace) + .input( + z.object({ + projectId: z.string(), + }), + ) + .query(async ({ ctx, input }) => { + try { + // First verify the project exists and belongs to this workspace + const project = await db.query.projects.findFirst({ + where: (table, { eq, and }) => + and(eq(table.id, input.projectId), eq(table.workspaceId, ctx.workspace.id)), + }); + + if (!project) { + throw new TRPCError({ + code: "NOT_FOUND", + message: "Project not found", + }); + } + + // Get all branches for this project + const branches = await db.query.branches.findMany({ + where: (table, { eq }) => eq(table.projectId, input.projectId), + orderBy: (table, { desc }) => [desc(table.createdAt)], + }); + + return { + project: { + id: project.id, + name: project.name, + slug: project.slug, + }, + branches: branches.map((branch) => ({ + id: branch.id, + name: branch.name, + createdAt: branch.createdAt, + updatedAt: branch.updatedAt, + })), + }; + } catch (error) { + if (error instanceof TRPCError) { + throw error; + } + + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to fetch branches", + }); + } + }); diff --git a/apps/dashboard/lib/trpc/routers/project/create.ts b/apps/dashboard/lib/trpc/routers/project/create.ts new file mode 100644 index 0000000000..bdc51d125e --- /dev/null +++ b/apps/dashboard/lib/trpc/routers/project/create.ts @@ -0,0 +1,73 @@ +import { db, schema } from "@/lib/db"; +import { TRPCError } from "@trpc/server"; +import { z } from "zod"; +import { requireUser, requireWorkspace, t } from "../../trpc"; + +export const createProject = t.procedure + .use(requireUser) + .use(requireWorkspace) + .input( + z.object({ + name: z.string().min(1, "Project name is required").max(256, "Project name too long"), + slug: z + .string() + .min(1, "Project slug is required") + .max(256, "Project slug too long") + .regex( + /^[a-z0-9-]+$/, + "Project slug must contain only lowercase letters, numbers, and hyphens", + ), + gitRepositoryUrl: z.string().url().optional().or(z.literal("")), + }), + ) + .mutation(async ({ ctx, input }) => { + try { + // Check if slug already exists in workspace + const existingProject = await db.query.projects.findFirst({ + where: (table, { eq, and }) => + and(eq(table.workspaceId, ctx.workspace.id), eq(table.slug, input.slug)), + }); + + if (existingProject) { + throw new TRPCError({ + code: "CONFLICT", + message: "A project with this slug already exists", + }); + } + + const projectId = `proj_${Math.random().toString(36).substring(2)}${Date.now().toString(36)}`; + const now = Date.now(); + + await db.transaction(async (tx) => { + // Insert new project + await tx.insert(schema.projects).values({ + id: projectId, + workspaceId: ctx.workspace.id, + partitionId: "part_default", // Default partition for now + name: input.name, + slug: input.slug, + gitRepositoryUrl: input.gitRepositoryUrl || null, + deleteProtection: false, + }); + + // TODO: Add proper audit logging for projects when the audit system supports it + }); + + return { + id: projectId, + name: input.name, + slug: input.slug, + gitRepositoryUrl: input.gitRepositoryUrl, + createdAt: now, + }; + } catch (error) { + if (error instanceof TRPCError) { + throw error; + } + + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to create project", + }); + } + }); diff --git a/apps/dashboard/lib/trpc/routers/project/index.ts b/apps/dashboard/lib/trpc/routers/project/index.ts new file mode 100644 index 0000000000..9cb72843c4 --- /dev/null +++ b/apps/dashboard/lib/trpc/routers/project/index.ts @@ -0,0 +1,10 @@ +import { t } from "../../trpc"; +import { listProjectBranches } from "./branches"; +import { createProject } from "./create"; +import { listProjects } from "./list"; + +export const projectRouter = t.router({ + list: listProjects, + create: createProject, + branches: listProjectBranches, +}); diff --git a/apps/dashboard/lib/trpc/routers/project/list.ts b/apps/dashboard/lib/trpc/routers/project/list.ts new file mode 100644 index 0000000000..d6904073f3 --- /dev/null +++ b/apps/dashboard/lib/trpc/routers/project/list.ts @@ -0,0 +1,31 @@ +import { db } from "@/lib/db"; +import { TRPCError } from "@trpc/server"; +import { requireUser, requireWorkspace, t } from "../../trpc"; + +export const listProjects = t.procedure + .use(requireUser) + .use(requireWorkspace) + .query(async ({ ctx }) => { + try { + const projects = await db.query.projects.findMany({ + where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id), + orderBy: (table, { desc }) => [desc(table.createdAt)], + }); + + return { + projects: projects.map((project) => ({ + id: project.id, + name: project.name, + slug: project.slug, + gitRepositoryUrl: project.gitRepositoryUrl, + createdAt: project.createdAt, + updatedAt: project.updatedAt, + })), + }; + } catch (_error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to fetch projects", + }); + } + }); diff --git a/apps/dashboard/lib/trpc/routers/version/index.ts b/apps/dashboard/lib/trpc/routers/version/index.ts new file mode 100644 index 0000000000..938ebcb78c --- /dev/null +++ b/apps/dashboard/lib/trpc/routers/version/index.ts @@ -0,0 +1,6 @@ +import { t } from "../../trpc"; +import { listVersions } from "./list"; + +export const versionRouter = t.router({ + list: listVersions, +}); diff --git a/apps/dashboard/lib/trpc/routers/version/list.ts b/apps/dashboard/lib/trpc/routers/version/list.ts new file mode 100644 index 0000000000..bd8071acfb --- /dev/null +++ b/apps/dashboard/lib/trpc/routers/version/list.ts @@ -0,0 +1,51 @@ +import { db } from "@/lib/db"; +import { TRPCError } from "@trpc/server"; +import { requireUser, requireWorkspace, t } from "../../trpc"; + +export const listVersions = t.procedure + .use(requireUser) + .use(requireWorkspace) + .query(async ({ ctx }) => { + try { + // Get all versions for this workspace with project and branch info + const versions = await db.query.versions.findMany({ + where: (table, { eq }) => eq(table.workspaceId, ctx.workspace.id), + orderBy: (table, { desc }) => [desc(table.createdAt)], + with: { + project: true, + branch: true, + }, + }); + + return { + versions: versions.map((version) => ({ + id: version.id, + status: version.status, + gitCommitSha: version.gitCommitSha, + gitBranch: version.gitBranch, + rootfsImageId: version.rootfsImageId, + buildId: version.buildId, + createdAt: version.createdAt, + updatedAt: version.updatedAt, + project: version.project + ? { + id: version.project.id, + name: version.project.name, + slug: version.project.slug, + } + : null, + branch: version.branch + ? { + id: version.branch.id, + name: version.branch.name, + } + : null, + })), + }; + } catch (_error) { + throw new TRPCError({ + code: "INTERNAL_SERVER_ERROR", + message: "Failed to fetch versions", + }); + } + }); diff --git a/deployment/04-seed-workspace.sql b/deployment/04-seed-workspace.sql new file mode 100644 index 0000000000..7863a300ba --- /dev/null +++ b/deployment/04-seed-workspace.sql @@ -0,0 +1,64 @@ +-- Seed the root workspace and API for local development +-- This matches what the tools/local CLI creates + +USE unkey; + +-- Insert root workspace +INSERT INTO workspaces ( + id, + org_id, + name, + created_at_m, + beta_features, + features +) VALUES ( + 'ws_local_root', + 'user_REPLACE_ME', + 'Unkey', + UNIX_TIMESTAMP() * 1000, + '{}', + '{}' +) ON DUPLICATE KEY UPDATE created_at_m = UNIX_TIMESTAMP() * 1000; + +-- Insert quotas for the workspace +INSERT INTO quotas ( + workspace_id, + requests_per_month, + audit_logs_retention_days, + logs_retention_days, + team +) VALUES ( + 'ws_local_root', + 150000, + 30, + 7, + false +) ON DUPLICATE KEY UPDATE workspace_id = 'ws_local_root'; + +-- Insert root keyspace +INSERT INTO key_auth ( + id, + workspace_id, + created_at_m +) VALUES ( + 'ks_local_root_keys', + 'ws_local_root', + UNIX_TIMESTAMP() * 1000 +) ON DUPLICATE KEY UPDATE created_at_m = UNIX_TIMESTAMP() * 1000; + +-- Insert root API +INSERT INTO apis ( + id, + name, + workspace_id, + auth_type, + key_auth_id, + created_at_m +) VALUES ( + 'api_local_root_keys', + 'Unkey', + 'ws_local_root', + 'key', + 'ks_local_root_keys', + UNIX_TIMESTAMP() * 1000 +) ON DUPLICATE KEY UPDATE created_at_m = UNIX_TIMESTAMP() * 1000; \ No newline at end of file diff --git a/deployment/Dockerfile.mysql b/deployment/Dockerfile.mysql index e5cb2523cc..8c4e30d155 100644 --- a/deployment/Dockerfile.mysql +++ b/deployment/Dockerfile.mysql @@ -7,3 +7,6 @@ COPY deployment/init-databases.sql /docker-entrypoint-initdb.d/00-init-databases COPY go/pkg/db/schema.sql /docker-entrypoint-initdb.d/01-main-schema.sql # COPY go/pkg/partition/schema.sql /docker-entrypoint-initdb.d/02-partition-schema.sql COPY go/pkg/hydra/store/schema.sql /docker-entrypoint-initdb.d/03-hydra-schema.sql + +# Copy seed data for local development +COPY deployment/04-seed-workspace.sql /docker-entrypoint-initdb.d/04-seed-workspace.sql diff --git a/deployment/docker-compose.yaml b/deployment/docker-compose.yaml index 3b339afe02..401adf5ff2 100644 --- a/deployment/docker-compose.yaml +++ b/deployment/docker-compose.yaml @@ -187,7 +187,7 @@ services: dockerfile: Dockerfile args: VERSION: "latest" - container_name: unkey-ctrl + container_name: ctrl command: ["run", "ctrl"] ports: - "7091:7091" @@ -209,7 +209,7 @@ services: container_name: otel hostname: otel ports: - - 3000:3000 + - 3001:3000 - 4317:4317 - 4318:4318 @@ -223,6 +223,39 @@ services: depends_on: - apiv2 + dashboard: + build: + context: .. + dockerfile: ./apps/dashboard/Dockerfile + container_name: unkey-dashboard + ports: + - "3000:3000" + depends_on: + - planetscale + - agent + environment: + # Database configuration + DATABASE_HOST: "planetscale:3900" + DATABASE_USERNAME: "unkey" + DATABASE_PASSWORD: "password" + + # Auth configuration + AUTH_PROVIDER: "local" + + # Agent configuration + AGENT_URL: "http://agent:8080" + AGENT_TOKEN: "agent-auth-secret" + + # Clickhouse configuration + CLICKHOUSE_URL: "http://default:password@clickhouse:8123" + + # Environment + NODE_ENV: "production" + + # Bootstrap workspace/API IDs + UNKEY_WORKSPACE_ID: "ws_local_root" + UNKEY_API_ID: "api_local_root_keys" + volumes: mysql: clickhouse: diff --git a/go/apps/ctrl/services/version/create_version.go b/go/apps/ctrl/services/version/create_version.go index 47b36f9348..77080ea3fa 100644 --- a/go/apps/ctrl/services/version/create_version.go +++ b/go/apps/ctrl/services/version/create_version.go @@ -3,6 +3,7 @@ package version import ( "context" "database/sql" + "fmt" "time" "connectrpc.com/connect" @@ -16,20 +17,87 @@ func (s *Service) CreateVersion( ctx context.Context, req *connect.Request[ctrlv1.CreateVersionRequest], ) (*connect.Response[ctrlv1.CreateVersionResponse], error) { + // Validate workspace exists + _, err := db.Query.FindWorkspaceByID(ctx, s.db.RO(), req.Msg.GetWorkspaceId()) + if err != nil { + if err == sql.ErrNoRows { + return nil, connect.NewError(connect.CodeNotFound, + fmt.Errorf("workspace not found: %s", req.Msg.GetWorkspaceId())) + } + return nil, connect.NewError(connect.CodeInternal, err) + } + + // Validate project exists and belongs to workspace + project, err := db.Query.FindProjectById(ctx, s.db.RO(), req.Msg.GetProjectId()) + if err != nil { + if err == sql.ErrNoRows { + return nil, connect.NewError(connect.CodeNotFound, + fmt.Errorf("project not found: %s", req.Msg.GetProjectId())) + } + return nil, connect.NewError(connect.CodeInternal, err) + } + + // Verify project belongs to the specified workspace + if project.WorkspaceID != req.Msg.GetWorkspaceId() { + return nil, connect.NewError(connect.CodeInvalidArgument, + fmt.Errorf("project %s does not belong to workspace %s", + req.Msg.GetProjectId(), req.Msg.GetWorkspaceId())) + } + + // Create or find branch record + branchName := req.Msg.GetBranch() + if branchName == "" { + branchName = project.DefaultBranch.String + if branchName == "" { + branchName = "main" // fallback default + } + } + + var branchID string + branch, err := db.Query.FindBranchByProjectName(ctx, s.db.RO(), db.FindBranchByProjectNameParams{ + ProjectID: req.Msg.GetProjectId(), + Name: branchName, + }) + if err != nil { + if err == sql.ErrNoRows { + // Branch doesn't exist, create it + branchID = uid.New("branch") + err = db.Query.InsertBranch(ctx, s.db.RW(), db.InsertBranchParams{ + ID: branchID, + WorkspaceID: req.Msg.GetWorkspaceId(), + ProjectID: req.Msg.GetProjectId(), + Name: branchName, + CreatedAt: time.Now().UnixMilli(), + UpdatedAt: sql.NullInt64{Int64: time.Now().UnixMilli(), Valid: true}, + }) + if err != nil { + return nil, connect.NewError(connect.CodeInternal, + fmt.Errorf("failed to create branch: %w", err)) + } + s.logger.Info("created new branch", "branch_id", branchID, "name", branchName, "project_id", req.Msg.GetProjectId()) + } else { + return nil, connect.NewError(connect.CodeInternal, err) + } + } else { + // Branch exists, use it + branchID = branch.ID + s.logger.Info("using existing branch", "branch_id", branchID, "name", branchName, "project_id", req.Msg.GetProjectId()) + } + // Generate version ID versionID := uid.New(uid.VersionPrefix, 4) now := time.Now().UnixMilli() // Insert version into database - err := db.Query.InsertVersion(ctx, s.db.RW(), db.InsertVersionParams{ + err = db.Query.InsertVersion(ctx, s.db.RW(), db.InsertVersionParams{ ID: versionID, WorkspaceID: req.Msg.GetWorkspaceId(), ProjectID: req.Msg.GetProjectId(), - BranchID: sql.NullString{String: "", Valid: false}, // Branch resolution not implemented yet + BranchID: sql.NullString{String: branchID, Valid: true}, BuildID: sql.NullString{String: "", Valid: false}, // Build creation handled separately RootfsImageID: "", // Image handling not implemented yet GitCommitSha: sql.NullString{String: req.Msg.GetGitCommitSha(), Valid: req.Msg.GetGitCommitSha() != ""}, - GitBranch: sql.NullString{String: req.Msg.GetBranch(), Valid: req.Msg.GetBranch() != ""}, + GitBranch: sql.NullString{String: branchName, Valid: true}, ConfigSnapshot: []byte("{}"), // Configuration snapshot placeholder Status: db.VersionsStatusPending, CreatedAt: now, diff --git a/go/apps/ctrl/services/version/deploy_workflow.go b/go/apps/ctrl/services/version/deploy_workflow.go index e1a4478d32..1029a8192e 100644 --- a/go/apps/ctrl/services/version/deploy_workflow.go +++ b/go/apps/ctrl/services/version/deploy_workflow.go @@ -73,6 +73,7 @@ type DeploymentResult struct { Status string `json:"status"` } + // Run executes the complete build and deployment workflow func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) error { w.logger.Info("starting deployment workflow", @@ -247,14 +248,16 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro // MOCK: Bypassing metald CreateVm call due to missing VM infrastructure // TODO: Remove this mock and use real metald call once VM assets are available w.logger.Info("MOCK: Simulating VM creation request", "docker_image", req.DockerImage) - + // Generate realistic mock VM ID and response mockVMID := uid.New("vm") // Generate mock VM ID resp := &vmprovisionerv1.CreateVmResponse{ VmId: mockVMID, State: vmprovisionerv1.VmState_VM_STATE_CREATED, - } +======= + } + w.logger.Info("MOCK: VM creation simulated successfully", "vm_id", mockVMID, "docker_image", req.DockerImage) w.logger.Info("VM created successfully", "vm_id", resp.VmId, "state", resp.State.String(), "docker_image", req.DockerImage) @@ -360,7 +363,6 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro // MOCK: Bypassing metald GetVmInfo call - simulating realistic VM preparation // TODO: Remove this mock and use real metald call once VM assets are available w.logger.Info("MOCK: Simulating VM status request", "vm_id", createResult.VmId, "attempt", attempt) - // Simulate realistic VM preparation progression var mockState vmprovisionerv1.VmState if attempt <= 2 { @@ -370,9 +372,9 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro mockState = vmprovisionerv1.VmState_VM_STATE_CREATED w.logger.Info("MOCK: VM preparation complete", "vm_id", createResult.VmId, "attempt", attempt) } - + resp := &vmprovisionerv1.GetVmInfoResponse{ - VmId: createResult.VmId, + VmId: createResult.VmId, State: mockState, } @@ -407,13 +409,13 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro // MOCK: Bypassing metald BootVm call - simulating successful boot // TODO: Remove this mock and use real metald call once VM assets are available w.logger.Info("MOCK: Simulating VM boot request", "vm_id", createResult.VmId) - + // Simulate successful VM boot resp := &vmprovisionerv1.BootVmResponse{ Success: true, State: vmprovisionerv1.VmState_VM_STATE_RUNNING, } - + w.logger.Info("MOCK: VM boot simulated successfully", "vm_id", createResult.VmId) if !resp.Success { @@ -453,9 +455,9 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro // Generate hostnames for this deployment // Use Git info for hostname generation gitInfo := git.GetInfo() - branch := "main" // Default branch + branch := "main" // Default branch identifier := req.VersionID // Use full version ID as identifier - + if gitInfo.IsRepo { if gitInfo.Branch != "" { branch = gitInfo.Branch @@ -469,10 +471,10 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro // Replace underscores with dashes for valid hostname format cleanIdentifier := strings.ReplaceAll(identifier, "_", "-") hostname := fmt.Sprintf("%s-%s-%s.unkey.app", branch, cleanIdentifier, req.WorkspaceID) - + // Create route entry routeID := uid.New("route") - insertErr := db.Query.InsertRoute(stepCtx, w.db.RW(), db.InsertRouteParams{ + insertErr := db.Query.InsertHostnameRoute(stepCtx, w.db.RW(), db.InsertHostnameRouteParams{ ID: routeID, WorkspaceID: req.WorkspaceID, ProjectID: req.ProjectID, @@ -569,4 +571,4 @@ func (w *DeployWorkflow) Run(ctx hydra.WorkflowContext, req *DeployRequest) erro "docker_image", req.DockerImage) return nil -} +} \ No newline at end of file diff --git a/go/apps/ctrl/services/version/get_version.go b/go/apps/ctrl/services/version/get_version.go index 24b75c14ed..2e9bb3703e 100644 --- a/go/apps/ctrl/services/version/get_version.go +++ b/go/apps/ctrl/services/version/get_version.go @@ -80,7 +80,7 @@ func (s *Service) GetVersion( } // Fetch routes (hostnames) for this version - routes, err := db.Query.FindRoutesByVersionId(ctx, s.db.RO(), version.ID) + routes, err := db.Query.FindHostnameRoutesByVersionId(ctx, s.db.RO(), version.ID) if err != nil { s.logger.Warn("failed to fetch routes for version", "error", err, "version_id", version.ID) // Continue without hostnames rather than failing the entire request diff --git a/go/cmd/version/bootstrap.go b/go/cmd/version/bootstrap.go new file mode 100644 index 0000000000..5406cf3759 --- /dev/null +++ b/go/cmd/version/bootstrap.go @@ -0,0 +1,140 @@ +package version + +import ( + "context" + "database/sql" + "fmt" + "time" + + "github.com/unkeyed/unkey/go/pkg/db" + "github.com/unkeyed/unkey/go/pkg/otel/logging" + "github.com/unkeyed/unkey/go/pkg/uid" + "github.com/urfave/cli/v3" + _ "github.com/go-sql-driver/mysql" +) + +// TODO: REMOVE THIS ENTIRE FILE - This is a temporary bootstrap helper +// Remove once we have proper UI for project management + +var bootstrapProjectCmd = &cli.Command{ + Name: "bootstrap-project", + Usage: "TEMPORARY: Create a project for testing (remove once we have UI)", + Description: `TEMPORARY BOOTSTRAP HELPER - REMOVE ONCE WE HAVE PROPER UI + +This command directly creates a project in the database for testing purposes. +This bypasses proper API workflows and should be removed once we have a UI.`, + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "workspace-id", + Usage: "Workspace ID", + Required: true, + }, + &cli.StringFlag{ + Name: "slug", + Usage: "Project slug", + Value: "my-api", + Required: false, + }, + &cli.StringFlag{ + Name: "db-url", + Usage: "Database connection string", + Value: "root:password@tcp(localhost:3306)/unkey", + Required: false, + }, + }, + Action: bootstrapProjectAction, +} + +func bootstrapProjectAction(ctx context.Context, cmd *cli.Command) error { + logger := logging.New() + + workspaceID := cmd.String("workspace-id") + projectSlug := cmd.String("slug") + dbURL := cmd.String("db-url") + + fmt.Printf("🚧 TEMPORARY BOOTSTRAP - Creating project...\n") + fmt.Printf(" Workspace ID: %s\n", workspaceID) + fmt.Printf(" Project Slug: %s\n", projectSlug) + fmt.Println() + + // Connect to database (TEMPORARY - this should be done via API) + sqlDB, err := sql.Open("mysql", dbURL) + if err != nil { + return fmt.Errorf("failed to connect to database: %w", err) + } + defer sqlDB.Close() + + // Create workspace if it doesn't exist + _, err = db.Query.FindWorkspaceByID(ctx, sqlDB, workspaceID) + if err != nil { + if err == sql.ErrNoRows { + // Workspace doesn't exist, create it + fmt.Printf("📁 Creating workspace: %s\n", workspaceID) + now := time.Now().UnixMilli() + err = db.Query.InsertWorkspace(ctx, sqlDB, db.InsertWorkspaceParams{ + ID: workspaceID, + OrgID: "org_bootstrap", // hardcoded for bootstrap + Name: workspaceID, // use ID as name for simplicity + CreatedAt: now, + }) + if err != nil { + return fmt.Errorf("failed to create workspace: %w", err) + } + fmt.Printf("✅ Workspace created: %s\n", workspaceID) + } else { + return fmt.Errorf("failed to validate workspace: %w", err) + } + } else { + fmt.Printf("📁 Using existing workspace: %s\n", workspaceID) + } + + // Check if project already exists + _, err = db.Query.FindProjectByWorkspaceSlug(ctx, sqlDB, db.FindProjectByWorkspaceSlugParams{ + WorkspaceID: workspaceID, + Slug: projectSlug, + }) + if err == nil { + return fmt.Errorf("project with slug '%s' already exists in workspace '%s'", projectSlug, workspaceID) + } else if err != sql.ErrNoRows { + return fmt.Errorf("failed to check existing project: %w", err) + } + + // Generate project ID + projectID := uid.New("proj") + + // Create project + now := time.Now().UnixMilli() + err = db.Query.InsertProject(ctx, sqlDB, db.InsertProjectParams{ + ID: projectID, + WorkspaceID: workspaceID, + PartitionID: "part_default", // hardcoded for now + Name: projectSlug, // use slug as name for simplicity + Slug: projectSlug, + GitRepositoryUrl: sql.NullString{String: "", Valid: false}, + DefaultBranch: sql.NullString{String: "main", Valid: true}, + DeleteProtection: sql.NullBool{Bool: false, Valid: true}, + CreatedAt: now, + UpdatedAt: sql.NullInt64{Int64: now, Valid: true}, + }) + if err != nil { + return fmt.Errorf("failed to create project: %w", err) + } + + fmt.Printf("✅ Project created successfully!\n") + fmt.Printf(" Project ID: %s\n", projectID) + fmt.Printf(" Workspace ID: %s\n", workspaceID) + fmt.Printf(" Project Slug: %s\n", projectSlug) + fmt.Println() + + fmt.Printf("📋 Use these values for deployment:\n") + fmt.Printf(" unkey-cli create --workspace-id=%s --project-id=%s\n", workspaceID, projectID) + fmt.Printf("\n") + fmt.Printf("🗑️ Remember to remove this bootstrap command once we have proper UI!\n") + + logger.Info("bootstrap project created", + "project_id", projectID, + "workspace_id", workspaceID, + "project_slug", projectSlug) + + return nil +} \ No newline at end of file diff --git a/go/cmd/version/main.go b/go/cmd/version/main.go index 0d57985a19..b1b86906f6 100644 --- a/go/cmd/version/main.go +++ b/go/cmd/version/main.go @@ -34,6 +34,8 @@ Versions are immutable snapshots of your code, configuration, and infrastructure getCmd, listCmd, rollbackCmd, + // TODO: Remove this bootstrap command once we have a proper UI + bootstrapProjectCmd, // defined in bootstrap.go }, } @@ -83,16 +85,14 @@ var createCmd = &cli.Command{ Required: false, }, &cli.StringFlag{ - Name: "workspace", + Name: "workspace-id", Usage: "Workspace ID", - Value: "acme", - Required: false, + Required: true, }, &cli.StringFlag{ - Name: "project", + Name: "project-id", Usage: "Project ID", - Value: "my-api", - Required: false, + Required: true, }, }, Action: createAction, @@ -101,9 +101,9 @@ var createCmd = &cli.Command{ func createAction(ctx context.Context, cmd *cli.Command) error { logger := logging.New() - // Hardcoded for demo - workspace := "acme" - project := "my-api" + // Get workspace and project IDs from CLI flags + workspaceID := cmd.String("workspace-id") + projectID := cmd.String("project-id") // Get Git information automatically gitInfo := git.GetInfo() @@ -123,7 +123,7 @@ func createAction(ctx context.Context, cmd *cli.Command) error { dockerfile := cmd.String("dockerfile") buildContext := cmd.String("context") - return runDeploymentSteps(ctx, cmd, workspace, project, branch, dockerImage, dockerfile, buildContext, commit, logger) + return runDeploymentSteps(ctx, cmd, workspaceID, projectID, branch, dockerImage, dockerfile, buildContext, commit, logger) } func printDeploymentComplete(versionID, workspace, branch, commit string) { @@ -389,12 +389,10 @@ func pollVersionStatus(ctx context.Context, logger logging.Logger, client ctrlv1 // displayVersionStep shows a version step with appropriate formatting func displayVersionStep(step *ctrlv1.VersionStep) { message := step.GetMessage() - // Display only the actual message from the database, indented under "Creating Version" if message != "" { fmt.Printf(" %s\n", message) } - // Show error message if present if step.GetErrorMessage() != "" { fmt.Printf(" Error: %s\n", step.GetErrorMessage()) diff --git a/go/go.mod b/go/go.mod index 9473a48830..b2c0184f47 100644 --- a/go/go.mod +++ b/go/go.mod @@ -28,9 +28,6 @@ require ( github.com/shirou/gopsutil/v4 v4.25.5 github.com/sqlc-dev/sqlc v1.28.0 github.com/stretchr/testify v1.10.0 - github.com/unkeyed/unkey/go/deploy/assetmanagerd v0.0.0-20250710211506-eee7a93abd66 - github.com/unkeyed/unkey/go/deploy/billaged v0.0.0-20250710211506-eee7a93abd66 - github.com/unkeyed/unkey/go/deploy/builderd v0.0.0-20250710211506-eee7a93abd66 github.com/unkeyed/unkey/go/deploy/pkg/tls v0.0.0-00010101000000-000000000000 github.com/urfave/cli/v3 v3.3.3 go.opentelemetry.io/contrib/bridges/otelslog v0.11.0 diff --git a/go/pkg/db/branch_find_by_project_name.sql_generated.go b/go/pkg/db/branch_find_by_project_name.sql_generated.go new file mode 100644 index 0000000000..56f98ce69c --- /dev/null +++ b/go/pkg/db/branch_find_by_project_name.sql_generated.go @@ -0,0 +1,52 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: branch_find_by_project_name.sql + +package db + +import ( + "context" +) + +const findBranchByProjectName = `-- name: FindBranchByProjectName :one +SELECT + id, + workspace_id, + project_id, + name, + created_at, + updated_at +FROM branches +WHERE project_id = ? AND name = ? +` + +type FindBranchByProjectNameParams struct { + ProjectID string `db:"project_id"` + Name string `db:"name"` +} + +// FindBranchByProjectName +// +// SELECT +// id, +// workspace_id, +// project_id, +// name, +// created_at, +// updated_at +// FROM branches +// WHERE project_id = ? AND name = ? +func (q *Queries) FindBranchByProjectName(ctx context.Context, db DBTX, arg FindBranchByProjectNameParams) (Branch, error) { + row := db.QueryRowContext(ctx, findBranchByProjectName, arg.ProjectID, arg.Name) + var i Branch + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.ProjectID, + &i.Name, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/go/pkg/db/branch_insert.sql_generated.go b/go/pkg/db/branch_insert.sql_generated.go new file mode 100644 index 0000000000..89cfcb9f11 --- /dev/null +++ b/go/pkg/db/branch_insert.sql_generated.go @@ -0,0 +1,57 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: branch_insert.sql + +package db + +import ( + "context" + "database/sql" +) + +const insertBranch = `-- name: InsertBranch :exec +INSERT INTO branches ( + id, + workspace_id, + project_id, + name, + created_at, + updated_at +) VALUES ( + ?, ?, ?, ?, ?, ? +) +` + +type InsertBranchParams struct { + ID string `db:"id"` + WorkspaceID string `db:"workspace_id"` + ProjectID string `db:"project_id"` + Name string `db:"name"` + CreatedAt int64 `db:"created_at"` + UpdatedAt sql.NullInt64 `db:"updated_at"` +} + +// InsertBranch +// +// INSERT INTO branches ( +// id, +// workspace_id, +// project_id, +// name, +// created_at, +// updated_at +// ) VALUES ( +// ?, ?, ?, ?, ?, ? +// ) +func (q *Queries) InsertBranch(ctx context.Context, db DBTX, arg InsertBranchParams) error { + _, err := db.ExecContext(ctx, insertBranch, + arg.ID, + arg.WorkspaceID, + arg.ProjectID, + arg.Name, + arg.CreatedAt, + arg.UpdatedAt, + ) + return err +} diff --git a/go/pkg/db/branch_upsert.sql_generated.go b/go/pkg/db/branch_upsert.sql_generated.go new file mode 100644 index 0000000000..aa8f54e036 --- /dev/null +++ b/go/pkg/db/branch_upsert.sql_generated.go @@ -0,0 +1,59 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: branch_upsert.sql + +package db + +import ( + "context" + "database/sql" +) + +const upsertBranch = `-- name: UpsertBranch :exec +INSERT INTO branches ( + id, + workspace_id, + project_id, + name, + created_at, + updated_at +) VALUES ( + ?, ?, ?, ?, ?, ? +) ON DUPLICATE KEY UPDATE + updated_at = VALUES(updated_at) +` + +type UpsertBranchParams struct { + ID string `db:"id"` + WorkspaceID string `db:"workspace_id"` + ProjectID string `db:"project_id"` + Name string `db:"name"` + CreatedAt int64 `db:"created_at"` + UpdatedAt sql.NullInt64 `db:"updated_at"` +} + +// UpsertBranch +// +// INSERT INTO branches ( +// id, +// workspace_id, +// project_id, +// name, +// created_at, +// updated_at +// ) VALUES ( +// ?, ?, ?, ?, ?, ? +// ) ON DUPLICATE KEY UPDATE +// updated_at = VALUES(updated_at) +func (q *Queries) UpsertBranch(ctx context.Context, db DBTX, arg UpsertBranchParams) error { + _, err := db.ExecContext(ctx, upsertBranch, + arg.ID, + arg.WorkspaceID, + arg.ProjectID, + arg.Name, + arg.CreatedAt, + arg.UpdatedAt, + ) + return err +} diff --git a/go/pkg/db/models_generated.go b/go/pkg/db/models_generated.go index 6afc493e0d..8f5039010f 100644 --- a/go/pkg/db/models_generated.go +++ b/go/pkg/db/models_generated.go @@ -141,92 +141,92 @@ func (ns NullBuildsStatus) Value() (driver.Value, error) { return string(ns.BuildsStatus), nil } -type HostnamesVerificationMethod string +type DomainsVerificationMethod string const ( - HostnamesVerificationMethodDnsTxt HostnamesVerificationMethod = "dns_txt" - HostnamesVerificationMethodDnsCname HostnamesVerificationMethod = "dns_cname" - HostnamesVerificationMethodFileUpload HostnamesVerificationMethod = "file_upload" - HostnamesVerificationMethodAutomatic HostnamesVerificationMethod = "automatic" + DomainsVerificationMethodDnsTxt DomainsVerificationMethod = "dns_txt" + DomainsVerificationMethodDnsCname DomainsVerificationMethod = "dns_cname" + DomainsVerificationMethodFileUpload DomainsVerificationMethod = "file_upload" + DomainsVerificationMethodAutomatic DomainsVerificationMethod = "automatic" ) -func (e *HostnamesVerificationMethod) Scan(src interface{}) error { +func (e *DomainsVerificationMethod) Scan(src interface{}) error { switch s := src.(type) { case []byte: - *e = HostnamesVerificationMethod(s) + *e = DomainsVerificationMethod(s) case string: - *e = HostnamesVerificationMethod(s) + *e = DomainsVerificationMethod(s) default: - return fmt.Errorf("unsupported scan type for HostnamesVerificationMethod: %T", src) + return fmt.Errorf("unsupported scan type for DomainsVerificationMethod: %T", src) } return nil } -type NullHostnamesVerificationMethod struct { - HostnamesVerificationMethod HostnamesVerificationMethod - Valid bool // Valid is true if HostnamesVerificationMethod is not NULL +type NullDomainsVerificationMethod struct { + DomainsVerificationMethod DomainsVerificationMethod + Valid bool // Valid is true if DomainsVerificationMethod is not NULL } // Scan implements the Scanner interface. -func (ns *NullHostnamesVerificationMethod) Scan(value interface{}) error { +func (ns *NullDomainsVerificationMethod) Scan(value interface{}) error { if value == nil { - ns.HostnamesVerificationMethod, ns.Valid = "", false + ns.DomainsVerificationMethod, ns.Valid = "", false return nil } ns.Valid = true - return ns.HostnamesVerificationMethod.Scan(value) + return ns.DomainsVerificationMethod.Scan(value) } // Value implements the driver Valuer interface. -func (ns NullHostnamesVerificationMethod) Value() (driver.Value, error) { +func (ns NullDomainsVerificationMethod) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.HostnamesVerificationMethod), nil + return string(ns.DomainsVerificationMethod), nil } -type HostnamesVerificationStatus string +type DomainsVerificationStatus string const ( - HostnamesVerificationStatusPending HostnamesVerificationStatus = "pending" - HostnamesVerificationStatusVerified HostnamesVerificationStatus = "verified" - HostnamesVerificationStatusFailed HostnamesVerificationStatus = "failed" - HostnamesVerificationStatusExpired HostnamesVerificationStatus = "expired" + DomainsVerificationStatusPending DomainsVerificationStatus = "pending" + DomainsVerificationStatusVerified DomainsVerificationStatus = "verified" + DomainsVerificationStatusFailed DomainsVerificationStatus = "failed" + DomainsVerificationStatusExpired DomainsVerificationStatus = "expired" ) -func (e *HostnamesVerificationStatus) Scan(src interface{}) error { +func (e *DomainsVerificationStatus) Scan(src interface{}) error { switch s := src.(type) { case []byte: - *e = HostnamesVerificationStatus(s) + *e = DomainsVerificationStatus(s) case string: - *e = HostnamesVerificationStatus(s) + *e = DomainsVerificationStatus(s) default: - return fmt.Errorf("unsupported scan type for HostnamesVerificationStatus: %T", src) + return fmt.Errorf("unsupported scan type for DomainsVerificationStatus: %T", src) } return nil } -type NullHostnamesVerificationStatus struct { - HostnamesVerificationStatus HostnamesVerificationStatus - Valid bool // Valid is true if HostnamesVerificationStatus is not NULL +type NullDomainsVerificationStatus struct { + DomainsVerificationStatus DomainsVerificationStatus + Valid bool // Valid is true if DomainsVerificationStatus is not NULL } // Scan implements the Scanner interface. -func (ns *NullHostnamesVerificationStatus) Scan(value interface{}) error { +func (ns *NullDomainsVerificationStatus) Scan(value interface{}) error { if value == nil { - ns.HostnamesVerificationStatus, ns.Valid = "", false + ns.DomainsVerificationStatus, ns.Valid = "", false return nil } ns.Valid = true - return ns.HostnamesVerificationStatus.Scan(value) + return ns.DomainsVerificationStatus.Scan(value) } // Value implements the driver Valuer interface. -func (ns NullHostnamesVerificationStatus) Value() (driver.Value, error) { +func (ns NullDomainsVerificationStatus) Value() (driver.Value, error) { if !ns.Valid { return nil, nil } - return string(ns.HostnamesVerificationStatus), nil + return string(ns.DomainsVerificationStatus), nil } type PartitionsStatus string @@ -592,13 +592,12 @@ type AuditLogTarget struct { } type Branch struct { - ID string `db:"id"` - WorkspaceID string `db:"workspace_id"` - ProjectID string `db:"project_id"` - Name string `db:"name"` - IsProduction bool `db:"is_production"` - CreatedAt int64 `db:"created_at"` - UpdatedAt sql.NullInt64 `db:"updated_at"` + ID string `db:"id"` + WorkspaceID string `db:"workspace_id"` + ProjectID string `db:"project_id"` + Name string `db:"name"` + CreatedAt int64 `db:"created_at"` + UpdatedAt sql.NullInt64 `db:"updated_at"` } type Build struct { @@ -618,6 +617,21 @@ type Build struct { UpdatedAt sql.NullInt64 `db:"updated_at"` } +type Domain struct { + ID string `db:"id"` + WorkspaceID string `db:"workspace_id"` + ProjectID string `db:"project_id"` + Hostname string `db:"hostname"` + IsCustomDomain bool `db:"is_custom_domain"` + CertificateID sql.NullString `db:"certificate_id"` + VerificationStatus NullDomainsVerificationStatus `db:"verification_status"` + VerificationToken sql.NullString `db:"verification_token"` + VerificationMethod NullDomainsVerificationMethod `db:"verification_method"` + SubdomainConfig []byte `db:"subdomain_config"` + CreatedAt int64 `db:"created_at"` + UpdatedAt sql.NullInt64 `db:"updated_at"` +} + type EncryptedKey struct { WorkspaceID string `db:"workspace_id"` KeyID string `db:"key_id"` @@ -627,19 +641,15 @@ type EncryptedKey struct { EncryptionKeyID string `db:"encryption_key_id"` } -type Hostname struct { - ID string `db:"id"` - WorkspaceID string `db:"workspace_id"` - ProjectID string `db:"project_id"` - Hostname string `db:"hostname"` - IsCustomDomain bool `db:"is_custom_domain"` - CertificateID sql.NullString `db:"certificate_id"` - VerificationStatus NullHostnamesVerificationStatus `db:"verification_status"` - VerificationToken sql.NullString `db:"verification_token"` - VerificationMethod NullHostnamesVerificationMethod `db:"verification_method"` - SubdomainConfig []byte `db:"subdomain_config"` - CreatedAt int64 `db:"created_at"` - UpdatedAt sql.NullInt64 `db:"updated_at"` +type HostnameRoute struct { + ID string `db:"id"` + WorkspaceID string `db:"workspace_id"` + ProjectID string `db:"project_id"` + Hostname string `db:"hostname"` + VersionID string `db:"version_id"` + IsEnabled bool `db:"is_enabled"` + CreatedAt int64 `db:"created_at"` + UpdatedAt sql.NullInt64 `db:"updated_at"` } type Identity struct { @@ -748,6 +758,7 @@ type Project struct { Name string `db:"name"` Slug string `db:"slug"` GitRepositoryUrl sql.NullString `db:"git_repository_url"` + DefaultBranch sql.NullString `db:"default_branch"` DeleteProtection sql.NullBool `db:"delete_protection"` CreatedAt int64 `db:"created_at"` UpdatedAt sql.NullInt64 `db:"updated_at"` @@ -825,17 +836,6 @@ type RootfsImage struct { UpdatedAt sql.NullInt64 `db:"updated_at"` } -type Route struct { - ID string `db:"id"` - WorkspaceID string `db:"workspace_id"` - ProjectID string `db:"project_id"` - Hostname string `db:"hostname"` - VersionID string `db:"version_id"` - IsEnabled bool `db:"is_enabled"` - CreatedAt int64 `db:"created_at"` - UpdatedAt sql.NullInt64 `db:"updated_at"` -} - type VercelBinding struct { ID string `db:"id"` IntegrationID string `db:"integration_id"` diff --git a/go/pkg/db/project_find_by_id.sql_generated.go b/go/pkg/db/project_find_by_id.sql_generated.go new file mode 100644 index 0000000000..9b5798ca57 --- /dev/null +++ b/go/pkg/db/project_find_by_id.sql_generated.go @@ -0,0 +1,59 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: project_find_by_id.sql + +package db + +import ( + "context" +) + +const findProjectById = `-- name: FindProjectById :one +SELECT + id, + workspace_id, + partition_id, + name, + slug, + git_repository_url, + default_branch, + delete_protection, + created_at, + updated_at +FROM projects +WHERE id = ? +` + +// FindProjectById +// +// SELECT +// id, +// workspace_id, +// partition_id, +// name, +// slug, +// git_repository_url, +// default_branch, +// delete_protection, +// created_at, +// updated_at +// FROM projects +// WHERE id = ? +func (q *Queries) FindProjectById(ctx context.Context, db DBTX, id string) (Project, error) { + row := db.QueryRowContext(ctx, findProjectById, id) + var i Project + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.PartitionID, + &i.Name, + &i.Slug, + &i.GitRepositoryUrl, + &i.DefaultBranch, + &i.DeleteProtection, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/go/pkg/db/project_find_by_workspace_slug.sql_generated.go b/go/pkg/db/project_find_by_workspace_slug.sql_generated.go new file mode 100644 index 0000000000..7506c9dc3f --- /dev/null +++ b/go/pkg/db/project_find_by_workspace_slug.sql_generated.go @@ -0,0 +1,64 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: project_find_by_workspace_slug.sql + +package db + +import ( + "context" +) + +const findProjectByWorkspaceSlug = `-- name: FindProjectByWorkspaceSlug :one +SELECT + id, + workspace_id, + partition_id, + name, + slug, + git_repository_url, + default_branch, + delete_protection, + created_at, + updated_at +FROM projects +WHERE workspace_id = ? AND slug = ? +` + +type FindProjectByWorkspaceSlugParams struct { + WorkspaceID string `db:"workspace_id"` + Slug string `db:"slug"` +} + +// FindProjectByWorkspaceSlug +// +// SELECT +// id, +// workspace_id, +// partition_id, +// name, +// slug, +// git_repository_url, +// default_branch, +// delete_protection, +// created_at, +// updated_at +// FROM projects +// WHERE workspace_id = ? AND slug = ? +func (q *Queries) FindProjectByWorkspaceSlug(ctx context.Context, db DBTX, arg FindProjectByWorkspaceSlugParams) (Project, error) { + row := db.QueryRowContext(ctx, findProjectByWorkspaceSlug, arg.WorkspaceID, arg.Slug) + var i Project + err := row.Scan( + &i.ID, + &i.WorkspaceID, + &i.PartitionID, + &i.Name, + &i.Slug, + &i.GitRepositoryUrl, + &i.DefaultBranch, + &i.DeleteProtection, + &i.CreatedAt, + &i.UpdatedAt, + ) + return i, err +} diff --git a/go/pkg/db/project_insert.sql_generated.go b/go/pkg/db/project_insert.sql_generated.go new file mode 100644 index 0000000000..0ad7023cb7 --- /dev/null +++ b/go/pkg/db/project_insert.sql_generated.go @@ -0,0 +1,73 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.27.0 +// source: project_insert.sql + +package db + +import ( + "context" + "database/sql" +) + +const insertProject = `-- name: InsertProject :exec +INSERT INTO projects ( + id, + workspace_id, + partition_id, + name, + slug, + git_repository_url, + default_branch, + delete_protection, + created_at, + updated_at +) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +) +` + +type InsertProjectParams struct { + ID string `db:"id"` + WorkspaceID string `db:"workspace_id"` + PartitionID string `db:"partition_id"` + Name string `db:"name"` + Slug string `db:"slug"` + GitRepositoryUrl sql.NullString `db:"git_repository_url"` + DefaultBranch sql.NullString `db:"default_branch"` + DeleteProtection sql.NullBool `db:"delete_protection"` + CreatedAt int64 `db:"created_at"` + UpdatedAt sql.NullInt64 `db:"updated_at"` +} + +// InsertProject +// +// INSERT INTO projects ( +// id, +// workspace_id, +// partition_id, +// name, +// slug, +// git_repository_url, +// default_branch, +// delete_protection, +// created_at, +// updated_at +// ) VALUES ( +// ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +// ) +func (q *Queries) InsertProject(ctx context.Context, db DBTX, arg InsertProjectParams) error { + _, err := db.ExecContext(ctx, insertProject, + arg.ID, + arg.WorkspaceID, + arg.PartitionID, + arg.Name, + arg.Slug, + arg.GitRepositoryUrl, + arg.DefaultBranch, + arg.DeleteProtection, + arg.CreatedAt, + arg.UpdatedAt, + ) + return err +} diff --git a/go/pkg/db/querier_generated.go b/go/pkg/db/querier_generated.go index 1b05cab6f6..7f2e063984 100644 --- a/go/pkg/db/querier_generated.go +++ b/go/pkg/db/querier_generated.go @@ -93,6 +93,18 @@ type Querier interface { // JOIN audit_log ON audit_log.id = audit_log_target.audit_log_id // WHERE audit_log_target.id = ? FindAuditLogTargetByID(ctx context.Context, db DBTX, id string) ([]FindAuditLogTargetByIDRow, error) + //FindBranchByProjectName + // + // SELECT + // id, + // workspace_id, + // project_id, + // name, + // created_at, + // updated_at + // FROM branches + // WHERE project_id = ? AND name = ? + FindBranchByProjectName(ctx context.Context, db DBTX, arg FindBranchByProjectNameParams) (Branch, error) //FindBuildById // // SELECT @@ -113,6 +125,21 @@ type Querier interface { // FROM `builds` // WHERE id = ? FindBuildById(ctx context.Context, db DBTX, id string) (Build, error) + //FindHostnameRoutesByVersionId + // + // SELECT + // id, + // workspace_id, + // project_id, + // hostname, + // version_id, + // is_enabled, + // created_at, + // updated_at + // FROM hostname_routes + // WHERE version_id = ? AND is_enabled = true + // ORDER BY created_at ASC + FindHostnameRoutesByVersionId(ctx context.Context, db DBTX, versionID string) ([]HostnameRoute, error) //FindIdentityByExternalID // // SELECT id, external_id, workspace_id, environment, meta, deleted, created_at, updated_at FROM identities WHERE workspace_id = ? AND external_id = ? AND deleted = ? @@ -267,6 +294,38 @@ type Querier interface { // AND workspace_id = ? // LIMIT 1 FindPermissionBySlugAndWorkspaceID(ctx context.Context, db DBTX, arg FindPermissionBySlugAndWorkspaceIDParams) (Permission, error) + //FindProjectById + // + // SELECT + // id, + // workspace_id, + // partition_id, + // name, + // slug, + // git_repository_url, + // default_branch, + // delete_protection, + // created_at, + // updated_at + // FROM projects + // WHERE id = ? + FindProjectById(ctx context.Context, db DBTX, id string) (Project, error) + //FindProjectByWorkspaceSlug + // + // SELECT + // id, + // workspace_id, + // partition_id, + // name, + // slug, + // git_repository_url, + // default_branch, + // delete_protection, + // created_at, + // updated_at + // FROM projects + // WHERE workspace_id = ? AND slug = ? + FindProjectByWorkspaceSlug(ctx context.Context, db DBTX, arg FindProjectByWorkspaceSlugParams) (Project, error) //FindRatelimitNamespaceByID // // SELECT id, workspace_id, name, created_at_m, updated_at_m, deleted_at_m FROM `ratelimit_namespaces` @@ -453,6 +512,19 @@ type Querier interface { // ? // ) InsertAuditLogTarget(ctx context.Context, db DBTX, arg InsertAuditLogTargetParams) error + //InsertBranch + // + // INSERT INTO branches ( + // id, + // workspace_id, + // project_id, + // name, + // created_at, + // updated_at + // ) VALUES ( + // ?, ?, ?, ?, ?, ? + // ) + InsertBranch(ctx context.Context, db DBTX, arg InsertBranchParams) error //InsertBuild // // INSERT INTO builds ( @@ -487,6 +559,21 @@ type Querier interface { // NULL // ) InsertBuild(ctx context.Context, db DBTX, arg InsertBuildParams) error + //InsertHostnameRoute + // + // INSERT INTO hostname_routes ( + // id, + // workspace_id, + // project_id, + // hostname, + // version_id, + // is_enabled, + // created_at, + // updated_at + // ) VALUES ( + // ?, ?, ?, ?, ?, ?, ?, ? + // ) + InsertHostnameRoute(ctx context.Context, db DBTX, arg InsertHostnameRouteParams) error //InsertIdentity // // INSERT INTO `identities` ( @@ -671,6 +758,23 @@ type Querier interface { // ? // ) InsertPermission(ctx context.Context, db DBTX, arg InsertPermissionParams) error + //InsertProject + // + // INSERT INTO projects ( + // id, + // workspace_id, + // partition_id, + // name, + // slug, + // git_repository_url, + // default_branch, + // delete_protection, + // created_at, + // updated_at + // ) VALUES ( + // ?, ?, ?, ?, ?, ?, ?, ?, ?, ? + // ) + InsertProject(ctx context.Context, db DBTX, arg InsertProjectParams) error //InsertRatelimitNamespace // // INSERT INTO @@ -1140,6 +1244,20 @@ type Querier interface { // SET plan = ? // WHERE id = ? UpdateWorkspacePlan(ctx context.Context, db DBTX, arg UpdateWorkspacePlanParams) (sql.Result, error) + //UpsertBranch + // + // INSERT INTO branches ( + // id, + // workspace_id, + // project_id, + // name, + // created_at, + // updated_at + // ) VALUES ( + // ?, ?, ?, ?, ?, ? + // ) ON DUPLICATE KEY UPDATE + // updated_at = VALUES(updated_at) + UpsertBranch(ctx context.Context, db DBTX, arg UpsertBranchParams) error } var _ Querier = (*Queries)(nil) diff --git a/go/pkg/db/queries/branch_find_by_project_name.sql b/go/pkg/db/queries/branch_find_by_project_name.sql new file mode 100644 index 0000000000..ceb33726db --- /dev/null +++ b/go/pkg/db/queries/branch_find_by_project_name.sql @@ -0,0 +1,10 @@ +-- name: FindBranchByProjectName :one +SELECT + id, + workspace_id, + project_id, + name, + created_at, + updated_at +FROM branches +WHERE project_id = ? AND name = ?; \ No newline at end of file diff --git a/go/pkg/db/queries/branch_insert.sql b/go/pkg/db/queries/branch_insert.sql new file mode 100644 index 0000000000..b23538b2aa --- /dev/null +++ b/go/pkg/db/queries/branch_insert.sql @@ -0,0 +1,11 @@ +-- name: InsertBranch :exec +INSERT INTO branches ( + id, + workspace_id, + project_id, + name, + created_at, + updated_at +) VALUES ( + ?, ?, ?, ?, ?, ? +); \ No newline at end of file diff --git a/go/pkg/db/queries/branch_upsert.sql b/go/pkg/db/queries/branch_upsert.sql new file mode 100644 index 0000000000..a6b73e8110 --- /dev/null +++ b/go/pkg/db/queries/branch_upsert.sql @@ -0,0 +1,12 @@ +-- name: UpsertBranch :exec +INSERT INTO branches ( + id, + workspace_id, + project_id, + name, + created_at, + updated_at +) VALUES ( + ?, ?, ?, ?, ?, ? +) ON DUPLICATE KEY UPDATE + updated_at = VALUES(updated_at); \ No newline at end of file diff --git a/go/pkg/db/queries/project_find_by_id.sql b/go/pkg/db/queries/project_find_by_id.sql new file mode 100644 index 0000000000..f17289f4fb --- /dev/null +++ b/go/pkg/db/queries/project_find_by_id.sql @@ -0,0 +1,14 @@ +-- name: FindProjectById :one +SELECT + id, + workspace_id, + partition_id, + name, + slug, + git_repository_url, + default_branch, + delete_protection, + created_at, + updated_at +FROM projects +WHERE id = ?; \ No newline at end of file diff --git a/go/pkg/db/queries/project_find_by_workspace_slug.sql b/go/pkg/db/queries/project_find_by_workspace_slug.sql new file mode 100644 index 0000000000..780a8546c8 --- /dev/null +++ b/go/pkg/db/queries/project_find_by_workspace_slug.sql @@ -0,0 +1,14 @@ +-- name: FindProjectByWorkspaceSlug :one +SELECT + id, + workspace_id, + partition_id, + name, + slug, + git_repository_url, + default_branch, + delete_protection, + created_at, + updated_at +FROM projects +WHERE workspace_id = ? AND slug = ?; \ No newline at end of file diff --git a/go/pkg/db/queries/project_insert.sql b/go/pkg/db/queries/project_insert.sql new file mode 100644 index 0000000000..9b2aadf17b --- /dev/null +++ b/go/pkg/db/queries/project_insert.sql @@ -0,0 +1,15 @@ +-- name: InsertProject :exec +INSERT INTO projects ( + id, + workspace_id, + partition_id, + name, + slug, + git_repository_url, + default_branch, + delete_protection, + created_at, + updated_at +) VALUES ( + ?, ?, ?, ?, ?, ?, ?, ?, ?, ? +); \ No newline at end of file diff --git a/go/pkg/db/queries/route_find_by_version_id.sql b/go/pkg/db/queries/route_find_by_version_id.sql index b0fc6445a3..e3a7290381 100644 --- a/go/pkg/db/queries/route_find_by_version_id.sql +++ b/go/pkg/db/queries/route_find_by_version_id.sql @@ -1,4 +1,4 @@ --- name: FindRoutesByVersionId :many +-- name: FindHostnameRoutesByVersionId :many SELECT id, workspace_id, @@ -8,6 +8,6 @@ SELECT is_enabled, created_at, updated_at -FROM routes +FROM hostname_routes WHERE version_id = ? AND is_enabled = true ORDER BY created_at ASC; \ No newline at end of file diff --git a/go/pkg/db/queries/route_insert.sql b/go/pkg/db/queries/route_insert.sql index 6e38ca45cc..51d24d3e11 100644 --- a/go/pkg/db/queries/route_insert.sql +++ b/go/pkg/db/queries/route_insert.sql @@ -1,5 +1,5 @@ --- name: InsertRoute :exec -INSERT INTO routes ( +-- name: InsertHostnameRoute :exec +INSERT INTO hostname_routes ( id, workspace_id, project_id, diff --git a/go/pkg/db/route_find_by_version_id.sql_generated.go b/go/pkg/db/route_find_by_version_id.sql_generated.go index b7c59c3942..0923eca039 100644 --- a/go/pkg/db/route_find_by_version_id.sql_generated.go +++ b/go/pkg/db/route_find_by_version_id.sql_generated.go @@ -9,7 +9,7 @@ import ( "context" ) -const findRoutesByVersionId = `-- name: FindRoutesByVersionId :many +const findHostnameRoutesByVersionId = `-- name: FindHostnameRoutesByVersionId :many SELECT id, workspace_id, @@ -19,12 +19,12 @@ SELECT is_enabled, created_at, updated_at -FROM routes +FROM hostname_routes WHERE version_id = ? AND is_enabled = true ORDER BY created_at ASC ` -// FindRoutesByVersionId +// FindHostnameRoutesByVersionId // // SELECT // id, @@ -35,18 +35,18 @@ ORDER BY created_at ASC // is_enabled, // created_at, // updated_at -// FROM routes +// FROM hostname_routes // WHERE version_id = ? AND is_enabled = true // ORDER BY created_at ASC -func (q *Queries) FindRoutesByVersionId(ctx context.Context, db DBTX, versionID string) ([]Route, error) { - rows, err := db.QueryContext(ctx, findRoutesByVersionId, versionID) +func (q *Queries) FindHostnameRoutesByVersionId(ctx context.Context, db DBTX, versionID string) ([]HostnameRoute, error) { + rows, err := db.QueryContext(ctx, findHostnameRoutesByVersionId, versionID) if err != nil { return nil, err } defer rows.Close() - var items []Route + var items []HostnameRoute for rows.Next() { - var i Route + var i HostnameRoute if err := rows.Scan( &i.ID, &i.WorkspaceID, diff --git a/go/pkg/db/route_insert.sql_generated.go b/go/pkg/db/route_insert.sql_generated.go index fb4a797ca6..7e21a62f32 100644 --- a/go/pkg/db/route_insert.sql_generated.go +++ b/go/pkg/db/route_insert.sql_generated.go @@ -10,8 +10,8 @@ import ( "database/sql" ) -const insertRoute = `-- name: InsertRoute :exec -INSERT INTO routes ( +const insertHostnameRoute = `-- name: InsertHostnameRoute :exec +INSERT INTO hostname_routes ( id, workspace_id, project_id, @@ -25,7 +25,7 @@ INSERT INTO routes ( ) ` -type InsertRouteParams struct { +type InsertHostnameRouteParams struct { ID string `db:"id"` WorkspaceID string `db:"workspace_id"` ProjectID string `db:"project_id"` @@ -36,9 +36,9 @@ type InsertRouteParams struct { UpdatedAt sql.NullInt64 `db:"updated_at"` } -// InsertRoute +// InsertHostnameRoute // -// INSERT INTO routes ( +// INSERT INTO hostname_routes ( // id, // workspace_id, // project_id, @@ -50,8 +50,8 @@ type InsertRouteParams struct { // ) VALUES ( // ?, ?, ?, ?, ?, ?, ?, ? // ) -func (q *Queries) InsertRoute(ctx context.Context, db DBTX, arg InsertRouteParams) error { - _, err := db.ExecContext(ctx, insertRoute, +func (q *Queries) InsertHostnameRoute(ctx context.Context, db DBTX, arg InsertHostnameRouteParams) error { + _, err := db.ExecContext(ctx, insertHostnameRoute, arg.ID, arg.WorkspaceID, arg.ProjectID, diff --git a/go/pkg/db/schema.sql b/go/pkg/db/schema.sql index 1221c72eda..991ccaf79a 100644 --- a/go/pkg/db/schema.sql +++ b/go/pkg/db/schema.sql @@ -315,6 +315,7 @@ CREATE TABLE `projects` ( `name` varchar(256) NOT NULL, `slug` varchar(256) NOT NULL, `git_repository_url` varchar(500), + `default_branch` varchar(256) DEFAULT 'main', `delete_protection` boolean DEFAULT false, `created_at` bigint NOT NULL, `updated_at` bigint, @@ -327,7 +328,6 @@ CREATE TABLE `branches` ( `workspace_id` varchar(256) NOT NULL, `project_id` varchar(256) NOT NULL, `name` varchar(256) NOT NULL, - `is_production` boolean NOT NULL DEFAULT false, `created_at` bigint NOT NULL, `updated_at` bigint, CONSTRAINT `branches_id` PRIMARY KEY(`id`), @@ -390,7 +390,7 @@ CREATE TABLE `versions` ( CONSTRAINT `versions_id` PRIMARY KEY(`id`) ); -CREATE TABLE `routes` ( +CREATE TABLE `hostname_routes` ( `id` varchar(256) NOT NULL, `workspace_id` varchar(256) NOT NULL, `project_id` varchar(256) NOT NULL, @@ -399,11 +399,11 @@ CREATE TABLE `routes` ( `is_enabled` boolean NOT NULL DEFAULT true, `created_at` bigint NOT NULL, `updated_at` bigint, - CONSTRAINT `routes_id` PRIMARY KEY(`id`), + CONSTRAINT `hostname_routes_id` PRIMARY KEY(`id`), CONSTRAINT `hostname_idx` UNIQUE(`hostname`) ); -CREATE TABLE `hostnames` ( +CREATE TABLE `domains` ( `id` varchar(256) NOT NULL, `workspace_id` varchar(256) NOT NULL, `project_id` varchar(256) NOT NULL, @@ -416,7 +416,7 @@ CREATE TABLE `hostnames` ( `subdomain_config` json, `created_at` bigint NOT NULL, `updated_at` bigint, - CONSTRAINT `hostnames_id` PRIMARY KEY(`id`), + CONSTRAINT `domains_id` PRIMARY KEY(`id`), CONSTRAINT `hostname_idx` UNIQUE(`hostname`) ); @@ -456,10 +456,10 @@ CREATE INDEX `project_idx` ON `versions` (`project_id`); CREATE INDEX `branch_idx` ON `versions` (`branch_id`); CREATE INDEX `status_idx` ON `versions` (`status`); CREATE INDEX `rootfs_image_idx` ON `versions` (`rootfs_image_id`); -CREATE INDEX `workspace_idx` ON `routes` (`workspace_id`); -CREATE INDEX `project_idx` ON `routes` (`project_id`); -CREATE INDEX `version_idx` ON `routes` (`version_id`); -CREATE INDEX `workspace_idx` ON `hostnames` (`workspace_id`); -CREATE INDEX `project_idx` ON `hostnames` (`project_id`); -CREATE INDEX `verification_status_idx` ON `hostnames` (`verification_status`); -CREATE INDEX `certificate_idx` ON `hostnames` (`certificate_id`); +CREATE INDEX `workspace_idx` ON `hostname_routes` (`workspace_id`); +CREATE INDEX `project_idx` ON `hostname_routes` (`project_id`); +CREATE INDEX `version_idx` ON `hostname_routes` (`version_id`); +CREATE INDEX `workspace_idx` ON `domains` (`workspace_id`); +CREATE INDEX `project_idx` ON `domains` (`project_id`); +CREATE INDEX `verification_status_idx` ON `domains` (`verification_status`); +CREATE INDEX `certificate_idx` ON `domains` (`certificate_id`); diff --git a/go/unkey-cli b/go/unkey-cli new file mode 100644 index 0000000000..82f9cb57d2 Binary files /dev/null and b/go/unkey-cli differ diff --git a/internal/db/src/schema/branches.ts b/internal/db/src/schema/branches.ts index 40b75001ab..afd1d729a0 100644 --- a/internal/db/src/schema/branches.ts +++ b/internal/db/src/schema/branches.ts @@ -1,7 +1,6 @@ import { relations } from "drizzle-orm"; -import { boolean, index, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"; +import { bigint, index, mysqlTable, uniqueIndex, varchar } from "drizzle-orm/mysql-core"; import { projects } from "./projects"; -import { lifecycleDates } from "./util/lifecycle_dates"; import { versions } from "./versions"; import { workspaces } from "./workspaces"; export const branches = mysqlTable( @@ -13,10 +12,8 @@ export const branches = mysqlTable( name: varchar("name", { length: 256 }).notNull(), // Git branch name - // Is this the main/production branch for the project - isProduction: boolean("is_production").notNull().default(false), - - ...lifecycleDates, + createdAt: bigint("created_at", { mode: "number" }).notNull(), + updatedAt: bigint("updated_at", { mode: "number" }), }, (table) => ({ workspaceIdx: index("workspace_idx").on(table.workspaceId), diff --git a/internal/db/src/schema/builds.ts b/internal/db/src/schema/builds.ts index db22189736..5200c81b5a 100644 --- a/internal/db/src/schema/builds.ts +++ b/internal/db/src/schema/builds.ts @@ -1,5 +1,13 @@ import { relations } from "drizzle-orm"; -import { bigint, index, mysqlEnum, mysqlTable, primaryKey, text, varchar } from "drizzle-orm/mysql-core"; +import { + bigint, + index, + mysqlEnum, + mysqlTable, + primaryKey, + text, + varchar, +} from "drizzle-orm/mysql-core"; import { projects } from "./projects"; import { rootfsImages } from "./rootfs_images"; import { lifecycleDates } from "./util/lifecycle_dates"; @@ -49,7 +57,17 @@ export const versionSteps = mysqlTable( "version_steps", { versionId: varchar("version_id", { length: 256 }).notNull(), - status: mysqlEnum("status", ["pending", "downloading_docker_image", "building_rootfs", "uploading_rootfs", "creating_vm", "booting_vm", "assigning_domains", "completed", "failed"]).notNull(), + status: mysqlEnum("status", [ + "pending", + "downloading_docker_image", + "building_rootfs", + "uploading_rootfs", + "creating_vm", + "booting_vm", + "assigning_domains", + "completed", + "failed", + ]).notNull(), message: text("message"), errorMessage: text("error_message"), createdAt: bigint("created_at", { mode: "number" }).notNull(), diff --git a/internal/db/src/schema/hostnames.ts b/internal/db/src/schema/hostnames.ts index e940c252c7..70bed687aa 100644 --- a/internal/db/src/schema/hostnames.ts +++ b/internal/db/src/schema/hostnames.ts @@ -11,7 +11,7 @@ import { import { lifecycleDates } from "./util/lifecycle_dates"; export const hostnames = mysqlTable( - "hostnames", + "domains", { id: varchar("id", { length: 256 }).primaryKey(), workspaceId: varchar("workspace_id", { length: 256 }).notNull(), diff --git a/internal/db/src/schema/routes.ts b/internal/db/src/schema/routes.ts index 826c252fad..c7a27ef7e8 100644 --- a/internal/db/src/schema/routes.ts +++ b/internal/db/src/schema/routes.ts @@ -6,7 +6,7 @@ import { lifecycleDates } from "./util/lifecycle_dates"; import { versions } from "./versions"; import { workspaces } from "./workspaces"; export const routes = mysqlTable( - "routes", + "hostname_routes", { id: varchar("id", { length: 256 }).primaryKey(), workspaceId: varchar("workspace_id", { length: 256 }).notNull(), diff --git a/internal/db/src/schema/versions.ts b/internal/db/src/schema/versions.ts index 74d817e1ad..e63d4b8005 100644 --- a/internal/db/src/schema/versions.ts +++ b/internal/db/src/schema/versions.ts @@ -1,10 +1,9 @@ import { relations } from "drizzle-orm"; -import { index, json, mysqlEnum, mysqlTable, varchar } from "drizzle-orm/mysql-core"; +import { bigint, index, json, mysqlEnum, mysqlTable, varchar } from "drizzle-orm/mysql-core"; import { branches } from "./branches"; import { builds } from "./builds"; import { projects } from "./projects"; import { rootfsImages } from "./rootfs_images"; -import { lifecycleDates } from "./util/lifecycle_dates"; import { workspaces } from "./workspaces"; export const versions = mysqlTable( @@ -45,7 +44,8 @@ export const versions = mysqlTable( .notNull() .default("pending"), - ...lifecycleDates, + createdAt: bigint("created_at", { mode: "number" }).notNull(), + updatedAt: bigint("updated_at", { mode: "number" }), }, (table) => ({ workspaceIdx: index("workspace_idx").on(table.workspaceId),