From 17cb4505be11a52f98faab46d0561b13f9a45d06 Mon Sep 17 00:00:00 2001 From: Jean Jacques Avril Date: Mon, 10 Mar 2025 07:29:34 +0000 Subject: [PATCH] feat: Add database object models and repositories for Activity, Company, Customer, Project, TimeEntry, and User with GORM integration --- backend/cmd/api/main.go | 14 ++- backend/go.mod | 9 ++ backend/go.sum | 10 ++ .../domain/persistence/activity_datasource.go | 15 +++ .../domain/persistence/company_datasource.go | 15 +++ .../domain/persistence/customer_datasource.go | 15 +++ .../domain/persistence/project_datasource.go | 15 +++ .../persistence/timeentry_datasource.go | 18 +++ .../domain/persistence/user_datasource.go | 16 +++ .../repositories/activity_repository.go | 15 +++ .../domain/repositories/company_repository.go | 15 +++ .../repositories/customer_repository.go | 15 +++ .../domain/repositories/project_repository.go | 15 +++ .../repositories/timeentry_repository.go | 18 +++ .../domain/repositories/user_repository.go | 16 +++ .../infrastructure/persistence/db/database.go | 99 ++++++++++++++++ .../{postgres => db}/dbo/activity_dbo.go | 0 .../{postgres => db}/dbo/company_dbo.go | 0 .../{postgres => db}/dbo/customer_dbo.go | 0 .../{postgres => db}/dbo/project_dbo.go | 0 .../{postgres => db}/dbo/timeentry_dbo.go | 0 .../{postgres => db}/dbo/user_dbo.go | 0 .../persistence/db/ds/activity_datasource.go | 66 +++++++++++ .../persistence/db/ds/company_datasource.go | 63 ++++++++++ .../persistence/db/ds/customer_datasource.go | 66 +++++++++++ .../persistence/db/ds/project_datasource.go | 66 +++++++++++ .../persistence/db/ds/timeentry_datasource.go | 109 ++++++++++++++++++ .../persistence/db/ds/user_datasource.go | 97 ++++++++++++++++ 28 files changed, 786 insertions(+), 1 deletion(-) create mode 100644 backend/internal/domain/persistence/activity_datasource.go create mode 100644 backend/internal/domain/persistence/company_datasource.go create mode 100644 backend/internal/domain/persistence/customer_datasource.go create mode 100644 backend/internal/domain/persistence/project_datasource.go create mode 100644 backend/internal/domain/persistence/timeentry_datasource.go create mode 100644 backend/internal/domain/persistence/user_datasource.go create mode 100644 backend/internal/domain/repositories/activity_repository.go create mode 100644 backend/internal/domain/repositories/company_repository.go create mode 100644 backend/internal/domain/repositories/customer_repository.go create mode 100644 backend/internal/domain/repositories/project_repository.go create mode 100644 backend/internal/domain/repositories/timeentry_repository.go create mode 100644 backend/internal/domain/repositories/user_repository.go create mode 100644 backend/internal/infrastructure/persistence/db/database.go rename backend/internal/infrastructure/persistence/{postgres => db}/dbo/activity_dbo.go (100%) rename backend/internal/infrastructure/persistence/{postgres => db}/dbo/company_dbo.go (100%) rename backend/internal/infrastructure/persistence/{postgres => db}/dbo/customer_dbo.go (100%) rename backend/internal/infrastructure/persistence/{postgres => db}/dbo/project_dbo.go (100%) rename backend/internal/infrastructure/persistence/{postgres => db}/dbo/timeentry_dbo.go (100%) rename backend/internal/infrastructure/persistence/{postgres => db}/dbo/user_dbo.go (100%) create mode 100644 backend/internal/infrastructure/persistence/db/ds/activity_datasource.go create mode 100644 backend/internal/infrastructure/persistence/db/ds/company_datasource.go create mode 100644 backend/internal/infrastructure/persistence/db/ds/customer_datasource.go create mode 100644 backend/internal/infrastructure/persistence/db/ds/project_datasource.go create mode 100644 backend/internal/infrastructure/persistence/db/ds/timeentry_datasource.go create mode 100644 backend/internal/infrastructure/persistence/db/ds/user_datasource.go diff --git a/backend/cmd/api/main.go b/backend/cmd/api/main.go index 72a2027..6dc1c4c 100644 --- a/backend/cmd/api/main.go +++ b/backend/cmd/api/main.go @@ -7,8 +7,10 @@ import ( "github.com/gin-gonic/gin" swaggerFiles "github.com/swaggo/files" ginSwagger "github.com/swaggo/gin-swagger" - _ "github.com/timetracker/backend/docs" // This line is important for swag to work + "github.com/timetracker/backend/internal/infrastructure/persistence/db" + _ "gorm.io/driver/postgres" + // GORM IMPORTS MARKER ) // @title Time Tracker API @@ -28,6 +30,16 @@ func helloHandler(c *gin.Context) { } func main() { + + db, _ := db.NewDatasourceContainer(db.DatabaseConfig{ + Host: "localhost", + Port: 5432, + User: "timetracker", + Password: "timetracker", + DBName: "timetracker", + SSLMode: "disable", + }) + r := gin.Default() r.GET("/", helloHandler) diff --git a/backend/go.mod b/backend/go.mod index 1a5ea13..1f04f11 100644 --- a/backend/go.mod +++ b/backend/go.mod @@ -11,6 +11,14 @@ require ( gorm.io/gorm v1.25.12 ) +require ( + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a // indirect + github.com/jackc/pgx/v5 v5.5.5 // indirect + github.com/jackc/puddle/v2 v2.2.1 // indirect + golang.org/x/sync v0.12.0 // indirect +) + require ( github.com/KyleBanks/depth v1.2.1 // indirect github.com/bytedance/sonic v1.13.1 // indirect @@ -47,4 +55,5 @@ require ( golang.org/x/tools v0.31.0 // indirect google.golang.org/protobuf v1.36.5 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect + gorm.io/driver/postgres v1.5.11 ) diff --git a/backend/go.sum b/backend/go.sum index 19f1ebf..9e907a0 100644 --- a/backend/go.sum +++ b/backend/go.sum @@ -40,6 +40,14 @@ github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PU github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI= github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +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-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.5.5 h1:amBjrZVmksIdNjxGW/IiIMzxMKZFelXbUoPNb+8sjQw= +github.com/jackc/pgx/v5 v5.5.5/go.mod h1:ez9gk+OAat140fv9ErkZDYFWmXLfV+++K0uAOiwgm1A= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= github.com/jinzhu/now v1.1.5 h1:/o9tlHleP7gOFmsnYNz3RGnqzefHA47wQpKrrdTIwXQ= @@ -150,6 +158,8 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/postgres v1.5.11 h1:ubBVAfbKEUld/twyKZ0IYn9rSQh448EdelLYk9Mv314= +gorm.io/driver/postgres v1.5.11/go.mod h1:DX3GReXH+3FPWGrrgffdvCk3DQ1dwDPdmbenSkweRGI= gorm.io/gorm v1.25.12 h1:I0u8i2hWQItBq1WfE0o2+WuL9+8L21K9e2HHSTE/0f8= gorm.io/gorm v1.25.12/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= diff --git a/backend/internal/domain/persistence/activity_datasource.go b/backend/internal/domain/persistence/activity_datasource.go new file mode 100644 index 0000000..a606423 --- /dev/null +++ b/backend/internal/domain/persistence/activity_datasource.go @@ -0,0 +1,15 @@ +package persistence + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type ActivityDatasource interface { + Get(ctx context.Context, id ulid.ULID) (*entities.Activity, error) + Create(ctx context.Context, activity *entities.Activity) error + Update(ctx context.Context, activity *entities.Activity) error + Delete(ctx context.Context, id ulid.ULID) error +} diff --git a/backend/internal/domain/persistence/company_datasource.go b/backend/internal/domain/persistence/company_datasource.go new file mode 100644 index 0000000..1e8e92c --- /dev/null +++ b/backend/internal/domain/persistence/company_datasource.go @@ -0,0 +1,15 @@ +package persistence + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type CompanyDatasource interface { + Get(ctx context.Context, id ulid.ULID) (*entities.Company, error) + Create(ctx context.Context, company *entities.Company) error + Update(ctx context.Context, company *entities.Company) error + Delete(ctx context.Context, id ulid.ULID) error +} diff --git a/backend/internal/domain/persistence/customer_datasource.go b/backend/internal/domain/persistence/customer_datasource.go new file mode 100644 index 0000000..c935d44 --- /dev/null +++ b/backend/internal/domain/persistence/customer_datasource.go @@ -0,0 +1,15 @@ +package persistence + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type CustomerDatasource interface { + Get(ctx context.Context, id ulid.ULID) (*entities.Customer, error) + Create(ctx context.Context, customer *entities.Customer) error + Update(ctx context.Context, customer *entities.Customer) error + Delete(ctx context.Context, id ulid.ULID) error +} diff --git a/backend/internal/domain/persistence/project_datasource.go b/backend/internal/domain/persistence/project_datasource.go new file mode 100644 index 0000000..4a1290d --- /dev/null +++ b/backend/internal/domain/persistence/project_datasource.go @@ -0,0 +1,15 @@ +package persistence + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type ProjectDatasource interface { + Get(ctx context.Context, id ulid.ULID) (*entities.Project, error) + Create(ctx context.Context, project *entities.Project) error + Update(ctx context.Context, project *entities.Project) error + Delete(ctx context.Context, id ulid.ULID) error +} diff --git a/backend/internal/domain/persistence/timeentry_datasource.go b/backend/internal/domain/persistence/timeentry_datasource.go new file mode 100644 index 0000000..a9fde17 --- /dev/null +++ b/backend/internal/domain/persistence/timeentry_datasource.go @@ -0,0 +1,18 @@ +package persistence + +import ( + "context" + + "time" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type TimeEntryDatasource interface { + Get(ctx context.Context, id ulid.ULID) (*entities.TimeEntry, error) + Create(ctx context.Context, timeEntry *entities.TimeEntry) error + Update(ctx context.Context, timeEntry *entities.TimeEntry) error + Delete(ctx context.Context, id ulid.ULID) error + GetByRange(ctx context.Context, userID ulid.ULID, from time.Time, to time.Time) ([]*entities.TimeEntry, error) +} diff --git a/backend/internal/domain/persistence/user_datasource.go b/backend/internal/domain/persistence/user_datasource.go new file mode 100644 index 0000000..fea5cc8 --- /dev/null +++ b/backend/internal/domain/persistence/user_datasource.go @@ -0,0 +1,16 @@ +package persistence + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type UserDatasource interface { + Get(ctx context.Context, id ulid.ULID) (*entities.User, error) + Create(ctx context.Context, user *entities.User) error + Update(ctx context.Context, user *entities.User) error + Delete(ctx context.Context, id ulid.ULID) error + GetByUsername(ctx context.Context, username string) (*entities.User, error) +} diff --git a/backend/internal/domain/repositories/activity_repository.go b/backend/internal/domain/repositories/activity_repository.go new file mode 100644 index 0000000..dd52fc3 --- /dev/null +++ b/backend/internal/domain/repositories/activity_repository.go @@ -0,0 +1,15 @@ +package repositories + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type ActivityRepository interface { + Get(ctx context.Context, id ulid.ULID) (*entities.Activity, error) + Create(ctx context.Context, activity *entities.Activity) error + Update(ctx context.Context, activity *entities.Activity) error + Delete(ctx context.Context, id ulid.ULID) error +} diff --git a/backend/internal/domain/repositories/company_repository.go b/backend/internal/domain/repositories/company_repository.go new file mode 100644 index 0000000..cc978b4 --- /dev/null +++ b/backend/internal/domain/repositories/company_repository.go @@ -0,0 +1,15 @@ +package repositories + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type CompanyRepository interface { + Get(ctx context.Context, id ulid.ULID) (*entities.Company, error) + Create(ctx context.Context, company *entities.Company) error + Update(ctx context.Context, company *entities.Company) error + Delete(ctx context.Context, id ulid.ULID) error +} diff --git a/backend/internal/domain/repositories/customer_repository.go b/backend/internal/domain/repositories/customer_repository.go new file mode 100644 index 0000000..31bc3d9 --- /dev/null +++ b/backend/internal/domain/repositories/customer_repository.go @@ -0,0 +1,15 @@ +package repositories + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type CustomerRepository interface { + Get(ctx context.Context, id ulid.ULID) (*entities.Customer, error) + Create(ctx context.Context, customer *entities.Customer) error + Update(ctx context.Context, customer *entities.Customer) error + Delete(ctx context.Context, id ulid.ULID) error +} diff --git a/backend/internal/domain/repositories/project_repository.go b/backend/internal/domain/repositories/project_repository.go new file mode 100644 index 0000000..61304a4 --- /dev/null +++ b/backend/internal/domain/repositories/project_repository.go @@ -0,0 +1,15 @@ +package repositories + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type ProjectRepository interface { + Get(ctx context.Context, id ulid.ULID) (*entities.Project, error) + Create(ctx context.Context, project *entities.Project) error + Update(ctx context.Context, project *entities.Project) error + Delete(ctx context.Context, id ulid.ULID) error +} diff --git a/backend/internal/domain/repositories/timeentry_repository.go b/backend/internal/domain/repositories/timeentry_repository.go new file mode 100644 index 0000000..41af6a7 --- /dev/null +++ b/backend/internal/domain/repositories/timeentry_repository.go @@ -0,0 +1,18 @@ +package repositories + +import ( + "context" + + "time" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type TimeEntryRepository interface { + Get(ctx context.Context, id ulid.ULID) (*entities.TimeEntry, error) + Create(ctx context.Context, timeEntry *entities.TimeEntry) error + Update(ctx context.Context, timeEntry *entities.TimeEntry) error + Delete(ctx context.Context, id ulid.ULID) error + GetByRange(ctx context.Context, userID ulid.ULID, from time.Time, to time.Time) ([]*entities.TimeEntry, error) +} diff --git a/backend/internal/domain/repositories/user_repository.go b/backend/internal/domain/repositories/user_repository.go new file mode 100644 index 0000000..6a9ea90 --- /dev/null +++ b/backend/internal/domain/repositories/user_repository.go @@ -0,0 +1,16 @@ +package repositories + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" +) + +type UserRepository interface { + Get(ctx context.Context, id ulid.ULID) (*entities.User, error) + Create(ctx context.Context, user *entities.User) error + Update(ctx context.Context, user *entities.User) error + Delete(ctx context.Context, id ulid.ULID) error + GetByUsername(ctx context.Context, username string) (*entities.User, error) +} diff --git a/backend/internal/infrastructure/persistence/db/database.go b/backend/internal/infrastructure/persistence/db/database.go new file mode 100644 index 0000000..f09abad --- /dev/null +++ b/backend/internal/infrastructure/persistence/db/database.go @@ -0,0 +1,99 @@ +package db + +import ( + "database/sql" + "fmt" + + "github.com/timetracker/backend/internal/domain/repositories" + "github.com/timetracker/backend/internal/infrastructure/persistence/db/ds" + "gorm.io/driver/postgres" + "gorm.io/gorm" +) + +// DatabaseConfig enthält die Konfigurationsinformationen für die Datenbankverbindung +type DatabaseConfig struct { + Host string + Port int + User string + Password string + DBName string + SSLMode string +} + +// DatasourceContainer enthält alle Repository-Instanzen +type DatasourceContainer struct { + ActivityDatasource repositories.ActivityRepository + CompanyDatasource repositories.CompanyRepository + CustomerDatasource repositories.CustomerRepository + ProjectDatasource repositories.ProjectRepository + TimeEntryDatasource repositories.TimeEntryRepository + UserDatasource repositories.UserRepository +} + +// NewDatasourceContainer erstellt und initialisiert alle Repository-Instanzen +func NewDatasourceContainer(config DatabaseConfig) (*DatasourceContainer, error) { + // Erstelle DSN (Data Source Name) für die Datenbankverbindung + dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s", + config.Host, config.Port, config.User, config.Password, config.DBName, config.SSLMode) + + // Erstelle SQL-Datenbankverbindung + sqlDB, err := sql.Open("pgx", dsn) + if err != nil { + return nil, fmt.Errorf("fehler beim Öffnen der SQL-Verbindung: %w", err) + } + + // Konfiguriere Verbindungspool + sqlDB.SetMaxIdleConns(10) + sqlDB.SetMaxOpenConns(100) + + // Initialisiere GORM mit der SQL-Verbindung + gormDB, err := gorm.Open(postgres.New(postgres.Config{ + Conn: sqlDB, + }), &gorm.Config{}) + if err != nil { + return nil, fmt.Errorf("fehler beim Initialisieren von GORM: %w", err) + } + + // Erstelle alle Repository-Instanzen + return &DatasourceContainer{ + ActivityDatasource: ds.NewActivityDatasource(gormDB), + CompanyDatasource: ds.NewCompanyDatasource(gormDB), + CustomerDatasource: ds.NewCustomerDatasource(gormDB), + ProjectDatasource: ds.NewProjectDatasource(gormDB), + TimeEntryDatasource: ds.NewTimeEntryDatasource(gormDB), + UserDatasource: ds.NewUserDatasource(gormDB), + }, nil +} + +// Close schließt die Datenbankverbindung +func (r *DatasourceContainer) Close() error { + db, err := r.getGormDB() + if err != nil { + return err + } + + sqlDB, err := db.DB() + if err != nil { + return err + } + + return sqlDB.Close() +} + +// Helper-Methode, um die GORM-DB aus einem der Repositories zu extrahieren +func (r *DatasourceContainer) getGormDB() (*gorm.DB, error) { + // Wir nehmen an, dass alle Repositories das gleiche DB-Handle verwenden + // Deshalb können wir einfach eines der Repositories nehmen + // Dies funktioniert nur, wenn wir Zugriff auf die interne DB haben oder eine Methode hinzufügen + // Hier müsste angepasst werden, wie Sie Zugriff auf die GORM-DB bekommen + + // Beispiel (müsste angepasst werden): + // activityDS, ok := r.ActivityRepository.(*ds.ActivityDatasource) + // if !ok { + // return nil, fmt.Errorf("Konnte GORM-DB nicht aus ActivityRepository extrahieren") + // } + // return activityDS.GetDB(), nil + + // Placeholder für die tatsächliche Implementierung: + return nil, fmt.Errorf("getGormDB() muss implementiert werden") +} diff --git a/backend/internal/infrastructure/persistence/postgres/dbo/activity_dbo.go b/backend/internal/infrastructure/persistence/db/dbo/activity_dbo.go similarity index 100% rename from backend/internal/infrastructure/persistence/postgres/dbo/activity_dbo.go rename to backend/internal/infrastructure/persistence/db/dbo/activity_dbo.go diff --git a/backend/internal/infrastructure/persistence/postgres/dbo/company_dbo.go b/backend/internal/infrastructure/persistence/db/dbo/company_dbo.go similarity index 100% rename from backend/internal/infrastructure/persistence/postgres/dbo/company_dbo.go rename to backend/internal/infrastructure/persistence/db/dbo/company_dbo.go diff --git a/backend/internal/infrastructure/persistence/postgres/dbo/customer_dbo.go b/backend/internal/infrastructure/persistence/db/dbo/customer_dbo.go similarity index 100% rename from backend/internal/infrastructure/persistence/postgres/dbo/customer_dbo.go rename to backend/internal/infrastructure/persistence/db/dbo/customer_dbo.go diff --git a/backend/internal/infrastructure/persistence/postgres/dbo/project_dbo.go b/backend/internal/infrastructure/persistence/db/dbo/project_dbo.go similarity index 100% rename from backend/internal/infrastructure/persistence/postgres/dbo/project_dbo.go rename to backend/internal/infrastructure/persistence/db/dbo/project_dbo.go diff --git a/backend/internal/infrastructure/persistence/postgres/dbo/timeentry_dbo.go b/backend/internal/infrastructure/persistence/db/dbo/timeentry_dbo.go similarity index 100% rename from backend/internal/infrastructure/persistence/postgres/dbo/timeentry_dbo.go rename to backend/internal/infrastructure/persistence/db/dbo/timeentry_dbo.go diff --git a/backend/internal/infrastructure/persistence/postgres/dbo/user_dbo.go b/backend/internal/infrastructure/persistence/db/dbo/user_dbo.go similarity index 100% rename from backend/internal/infrastructure/persistence/postgres/dbo/user_dbo.go rename to backend/internal/infrastructure/persistence/db/dbo/user_dbo.go diff --git a/backend/internal/infrastructure/persistence/db/ds/activity_datasource.go b/backend/internal/infrastructure/persistence/db/ds/activity_datasource.go new file mode 100644 index 0000000..55e4ea6 --- /dev/null +++ b/backend/internal/infrastructure/persistence/db/ds/activity_datasource.go @@ -0,0 +1,66 @@ +package ds + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" + "github.com/timetracker/backend/internal/domain/persistence" + "github.com/timetracker/backend/internal/infrastructure/persistence/db/dbo" + "gorm.io/gorm" +) + +type ActivityDatasource struct { + db *gorm.DB +} + +func NewActivityDatasource(db *gorm.DB) persistence.ActivityDatasource { + return &ActivityDatasource{db: db} +} + +func (r *ActivityDatasource) Get(ctx context.Context, id ulid.ULID) (*entities.Activity, error) { + var activityDBO dbo.ActivityDBO + if err := r.db.WithContext(ctx).First(&activityDBO, "id = ?", id).Error; err != nil { + return nil, err + } + + activity := &entities.Activity{ + EntityBase: entities.EntityBase{ + ID: activityDBO.ID, + CreatedAt: activityDBO.CreatedAt, + UpdatedAt: activityDBO.UpdatedAt, + }, + Name: activityDBO.Name, + BillingRate: activityDBO.BillingRate, + } + + return activity, nil +} + +func (r *ActivityDatasource) Create(ctx context.Context, activity *entities.Activity) error { + activityDBO := dbo.ActivityDBO{ + ID: activity.ID, + CreatedAt: activity.CreatedAt, + UpdatedAt: activity.UpdatedAt, + Name: activity.Name, + BillingRate: activity.BillingRate, + } + + return r.db.WithContext(ctx).Create(&activityDBO).Error +} + +func (r *ActivityDatasource) Update(ctx context.Context, activity *entities.Activity) error { + activityDBO := dbo.ActivityDBO{ + ID: activity.ID, + CreatedAt: activity.CreatedAt, + UpdatedAt: activity.UpdatedAt, + Name: activity.Name, + BillingRate: activity.BillingRate, + } + + return r.db.WithContext(ctx).Save(&activityDBO).Error +} + +func (r *ActivityDatasource) Delete(ctx context.Context, id ulid.ULID) error { + return r.db.WithContext(ctx).Delete(&dbo.ActivityDBO{}, "id = ?", id).Error +} diff --git a/backend/internal/infrastructure/persistence/db/ds/company_datasource.go b/backend/internal/infrastructure/persistence/db/ds/company_datasource.go new file mode 100644 index 0000000..44d170a --- /dev/null +++ b/backend/internal/infrastructure/persistence/db/ds/company_datasource.go @@ -0,0 +1,63 @@ +package ds + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" + "github.com/timetracker/backend/internal/domain/persistence" + "github.com/timetracker/backend/internal/infrastructure/persistence/db/dbo" + "gorm.io/gorm" +) + +type CompanyyDatasource struct { + db *gorm.DB +} + +func NewCompanyDatasource(db *gorm.DB) persistence.CompanyDatasource { + return &CompanyyDatasource{db: db} +} + +func (r *CompanyyDatasource) Get(ctx context.Context, id ulid.ULID) (*entities.Company, error) { + var companyDBO dbo.CompanyDBO + if err := r.db.WithContext(ctx).First(&companyDBO, "id = ?", id).Error; err != nil { + return nil, err + } + + company := &entities.Company{ + EntityBase: entities.EntityBase{ + ID: companyDBO.ID, + CreatedAt: companyDBO.CreatedAt, + UpdatedAt: companyDBO.UpdatedAt, + }, + Name: companyDBO.Name, + } + + return company, nil +} + +func (r *CompanyyDatasource) Create(ctx context.Context, company *entities.Company) error { + companyDBO := dbo.CompanyDBO{ + ID: company.ID, + CreatedAt: company.CreatedAt, + UpdatedAt: company.UpdatedAt, + Name: company.Name, + } + + return r.db.WithContext(ctx).Create(&companyDBO).Error +} + +func (r *CompanyyDatasource) Update(ctx context.Context, company *entities.Company) error { + companyDBO := dbo.CompanyDBO{ + ID: company.ID, + CreatedAt: company.CreatedAt, + UpdatedAt: company.UpdatedAt, + Name: company.Name, + } + + return r.db.WithContext(ctx).Save(&companyDBO).Error +} + +func (r *CompanyyDatasource) Delete(ctx context.Context, id ulid.ULID) error { + return r.db.WithContext(ctx).Delete(&dbo.CompanyDBO{}, "id = ?", id).Error +} diff --git a/backend/internal/infrastructure/persistence/db/ds/customer_datasource.go b/backend/internal/infrastructure/persistence/db/ds/customer_datasource.go new file mode 100644 index 0000000..57ffa0c --- /dev/null +++ b/backend/internal/infrastructure/persistence/db/ds/customer_datasource.go @@ -0,0 +1,66 @@ +package ds + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" + "github.com/timetracker/backend/internal/domain/persistence" + "github.com/timetracker/backend/internal/infrastructure/persistence/db/dbo" + "gorm.io/gorm" +) + +type CustomerDatasource struct { + db *gorm.DB +} + +func NewCustomerDatasource(db *gorm.DB) persistence.CustomerDatasource { + return &CustomerDatasource{db: db} +} + +func (r *CustomerDatasource) Get(ctx context.Context, id ulid.ULID) (*entities.Customer, error) { + var customerDBO dbo.CustomerDBO + if err := r.db.WithContext(ctx).First(&customerDBO, "id = ?", id).Error; err != nil { + return nil, err + } + + customer := &entities.Customer{ + EntityBase: entities.EntityBase{ + ID: customerDBO.ID, + CreatedAt: customerDBO.CreatedAt, + UpdatedAt: customerDBO.UpdatedAt, + }, + Name: customerDBO.Name, + CompanyID: customerDBO.CompanyID, + } + + return customer, nil +} + +func (r *CustomerDatasource) Create(ctx context.Context, customer *entities.Customer) error { + customerDBO := dbo.CustomerDBO{ + ID: customer.ID, + CreatedAt: customer.CreatedAt, + UpdatedAt: customer.UpdatedAt, + Name: customer.Name, + CompanyID: customer.CompanyID, + } + + return r.db.WithContext(ctx).Create(&customerDBO).Error +} + +func (r *CustomerDatasource) Update(ctx context.Context, customer *entities.Customer) error { + customerDBO := dbo.CustomerDBO{ + ID: customer.ID, + CreatedAt: customer.CreatedAt, + UpdatedAt: customer.UpdatedAt, + Name: customer.Name, + CompanyID: customer.CompanyID, + } + + return r.db.WithContext(ctx).Save(&customerDBO).Error +} + +func (r *CustomerDatasource) Delete(ctx context.Context, id ulid.ULID) error { + return r.db.WithContext(ctx).Delete(&dbo.CustomerDBO{}, "id = ?", id).Error +} diff --git a/backend/internal/infrastructure/persistence/db/ds/project_datasource.go b/backend/internal/infrastructure/persistence/db/ds/project_datasource.go new file mode 100644 index 0000000..1943f34 --- /dev/null +++ b/backend/internal/infrastructure/persistence/db/ds/project_datasource.go @@ -0,0 +1,66 @@ +package ds + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" + "github.com/timetracker/backend/internal/domain/persistence" + "github.com/timetracker/backend/internal/infrastructure/persistence/db/dbo" + "gorm.io/gorm" +) + +type ProjectDatasource struct { + db *gorm.DB +} + +func NewProjectDatasource(db *gorm.DB) persistence.ProjectDatasource { + return &ProjectDatasource{db: db} +} + +func (r *ProjectDatasource) Get(ctx context.Context, id ulid.ULID) (*entities.Project, error) { + var projectDBO dbo.ProjectDBO + if err := r.db.WithContext(ctx).First(&projectDBO, "id = ?", id).Error; err != nil { + return nil, err + } + + project := &entities.Project{ + EntityBase: entities.EntityBase{ + ID: projectDBO.ID, + CreatedAt: projectDBO.CreatedAt, + UpdatedAt: projectDBO.UpdatedAt, + }, + Name: projectDBO.Name, + CustomerID: projectDBO.CustomerID, + } + + return project, nil +} + +func (r *ProjectDatasource) Create(ctx context.Context, project *entities.Project) error { + projectDBO := dbo.ProjectDBO{ + ID: project.ID, + CreatedAt: project.CreatedAt, + UpdatedAt: project.UpdatedAt, + Name: project.Name, + CustomerID: project.CustomerID, + } + + return r.db.WithContext(ctx).Create(&projectDBO).Error +} + +func (r *ProjectDatasource) Update(ctx context.Context, project *entities.Project) error { + projectDBO := dbo.ProjectDBO{ + ID: project.ID, + CreatedAt: project.CreatedAt, + UpdatedAt: project.UpdatedAt, + Name: project.Name, + CustomerID: project.CustomerID, + } + + return r.db.WithContext(ctx).Save(&projectDBO).Error +} + +func (r *ProjectDatasource) Delete(ctx context.Context, id ulid.ULID) error { + return r.db.WithContext(ctx).Delete(&dbo.ProjectDBO{}, "id = ?", id).Error +} diff --git a/backend/internal/infrastructure/persistence/db/ds/timeentry_datasource.go b/backend/internal/infrastructure/persistence/db/ds/timeentry_datasource.go new file mode 100644 index 0000000..7268ef9 --- /dev/null +++ b/backend/internal/infrastructure/persistence/db/ds/timeentry_datasource.go @@ -0,0 +1,109 @@ +package ds + +import ( + "context" + "time" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" + "github.com/timetracker/backend/internal/domain/persistence" + "github.com/timetracker/backend/internal/infrastructure/persistence/db/dbo" + "gorm.io/gorm" +) + +type TimeEntryDatasource struct { + db *gorm.DB +} + +func NewTimeEntryDatasource(db *gorm.DB) persistence.TimeEntryDatasource { + return &TimeEntryDatasource{db: db} +} + +func (r *TimeEntryDatasource) Get(ctx context.Context, id ulid.ULID) (*entities.TimeEntry, error) { + var timeEntryDBO dbo.TimeEntryDBO + if err := r.db.WithContext(ctx).First(&timeEntryDBO, "id = ?", id).Error; err != nil { + return nil, err + } + + timeEntry := &entities.TimeEntry{ + EntityBase: entities.EntityBase{ + ID: timeEntryDBO.ID, + CreatedAt: timeEntryDBO.CreatedAt, + UpdatedAt: timeEntryDBO.UpdatedAt, + }, + UserID: timeEntryDBO.UserID, + ProjectID: timeEntryDBO.ProjectID, + ActivityID: timeEntryDBO.ActivityID, + Start: timeEntryDBO.Start, + End: timeEntryDBO.End, + Description: timeEntryDBO.Description, + Billable: timeEntryDBO.Billable, + } + + return timeEntry, nil +} + +func (r *TimeEntryDatasource) Create(ctx context.Context, timeEntry *entities.TimeEntry) error { + timeEntryDBO := dbo.TimeEntryDBO{ + ID: timeEntry.ID, + CreatedAt: timeEntry.CreatedAt, + UpdatedAt: timeEntry.UpdatedAt, + UserID: timeEntry.UserID, + ProjectID: timeEntry.ProjectID, + ActivityID: timeEntry.ActivityID, + Start: timeEntry.Start, + End: timeEntry.End, + Description: timeEntry.Description, + Billable: timeEntry.Billable, + } + + return r.db.WithContext(ctx).Create(&timeEntryDBO).Error +} + +func (r *TimeEntryDatasource) Update(ctx context.Context, timeEntry *entities.TimeEntry) error { + timeEntryDBO := dbo.TimeEntryDBO{ + ID: timeEntry.ID, + CreatedAt: timeEntry.CreatedAt, + UpdatedAt: timeEntry.UpdatedAt, + UserID: timeEntry.UserID, + ProjectID: timeEntry.ProjectID, + ActivityID: timeEntry.ActivityID, + Start: timeEntry.Start, + End: timeEntry.End, + Description: timeEntry.Description, + Billable: timeEntry.Billable, + } + + return r.db.WithContext(ctx).Save(&timeEntryDBO).Error +} + +func (r *TimeEntryDatasource) Delete(ctx context.Context, id ulid.ULID) error { + return r.db.WithContext(ctx).Delete(&dbo.TimeEntryDBO{}, "id = ?", id).Error +} + +func (r *TimeEntryDatasource) GetByRange(ctx context.Context, userID ulid.ULID, from time.Time, to time.Time) ([]*entities.TimeEntry, error) { + var timeEntryDBOs []*dbo.TimeEntryDBO + if err := r.db.WithContext(ctx).Where("user_id = ? AND start_time >= ? AND end_time <= ?", userID, from, to).Find(&timeEntryDBOs).Error; err != nil { + return nil, err + } + + timeEntries := make([]*entities.TimeEntry, len(timeEntryDBOs)) + for i, timeEntryDBO := range timeEntryDBOs { + timeEntries[i] = &entities.TimeEntry{ + EntityBase: entities.EntityBase{ + ID: timeEntryDBO.ID, + CreatedAt: timeEntryDBO.CreatedAt, + UpdatedAt: timeEntryDBO.UpdatedAt, + }, + UserID: timeEntryDBO.UserID, + ProjectID: timeEntryDBO.ProjectID, + ActivityID: timeEntryDBO.ActivityID, + Start: timeEntryDBO.Start, + End: timeEntryDBO.End, + Description: timeEntryDBO.Description, + Billable: timeEntryDBO.Billable, + } + } + + return timeEntries, nil +} diff --git a/backend/internal/infrastructure/persistence/db/ds/user_datasource.go b/backend/internal/infrastructure/persistence/db/ds/user_datasource.go new file mode 100644 index 0000000..096f60c --- /dev/null +++ b/backend/internal/infrastructure/persistence/db/ds/user_datasource.go @@ -0,0 +1,97 @@ +package ds + +import ( + "context" + + "github.com/oklog/ulid/v2" + "github.com/timetracker/backend/internal/domain/entities" + "github.com/timetracker/backend/internal/domain/persistence" + "github.com/timetracker/backend/internal/infrastructure/persistence/db/dbo" + "gorm.io/gorm" +) + +type UserDatasource struct { + db *gorm.DB +} + +func NewUserDatasource(db *gorm.DB) persistence.UserDatasource { + return &UserDatasource{db: db} +} + +func (r *UserDatasource) Get(ctx context.Context, id ulid.ULID) (*entities.User, error) { + var userDBO dbo.UserDBO + if err := r.db.WithContext(ctx).First(&userDBO, "id = ?", id).Error; err != nil { + return nil, err + } + + user := &entities.User{ + EntityBase: entities.EntityBase{ + ID: userDBO.ID, + CreatedAt: userDBO.CreatedAt, + UpdatedAt: userDBO.UpdatedAt, + }, + Username: userDBO.Username, + Password: userDBO.Password, + Role: userDBO.Role, + CompanyID: userDBO.CompanyID, + HourlyRate: userDBO.HourlyRate, + } + + return user, nil +} + +func (r *UserDatasource) Create(ctx context.Context, user *entities.User) error { + userDBO := dbo.UserDBO{ + ID: user.ID, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + Username: user.Username, + Password: user.Password, + Role: user.Role, + CompanyID: user.CompanyID, + HourlyRate: user.HourlyRate, + } + + return r.db.WithContext(ctx).Create(&userDBO).Error +} + +func (r *UserDatasource) Update(ctx context.Context, user *entities.User) error { + userDBO := dbo.UserDBO{ + ID: user.ID, + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + Username: user.Username, + Password: user.Password, + Role: user.Role, + CompanyID: user.CompanyID, + HourlyRate: user.HourlyRate, + } + + return r.db.WithContext(ctx).Save(&userDBO).Error +} + +func (r *UserDatasource) Delete(ctx context.Context, id ulid.ULID) error { + return r.db.WithContext(ctx).Delete(&dbo.UserDBO{}, "id = ?", id).Error +} + +func (r *UserDatasource) GetByUsername(ctx context.Context, username string) (*entities.User, error) { + var userDBO dbo.UserDBO + if err := r.db.WithContext(ctx).Where("username = ?", username).First(&userDBO).Error; err != nil { + return nil, err + } + + user := &entities.User{ + EntityBase: entities.EntityBase{ + ID: userDBO.ID, + CreatedAt: userDBO.CreatedAt, + UpdatedAt: userDBO.UpdatedAt, + }, + Username: userDBO.Username, + Password: userDBO.Password, + Role: userDBO.Role, + CompanyID: userDBO.CompanyID, + HourlyRate: userDBO.HourlyRate, + } + + return user, nil +}