From b5aca2e0925c1bf46278a873e634183d29ed3786 Mon Sep 17 00:00:00 2001 From: Andriy Semenets Date: Fri, 29 Mar 2024 11:28:53 +0100 Subject: [PATCH 1/8] Add FE build step --- .dockerignore | 9 +++++++++ Dockerfile | 15 +++++++++++++++ frontend/src/components/Terminal/Terminal.tsx | 4 +++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 .dockerignore create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..ba4be58 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,9 @@ +frontend/dist +frontend/node_modules +frontend/dist +frontend/.env.local + +**/*.log +**/*.env +**/.DS_Store +**/Thumbs.db diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ef64022 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,15 @@ +FROM node:21-slim + +ENV NODE_ENV=production + +WORKDIR /frontend + +COPY ./backend/graph/schema.graphqls ../backend/graph/ + +COPY frontend/ . + +# --production=false is required because we want to install the @graphql-codegen/cli package (and it's in the devDependencies) +# https://classic.yarnpkg.com/lang/en/docs/cli/install/#toc-yarn-install-production-true-false +RUN yarn install --frozen-lockfile --production=false +RUN ls -la /frontend +RUN yarn build diff --git a/frontend/src/components/Terminal/Terminal.tsx b/frontend/src/components/Terminal/Terminal.tsx index 4233df0..4909974 100644 --- a/frontend/src/components/Terminal/Terminal.tsx +++ b/frontend/src/components/Terminal/Terminal.tsx @@ -8,7 +8,6 @@ import { FitAddon } from "xterm-addon-fit"; import { Unicode11Addon } from "xterm-addon-unicode11"; import { WebLinksAddon } from "xterm-addon-web-links"; import { WebglAddon } from "xterm-addon-webgl"; -import { Broadcast } from "xterm-theme"; import "xterm/css/xterm.css"; import dockerSvg from "@/assets/docker.svg"; @@ -16,6 +15,9 @@ import { Log } from "@/generated/graphql"; import { headerStyles } from "./Terminal.css"; +// We don't have types for xterm-theme, so this is a hack to get it to compile +const { Broadcast } = require("xterm-theme"); + const isWebGl2Supported = !!document .createElement("canvas") .getContext("webgl2"); From 3793b232733381588224009274ecb4e6a5637fee Mon Sep 17 00:00:00 2001 From: Andriy Semenets Date: Fri, 29 Mar 2024 11:52:27 +0100 Subject: [PATCH 2/8] Add BE build step --- .dockerignore | 2 ++ Dockerfile | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index ba4be58..d34624d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -3,6 +3,8 @@ frontend/node_modules frontend/dist frontend/.env.local +backend/.env + **/*.log **/*.env **/.DS_Store diff --git a/Dockerfile b/Dockerfile index ef64022..798217c 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,5 @@ -FROM node:21-slim +# STEP 1: Build the frontend +FROM node:21-slim as fe-build ENV NODE_ENV=production @@ -13,3 +14,22 @@ COPY frontend/ . RUN yarn install --frozen-lockfile --production=false RUN ls -la /frontend RUN yarn build + +# STEP 2: Build the backend +FROM golang:1.22-alpine as be-build + +WORKDIR /backend + +COPY backend/ . + +RUN go mod download + +RUN go build -o /app + +# STEP 3: Build the final image +FROM alpine:3.14 + +COPY --from=be-build /app /app +COPY --from=fe-build /frontend/dist /frontend/dist + +CMD /app From f1ee941f2f122ea6f2640077e9653ec9d3d2eca9 Mon Sep 17 00:00:00 2001 From: Andriy Semenets Date: Fri, 29 Mar 2024 12:23:41 +0100 Subject: [PATCH 3/8] Implement fe file serving --- Dockerfile | 2 +- backend/go.mod | 5 +++-- backend/go.sum | 11 +++++++---- backend/router/router.go | 3 +++ frontend/src/components/Terminal/Terminal.tsx | 5 ++--- 5 files changed, 16 insertions(+), 10 deletions(-) diff --git a/Dockerfile b/Dockerfile index 798217c..567bf56 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,6 +30,6 @@ RUN go build -o /app FROM alpine:3.14 COPY --from=be-build /app /app -COPY --from=fe-build /frontend/dist /frontend/dist +COPY --from=fe-build /frontend/dist /fe CMD /app diff --git a/backend/go.mod b/backend/go.mod index 614b515..0a50434 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -8,6 +8,7 @@ require ( github.com/docker/docker v25.0.5+incompatible github.com/docker/go-connections v0.5.0 github.com/gin-contrib/cors v1.7.0 + github.com/gin-contrib/static v1.1.1 github.com/gin-gonic/gin v1.9.1 github.com/go-rod/rod v0.114.8 github.com/gorilla/websocket v1.5.0 @@ -24,7 +25,7 @@ require ( github.com/agnivade/levenshtein v1.1.1 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/buger/jsonparser v1.1.1 // indirect - github.com/bytedance/sonic v1.11.2 // indirect + github.com/bytedance/sonic v1.11.3 // indirect github.com/chenzhuoyu/base64x v0.0.0-20230717121745-296ad89f973d // indirect github.com/chenzhuoyu/iasm v0.9.1 // indirect github.com/containerd/log v0.1.0 // indirect @@ -58,7 +59,7 @@ require ( github.com/morikuni/aec v1.0.0 // indirect github.com/opencontainers/go-digest v1.0.0 // indirect github.com/opencontainers/image-spec v1.1.0 // indirect - github.com/pelletier/go-toml/v2 v2.1.1 // indirect + github.com/pelletier/go-toml/v2 v2.2.0 // indirect github.com/pkg/errors v0.9.1 // indirect github.com/rogpeppe/go-internal v1.12.0 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index f7667bd..f3bf5b8 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -32,8 +32,8 @@ github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMU github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/bytedance/sonic v1.5.0/go.mod h1:ED5hyg4y6t3/9Ku1R6dU/4KyJ48DZ4jPhfY1O2AihPM= github.com/bytedance/sonic v1.10.0-rc/go.mod h1:ElCzW+ufi8qKqNW0FY314xriJhyJhuoJ3gFZdAHF7NM= -github.com/bytedance/sonic v1.11.2 h1:ywfwo0a/3j9HR8wsYGWsIWl2mvRsI950HyoxiBERw5A= -github.com/bytedance/sonic v1.11.2/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= +github.com/bytedance/sonic v1.11.3 h1:jRN+yEjakWh8aK5FzrciUHG8OFXK+4/KrAX/ysEtHAA= +github.com/bytedance/sonic v1.11.3/go.mod h1:iZcSUejdk5aukTND/Eu/ivjQuEL0Cu9/rf50Hi0u/g4= github.com/caarlos0/env/v10 v10.0.0 h1:yIHUBZGsyqCnpTkbjk8asUlx6RFhhEs+h7TOBdgdzXA= github.com/caarlos0/env/v10 v10.0.0/go.mod h1:ZfulV76NvVPw3tm591U4SwL3Xx9ldzBP9aGxzeN7G18= github.com/cenkalti/backoff/v4 v4.2.1 h1:y4OZtCnogmCPw98Zjyt5a6+QwPLGkiQsYW5oUqylYbM= @@ -80,6 +80,8 @@ github.com/gin-contrib/cors v1.7.0 h1:wZX2wuZ0o7rV2/1i7gb4Jn+gW7HBqaP91fizJkBUJO github.com/gin-contrib/cors v1.7.0/go.mod h1:cI+h6iOAyxKRtUtC6iF/Si1KSFvGm/gK+kshxlCi8ro= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-contrib/static v1.1.1 h1:XEvBd4DDLG1HBlyPBQU1XO8NlTpw6mgdqcPteetYA5k= +github.com/gin-contrib/static v1.1.1/go.mod h1:yRGmar7+JYvbMLRPIi4H5TVVSBwULfT9vetnVD0IO74= github.com/gin-gonic/gin v1.9.1 h1:4idEAncQnU5cB7BeOkPtxjfCSye0AAm1R0RVIqJ+Jmg= github.com/gin-gonic/gin v1.9.1/go.mod h1:hPrL7YrpYKXt5YId3A/Tnip5kqbEAP+KLuI3SUcPTeU= github.com/go-faster/city v1.0.1 h1:4WAxSZ3V2Ws4QRDrscLEDcibJY8uf41H6AhXDrNDcGw= @@ -190,8 +192,8 @@ github.com/ory/dockertest/v3 v3.10.0 h1:4K3z2VMe8Woe++invjaTB7VRyQXQy5UY+loujO4a github.com/ory/dockertest/v3 v3.10.0/go.mod h1:nr57ZbRWMqfsdGdFNLHz5jjNdDb7VVFnzAeW1n5N1Lg= github.com/paulmach/orb v0.10.0 h1:guVYVqzxHE/CQ1KpfGO077TR0ATHSNjp4s6XGLn3W9s= github.com/paulmach/orb v0.10.0/go.mod h1:5mULz1xQfs3bmQm63QEJA6lNGujuRafwA5S/EnuLaLU= -github.com/pelletier/go-toml/v2 v2.1.1 h1:LWAJwfNvjQZCFIDKWYQaM62NcYeYViCmWIwmOStowAI= -github.com/pelletier/go-toml/v2 v2.1.1/go.mod h1:tJU2Z3ZkXwnxa4DPO899bsyIoywizdUvyaeZurnPPDc= +github.com/pelletier/go-toml/v2 v2.2.0 h1:QLgLl2yMN7N+ruc31VynXs1vhMZa7CeHHejIeBAsoHo= +github.com/pelletier/go-toml/v2 v2.2.0/go.mod h1:1t835xjRzz80PqgE6HHgN2JOsmgYu/h4qDAS4n929Rs= github.com/pierrec/lz4/v4 v4.1.18 h1:xaKrnTkyoqfh1YItXl56+6KJNVYWlEEPuAQW9xsplYQ= github.com/pierrec/lz4/v4 v4.1.18/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -225,6 +227,7 @@ github.com/sosodev/duration v1.2.0/go.mod h1:RQIBBX0+fMLc/D9+Jb/fwvVmo0eZvDDEERA github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw= github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= diff --git a/backend/router/router.go b/backend/router/router.go index e6062e2..82467d1 100644 --- a/backend/router/router.go +++ b/backend/router/router.go @@ -8,6 +8,7 @@ import ( "strconv" "github.com/gin-contrib/cors" + "github.com/gin-contrib/static" "github.com/gin-gonic/gin" "github.com/99designs/gqlgen/graphql" @@ -34,6 +35,8 @@ func New(db *database.Queries) *gin.Engine { config.AllowMethods = []string{"GET", "POST", "PUT", "DELETE", "OPTIONS"} r.Use(cors.New(config)) + r.Use(static.Serve("/", static.LocalFile("./fe", true))) + // GraphQL endpoint r.Any("/graphql", graphqlHandler(db)) diff --git a/frontend/src/components/Terminal/Terminal.tsx b/frontend/src/components/Terminal/Terminal.tsx index 4909974..b311f85 100644 --- a/frontend/src/components/Terminal/Terminal.tsx +++ b/frontend/src/components/Terminal/Terminal.tsx @@ -8,6 +8,8 @@ import { FitAddon } from "xterm-addon-fit"; import { Unicode11Addon } from "xterm-addon-unicode11"; import { WebLinksAddon } from "xterm-addon-web-links"; import { WebglAddon } from "xterm-addon-webgl"; +// @ts-ignore - This package is not typed +import { Broadcast } from "xterm-theme"; import "xterm/css/xterm.css"; import dockerSvg from "@/assets/docker.svg"; @@ -15,9 +17,6 @@ import { Log } from "@/generated/graphql"; import { headerStyles } from "./Terminal.css"; -// We don't have types for xterm-theme, so this is a hack to get it to compile -const { Broadcast } = require("xterm-theme"); - const isWebGl2Supported = !!document .createElement("canvas") .getContext("webgl2"); From e0138ebbac8b8dc2db6db6185e302766f689cdfd Mon Sep 17 00:00:00 2001 From: Andriy Semenets Date: Sun, 31 Mar 2024 22:41:09 +0200 Subject: [PATCH 4/8] Migrate from postgres to sqlite3 --- backend/.gitignore | 1 + backend/agent/agent.go | 53 +++++-------- backend/database/containers.sql.go | 34 +++++---- backend/database/database.go | 8 +- backend/database/db.go | 13 ++-- backend/database/flows.sql.go | 76 ++++++++++--------- backend/database/logs.sql.go | 18 +++-- backend/database/models.go | 45 +++++------ backend/database/tasks.sql.go | 74 +++++++++--------- backend/executor/container.go | 16 ++-- backend/executor/queue.go | 36 ++++----- backend/executor/terminal.go | 10 +-- backend/go.mod | 4 +- backend/go.sum | 2 + backend/graph/schema.resolvers.go | 26 +++---- backend/main.go | 40 ++-------- .../20240325154630_initial_migration.sql | 43 ++++++----- .../20240325193843_add_logs_table.sql | 6 +- .../20240327201116_migrate_to_timestamptz.sql | 17 ----- backend/models/containers.sql | 10 +-- backend/models/flows.sql | 16 ++-- backend/models/logs.sql | 4 +- backend/models/tasks.sql | 16 ++-- backend/sqlc.yml | 3 +- 24 files changed, 264 insertions(+), 307 deletions(-) delete mode 100644 backend/migrations/20240327201116_migrate_to_timestamptz.sql diff --git a/backend/.gitignore b/backend/.gitignore index cc49bfa..d68dfae 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,2 +1,3 @@ ai-coder tmp +database diff --git a/backend/agent/agent.go b/backend/agent/agent.go index 4345c9a..30792a1 100644 --- a/backend/agent/agent.go +++ b/backend/agent/agent.go @@ -2,12 +2,12 @@ package agent import ( "context" + "database/sql" "encoding/json" "fmt" "log" "github.com/invopop/jsonschema" - "github.com/jackc/pgx/v5/pgtype" openai "github.com/sashabaranov/go-openai" "github.com/semanser/ai-coder/assets" "github.com/semanser/ai-coder/config" @@ -137,7 +137,7 @@ func NextTask(args AgentPrompt) *database.Task { if task.Type.String == "input" { messages = append(messages, openai.ChatCompletionMessage{ Role: openai.ChatMessageRoleUser, - Content: string(task.Args), + Content: task.Args.String, }) } @@ -149,7 +149,7 @@ func NextTask(args AgentPrompt) *database.Task { ID: task.ToolCallID.String, Function: openai.FunctionCall{ Name: task.Type.String, - Arguments: string(task.Args), + Arguments: task.Args.String, }, Type: openai.ToolTypeFunction, }, @@ -212,7 +212,7 @@ func NextTask(args AgentPrompt) *database.Task { } task := database.Task{ - Type: database.StringToPgText(tool.Function.Name), + Type: database.StringToNullString(tool.Function.Name), } switch tool.Function.Name { @@ -227,7 +227,7 @@ func NextTask(args AgentPrompt) *database.Task { log.Printf("Failed to marshal terminal args, asking user: %v", err) return defaultAskTask("There was an error running the terminal command") } - task.Args = args + task.Args = database.StringToNullString(string(args)) // Sometimes the model returns an empty string for the message msg := string(params.Message) @@ -235,8 +235,8 @@ func NextTask(args AgentPrompt) *database.Task { msg = params.Input } - task.Message = database.StringToPgText(msg) - task.Status = database.StringToPgText("in_progress") + task.Message = database.StringToNullString(msg) + task.Status = database.StringToNullString("in_progress") case "browser": params, err := extractArgs(tool.Function.Arguments, &BrowserArgs{}) @@ -249,11 +249,8 @@ func NextTask(args AgentPrompt) *database.Task { log.Printf("Failed to marshal browser args, asking user: %v", err) return defaultAskTask("There was an error opening the browser") } - task.Args = args - task.Message = pgtype.Text{ - String: string(params.Message), - Valid: true, - } + task.Args = database.StringToNullString(string(args)) + task.Message = database.StringToNullString(string(params.Message)) case "code": params, err := extractArgs(tool.Function.Arguments, &CodeArgs{}) if err != nil { @@ -265,11 +262,8 @@ func NextTask(args AgentPrompt) *database.Task { log.Printf("Failed to marshal code args, asking user: %v", err) return defaultAskTask("There was an error reading or updating the file") } - task.Args = args - task.Message = pgtype.Text{ - String: string(params.Message), - Valid: true, - } + task.Args = database.StringToNullString(string(args)) + task.Message = database.StringToNullString(string(params.Message)) case "ask": params, err := extractArgs(tool.Function.Arguments, &AskArgs{}) if err != nil { @@ -281,11 +275,8 @@ func NextTask(args AgentPrompt) *database.Task { log.Printf("Failed to marshal ask args, asking user: %v", err) return defaultAskTask("There was an error asking the user for additional information") } - task.Args = args - task.Message = pgtype.Text{ - String: string(params.Message), - Valid: true, - } + task.Args = database.StringToNullString(string(args)) + task.Message = database.StringToNullString(string(params.Message)) case "done": params, err := extractArgs(tool.Function.Arguments, &DoneArgs{}) if err != nil { @@ -296,28 +287,22 @@ func NextTask(args AgentPrompt) *database.Task { if err != nil { return defaultAskTask("There was an error marking the task as done") } - task.Args = args - task.Message = pgtype.Text{ - String: string(params.Message), - Valid: true, - } + task.Args = database.StringToNullString(string(args)) + task.Message = database.StringToNullString(string(params.Message)) } - task.ToolCallID = pgtype.Text{ - String: tool.ID, - Valid: true, - } + task.ToolCallID = database.StringToNullString(tool.ID) return &task } func defaultAskTask(message string) *database.Task { task := database.Task{ - Type: database.StringToPgText("ask"), + Type: database.StringToNullString("ask"), } - task.Args = []byte("{}") - task.Message = pgtype.Text{ + task.Args = database.StringToNullString("{}") + task.Message = sql.NullString{ String: fmt.Sprintf("%s. What should I do next?", message), Valid: true, } diff --git a/backend/database/containers.sql.go b/backend/database/containers.sql.go index 579212b..82dad5a 100644 --- a/backend/database/containers.sql.go +++ b/backend/database/containers.sql.go @@ -7,8 +7,7 @@ package database import ( "context" - - "github.com/jackc/pgx/v5/pgtype" + "database/sql" ) const createContainer = `-- name: CreateContainer :one @@ -16,19 +15,19 @@ INSERT INTO containers ( name, image, status ) VALUES ( - $1, $2, $3 + ?, ?, ? ) RETURNING id, name, local_id, image, status ` type CreateContainerParams struct { - Name pgtype.Text - Image pgtype.Text - Status pgtype.Text + Name sql.NullString + Image sql.NullString + Status sql.NullString } func (q *Queries) CreateContainer(ctx context.Context, arg CreateContainerParams) (Container, error) { - row := q.db.QueryRow(ctx, createContainer, arg.Name, arg.Image, arg.Status) + row := q.db.QueryRowContext(ctx, createContainer, arg.Name, arg.Image, arg.Status) var i Container err := row.Scan( &i.ID, @@ -45,7 +44,7 @@ SELECT id, name, local_id, image, status FROM containers WHERE status = 'running ` func (q *Queries) GetAllRunningContainers(ctx context.Context) ([]Container, error) { - rows, err := q.db.Query(ctx, getAllRunningContainers) + rows, err := q.db.QueryContext(ctx, getAllRunningContainers) if err != nil { return nil, err } @@ -64,6 +63,9 @@ func (q *Queries) GetAllRunningContainers(ctx context.Context) ([]Container, err } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -72,18 +74,18 @@ func (q *Queries) GetAllRunningContainers(ctx context.Context) ([]Container, err const updateContainerLocalId = `-- name: UpdateContainerLocalId :one UPDATE containers -SET local_id = $1 -WHERE id = $2 +SET local_id = ? +WHERE id = ? RETURNING id, name, local_id, image, status ` type UpdateContainerLocalIdParams struct { - LocalID pgtype.Text + LocalID sql.NullString ID int64 } func (q *Queries) UpdateContainerLocalId(ctx context.Context, arg UpdateContainerLocalIdParams) (Container, error) { - row := q.db.QueryRow(ctx, updateContainerLocalId, arg.LocalID, arg.ID) + row := q.db.QueryRowContext(ctx, updateContainerLocalId, arg.LocalID, arg.ID) var i Container err := row.Scan( &i.ID, @@ -97,18 +99,18 @@ func (q *Queries) UpdateContainerLocalId(ctx context.Context, arg UpdateContaine const updateContainerStatus = `-- name: UpdateContainerStatus :one UPDATE containers -SET status = $1 -WHERE id = $2 +SET status = ? +WHERE id = ? RETURNING id, name, local_id, image, status ` type UpdateContainerStatusParams struct { - Status pgtype.Text + Status sql.NullString ID int64 } func (q *Queries) UpdateContainerStatus(ctx context.Context, arg UpdateContainerStatusParams) (Container, error) { - row := q.db.QueryRow(ctx, updateContainerStatus, arg.Status, arg.ID) + row := q.db.QueryRowContext(ctx, updateContainerStatus, arg.Status, arg.ID) var i Container err := row.Scan( &i.ID, diff --git a/backend/database/database.go b/backend/database/database.go index cde9887..3822313 100644 --- a/backend/database/database.go +++ b/backend/database/database.go @@ -1,7 +1,9 @@ package database -import "github.com/jackc/pgx/v5/pgtype" +import ( + "database/sql" +) -func StringToPgText(s string) pgtype.Text { - return pgtype.Text{String: s, Valid: true} +func StringToNullString(s string) sql.NullString { + return sql.NullString{String: s, Valid: true} } diff --git a/backend/database/db.go b/backend/database/db.go index e7c370a..61f5bf4 100644 --- a/backend/database/db.go +++ b/backend/database/db.go @@ -6,15 +6,14 @@ package database import ( "context" - - "github.com/jackc/pgx/v5" - "github.com/jackc/pgx/v5/pgconn" + "database/sql" ) type DBTX interface { - Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error) - Query(context.Context, string, ...interface{}) (pgx.Rows, error) - QueryRow(context.Context, string, ...interface{}) pgx.Row + ExecContext(context.Context, string, ...interface{}) (sql.Result, error) + PrepareContext(context.Context, string) (*sql.Stmt, error) + QueryContext(context.Context, string, ...interface{}) (*sql.Rows, error) + QueryRowContext(context.Context, string, ...interface{}) *sql.Row } func New(db DBTX) *Queries { @@ -25,7 +24,7 @@ type Queries struct { db DBTX } -func (q *Queries) WithTx(tx pgx.Tx) *Queries { +func (q *Queries) WithTx(tx *sql.Tx) *Queries { return &Queries{ db: tx, } diff --git a/backend/database/flows.sql.go b/backend/database/flows.sql.go index 760b765..f404ee1 100644 --- a/backend/database/flows.sql.go +++ b/backend/database/flows.sql.go @@ -7,8 +7,7 @@ package database import ( "context" - - "github.com/jackc/pgx/v5/pgtype" + "database/sql" ) const createFlow = `-- name: CreateFlow :one @@ -16,19 +15,19 @@ INSERT INTO flows ( name, status, container_id ) VALUES ( - $1, $2, $3 + ?, ?, ? ) RETURNING id, created_at, updated_at, name, status, container_id ` type CreateFlowParams struct { - Name pgtype.Text - Status pgtype.Text - ContainerID pgtype.Int8 + Name sql.NullString + Status sql.NullString + ContainerID sql.NullInt64 } func (q *Queries) CreateFlow(ctx context.Context, arg CreateFlowParams) (Flow, error) { - row := q.db.QueryRow(ctx, createFlow, arg.Name, arg.Status, arg.ContainerID) + row := q.db.QueryRowContext(ctx, createFlow, arg.Name, arg.Status, arg.ContainerID) var i Flow err := row.Scan( &i.ID, @@ -52,16 +51,16 @@ ORDER BY f.created_at DESC type ReadAllFlowsRow struct { ID int64 - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz - Name pgtype.Text - Status pgtype.Text - ContainerID pgtype.Int8 - ContainerName pgtype.Text + CreatedAt sql.NullTime + UpdatedAt sql.NullTime + Name sql.NullString + Status sql.NullString + ContainerID sql.NullInt64 + ContainerName sql.NullString } func (q *Queries) ReadAllFlows(ctx context.Context) ([]ReadAllFlowsRow, error) { - rows, err := q.db.Query(ctx, readAllFlows) + rows, err := q.db.QueryContext(ctx, readAllFlows) if err != nil { return nil, err } @@ -82,6 +81,9 @@ func (q *Queries) ReadAllFlows(ctx context.Context) ([]ReadAllFlowsRow, error) { } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -97,24 +99,24 @@ SELECT c.local_id AS container_local_id FROM flows f LEFT JOIN containers c ON f.container_id = c.id -WHERE f.id = $1 +WHERE f.id = ? ` type ReadFlowRow struct { ID int64 - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz - Name pgtype.Text - Status pgtype.Text - ContainerID pgtype.Int8 - ContainerName pgtype.Text - ContainerImage pgtype.Text - ContainerStatus pgtype.Text - ContainerLocalID pgtype.Text + CreatedAt sql.NullTime + UpdatedAt sql.NullTime + Name sql.NullString + Status sql.NullString + ContainerID sql.NullInt64 + ContainerName sql.NullString + ContainerImage sql.NullString + ContainerStatus sql.NullString + ContainerLocalID sql.NullString } func (q *Queries) ReadFlow(ctx context.Context, id int64) (ReadFlowRow, error) { - row := q.db.QueryRow(ctx, readFlow, id) + row := q.db.QueryRowContext(ctx, readFlow, id) var i ReadFlowRow err := row.Scan( &i.ID, @@ -133,18 +135,18 @@ func (q *Queries) ReadFlow(ctx context.Context, id int64) (ReadFlowRow, error) { const updateFlowContainer = `-- name: UpdateFlowContainer :one UPDATE flows -SET container_id = $1 -WHERE id = $2 +SET container_id = ? +WHERE id = ? RETURNING id, created_at, updated_at, name, status, container_id ` type UpdateFlowContainerParams struct { - ContainerID pgtype.Int8 + ContainerID sql.NullInt64 ID int64 } func (q *Queries) UpdateFlowContainer(ctx context.Context, arg UpdateFlowContainerParams) (Flow, error) { - row := q.db.QueryRow(ctx, updateFlowContainer, arg.ContainerID, arg.ID) + row := q.db.QueryRowContext(ctx, updateFlowContainer, arg.ContainerID, arg.ID) var i Flow err := row.Scan( &i.ID, @@ -159,18 +161,18 @@ func (q *Queries) UpdateFlowContainer(ctx context.Context, arg UpdateFlowContain const updateFlowName = `-- name: UpdateFlowName :one UPDATE flows -SET name = $1 -WHERE id = $2 +SET name = ? +WHERE id = ? RETURNING id, created_at, updated_at, name, status, container_id ` type UpdateFlowNameParams struct { - Name pgtype.Text + Name sql.NullString ID int64 } func (q *Queries) UpdateFlowName(ctx context.Context, arg UpdateFlowNameParams) (Flow, error) { - row := q.db.QueryRow(ctx, updateFlowName, arg.Name, arg.ID) + row := q.db.QueryRowContext(ctx, updateFlowName, arg.Name, arg.ID) var i Flow err := row.Scan( &i.ID, @@ -185,18 +187,18 @@ func (q *Queries) UpdateFlowName(ctx context.Context, arg UpdateFlowNameParams) const updateFlowStatus = `-- name: UpdateFlowStatus :one UPDATE flows -SET status = $1 -WHERE id = $2 +SET status = ? +WHERE id = ? RETURNING id, created_at, updated_at, name, status, container_id ` type UpdateFlowStatusParams struct { - Status pgtype.Text + Status sql.NullString ID int64 } func (q *Queries) UpdateFlowStatus(ctx context.Context, arg UpdateFlowStatusParams) (Flow, error) { - row := q.db.QueryRow(ctx, updateFlowStatus, arg.Status, arg.ID) + row := q.db.QueryRowContext(ctx, updateFlowStatus, arg.Status, arg.ID) var i Flow err := row.Scan( &i.ID, diff --git a/backend/database/logs.sql.go b/backend/database/logs.sql.go index a63ed76..ad63cee 100644 --- a/backend/database/logs.sql.go +++ b/backend/database/logs.sql.go @@ -7,8 +7,7 @@ package database import ( "context" - - "github.com/jackc/pgx/v5/pgtype" + "database/sql" ) const createLog = `-- name: CreateLog :one @@ -16,19 +15,19 @@ INSERT INTO logs ( message, flow_id, type ) VALUES ( - $1, $2, $3 + ?, ?, ? ) RETURNING id, message, created_at, flow_id, type ` type CreateLogParams struct { Message string - FlowID pgtype.Int8 + FlowID sql.NullInt64 Type string } func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, error) { - row := q.db.QueryRow(ctx, createLog, arg.Message, arg.FlowID, arg.Type) + row := q.db.QueryRowContext(ctx, createLog, arg.Message, arg.FlowID, arg.Type) var i Log err := row.Scan( &i.ID, @@ -43,12 +42,12 @@ func (q *Queries) CreateLog(ctx context.Context, arg CreateLogParams) (Log, erro const getLogsByFlowId = `-- name: GetLogsByFlowId :many SELECT id, message, created_at, flow_id, type FROM logs -WHERE flow_id = $1 +WHERE flow_id = ? ORDER BY created_at ASC ` -func (q *Queries) GetLogsByFlowId(ctx context.Context, flowID pgtype.Int8) ([]Log, error) { - rows, err := q.db.Query(ctx, getLogsByFlowId, flowID) +func (q *Queries) GetLogsByFlowId(ctx context.Context, flowID sql.NullInt64) ([]Log, error) { + rows, err := q.db.QueryContext(ctx, getLogsByFlowId, flowID) if err != nil { return nil, err } @@ -67,6 +66,9 @@ func (q *Queries) GetLogsByFlowId(ctx context.Context, flowID pgtype.Int8) ([]Lo } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } diff --git a/backend/database/models.go b/backend/database/models.go index 8e0bdb9..795487b 100644 --- a/backend/database/models.go +++ b/backend/database/models.go @@ -5,43 +5,44 @@ package database import ( - "github.com/jackc/pgx/v5/pgtype" + "database/sql" + "time" ) type Container struct { ID int64 - Name pgtype.Text - LocalID pgtype.Text - Image pgtype.Text - Status pgtype.Text + Name sql.NullString + LocalID sql.NullString + Image sql.NullString + Status sql.NullString } type Flow struct { ID int64 - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz - Name pgtype.Text - Status pgtype.Text - ContainerID pgtype.Int8 + CreatedAt sql.NullTime + UpdatedAt sql.NullTime + Name sql.NullString + Status sql.NullString + ContainerID sql.NullInt64 } type Log struct { - ID int32 + ID int64 Message string - CreatedAt pgtype.Timestamptz - FlowID pgtype.Int8 + CreatedAt time.Time + FlowID sql.NullInt64 Type string } type Task struct { ID int64 - CreatedAt pgtype.Timestamptz - UpdatedAt pgtype.Timestamptz - Type pgtype.Text - Status pgtype.Text - Args []byte - Results pgtype.Text - FlowID pgtype.Int8 - Message pgtype.Text - ToolCallID pgtype.Text + CreatedAt sql.NullTime + UpdatedAt sql.NullTime + Type sql.NullString + Status sql.NullString + Args sql.NullString + Results sql.NullString + Message sql.NullString + FlowID sql.NullInt64 + ToolCallID sql.NullString } diff --git a/backend/database/tasks.sql.go b/backend/database/tasks.sql.go index 5935484..2203c7c 100644 --- a/backend/database/tasks.sql.go +++ b/backend/database/tasks.sql.go @@ -7,8 +7,7 @@ package database import ( "context" - - "github.com/jackc/pgx/v5/pgtype" + "database/sql" ) const createTask = `-- name: CreateTask :one @@ -21,23 +20,23 @@ INSERT INTO tasks ( message, tool_call_id ) VALUES ( - $1, $2, $3, $4, $5, $6, $7 + ?, ?, ?, ?, ?, ?, ? ) -RETURNING id, created_at, updated_at, type, status, args, results, flow_id, message, tool_call_id +RETURNING id, created_at, updated_at, type, status, args, results, message, flow_id, tool_call_id ` type CreateTaskParams struct { - Type pgtype.Text - Status pgtype.Text - Args []byte - Results pgtype.Text - FlowID pgtype.Int8 - Message pgtype.Text - ToolCallID pgtype.Text + Type sql.NullString + Status sql.NullString + Args sql.NullString + Results sql.NullString + FlowID sql.NullInt64 + Message sql.NullString + ToolCallID sql.NullString } func (q *Queries) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, error) { - row := q.db.QueryRow(ctx, createTask, + row := q.db.QueryRowContext(ctx, createTask, arg.Type, arg.Status, arg.Args, @@ -55,21 +54,21 @@ func (q *Queries) CreateTask(ctx context.Context, arg CreateTaskParams) (Task, e &i.Status, &i.Args, &i.Results, - &i.FlowID, &i.Message, + &i.FlowID, &i.ToolCallID, ) return i, err } const readTasksByFlowId = `-- name: ReadTasksByFlowId :many -SELECT id, created_at, updated_at, type, status, args, results, flow_id, message, tool_call_id FROM tasks -WHERE flow_id = $1 +SELECT id, created_at, updated_at, type, status, args, results, message, flow_id, tool_call_id FROM tasks +WHERE flow_id = ? ORDER BY created_at ASC ` -func (q *Queries) ReadTasksByFlowId(ctx context.Context, flowID pgtype.Int8) ([]Task, error) { - rows, err := q.db.Query(ctx, readTasksByFlowId, flowID) +func (q *Queries) ReadTasksByFlowId(ctx context.Context, flowID sql.NullInt64) ([]Task, error) { + rows, err := q.db.QueryContext(ctx, readTasksByFlowId, flowID) if err != nil { return nil, err } @@ -85,14 +84,17 @@ func (q *Queries) ReadTasksByFlowId(ctx context.Context, flowID pgtype.Int8) ([] &i.Status, &i.Args, &i.Results, - &i.FlowID, &i.Message, + &i.FlowID, &i.ToolCallID, ); err != nil { return nil, err } items = append(items, i) } + if err := rows.Close(); err != nil { + return nil, err + } if err := rows.Err(); err != nil { return nil, err } @@ -101,18 +103,18 @@ func (q *Queries) ReadTasksByFlowId(ctx context.Context, flowID pgtype.Int8) ([] const updateTaskResults = `-- name: UpdateTaskResults :one UPDATE tasks -SET results = $1 -WHERE id = $2 -RETURNING id, created_at, updated_at, type, status, args, results, flow_id, message, tool_call_id +SET results = ? +WHERE id = ? +RETURNING id, created_at, updated_at, type, status, args, results, message, flow_id, tool_call_id ` type UpdateTaskResultsParams struct { - Results pgtype.Text + Results sql.NullString ID int64 } func (q *Queries) UpdateTaskResults(ctx context.Context, arg UpdateTaskResultsParams) (Task, error) { - row := q.db.QueryRow(ctx, updateTaskResults, arg.Results, arg.ID) + row := q.db.QueryRowContext(ctx, updateTaskResults, arg.Results, arg.ID) var i Task err := row.Scan( &i.ID, @@ -122,8 +124,8 @@ func (q *Queries) UpdateTaskResults(ctx context.Context, arg UpdateTaskResultsPa &i.Status, &i.Args, &i.Results, - &i.FlowID, &i.Message, + &i.FlowID, &i.ToolCallID, ) return i, err @@ -131,18 +133,18 @@ func (q *Queries) UpdateTaskResults(ctx context.Context, arg UpdateTaskResultsPa const updateTaskStatus = `-- name: UpdateTaskStatus :one UPDATE tasks -SET status = $1 -WHERE id = $2 -RETURNING id, created_at, updated_at, type, status, args, results, flow_id, message, tool_call_id +SET status = ? +WHERE id = ? +RETURNING id, created_at, updated_at, type, status, args, results, message, flow_id, tool_call_id ` type UpdateTaskStatusParams struct { - Status pgtype.Text + Status sql.NullString ID int64 } func (q *Queries) UpdateTaskStatus(ctx context.Context, arg UpdateTaskStatusParams) (Task, error) { - row := q.db.QueryRow(ctx, updateTaskStatus, arg.Status, arg.ID) + row := q.db.QueryRowContext(ctx, updateTaskStatus, arg.Status, arg.ID) var i Task err := row.Scan( &i.ID, @@ -152,8 +154,8 @@ func (q *Queries) UpdateTaskStatus(ctx context.Context, arg UpdateTaskStatusPara &i.Status, &i.Args, &i.Results, - &i.FlowID, &i.Message, + &i.FlowID, &i.ToolCallID, ) return i, err @@ -161,18 +163,18 @@ func (q *Queries) UpdateTaskStatus(ctx context.Context, arg UpdateTaskStatusPara const updateTaskToolCallId = `-- name: UpdateTaskToolCallId :one UPDATE tasks -SET tool_call_id = $1 -WHERE id = $2 -RETURNING id, created_at, updated_at, type, status, args, results, flow_id, message, tool_call_id +SET tool_call_id = ? +WHERE id = ? +RETURNING id, created_at, updated_at, type, status, args, results, message, flow_id, tool_call_id ` type UpdateTaskToolCallIdParams struct { - ToolCallID pgtype.Text + ToolCallID sql.NullString ID int64 } func (q *Queries) UpdateTaskToolCallId(ctx context.Context, arg UpdateTaskToolCallIdParams) (Task, error) { - row := q.db.QueryRow(ctx, updateTaskToolCallId, arg.ToolCallID, arg.ID) + row := q.db.QueryRowContext(ctx, updateTaskToolCallId, arg.ToolCallID, arg.ID) var i Task err := row.Scan( &i.ID, @@ -182,8 +184,8 @@ func (q *Queries) UpdateTaskToolCallId(ctx context.Context, arg UpdateTaskToolCa &i.Status, &i.Args, &i.Results, - &i.FlowID, &i.Message, + &i.FlowID, &i.ToolCallID, ) return i, err diff --git a/backend/executor/container.go b/backend/executor/container.go index 3d221c7..2e3db27 100644 --- a/backend/executor/container.go +++ b/backend/executor/container.go @@ -46,9 +46,9 @@ func SpawnContainer(ctx context.Context, name string, config *container.Config, log.Printf("Spawning container %s \"%s\"\n", config.Image, name) dbContainer, err := db.CreateContainer(ctx, database.CreateContainerParams{ - Name: database.StringToPgText(name), - Image: database.StringToPgText(config.Image), - Status: database.StringToPgText("starting"), + Name: database.StringToNullString(name), + Image: database.StringToNullString(config.Image), + Status: database.StringToNullString("starting"), }) if err != nil { @@ -72,7 +72,7 @@ func SpawnContainer(ctx context.Context, name string, config *container.Config, _, err := db.UpdateContainerStatus(ctx, database.UpdateContainerStatusParams{ ID: dbContainer.ID, - Status: database.StringToPgText(status), + Status: database.StringToNullString(status), }) if err != nil { @@ -81,7 +81,7 @@ func SpawnContainer(ctx context.Context, name string, config *container.Config, _, err = db.UpdateContainerLocalId(ctx, database.UpdateContainerLocalIdParams{ ID: dbContainer.ID, - LocalID: database.StringToPgText(localContainerID), + LocalID: database.StringToNullString(localContainerID), }) }() @@ -143,7 +143,7 @@ func StopContainer(containerID string, dbID int64, db *database.Queries) error { if client.IsErrNotFound(err) { log.Printf("Container %s not found. Marking it as stopped.\n", containerID) db.UpdateContainerStatus(context.Background(), database.UpdateContainerStatusParams{ - Status: database.StringToPgText("stopped"), + Status: database.StringToNullString("stopped"), ID: dbID, }) @@ -154,7 +154,7 @@ func StopContainer(containerID string, dbID int64, db *database.Queries) error { } _, err := db.UpdateContainerStatus(context.Background(), database.UpdateContainerStatusParams{ - Status: database.StringToPgText("stopped"), + Status: database.StringToNullString("stopped"), ID: dbID, }) @@ -220,7 +220,7 @@ func Cleanup(db *database.Queries) error { for _, flow := range flows { if flow.Status.String == "in_progress" { _, err := db.UpdateFlowStatus(context.Background(), database.UpdateFlowStatusParams{ - Status: database.StringToPgText("finished"), + Status: database.StringToNullString("finished"), ID: flow.ID, }) diff --git a/backend/executor/queue.go b/backend/executor/queue.go index a6630b3..6b26023 100644 --- a/backend/executor/queue.go +++ b/backend/executor/queue.go @@ -2,12 +2,12 @@ package executor import ( "context" + "database/sql" "encoding/json" "fmt" "log" "github.com/docker/docker/api/types/container" - "github.com/jackc/pgx/v5/pgtype" "github.com/semanser/ai-coder/agent" "github.com/semanser/ai-coder/database" gmodel "github.com/semanser/ai-coder/graph/model" @@ -73,7 +73,7 @@ func ProcessQueue(flowId int64, db *database.Queries) { Type: gmodel.TaskType(task.Type.String), CreatedAt: task.CreatedAt.Time, Status: gmodel.TaskStatus(task.Status.String), - Args: string(task.Args), + Args: task.Args.String, Results: task.Results.String, }) @@ -172,7 +172,7 @@ func ProcessQueue(flowId int64, db *database.Queries) { func processBrowserTask(db *database.Queries, task database.Task) error { var args = agent.BrowserArgs{} - err := json.Unmarshal(task.Args, &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { return fmt.Errorf("failed to unmarshal args: %v", err) } @@ -192,7 +192,7 @@ func processBrowserTask(db *database.Queries, task database.Task) error { _, err = db.UpdateTaskResults(context.Background(), database.UpdateTaskResultsParams{ ID: task.ID, - Results: database.StringToPgText(content), + Results: database.StringToNullString(content), }) if err != nil { @@ -211,7 +211,7 @@ func processBrowserTask(db *database.Queries, task database.Task) error { _, err = db.UpdateTaskResults(context.Background(), database.UpdateTaskResultsParams{ ID: task.ID, - Results: database.StringToPgText(content), + Results: database.StringToNullString(content), }) if err != nil { @@ -231,7 +231,7 @@ func processBrowserTask(db *database.Queries, task database.Task) error { func processDoneTask(db *database.Queries, task database.Task) error { flow, err := db.UpdateFlowStatus(context.Background(), database.UpdateFlowStatusParams{ ID: task.FlowID.Int64, - Status: database.StringToPgText("finished"), + Status: database.StringToNullString("finished"), }) if err != nil { @@ -248,7 +248,7 @@ func processDoneTask(db *database.Queries, task database.Task) error { } func processInputTask(db *database.Queries, task database.Task) error { - tasks, err := db.ReadTasksByFlowId(context.Background(), pgtype.Int8{ + tasks, err := db.ReadTasksByFlowId(context.Background(), sql.NullInt64{ Int64: task.FlowID.Int64, Valid: true, }) @@ -274,7 +274,7 @@ func processInputTask(db *database.Queries, task database.Task) error { flow, err := db.UpdateFlowName(context.Background(), database.UpdateFlowNameParams{ ID: task.FlowID.Int64, - Name: database.StringToPgText(summary), + Name: database.StringToNullString(summary), }) if err != nil { @@ -332,7 +332,7 @@ func processInputTask(db *database.Queries, task database.Task) error { _, err = db.UpdateFlowContainer(context.Background(), database.UpdateFlowContainerParams{ ID: flow.ID, - ContainerID: pgtype.Int8{Int64: terminalContainerID, Valid: true}, + ContainerID: sql.NullInt64{Int64: terminalContainerID, Valid: true}, }) if err != nil { @@ -364,7 +364,7 @@ func processInputTask(db *database.Queries, task database.Task) error { func processAskTask(db *database.Queries, task database.Task) error { task, err := db.UpdateTaskStatus(context.Background(), database.UpdateTaskStatusParams{ - Status: database.StringToPgText("finished"), + Status: database.StringToNullString("finished"), ID: task.ID, }) @@ -377,7 +377,7 @@ func processAskTask(db *database.Queries, task database.Task) error { func processTerminalTask(db *database.Queries, task database.Task) error { var args = agent.TerminalArgs{} - err := json.Unmarshal(task.Args, &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { return fmt.Errorf("failed to unmarshal args: %v", err) } @@ -390,7 +390,7 @@ func processTerminalTask(db *database.Queries, task database.Task) error { _, err = db.UpdateTaskResults(context.Background(), database.UpdateTaskResultsParams{ ID: task.ID, - Results: database.StringToPgText(results), + Results: database.StringToNullString(results), }) if err != nil { @@ -402,7 +402,7 @@ func processTerminalTask(db *database.Queries, task database.Task) error { func processCodeTask(db *database.Queries, task database.Task) error { var args = agent.CodeArgs{} - err := json.Unmarshal(task.Args, &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { return fmt.Errorf("failed to unmarshal args: %v", err) } @@ -436,7 +436,7 @@ func processCodeTask(db *database.Queries, task database.Task) error { _, err = db.UpdateTaskResults(context.Background(), database.UpdateTaskResultsParams{ ID: task.ID, - Results: database.StringToPgText(results), + Results: database.StringToNullString(results), }) if err != nil { @@ -453,7 +453,7 @@ func getNextTask(db *database.Queries, flowId int64) (*database.Task, error) { return nil, fmt.Errorf("failed to get flow: %w", err) } - tasks, err := db.ReadTasksByFlowId(context.Background(), pgtype.Int8{ + tasks, err := db.ReadTasksByFlowId(context.Background(), sql.NullInt64{ Int64: flowId, Valid: true, }) @@ -468,7 +468,7 @@ func getNextTask(db *database.Queries, flowId int64) (*database.Task, error) { if len(task.Results.String) > maxResultsLength { // Get the last N symbols from the output results := task.Results.String[len(task.Results.String)-maxResultsLength:] - tasks[i].Results = database.StringToPgText(results) + tasks[i].Results = database.StringToNullString(results) } } @@ -492,8 +492,8 @@ func getNextTask(db *database.Queries, flowId int64) (*database.Task, error) { Args: c.Args, Message: c.Message, Type: c.Type, - Status: database.StringToPgText("in_progress"), - FlowID: pgtype.Int8{Int64: flowId, Valid: true}, + Status: database.StringToNullString("in_progress"), + FlowID: sql.NullInt64{Int64: flowId, Valid: true}, ToolCallID: c.ToolCallID, }) diff --git a/backend/executor/terminal.go b/backend/executor/terminal.go index cb1791d..12c5f84 100644 --- a/backend/executor/terminal.go +++ b/backend/executor/terminal.go @@ -4,12 +4,12 @@ import ( "archive/tar" "bytes" "context" + "database/sql" "fmt" "io" "path/filepath" "github.com/docker/docker/api/types" - "github.com/jackc/pgx/v5/pgtype" "github.com/semanser/ai-coder/database" gmodel "github.com/semanser/ai-coder/graph/model" "github.com/semanser/ai-coder/graph/subscriptions" @@ -39,7 +39,7 @@ func ExecCommand(flowID int64, command string, db *database.Queries) (result str // TODO avoid duplicating here and in the flows table log, err := db.CreateLog(context.Background(), database.CreateLogParams{ - FlowID: pgtype.Int8{Int64: flowID, Valid: true}, + FlowID: sql.NullInt64{Int64: flowID, Valid: true}, Message: command, Type: "input", }) @@ -88,7 +88,7 @@ func ExecCommand(flowID int64, command string, db *database.Queries) (result str // TODO avoid duplicating here and in the flows table log, err = db.CreateLog(context.Background(), database.CreateLogParams{ - FlowID: pgtype.Int8{Int64: flowID, Valid: true}, + FlowID: sql.NullInt64{Int64: flowID, Valid: true}, Message: results, Type: "output", }) @@ -127,7 +127,7 @@ func WriteFile(flowID int64, content string, path string, db *database.Queries) // TODO avoid duplicating here and in the flows table log, err := db.CreateLog(context.Background(), database.CreateLogParams{ - FlowID: pgtype.Int8{Int64: flowID, Valid: true}, + FlowID: sql.NullInt64{Int64: flowID, Valid: true}, Message: content, Type: "input", }) @@ -171,7 +171,7 @@ func WriteFile(flowID int64, content string, path string, db *database.Queries) // TODO avoid duplicating here and in the flows table log, err = db.CreateLog(context.Background(), database.CreateLogParams{ - FlowID: pgtype.Int8{Int64: flowID, Valid: true}, + FlowID: sql.NullInt64{Int64: flowID, Valid: true}, Message: message, Type: "output", }) diff --git a/backend/go.mod b/backend/go.mod index 0a50434..e7c5f5b 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -13,8 +13,8 @@ require ( github.com/go-rod/rod v0.114.8 github.com/gorilla/websocket v1.5.0 github.com/invopop/jsonschema v0.12.0 - github.com/jackc/pgx/v5 v5.5.5 github.com/joho/godotenv v1.5.1 + github.com/mattn/go-sqlite3 v1.14.22 github.com/pressly/goose/v3 v3.19.2 github.com/sashabaranov/go-openai v1.20.4 github.com/vektah/gqlparser/v2 v2.5.11 @@ -44,9 +44,7 @@ require ( github.com/gogo/protobuf v1.3.2 // indirect github.com/google/uuid v1.6.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20231201235250-de7065d80cb9 // indirect - github.com/jackc/puddle/v2 v2.2.1 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/cpuid/v2 v2.2.7 // indirect github.com/leodido/go-urn v1.4.0 // indirect diff --git a/backend/go.sum b/backend/go.sum index f3bf5b8..f1b55b1 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -167,6 +167,8 @@ github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0 github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= +github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mfridman/interpolate v0.0.2 h1:pnuTK7MQIxxFz1Gr+rjSIx9u7qVjf5VOoM/u6BbAxPY= github.com/mfridman/interpolate v0.0.2/go.mod h1:p+7uk6oE07mpE/Ik1b8EckO0O4ZXiGAfshKBWLUM9Xg= github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= diff --git a/backend/graph/schema.resolvers.go b/backend/graph/schema.resolvers.go index 88ae160..8c9dc5c 100644 --- a/backend/graph/schema.resolvers.go +++ b/backend/graph/schema.resolvers.go @@ -7,11 +7,11 @@ package graph import ( "bytes" "context" + "database/sql" "encoding/json" "fmt" "log" - "github.com/jackc/pgx/v5/pgtype" "github.com/semanser/ai-coder/database" "github.com/semanser/ai-coder/executor" gmodel "github.com/semanser/ai-coder/graph/model" @@ -22,8 +22,8 @@ import ( // CreateFlow is the resolver for the createFlow field. func (r *mutationResolver) CreateFlow(ctx context.Context) (*gmodel.Flow, error) { flow, err := r.Db.CreateFlow(ctx, database.CreateFlowParams{ - Name: database.StringToPgText("New Task"), - Status: database.StringToPgText("in_progress"), + Name: database.StringToNullString("New Task"), + Status: database.StringToNullString("in_progress"), }) if err != nil { @@ -52,11 +52,11 @@ func (r *mutationResolver) CreateTask(ctx context.Context, flowID uint, query st } task, err := r.Db.CreateTask(ctx, database.CreateTaskParams{ - Type: database.StringToPgText("input"), - Message: database.StringToPgText(query), - Status: database.StringToPgText("finished"), - Args: arg, - FlowID: pgtype.Int8{Int64: int64(flowID), Valid: true}, + Type: database.StringToNullString("input"), + Message: database.StringToNullString(query), + Status: database.StringToNullString("finished"), + Args: database.StringToNullString(string(arg)), + FlowID: sql.NullInt64{Int64: int64(flowID), Valid: true}, }) if err != nil { @@ -74,7 +74,7 @@ func (r *mutationResolver) CreateTask(ctx context.Context, flowID uint, query st Message: task.Message.String, Type: gmodel.TaskType(task.Type.String), Status: gmodel.TaskStatus(task.Status.String), - Args: string(task.Args), + Args: database.StringToNullString(string(arg)).String, CreatedAt: task.CreatedAt.Time, }, nil } @@ -101,7 +101,7 @@ func (r *mutationResolver) FinishFlow(ctx context.Context, flowID uint) (*gmodel // Update flow status r.Db.UpdateFlowStatus(ctx, database.UpdateFlowStatusParams{ - Status: database.StringToPgText("finished"), + Status: database.StringToNullString("finished"), ID: int64(flowID), }) @@ -167,7 +167,7 @@ func (r *queryResolver) Flow(ctx context.Context, id uint) (*gmodel.Flow, error) var gTasks []*gmodel.Task var gLogs []*gmodel.Log - tasks, err := r.Db.ReadTasksByFlowId(ctx, pgtype.Int8{Int64: int64(id), Valid: true}) + tasks, err := r.Db.ReadTasksByFlowId(ctx, sql.NullInt64{Int64: int64(id), Valid: true}) if err != nil { return nil, fmt.Errorf("failed to fetch tasks: %w", err) @@ -179,12 +179,12 @@ func (r *queryResolver) Flow(ctx context.Context, id uint) (*gmodel.Flow, error) Message: task.Message.String, Type: gmodel.TaskType(task.Type.String), Status: gmodel.TaskStatus(task.Status.String), - Args: string(task.Args), + Args: task.Args.String, Results: task.Results.String, CreatedAt: task.CreatedAt.Time, }) } - logs, err := r.Db.GetLogsByFlowId(ctx, pgtype.Int8{Int64: flow.ID, Valid: true}) + logs, err := r.Db.GetLogsByFlowId(ctx, sql.NullInt64{Int64: flow.ID, Valid: true}) if err != nil { return nil, fmt.Errorf("failed to fetch logs: %w", err) diff --git a/backend/main.go b/backend/main.go index 36d1166..02d7a4c 100644 --- a/backend/main.go +++ b/backend/main.go @@ -1,7 +1,6 @@ package main import ( - "context" "database/sql" "embed" "log" @@ -11,8 +10,7 @@ import ( "strconv" "syscall" - "github.com/jackc/pgx/v5/pgxpool" - _ "github.com/jackc/pgx/v5/stdlib" + _ "github.com/mattn/go-sqlite3" "github.com/pressly/goose/v3" "github.com/semanser/ai-coder/assets" "github.com/semanser/ai-coder/config" @@ -36,39 +34,17 @@ func main() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) - poolConfig, err := pgxpool.ParseConfig(config.Config.DatabaseURL) - if err != nil { - log.Fatalf("failed to create a pool: %w", err) - } + db, err := sql.Open("sqlite3", config.Config.DatabaseURL) - dbPool, err := pgxpool.NewWithConfig(context.Background(), poolConfig) - - if err != nil { - log.Fatalf("Unable to connect to database: %v\n", err) - } - - err = dbPool.Ping(context.Background()) - if err != nil { - log.Fatalf("Unable to ping database: %v\n", err) - } - - defer dbPool.Close() - - db := database.New(dbPool) - - // Setup migrations - dbMigrationsConnection, err := sql.Open("pgx", config.Config.DatabaseURL) - if err != nil { - log.Fatalf("Unable to connect to database: %v\n", err) - } + queries := database.New(db) goose.SetBaseFS(embedMigrations) - if err := goose.SetDialect("postgres"); err != nil { + if err := goose.SetDialect("sqlite3"); err != nil { log.Fatalf("Unable to set dialect: %v\n", err) } - if err := goose.Up(dbMigrationsConnection, "migrations"); err != nil { + if err := goose.Up(db, "migrations"); err != nil { log.Fatalf("Unable to run migrations: %v\n", err) } @@ -76,7 +52,7 @@ func main() { port := strconv.Itoa(config.Config.Port) - r := router.New(db) + r := router.New(queries) assets.Init(promptTemplates, scriptTemplates) services.Init() @@ -86,7 +62,7 @@ func main() { log.Fatalf("failed to initialize Docker client: %v", err) } - err = executor.InitBrowser(db) + err = executor.InitBrowser(queries) if err != nil { log.Fatalf("failed to initialize browser container: %v", err) } @@ -104,7 +80,7 @@ func main() { log.Println("Shutting down...") // Cleanup resources - if err := executor.Cleanup(db); err != nil { + if err := executor.Cleanup(queries); err != nil { log.Printf("Error during cleanup: %v", err) } diff --git a/backend/migrations/20240325154630_initial_migration.sql b/backend/migrations/20240325154630_initial_migration.sql index 86bd9f4..ac12879 100644 --- a/backend/migrations/20240325154630_initial_migration.sql +++ b/backend/migrations/20240325154630_initial_migration.sql @@ -1,32 +1,34 @@ -- +goose Up -- +goose StatementBegin CREATE TABLE containers ( - id BIGSERIAL PRIMARY KEY, - name text, - local_id text, - image text, - status text DEFAULT 'starting'::text + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT, + local_id TEXT, + image TEXT, + status TEXT DEFAULT 'starting' ); CREATE TABLE flows ( - id BIGSERIAL PRIMARY KEY, - created_at timestamp DEFAULT now(), - updated_at timestamp DEFAULT now(), - name text, - status text, - container_id bigint REFERENCES containers(id) + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + name TEXT, + status TEXT, + container_id INTEGER, + FOREIGN KEY (container_id) REFERENCES containers (id) ); CREATE TABLE tasks ( - id BIGSERIAL PRIMARY KEY, - created_at timestamp DEFAULT now(), - updated_at timestamp DEFAULT now(), - type text, - status text, - args jsonb DEFAULT '{}'::jsonb, - results text DEFAULT '{}'::jsonb, - flow_id bigint, - message text + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + type TEXT, + status TEXT, + args TEXT DEFAULT '{}', + results TEXT DEFAULT '{}', + message TEXT, + flow_id INTEGER, + FOREIGN KEY (flow_id) REFERENCES flows (id) ); -- +goose StatementEnd @@ -36,3 +38,4 @@ DROP TABLE tasks; DROP TABLE flows; DROP TABLE containers; -- +goose StatementEnd +``` diff --git a/backend/migrations/20240325193843_add_logs_table.sql b/backend/migrations/20240325193843_add_logs_table.sql index d06cf19..b1b2809 100644 --- a/backend/migrations/20240325193843_add_logs_table.sql +++ b/backend/migrations/20240325193843_add_logs_table.sql @@ -1,10 +1,10 @@ -- +goose Up -- +goose StatementBegin CREATE TABLE logs ( - id SERIAL PRIMARY KEY, + id INTEGER PRIMARY KEY AUTOINCREMENT, message TEXT NOT NULL, - created_at TIMESTAMP NOT NULL DEFAULT NOW(), - flow_id bigint REFERENCES flows(id) ON DELETE CASCADE, + created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, + flow_id INTEGER REFERENCES flows(id) ON DELETE CASCADE, type TEXT NOT NULL -- "input" or "output" ); -- +goose StatementEnd diff --git a/backend/migrations/20240327201116_migrate_to_timestamptz.sql b/backend/migrations/20240327201116_migrate_to_timestamptz.sql deleted file mode 100644 index 9358792..0000000 --- a/backend/migrations/20240327201116_migrate_to_timestamptz.sql +++ /dev/null @@ -1,17 +0,0 @@ --- +goose Up --- +goose StatementBegin -ALTER TABLE flows ALTER COLUMN created_at TYPE timestamptz USING created_at AT TIME ZONE 'UTC'; -ALTER TABLE flows ALTER COLUMN updated_at TYPE timestamptz USING updated_at AT TIME ZONE 'UTC'; -ALTER TABLE tasks ALTER COLUMN created_at TYPE timestamptz USING created_at AT TIME ZONE 'UTC'; -ALTER TABLE tasks ALTER COLUMN updated_at TYPE timestamptz USING updated_at AT TIME ZONE 'UTC'; -ALTER TABLE logs ALTER COLUMN created_at TYPE timestamptz USING created_at AT TIME ZONE 'UTC'; --- +goose StatementEnd - --- +goose Down --- +goose StatementBegin -ALTER TABLE flows ALTER COLUMN created_at TYPE timestamp USING created_at AT TIME ZONE 'UTC'; -ALTER TABLE flows ALTER COLUMN updated_at TYPE timestamp USING updated_at AT TIME ZONE 'UTC'; -ALTER TABLE tasks ALTER COLUMN created_at TYPE timestamp USING created_at AT TIME ZONE 'UTC'; -ALTER TABLE tasks ALTER COLUMN updated_at TYPE timestamptz USING updated_at AT TIME ZONE 'UTC'; -ALTER TABLE logs ALTER COLUMN created_at TYPE timestamp USING created_at AT TIME ZONE 'UTC'; --- +goose StatementEnd diff --git a/backend/models/containers.sql b/backend/models/containers.sql index c256bdc..286ddd4 100644 --- a/backend/models/containers.sql +++ b/backend/models/containers.sql @@ -6,18 +6,18 @@ INSERT INTO containers ( name, image, status ) VALUES ( - $1, $2, $3 + ?, ?, ? ) RETURNING *; -- name: UpdateContainerStatus :one UPDATE containers -SET status = $1 -WHERE id = $2 +SET status = ? +WHERE id = ? RETURNING *; -- name: UpdateContainerLocalId :one UPDATE containers -SET local_id = $1 -WHERE id = $2 +SET local_id = ? +WHERE id = ? RETURNING *; diff --git a/backend/models/flows.sql b/backend/models/flows.sql index e4bd6ba..25fe9b8 100644 --- a/backend/models/flows.sql +++ b/backend/models/flows.sql @@ -3,7 +3,7 @@ INSERT INTO flows ( name, status, container_id ) VALUES ( - $1, $2, $3 + ?, ?, ? ) RETURNING *; @@ -24,22 +24,22 @@ SELECT c.local_id AS container_local_id FROM flows f LEFT JOIN containers c ON f.container_id = c.id -WHERE f.id = $1; +WHERE f.id = ?; -- name: UpdateFlowStatus :one UPDATE flows -SET status = $1 -WHERE id = $2 +SET status = ? +WHERE id = ? RETURNING *; -- name: UpdateFlowName :one UPDATE flows -SET name = $1 -WHERE id = $2 +SET name = ? +WHERE id = ? RETURNING *; -- name: UpdateFlowContainer :one UPDATE flows -SET container_id = $1 -WHERE id = $2 +SET container_id = ? +WHERE id = ? RETURNING *; diff --git a/backend/models/logs.sql b/backend/models/logs.sql index 2669d89..cce874d 100644 --- a/backend/models/logs.sql +++ b/backend/models/logs.sql @@ -3,12 +3,12 @@ INSERT INTO logs ( message, flow_id, type ) VALUES ( - $1, $2, $3 + ?, ?, ? ) RETURNING *; -- name: GetLogsByFlowId :many SELECT * FROM logs -WHERE flow_id = $1 +WHERE flow_id = ? ORDER BY created_at ASC; diff --git a/backend/models/tasks.sql b/backend/models/tasks.sql index b2afe6e..479b5ed 100644 --- a/backend/models/tasks.sql +++ b/backend/models/tasks.sql @@ -8,29 +8,29 @@ INSERT INTO tasks ( message, tool_call_id ) VALUES ( - $1, $2, $3, $4, $5, $6, $7 + ?, ?, ?, ?, ?, ?, ? ) RETURNING *; -- name: ReadTasksByFlowId :many SELECT * FROM tasks -WHERE flow_id = $1 +WHERE flow_id = ? ORDER BY created_at ASC; -- name: UpdateTaskStatus :one UPDATE tasks -SET status = $1 -WHERE id = $2 +SET status = ? +WHERE id = ? RETURNING *; -- name: UpdateTaskResults :one UPDATE tasks -SET results = $1 -WHERE id = $2 +SET results = ? +WHERE id = ? RETURNING *; -- name: UpdateTaskToolCallId :one UPDATE tasks -SET tool_call_id = $1 -WHERE id = $2 +SET tool_call_id = ? +WHERE id = ? RETURNING *; diff --git a/backend/sqlc.yml b/backend/sqlc.yml index 201355b..2fb7ca6 100644 --- a/backend/sqlc.yml +++ b/backend/sqlc.yml @@ -1,7 +1,7 @@ version: "2" cloud: sql: - - engine: "postgresql" + - engine: "sqlite" queries: - "models/*.sql" schema: "./migrations" @@ -9,7 +9,6 @@ sql: go: package: "database" out: "database" - sql_package: "pgx/v5" database: uri: ${DATABASE_URL} From cbc9d3174982589ce1efba0040b2a0320575dcff Mon Sep 17 00:00:00 2001 From: Andriy Semenets Date: Mon, 1 Apr 2024 07:55:53 +0200 Subject: [PATCH 5/8] Add db to the .gitignore --- backend/.gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backend/.gitignore b/backend/.gitignore index d68dfae..e855491 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,3 @@ ai-coder tmp -database +database.db From 02094a04430cec44b824ba5aef420ae1a1c3ed37 Mon Sep 17 00:00:00 2001 From: Andriy Semenets Date: Mon, 1 Apr 2024 08:50:18 +0200 Subject: [PATCH 6/8] Fix configs --- .dockerignore | 1 - .gitignore | 2 ++ Dockerfile | 8 +++++++- backend/.gitignore | 1 + backend/main.go | 4 ++-- backend/router/router.go | 4 ++++ 6 files changed, 16 insertions(+), 4 deletions(-) diff --git a/.dockerignore b/.dockerignore index d34624d..7f58d15 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,7 +1,6 @@ frontend/dist frontend/node_modules frontend/dist -frontend/.env.local backend/.env diff --git a/.gitignore b/.gitignore index ece41b9..0350214 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ .DS_Store .env +.env.* .envrc +fe diff --git a/Dockerfile b/Dockerfile index 567bf56..7d70bb8 100644 --- a/Dockerfile +++ b/Dockerfile @@ -17,6 +17,8 @@ RUN yarn build # STEP 2: Build the backend FROM golang:1.22-alpine as be-build +ENV CGO_ENABLED=1 +RUN apk add --no-cache gcc musl-dev WORKDIR /backend @@ -24,7 +26,7 @@ COPY backend/ . RUN go mod download -RUN go build -o /app +RUN go build -ldflags='-extldflags "-static"' -o /app # STEP 3: Build the final image FROM alpine:3.14 @@ -32,4 +34,8 @@ FROM alpine:3.14 COPY --from=be-build /app /app COPY --from=fe-build /frontend/dist /fe +# Install sqlite3 + +RUN apk add --no-cache sqlite + CMD /app diff --git a/backend/.gitignore b/backend/.gitignore index e855491..f04ef1f 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -1,3 +1,4 @@ ai-coder tmp database.db +.env.* diff --git a/backend/main.go b/backend/main.go index 02d7a4c..00065bd 100644 --- a/backend/main.go +++ b/backend/main.go @@ -10,7 +10,7 @@ import ( "strconv" "syscall" - _ "github.com/mattn/go-sqlite3" + _ "github.com/mattn/go-sqlite3" "github.com/pressly/goose/v3" "github.com/semanser/ai-coder/assets" "github.com/semanser/ai-coder/config" @@ -34,7 +34,7 @@ func main() { sigChan := make(chan os.Signal, 1) signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM) - db, err := sql.Open("sqlite3", config.Config.DatabaseURL) + db, err := sql.Open("sqlite3", config.Config.DatabaseURL) queries := database.New(db) diff --git a/backend/router/router.go b/backend/router/router.go index 82467d1..2b37821 100644 --- a/backend/router/router.go +++ b/backend/router/router.go @@ -49,6 +49,10 @@ func New(db *database.Queries) *gin.Engine { // Static file server r.Static("/browser", "./tmp/browser") + r.NoRoute(func(c *gin.Context) { + c.Redirect(301, "/") + }) + return r } From 178955d394ae6060da9d78a60ac22fed897a7ed8 Mon Sep 17 00:00:00 2001 From: Andriy Semenets Date: Mon, 1 Apr 2024 10:04:47 +0200 Subject: [PATCH 7/8] Add GH automation --- .github/workflows/release.yml | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..129f78b --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,35 @@ +name: Release Docker Image + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+' + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GitHub Container Registry + run: echo "${{ secrets.GITHUB_TOKEN }}" | docker login ghcr.io -u ${{ github.actor }} --password-stdin + + - name: Build and push Docker image + uses: docker/build-push-action@v3 + with: + context: . + file: ./Dockerfile + platforms: linux/amd64,linux/arm64 + push: true + tags: ghcr.io/${{ github.repository }}:${{ github.ref_name }} + + - name: Create GitHub Release + uses: softprops/action-gh-release@v2 From c05037d95bae7f4b799fa4d133da48278af29638 Mon Sep 17 00:00:00 2001 From: Andriy Semenets Date: Mon, 1 Apr 2024 10:14:45 +0200 Subject: [PATCH 8/8] Add a missing env variable --- .dockerignore | 1 + Dockerfile | 1 + 2 files changed, 2 insertions(+) diff --git a/.dockerignore b/.dockerignore index 7f58d15..d34624d 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,6 +1,7 @@ frontend/dist frontend/node_modules frontend/dist +frontend/.env.local backend/.env diff --git a/Dockerfile b/Dockerfile index 7d70bb8..c81a291 100644 --- a/Dockerfile +++ b/Dockerfile @@ -2,6 +2,7 @@ FROM node:21-slim as fe-build ENV NODE_ENV=production +ENV VITE_API_URL=localhost:3000 WORKDIR /frontend