WorkspaceGroupMembership

WorkspaceGroupMembership represents a single person's membership within a workspace group, capturing the lifecycle from invitation through acceptance or revocation. It associates a People record with a WorkspaceGroup, carries the assigned role, invitation token, and status, and is soft-deleted on revocation so the audit trail is preserved. A partial unique index on (person_pk, workspace_group_pk) WHERE deleted_at IS NULL enforces the idempotent-invite guard at the schema level: at most one live membership per person-group pair, while allowing revoke-then-re-invite semantics.

NamingValue
ObjectWorkspaceGroupMembership
Resource type (JSON:API type)people
Collection / records rootโ€” (not a records root)
REST base/v1/workspace-group-memberships
Entity classWorkspaceGroupMembership

Internal object. Not currently exposed on the public REST API. The operations below describe the intended contract.

API operations

OperationMethod & pathStatus
ListGET /v1/workspace-group-memberships๐ŸŸก Planned
RetrieveGET /v1/workspace-group-memberships/{id}๐ŸŸก Planned
CreatePOST /v1/workspace-group-memberships๐ŸŸก Planned
UpdatePATCH /v1/workspace-group-memberships/{id}๐ŸŸก Planned
DeleteDELETE /v1/workspace-group-memberships/{id}๐ŸŸก Planned

Data model

Attributes

FieldTypeRequiredConstraintsAllowed valuesDescription
workspace_group_membership_idstring (UUID)โœ… Yesunique; generated by gen_random_uuid() at row creationany valid UUID v4Public stable identifier for the membership. Exposed as the JSON:API resource id. Never changes after creation.
membership_rolestring (enum)โœ… YesNOT NULL; DB default 'member'owner | admin | member | guestThe role assigned to the person within the group. Set at invitation time. Changeable via PATCH /v1/workspace-groups/memberships/:membership_id by an owner or admin.
statusstring (enum)โœ… YesNOT NULL; DB default 'pending'pending | activeLifecycle state of the membership. Starts as 'pending' on invitation; flips to 'active' when the invitee accepts via POST .../accept.
invite_tokenstring (UUID) | nullโšช Nounique; nullableany valid UUID v4, or nullOne-time cryptographic token sent to the invitee. Cleared (set to null) or consumed when the invitation is accepted. The accept endpoint validates this token; the membership_id alone is not sufficient proof.
is_defaultbooleanโœ… YesNOT NULL; DB default falsetrue | falseMarks whether this membership is the person's default group membership for the workspace. Used to determine the group selected by default in UI contexts. DB-defaulted to false.
created_at๐Ÿ”’ system โ€” datetimeโœ… YesNOT NULLISO 8601 datetimeTimestamp of row creation. Set once by the onCreate MikroORM hook; never updated.
updated_at๐Ÿ”’ system โ€” datetime | nullโšช NonullableISO 8601 datetime, or nullTimestamp of the most recent mutation. Set by onCreate and refreshed by onUpdate hooks.
deleted_at๐Ÿ”’ system โ€” datetime | nullโšช NonullableISO 8601 datetime, or nullSoft-delete timestamp. Set when a membership is revoked. A non-null value excludes the row from active queries and from the partial unique index on (person_pk, workspace_group_pk), allowing revoke-then-re-invite.

Relationships

NameTypeRequiredDescription
personto-one (ManyToOne)Yes โ€” NOT NULL FKThe People record being granted membership in the group. FK: workspace_group_memberships.person_pk โ†’ peoples.pk (ON DELETE RESTRICT by default).
workspace_groupto-one (ManyToOne)Yes โ€” NOT NULL FKThe WorkspaceGroup this membership belongs to. FK: workspace_group_memberships.workspace_group_pk โ†’ workspace_groups.pk (ON DELETE RESTRICT by default).
invited_byto-one (ManyToOne, nullable)No โ€” nullable FKThe People record of the actor who issued the invitation. Nullable; SET NULL on delete of the inviter. Null for system-created memberships.

System-computed

  • workspace_group_membership_id โ€” generated by gen_random_uuid() at INSERT; unique constraint enforced at the DB level
  • created_at โ€” set once by MikroORM onCreate hook (new Date()); never overwritten
  • updated_at โ€” set by onCreate hook and refreshed on every UPDATE by onUpdate hook
  • deleted_at โ€” set to current timestamp by the revocation path (WorkspaceGroupService.revokeMembership); NULL for live rows
  • Partial unique index workspace_group_memberships_person_group_active_unique on (person_pk, workspace_group_pk) WHERE deleted_at IS NULL โ€” enforced at the schema level; the service's idempotent-invite guard relies on this index to prevent duplicate pending/active memberships
  • invite_token โ€” generated as a UUID by the service's invite flow (WorkspaceGroupService.inviteToGroup); consumed/cleared on accept; the token is the cryptographic proof validated by the accept endpoint rather than the membership_id alone
  • status transitions โ€” 'pending' on creation (DB default); 'active' set by the service on a successful acceptInvite call; no direct user-patch path for status

Example

{
  "data": {
    "type": "workspace-group-membership",
    "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
    "attributes": {
      "membership_role": "member",
      "status": "active",
      "invite_token": null,
      "is_default": false,
      "created_at": "2026-05-29T10:15:00.000Z",
      "updated_at": "2026-05-30T08:22:00.000Z"
    },
    "relationships": {
      "person": {
        "data": { "type": "people", "id": "b2c3d4e5-f6a7-8901-bcde-f12345678901" }
      },
      "workspace_group": {
        "data": { "type": "workspace-group", "id": "c3d4e5f6-a7b8-9012-cdef-123456789012" }
      },
      "invited_by": {
        "data": { "type": "people", "id": "d4e5f6a7-b8c9-0123-defa-234567890123" }
      }
    }
  }
}
Source: apps/api/src/database/entities/WorkspaceGroupMembership.ts ยท domain: workspace ยท tier: Platform