Remove user from organization

Signed-off-by: gearnode <bryan@frimin.fr>
This commit is contained in:
gearnode
2025-03-14 11:17:07 +01:00
parent c2a93f6871
commit 7153831fb2
7 changed files with 498 additions and 2 deletions

View File

@@ -38,6 +38,7 @@ import {
import type { SettingsPageQuery as SettingsPageQueryType } from "./__generated__/SettingsPageQuery.graphql"; import type { SettingsPageQuery as SettingsPageQueryType } from "./__generated__/SettingsPageQuery.graphql";
import type { SettingsPageUpdateOrganizationMutation as SettingsPageUpdateOrganizationMutationType } from "./__generated__/SettingsPageUpdateOrganizationMutation.graphql"; import type { SettingsPageUpdateOrganizationMutation as SettingsPageUpdateOrganizationMutationType } from "./__generated__/SettingsPageUpdateOrganizationMutation.graphql";
import type { SettingsPageInviteUserMutation as SettingsPageInviteUserMutationType } from "./__generated__/SettingsPageInviteUserMutation.graphql"; import type { SettingsPageInviteUserMutation as SettingsPageInviteUserMutationType } from "./__generated__/SettingsPageInviteUserMutation.graphql";
import type { SettingsPageRemoveUserMutation as SettingsPageRemoveUserMutationType } from "./__generated__/SettingsPageRemoveUserMutation.graphql";
const settingsPageQuery = graphql` const settingsPageQuery = graphql`
query SettingsPageQuery($organizationID: ID!) { query SettingsPageQuery($organizationID: ID!) {
@@ -83,6 +84,14 @@ const inviteUserMutation = graphql`
} }
`; `;
const removeUserMutation = graphql`
mutation SettingsPageRemoveUserMutation($input: RemoveUserInput!) {
removeUser(input: $input) {
success
}
}
`;
function SettingsPageContent({ function SettingsPageContent({
queryRef, queryRef,
}: { }: {
@@ -103,6 +112,7 @@ function SettingsPageContent({
organization.name || "" organization.name || ""
); );
const [isUploading, setIsUploading] = useState(false); const [isUploading, setIsUploading] = useState(false);
const [isRemoving, setIsRemoving] = useState(false);
const [updateOrganization] = const [updateOrganization] =
useMutation<SettingsPageUpdateOrganizationMutationType>( useMutation<SettingsPageUpdateOrganizationMutationType>(
@@ -112,6 +122,13 @@ function SettingsPageContent({
const [inviteUser] = const [inviteUser] =
useMutation<SettingsPageInviteUserMutationType>(inviteUserMutation); useMutation<SettingsPageInviteUserMutationType>(inviteUserMutation);
const [removeUser] =
useMutation<SettingsPageRemoveUserMutationType>(removeUserMutation);
const { organizationId } = useParams();
const [, loadQuery] =
useQueryLoader<SettingsPageQueryType>(settingsPageQuery);
const handleUpdateName = () => { const handleUpdateName = () => {
updateOrganization({ updateOrganization({
variables: { variables: {
@@ -232,6 +249,45 @@ function SettingsPageContent({
}); });
}; };
const handleRemoveUser = (userId: string) => {
setIsRemoving(true);
removeUser({
variables: {
input: {
organizationId: organization.id,
userId: userId,
},
},
onCompleted: (response) => {
setIsRemoving(false);
if (response.removeUser?.success) {
toast({
title: "User removed",
description: "The user has been removed from the organization.",
variant: "default",
});
// Refresh the query to update the UI
loadQuery({ organizationID: organizationId! });
} else {
toast({
title: "Error removing user",
description: "The user could not be removed. Please try again.",
variant: "destructive",
});
}
},
onError: (error) => {
setIsRemoving(false);
toast({
title: "Error removing user",
description: error.message,
variant: "destructive",
});
},
});
};
return ( return (
<> <>
<div className="container mx-auto p-6 space-y-6"> <div className="container mx-auto p-6 space-y-6">
@@ -377,8 +433,12 @@ function SettingsPageContent({
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem className="text-red-600"> <DropdownMenuItem
Remove member className="text-red-600"
onClick={() => handleRemoveUser(user.id)}
disabled={isRemoving}
>
{isRemoving ? "Removing..." : "Remove member"}
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>

View File

@@ -0,0 +1,93 @@
/**
* @generated SignedSource<<9d909d5fc7076593317b01aab165513d>>
* @lightSyntaxTransform
* @nogrep
*/
/* tslint:disable */
/* eslint-disable */
// @ts-nocheck
import { ConcreteRequest } from 'relay-runtime';
export type RemoveUserInput = {
organizationId: string;
userId: string;
};
export type SettingsPageRemoveUserMutation$variables = {
input: RemoveUserInput;
};
export type SettingsPageRemoveUserMutation$data = {
readonly removeUser: {
readonly success: boolean;
};
};
export type SettingsPageRemoveUserMutation = {
response: SettingsPageRemoveUserMutation$data;
variables: SettingsPageRemoveUserMutation$variables;
};
const node: ConcreteRequest = (function(){
var v0 = [
{
"defaultValue": null,
"kind": "LocalArgument",
"name": "input"
}
],
v1 = [
{
"alias": null,
"args": [
{
"kind": "Variable",
"name": "input",
"variableName": "input"
}
],
"concreteType": "RemoveUserPayload",
"kind": "LinkedField",
"name": "removeUser",
"plural": false,
"selections": [
{
"alias": null,
"args": null,
"kind": "ScalarField",
"name": "success",
"storageKey": null
}
],
"storageKey": null
}
];
return {
"fragment": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Fragment",
"metadata": null,
"name": "SettingsPageRemoveUserMutation",
"selections": (v1/*: any*/),
"type": "Mutation",
"abstractKey": null
},
"kind": "Request",
"operation": {
"argumentDefinitions": (v0/*: any*/),
"kind": "Operation",
"name": "SettingsPageRemoveUserMutation",
"selections": (v1/*: any*/)
},
"params": {
"cacheID": "62588cd355b82b5a48d0b539a009b3bd",
"id": null,
"metadata": {},
"name": "SettingsPageRemoveUserMutation",
"operationKind": "mutation",
"text": "mutation SettingsPageRemoveUserMutation(\n $input: RemoveUserInput!\n) {\n removeUser(input: $input) {\n success\n }\n}\n"
}
};
})();
(node as any).hash = "8d375bb2dfdab0f9d9520eee9f732b10";
export default node;

View File

@@ -388,6 +388,7 @@ type Mutation {
confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload! confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload!
inviteUser(input: InviteUserInput!): InviteUserPayload! inviteUser(input: InviteUserInput!): InviteUserPayload!
confirmInvitation(input: ConfirmInvitationInput!): ConfirmInvitationPayload! confirmInvitation(input: ConfirmInvitationInput!): ConfirmInvitationPayload!
removeUser(input: RemoveUserInput!): RemoveUserPayload!
} }
input CreateVendorInput { input CreateVendorInput {
@@ -733,3 +734,12 @@ input ConfirmInvitationInput {
type ConfirmInvitationPayload { type ConfirmInvitationPayload {
success: Boolean! success: Boolean!
} }
input RemoveUserInput {
organizationId: ID!
userId: ID!
}
type RemoveUserPayload {
success: Boolean!
}

View File

@@ -212,6 +212,7 @@ type ComplexityRoot struct {
DeleteVendor func(childComplexity int, input types.DeleteVendorInput) int DeleteVendor func(childComplexity int, input types.DeleteVendorInput) int
ImportFramework func(childComplexity int, input types.ImportFrameworkInput) int ImportFramework func(childComplexity int, input types.ImportFrameworkInput) int
InviteUser func(childComplexity int, input types.InviteUserInput) int InviteUser func(childComplexity int, input types.InviteUserInput) int
RemoveUser func(childComplexity int, input types.RemoveUserInput) int
UnassignTask func(childComplexity int, input types.UnassignTaskInput) int UnassignTask func(childComplexity int, input types.UnassignTaskInput) int
UpdateControl func(childComplexity int, input types.UpdateControlInput) int UpdateControl func(childComplexity int, input types.UpdateControlInput) int
UpdateFramework func(childComplexity int, input types.UpdateFrameworkInput) int UpdateFramework func(childComplexity int, input types.UpdateFrameworkInput) int
@@ -301,6 +302,10 @@ type ComplexityRoot struct {
Viewer func(childComplexity int) int Viewer func(childComplexity int) int
} }
RemoveUserPayload struct {
Success func(childComplexity int) int
}
Session struct { Session struct {
ExpiresAt func(childComplexity int) int ExpiresAt func(childComplexity int) int
ID func(childComplexity int) int ID func(childComplexity int) int
@@ -453,6 +458,7 @@ type MutationResolver interface {
ConfirmEmail(ctx context.Context, input types.ConfirmEmailInput) (*types.ConfirmEmailPayload, error) ConfirmEmail(ctx context.Context, input types.ConfirmEmailInput) (*types.ConfirmEmailPayload, error)
InviteUser(ctx context.Context, input types.InviteUserInput) (*types.InviteUserPayload, error) InviteUser(ctx context.Context, input types.InviteUserInput) (*types.InviteUserPayload, error)
ConfirmInvitation(ctx context.Context, input types.ConfirmInvitationInput) (*types.ConfirmInvitationPayload, error) ConfirmInvitation(ctx context.Context, input types.ConfirmInvitationInput) (*types.ConfirmInvitationPayload, error)
RemoveUser(ctx context.Context, input types.RemoveUserInput) (*types.RemoveUserPayload, error)
} }
type OrganizationResolver interface { type OrganizationResolver interface {
LogoURL(ctx context.Context, obj *types.Organization) (*string, error) LogoURL(ctx context.Context, obj *types.Organization) (*string, error)
@@ -1107,6 +1113,18 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Mutation.InviteUser(childComplexity, args["input"].(types.InviteUserInput)), true return e.complexity.Mutation.InviteUser(childComplexity, args["input"].(types.InviteUserInput)), true
case "Mutation.removeUser":
if e.complexity.Mutation.RemoveUser == nil {
break
}
args, err := ec.field_Mutation_removeUser_args(context.TODO(), rawArgs)
if err != nil {
return 0, false
}
return e.complexity.Mutation.RemoveUser(childComplexity, args["input"].(types.RemoveUserInput)), true
case "Mutation.unassignTask": case "Mutation.unassignTask":
if e.complexity.Mutation.UnassignTask == nil { if e.complexity.Mutation.UnassignTask == nil {
break break
@@ -1560,6 +1578,13 @@ func (e *executableSchema) Complexity(typeName, field string, childComplexity in
return e.complexity.Query.Viewer(childComplexity), true return e.complexity.Query.Viewer(childComplexity), true
case "RemoveUserPayload.success":
if e.complexity.RemoveUserPayload.Success == nil {
break
}
return e.complexity.RemoveUserPayload.Success(childComplexity), true
case "Session.expiresAt": case "Session.expiresAt":
if e.complexity.Session.ExpiresAt == nil { if e.complexity.Session.ExpiresAt == nil {
break break
@@ -1974,6 +1999,7 @@ func (e *executableSchema) Exec(ctx context.Context) graphql.ResponseHandler {
ec.unmarshalInputDeleteVendorInput, ec.unmarshalInputDeleteVendorInput,
ec.unmarshalInputImportFrameworkInput, ec.unmarshalInputImportFrameworkInput,
ec.unmarshalInputInviteUserInput, ec.unmarshalInputInviteUserInput,
ec.unmarshalInputRemoveUserInput,
ec.unmarshalInputUnassignTaskInput, ec.unmarshalInputUnassignTaskInput,
ec.unmarshalInputUpdateControlInput, ec.unmarshalInputUpdateControlInput,
ec.unmarshalInputUpdateFrameworkInput, ec.unmarshalInputUpdateFrameworkInput,
@@ -2470,6 +2496,7 @@ type Mutation {
confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload! confirmEmail(input: ConfirmEmailInput!): ConfirmEmailPayload!
inviteUser(input: InviteUserInput!): InviteUserPayload! inviteUser(input: InviteUserInput!): InviteUserPayload!
confirmInvitation(input: ConfirmInvitationInput!): ConfirmInvitationPayload! confirmInvitation(input: ConfirmInvitationInput!): ConfirmInvitationPayload!
removeUser(input: RemoveUserInput!): RemoveUserPayload!
} }
input CreateVendorInput { input CreateVendorInput {
@@ -2815,6 +2842,15 @@ input ConfirmInvitationInput {
type ConfirmInvitationPayload { type ConfirmInvitationPayload {
success: Boolean! success: Boolean!
} }
input RemoveUserInput {
organizationId: ID!
userId: ID!
}
type RemoveUserPayload {
success: Boolean!
}
`, BuiltIn: false}, `, BuiltIn: false},
} }
var parsedSchema = gqlparser.MustLoadSchema(sources...) var parsedSchema = gqlparser.MustLoadSchema(sources...)
@@ -3391,6 +3427,29 @@ func (ec *executionContext) field_Mutation_inviteUser_argsInput(
return zeroVal, nil return zeroVal, nil
} }
func (ec *executionContext) field_Mutation_removeUser_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error
args := map[string]any{}
arg0, err := ec.field_Mutation_removeUser_argsInput(ctx, rawArgs)
if err != nil {
return nil, err
}
args["input"] = arg0
return args, nil
}
func (ec *executionContext) field_Mutation_removeUser_argsInput(
ctx context.Context,
rawArgs map[string]any,
) (types.RemoveUserInput, error) {
ctx = graphql.WithPathContext(ctx, graphql.NewPathWithField("input"))
if tmp, ok := rawArgs["input"]; ok {
return ec.unmarshalNRemoveUserInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐRemoveUserInput(ctx, tmp)
}
var zeroVal types.RemoveUserInput
return zeroVal, nil
}
func (ec *executionContext) field_Mutation_unassignTask_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) { func (ec *executionContext) field_Mutation_unassignTask_args(ctx context.Context, rawArgs map[string]any) (map[string]any, error) {
var err error var err error
args := map[string]any{} args := map[string]any{}
@@ -7838,6 +7897,53 @@ func (ec *executionContext) fieldContext_Mutation_confirmInvitation(ctx context.
return fc, nil return fc, nil
} }
func (ec *executionContext) _Mutation_removeUser(ctx context.Context, field graphql.CollectedField) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Mutation_removeUser(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return ec.resolvers.Mutation().RemoveUser(rctx, fc.Args["input"].(types.RemoveUserInput))
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(*types.RemoveUserPayload)
fc.Result = res
return ec.marshalNRemoveUserPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐRemoveUserPayload(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_Mutation_removeUser(ctx context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "Mutation",
Field: field,
IsMethod: true,
IsResolver: true,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
switch field.Name {
case "success":
return ec.fieldContext_RemoveUserPayload_success(ctx, field)
}
return nil, fmt.Errorf("no field named %q was found under type RemoveUserPayload", field.Name)
},
}
ctx = graphql.WithFieldContext(ctx, fc)
if fc.Args, err = ec.field_Mutation_removeUser_args(ctx, field.ArgumentMap(ec.Variables)); err != nil {
ec.Error(ctx, err)
return fc, err
}
return fc, nil
}
func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) { func (ec *executionContext) _Organization_id(ctx context.Context, field graphql.CollectedField, obj *types.Organization) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Organization_id(ctx, field) fc, err := ec.fieldContext_Organization_id(ctx, field)
if err != nil { if err != nil {
@@ -9843,6 +9949,44 @@ func (ec *executionContext) fieldContext_Query___schema(_ context.Context, field
return fc, nil return fc, nil
} }
func (ec *executionContext) _RemoveUserPayload_success(ctx context.Context, field graphql.CollectedField, obj *types.RemoveUserPayload) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_RemoveUserPayload_success(ctx, field)
if err != nil {
return graphql.Null
}
ctx = graphql.WithFieldContext(ctx, fc)
resTmp, err := ec.ResolverMiddleware(ctx, func(rctx context.Context) (any, error) {
ctx = rctx // use context from middleware stack in children
return obj.Success, nil
})
if err != nil {
ec.Error(ctx, err)
return graphql.Null
}
if resTmp == nil {
if !graphql.HasFieldError(ctx, fc) {
ec.Errorf(ctx, "must not be null")
}
return graphql.Null
}
res := resTmp.(bool)
fc.Result = res
return ec.marshalNBoolean2bool(ctx, field.Selections, res)
}
func (ec *executionContext) fieldContext_RemoveUserPayload_success(_ context.Context, field graphql.CollectedField) (fc *graphql.FieldContext, err error) {
fc = &graphql.FieldContext{
Object: "RemoveUserPayload",
Field: field,
IsMethod: false,
IsResolver: false,
Child: func(ctx context.Context, field graphql.CollectedField) (*graphql.FieldContext, error) {
return nil, errors.New("field of type Boolean does not have child fields")
},
}
return fc, nil
}
func (ec *executionContext) _Session_id(ctx context.Context, field graphql.CollectedField, obj *types.Session) (ret graphql.Marshaler) { func (ec *executionContext) _Session_id(ctx context.Context, field graphql.CollectedField, obj *types.Session) (ret graphql.Marshaler) {
fc, err := ec.fieldContext_Session_id(ctx, field) fc, err := ec.fieldContext_Session_id(ctx, field)
if err != nil { if err != nil {
@@ -14634,6 +14778,40 @@ func (ec *executionContext) unmarshalInputInviteUserInput(ctx context.Context, o
return it, nil return it, nil
} }
func (ec *executionContext) unmarshalInputRemoveUserInput(ctx context.Context, obj any) (types.RemoveUserInput, error) {
var it types.RemoveUserInput
asMap := map[string]any{}
for k, v := range obj.(map[string]any) {
asMap[k] = v
}
fieldsInOrder := [...]string{"organizationId", "userId"}
for _, k := range fieldsInOrder {
v, ok := asMap[k]
if !ok {
continue
}
switch k {
case "organizationId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("organizationId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.OrganizationID = data
case "userId":
ctx := graphql.WithPathContext(ctx, graphql.NewPathWithField("userId"))
data, err := ec.unmarshalNID2githubᚗcomᚋgetproboᚋproboᚋpkgᚋgidᚐGID(ctx, v)
if err != nil {
return it, err
}
it.UserID = data
}
}
return it, nil
}
func (ec *executionContext) unmarshalInputUnassignTaskInput(ctx context.Context, obj any) (types.UnassignTaskInput, error) { func (ec *executionContext) unmarshalInputUnassignTaskInput(ctx context.Context, obj any) (types.UnassignTaskInput, error) {
var it types.UnassignTaskInput var it types.UnassignTaskInput
asMap := map[string]any{} asMap := map[string]any{}
@@ -16709,6 +16887,13 @@ func (ec *executionContext) _Mutation(ctx context.Context, sel ast.SelectionSet)
if out.Values[i] == graphql.Null { if out.Values[i] == graphql.Null {
out.Invalids++ out.Invalids++
} }
case "removeUser":
out.Values[i] = ec.OperationContext.RootResolverMiddleware(innerCtx, func(ctx context.Context) (res graphql.Marshaler) {
return ec._Mutation_removeUser(ctx, field)
})
if out.Values[i] == graphql.Null {
out.Invalids++
}
default: default:
panic("unknown field " + strconv.Quote(field.Name)) panic("unknown field " + strconv.Quote(field.Name))
} }
@@ -17541,6 +17726,45 @@ func (ec *executionContext) _Query(ctx context.Context, sel ast.SelectionSet) gr
return out return out
} }
var removeUserPayloadImplementors = []string{"RemoveUserPayload"}
func (ec *executionContext) _RemoveUserPayload(ctx context.Context, sel ast.SelectionSet, obj *types.RemoveUserPayload) graphql.Marshaler {
fields := graphql.CollectFields(ec.OperationContext, sel, removeUserPayloadImplementors)
out := graphql.NewFieldSet(fields)
deferred := make(map[string]*graphql.FieldSet)
for i, field := range fields {
switch field.Name {
case "__typename":
out.Values[i] = graphql.MarshalString("RemoveUserPayload")
case "success":
out.Values[i] = ec._RemoveUserPayload_success(ctx, field, obj)
if out.Values[i] == graphql.Null {
out.Invalids++
}
default:
panic("unknown field " + strconv.Quote(field.Name))
}
}
out.Dispatch(ctx)
if out.Invalids > 0 {
return graphql.Null
}
atomic.AddInt32(&ec.deferred, int32(len(deferred)))
for label, dfs := range deferred {
ec.processDeferredGroup(graphql.DeferredGroup{
Label: label,
Path: graphql.GetPath(ctx),
FieldSet: dfs,
Context: ctx,
})
}
return out
}
var sessionImplementors = []string{"Session"} var sessionImplementors = []string{"Session"}
func (ec *executionContext) _Session(ctx context.Context, sel ast.SelectionSet, obj *types.Session) graphql.Marshaler { func (ec *executionContext) _Session(ctx context.Context, sel ast.SelectionSet, obj *types.Session) graphql.Marshaler {
@@ -19920,6 +20144,25 @@ var (
} }
) )
func (ec *executionContext) unmarshalNRemoveUserInput2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐRemoveUserInput(ctx context.Context, v any) (types.RemoveUserInput, error) {
res, err := ec.unmarshalInputRemoveUserInput(ctx, v)
return res, graphql.ErrorOnPath(ctx, err)
}
func (ec *executionContext) marshalNRemoveUserPayload2githubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐRemoveUserPayload(ctx context.Context, sel ast.SelectionSet, v types.RemoveUserPayload) graphql.Marshaler {
return ec._RemoveUserPayload(ctx, sel, &v)
}
func (ec *executionContext) marshalNRemoveUserPayload2ᚖgithubᚗcomᚋgetproboᚋproboᚋpkgᚋserverᚋapiᚋconsoleᚋv1ᚋtypesᚐRemoveUserPayload(ctx context.Context, sel ast.SelectionSet, v *types.RemoveUserPayload) graphql.Marshaler {
if v == nil {
if !graphql.HasFieldError(ctx, graphql.GetFieldContext(ctx)) {
ec.Errorf(ctx, "the requested element is null which the schema does not allow")
}
return graphql.Null
}
return ec._RemoveUserPayload(ctx, sel, v)
}
func (ec *executionContext) unmarshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐRiskTier(ctx context.Context, v any) (coredata.RiskTier, error) { func (ec *executionContext) unmarshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐRiskTier(ctx context.Context, v any) (coredata.RiskTier, error) {
tmp, err := graphql.UnmarshalString(v) tmp, err := graphql.UnmarshalString(v)
res := unmarshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐRiskTier[tmp] res := unmarshalNRiskTier2githubᚗcomᚋgetproboᚋproboᚋpkgᚋcoredataᚐRiskTier[tmp]

View File

@@ -354,6 +354,15 @@ type PolicyEdge struct {
type Query struct { type Query struct {
} }
type RemoveUserInput struct {
OrganizationID gid.GID `json:"organizationId"`
UserID gid.GID `json:"userId"`
}
type RemoveUserPayload struct {
Success bool `json:"success"`
}
type Session struct { type Session struct {
ID gid.GID `json:"id"` ID gid.GID `json:"id"`
ExpiresAt time.Time `json:"expiresAt"` ExpiresAt time.Time `json:"expiresAt"`

View File

@@ -543,6 +543,29 @@ func (r *mutationResolver) ConfirmInvitation(ctx context.Context, input types.Co
return &types.ConfirmInvitationPayload{Success: true}, nil return &types.ConfirmInvitationPayload{Success: true}, nil
} }
// RemoveUser is the resolver for the removeUser field.
func (r *mutationResolver) RemoveUser(ctx context.Context, input types.RemoveUserInput) (*types.RemoveUserPayload, error) {
user := UserFromContext(ctx)
organizations, err := r.usrmgrSvc.ListOrganizationsForUserID(ctx, user.ID)
if err != nil {
panic(fmt.Errorf("failed to list organizations for user: %w", err))
}
for _, organization := range organizations {
if organization.ID == input.OrganizationID {
err := r.usrmgrSvc.RemoveUser(ctx, input.OrganizationID, input.UserID)
if err != nil {
return nil, err
}
return &types.RemoveUserPayload{Success: true}, nil
}
}
return nil, fmt.Errorf("organization not found")
}
// LogoURL is the resolver for the logoUrl field. // LogoURL is the resolver for the logoUrl field.
func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) { func (r *organizationResolver) LogoURL(ctx context.Context, obj *types.Organization) (*string, error) {
svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID()) svc := r.GetTenantServiceIfAuthorized(ctx, obj.ID.TenantID())

View File

@@ -542,6 +542,46 @@ func (s Service) InviteUser(
return &ErrInvalidFullName{fullName} return &ErrInvalidFullName{fullName}
} }
var userExists bool
err := s.pg.WithConn(
ctx,
func(tx pg.Conn) error {
user := &coredata.User{}
if err := user.LoadByEmail(ctx, tx, emailAddress); err != nil {
var errUserNotFound *coredata.ErrUserNotFound
if errors.As(err, &errUserNotFound) {
userExists = false
return nil
}
return fmt.Errorf("cannot load user by email: %w", err)
}
userExists = true
uo := coredata.UserOrganization{
UserID: user.ID,
OrganizationID: organizationID,
CreatedAt: time.Now(),
}
if err := uo.Insert(ctx, tx); err != nil {
return fmt.Errorf("cannot insert user organization: %w", err)
}
return nil
},
)
if err != nil {
return err
}
if userExists {
return nil
}
confirmationToken, err := statelesstoken.NewToken( confirmationToken, err := statelesstoken.NewToken(
s.tokenSecret, s.tokenSecret,
TokenTypeOrganizationInvitation, TokenTypeOrganizationInvitation,
@@ -640,3 +680,21 @@ func (s Service) ConfirmInvitation(ctx context.Context, tokenString string, pass
}, },
) )
} }
func (s Service) RemoveUser(ctx context.Context, organizationID gid.GID, userID gid.GID) error {
return s.pg.WithConn(
ctx,
func(tx pg.Conn) error {
uo := coredata.UserOrganization{
UserID: userID,
OrganizationID: organizationID,
}
if err := uo.Delete(ctx, tx); err != nil {
return fmt.Errorf("cannot delete user organization: %w", err)
}
return nil
},
)
}