feat: added db transactions abstraction; cleaned up user registration; repos adapted new abstraction: all repos now support transactions, if present in ctx

This commit is contained in:
2026-09-01 14:55:20 +02:00
parent f87b0a0707
commit 2e3e82e776
14 changed files with 219 additions and 145 deletions
+11 -6
View File
@@ -4,7 +4,6 @@ import (
"context"
"errors"
"log/slog"
"slices"
"time"
)
@@ -36,7 +35,7 @@ type ListItem struct {
}
type ListRepository interface {
CreateList(ctx context.Context, name string, userIDs []string) (*List, error)
CreateList(ctx context.Context, name string) (*List, error)
DeleteList(ctx context.Context, listID string) error
GetLists(ctx context.Context, userID string) ([]List, error)
AddUserToList(ctx context.Context, listID string, userID string) error
@@ -58,16 +57,22 @@ func NewListService(r ListRepository) *ListService {
return &ListService{repo: r}
}
func (s *ListService) CreateList(ctx context.Context, authUserID string, name string, userIDs []string) (*List, error) {
func (s *ListService) CreateList(ctx context.Context, authUserID string, name string) (*List, error) {
if name == "" {
return nil, ErrListNameEmpty
}
if !slices.Contains(userIDs, authUserID) {
userIDs = append(userIDs, authUserID)
list, err := s.repo.CreateList(ctx, name)
if err != nil {
return nil, err
}
return s.repo.CreateList(ctx, name, userIDs)
err = s.repo.AddUserToList(ctx, list.ID, authUserID)
if err != nil {
return nil, err
}
return list, nil
}
func (s *ListService) DeleteList(ctx context.Context, authUserID string, listID string) error {
+49
View File
@@ -0,0 +1,49 @@
package domain
import (
"context"
"log/slog"
)
type RegistrationService struct {
tx Transactor
UserService *UserService
InviteService *InviteService
}
func NewRegistrationService(tx Transactor, u *UserService, i *InviteService) *RegistrationService {
return &RegistrationService{tx: tx, UserService: u, InviteService: i}
}
func (s *RegistrationService) Register(ctx context.Context, inviteCode string, email string, username string, password string) (*User, error) {
invite, err := s.InviteService.GetInvite(ctx, inviteCode)
if err != nil {
slog.ErrorContext(ctx, "failed to get invite", slog.Any("error", err))
return nil, err
}
var user *User
err = s.tx.WithinTx(ctx, func(ctx context.Context) error {
user, err = s.UserService.CreateUser(ctx, email, username, password)
if err != nil {
slog.ErrorContext(ctx, "failed to create user", slog.Any("error", err))
return err
}
err = s.InviteService.ConsumeInvite(ctx, invite.ID, user.ID)
if err != nil {
slog.ErrorContext(ctx, "failed to consume invite", slog.Any("error", err))
return err
}
return nil
})
if err != nil {
return nil, err
}
slog.InfoContext(ctx, "user registration complete",
slog.String("user_id", user.ID),
slog.String("invite_id", invite.ID))
return user, nil
}
+7
View File
@@ -0,0 +1,7 @@
package domain
import "context"
type Transactor interface {
WithinTx(ctx context.Context, fn func(ctx context.Context) error) error
}