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 (
+
+ );
+ }
+
+ if (isLoading) {
+ return (
+
+ );
+ }
+
+ 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 (
+
+ );
+ }
+
+ 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),