Skip to content

Commit

Permalink
Merge pull request #50 from fabiante/feat/postgres
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiante authored Sep 19, 2023
2 parents 3b5ac41 + e4656db commit f483377
Show file tree
Hide file tree
Showing 17 changed files with 183 additions and 85 deletions.
7 changes: 7 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ on:
jobs:
test:
runs-on: ubuntu-latest
env:
PERSURL_DB_DSN: postgresql://persurl:persurl@localhost:5432/persurl?sslmode=disable
steps:
- uses: actions/checkout@v3
- name: Set up Go
Expand All @@ -20,6 +22,8 @@ jobs:
- name: Lint API Spec
run: npx @redocly/cli lint api/openapi.yml

- name: Run database
run: docker compose up --quiet-pull -d && sleep 5
- name: Install Test Runner
run: go install github.com/mfridman/tparse@latest
- name: Test
Expand All @@ -32,13 +36,16 @@ jobs:
runs-on: ubuntu-latest
env:
TEST_LOAD: 1
PERSURL_DB_DSN: postgresql://persurl:persurl@localhost:5432/persurl?sslmode=disable
steps:
- uses: actions/checkout@v3
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: '1.21'

- name: Run database
run: docker compose up --quiet-pull -d && sleep 5
- name: Install Test Runner
run: go install github.com/mfridman/tparse@latest
- name: Test
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
.idea
/.env
*.sqlite
22 changes: 0 additions & 22 deletions cli/cmds/env.go

This file was deleted.

9 changes: 3 additions & 6 deletions cli/cmds/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package cmds
import (
"log"

"github.com/fabiante/persurl/config"
"github.com/fabiante/persurl/db"
"github.com/fabiante/persurl/db/migrations"
"github.com/spf13/cobra"
Expand All @@ -15,16 +16,12 @@ func init() {
}

cmd.Run = func(cmd *cobra.Command, args []string) {
dataDir := envDataDir()

dbFile := envDbFile(dataDir)

database, _, err := db.SetupDB(dbFile)
database, _, err := db.SetupPostgresDB(config.DbDSN(), config.DbMaxConnections())
if err != nil {
log.Fatalf("setting up database failed: %s", err)
}

err = migrations.Run(database)
err = migrations.RunPostgres(database)
if err != nil {
log.Fatalf("migrating database failed: %s", err)
}
Expand Down
7 changes: 2 additions & 5 deletions cli/cmds/run.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"log"

"github.com/fabiante/persurl/api"
"github.com/fabiante/persurl/config"
"github.com/fabiante/persurl/db"
"github.com/gin-gonic/gin"
"github.com/spf13/cobra"
Expand All @@ -16,11 +17,7 @@ func init() {
}

cmd.Run = func(cmd *cobra.Command, args []string) {
dataDir := envDataDir()

dbFile := envDbFile(dataDir)

_, database, err := db.SetupDB(dbFile)
_, database, err := db.SetupPostgresDB(config.DbDSN(), config.DbMaxConnections())
if err != nil {
log.Fatalf("setting up database failed: %s", err)
}
Expand Down
51 changes: 51 additions & 0 deletions config/env.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package config

import (
"errors"
"fmt"
"log"
"os"
"strconv"

"github.com/joho/godotenv"
)

func LoadEnv() {
path := ".env"

// check if .env file exists - if not, exit early.
_, err := os.Stat(path)
if errors.Is(err, os.ErrNotExist) {
return
}

err = godotenv.Load(path)
if err != nil {
panic(fmt.Errorf("loading env failed: %w", err))
}
}

func DbDSN() string {
dsn := os.Getenv("PERSURL_DB_DSN")
if dsn == "" {
dsn = os.Getenv("DATABASE_URL")
}
if dsn == "" {
log.Fatalf("persurl db dsn may not be empty")
}
return dsn
}

func DbMaxConnections() int {
val := os.Getenv("PERSURL_DB_MAX_CONNECTIONS")
if val == "" {
val = "10"
}

maxCon, err := strconv.ParseInt(val, 10, 32)
if err != nil {
log.Fatalf("invalid db max connection parameter %s", val)
}

return int(maxCon)
}
33 changes: 14 additions & 19 deletions db/database.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,32 +3,25 @@ package db
import (
"errors"
"fmt"
"sync"

"github.com/doug-martin/goqu/v9"
"github.com/doug-martin/goqu/v9/exec"
"github.com/fabiante/persurl/app"
"modernc.org/sqlite"
sqlite3 "modernc.org/sqlite/lib"
"github.com/lib/pq"
)

// Database implements the applications core logic.
type Database struct {
db *goqu.Database
lock *sync.RWMutex
db *goqu.Database
}

func NewDatabase(db *goqu.Database) *Database {
return &Database{
db: db,
lock: &sync.RWMutex{},
db: db,
}
}

func (db *Database) Resolve(domain, name string) (string, error) {
db.lock.RLock()
defer db.lock.RUnlock()

query := db.db.Select("purls.target").
From("purls").
Join(goqu.T("domains"), goqu.On(goqu.I("domains.id").Eq(goqu.I("purls.domain_id")))).
Expand All @@ -50,9 +43,6 @@ func (db *Database) Resolve(domain, name string) (string, error) {
}

func (db *Database) SavePURL(domain, name, target string) error {
db.lock.Lock()
defer db.lock.Unlock()

// lookup domain first
query := db.db.Select("id").From("domains").Where(goqu.C("name").Eq(domain)).Limit(1)

Expand Down Expand Up @@ -100,9 +90,6 @@ func (db *Database) SavePURL(domain, name, target string) error {
}

func (db *Database) CreateDomain(domain string) error {
db.lock.Lock()
defer db.lock.Unlock()

stmt := db.db.Insert("domains").
Cols("name").
Vals(goqu.Vals{domain})
Expand All @@ -114,15 +101,23 @@ func (db *Database) CreateDomain(domain string) error {
}
}

const (
pgErrUniqueKeyViolation = "23505"
)

func mapDBError(err error) error {
var serr *sqlite.Error
var serr *pq.Error
if !errors.As(err, &serr) {
return err
}

code := serr.Code()
// Error codes
// SQLite: https://www.sqlite.org/rescode.html
// Postgres: http://www.postgresql.org/docs/9.3/static/errcodes-appendix.html

code := serr.Code
switch code {
case sqlite3.SQLITE_CONSTRAINT_UNIQUE:
case pgErrUniqueKeyViolation:
return fmt.Errorf("%w: %s", app.ErrBadRequest, err)
default:
return fmt.Errorf("unexpected error: %w", err)
Expand Down
20 changes: 20 additions & 0 deletions db/empty.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package db

import (
"errors"

"github.com/doug-martin/goqu/v9"
)

// EmptyTables is used to empty a collection of tables. This may be useful if truncating a
// table is not possible.
func EmptyTables(db *goqu.Database, tables ...string) error {
return db.WithTx(func(db *goqu.TxDatabase) error {
var errs []error
for _, table := range tables {
_, err := db.Delete(table).Executor().Exec()
errs = append(errs, err)
}
return errors.Join(errs...)
})
}
30 changes: 15 additions & 15 deletions db/migrations/migrate.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
"github.com/lopezator/migrator"
)

func Run(db *sql.DB) error {
func RunPostgres(db *sql.DB) error {
// Configure migrations

m, err := migrator.New(
// type cast is required because []*migrator.MigrationNoTx is not assignable to []migrator.Migration
migrator.Migrations(migrationsSQLite...),
migrator.Migrations(migrationsPostgres...),
)
if err != nil {
return fmt.Errorf("initializing migrations failed: %w", err)
Expand All @@ -38,29 +38,29 @@ func newMigration(name string, query string) *migrator.MigrationNoTx {
}
}

var migrationsSQLite = []any{
newMigration("2023-09-18-00000001-CreateTableDomains", `create table main.domains
var migrationsPostgres = []any{
newMigration("2023-09-18-00000001-CreateTableDomains", `create table domains
(
id integer not null
constraint domains_pk
primary key autoincrement,
name varchar(128) not null
id serial
constraint domains_pk2
unique
unique,
name varchar(128) not null
constraint domains_pk
primary key
)`,
),
newMigration("2023-09-18-00000002-CreateTablePurls", `create table purls
(
id integer not null
constraint puls_pk
primary key autoincrement,
id serial
constraint purls_pk
primary key,
domain_id integer not null
constraint purls_domains_id_fk
references domains
on delete restrict,
references domains (id)
on delete restrict,
name varchar(128) not null,
target varchar(4096) not null,
constraint purls_pk
constraint purls_pk2
unique (domain_id, name)
)`,
),
Expand Down
22 changes: 12 additions & 10 deletions db/setup.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,32 @@ import (
"fmt"

"github.com/doug-martin/goqu/v9"
_ "github.com/doug-martin/goqu/v9/dialect/sqlite3"
_ "github.com/doug-martin/goqu/v9/dialect/postgres"
"github.com/fabiante/persurl/db/migrations"
_ "modernc.org/sqlite"
_ "github.com/lib/pq"
)

func SetupAndMigrateDB(path string) (*sql.DB, *goqu.Database, error) {
db, gdb, err := SetupDB(path)
func SetupAndMigratePostgresDB(dsn string, maxConnections int) (*sql.DB, *goqu.Database, error) {
db, gdb, err := SetupPostgresDB(dsn, maxConnections)
if err != nil {
return nil, nil, err
}

err = migrations.Run(db)
err = migrations.RunPostgres(db)
if err != nil {
return nil, nil, fmt.Errorf("migrating sqlite database failed: %s", err)
return nil, nil, fmt.Errorf("migrating postgres database failed: %s", err)
}

return db, gdb, nil
}

func SetupDB(path string) (*sql.DB, *goqu.Database, error) {
database, err := sql.Open("sqlite", path)
func SetupPostgresDB(dsn string, maxConnections int) (*sql.DB, *goqu.Database, error) {
database, err := sql.Open("postgres", dsn)
if err != nil {
return nil, nil, fmt.Errorf("opening sqlite database failed: %s", err)
return nil, nil, fmt.Errorf("opening postgres database failed: %s", err)
}

return database, goqu.New("sqlite3", database), nil
database.SetMaxOpenConns(maxConnections)

return database, goqu.New("postgres", database), nil
}
30 changes: 30 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
version: '3.8'

services:
db:
image: postgres:14-alpine
environment:
- POSTGRES_DB=persurl
- POSTGRES_USER=persurl
- POSTGRES_PASSWORD=persurl
ports:
- '5432:5432'
volumes:
- db:/var/lib/postgresql/data:rw
healthcheck:
test: [ "CMD-SHELL", "pg_isready" ]
interval: 10s
timeout: 5s
retries: 5

pgadmin:
profiles: ["pgadmin"]
image: dpage/pgadmin4
environment:
PGADMIN_DEFAULT_EMAIL: [email protected]
PGADMIN_DEFAULT_PASSWORD: root
ports:
- "5454:80"

volumes:
db:
Loading

0 comments on commit f483377

Please sign in to comment.