Scope sub-third-parties per parent
Replace the many-to-many junction table with a direct parent_third_party_id foreign key on third_parties. Each sub-third-party now belongs to exactly one parent, making duplicates across parents independent entities. Replace the firstLevel boolean with an integer level field (1 = direct, 2+ = parent level + 1) to support arbitrary nesting depth. Remove the createThirdPartyThirdPartyMapping and deleteThirdPartyThirdPartyMapping mutations, the CLI link/unlink commands, and the corresponding MCP tools. Creating a child third party now just requires passing parentThirdPartyId on the existing createThirdParty mutation. The frontend walks the parentThirdParty chain to build display names like "Name (Ancestor1/Ancestor2)" and shows clickable ancestor links on the detail page. Signed-off-by: Sacha Al Himdani <sacha@getprobo.com>
This commit is contained in:
@@ -33,6 +33,12 @@ func (r *mutationResolver) CreateThirdParty(ctx context.Context, input types.Cre
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if input.ParentThirdPartyID != nil {
|
||||
if _, err := r.authorize(ctx, *input.ParentThirdPartyID, probo.ActionThirdPartyRelationCreate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
|
||||
thirdParty, err := r.probo.ThirdParties.Create(
|
||||
ctx, scope,
|
||||
probo.CreateThirdPartyRequest{
|
||||
@@ -56,7 +62,7 @@ func (r *mutationResolver) CreateThirdParty(ctx context.Context, input types.Cre
|
||||
BusinessOwnerID: input.BusinessOwnerID,
|
||||
SecurityOwnerID: input.SecurityOwnerID,
|
||||
Countries: input.Countries,
|
||||
FirstLevel: input.FirstLevel,
|
||||
ParentThirdPartyID: input.ParentThirdPartyID,
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
@@ -108,7 +114,6 @@ func (r *mutationResolver) UpdateThirdParty(ctx context.Context, input types.Upd
|
||||
BusinessOwnerID: gqlutils.UnwrapOmittable(input.BusinessOwnerID),
|
||||
SecurityOwnerID: gqlutils.UnwrapOmittable(input.SecurityOwnerID),
|
||||
ShowOnTrustCenter: input.ShowOnTrustCenter,
|
||||
FirstLevel: input.FirstLevel,
|
||||
Countries: input.Countries,
|
||||
},
|
||||
)
|
||||
@@ -603,46 +608,6 @@ func (r *mutationResolver) PublishThirdPartyList(ctx context.Context, input type
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateThirdPartyThirdPartyMapping is the resolver for the linkThirdPartyThirdParty field.
|
||||
func (r *mutationResolver) CreateThirdPartyThirdPartyMapping(ctx context.Context, input types.CreateThirdPartyThirdPartyMappingInput) (*types.CreateThirdPartyThirdPartyMappingPayload, error) {
|
||||
scope, err := r.authorize(ctx, input.ParentThirdPartyID, probo.ActionThirdPartyRelationCreate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
childThirdParty, err := r.probo.ThirdParties.CreateThirdPartyMapping(ctx, scope, input.ParentThirdPartyID, input.ChildThirdPartyID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot create third party mapping", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.CreateThirdPartyThirdPartyMappingPayload{
|
||||
ThirdPartyEdge: types.NewThirdPartyEdge(childThirdParty, coredata.ThirdPartyOrderFieldName),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// DeleteThirdPartyThirdPartyMapping is the resolver for the deleteThirdPartyThirdPartyMapping field.
|
||||
func (r *mutationResolver) DeleteThirdPartyThirdPartyMapping(ctx context.Context, input types.DeleteThirdPartyThirdPartyMappingInput) (*types.DeleteThirdPartyThirdPartyMappingPayload, error) {
|
||||
scope, err := r.authorize(ctx, input.ParentThirdPartyID, probo.ActionThirdPartyRelationDelete)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := r.probo.ThirdParties.DeleteThirdPartyMapping(ctx, scope, input.ParentThirdPartyID, input.ChildThirdPartyID); err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot delete third party mapping", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return &types.DeleteThirdPartyThirdPartyMappingPayload{
|
||||
RemovedThirdPartyID: input.ChildThirdPartyID,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Organization is the resolver for the organization field.
|
||||
func (r *thirdPartyResolver) Organization(ctx context.Context, obj *types.ThirdParty) (*types.Organization, error) {
|
||||
if _, err := r.authorize(ctx, obj.ID, probo.ActionOrganizationGet); err != nil {
|
||||
@@ -913,6 +878,53 @@ func (r *thirdPartyResolver) SecurityOwner(ctx context.Context, obj *types.Third
|
||||
return types.NewProfile(securityOwner), nil
|
||||
}
|
||||
|
||||
// ParentThirdParty is the resolver for the parentThirdParty field.
|
||||
func (r *thirdPartyResolver) ParentThirdParty(ctx context.Context, obj *types.ThirdParty) (*types.ThirdParty, error) {
|
||||
if obj.ParentThirdParty == nil {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
if _, err := r.authorize(ctx, obj.ID, probo.ActionThirdPartyGet); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
loaders := dataloader.FromContext(ctx)
|
||||
|
||||
parent, err := loaders.ThirdParty.Load(ctx, obj.ParentThirdParty.ID)
|
||||
if err != nil {
|
||||
if errors.Is(err, coredata.ErrResourceNotFound) || errors.Is(err, dataloadgen.ErrNotFound) {
|
||||
return nil, gqlutils.NotFound(ctx, err)
|
||||
}
|
||||
|
||||
r.logger.ErrorCtx(ctx, "cannot load parent third party", log.Error(err))
|
||||
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
return types.NewThirdParty(parent), nil
|
||||
}
|
||||
|
||||
// Ancestors is the resolver for the ancestors field.
|
||||
func (r *thirdPartyResolver) Ancestors(ctx context.Context, obj *types.ThirdParty) ([]*types.ThirdParty, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionThirdPartyGet)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ancestors, err := r.probo.ThirdParties.GetAncestors(ctx, scope, obj.ID)
|
||||
if err != nil {
|
||||
r.logger.ErrorCtx(ctx, "cannot load ancestors", log.Error(err))
|
||||
return nil, gqlutils.Internal(ctx)
|
||||
}
|
||||
|
||||
result := make([]*types.ThirdParty, len(ancestors))
|
||||
for i, a := range ancestors {
|
||||
result[i] = types.NewThirdParty(a)
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// ChildThirdParties is the resolver for the childThirdParties field.
|
||||
func (r *thirdPartyResolver) ChildThirdParties(ctx context.Context, obj *types.ThirdParty, first *int, after *page.CursorKey, last *int, before *page.CursorKey, orderBy *types.ThirdPartyOrderBy) (*types.ThirdPartyConnection, error) {
|
||||
scope, err := r.authorize(ctx, obj.ID, probo.ActionThirdPartyRelationList)
|
||||
|
||||
Reference in New Issue
Block a user