Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
6e2e15c
feat: delete AddOrganizationUsers RPC — replaced by AddOrganizationMe…
whoAbhishekSah Apr 17, 2026
7fd4d07
feat: migrate org.Create and AdminCreate to use membership.AddOrganiz…
whoAbhishekSah Apr 17, 2026
b058175
feat: migrate invitation.Accept to use membership.AddOrganizationMember
whoAbhishekSah Apr 17, 2026
89fcbaf
feat: migrate domain.Service to use membership.AddOrganizationMember
whoAbhishekSah Apr 17, 2026
61bc2d3
chore: regenerate mocks after removing AddMember/AddUsers interfaces
whoAbhishekSah Apr 17, 2026
ea1fc5e
fix: remove unused mapPrincipalTypeToAuditType after AddMember deletion
whoAbhishekSah Apr 17, 2026
431d0be
docs: clarify why AddOrganizationMember is used instead of SetOrganiz…
whoAbhishekSah Apr 17, 2026
cfac38b
refactor: remove redundant ListByUser check in domain auto-join
whoAbhishekSah Apr 17, 2026
9b789c1
feat: use invitation's role when accepting, remove raw policy CRUD fr…
whoAbhishekSah Apr 20, 2026
c25471a
fix: simplify future multi-role comment in invitation accept
whoAbhishekSah Apr 20, 2026
5d3a3a0
fix: simplify invitation role comment
whoAbhishekSah Apr 20, 2026
de7269a
fix: reject service user principals from creating organizations
whoAbhishekSah Apr 20, 2026
183aaa2
test: migrate e2e tests from AddOrganizationUsers to AddOrganizationM…
whoAbhishekSah Apr 20, 2026
bfa733c
fix: remove redundant orgService.Get and add missing error handling i…
whoAbhishekSah Apr 20, 2026
694d07d
test: remove stale orgService.Get mocks from JoinOrganization and Acc…
whoAbhishekSah Apr 20, 2026
6d7f822
fix: create org as enabled during bootstrap, disable after membership…
whoAbhishekSah Apr 20, 2026
96ef0fe
chore: remove stale orgService.Get comments
whoAbhishekSah Apr 20, 2026
c6d5e3b
test: update disabled org creation tests to expect success with disab…
whoAbhishekSah Apr 20, 2026
708f25f
chore: add comment explaining circular dependency setter injection
whoAbhishekSah Apr 20, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 6 additions & 3 deletions cmd/serve.go
Original file line number Diff line number Diff line change
Expand Up @@ -432,6 +432,9 @@ func buildAPIDependencies(
authnService, policyService, preferenceService, auditRecordRepository, roleService)

membershipService := membership.NewService(logger, policyService, relationService, roleService, organizationService, userService, auditRecordRepository)
// Setter injection: org → membership is circular (membership needs org for validation,
// org needs membership for Create/AdminCreate). Break the cycle with a post-init setter.
organizationService.SetMembershipService(membershipService)

orgKycRepository := postgres.NewOrgKycRepository(dbc)
orgKycService := kyc.NewService(orgKycRepository)
Expand Down Expand Up @@ -467,7 +470,7 @@ func buildAPIDependencies(
userProjectsService := userprojects.NewService(userProjectsRepository)

domainRepository := postgres.NewDomainRepository(logger, dbc)
domainService := domain.NewService(logger, domainRepository, userService, organizationService)
domainService := domain.NewService(logger, domainRepository, userService, organizationService, membershipService)

metaschemaRepository := postgres.NewMetaSchemaRepository(logger, dbc)
metaschemaService := metaschema.NewService(metaschemaRepository)
Expand Down Expand Up @@ -495,8 +498,8 @@ func buildAPIDependencies(
)

invitationService := invitation.NewService(mailDialer, postgres.NewInvitationRepository(logger, dbc),
organizationService, groupService, userService, relationService, policyService, preferenceService,
auditRecordRepository)
organizationService, groupService, userService, relationService, preferenceService,
auditRecordRepository, membershipService)

if GetStripeClientFunc == nil {
// allow to override the stripe client creation function in tests
Expand Down
56 changes: 24 additions & 32 deletions core/domain/service.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,13 @@ import (
"context"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"net"
"strings"
"time"

"github.com/raystack/frontier/core/membership"
"github.com/raystack/frontier/core/organization"
"github.com/raystack/frontier/pkg/utils"

Expand All @@ -25,17 +27,21 @@ type UserService interface {
}

type OrgService interface {
ListByUser(ctx context.Context, principal authenticate.Principal, filter organization.Filter) ([]organization.Organization, error)
AddMember(ctx context.Context, orgID, relationName string, principal authenticate.Principal) error
Get(ctx context.Context, id string) (organization.Organization, error)
ListByUser(ctx context.Context, principal authenticate.Principal, filter organization.Filter) ([]organization.Organization, error)
}

type MembershipService interface {
AddOrganizationMember(ctx context.Context, orgID, principalID, principalType, roleID string) error
}

type Service struct {
repository Repository
userService UserService
orgService OrgService
cron *cron.Cron
log log.Logger
repository Repository
userService UserService
orgService OrgService
membershipService MembershipService
cron *cron.Cron
log log.Logger
}

const (
Expand All @@ -45,13 +51,14 @@ const (
refreshTime = "0 0 * * *" // Once a day at midnight (UTC)
)

func NewService(logger log.Logger, repository Repository, userService UserService, orgService OrgService) *Service {
func NewService(logger log.Logger, repository Repository, userService UserService, orgService OrgService, membershipService MembershipService) *Service {
return &Service{
repository: repository,
userService: userService,
orgService: orgService,
cron: cron.New(),
log: logger,
repository: repository,
userService: userService,
orgService: orgService,
membershipService: membershipService,
cron: cron.New(),
log: logger,
}
}

Expand Down Expand Up @@ -133,21 +140,6 @@ func (s Service) Join(ctx context.Context, orgID string, userId string) error {
return err
}

// check if user is already a member of the organization. if yes, do nothing and return nil
userOrgs, err := s.orgService.ListByUser(ctx, authenticate.Principal{
ID: currUser.ID,
Type: schema.UserPrincipal,
}, organization.Filter{})
if err != nil {
return err
}

for _, org := range userOrgs {
if org.ID == orgResp.ID {
return nil
}
}

userDomain := utils.ExtractDomainFromEmail(currUser.Email)
if userDomain == "" {
return user.ErrInvalidEmail
Expand All @@ -164,10 +156,10 @@ func (s Service) Join(ctx context.Context, orgID string, userId string) error {

for _, dmn := range orgTrustedDomains {
if userDomain == dmn.Name {
if err = s.orgService.AddMember(ctx, orgResp.ID, schema.MemberRelationName, authenticate.Principal{
ID: currUser.ID,
Type: schema.UserPrincipal,
}); err != nil {
// AddOrganizationMember checks if user is already a member and
// returns ErrAlreadyMember — treat that as success (nothing to do)
err = s.membershipService.AddOrganizationMember(ctx, orgResp.ID, currUser.ID, schema.UserPrincipal, schema.RoleOrganizationViewer)
if err != nil && !errors.Is(err, membership.ErrAlreadyMember) {
return err
}
return nil
Expand Down
86 changes: 86 additions & 0 deletions core/invitation/mocks/membership_service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 0 additions & 49 deletions core/invitation/mocks/organization_service.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading
Loading