Skip to content
Updated Sep 26, 2026 by Barča Dvořáková · Owner: analysisactiveentity Edit on GitHub

Entity: user ​

Entity Type: Database table

Description: Represents a single, platform-wide identity for a person who accesses EM3 — whether a Porsenna platform operator or a client-side user. A user is identified once, regardless of how many tenants they access; per-tenant access is granted separately, either directly (see permissionSetUserAssignment) or through a group (see permissionSetGroupAssignment and tenantUser). Authentication itself is performed by an external identity provider; this table exists to recognise a previously-authenticated person, and to hold the platform-level attributes needed to grant them access.

A row is created when an administrator invites a person (email + first/last name, phone number optional), before their identity-provider identity is known — oid is therefore not required until the person completes their first sign-in, at which point it is attached and status moves from invited to active.

Decided (stakeholder requirement): firstName and lastName are separate stored columns; there is no single displayName column. Every form and table that shows a person's name keeps the two fields separate. displayName exists only as a presentation-only, derived value — never stored — computed as lastName + " " + firstName, used only in overview-style listings that show one combined name column; anywhere it appears in an API response it is computed at read time, not read from a column. email, firstName and lastName refresh from the identity provider's claims on every sign-in (given_name/family_name mapped to firstName/lastName where the provider supplies them); a locally-entered name is retained unchanged for a sign-in whose IdP claims don't include those fields.

Decided: this table has no platformRole column and no emailPending/displayNamePending confirmation-flow columns. email, firstName and lastName refresh from the identity provider's claims on every sign-in, so there is no local confirmation step and nothing needs to be held "pending". What a Porsenna platform operator can do is expressed as an ordinary platform-level (tenantId = null) permissionSet grant via permissionSetUserAssignment, the same mechanism used for every other role, rather than a fixed enum column on user.

Decided: the four notification-preference attributes (emailReporting, emailReminder, incidentEmailReporting, smsNotificationsEnabled) live on their own notificationPreference entity, and showUiHints lives on its own userPreference entity. Neither is a column on user.

Decided: isPorsennaUser records whether a person is a Porsenna employee — independently of what access they hold. It exists because a tenant's user list must never show Porsenna staff, even though a Porsenna person and a tenant person granted access via the direct-assignment edge case use the exact same permissionSetUserAssignment mechanism and so cannot be told apart by grant shape alone.

It must not be read as "platform-level access implies Porsenna staff." A platform-level (tenantId = null) grant is not exclusive to Porsenna employees: an external, non-Porsenna role (e.g. an "Admin-manažer" restricted to one or more client groups — see permissionSetUserAssignment and the clientGroup restriction dimension) is isPorsennaUser = false while still holding a platform-level grant. The flag tracks employment, not scope of access.

Data Attributes Table ​

Attribute NameDescriptionData TypeDefault ValueRequired (= Nullable)UniqueFormatValidationsIndexExample
idPrimary key of the entity.UUIDGenerated in code (app layer)YesYesUUID v7-Primary Key018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2
oidStable identity-provider subject identifier. The system's anchor for "this is the same person" across sign-ins. Null for an invited user who has not yet signed in; attached on their first successful sign-in.String-NoYes (when set)Opaque identity-provider identifierRequired once status is active or beyondname: idx_users_oid, type: btree00000000-0000-0000-0000-000000000000
emailThe person's email address. Refreshed from the identity provider's claims on every sign-in — see note above.String-YesNoEmail addressNon-emptyname: idx_users_email, type: btree (active records)jane.doe@example.com
firstNameThe person's first name. Set at invitation; refreshed from the identity provider's given_name claim on sign-in when supplied.String-YesNo-Non-empty-Jane
lastNameThe person's last name. Set at invitation; refreshed from the identity provider's family_name claim on sign-in when supplied.String-YesNo-Non-empty-Doe
phoneNumberThe person's phone number. Optional — never required at invitation or afterwards. Local to EM3 only: unlike email/firstName/lastName, never refreshed from the identity provider.String-NoNo---+420 601 123 456
isPorsennaUserWhether this person is a Porsenna employee (true) or not (false) — an identity fact about the person, independent of what they are granted. A non-Porsenna person can still hold a platform-level (tenantId = null) grant (e.g. a client-group-restricted Admin-manažer); a Porsenna employee without a fixed platform administrative role is still isPorsennaUser = true. Used to exclude Porsenna staff from any tenant-scoped user listing.Boolean-YesNo--name: idx_users_is_porsenna_user, type: btree (active records)false
statusLifecycle status of the user record itself.Enum (UserStatus)invitedYesNo-One of: invited, active, suspendedname: idx_users_status, type: btree (active records)active
createdAtTimestamp of when the record was created. Immutable after insert.Timestamp with time zonenow() — set in codeYesNoISO 8601Cannot be null; cannot be modified after creation.-2025-03-16T18:00:00Z
updatedAtTimestamp of the last update. Set on insert (equal to createdAt) and updated on every change.Timestamp with time zonenow() — set in codeYesNoISO 8601Cannot be null.-2025-03-16T18:00:00Z
deletedAtTimestamp of soft deletion. Null means the record is active. Once set, immutable.Timestamp with time zone-NoNoISO 8601Immutable once set. Active records: WHERE deletedAt IS NULLname: idx_users_deleted_at, type: btree (active records)2025-06-01T09:00:00Z
createdByIdentifier of the actor who created the record.String-YesNotype:actor — e.g. user:uuid or system:migrationNon-empty.-system:migration
updatedByIdentifier of the actor who last updated the record.String-YesNotype:actor — e.g. user:uuid or system:migrationNon-empty.-system:migration

Audited fields ​

Recorded (created/deleted: full set; updated: changed fields only): oid, email, firstName, lastName, phoneNumber, isPorsennaUser, status. displayName is not audited — it is never stored, only computed at read time from firstName/lastName.

Excluded:

  • id, createdAt, updatedAt, deletedAt, createdBy, updatedBy — redundant with the audit entry's own entityId/createdAt/createdBy, which already identify the record and the write.