package users import ( "context" "errors" "github.com/google/uuid" "github.com/jackc/pgx/v5/pgconn" "gorm.io/gorm" ) var ErrNotFound = errors.New("user not found") var ErrEmailTaken = errors.New("email already in use") type Repository interface { Create(ctx context.Context, user *User) error FindByEmail(ctx context.Context, email string) (*User, error) FindByID(ctx context.Context, id uuid.UUID) (*User, error) List(ctx context.Context) ([]*User, error) Update(ctx context.Context, user *User) error Delete(ctx context.Context, id uuid.UUID) error } type gormRepository struct { db *gorm.DB } func NewRepository(db *gorm.DB) Repository { return &gormRepository{db: db} } func (r *gormRepository) Create(ctx context.Context, user *User) error { if err := r.db.WithContext(ctx).Create(user).Error; err != nil { if isUniqueViolation(err) { return ErrEmailTaken } return err } return nil } func (r *gormRepository) FindByEmail(ctx context.Context, email string) (*User, error) { var user User err := r.db.WithContext(ctx).Where("email = ?", email).First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrNotFound } if err != nil { return nil, err } return &user, nil } func (r *gormRepository) FindByID(ctx context.Context, id uuid.UUID) (*User, error) { var user User err := r.db.WithContext(ctx).Where("id = ?", id).First(&user).Error if errors.Is(err, gorm.ErrRecordNotFound) { return nil, ErrNotFound } if err != nil { return nil, err } return &user, nil } func (r *gormRepository) List(ctx context.Context) ([]*User, error) { var list []*User if err := r.db.WithContext(ctx).Order("created_at asc").Find(&list).Error; err != nil { return nil, err } return list, nil } func (r *gormRepository) Update(ctx context.Context, user *User) error { err := r.db.WithContext(ctx).Save(user).Error if isUniqueViolation(err) { return ErrEmailTaken } return err } func (r *gormRepository) Delete(ctx context.Context, id uuid.UUID) error { res := r.db.WithContext(ctx).Delete(&User{}, "id = ?", id) if res.Error != nil { return res.Error } if res.RowsAffected == 0 { return ErrNotFound } return nil } // isUniqueViolation reports whether err is a Postgres unique-constraint // violation (SQLSTATE 23505), e.g. a duplicate email. func isUniqueViolation(err error) bool { var pgErr *pgconn.PgError return errors.As(err, &pgErr) && pgErr.Code == "23505" }