diff --git a/comicverse.go b/comicverse.go index eada92e..dcd224f 100644 --- a/comicverse.go +++ b/comicverse.go @@ -9,6 +9,7 @@ import ( "log/slog" "net/http" + "forge.capytal.company/capytalcode/project-comicverse/database" "forge.capytal.company/capytalcode/project-comicverse/router" "forge.capytal.company/capytalcode/project-comicverse/service" "forge.capytal.company/capytalcode/project-comicverse/static" @@ -111,9 +112,19 @@ func (app *app) setup() error { var err error + database, err := database.New(database.Config{ + SQL: app.db, + Context: app.ctx, + Assertions: app.assert, + Logger: app.logger, + }) + if err != nil { + return errors.Join(errors.New("unable to create database struct"), err) + } + service, err := service.New(service.Config{ - DB: app.db, S3: app.s3, + DB: database, Context: app.ctx, diff --git a/database/database.go b/database/database.go new file mode 100644 index 0000000..4775240 --- /dev/null +++ b/database/database.go @@ -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) +} diff --git a/database/projects.go b/database/projects.go new file mode 100644 index 0000000..33777fe --- /dev/null +++ b/database/projects.go @@ -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 +} diff --git a/go.mod b/go.mod index 355f286..eddf7e2 100644 --- a/go.mod +++ b/go.mod @@ -8,6 +8,7 @@ require ( forge.capytal.company/loreddev/x v0.0.0-20250305165122-0ccb26ab783b github.com/aws/aws-sdk-go-v2 v1.36.3 github.com/aws/aws-sdk-go-v2/service/s3 v1.78.1 + github.com/google/uuid v1.6.0 github.com/tursodatabase/go-libsql v0.0.0-20241221181756-6121e81fbf92 ) diff --git a/go.sum b/go.sum index c01df5f..097a65e 100644 --- a/go.sum +++ b/go.sum @@ -26,6 +26,8 @@ github.com/aws/smithy-go v1.22.2 h1:6D9hW43xKFrRx/tXXfAlIZc4JI+yQe6snnWcQyxSyLQ= github.com/aws/smithy-go v1.22.2/go.mod h1:irrKGvNn1InZwb2d7fkIRNucdfwR8R+Ts3wxYa/cJHg= github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= +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/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06 h1:JLvn7D+wXjH9g4Jsjo+VqmzTUpl/LX7vfr6VOfSWTdM= github.com/libsql/sqlite-antlr4-parser v0.0.0-20240327125255-dbf53b6cbf06/go.mod h1:FUkZ5OHjlGPjnM2UyGJz9TypXQFgYqw6AFNO1UiROTM= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= diff --git a/service/projects.go b/service/projects.go index df5110a..f5e9691 100644 --- a/service/projects.go +++ b/service/projects.go @@ -1,6 +1,32 @@ package service -func (s *service) NewProject() { +import ( + "encoding/xml" + "errors" + + "forge.capytal.company/capytalcode/project-comicverse/database" +) + +func (s *service) NewProject() error { + s.assert.NotNil(s.db) + + id, err := uuid.NewV7() + if err != nil { + return err + } + + s.assert.NotZero(id.String(), "UUID should never be invalid") + + err = s.db.Insert(&database.Project{ + ID: id.String(), + Title: "New Project", + }) + if err != nil { + return errors.Join(errors.New("database returned error while inserting new project"), err) + } + + + return nil } func (s *service) ListProjects() { diff --git a/service/service.go b/service/service.go index eb33b6d..9d54e8d 100644 --- a/service/service.go +++ b/service/service.go @@ -2,17 +2,17 @@ package service import ( "context" - "database/sql" "errors" "log/slog" + "forge.capytal.company/capytalcode/project-comicverse/database" "forge.capytal.company/loreddev/x/tinyssert" "github.com/aws/aws-sdk-go-v2/service/s3" ) type service struct { - db *sql.DB s3 *s3.Client + db *database.Database ctx context.Context @@ -22,7 +22,7 @@ type service struct { func New(cfg Config) (Service, error) { if cfg.DB == nil { - return nil, errors.New("database should not be a nil interface") + return nil, errors.New("database should not be a nil pointer") } if cfg.S3 == nil { return nil, errors.New("s3 client should not be a nil pointer") @@ -47,8 +47,8 @@ func New(cfg Config) (Service, error) { } type Config struct { - DB *sql.DB S3 *s3.Client + DB *database.Database Context context.Context