Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ name: CI

on:
push:
branches: [main, dev]
branches: [main]
pull_request:
branches: [main, dev]

Expand Down
41 changes: 32 additions & 9 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
.PHONY: help lint test test-verbose test-coverage test-coverage-html test-clean
.PHONY:
help
lint
test-all
test-verbose
test-coverage
test-coverage-html
test-clean
install-pre-push-hook
uninstall-pre-push-hook

help:
@echo "Available commands:"
@echo " help - Show this help message"
@echo " lint - Run golangci-lint on the codebase"
@echo " test - Run all tests"
@echo " test-verbose - Run all tests with verbose output"
@echo " test-coverage - Run all tests with coverage report"
@echo " test-coverage-html - Run all tests and generate HTML coverage report"
@echo " test-clean - Clean test cache and run tests"
@echo " help - Show this help message"
@echo " lint - Run golangci-lint on the codebase"
@echo " test - Run all tests"
@echo " test-verbose - Run all tests with verbose output"
@echo " test-coverage - Run all tests with coverage report"
@echo " test-coverage-html - Run all tests and generate HTML coverage report"
@echo " test-clean - Clean test cache and run tests"
@echo " install-pre-push-hook - Install the pre-push git hook"
@echo " uninstall-pre-push-hook - Uninstall the pre-push git hook"

lint:
golangci-lint run ./...

test:
test-all:
@echo "Running all tests..."
go test ./test/...

Expand All @@ -34,3 +45,15 @@ test-coverage-html:
test-clean:
@echo "Cleaning test cache and running tests..."
go clean -testcache && go test -v ./test/...

install-pre-push-hook:
@echo "Installing pre-push git hook..."
@mkdir -p .git/hooks
@cp scripts/git-pre-push.sh .git/hooks/pre-push
@chmod +x .git/hooks/pre-push
@echo "Pre-push hook installed successfully!"

uninstall-pre-push-hook:
@echo "Uninstalling pre-push git hook..."
@rm -f .git/hooks/pre-push
@echo "Pre-push hook uninstalled successfully!"
55 changes: 29 additions & 26 deletions gorm_crud_repository.go → gorm_repository.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@ import (
"gorm.io/gorm"
)

// CRUDRepository defines a generic interface for basic CRUD operations on entities of type T.
// Repository defines a generic interface for basic CRUD operations on entities of type T.
// It provides standard database operations with context support and transaction awareness.
// The interface abstracts the underlying database implementation for easier testing and flexibility.
type CRUDRepository[T any] interface {
type Repository[T any] interface {
// Insert creates a new record in the database.
Insert(ctx context.Context, model T) (T, error)
// FindAll retrieves multiple records based on the specification.
Expand All @@ -34,30 +34,31 @@ type Specification[T any] struct {
Model T // Model with fields set for WHERE conditions
PreloadRelations []string // Relations to eager load
ForUpdate bool // Whether to use SELECT ... FOR UPDATE
DeletedFilter DeletedFilter
}

// NewCRUDRepository creates a new CRUD repository implementation using GORM.
// NewRepository creates a new CRUD repository implementation using GORM.
// The repository provides transaction-aware database operations for the specified entity type T.
func NewCRUDRepository[T any](db *gorm.DB) CRUDRepository[T] {
func NewRepository[T any](db *gorm.DB) Repository[T] {
var zero T
if reflect.TypeOf(zero).Kind() == reflect.Ptr {
panic("CRUDRepository does not support pointer types for T")
if typ := reflect.TypeOf(zero); typ != nil && typ.Kind() == reflect.Ptr {
panic("Repository does not support pointer types for T")
}
return &crudRepositoryGorm[T]{db}
return &gormRepository[T]{db}
}
Comment thread
itsLeonB marked this conversation as resolved.

type crudRepositoryGorm[T any] struct {
type gormRepository[T any] struct {
db *gorm.DB
}

func (cr *crudRepositoryGorm[T]) Insert(ctx context.Context, model T) (T, error) {
func (gr *gormRepository[T]) Insert(ctx context.Context, model T) (T, error) {
var zero T

if err := cr.checkZeroValue(model); err != nil {
if err := gr.checkZeroValue(model); err != nil {
return zero, err
}

db, err := cr.GetGormInstance(ctx)
db, err := gr.GetGormInstance(ctx)
if err != nil {
return zero, err
}
Expand All @@ -69,10 +70,10 @@ func (cr *crudRepositoryGorm[T]) Insert(ctx context.Context, model T) (T, error)
return model, nil
}

func (cr *crudRepositoryGorm[T]) FindAll(ctx context.Context, spec Specification[T]) ([]T, error) {
func (gr *gormRepository[T]) FindAll(ctx context.Context, spec Specification[T]) ([]T, error) {
var models []T

db, err := cr.GetGormInstance(ctx)
db, err := gr.GetGormInstance(ctx)
if err != nil {
return nil, err
}
Expand All @@ -82,6 +83,7 @@ func (cr *crudRepositoryGorm[T]) FindAll(ctx context.Context, spec Specification
DefaultOrder(),
PreloadRelations(spec.PreloadRelations),
ForUpdate(spec.ForUpdate),
spec.DeletedFilter.WhereDeleted(),
).
Find(&models).
Error
Expand All @@ -93,10 +95,10 @@ func (cr *crudRepositoryGorm[T]) FindAll(ctx context.Context, spec Specification
return models, nil
}

func (cr *crudRepositoryGorm[T]) FindFirst(ctx context.Context, spec Specification[T]) (T, error) {
func (gr *gormRepository[T]) FindFirst(ctx context.Context, spec Specification[T]) (T, error) {
var model T

db, err := cr.GetGormInstance(ctx)
db, err := gr.GetGormInstance(ctx)
if err != nil {
return model, err
}
Expand All @@ -106,6 +108,7 @@ func (cr *crudRepositoryGorm[T]) FindFirst(ctx context.Context, spec Specificati
DefaultOrder(),
PreloadRelations(spec.PreloadRelations),
ForUpdate(spec.ForUpdate),
spec.DeletedFilter.WhereDeleted(),
).
First(&model).
Error
Expand All @@ -120,14 +123,14 @@ func (cr *crudRepositoryGorm[T]) FindFirst(ctx context.Context, spec Specificati
return model, nil
}

func (cr *crudRepositoryGorm[T]) Update(ctx context.Context, model T) (T, error) {
func (gr *gormRepository[T]) Update(ctx context.Context, model T) (T, error) {
var zero T

if err := cr.checkZeroValue(model); err != nil {
if err := gr.checkZeroValue(model); err != nil {
return zero, err
}

db, err := cr.GetGormInstance(ctx)
db, err := gr.GetGormInstance(ctx)
if err != nil {
return zero, err
}
Expand All @@ -139,12 +142,12 @@ func (cr *crudRepositoryGorm[T]) Update(ctx context.Context, model T) (T, error)
return model, nil
}

func (cr *crudRepositoryGorm[T]) Delete(ctx context.Context, model T) error {
if err := cr.checkZeroValue(model); err != nil {
func (gr *gormRepository[T]) Delete(ctx context.Context, model T) error {
if err := gr.checkZeroValue(model); err != nil {
return err
}

db, err := cr.GetGormInstance(ctx)
db, err := gr.GetGormInstance(ctx)
if err != nil {
return err
}
Expand All @@ -156,12 +159,12 @@ func (cr *crudRepositoryGorm[T]) Delete(ctx context.Context, model T) error {
return nil
}

func (cr *crudRepositoryGorm[T]) BatchInsert(ctx context.Context, models []T) ([]T, error) {
func (gr *gormRepository[T]) BatchInsert(ctx context.Context, models []T) ([]T, error) {
if len(models) < 1 {
return nil, eris.Errorf("inserted models cannot be empty")
}

db, err := cr.GetGormInstance(ctx)
db, err := gr.GetGormInstance(ctx)
if err != nil {
return nil, err
}
Expand All @@ -173,15 +176,15 @@ func (cr *crudRepositoryGorm[T]) BatchInsert(ctx context.Context, models []T) ([
return models, nil
}

func (cr *crudRepositoryGorm[T]) checkZeroValue(model T) error {
func (gr *gormRepository[T]) checkZeroValue(model T) error {
if reflect.DeepEqual(model, *new(T)) {
return eris.New("model cannot be zero value")
}

return nil
}

func (cr *crudRepositoryGorm[T]) GetGormInstance(ctx context.Context) (*gorm.DB, error) {
func (gr *gormRepository[T]) GetGormInstance(ctx context.Context) (*gorm.DB, error) {
tx, err := GetTxFromContext(ctx)
if err != nil {
return nil, err
Expand All @@ -190,5 +193,5 @@ func (cr *crudRepositoryGorm[T]) GetGormInstance(ctx context.Context) (*gorm.DB,
return tx, nil
}

return cr.db.WithContext(ctx), nil
return gr.db.WithContext(ctx), nil
}
19 changes: 19 additions & 0 deletions gorm_scopes.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,3 +106,22 @@ func ForUpdate(enable bool) func(*gorm.DB) *gorm.DB {
return db
}
}

type DeletedFilter struct {
filterType internal.DeletedFilterType
}

func (df *DeletedFilter) WhereDeleted() func(*gorm.DB) *gorm.DB {
return func(d *gorm.DB) *gorm.DB {
if df.filterType == nil {
return d
}
return df.filterType.WhereDeleted()(d)
}
}

var (
ExcludeDeleted DeletedFilter = DeletedFilter{internal.ExcludeDeleted{}}
IncludeDeleted DeletedFilter = DeletedFilter{internal.IncludeDeleted{}}
OnlyDeleted DeletedFilter = DeletedFilter{internal.OnlyDeleted{}}
)
31 changes: 31 additions & 0 deletions internal/gorm_scopes.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package internal

import "gorm.io/gorm"

type DeletedFilterType interface {
WhereDeleted() func(*gorm.DB) *gorm.DB
}

type ExcludeDeleted struct{}

func (ed ExcludeDeleted) WhereDeleted() func(*gorm.DB) *gorm.DB {
return func(d *gorm.DB) *gorm.DB {
return d.Where("deleted_at IS NULL")
}
}

type IncludeDeleted struct{}

func (id IncludeDeleted) WhereDeleted() func(*gorm.DB) *gorm.DB {
return func(d *gorm.DB) *gorm.DB {
return d
}
}

type OnlyDeleted struct{}

func (od OnlyDeleted) WhereDeleted() func(*gorm.DB) *gorm.DB {
return func(d *gorm.DB) *gorm.DB {
return d.Where("deleted_at IS NOT NULL")
}
}
Comment thread
itsLeonB marked this conversation as resolved.
21 changes: 21 additions & 0 deletions scripts/git-pre-push.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#!/bin/sh
#
# Pre-push hook that runs linting and tests before allowing push

echo "Running pre-push checks..."

# Run linting
echo "\n=== Running linting ==="
if ! make lint; then
echo "❌ Linting failed! Please fix the issues before pushing."
exit 1
fi

# Run tests
echo "\n=== Running tests ==="
if ! make test-all; then
echo "❌ Tests failed! Please fix the test issues before pushing."
exit 1
fi

echo "\n✅ All checks passed! Pushing can continue...\n"
Loading
Loading