diff --git a/internal/app/app.go b/internal/app/app.go
index 021b5f7..6c823bd 100644
--- a/internal/app/app.go
+++ b/internal/app/app.go
@@ -11,13 +11,14 @@ import (
)
type App struct {
- Cfg *config.Config
- DB *sqlx.DB
- UserService *service.UserService
- AuthService *service.AuthService
- EmailService *service.EmailService
- SpaceService *service.SpaceService
- InviteService *service.InviteService
+ Cfg *config.Config
+ DB *sqlx.DB
+ UserService *service.UserService
+ AuthService *service.AuthService
+ EmailService *service.EmailService
+ SpaceService *service.SpaceService
+ AccountService *service.AccountService
+ InviteService *service.InviteService
}
func New(cfg *config.Config) (*App, error) {
@@ -37,11 +38,13 @@ func New(cfg *config.Config) (*App, error) {
userRepository := repository.NewUserRepository(database)
tokenRepository := repository.NewTokenRepository(database)
spaceRepository := repository.NewSpaceRepository(database)
+ accountRepository := repository.NewAccountRepository(database)
invitationRepository := repository.NewInvitationRepository(database)
// Services
userService := service.NewUserService(userRepository)
spaceService := service.NewSpaceService(spaceRepository)
+ accountService := service.NewAccountService(accountRepository)
emailService := service.NewEmailService(
emailClient,
cfg.MailerEmailFrom,
@@ -54,6 +57,7 @@ func New(cfg *config.Config) (*App, error) {
userRepository,
tokenRepository,
spaceService,
+ accountService,
cfg.JWTSecret,
cfg.JWTExpiry,
cfg.TokenMagicLinkExpiry,
@@ -62,13 +66,14 @@ func New(cfg *config.Config) (*App, error) {
inviteService := service.NewInviteService(invitationRepository, spaceRepository, userRepository, emailService)
return &App{
- Cfg: cfg,
- DB: database,
- UserService: userService,
- AuthService: authService,
- EmailService: emailService,
- SpaceService: spaceService,
- InviteService: inviteService,
+ Cfg: cfg,
+ DB: database,
+ UserService: userService,
+ AuthService: authService,
+ EmailService: emailService,
+ SpaceService: spaceService,
+ AccountService: accountService,
+ InviteService: inviteService,
}, nil
}
diff --git a/internal/handler/auth.go b/internal/handler/auth.go
index de1ecad..4c1e342 100644
--- a/internal/handler/auth.go
+++ b/internal/handler/auth.go
@@ -168,13 +168,11 @@ func (h *authHandler) completeLogin(w http.ResponseWriter, r *http.Request, user
}
func (h *authHandler) OnboardingPage(w http.ResponseWriter, r *http.Request) {
- step := r.URL.Query().Get("step")
- switch step {
- case "2":
+ if r.URL.Query().Get("step") == "name" {
ui.Render(w, r, pages.OnboardingName(""))
- default:
- ui.Render(w, r, pages.OnboardingWelcome())
+ return
}
+ ui.Render(w, r, pages.OnboardingWelcome())
}
func (h *authHandler) CompleteOnboarding(w http.ResponseWriter, r *http.Request) {
@@ -184,50 +182,19 @@ func (h *authHandler) CompleteOnboarding(w http.ResponseWriter, r *http.Request)
return
}
- step := r.FormValue("step")
-
- switch step {
- case "2":
- name := strings.TrimSpace(r.FormValue("name"))
- if name == "" {
- ui.Render(w, r, pages.OnboardingName("Please enter your name"))
- return
- }
- ui.Render(w, r, pages.OnboardingSpace(name, ""))
-
- case "3":
- name := strings.TrimSpace(r.FormValue("name"))
- spaceName := strings.TrimSpace(r.FormValue("space_name"))
-
- if name == "" {
- ui.Render(w, r, pages.OnboardingName("Please enter your name"))
- return
- }
- if spaceName == "" {
- ui.Render(w, r, pages.OnboardingSpace(name, "Please enter a space name"))
- return
- }
-
- err := h.authService.CompleteOnboarding(user.ID, name)
- if err != nil {
- slog.Error("onboarding failed", "error", err, "user_id", user.ID)
- ui.Render(w, r, pages.OnboardingName("Please enter a valid name"))
- return
- }
-
- _, err = h.spaceService.CreateSpace(spaceName, user.ID)
- if err != nil {
- slog.Error("failed to create space during onboarding", "error", err, "user_id", user.ID)
- ui.Render(w, r, pages.OnboardingSpace(name, "Failed to create space. Please try again."))
- return
- }
-
- slog.Info("onboarding completed", "user_id", user.ID, "name", name, "space", spaceName)
- http.Redirect(w, r, "/app/dashboard", http.StatusSeeOther)
-
- default:
- ui.Render(w, r, pages.OnboardingWelcome())
+ name := strings.TrimSpace(r.FormValue("name"))
+ if name == "" {
+ ui.Render(w, r, pages.OnboardingName("Please enter your name"))
+ return
}
+
+ if err := h.authService.CompleteOnboarding(user.ID, name); err != nil {
+ slog.Error("onboarding failed", "error", err, "user_id", user.ID)
+ ui.Render(w, r, pages.OnboardingName("We couldn't finish setting you up. Please try again."))
+ return
+ }
+
+ http.Redirect(w, r, "/app/dashboard", http.StatusSeeOther)
}
func (h *authHandler) JoinSpace(w http.ResponseWriter, r *http.Request) {
diff --git a/internal/handler/auth_test.go b/internal/handler/auth_test.go
index 51d09f5..257dd1a 100644
--- a/internal/handler/auth_test.go
+++ b/internal/handler/auth_test.go
@@ -19,10 +19,12 @@ func newTestAuthHandler(dbi testutil.DBInfo) *authHandler {
userRepo := repository.NewUserRepository(dbi.DB)
tokenRepo := repository.NewTokenRepository(dbi.DB)
spaceRepo := repository.NewSpaceRepository(dbi.DB)
+ accountRepo := repository.NewAccountRepository(dbi.DB)
inviteRepo := repository.NewInvitationRepository(dbi.DB)
spaceSvc := service.NewSpaceService(spaceRepo)
+ accountSvc := service.NewAccountService(accountRepo)
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
- authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
+ authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
inviteSvc := service.NewInviteService(inviteRepo, spaceRepo, userRepo, emailSvc)
return NewAuthHandler(authSvc, inviteSvc, spaceSvc)
}
@@ -95,40 +97,57 @@ func TestAuthHandler_Logout(t *testing.T) {
})
}
-func TestAuthHandler_CompleteOnboarding_Step2(t *testing.T) {
+func TestAuthHandler_CompleteOnboarding_Success(t *testing.T) {
testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
h := newTestAuthHandler(dbi)
user := testutil.CreateTestUser(t, dbi.DB, "test@example.com", nil)
req := testutil.NewAuthenticatedRequest(t, http.MethodPost, "/auth/onboarding", user, url.Values{
- "step": {"2"},
"name": {"John"},
})
w := httptest.NewRecorder()
h.CompleteOnboarding(w, req)
- assert.Equal(t, http.StatusOK, w.Code)
- })
-}
-
-func TestAuthHandler_CompleteOnboarding_Step3(t *testing.T) {
- testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
- h := newTestAuthHandler(dbi)
-
- user := testutil.CreateTestUser(t, dbi.DB, "test@example.com", nil)
-
- req := testutil.NewAuthenticatedRequest(t, http.MethodPost, "/auth/onboarding", user, url.Values{
- "step": {"3"},
- "name": {"John"},
- "space_name": {"My Space"},
- })
-
- w := httptest.NewRecorder()
- h.CompleteOnboarding(w, req)
-
assert.Equal(t, http.StatusSeeOther, w.Code)
assert.Equal(t, "/app/dashboard", w.Header().Get("Location"))
+
+ // Space "John's Space" with a default account should now exist
+ spaceRepo := repository.NewSpaceRepository(dbi.DB)
+ spaces, err := spaceRepo.ByUserID(user.ID)
+ assert.NoError(t, err)
+ assert.Len(t, spaces, 1)
+ assert.Equal(t, "John's Space", spaces[0].Name)
+
+ accountRepo := repository.NewAccountRepository(dbi.DB)
+ accounts, err := accountRepo.BySpaceID(spaces[0].ID)
+ assert.NoError(t, err)
+ assert.Len(t, accounts, 1)
+ assert.Equal(t, service.DefaultAccountName, accounts[0].Name)
+ })
+}
+
+func TestAuthHandler_CompleteOnboarding_EmptyName(t *testing.T) {
+ testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
+ h := newTestAuthHandler(dbi)
+
+ user := testutil.CreateTestUser(t, dbi.DB, "empty@example.com", nil)
+
+ req := testutil.NewAuthenticatedRequest(t, http.MethodPost, "/auth/onboarding", user, url.Values{
+ "name": {" "},
+ })
+
+ w := httptest.NewRecorder()
+ h.CompleteOnboarding(w, req)
+
+ assert.Equal(t, http.StatusOK, w.Code)
+ assert.Contains(t, w.Body.String(), "Please enter your name")
+
+ // No space should have been created
+ spaceRepo := repository.NewSpaceRepository(dbi.DB)
+ spaces, err := spaceRepo.ByUserID(user.ID)
+ assert.NoError(t, err)
+ assert.Empty(t, spaces)
})
}
diff --git a/internal/handler/settings_test.go b/internal/handler/settings_test.go
index b0c6ac3..e72c49a 100644
--- a/internal/handler/settings_test.go
+++ b/internal/handler/settings_test.go
@@ -17,9 +17,11 @@ func newTestSettingsHandler(dbi testutil.DBInfo) (*settingsHandler, *service.Aut
userRepo := repository.NewUserRepository(dbi.DB)
tokenRepo := repository.NewTokenRepository(dbi.DB)
spaceRepo := repository.NewSpaceRepository(dbi.DB)
+ accountRepo := repository.NewAccountRepository(dbi.DB)
spaceSvc := service.NewSpaceService(spaceRepo)
+ accountSvc := service.NewAccountService(accountRepo)
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
- authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
+ authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
userSvc := service.NewUserService(userRepo)
return NewSettingsHandler(authSvc, userSvc), authSvc
}
diff --git a/internal/repository/account.go b/internal/repository/account.go
new file mode 100644
index 0000000..70354bf
--- /dev/null
+++ b/internal/repository/account.go
@@ -0,0 +1,59 @@
+package repository
+
+import (
+ "database/sql"
+ "errors"
+
+ "git.juancwu.dev/juancwu/budgit/internal/model"
+ "github.com/jmoiron/sqlx"
+)
+
+var ErrAccountNotFound = errors.New("account not found")
+
+type AccountRepository interface {
+ Create(account *model.Account) error
+ ByID(id string) (*model.Account, error)
+ BySpaceID(spaceID string) ([]*model.Account, error)
+ Delete(id string) error
+}
+
+type accountRepository struct {
+ db *sqlx.DB
+}
+
+func NewAccountRepository(db *sqlx.DB) AccountRepository {
+ return &accountRepository{db: db}
+}
+
+func (r *accountRepository) Create(account *model.Account) error {
+ query := `INSERT INTO accounts (id, name, space_id, created_at, updated_at)
+ VALUES ($1, $2, $3, $4, $5);`
+ _, err := r.db.Exec(query, account.ID, account.Name, account.SpaceID, account.CreatedAt, account.UpdatedAt)
+ return err
+}
+
+func (r *accountRepository) ByID(id string) (*model.Account, error) {
+ account := &model.Account{}
+ query := `SELECT * FROM accounts WHERE id = $1;`
+ err := r.db.Get(account, query, id)
+ if err == sql.ErrNoRows {
+ return nil, ErrAccountNotFound
+ }
+ return account, err
+}
+
+func (r *accountRepository) BySpaceID(spaceID string) ([]*model.Account, error) {
+ var accounts []*model.Account
+ query := `SELECT * FROM accounts WHERE space_id = $1 ORDER BY created_at ASC;`
+ err := r.db.Select(&accounts, query, spaceID)
+ if err != nil {
+ return nil, err
+ }
+ return accounts, nil
+}
+
+func (r *accountRepository) Delete(id string) error {
+ query := `DELETE FROM accounts WHERE id = $1;`
+ _, err := r.db.Exec(query, id)
+ return err
+}
diff --git a/internal/repository/account_test.go b/internal/repository/account_test.go
new file mode 100644
index 0000000..8fa2522
--- /dev/null
+++ b/internal/repository/account_test.go
@@ -0,0 +1,77 @@
+package repository
+
+import (
+ "testing"
+ "time"
+
+ "git.juancwu.dev/juancwu/budgit/internal/model"
+ "git.juancwu.dev/juancwu/budgit/internal/testutil"
+ "github.com/google/uuid"
+ "github.com/stretchr/testify/assert"
+ "github.com/stretchr/testify/require"
+)
+
+func TestAccountRepository_CreateAndRead(t *testing.T) {
+ testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
+ repo := NewAccountRepository(dbi.DB)
+
+ user := testutil.CreateTestUser(t, dbi.DB, "account-create@example.com", nil)
+ space := testutil.CreateTestSpace(t, dbi.DB, user.ID, "Space With Account")
+
+ now := time.Now()
+ account := &model.Account{
+ ID: uuid.NewString(),
+ Name: "Money Account",
+ SpaceID: space.ID,
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+
+ err := repo.Create(account)
+ require.NoError(t, err)
+
+ fetched, err := repo.ByID(account.ID)
+ require.NoError(t, err)
+ assert.Equal(t, "Money Account", fetched.Name)
+ assert.Equal(t, space.ID, fetched.SpaceID)
+
+ accounts, err := repo.BySpaceID(space.ID)
+ require.NoError(t, err)
+ require.Len(t, accounts, 1)
+ assert.Equal(t, account.ID, accounts[0].ID)
+ })
+}
+
+func TestAccountRepository_ByID_NotFound(t *testing.T) {
+ testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
+ repo := NewAccountRepository(dbi.DB)
+
+ _, err := repo.ByID(uuid.NewString())
+ assert.ErrorIs(t, err, ErrAccountNotFound)
+ })
+}
+
+func TestAccountRepository_Delete(t *testing.T) {
+ testutil.ForEachDB(t, func(t *testing.T, dbi testutil.DBInfo) {
+ repo := NewAccountRepository(dbi.DB)
+
+ user := testutil.CreateTestUser(t, dbi.DB, "account-delete@example.com", nil)
+ space := testutil.CreateTestSpace(t, dbi.DB, user.ID, "Delete Space")
+
+ now := time.Now()
+ account := &model.Account{
+ ID: uuid.NewString(),
+ Name: "To Delete",
+ SpaceID: space.ID,
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+ require.NoError(t, repo.Create(account))
+
+ err := repo.Delete(account.ID)
+ require.NoError(t, err)
+
+ _, err = repo.ByID(account.ID)
+ assert.ErrorIs(t, err, ErrAccountNotFound)
+ })
+}
diff --git a/internal/routes/routes_test.go b/internal/routes/routes_test.go
index ec9242c..740ddf4 100644
--- a/internal/routes/routes_test.go
+++ b/internal/routes/routes_test.go
@@ -21,22 +21,25 @@ func newTestApp(dbi testutil.DBInfo) *app.App {
userRepo := repository.NewUserRepository(dbi.DB)
tokenRepo := repository.NewTokenRepository(dbi.DB)
spaceRepo := repository.NewSpaceRepository(dbi.DB)
+ accountRepo := repository.NewAccountRepository(dbi.DB)
inviteRepo := repository.NewInvitationRepository(dbi.DB)
spaceSvc := service.NewSpaceService(spaceRepo)
+ accountSvc := service.NewAccountService(accountRepo)
emailSvc := service.NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
- authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
+ authSvc := service.NewAuthService(emailSvc, userRepo, tokenRepo, spaceSvc, accountSvc, cfg.JWTSecret, cfg.JWTExpiry, cfg.TokenMagicLinkExpiry, false)
userSvc := service.NewUserService(userRepo)
inviteSvc := service.NewInviteService(inviteRepo, spaceRepo, userRepo, emailSvc)
return &app.App{
- Cfg: cfg,
- DB: dbi.DB,
- UserService: userSvc,
- AuthService: authSvc,
- EmailService: emailSvc,
- SpaceService: spaceSvc,
- InviteService: inviteSvc,
+ Cfg: cfg,
+ DB: dbi.DB,
+ UserService: userSvc,
+ AuthService: authSvc,
+ EmailService: emailSvc,
+ SpaceService: spaceSvc,
+ AccountService: accountSvc,
+ InviteService: inviteSvc,
}
}
diff --git a/internal/service/account.go b/internal/service/account.go
new file mode 100644
index 0000000..0331bf1
--- /dev/null
+++ b/internal/service/account.go
@@ -0,0 +1,58 @@
+package service
+
+import (
+ "fmt"
+ "time"
+
+ "git.juancwu.dev/juancwu/budgit/internal/model"
+ "git.juancwu.dev/juancwu/budgit/internal/repository"
+ "github.com/google/uuid"
+)
+
+const DefaultAccountName = "Money Account"
+
+type AccountService struct {
+ accountRepo repository.AccountRepository
+}
+
+func NewAccountService(accountRepo repository.AccountRepository) *AccountService {
+ return &AccountService{accountRepo: accountRepo}
+}
+
+func (s *AccountService) CreateAccount(spaceID, name string) (*model.Account, error) {
+ if spaceID == "" {
+ return nil, fmt.Errorf("space id is required")
+ }
+ if name == "" {
+ return nil, fmt.Errorf("account name cannot be empty")
+ }
+
+ now := time.Now()
+ account := &model.Account{
+ ID: uuid.NewString(),
+ Name: name,
+ SpaceID: spaceID,
+ CreatedAt: now,
+ UpdatedAt: now,
+ }
+ if err := s.accountRepo.Create(account); err != nil {
+ return nil, fmt.Errorf("failed to create account: %w", err)
+ }
+ return account, nil
+}
+
+func (s *AccountService) GetAccount(id string) (*model.Account, error) {
+ account, err := s.accountRepo.ByID(id)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get account: %w", err)
+ }
+ return account, nil
+}
+
+func (s *AccountService) GetAccountsForSpace(spaceID string) ([]*model.Account, error) {
+ accounts, err := s.accountRepo.BySpaceID(spaceID)
+ if err != nil {
+ return nil, fmt.Errorf("failed to get accounts for space: %w", err)
+ }
+ return accounts, nil
+}
diff --git a/internal/service/auth.go b/internal/service/auth.go
index 4fc789f..a5d6ff6 100644
--- a/internal/service/auth.go
+++ b/internal/service/auth.go
@@ -36,6 +36,7 @@ type AuthService struct {
userRepository repository.UserRepository
tokenRepository repository.TokenRepository
spaceService *SpaceService
+ accountService *AccountService
jwtSecret string
jwtExpiry time.Duration
tokenMagicLinkExpiry time.Duration
@@ -47,20 +48,22 @@ func NewAuthService(
userRepository repository.UserRepository,
tokenRepository repository.TokenRepository,
spaceService *SpaceService,
+ accountService *AccountService,
jwtSecret string,
jwtExpiry time.Duration,
tokenMagicLinkExpiry time.Duration,
isProduction bool,
) *AuthService {
return &AuthService{
- emailService: emailService,
- userRepository: userRepository,
- tokenRepository: tokenRepository,
- spaceService: spaceService,
- jwtSecret: jwtSecret,
- jwtExpiry: jwtExpiry,
+ emailService: emailService,
+ userRepository: userRepository,
+ tokenRepository: tokenRepository,
+ spaceService: spaceService,
+ accountService: accountService,
+ jwtSecret: jwtSecret,
+ jwtExpiry: jwtExpiry,
tokenMagicLinkExpiry: tokenMagicLinkExpiry,
- isProduction: isProduction,
+ isProduction: isProduction,
}
}
@@ -331,12 +334,16 @@ func (s *AuthService) NeedsOnboarding(userID string) (bool, error) {
return user.Name == nil || *user.Name == "", nil
}
-// CompleteOnboarding sets the user's name during onboarding
+// CompleteOnboarding finalizes a user's onboarding by provisioning their first
+// space and its default account, then saving their display name.
+//
+// The user-name update happens LAST so that if any step fails partway through,
+// NeedsOnboarding still returns true and the user is routed back to retry.
+// A retry is idempotent: if the user already has a space, the provisioning
+// steps are skipped and only the name update runs.
func (s *AuthService) CompleteOnboarding(userID, name string) error {
name = strings.TrimSpace(name)
-
- err := validation.ValidateName(name)
- if err != nil {
+ if err := validation.ValidateName(name); err != nil {
return err
}
@@ -345,18 +352,38 @@ func (s *AuthService) CompleteOnboarding(userID, name string) error {
return fmt.Errorf("failed to get user: %w", err)
}
- user.Name = &name
- err = s.userRepository.Update(user)
+ existing, err := s.spaceService.GetSpacesForUser(userID)
if err != nil {
+ return fmt.Errorf("failed to check existing spaces: %w", err)
+ }
+
+ if len(existing) == 0 {
+ spaceName := name + "'s Space"
+ space, err := s.spaceService.CreateSpace(spaceName, userID)
+ if err != nil {
+ return fmt.Errorf("failed to create onboarding space: %w", err)
+ }
+
+ if _, err := s.accountService.CreateAccount(space.ID, DefaultAccountName); err != nil {
+ if delErr := s.spaceService.DeleteSpace(space.ID); delErr != nil {
+ slog.Error("failed to roll back space after account creation error",
+ "space_id", space.ID, "error", delErr)
+ }
+ return fmt.Errorf("failed to create default account: %w", err)
+ }
+ }
+
+ user.Name = &name
+ if err := s.userRepository.Update(user); err != nil {
return fmt.Errorf("failed to update user: %w", err)
}
- err = s.emailService.SendWelcomeEmail(user.Email, name)
- if err != nil {
+ if err := s.emailService.SendWelcomeEmail(user.Email, name); err != nil {
slog.Warn("failed to send welcome email", "error", err, "email", user.Email)
}
- slog.Info("onboarding completed", "user_id", user.ID, "name", name)
+ slog.Info("onboarding completed",
+ "user_id", user.ID, "name", name, "provisioned_space", len(existing) == 0)
return nil
}
diff --git a/internal/service/auth_test.go b/internal/service/auth_test.go
index efa7f90..d34fdd6 100644
--- a/internal/service/auth_test.go
+++ b/internal/service/auth_test.go
@@ -16,13 +16,16 @@ func newTestAuthService(dbi testutil.DBInfo) *AuthService {
userRepo := repository.NewUserRepository(dbi.DB)
tokenRepo := repository.NewTokenRepository(dbi.DB)
spaceRepo := repository.NewSpaceRepository(dbi.DB)
+ accountRepo := repository.NewAccountRepository(dbi.DB)
spaceSvc := NewSpaceService(spaceRepo)
+ accountSvc := NewAccountService(accountRepo)
emailSvc := NewEmailService(nil, "test@example.com", "http://localhost:9999", "Budgit Test", false)
return NewAuthService(
emailSvc,
userRepo,
tokenRepo,
spaceSvc,
+ accountSvc,
cfg.JWTSecret,
cfg.JWTExpiry,
cfg.TokenMagicLinkExpiry,
@@ -179,11 +182,46 @@ func TestAuthService_CompleteOnboarding(t *testing.T) {
err := svc.CompleteOnboarding(user.ID, "New Name")
require.NoError(t, err)
- // Verify user name was updated
+ // User name is updated
userRepo := repository.NewUserRepository(dbi.DB)
updated, err := userRepo.ByID(user.ID)
require.NoError(t, err)
- assert.NotNil(t, updated.Name)
+ require.NotNil(t, updated.Name)
assert.Equal(t, "New Name", *updated.Name)
+
+ // A space named "