feat: add sqlite-backed API docs and tests
All checks were successful
ci / test-and-build (push) Successful in 11m21s
All checks were successful
ci / test-and-build (push) Successful in 11m21s
This commit is contained in:
18
.github/workflows/ci.yml
vendored
Normal file
18
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-and-build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24.2'
|
||||||
|
- name: Test
|
||||||
|
run: go test ./...
|
||||||
|
- name: Build Docker image
|
||||||
|
run: docker build -t git.danhenry.dev/thelab/work-queue-api:latest .
|
||||||
15
Dockerfile
Normal file
15
Dockerfile
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
FROM golang:1.24 AS build
|
||||||
|
WORKDIR /app
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
RUN go mod download
|
||||||
|
COPY . .
|
||||||
|
RUN CGO_ENABLED=1 GOOS=linux GOARCH=amd64 go build -o /out/work-queue-api ./cmd/work-queue-api
|
||||||
|
|
||||||
|
FROM debian:bookworm-slim
|
||||||
|
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/*
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=build /out/work-queue-api /usr/local/bin/work-queue-api
|
||||||
|
EXPOSE 8080
|
||||||
|
ENV PORT=8080
|
||||||
|
ENV DATABASE_URL=/data/work_queue.db
|
||||||
|
CMD ["/usr/local/bin/work-queue-api"]
|
||||||
33
README.md
Normal file
33
README.md
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
# work-queue-api
|
||||||
|
|
||||||
|
Lightweight internal work queue API for TheLab agents.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
- Go HTTP API with chi router
|
||||||
|
- SQLite storage, auto-migrated on startup
|
||||||
|
- Projects and work items endpoints from `SPEC.md`
|
||||||
|
- Dispatch history tracking
|
||||||
|
- Enforces one `in_progress` item per agent
|
||||||
|
- No auth, binds to port `8080` by default
|
||||||
|
|
||||||
|
## Run locally
|
||||||
|
```bash
|
||||||
|
export DATABASE_URL=./work_queue.db
|
||||||
|
export PORT=8080
|
||||||
|
~/.local/go/bin/go run ./cmd/work-queue-api
|
||||||
|
```
|
||||||
|
|
||||||
|
## Build
|
||||||
|
```bash
|
||||||
|
~/.local/go/bin/go build ./cmd/work-queue-api
|
||||||
|
```
|
||||||
|
|
||||||
|
## Endpoints
|
||||||
|
- `GET /health`
|
||||||
|
- `POST/GET/PATCH /projects`
|
||||||
|
- `POST/GET/PATCH/DELETE /work`
|
||||||
|
|
||||||
|
## Notes
|
||||||
|
- `POST /work` accepts `assigned_agent`, but still creates the item in `queued`
|
||||||
|
- `PATCH /work/:id` enforces status transitions from the spec
|
||||||
|
- `DELETE /work/:id` cancels queued or dispatched work items
|
||||||
18
ci.yml
Normal file
18
ci.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [main]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.24.2'
|
||||||
|
- name: Test
|
||||||
|
run: go test ./...
|
||||||
|
- name: Build image
|
||||||
|
run: docker build -t git.danhenry.dev/thelab/work-queue-api:latest .
|
||||||
18
docker-compose.yml
Normal file
18
docker-compose.yml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
version: '3.8'
|
||||||
|
services:
|
||||||
|
work-queue-api:
|
||||||
|
build: .
|
||||||
|
image: git.danhenry.dev/thelab/work-queue-api:latest
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
environment:
|
||||||
|
DATABASE_URL: /data/work_queue.db
|
||||||
|
PORT: 8080
|
||||||
|
volumes:
|
||||||
|
- ./data:/data
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "wget", "-qO-", "http://localhost:8080/health"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
14
go.mod
14
go.mod
@@ -5,5 +5,17 @@ go 1.24.0
|
|||||||
require (
|
require (
|
||||||
github.com/go-chi/chi/v5 v5.2.3
|
github.com/go-chi/chi/v5 v5.2.3
|
||||||
github.com/google/uuid v1.6.0
|
github.com/google/uuid v1.6.0
|
||||||
github.com/mattn/go-sqlite3 v1.14.32
|
modernc.org/sqlite v1.39.1
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/dustin/go-humanize v1.0.1 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 // indirect
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b // indirect
|
||||||
|
golang.org/x/sys v0.36.0 // indirect
|
||||||
|
modernc.org/libc v1.66.10 // indirect
|
||||||
|
modernc.org/mathutil v1.7.1 // indirect
|
||||||
|
modernc.org/memory v1.11.0 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
49
go.sum
49
go.sum
@@ -1,6 +1,51 @@
|
|||||||
|
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||||
|
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||||
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
github.com/go-chi/chi/v5 v5.2.3 h1:WQIt9uxdsAbgIYgid+BpYc+liqQZGMHRaUwp0JUcvdE=
|
||||||
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
github.com/go-chi/chi/v5 v5.2.3/go.mod h1:L2yAIGWB3H+phAw1NxKwWM+7eUH/lU8pOMm5hHcoops=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e h1:ijClszYn+mADRFY17kjQEVQ1XRhq2/JR1M3sGqeJoxs=
|
||||||
|
github.com/google/pprof v0.0.0-20250317173921-a4b03ec1a45e/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA=
|
||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9 h1:bY0MQC28UADQmHmaF5dgpLmImcShSi2kHU9XLdhx/f4=
|
||||||
|
github.com/ncruces/go-strftime v0.1.9/go.mod h1:Fwc5htZGVVkseilnfgOVb9mKy6w1naJmn9CehxcKcls=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||||
|
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
|
||||||
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b h1:M2rDM6z3Fhozi9O7NWsxAkg/yqS/lQJ6PmkyIV3YP+o=
|
||||||
|
golang.org/x/exp v0.0.0-20250620022241-b7579e27df2b/go.mod h1:3//PLf8L/X+8b4vuAfHzxeRUl04Adcb341+IGKfnqS8=
|
||||||
|
golang.org/x/mod v0.27.0 h1:kb+q2PyFnEADO2IEF935ehFUXlWiNjJWtRNgBLSfbxQ=
|
||||||
|
golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc=
|
||||||
|
golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw=
|
||||||
|
golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
|
||||||
|
golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
|
||||||
|
golang.org/x/tools v0.36.0 h1:kWS0uv/zsvHEle1LbV5LE8QujrxB3wfQyxHfhOk0Qkg=
|
||||||
|
golang.org/x/tools v0.36.0/go.mod h1:WBDiHKJK8YgLHlcQPYQzNCkUxUypCaa5ZegCVutKm+s=
|
||||||
|
modernc.org/cc/v4 v4.26.5 h1:xM3bX7Mve6G8K8b+T11ReenJOT+BmVqQj0FY5T4+5Y4=
|
||||||
|
modernc.org/cc/v4 v4.26.5/go.mod h1:uVtb5OGqUKpoLWhqwNQo/8LwvoiEBLvZXIQ/SmO6mL0=
|
||||||
|
modernc.org/ccgo/v4 v4.28.1 h1:wPKYn5EC/mYTqBO373jKjvX2n+3+aK7+sICCv4Fjy1A=
|
||||||
|
modernc.org/ccgo/v4 v4.28.1/go.mod h1:uD+4RnfrVgE6ec9NGguUNdhqzNIeeomeXf6CL0GTE5Q=
|
||||||
|
modernc.org/fileutil v1.3.40 h1:ZGMswMNc9JOCrcrakF1HrvmergNLAmxOPjizirpfqBA=
|
||||||
|
modernc.org/fileutil v1.3.40/go.mod h1:HxmghZSZVAz/LXcMNwZPA/DRrQZEVP9VX0V4LQGQFOc=
|
||||||
|
modernc.org/gc/v2 v2.6.5 h1:nyqdV8q46KvTpZlsw66kWqwXRHdjIlJOhG6kxiV/9xI=
|
||||||
|
modernc.org/gc/v2 v2.6.5/go.mod h1:YgIahr1ypgfe7chRuJi2gD7DBQiKSLMPgBQe9oIiito=
|
||||||
|
modernc.org/goabi0 v0.2.0 h1:HvEowk7LxcPd0eq6mVOAEMai46V+i7Jrj13t4AzuNks=
|
||||||
|
modernc.org/goabi0 v0.2.0/go.mod h1:CEFRnnJhKvWT1c1JTI3Avm+tgOWbkOu5oPA8eH8LnMI=
|
||||||
|
modernc.org/libc v1.66.10 h1:yZkb3YeLx4oynyR+iUsXsybsX4Ubx7MQlSYEw4yj59A=
|
||||||
|
modernc.org/libc v1.66.10/go.mod h1:8vGSEwvoUoltr4dlywvHqjtAqHBaw0j1jI7iFBTAr2I=
|
||||||
|
modernc.org/mathutil v1.7.1 h1:GCZVGXdaN8gTqB1Mf/usp1Y/hSqgI2vAGGP4jZMCxOU=
|
||||||
|
modernc.org/mathutil v1.7.1/go.mod h1:4p5IwJITfppl0G4sUEDtCr4DthTaT47/N3aT6MhfgJg=
|
||||||
|
modernc.org/memory v1.11.0 h1:o4QC8aMQzmcwCK3t3Ux/ZHmwFPzE6hf2Y5LbkRs+hbI=
|
||||||
|
modernc.org/memory v1.11.0/go.mod h1:/JP4VbVC+K5sU2wZi9bHoq2MAkCnrt2r98UGeSK7Mjw=
|
||||||
|
modernc.org/opt v0.1.4 h1:2kNGMRiUjrp4LcaPuLY2PzUfqM/w9N23quVwhKt5Qm8=
|
||||||
|
modernc.org/opt v0.1.4/go.mod h1:03fq9lsNfvkYSfxrfUhZCWPk1lm4cq4N+Bh//bEtgns=
|
||||||
|
modernc.org/sortutil v1.2.1 h1:+xyoGf15mM3NMlPDnFqrteY07klSFxLElE2PVuWIJ7w=
|
||||||
|
modernc.org/sortutil v1.2.1/go.mod h1:7ZI3a3REbai7gzCLcotuw9AC4VZVpYMjDzETGsSMqJE=
|
||||||
|
modernc.org/sqlite v1.39.1 h1:H+/wGFzuSCIEVCvXYVHX5RQglwhMOvtHSv+VtidL2r4=
|
||||||
|
modernc.org/sqlite v1.39.1/go.mod h1:9fjQZ0mB1LLP0GYrp39oOJXx/I2sxEnZtzCmEQIKvGE=
|
||||||
|
modernc.org/strutil v1.2.1 h1:UneZBkQA+DX2Rp35KcM69cSsNES9ly8mQWD71HKlOA0=
|
||||||
|
modernc.org/strutil v1.2.1/go.mod h1:EHkiggD70koQxjVdSBM3JKM7k6L0FbGE5eymy9i3B9A=
|
||||||
|
modernc.org/token v1.1.0 h1:Xl7Ap9dKaEs5kLoOQeQmPWevfnk/DM5qcLcYlA8ys6Y=
|
||||||
|
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||||
|
|||||||
140
internal/api/server_test.go
Normal file
140
internal/api/server_test.go
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
package api
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"database/sql"
|
||||||
|
"encoding/json"
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"work-queue-api/internal/db"
|
||||||
|
)
|
||||||
|
|
||||||
|
func newTestServer(t *testing.T) (*Server, *sql.DB) {
|
||||||
|
t.Helper()
|
||||||
|
sqliteDB, err := db.Open(t.TempDir() + "/test.db")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("open db: %v", err)
|
||||||
|
}
|
||||||
|
server, err := NewServer(sqliteDB)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("new server: %v", err)
|
||||||
|
}
|
||||||
|
return server, sqliteDB
|
||||||
|
}
|
||||||
|
|
||||||
|
func doJSON(t *testing.T, h http.Handler, method, path string, body any) *httptest.ResponseRecorder {
|
||||||
|
t.Helper()
|
||||||
|
var payload []byte
|
||||||
|
if body != nil {
|
||||||
|
var err error
|
||||||
|
payload, err = json.Marshal(body)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("marshal body: %v", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
req := httptest.NewRequest(method, path, bytes.NewReader(payload))
|
||||||
|
res := httptest.NewRecorder()
|
||||||
|
h.ServeHTTP(res, req)
|
||||||
|
return res
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestProjectAndWorkLifecycle(t *testing.T) {
|
||||||
|
server, sqliteDB := newTestServer(t)
|
||||||
|
defer sqliteDB.Close()
|
||||||
|
router := server.Router()
|
||||||
|
|
||||||
|
projectRes := doJSON(t, router, http.MethodPost, "/projects", map[string]any{
|
||||||
|
"name": "Shopping List API",
|
||||||
|
"external_ref": "todoist:123",
|
||||||
|
})
|
||||||
|
if projectRes.Code != http.StatusCreated {
|
||||||
|
t.Fatalf("create project status = %d body=%s", projectRes.Code, projectRes.Body.String())
|
||||||
|
}
|
||||||
|
var project map[string]any
|
||||||
|
if err := json.Unmarshal(projectRes.Body.Bytes(), &project); err != nil {
|
||||||
|
t.Fatalf("decode project: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
workRes := doJSON(t, router, http.MethodPost, "/work", map[string]any{
|
||||||
|
"project_id": project["id"],
|
||||||
|
"type": "code_review",
|
||||||
|
"description": "Review PR #3",
|
||||||
|
"payload": map[string]any{"pr": 3},
|
||||||
|
"priority": 2,
|
||||||
|
"assigned_agent": "steve-w",
|
||||||
|
"created_by": "marcus-a",
|
||||||
|
})
|
||||||
|
if workRes.Code != http.StatusCreated {
|
||||||
|
t.Fatalf("create work status = %d body=%s", workRes.Code, workRes.Body.String())
|
||||||
|
}
|
||||||
|
var work map[string]any
|
||||||
|
if err := json.Unmarshal(workRes.Body.Bytes(), &work); err != nil {
|
||||||
|
t.Fatalf("decode work: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, step := range []map[string]any{
|
||||||
|
{"status": "dispatched", "assigned_agent": "steve-w"},
|
||||||
|
{"status": "in_progress"},
|
||||||
|
{"status": "completed", "outcome": "success", "notes": "done"},
|
||||||
|
} {
|
||||||
|
res := doJSON(t, router, http.MethodPatch, "/work/"+work["id"].(string), step)
|
||||||
|
if res.Code != http.StatusOK {
|
||||||
|
t.Fatalf("patch work step=%v status = %d body=%s", step, res.Code, res.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
getRes := doJSON(t, router, http.MethodGet, "/work/"+work["id"].(string), nil)
|
||||||
|
if getRes.Code != http.StatusOK {
|
||||||
|
t.Fatalf("get work status = %d body=%s", getRes.Code, getRes.Body.String())
|
||||||
|
}
|
||||||
|
var fetched map[string]any
|
||||||
|
if err := json.Unmarshal(getRes.Body.Bytes(), &fetched); err != nil {
|
||||||
|
t.Fatalf("decode fetched work: %v", err)
|
||||||
|
}
|
||||||
|
if fetched["status"] != "completed" {
|
||||||
|
t.Fatalf("expected completed status, got %v", fetched["status"])
|
||||||
|
}
|
||||||
|
logs, ok := fetched["dispatch_log"].([]any)
|
||||||
|
if !ok || len(logs) != 1 {
|
||||||
|
t.Fatalf("expected 1 dispatch log, got %v", fetched["dispatch_log"])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestOneInProgressPerAgent(t *testing.T) {
|
||||||
|
server, sqliteDB := newTestServer(t)
|
||||||
|
defer sqliteDB.Close()
|
||||||
|
router := server.Router()
|
||||||
|
|
||||||
|
create := func(desc string) string {
|
||||||
|
res := doJSON(t, router, http.MethodPost, "/work", map[string]any{
|
||||||
|
"type": "bug_fix",
|
||||||
|
"description": desc,
|
||||||
|
"assigned_agent": "steve-w",
|
||||||
|
})
|
||||||
|
if res.Code != http.StatusCreated {
|
||||||
|
t.Fatalf("create work status = %d body=%s", res.Code, res.Body.String())
|
||||||
|
}
|
||||||
|
var body map[string]any
|
||||||
|
_ = json.Unmarshal(res.Body.Bytes(), &body)
|
||||||
|
return body["id"].(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
first := create("first")
|
||||||
|
second := create("second")
|
||||||
|
for _, id := range []string{first, second} {
|
||||||
|
res := doJSON(t, router, http.MethodPatch, "/work/"+id, map[string]any{"status": "dispatched", "assigned_agent": "steve-w"})
|
||||||
|
if res.Code != http.StatusOK {
|
||||||
|
t.Fatalf("dispatch status = %d body=%s", res.Code, res.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
res := doJSON(t, router, http.MethodPatch, "/work/"+first, map[string]any{"status": "in_progress"})
|
||||||
|
if res.Code != http.StatusOK {
|
||||||
|
t.Fatalf("first in progress status = %d body=%s", res.Code, res.Body.String())
|
||||||
|
}
|
||||||
|
res = doJSON(t, router, http.MethodPatch, "/work/"+second, map[string]any{"status": "in_progress"})
|
||||||
|
if res.Code != http.StatusConflict {
|
||||||
|
t.Fatalf("expected 409, got %d body=%s", res.Code, res.Body.String())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -7,7 +7,7 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "modernc.org/sqlite"
|
||||||
)
|
)
|
||||||
|
|
||||||
//go:embed migrations/001_initial.sql
|
//go:embed migrations/001_initial.sql
|
||||||
@@ -18,7 +18,7 @@ func Open(databaseURL string) (*sql.DB, error) {
|
|||||||
return nil, fmt.Errorf("mkdir db dir: %w", err)
|
return nil, fmt.Errorf("mkdir db dir: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
db, err := sql.Open("sqlite3", databaseURL+"?_foreign_keys=on&_busy_timeout=5000")
|
db, err := sql.Open("sqlite", databaseURL+"?_pragma=foreign_keys(1)&_pragma=busy_timeout(5000)")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user