commit 4ecc12f0355453fc7ed1efb79500f1357671b207 Author: Leon Mika Date: Mon Jan 27 07:39:19 2025 +1100 Initial commot Have got DB creation and migration working diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..6793a9c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,9 @@ +version: "3" + +services: + db: + image: "postgres:17.2" + ports: + - "5432:5432" + environment: + POSTGRES_PASSWORD: postgres \ No newline at end of file diff --git a/gen/sqlc/dbq/db.go b/gen/sqlc/dbq/db.go new file mode 100644 index 0000000..8fa890d --- /dev/null +++ b/gen/sqlc/dbq/db.go @@ -0,0 +1,32 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 + +package dbq + +import ( + "context" + + "github.com/jackc/pgx/v5" + "github.com/jackc/pgx/v5/pgconn" +) + +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 +} + +func New(db DBTX) *Queries { + return &Queries{db: db} +} + +type Queries struct { + db DBTX +} + +func (q *Queries) WithTx(tx pgx.Tx) *Queries { + return &Queries{ + db: tx, + } +} diff --git a/gen/sqlc/dbq/models.go b/gen/sqlc/dbq/models.go new file mode 100644 index 0000000..a4344b5 --- /dev/null +++ b/gen/sqlc/dbq/models.go @@ -0,0 +1,13 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 + +package dbq + +type Site struct { + ID int64 + Name string + Url string + Theme string + Props []byte +} diff --git a/gen/sqlc/dbq/sites.sql.go b/gen/sqlc/dbq/sites.sql.go new file mode 100644 index 0000000..258befb --- /dev/null +++ b/gen/sqlc/dbq/sites.sql.go @@ -0,0 +1,56 @@ +// Code generated by sqlc. DO NOT EDIT. +// versions: +// sqlc v1.28.0 +// source: sites.sql + +package dbq + +import ( + "context" +) + +const listSites = `-- name: ListSites :one +SELECT id, name, url, theme, props FROM site +` + +func (q *Queries) ListSites(ctx context.Context) (Site, error) { + row := q.db.QueryRow(ctx, listSites) + var i Site + err := row.Scan( + &i.ID, + &i.Name, + &i.Url, + &i.Theme, + &i.Props, + ) + return i, err +} + +const newSite = `-- name: NewSite :one +INSERT INTO site ( + name, + url, + theme, + props +) VALUES ($1, $2, $3, $4) +RETURNING id +` + +type NewSiteParams struct { + Name string + Url string + Theme string + Props []byte +} + +func (q *Queries) NewSite(ctx context.Context, arg NewSiteParams) (int64, error) { + row := q.db.QueryRow(ctx, newSite, + arg.Name, + arg.Url, + arg.Theme, + arg.Props, + ) + var id int64 + err := row.Scan(&id) + return id, err +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..dee3307 --- /dev/null +++ b/go.mod @@ -0,0 +1,21 @@ +module lmika.dev/lmika/hugo-crm + +go 1.23.3 + +require ( + github.com/golang-migrate/migrate/v4 v4.18.1 + github.com/jackc/pgx/v5 v5.7.2 +) + +require ( + github.com/hashicorp/errwrap v1.1.0 // indirect + github.com/hashicorp/go-multierror v1.1.1 // indirect + github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect + github.com/jackc/puddle/v2 v2.2.2 // indirect + go.uber.org/atomic v1.7.0 // indirect + golang.org/x/crypto v0.31.0 // indirect + golang.org/x/sync v0.10.0 // indirect + golang.org/x/text v0.21.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..29b6f37 --- /dev/null +++ b/go.sum @@ -0,0 +1,33 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-migrate/migrate/v4 v4.18.1 h1:JML/k+t4tpHCpQTCAD62Nu43NUFzHY4CV3uAuvHGC+Y= +github.com/golang-migrate/migrate/v4 v4.18.1/go.mod h1:HAX6m3sQgcdO81tdjn5exv20+3Kb13cmGli1hrD6hks= +github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= +github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= +github.com/hashicorp/go-multierror v1.1.1 h1:H5DkEtf6CXdFp0N0Em5UCwQpXMWke8IA0+lD48awMYo= +github.com/hashicorp/go-multierror v1.1.1/go.mod h1:iw975J/qwKPdAO1clOe2L8331t/9/fmwbPZ6JB6eMoM= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa h1:s+4MhCQ6YrzisK6hFJUX53drDT4UsSW3DEhKn0ifuHw= +github.com/jackc/pgerrcode v0.0.0-20220416144525-469b46aa5efa/go.mod h1:a/s9Lp5W7n/DD0VrVoyJ00FbP2ytTPDVOivvn2bMlds= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 h1:iCEnooe7UlwOQYpKFhBabPMi4aNAfoODPEFNiAnClxo= +github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.7.2 h1:mLoDLV6sonKlvjIEsV56SkWNCnuNv531l94GaIzO+XI= +github.com/jackc/pgx/v5 v5.7.2/go.mod h1:ncY89UGWxg82EykZUwSpUKEfccBGGYq1xjrOpsbsfGQ= +github.com/jackc/puddle/v2 v2.2.2 h1:PR8nw+E/1w0GLuRFSmiioY6UooMp6KJv0/61nB7icHo= +github.com/jackc/puddle/v2 v2.2.2/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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= +go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= +go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sync v0.10.0 h1:3NQrjDixjgGwUOCaF8w2+VYHv0Ve/vGYSbdkTa98gmQ= +golang.org/x/sync v0.10.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk= +golang.org/x/text v0.21.0 h1:zyQAAkrwaneQ066sspRyJaG9VNi/YJ1NfzcGB3hZ/qo= +golang.org/x/text v0.21.0/go.mod h1:4IBbMaMmOPCJ8SecivzSH54+73PCFmPWxNTLm+vZkEQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/main.go b/main.go new file mode 100644 index 0000000..ff390d1 --- /dev/null +++ b/main.go @@ -0,0 +1,30 @@ +package main + +import ( + "context" + "lmika.dev/lmika/hugo-crm/models" + "lmika.dev/lmika/hugo-crm/providers/db" + "log" +) + +func main() { + dbp, err := db.New("postgres://postgres:postgres@localhost:5432/postgres?sslmode=disable") + if err != nil { + log.Fatal(err) + } + defer dbp.Close() + + log.Println("Connected to database") + if err := dbp.Migrate(context.Background()); err != nil { + log.Fatal(err) + } + + log.Println("Database migrated") + + if err := dbp.InsertSite(context.Background(), &models.Site{ + Name: "Test site", + URL: "https://www.testsite.com", + }); err != nil { + log.Fatal(err) + } +} diff --git a/models/sites.go b/models/sites.go new file mode 100644 index 0000000..d2d45ed --- /dev/null +++ b/models/sites.go @@ -0,0 +1,7 @@ +package models + +type Site struct { + ID int64 + Name string + URL string +} diff --git a/providers/db/provider.go b/providers/db/provider.go new file mode 100644 index 0000000..4ca4bda --- /dev/null +++ b/providers/db/provider.go @@ -0,0 +1,59 @@ +package db + +import ( + "context" + "errors" + "github.com/golang-migrate/migrate/v4" + _ "github.com/golang-migrate/migrate/v4/database/pgx/v5" + "github.com/golang-migrate/migrate/v4/source/iofs" + "github.com/jackc/pgx/v5/pgxpool" + "lmika.dev/lmika/hugo-crm/gen/sqlc/dbq" + "lmika.dev/lmika/hugo-crm/sql" + "strings" +) + +type DB struct { + url string + pool *pgxpool.Pool + q *dbq.Queries +} + +func New(url string) (*DB, error) { + pool, err := pgxpool.New(context.Background(), url) + if err != nil { + return nil, err + } + + return &DB{ + url: url, + pool: pool, + q: dbq.New(pool), + }, nil +} + +func (db *DB) Close() { + db.pool.Close() +} + +func (db *DB) Ping(ctx context.Context) error { + return db.pool.Ping(ctx) +} + +func (db *DB) Migrate(ctx context.Context) error { + ms, err := iofs.New(sql.FS, "schema") + if err != nil { + return err + } + + dbURL := "pgx5://" + strings.TrimPrefix(db.url, "postgres://") + + m, err := migrate.NewWithSourceInstance("iofs", ms, dbURL) + if err != nil { + return err + } + err = m.Up() + if err != nil && !errors.Is(err, migrate.ErrNoChange) { + return err + } + return nil +} diff --git a/providers/db/sites.go b/providers/db/sites.go new file mode 100644 index 0000000..98131e8 --- /dev/null +++ b/providers/db/sites.go @@ -0,0 +1,21 @@ +package db + +import ( + "context" + "lmika.dev/lmika/hugo-crm/gen/sqlc/dbq" + "lmika.dev/lmika/hugo-crm/models" +) + +func (db *DB) InsertSite(ctx context.Context, site *models.Site) error { + id, err := db.q.NewSite(ctx, dbq.NewSiteParams{ + Name: site.Name, + Url: site.URL, + Theme: "default", + Props: []byte("{}"), + }) + if err != nil { + return err + } + site.ID = id + return nil +} diff --git a/sql/fs.go b/sql/fs.go new file mode 100644 index 0000000..6755cf8 --- /dev/null +++ b/sql/fs.go @@ -0,0 +1,6 @@ +package sql + +import "embed" + +//go:embed schema/*.sql +var FS embed.FS diff --git a/sql/queries/sites.sql b/sql/queries/sites.sql new file mode 100644 index 0000000..bc5626a --- /dev/null +++ b/sql/queries/sites.sql @@ -0,0 +1,11 @@ +-- name: ListSites :one +SELECT * FROM site; + +-- name: NewSite :one +INSERT INTO site ( + name, + url, + theme, + props +) VALUES ($1, $2, $3, $4) +RETURNING id; \ No newline at end of file diff --git a/sql/schema/1_init.up.sql b/sql/schema/1_init.up.sql new file mode 100644 index 0000000..537eafd --- /dev/null +++ b/sql/schema/1_init.up.sql @@ -0,0 +1,7 @@ +CREATE TABLE site ( + id BIGSERIAL NOT NULL PRIMARY KEY, + name TEXT NOT NULL, + url TEXT NOT NULL, + theme TEXT NOT NULL, + props JSON NOT NULL +); diff --git a/sqlc.yaml b/sqlc.yaml new file mode 100644 index 0000000..5a98a2e --- /dev/null +++ b/sqlc.yaml @@ -0,0 +1,10 @@ +version: "2" +sql: + - engine: "postgresql" + queries: "sql/queries" + schema: "sql/schema" + gen: + go: + package: "dbq" + out: "gen/sqlc/dbq" + sql_package: "pgx/v5" \ No newline at end of file