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
+1 -1
View File
@@ -47,7 +47,7 @@ func (h *ListHandler) CreateList(w http.ResponseWriter, r *http.Request) {
return
}
list, err := h.ListService.CreateList(ctx, authContext.UserID, payload.Name, payload.UserIDs)
list, err := h.ListService.CreateList(ctx, authContext.UserID, payload.Name)
if err != nil {
slog.ErrorContext(ctx, "failed to create list", slog.Any("error", err))
response.Error(ctx, w, http.StatusInternalServerError, "failed to create list")
+10 -34
View File
@@ -10,13 +10,13 @@ import (
)
type UserHandler struct {
UserService *domain.UserService
AuthService *domain.AuthService
InviteService *domain.InviteService
UserService *domain.UserService
AuthService *domain.AuthService
RegistrationService *domain.RegistrationService
}
func NewUserHandler(userService *domain.UserService, authService *domain.AuthService, inviteService *domain.InviteService) *UserHandler {
return &UserHandler{UserService: userService, AuthService: authService, InviteService: inviteService}
func NewUserHandler(userService *domain.UserService, authService *domain.AuthService, registrationService *domain.RegistrationService) *UserHandler {
return &UserHandler{UserService: userService, AuthService: authService, RegistrationService: registrationService}
}
// CreateUser handles the creation of a user
@@ -42,41 +42,17 @@ func (h *UserHandler) CreateUser(w http.ResponseWriter, r *http.Request) {
return
}
invite, err := h.InviteService.GetInvite(ctx, payload.InviteCode)
user, err := h.RegistrationService.Register(ctx, payload.InviteCode, payload.Email, payload.Name, payload.Password)
if err != nil {
slog.ErrorContext(ctx, "failed to get invite", slog.Any("error", err))
response.Error(ctx, w, http.StatusBadRequest, "invite is invalid")
return
}
// TODO: Creating user and consuming the invite must be in a transaction.
// The repositories must support tx in context, and the handler must be able to start a transaction
user, err := h.UserService.CreateUser(ctx, payload.Email, payload.Name, payload.Password)
if err != nil {
slog.ErrorContext(ctx, "failed to create user", slog.Any("error", err))
response.Error(ctx, w, http.StatusInternalServerError, "failed to create user")
return
}
err = h.InviteService.ConsumeInvite(ctx, invite.ID, user.ID)
if err != nil {
slog.ErrorContext(ctx, "failed to consume invite", slog.Any("error", err))
// TODO: This should be a transaction rollback, once we have db transactions in the handler
err = h.UserService.DeleteUser(ctx, user.ID)
if err != nil {
slog.ErrorContext(ctx, "failed to delete user again",
slog.Any("error", err),
slog.String("user_id", user.ID),
)
}
response.Error(ctx, w, http.StatusInternalServerError, "failed to consume invite")
slog.ErrorContext(ctx, "failed to register user",
slog.Any("error", err),
slog.Any("payload", payload))
response.Error(ctx, w, http.StatusInternalServerError, "failed to register")
return
}
slog.InfoContext(ctx, "created user successfully",
slog.String("user_id", user.ID),
slog.String("invite_id", invite.ID),
)
response.JSON(ctx, w, http.StatusCreated, user)
}
+1 -2
View File
@@ -1,8 +1,7 @@
package request
type CreateListPayload struct {
Name string `json:"name"`
UserIDs []string `json:"user_ids"`
Name string `json:"name"`
}
type AddUserToListPayload struct {
+9 -12
View File
@@ -16,20 +16,17 @@ type Config struct {
}
func NewMux(cfg Config) http.Handler {
authRepo := repository.NewAuthRepo(cfg.Database)
authService := domain.NewAuthService(authRepo, []byte(cfg.JWTSecret))
store := repository.NewStore(cfg.Database)
authService := domain.NewAuthService(store.Auth, []byte(cfg.JWTSecret))
inviteService := domain.NewInviteService(store.Invite)
userService := domain.NewUserService(store.User)
registrationService := domain.NewRegistrationService(store, userService, inviteService)
listService := domain.NewListService(store.List)
authHandler := handler.NewAuthHandler(authService)
inviteRepo := repository.NewInviteRepo(cfg.Database)
inviteService := domain.NewInviteService(inviteRepo)
inviteHandler := handler.NewInviteHandler(inviteService)
userRepo := repository.NewUserRepo(cfg.Database)
userService := domain.NewUserService(userRepo)
userHandler := handler.NewUserHandler(userService, authService, inviteService)
listRepo := repository.NewListRepo(cfg.Database)
listService := domain.NewListService(listRepo)
userHandler := handler.NewUserHandler(userService, authService, registrationService)
listHandler := handler.NewListHandler(listService, userService)
protected := middleware.WithJWT(authService)