diff --git a/apps/api/internal/service/workspace.go b/apps/api/internal/service/workspace.go index e8221de..898bd88 100644 --- a/apps/api/internal/service/workspace.go +++ b/apps/api/internal/service/workspace.go @@ -142,11 +142,9 @@ func (s *WorkspaceService) Create(ctx context.Context, name, slug, organizationS CreatedByID: &ownerID, OrganizationSize: orgSize, } - if err := s.ws.Create(ctx, w); err != nil { + if err := s.ws.CreateWithOwner(ctx, w, ownerID); err != nil { return nil, err } - m := &model.WorkspaceMember{WorkspaceID: w.ID, MemberID: ownerID, Role: 20} - _ = s.ws.AddMember(ctx, m) return w, nil } diff --git a/apps/api/internal/store/workspace.go b/apps/api/internal/store/workspace.go index d6dbad1..c356f4e 100644 --- a/apps/api/internal/store/workspace.go +++ b/apps/api/internal/store/workspace.go @@ -22,6 +22,23 @@ func (s *WorkspaceStore) Create(ctx context.Context, w *model.Workspace) error { return s.db.WithContext(ctx).Create(w).Error } +// CreateWithOwner inserts a workspace and its owner membership in one +// transaction. Without this, a failed membership insert would leave a workspace +// with no members, and since access is gated on membership the owner would be +// locked out of the workspace they just created. +func (s *WorkspaceStore) CreateWithOwner(ctx context.Context, w *model.Workspace, ownerID uuid.UUID) error { + return s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error { + if err := tx.Create(w).Error; err != nil { + return err + } + return tx.Create(&model.WorkspaceMember{ + WorkspaceID: w.ID, + MemberID: ownerID, + Role: model.RoleOwner, + }).Error + }) +} + func (s *WorkspaceStore) GetByID(ctx context.Context, id uuid.UUID) (*model.Workspace, error) { var w model.Workspace err := s.db.WithContext(ctx).Where("id = ? AND deleted_at IS NULL", id).First(&w).Error