feat: Add database object models and repositories for Activity, Company, Customer, Project, TimeEntry, and User with GORM integration

This commit is contained in:
2025-03-10 07:29:34 +00:00
parent 4dda83904a
commit 17cb4505be
28 changed files with 786 additions and 1 deletions
@@ -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")
}
@@ -0,0 +1,18 @@
package dbo
import (
"time"
"github.com/oklog/ulid/v2"
"gorm.io/gorm"
)
type ActivityDBO struct {
gorm.Model
ID ulid.ULID `gorm:"primaryKey;type:uuid"`
CreatedAt time.Time `gorm:"not null"`
UpdatedAt time.Time `gorm:"not null"`
LastEditorID ulid.ULID
Name string `gorm:"type:varchar(255);not null"`
BillingRate float64 `gorm:"type:decimal(10,2)"`
}
@@ -0,0 +1,17 @@
package dbo
import (
"time"
"github.com/oklog/ulid/v2"
"gorm.io/gorm"
)
type CompanyDBO struct {
gorm.Model
ID ulid.ULID `gorm:"primaryKey;type:uuid"`
CreatedAt time.Time `gorm:"not null"`
UpdatedAt time.Time `gorm:"not null"`
LastEditorID ulid.ULID
Name string `gorm:"type:varchar(255);not null"`
}
@@ -0,0 +1,18 @@
package dbo
import (
"time"
"github.com/oklog/ulid/v2"
"gorm.io/gorm"
)
type CustomerDBO struct {
gorm.Model
ID ulid.ULID `gorm:"primaryKey;type:uuid"`
CreatedAt time.Time `gorm:"not null"`
UpdatedAt time.Time `gorm:"not null"`
LastEditorID ulid.ULID
Name string `gorm:"type:varchar(255);not null"`
CompanyID int
}
@@ -0,0 +1,18 @@
package dbo
import (
"time"
"github.com/oklog/ulid/v2"
"gorm.io/gorm"
)
type ProjectDBO struct {
gorm.Model
ID ulid.ULID `gorm:"primaryKey;type:uuid"`
CreatedAt time.Time `gorm:"not null"`
UpdatedAt time.Time `gorm:"not null"`
LastEditorID ulid.ULID
Name string `gorm:"type:varchar(255);not null"`
CustomerID int
}
@@ -0,0 +1,23 @@
package dbo
import (
"time"
"github.com/oklog/ulid/v2"
"gorm.io/gorm"
)
type TimeEntryDBO struct {
gorm.Model
ID ulid.ULID `gorm:"primaryKey;type:uuid"`
CreatedAt time.Time `gorm:"not null"`
UpdatedAt time.Time `gorm:"not null"`
LastEditorID ulid.ULID
UserID int
ProjectID int
ActivityID int
Start time.Time
End time.Time
Description string
Billable int // Percentage (0-100)
}
@@ -0,0 +1,21 @@
package dbo
import (
"time"
"github.com/oklog/ulid/v2"
"gorm.io/gorm"
)
type UserDBO struct {
gorm.Model
ID ulid.ULID `gorm:"primaryKey;type:uuid"`
CreatedAt time.Time `gorm:"not null"`
UpdatedAt time.Time `gorm:"not null"`
LastEditorID ulid.ULID
Username string
Password string
Role string
CompanyID int
HourlyRate float64
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}
@@ -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
}