feat(service,database): new Database abstraction to initiate and manipulate database
This commit is contained in:
115
database/database.go
Normal file
115
database/database.go
Normal file
@@ -0,0 +1,115 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"log/slog"
|
||||
|
||||
"forge.capytal.company/loreddev/x/tinyssert"
|
||||
)
|
||||
|
||||
type Database struct {
|
||||
sql *sql.DB
|
||||
ctx context.Context
|
||||
|
||||
assert tinyssert.Assertions
|
||||
log *slog.Logger
|
||||
}
|
||||
|
||||
func New(cfg Config) (*Database, error) {
|
||||
if cfg.SQL == nil {
|
||||
return nil, errors.New("SQL database interface should not be nil")
|
||||
}
|
||||
if cfg.Context == nil {
|
||||
return nil, errors.New("context interface should not be nil")
|
||||
}
|
||||
if cfg.Assertions == nil {
|
||||
return nil, errors.New("assertions interface should not be nil")
|
||||
}
|
||||
if cfg.Logger == nil {
|
||||
return nil, errors.New("logger should not be a nil pointer")
|
||||
}
|
||||
|
||||
db := &Database{
|
||||
sql: cfg.SQL,
|
||||
ctx: cfg.Context,
|
||||
|
||||
assert: cfg.Assertions,
|
||||
log: cfg.Logger,
|
||||
}
|
||||
|
||||
if err := db.setup(); err != nil {
|
||||
return nil, errors.New("error while setting up Database struct")
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
SQL *sql.DB
|
||||
Context context.Context
|
||||
|
||||
Assertions tinyssert.Assertions
|
||||
Logger *slog.Logger
|
||||
}
|
||||
|
||||
func (db *Database) setup() error {
|
||||
db.assert.NotNil(db.sql)
|
||||
db.assert.NotNil(db.ctx)
|
||||
db.assert.NotNil(db.log)
|
||||
|
||||
log := db.log
|
||||
log.Info("Setting up database")
|
||||
|
||||
log.Debug("Pinging database")
|
||||
|
||||
err := db.sql.PingContext(db.ctx)
|
||||
if err != nil {
|
||||
return errors.Join(errors.New("unable to ping database"), err)
|
||||
}
|
||||
|
||||
log.Debug("Creating tables")
|
||||
|
||||
tables := []Table{
|
||||
&Project{},
|
||||
}
|
||||
|
||||
tx, err := db.sql.BeginTx(db.ctx, nil)
|
||||
if err != nil {
|
||||
return errors.Join(errors.New("unable to start transaction to create tables"), err)
|
||||
}
|
||||
|
||||
for _, t := range tables {
|
||||
_, err := tx.Exec(t.setup())
|
||||
if err != nil {
|
||||
return errors.Join(fmt.Errorf("error while trying to create table %T", t), err)
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.Commit()
|
||||
if err != nil {
|
||||
return errors.Join(errors.New("unable to run transaction to create tables"), err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *Database) Insert(t Table) error {
|
||||
q, err := t.insert()
|
||||
if err != nil {
|
||||
return errors.Join(fmt.Errorf("creating query to insert table %T resulted in error", t), err)
|
||||
}
|
||||
|
||||
_, err = db.sql.ExecContext(db.ctx, q)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type Table interface {
|
||||
setup() string
|
||||
insert() (string, error)
|
||||
}
|
||||
38
database/projects.go
Normal file
38
database/projects.go
Normal file
@@ -0,0 +1,38 @@
|
||||
package database
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
)
|
||||
|
||||
type Project struct {
|
||||
ID string
|
||||
Title string
|
||||
}
|
||||
|
||||
var _ Table = (*Project)(nil)
|
||||
|
||||
func (p *Project) setup() string {
|
||||
return `
|
||||
CREATE TABLE IF NOT EXISTS projects (
|
||||
id TEXT PRIMARY KEY NOT NULL,
|
||||
title TEXT NOT NULL
|
||||
) STRICT`
|
||||
}
|
||||
|
||||
func (p *Project) insert() (string, error) {
|
||||
if p.ID == "" {
|
||||
return "", errors.New("ID field shouldn't be a empty string")
|
||||
}
|
||||
if p.Title == "" {
|
||||
return "", errors.New("Title field shouldn't be a empty string")
|
||||
}
|
||||
return fmt.Sprintf(`
|
||||
INSERT OR FAIL INTO projects (
|
||||
id,
|
||||
title
|
||||
) VALUES (
|
||||
'%s',
|
||||
'%s'
|
||||
)`, p.ID, p.Title), nil
|
||||
}
|
||||
Reference in New Issue
Block a user