Split inactive profile state
Replace the binary profile ACTIVE/INACTIVE model with PENDING, ACTIVE, and DEACTIVATED so invited-but-not-yet-activated members remain assignable to assets, data, and risks instead of being treated like deactivated users. Add activated_at/deactivated_at timestamps and Mark* lifecycle helpers, and update every transition (create, invite/re-invite, activation, archive, SCIM, SAML, sessions, compliance-portal grant) to the new states. Expose a multi-state states[] filter across coredata, GraphQL, MCP, and the console owner pickers, which now request ACTIVE and PENDING members. A migration renames the membership_state enum, classifies existing inactive profiles as PENDING from recent invitation activity, and backfills the new timestamp columns. Signed-off-by: Émile Ré <emile@probo.com>
This commit is contained in:
@@ -1371,7 +1371,7 @@
|
|||||||
"columns": { "name": "Name", "status": "Status", "email": "Email", "role": "Role", "createdOn": "Created on" },
|
"columns": { "name": "Name", "status": "Status", "email": "Email", "role": "Role", "createdOn": "Created on" },
|
||||||
"empty": "No people",
|
"empty": "No people",
|
||||||
"searchPlaceholder": "Search people...",
|
"searchPlaceholder": "Search people...",
|
||||||
"filters": { "allStatuses": "All statuses", "active": "Active", "inactive": "Inactive", "allRoles": "All roles", "allTypes": "All types" }
|
"filters": { "allStatuses": "All statuses", "pending": "Pending", "active": "Active", "deactivated": "Deactivated", "allRoles": "All roles", "allTypes": "All types" }
|
||||||
},
|
},
|
||||||
"peopleListItem": {
|
"peopleListItem": {
|
||||||
"messages": { "invitationSent": "Invitation sent successfully", "roleUpdated": "Role updated successfully", "archived": "Person archived successfully", "removed": "Person removed successfully" },
|
"messages": { "invitationSent": "Invitation sent successfully", "roleUpdated": "Role updated successfully", "archived": "Person archived successfully", "removed": "Person removed successfully" },
|
||||||
|
|||||||
@@ -2458,8 +2458,9 @@
|
|||||||
"searchPlaceholder": "Rechercher des personnes...",
|
"searchPlaceholder": "Rechercher des personnes...",
|
||||||
"filters": {
|
"filters": {
|
||||||
"allStatuses": "Tous les statuts",
|
"allStatuses": "Tous les statuts",
|
||||||
|
"pending": "En attente",
|
||||||
"active": "Actif",
|
"active": "Actif",
|
||||||
"inactive": "Inactif",
|
"deactivated": "Désactivé",
|
||||||
"allRoles": "Tous les rôles",
|
"allRoles": "Tous les rôles",
|
||||||
"allTypes": "Tous les types"
|
"allTypes": "Tous les types"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ import {
|
|||||||
} from "react-relay";
|
} from "react-relay";
|
||||||
import { graphql } from "relay-runtime";
|
import { graphql } from "relay-runtime";
|
||||||
|
|
||||||
import type { PeopleGraphQuery } from "#/__generated__/core/PeopleGraphQuery.graphql";
|
import type { PeopleGraphQuery, ProfileFilter } from "#/__generated__/core/PeopleGraphQuery.graphql";
|
||||||
|
|
||||||
/* eslint-disable relay/unused-fields */
|
/* eslint-disable relay/unused-fields */
|
||||||
|
|
||||||
@@ -57,11 +57,15 @@ export function usePeople(
|
|||||||
organizationId: string,
|
organizationId: string,
|
||||||
{ contractEnded }: { contractEnded?: boolean } = {},
|
{ contractEnded }: { contractEnded?: boolean } = {},
|
||||||
) {
|
) {
|
||||||
|
const filter: ProfileFilter = contractEnded !== undefined
|
||||||
|
? { contractEnded, states: ["ACTIVE", "PENDING"] }
|
||||||
|
: { states: ["ACTIVE", "PENDING"] };
|
||||||
|
|
||||||
const data = useLazyLoadQuery<PeopleGraphQuery>(
|
const data = useLazyLoadQuery<PeopleGraphQuery>(
|
||||||
peopleQuery,
|
peopleQuery,
|
||||||
{
|
{
|
||||||
organizationId: organizationId,
|
organizationId: organizationId,
|
||||||
filter: contractEnded !== undefined ? { contractEnded } : null,
|
filter,
|
||||||
},
|
},
|
||||||
{ fetchPolicy: "network-only" },
|
{ fetchPolicy: "network-only" },
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -139,7 +139,7 @@ export function PersonPage(props: { queryRef: PreloadedQuery<PersonPageQuery> })
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
const canArchive = person.canDelete && person.source !== "SCIM" && person.state !== "INACTIVE";
|
const canArchive = person.canDelete && person.source !== "SCIM" && person.state !== "DEACTIVATED";
|
||||||
const canRemove = person.canRemoveMember && person.source !== "SCIM";
|
const canRemove = person.canRemoveMember && person.source !== "SCIM";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -231,8 +231,9 @@ export function PeopleList(props: {
|
|||||||
onValueChange={handleStateFilterChange}
|
onValueChange={handleStateFilterChange}
|
||||||
>
|
>
|
||||||
<Option value="ALL">{t("peopleList.filters.allStatuses")}</Option>
|
<Option value="ALL">{t("peopleList.filters.allStatuses")}</Option>
|
||||||
|
<Option value="PENDING">{t("peopleList.filters.pending")}</Option>
|
||||||
<Option value="ACTIVE">{t("peopleList.filters.active")}</Option>
|
<Option value="ACTIVE">{t("peopleList.filters.active")}</Option>
|
||||||
<Option value="INACTIVE">{t("peopleList.filters.inactive")}</Option>
|
<Option value="DEACTIVATED">{t("peopleList.filters.deactivated")}</Option>
|
||||||
</Select>
|
</Select>
|
||||||
<Select
|
<Select
|
||||||
value={roleFilter ?? "ALL"}
|
value={roleFilter ?? "ALL"}
|
||||||
|
|||||||
@@ -145,10 +145,11 @@ export function PeopleListItem(props: {
|
|||||||
? availableRoles
|
? availableRoles
|
||||||
: [...availableRoles, profile.membership.role];
|
: [...availableRoles, profile.membership.role];
|
||||||
|
|
||||||
const isInactive = profile.state === "INACTIVE";
|
const isActive = profile.state === "ACTIVE";
|
||||||
|
const isInactive = !isActive;
|
||||||
|
|
||||||
const canSendActivationMail = isInactive && profile.source !== "SCIM" && profile.canInvite;
|
const canSendActivationMail = !isActive && profile.source !== "SCIM" && profile.canInvite;
|
||||||
const canArchive = profile.canDelete && profile.source !== "SCIM" && profile.state !== "INACTIVE";
|
const canArchive = profile.canDelete && profile.source !== "SCIM" && profile.state !== "DEACTIVATED";
|
||||||
const canRemove = profile.canRemoveMember && profile.source !== "SCIM";
|
const canRemove = profile.canRemoveMember && profile.source !== "SCIM";
|
||||||
|
|
||||||
const [inviteUser]
|
const [inviteUser]
|
||||||
@@ -262,7 +263,7 @@ export function PeopleListItem(props: {
|
|||||||
<span className="font-semibold">{profile.fullName}</span>
|
<span className="font-semibold">{profile.fullName}</span>
|
||||||
</Td>
|
</Td>
|
||||||
<Td>
|
<Td>
|
||||||
<Badge variant={profile.state === "INACTIVE" ? "neutral" : "success"}>{profile.state}</Badge>
|
<Badge variant={isActive ? "success" : "neutral"}>{profile.state}</Badge>
|
||||||
</Td>
|
</Td>
|
||||||
<Td className={clsx(
|
<Td className={clsx(
|
||||||
isMutating && "opacity-60 pointer-events-none",
|
isMutating && "opacity-60 pointer-events-none",
|
||||||
|
|||||||
@@ -704,7 +704,7 @@ func TestUser_ArchiveUser(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
require.NotEmpty(t, archivedUserState, "Should still find archived user")
|
require.NotEmpty(t, archivedUserState, "Should still find archived user")
|
||||||
assert.Equal(t, "INACTIVE", archivedUserState)
|
assert.Equal(t, "DEACTIVATED", archivedUserState)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestUser_DeactivateUserCancelsSignatureRequests(t *testing.T) {
|
func TestUser_DeactivateUserCancelsSignatureRequests(t *testing.T) {
|
||||||
|
|||||||
@@ -105,8 +105,9 @@ export const description: INodeProperties[] = [
|
|||||||
description: 'Filter by signatory profile state',
|
description: 'Filter by signatory profile state',
|
||||||
options: [
|
options: [
|
||||||
{ name: 'Any', value: '' },
|
{ name: 'Any', value: '' },
|
||||||
|
{ name: 'Pending', value: 'PENDING' },
|
||||||
{ name: 'Active', value: 'ACTIVE' },
|
{ name: 'Active', value: 'ACTIVE' },
|
||||||
{ name: 'Inactive', value: 'INACTIVE' },
|
{ name: 'Deactivated', value: 'DEACTIVATED' },
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -97,7 +97,8 @@ export const description: INodeProperties[] = [
|
|||||||
options: [
|
options: [
|
||||||
{ name: 'Active', value: 'ACTIVE' },
|
{ name: 'Active', value: 'ACTIVE' },
|
||||||
{ name: 'All', value: '' },
|
{ name: 'All', value: '' },
|
||||||
{ name: 'Inactive', value: 'INACTIVE' },
|
{ name: 'Deactivated', value: 'DEACTIVATED' },
|
||||||
|
{ name: 'Pending', value: 'PENDING' },
|
||||||
],
|
],
|
||||||
default: '',
|
default: '',
|
||||||
description: 'Filter by profile state',
|
description: 'Filter by profile state',
|
||||||
|
|||||||
@@ -158,7 +158,7 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if flagState != "" {
|
if flagState != "" {
|
||||||
if err := cmdutil.ValidateEnum("state", flagState, []string{"ACTIVE", "INACTIVE"}); err != nil {
|
if err := cmdutil.ValidateEnum("state", flagState, []string{"PENDING", "ACTIVE", "DEACTIVATED"}); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -273,7 +273,7 @@ func NewCmdList(f *cmdutil.Factory) *cobra.Command {
|
|||||||
cmd.Flags().StringVar(&flagOrder, "order-by", "", "Order by field (FULL_NAME, CREATED_AT, KIND)")
|
cmd.Flags().StringVar(&flagOrder, "order-by", "", "Order by field (FULL_NAME, CREATED_AT, KIND)")
|
||||||
cmd.Flags().StringVar(&flagOrderDir, "order-direction", "DESC", "Sort direction (ASC, DESC)")
|
cmd.Flags().StringVar(&flagOrderDir, "order-direction", "DESC", "Sort direction (ASC, DESC)")
|
||||||
cmd.Flags().StringVar(&flagContractEnded, "contract-ended", "", "Filter by contract status (true or false)")
|
cmd.Flags().StringVar(&flagContractEnded, "contract-ended", "", "Filter by contract status (true or false)")
|
||||||
cmd.Flags().StringVar(&flagState, "state", "", "Filter by profile state (ACTIVE or INACTIVE)")
|
cmd.Flags().StringVar(&flagState, "state", "", "Filter by profile state (PENDING, ACTIVE, DEACTIVATED)")
|
||||||
cmd.Flags().StringVarP(&flagFilter, "filter", "q", "", "Filter users by name or email search query")
|
cmd.Flags().StringVarP(&flagFilter, "filter", "q", "", "Filter users by name or email search query")
|
||||||
cmd.Flags().StringVar(&flagRole, "role", "", "Filter by membership role (OWNER, ADMIN, VIEWER, AUDITOR, EMPLOYEE)")
|
cmd.Flags().StringVar(&flagRole, "role", "", "Filter by membership role (OWNER, ADMIN, VIEWER, AUDITOR, EMPLOYEE)")
|
||||||
cmd.Flags().StringVar(&flagKind, "kind", "", "Filter by profile kind (EMPLOYEE, CONTRACTOR, SERVICE_ACCOUNT)")
|
cmd.Flags().StringVar(&flagKind, "kind", "", "Filter by profile kind (EMPLOYEE, CONTRACTOR, SERVICE_ACCOUNT)")
|
||||||
|
|||||||
@@ -396,9 +396,8 @@ func (s *Service) GrantPortalAccessByIDs(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shouldSendEmail {
|
if shouldSendEmail {
|
||||||
profile.State = coredata.ProfileStateActive
|
profile.MarkActive(now)
|
||||||
|
|
||||||
profile.UpdatedAt = now
|
|
||||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot update profile: %w", err)
|
return fmt.Errorf("cannot update profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -394,6 +394,7 @@ func (s *Service) ProvisionPortalMember(
|
|||||||
EmailAddress: identity.EmailAddress,
|
EmailAddress: identity.EmailAddress,
|
||||||
Source: coredata.ProfileSourceManual,
|
Source: coredata.ProfileSourceManual,
|
||||||
State: coredata.ProfileStateActive,
|
State: coredata.ProfileStateActive,
|
||||||
|
ActivatedAt: &now,
|
||||||
FullName: identity.FullName,
|
FullName: identity.FullName,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
|
|||||||
@@ -70,6 +70,8 @@ type (
|
|||||||
EnterpriseOrganization *string `db:"enterprise_organization"`
|
EnterpriseOrganization *string `db:"enterprise_organization"`
|
||||||
Division *string `db:"division"`
|
Division *string `db:"division"`
|
||||||
ManagerValue *string `db:"manager_value"`
|
ManagerValue *string `db:"manager_value"`
|
||||||
|
ActivatedAt *time.Time `db:"activated_at"`
|
||||||
|
DeactivatedAt *time.Time `db:"deactivated_at"`
|
||||||
CreatedAt time.Time `db:"created_at"`
|
CreatedAt time.Time `db:"created_at"`
|
||||||
UpdatedAt time.Time `db:"updated_at"`
|
UpdatedAt time.Time `db:"updated_at"`
|
||||||
}
|
}
|
||||||
@@ -96,6 +98,26 @@ func (p MembershipProfile) CursorKey(orderBy MembershipProfileOrderField) page.C
|
|||||||
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
panic(fmt.Sprintf("unsupported order by: %s", orderBy))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *MembershipProfile) MarkPending(now time.Time) {
|
||||||
|
p.State = ProfileStatePending
|
||||||
|
p.ActivatedAt = nil
|
||||||
|
p.DeactivatedAt = nil
|
||||||
|
p.UpdatedAt = now
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MembershipProfile) MarkActive(now time.Time) {
|
||||||
|
p.State = ProfileStateActive
|
||||||
|
p.ActivatedAt = &now
|
||||||
|
p.DeactivatedAt = nil
|
||||||
|
p.UpdatedAt = now
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *MembershipProfile) MarkDeactivated(now time.Time) {
|
||||||
|
p.State = ProfileStateDeactivated
|
||||||
|
p.DeactivatedAt = &now
|
||||||
|
p.UpdatedAt = now
|
||||||
|
}
|
||||||
|
|
||||||
func (p *MembershipProfile) AuthorizationAttributes(
|
func (p *MembershipProfile) AuthorizationAttributes(
|
||||||
ctx context.Context,
|
ctx context.Context,
|
||||||
conn pg.Querier,
|
conn pg.Querier,
|
||||||
@@ -192,6 +214,8 @@ SELECT
|
|||||||
p.enterprise_organization,
|
p.enterprise_organization,
|
||||||
p.division,
|
p.division,
|
||||||
p.manager_value,
|
p.manager_value,
|
||||||
|
p.activated_at,
|
||||||
|
p.deactivated_at,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -269,6 +293,8 @@ SELECT
|
|||||||
p.enterprise_organization,
|
p.enterprise_organization,
|
||||||
p.division,
|
p.division,
|
||||||
p.manager_value,
|
p.manager_value,
|
||||||
|
p.activated_at,
|
||||||
|
p.deactivated_at,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -350,6 +376,8 @@ SELECT
|
|||||||
p.enterprise_organization,
|
p.enterprise_organization,
|
||||||
p.division,
|
p.division,
|
||||||
p.manager_value,
|
p.manager_value,
|
||||||
|
p.activated_at,
|
||||||
|
p.deactivated_at,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -430,6 +458,8 @@ SELECT
|
|||||||
p.enterprise_organization,
|
p.enterprise_organization,
|
||||||
p.division,
|
p.division,
|
||||||
p.manager_value,
|
p.manager_value,
|
||||||
|
p.activated_at,
|
||||||
|
p.deactivated_at,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -507,6 +537,8 @@ WITH profiles AS (
|
|||||||
p.enterprise_organization,
|
p.enterprise_organization,
|
||||||
p.division,
|
p.division,
|
||||||
p.manager_value,
|
p.manager_value,
|
||||||
|
p.activated_at,
|
||||||
|
p.deactivated_at,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -550,6 +582,8 @@ SELECT
|
|||||||
enterprise_organization,
|
enterprise_organization,
|
||||||
division,
|
division,
|
||||||
manager_value,
|
manager_value,
|
||||||
|
activated_at,
|
||||||
|
deactivated_at,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
FROM profiles
|
FROM profiles
|
||||||
@@ -620,6 +654,8 @@ WITH profiles AS (
|
|||||||
p.enterprise_organization,
|
p.enterprise_organization,
|
||||||
p.division,
|
p.division,
|
||||||
p.manager_value,
|
p.manager_value,
|
||||||
|
p.activated_at,
|
||||||
|
p.deactivated_at,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -662,6 +698,8 @@ SELECT
|
|||||||
p.enterprise_organization,
|
p.enterprise_organization,
|
||||||
p.division,
|
p.division,
|
||||||
p.manager_value,
|
p.manager_value,
|
||||||
|
p.activated_at,
|
||||||
|
p.deactivated_at,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM profiles p
|
FROM profiles p
|
||||||
@@ -743,6 +781,8 @@ profiles AS (
|
|||||||
mp.enterprise_organization,
|
mp.enterprise_organization,
|
||||||
mp.division,
|
mp.division,
|
||||||
mp.manager_value,
|
mp.manager_value,
|
||||||
|
mp.activated_at,
|
||||||
|
mp.deactivated_at,
|
||||||
mp.created_at,
|
mp.created_at,
|
||||||
mp.updated_at
|
mp.updated_at
|
||||||
FROM
|
FROM
|
||||||
@@ -785,6 +825,8 @@ SELECT
|
|||||||
p.enterprise_organization,
|
p.enterprise_organization,
|
||||||
p.division,
|
p.division,
|
||||||
p.manager_value,
|
p.manager_value,
|
||||||
|
p.activated_at,
|
||||||
|
p.deactivated_at,
|
||||||
p.created_at,
|
p.created_at,
|
||||||
p.updated_at
|
p.updated_at
|
||||||
FROM profiles p
|
FROM profiles p
|
||||||
@@ -1000,6 +1042,8 @@ INSERT INTO
|
|||||||
enterprise_organization,
|
enterprise_organization,
|
||||||
division,
|
division,
|
||||||
manager_value,
|
manager_value,
|
||||||
|
activated_at,
|
||||||
|
deactivated_at,
|
||||||
created_at,
|
created_at,
|
||||||
updated_at
|
updated_at
|
||||||
)
|
)
|
||||||
@@ -1035,6 +1079,8 @@ VALUES (
|
|||||||
@enterprise_organization,
|
@enterprise_organization,
|
||||||
@division,
|
@division,
|
||||||
@manager_value,
|
@manager_value,
|
||||||
|
@activated_at,
|
||||||
|
@deactivated_at,
|
||||||
@created_at,
|
@created_at,
|
||||||
@updated_at
|
@updated_at
|
||||||
)
|
)
|
||||||
@@ -1072,6 +1118,8 @@ VALUES (
|
|||||||
"enterprise_organization": p.EnterpriseOrganization,
|
"enterprise_organization": p.EnterpriseOrganization,
|
||||||
"division": p.Division,
|
"division": p.Division,
|
||||||
"manager_value": p.ManagerValue,
|
"manager_value": p.ManagerValue,
|
||||||
|
"activated_at": p.ActivatedAt,
|
||||||
|
"deactivated_at": p.DeactivatedAt,
|
||||||
"created_at": p.CreatedAt,
|
"created_at": p.CreatedAt,
|
||||||
"updated_at": p.UpdatedAt,
|
"updated_at": p.UpdatedAt,
|
||||||
}
|
}
|
||||||
@@ -1130,6 +1178,8 @@ SET
|
|||||||
enterprise_organization = @enterprise_organization,
|
enterprise_organization = @enterprise_organization,
|
||||||
division = @division,
|
division = @division,
|
||||||
manager_value = @manager_value,
|
manager_value = @manager_value,
|
||||||
|
activated_at = @activated_at,
|
||||||
|
deactivated_at = @deactivated_at,
|
||||||
updated_at = @updated_at
|
updated_at = @updated_at
|
||||||
WHERE
|
WHERE
|
||||||
id = @id
|
id = @id
|
||||||
@@ -1168,6 +1218,8 @@ WHERE
|
|||||||
"enterprise_organization": p.EnterpriseOrganization,
|
"enterprise_organization": p.EnterpriseOrganization,
|
||||||
"division": p.Division,
|
"division": p.Division,
|
||||||
"manager_value": p.ManagerValue,
|
"manager_value": p.ManagerValue,
|
||||||
|
"activated_at": p.ActivatedAt,
|
||||||
|
"deactivated_at": p.DeactivatedAt,
|
||||||
"updated_at": p.UpdatedAt,
|
"updated_at": p.UpdatedAt,
|
||||||
}
|
}
|
||||||
maps.Copy(args, scope.SQLArguments())
|
maps.Copy(args, scope.SQLArguments())
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ type (
|
|||||||
email *mail.Addr
|
email *mail.Addr
|
||||||
userName *string
|
userName *string
|
||||||
externalID *string
|
externalID *string
|
||||||
state *ProfileState
|
states ProfileStateValues
|
||||||
source *ProfileSource
|
source *ProfileSource
|
||||||
query *string
|
query *string
|
||||||
role *MembershipRole
|
role *MembershipRole
|
||||||
@@ -82,12 +82,17 @@ func (f *MembershipProfileFilter) WithExternalID(externalID string) *MembershipP
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (f *MembershipProfileFilter) WithState(state ProfileState) *MembershipProfileFilter {
|
func (f *MembershipProfileFilter) WithState(state ProfileState) *MembershipProfileFilter {
|
||||||
f.state = &state
|
f.states = ProfileStateValues{state}
|
||||||
return f
|
return f
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *MembershipProfileFilter) State() *ProfileState {
|
func (f *MembershipProfileFilter) WithStates(states ...ProfileState) *MembershipProfileFilter {
|
||||||
return f.state
|
f.states = ProfileStateValues(states)
|
||||||
|
return f
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *MembershipProfileFilter) States() []ProfileState {
|
||||||
|
return f.states
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *MembershipProfileFilter) WithSource(source ProfileSource) *MembershipProfileFilter {
|
func (f *MembershipProfileFilter) WithSource(source ProfileSource) *MembershipProfileFilter {
|
||||||
@@ -146,7 +151,7 @@ func (f *MembershipProfileFilter) SQLArguments() pgx.StrictNamedArgs {
|
|||||||
"with_trust_center_access": f.withCompliancePortalAccess,
|
"with_trust_center_access": f.withCompliancePortalAccess,
|
||||||
"contract_ended": f.contractEnded,
|
"contract_ended": f.contractEnded,
|
||||||
"current_date": f.currentDate,
|
"current_date": f.currentDate,
|
||||||
"filter_state": f.state,
|
"filter_states": f.states,
|
||||||
"filter_source": f.source,
|
"filter_source": f.source,
|
||||||
"filter_query": filterQuery,
|
"filter_query": filterQuery,
|
||||||
"filter_role": f.role,
|
"filter_role": f.role,
|
||||||
@@ -192,8 +197,8 @@ AND (
|
|||||||
)
|
)
|
||||||
AND (
|
AND (
|
||||||
CASE
|
CASE
|
||||||
WHEN @filter_state::text IS NOT NULL THEN
|
WHEN @filter_states::membership_state[] IS NOT NULL THEN
|
||||||
p.state = @filter_state::membership_state
|
p.state = ANY(@filter_states::membership_state[])
|
||||||
ELSE TRUE
|
ELSE TRUE
|
||||||
END
|
END
|
||||||
)
|
)
|
||||||
|
|||||||
55
pkg/coredata/migrations/20260728T143246Z.sql
Normal file
55
pkg/coredata/migrations/20260728T143246Z.sql
Normal file
@@ -0,0 +1,55 @@
|
|||||||
|
-- Copyright (c) 2026 Probo Inc <hello@getprobo.com>.
|
||||||
|
--
|
||||||
|
-- Permission to use, copy, modify, and/or distribute this software for any
|
||||||
|
-- purpose with or without fee is hereby granted, provided that the above
|
||||||
|
-- copyright notice and this permission notice appear in all copies.
|
||||||
|
--
|
||||||
|
-- THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
||||||
|
-- REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
||||||
|
-- AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
||||||
|
-- INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
||||||
|
-- LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
||||||
|
-- OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
||||||
|
-- PERFORMANCE OF THIS SOFTWARE.
|
||||||
|
|
||||||
|
ALTER TABLE iam_membership_profiles
|
||||||
|
ADD COLUMN activated_at TIMESTAMP WITH TIME ZONE,
|
||||||
|
ADD COLUMN deactivated_at TIMESTAMP WITH TIME ZONE;
|
||||||
|
|
||||||
|
ALTER TABLE iam_membership_profiles
|
||||||
|
ALTER COLUMN state DROP DEFAULT;
|
||||||
|
|
||||||
|
ALTER TYPE membership_state RENAME TO membership_state_old;
|
||||||
|
CREATE TYPE membership_state AS ENUM ('PENDING', 'ACTIVE', 'DEACTIVATED');
|
||||||
|
|
||||||
|
ALTER TABLE iam_membership_profiles
|
||||||
|
ALTER COLUMN state TYPE membership_state
|
||||||
|
USING CASE
|
||||||
|
WHEN state::text = 'ACTIVE' THEN 'ACTIVE'::membership_state
|
||||||
|
ELSE 'DEACTIVATED'::membership_state
|
||||||
|
END;
|
||||||
|
|
||||||
|
DROP TYPE membership_state_old;
|
||||||
|
|
||||||
|
UPDATE iam_membership_profiles
|
||||||
|
SET state = 'PENDING'
|
||||||
|
WHERE state = 'DEACTIVATED'
|
||||||
|
AND source != 'SCIM'
|
||||||
|
AND EXISTS (
|
||||||
|
SELECT 1
|
||||||
|
FROM iam_invitations i
|
||||||
|
WHERE i.user_id = iam_membership_profiles.id
|
||||||
|
AND i.accepted_at IS NULL
|
||||||
|
AND (
|
||||||
|
i.expires_at >= NOW()
|
||||||
|
OR i.created_at >= NOW() - INTERVAL '7 days'
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
UPDATE iam_membership_profiles
|
||||||
|
SET activated_at = updated_at
|
||||||
|
WHERE state = 'ACTIVE';
|
||||||
|
|
||||||
|
UPDATE iam_membership_profiles
|
||||||
|
SET deactivated_at = NOW()
|
||||||
|
WHERE state = 'DEACTIVATED';
|
||||||
@@ -21,15 +21,21 @@
|
|||||||
package coredata
|
package coredata
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"database/sql/driver"
|
||||||
"encoding"
|
"encoding"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProfileState string
|
type (
|
||||||
|
ProfileState string
|
||||||
|
ProfileStateValues []ProfileState
|
||||||
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
|
ProfileStatePending ProfileState = "PENDING"
|
||||||
ProfileStateActive ProfileState = "ACTIVE"
|
ProfileStateActive ProfileState = "ACTIVE"
|
||||||
ProfileStateInactive ProfileState = "INACTIVE"
|
ProfileStateDeactivated ProfileState = "DEACTIVATED"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -40,16 +46,18 @@ var (
|
|||||||
|
|
||||||
func ProfileStates() []ProfileState {
|
func ProfileStates() []ProfileState {
|
||||||
return []ProfileState{
|
return []ProfileState{
|
||||||
|
ProfileStatePending,
|
||||||
ProfileStateActive,
|
ProfileStateActive,
|
||||||
ProfileStateInactive,
|
ProfileStateDeactivated,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v ProfileState) IsValid() bool {
|
func (v ProfileState) IsValid() bool {
|
||||||
switch v {
|
switch v {
|
||||||
case
|
case
|
||||||
|
ProfileStatePending,
|
||||||
ProfileStateActive,
|
ProfileStateActive,
|
||||||
ProfileStateInactive:
|
ProfileStateDeactivated:
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -74,3 +82,24 @@ func (v *ProfileState) UnmarshalText(text []byte) error {
|
|||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (states ProfileStateValues) Value() (driver.Value, error) {
|
||||||
|
if len(states) == 0 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
var result strings.Builder
|
||||||
|
result.WriteString("{")
|
||||||
|
|
||||||
|
for i, state := range states {
|
||||||
|
if i > 0 {
|
||||||
|
result.WriteString(",")
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(&result, "%q", state.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
result.WriteString("}")
|
||||||
|
|
||||||
|
return result.String(), nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -179,9 +179,8 @@ func (s *AuthService) ActivateAccount(
|
|||||||
return NewUserManagedBySCIMError(profile.ID)
|
return NewUserManagedBySCIMError(profile.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.State == coredata.ProfileStateInactive {
|
if profile.State == coredata.ProfileStatePending {
|
||||||
profile.State = coredata.ProfileStateActive
|
profile.MarkActive(now)
|
||||||
profile.UpdatedAt = now
|
|
||||||
|
|
||||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot update user: %w", err)
|
return fmt.Errorf("cannot update user: %w", err)
|
||||||
|
|||||||
@@ -438,9 +438,8 @@ func (s *OrganizationService) ArchiveUser(
|
|||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
|
|
||||||
if profile.State != coredata.ProfileStateInactive {
|
if profile.State != coredata.ProfileStateDeactivated {
|
||||||
profile.State = coredata.ProfileStateInactive
|
profile.MarkDeactivated(now)
|
||||||
profile.UpdatedAt = now
|
|
||||||
|
|
||||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot update profile state: %w", err)
|
return fmt.Errorf("cannot update profile state: %w", err)
|
||||||
@@ -505,6 +504,14 @@ func (s *OrganizationService) InviteUser(
|
|||||||
return NewUserManagedBySCIMError(profile.ID)
|
return NewUserManagedBySCIMError(profile.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if profile.State == coredata.ProfileStateDeactivated {
|
||||||
|
profile.MarkPending(now)
|
||||||
|
|
||||||
|
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||||
|
return fmt.Errorf("cannot update profile state: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
err = invitation.Insert(ctx, tx, scope)
|
err = invitation.Insert(ctx, tx, scope)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("cannot insert invitation: %w", err)
|
return fmt.Errorf("cannot insert invitation: %w", err)
|
||||||
@@ -583,6 +590,7 @@ func (s *OrganizationService) CreateOrganization(
|
|||||||
OrganizationID: organization.ID,
|
OrganizationID: organization.ID,
|
||||||
Source: coredata.ProfileSourceManual,
|
Source: coredata.ProfileSourceManual,
|
||||||
State: coredata.ProfileStateActive,
|
State: coredata.ProfileStateActive,
|
||||||
|
ActivatedAt: &now,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -1038,8 +1046,8 @@ func (s *OrganizationService) CreateUser(ctx context.Context, scope coredata.Sco
|
|||||||
Kind: req.Kind,
|
Kind: req.Kind,
|
||||||
AdditionalEmailAddresses: req.AdditionalEmailAddresses,
|
AdditionalEmailAddresses: req.AdditionalEmailAddresses,
|
||||||
Position: req.Position,
|
Position: req.Position,
|
||||||
// User is created inactive
|
// User is pending until they accept an invitation.
|
||||||
State: coredata.ProfileStateInactive,
|
State: coredata.ProfileStatePending,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
}
|
}
|
||||||
@@ -1192,14 +1200,22 @@ func (s *OrganizationService) UpdateUserState(
|
|||||||
return fmt.Errorf("cannot load profile: %w", err)
|
return fmt.Errorf("cannot load profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
profile.State = state
|
now := time.Now()
|
||||||
profile.UpdatedAt = time.Now()
|
|
||||||
|
switch state {
|
||||||
|
case coredata.ProfileStatePending:
|
||||||
|
profile.MarkPending(now)
|
||||||
|
case coredata.ProfileStateActive:
|
||||||
|
profile.MarkActive(now)
|
||||||
|
case coredata.ProfileStateDeactivated:
|
||||||
|
profile.MarkDeactivated(now)
|
||||||
|
}
|
||||||
|
|
||||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot update profile: %w", err)
|
return fmt.Errorf("cannot update profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if state == coredata.ProfileStateInactive {
|
if state == coredata.ProfileStateDeactivated {
|
||||||
signatures := &coredata.DocumentVersionSignatures{}
|
signatures := &coredata.DocumentVersionSignatures{}
|
||||||
if err := signatures.DeleteRequestedBySignatory(ctx, tx, scope, profile.ID); err != nil {
|
if err := signatures.DeleteRequestedBySignatory(ctx, tx, scope, profile.ID); err != nil {
|
||||||
return fmt.Errorf("cannot delete requested signatures: %w", err)
|
return fmt.Errorf("cannot delete requested signatures: %w", err)
|
||||||
|
|||||||
@@ -322,6 +322,7 @@ func (s *Service) HandleAssertion(
|
|||||||
OrganizationID: config.OrganizationID,
|
OrganizationID: config.OrganizationID,
|
||||||
Source: coredata.ProfileSourceSAML,
|
Source: coredata.ProfileSourceSAML,
|
||||||
State: coredata.ProfileStateActive,
|
State: coredata.ProfileStateActive,
|
||||||
|
ActivatedAt: &now,
|
||||||
FullName: fullname,
|
FullName: fullname,
|
||||||
CreatedAt: now,
|
CreatedAt: now,
|
||||||
UpdatedAt: now,
|
UpdatedAt: now,
|
||||||
@@ -332,7 +333,7 @@ func (s *Service) HandleAssertion(
|
|||||||
return fmt.Errorf("cannot insert membership profile: %w", err)
|
return fmt.Errorf("cannot insert membership profile: %w", err)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if profile.State == coredata.ProfileStateInactive {
|
if profile.State == coredata.ProfileStateDeactivated {
|
||||||
return NewUserInactiveError(profile.ID)
|
return NewUserInactiveError(profile.ID)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -150,7 +150,7 @@ func (s *Service) CreateUser(
|
|||||||
|
|
||||||
profileState := coredata.ProfileStateActive
|
profileState := coredata.ProfileStateActive
|
||||||
if !attrs.Active {
|
if !attrs.Active {
|
||||||
profileState = coredata.ProfileStateInactive
|
profileState = coredata.ProfileStateDeactivated
|
||||||
}
|
}
|
||||||
|
|
||||||
var externalIdPtr *string
|
var externalIdPtr *string
|
||||||
@@ -536,8 +536,8 @@ func (s *Service) updateUser(
|
|||||||
previousMembership := *membership
|
previousMembership := *membership
|
||||||
previousUser := webhooktypes.NewUser(&previousProfile, &previousMembership)
|
previousUser := webhooktypes.NewUser(&previousProfile, &previousMembership)
|
||||||
|
|
||||||
shouldReactivate := attrs.Active != nil && *attrs.Active && profile.State == coredata.ProfileStateInactive
|
shouldReactivate := attrs.Active != nil && *attrs.Active && profile.State == coredata.ProfileStateDeactivated
|
||||||
shouldDeactivate := attrs.Active != nil && !*attrs.Active && profile.State == coredata.ProfileStateActive
|
shouldDeactivate := attrs.Active != nil && !*attrs.Active && profile.State != coredata.ProfileStateDeactivated
|
||||||
|
|
||||||
if attrs.FullName != "" {
|
if attrs.FullName != "" {
|
||||||
profile.FullName = attrs.FullName
|
profile.FullName = attrs.FullName
|
||||||
@@ -748,11 +748,9 @@ func (s *Service) updateUser(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if shouldReactivate {
|
if shouldReactivate {
|
||||||
profile.State = coredata.ProfileStateActive
|
profile.MarkActive(now)
|
||||||
profile.UpdatedAt = now
|
|
||||||
} else if shouldDeactivate {
|
} else if shouldDeactivate {
|
||||||
profile.State = coredata.ProfileStateInactive
|
profile.MarkDeactivated(now)
|
||||||
profile.UpdatedAt = now
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.Source != coredata.ProfileSourceSCIM {
|
if profile.Source != coredata.ProfileSourceSCIM {
|
||||||
@@ -826,7 +824,15 @@ func applyUserAttributes(
|
|||||||
now time.Time,
|
now time.Time,
|
||||||
) {
|
) {
|
||||||
profile.Source = coredata.ProfileSourceSCIM
|
profile.Source = coredata.ProfileSourceSCIM
|
||||||
|
switch {
|
||||||
|
case state == coredata.ProfileStateActive && profile.State != coredata.ProfileStateActive:
|
||||||
|
profile.MarkActive(now)
|
||||||
|
case state == coredata.ProfileStateDeactivated && profile.State != coredata.ProfileStateDeactivated:
|
||||||
|
profile.MarkDeactivated(now)
|
||||||
|
default:
|
||||||
profile.State = state
|
profile.State = state
|
||||||
|
}
|
||||||
|
|
||||||
profile.FullName = attrs.FullName
|
profile.FullName = attrs.FullName
|
||||||
profile.Position = &attrs.Title
|
profile.Position = &attrs.Title
|
||||||
profile.UserName = &attrs.UserName
|
profile.UserName = &attrs.UserName
|
||||||
@@ -956,15 +962,14 @@ func (s *Service) deactivateProfileInTx(
|
|||||||
profile *coredata.MembershipProfile,
|
profile *coredata.MembershipProfile,
|
||||||
membership *coredata.Membership,
|
membership *coredata.Membership,
|
||||||
) error {
|
) error {
|
||||||
if profile.State == coredata.ProfileStateInactive {
|
if profile.State == coredata.ProfileStateDeactivated {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
previousUser := webhooktypes.NewUser(profile, membership)
|
previousUser := webhooktypes.NewUser(profile, membership)
|
||||||
|
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
profile.State = coredata.ProfileStateInactive
|
profile.MarkDeactivated(now)
|
||||||
profile.UpdatedAt = now
|
|
||||||
|
|
||||||
if err := profile.Update(ctx, tx, scope); err != nil {
|
if err := profile.Update(ctx, tx, scope); err != nil {
|
||||||
return fmt.Errorf("cannot deactivate profile: %w", err)
|
return fmt.Errorf("cannot deactivate profile: %w", err)
|
||||||
|
|||||||
@@ -364,7 +364,7 @@ func (s SessionService) OpenPasswordChildSessionForOrganization(
|
|||||||
return fmt.Errorf("cannot load profile: %w", err)
|
return fmt.Errorf("cannot load profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.State == coredata.ProfileStateInactive {
|
if profile.State == coredata.ProfileStateDeactivated {
|
||||||
return NewUserInactiveError(profile.ID)
|
return NewUserInactiveError(profile.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -469,7 +469,7 @@ func (s SessionService) OpenSAMLChildSessionForOrganization(
|
|||||||
return fmt.Errorf("cannot load profile: %w", err)
|
return fmt.Errorf("cannot load profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.State == coredata.ProfileStateInactive {
|
if profile.State == coredata.ProfileStateDeactivated {
|
||||||
return NewUserInactiveError(profile.ID)
|
return NewUserInactiveError(profile.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -562,7 +562,7 @@ func (s SessionService) OpenOIDCChildSessionForOrganization(
|
|||||||
return fmt.Errorf("cannot load profile: %w", err)
|
return fmt.Errorf("cannot load profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.State == coredata.ProfileStateInactive {
|
if profile.State == coredata.ProfileStateDeactivated {
|
||||||
return NewUserInactiveError(profile.ID)
|
return NewUserInactiveError(profile.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -651,7 +651,7 @@ func (s SessionService) AssumeOrganizationSession(
|
|||||||
return fmt.Errorf("cannot load profile: %w", err)
|
return fmt.Errorf("cannot load profile: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
if profile.State == coredata.ProfileStateInactive {
|
if profile.State == coredata.ProfileStateDeactivated {
|
||||||
return NewUserInactiveError(profile.ID)
|
return NewUserInactiveError(profile.ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -30,9 +30,10 @@ type Profile implements Node {
|
|||||||
|
|
||||||
enum ProfileState
|
enum ProfileState
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") {
|
||||||
|
PENDING @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStatePending")
|
||||||
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive")
|
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive")
|
||||||
INACTIVE
|
DEACTIVATED
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateDeactivated")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum ProfileSource
|
enum ProfileSource
|
||||||
@@ -71,6 +72,7 @@ enum ProfileOrderField
|
|||||||
input ProfileFilter {
|
input ProfileFilter {
|
||||||
contractEnded: Boolean
|
contractEnded: Boolean
|
||||||
state: ProfileState
|
state: ProfileState
|
||||||
|
states: [ProfileState!]
|
||||||
query: String
|
query: String
|
||||||
role: MembershipRole
|
role: MembershipRole
|
||||||
kind: String
|
kind: String
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ func (r *identityResolver) Profiles(ctx context.Context, obj *types.Identity, fi
|
|||||||
if filter != nil {
|
if filter != nil {
|
||||||
filters = coredata.NewMembershipProfileFilter(filter.ContractEnded).WithMembership()
|
filters = coredata.NewMembershipProfileFilter(filter.ContractEnded).WithMembership()
|
||||||
|
|
||||||
|
if len(filter.States) > 0 {
|
||||||
|
filters.WithStates(filter.States...)
|
||||||
|
}
|
||||||
|
|
||||||
if filter.State != nil {
|
if filter.State != nil {
|
||||||
filters.WithState(*filter.State)
|
filters.WithState(*filter.State)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -188,6 +188,10 @@ func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organiza
|
|||||||
if filter != nil {
|
if filter != nil {
|
||||||
filters = coredata.NewMembershipProfileFilter(filter.ContractEnded).WithMembership()
|
filters = coredata.NewMembershipProfileFilter(filter.ContractEnded).WithMembership()
|
||||||
|
|
||||||
|
if len(filter.States) > 0 {
|
||||||
|
filters.WithStates(filter.States...)
|
||||||
|
}
|
||||||
|
|
||||||
if filter.State != nil {
|
if filter.State != nil {
|
||||||
filters.WithState(*filter.State)
|
filters.WithState(*filter.State)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ func (r *mutationResolver) DeactivateUser(ctx context.Context, input types.Deact
|
|||||||
_, err := r.iam.OrganizationService.UpdateUserState(
|
_, err := r.iam.OrganizationService.UpdateUserState(
|
||||||
ctx,
|
ctx,
|
||||||
input.ProfileID,
|
input.ProfileID,
|
||||||
coredata.ProfileStateInactive,
|
coredata.ProfileStateDeactivated,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
r.logger.ErrorCtx(ctx, "cannot deactivate profile", log.Error(err))
|
r.logger.ErrorCtx(ctx, "cannot deactivate profile", log.Error(err))
|
||||||
|
|||||||
@@ -9,9 +9,10 @@ type OrganizationContext {
|
|||||||
|
|
||||||
enum ProfileState
|
enum ProfileState
|
||||||
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") {
|
@goModel(model: "go.probo.inc/probo/pkg/coredata.ProfileState") {
|
||||||
|
PENDING @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStatePending")
|
||||||
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive")
|
ACTIVE @goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateActive")
|
||||||
INACTIVE
|
DEACTIVATED
|
||||||
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateInactive")
|
@goEnum(value: "go.probo.inc/probo/pkg/coredata.ProfileStateDeactivated")
|
||||||
}
|
}
|
||||||
|
|
||||||
enum MembershipRole
|
enum MembershipRole
|
||||||
@@ -69,6 +70,7 @@ input ProfileOrder
|
|||||||
input ProfileFilter {
|
input ProfileFilter {
|
||||||
contractEnded: Boolean
|
contractEnded: Boolean
|
||||||
state: ProfileState
|
state: ProfileState
|
||||||
|
states: [ProfileState!]
|
||||||
query: String
|
query: String
|
||||||
role: MembershipRole
|
role: MembershipRole
|
||||||
kind: String
|
kind: String
|
||||||
|
|||||||
@@ -119,6 +119,10 @@ func (r *organizationResolver) Profiles(ctx context.Context, obj *types.Organiza
|
|||||||
if filter != nil {
|
if filter != nil {
|
||||||
filters = coredata.NewMembershipProfileFilter(filter.ContractEnded).WithMembership()
|
filters = coredata.NewMembershipProfileFilter(filter.ContractEnded).WithMembership()
|
||||||
|
|
||||||
|
if len(filter.States) > 0 {
|
||||||
|
filters.WithStates(filter.States...)
|
||||||
|
}
|
||||||
|
|
||||||
if filter.State != nil {
|
if filter.State != nil {
|
||||||
filters.WithState(*filter.State)
|
filters.WithState(*filter.State)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2776,6 +2776,10 @@ func (r *Resolver) ListUsersTool(ctx context.Context, req *mcp.CallToolRequest,
|
|||||||
if input.Filter != nil {
|
if input.Filter != nil {
|
||||||
filter = coredata.NewMembershipProfileFilter(input.Filter.ContractEnded).WithMembership()
|
filter = coredata.NewMembershipProfileFilter(input.Filter.ContractEnded).WithMembership()
|
||||||
|
|
||||||
|
if len(input.Filter.States) > 0 {
|
||||||
|
filter.WithStates(input.Filter.States...)
|
||||||
|
}
|
||||||
|
|
||||||
if input.Filter.State != nil {
|
if input.Filter.State != nil {
|
||||||
filter.WithState(*input.Filter.State)
|
filter.WithState(*input.Filter.State)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -184,8 +184,9 @@ components:
|
|||||||
ProfileState:
|
ProfileState:
|
||||||
type: string
|
type: string
|
||||||
enum:
|
enum:
|
||||||
|
- PENDING
|
||||||
- ACTIVE
|
- ACTIVE
|
||||||
- INACTIVE
|
- DEACTIVATED
|
||||||
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ProfileState
|
go.probo.inc/mcpgen/type: go.probo.inc/probo/pkg/coredata.ProfileState
|
||||||
|
|
||||||
ProfileKind:
|
ProfileKind:
|
||||||
@@ -526,7 +527,7 @@ components:
|
|||||||
description: Profile source (MANUAL, SCIM, or SAML)
|
description: Profile source (MANUAL, SCIM, or SAML)
|
||||||
state:
|
state:
|
||||||
$ref: "#/components/schemas/ProfileState"
|
$ref: "#/components/schemas/ProfileState"
|
||||||
description: Profile state (ACTIVE or INACTIVE)
|
description: Profile state (PENDING, ACTIVE, or DEACTIVATED)
|
||||||
position:
|
position:
|
||||||
type:
|
type:
|
||||||
- string
|
- string
|
||||||
@@ -578,7 +579,12 @@ components:
|
|||||||
description: Filter by contract status. True returns only users with ended contracts, false returns only users with active or no contract.
|
description: Filter by contract status. True returns only users with ended contracts, false returns only users with active or no contract.
|
||||||
state:
|
state:
|
||||||
$ref: "#/components/schemas/ProfileState"
|
$ref: "#/components/schemas/ProfileState"
|
||||||
description: Filter by profile state (ACTIVE or INACTIVE)
|
description: Filter by profile state (PENDING, ACTIVE, or DEACTIVATED)
|
||||||
|
states:
|
||||||
|
type: array
|
||||||
|
items:
|
||||||
|
$ref: "#/components/schemas/ProfileState"
|
||||||
|
description: Filter by profile states (PENDING, ACTIVE, or DEACTIVATED)
|
||||||
query:
|
query:
|
||||||
type: string
|
type: string
|
||||||
description: Search by full name, email address, or position
|
description: Search by full name, email address, or position
|
||||||
|
|||||||
Reference in New Issue
Block a user