feat: Update database models and DTOs to use bytea for ULIDWrapper and add JWT configuration to environment
This commit is contained in:
@@ -3,7 +3,6 @@ package models
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
|
||||
"github.com/oklog/ulid/v2"
|
||||
@@ -11,7 +10,7 @@ import (
|
||||
)
|
||||
|
||||
type EntityBase struct {
|
||||
ID ULIDWrapper `gorm:"type:char(26);primaryKey"`
|
||||
ID ULIDWrapper `gorm:"type:bytea;primaryKey"`
|
||||
CreatedAt time.Time `gorm:"index"`
|
||||
UpdatedAt time.Time `gorm:"index"`
|
||||
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||
@@ -19,9 +18,6 @@ type EntityBase struct {
|
||||
|
||||
// BeforeCreate is called by GORM before creating a record
|
||||
func (eb *EntityBase) BeforeCreate(tx *gorm.DB) error {
|
||||
fmt.Println("BeforeCreate called")
|
||||
stack := debug.Stack()
|
||||
fmt.Println("foo's stack:", string(stack))
|
||||
if eb.ID.Compare(ULIDWrapper{}) == 0 { // If ID is empty
|
||||
// Generate a new ULID
|
||||
entropy := ulid.Monotonic(rand.New(rand.NewSource(time.Now().UnixNano())), 0)
|
||||
|
||||
@@ -10,8 +10,8 @@ import (
|
||||
// Customer represents a customer in the system
|
||||
type Customer struct {
|
||||
EntityBase
|
||||
Name string `gorm:"column:name"`
|
||||
CompanyID int `gorm:"column:company_id"`
|
||||
Name string `gorm:"column:name"`
|
||||
CompanyID ULIDWrapper `gorm:"type:bytea;column:company_id"`
|
||||
}
|
||||
|
||||
// TableName specifies the table name for GORM
|
||||
@@ -22,14 +22,14 @@ func (Customer) TableName() string {
|
||||
// CustomerCreate contains the fields for creating a new customer
|
||||
type CustomerCreate struct {
|
||||
Name string
|
||||
CompanyID int
|
||||
CompanyID ULIDWrapper
|
||||
}
|
||||
|
||||
// CustomerUpdate contains the updatable fields of a customer
|
||||
type CustomerUpdate struct {
|
||||
ID ULIDWrapper `gorm:"-"` // Exclude from updates
|
||||
Name *string `gorm:"column:name"`
|
||||
CompanyID *int `gorm:"column:company_id"`
|
||||
ID ULIDWrapper `gorm:"-"` // Exclude from updates
|
||||
Name *string `gorm:"column:name"`
|
||||
CompanyID *ULIDWrapper `gorm:"column:company_id"`
|
||||
}
|
||||
|
||||
// GetCustomerByID finds a customer by its ID
|
||||
|
||||
@@ -141,6 +141,31 @@ func CloseDB() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetGormDB(dbConfig DatabaseConfig, dbName string) (*gorm.DB, error) {
|
||||
dsn := fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=%s",
|
||||
dbConfig.Host, dbConfig.Port, dbConfig.User, dbConfig.Password, dbName, dbConfig.SSLMode)
|
||||
|
||||
// Configure GORM logger
|
||||
gormLogger := logger.New(
|
||||
log.New(log.Writer(), "\r\n", log.LstdFlags), // io writer
|
||||
logger.Config{
|
||||
SlowThreshold: 200 * time.Millisecond, // Slow SQL threshold
|
||||
LogLevel: dbConfig.LogLevel, // Log level
|
||||
IgnoreRecordNotFoundError: true, // Ignore ErrRecordNotFound error for logger
|
||||
Colorful: true, // Enable color
|
||||
},
|
||||
)
|
||||
|
||||
db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
|
||||
Logger: gormLogger,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("error connecting to the database: %w", err)
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
||||
// UpdateModel updates a model based on the set pointer fields
|
||||
func UpdateModel(ctx context.Context, model any, updates any) error {
|
||||
updateValue := reflect.ValueOf(updates)
|
||||
|
||||
@@ -13,7 +13,7 @@ import (
|
||||
type Project struct {
|
||||
EntityBase
|
||||
Name string `gorm:"column:name;not null"`
|
||||
CustomerID ULIDWrapper `gorm:"column:customer_id;type:char(26);not null"`
|
||||
CustomerID ULIDWrapper `gorm:"column:customer_id;type:bytea;not null"`
|
||||
|
||||
// Relationships (for Eager Loading)
|
||||
Customer *Customer `gorm:"foreignKey:CustomerID"`
|
||||
|
||||
@@ -12,9 +12,9 @@ import (
|
||||
// TimeEntry represents a time entry in the system
|
||||
type TimeEntry struct {
|
||||
EntityBase
|
||||
UserID ULIDWrapper `gorm:"column:user_id;type:char(26);not null;index"`
|
||||
ProjectID ULIDWrapper `gorm:"column:project_id;type:char(26);not null;index"`
|
||||
ActivityID ULIDWrapper `gorm:"column:activity_id;type:char(26);not null;index"`
|
||||
UserID ULIDWrapper `gorm:"column:user_id;type:bytea;not null;index"`
|
||||
ProjectID ULIDWrapper `gorm:"column:project_id;type:bytea;not null;index"`
|
||||
ActivityID ULIDWrapper `gorm:"column:activity_id;type:bytea;not null;index"`
|
||||
Start time.Time `gorm:"column:start;not null"`
|
||||
End time.Time `gorm:"column:end;not null"`
|
||||
Description string `gorm:"column:description"`
|
||||
|
||||
@@ -10,14 +10,14 @@ import (
|
||||
"gorm.io/gorm/clause"
|
||||
)
|
||||
|
||||
// ULIDWrapper wraps ulid.ULID to allow method definitions
|
||||
// ULIDWrapper wraps ulid.ULID to make it work nicely with GORM
|
||||
type ULIDWrapper struct {
|
||||
ulid.ULID
|
||||
}
|
||||
|
||||
// Compare implements the same comparison method as ulid.ULID
|
||||
func (u ULIDWrapper) Compare(other ULIDWrapper) int {
|
||||
return u.ULID.Compare(other.ULID)
|
||||
// NewULIDWrapper creates a new ULIDWrapper with a new ULID
|
||||
func NewULIDWrapper() ULIDWrapper {
|
||||
return ULIDWrapper{ULID: ulid.Make()}
|
||||
}
|
||||
|
||||
// FromULID creates a ULIDWrapper from a ulid.ULID
|
||||
@@ -25,42 +25,30 @@ func FromULID(id ulid.ULID) ULIDWrapper {
|
||||
return ULIDWrapper{ULID: id}
|
||||
}
|
||||
|
||||
// From String creates a ULIDWrapper from a string
|
||||
// ULIDWrapperFromString creates a ULIDWrapper from a string
|
||||
func ULIDWrapperFromString(id string) (ULIDWrapper, error) {
|
||||
parsed, err := ulid.Parse(id)
|
||||
if err != nil {
|
||||
return ULIDWrapper{}, fmt.Errorf("failed to parse ULID string: %w", err)
|
||||
}
|
||||
return FromULID(parsed), nil
|
||||
return ULIDWrapper{ULID: parsed}, nil
|
||||
}
|
||||
|
||||
// ToULID converts a ULIDWrapper to a ulid.ULID
|
||||
func (u ULIDWrapper) ToULID() ulid.ULID {
|
||||
return u.ULID
|
||||
}
|
||||
|
||||
// GormValue implements the gorm.Valuer interface for ULIDWrapper
|
||||
func (u ULIDWrapper) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
|
||||
return clause.Expr{
|
||||
SQL: "?",
|
||||
Vars: []any{u.String()},
|
||||
}
|
||||
}
|
||||
|
||||
// Scan implements the Scanner interface for ULIDWrapper
|
||||
// Scan implements the sql.Scanner interface for ULIDWrapper
|
||||
func (u *ULIDWrapper) Scan(src any) error {
|
||||
switch v := src.(type) {
|
||||
case []byte:
|
||||
// If it's exactly 16 bytes, it's the binary representation
|
||||
if len(v) == 16 {
|
||||
copy(u.ULID[:], v)
|
||||
return nil
|
||||
}
|
||||
// Otherwise, try as string
|
||||
return fmt.Errorf("cannot scan []byte of length %d into ULIDWrapper", len(v))
|
||||
case string:
|
||||
parsed, err := ulid.Parse(v)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse ULID string: %w", err)
|
||||
}
|
||||
u.ULID = parsed
|
||||
return nil
|
||||
case []byte:
|
||||
parsed, err := ulid.Parse(string(v))
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse ULID bytes: %w", err)
|
||||
return fmt.Errorf("failed to parse ULID: %w", err)
|
||||
}
|
||||
u.ULID = parsed
|
||||
return nil
|
||||
@@ -70,6 +58,20 @@ func (u *ULIDWrapper) Scan(src any) error {
|
||||
}
|
||||
|
||||
// Value implements the driver.Valuer interface for ULIDWrapper
|
||||
// Returns the binary representation of the ULID for maximum efficiency
|
||||
func (u ULIDWrapper) Value() (driver.Value, error) {
|
||||
return u.String(), nil
|
||||
return u.ULID.Bytes(), nil
|
||||
}
|
||||
|
||||
// GormValue implements the gorm.Valuer interface for ULIDWrapper
|
||||
func (u ULIDWrapper) GormValue(ctx context.Context, db *gorm.DB) clause.Expr {
|
||||
return clause.Expr{
|
||||
SQL: "?",
|
||||
Vars: []any{u.Bytes()},
|
||||
}
|
||||
}
|
||||
|
||||
// Compare implements comparison for ULIDWrapper
|
||||
func (u ULIDWrapper) Compare(other ULIDWrapper) int {
|
||||
return u.ULID.Compare(other.ULID)
|
||||
}
|
||||
|
||||
@@ -39,7 +39,7 @@ type User struct {
|
||||
Salt string `gorm:"column:salt;not null;type:varchar(64)"` // Base64-encoded Salt
|
||||
Hash string `gorm:"column:hash;not null;type:varchar(128)"` // Base64-encoded Hash
|
||||
Role string `gorm:"column:role;not null;default:'user'"`
|
||||
CompanyID ULIDWrapper `gorm:"column:company_id;type:char(26);not null;index"`
|
||||
CompanyID ULIDWrapper `gorm:"column:company_id;type:bytea;not null;index"`
|
||||
HourlyRate float64 `gorm:"column:hourly_rate;not null;default:0"`
|
||||
|
||||
// Relationship for Eager Loading
|
||||
|
||||
Reference in New Issue
Block a user