Appearance
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 Name | Description | Data Type | Default Value | Required (= Nullable) | Unique | Format | Validations | Index | Example |
|---|---|---|---|---|---|---|---|---|---|
| id | Primary key of the entity. | UUID | Generated in code (app layer) | Yes | Yes | UUID v7 | - | Primary Key | 018ed0b3-c298-7c7a-96d5-8b36f5a7f8d2 |
| oid | Stable 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 | - | No | Yes (when set) | Opaque identity-provider identifier | Required once status is active or beyond | name: idx_users_oid, type: btree | 00000000-0000-0000-0000-000000000000 |
| The person's email address. Refreshed from the identity provider's claims on every sign-in — see note above. | String | - | Yes | No | Email address | Non-empty | name: idx_users_email, type: btree (active records) | jane.doe@example.com | |
| firstName | The person's first name. Set at invitation; refreshed from the identity provider's given_name claim on sign-in when supplied. | String | - | Yes | No | - | Non-empty | - | Jane |
| lastName | The person's last name. Set at invitation; refreshed from the identity provider's family_name claim on sign-in when supplied. | String | - | Yes | No | - | Non-empty | - | Doe |
| phoneNumber | The 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 | - | No | No | - | - | - | +420 601 123 456 |
| isPorsennaUser | Whether 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 | - | Yes | No | - | - | name: idx_users_is_porsenna_user, type: btree (active records) | false |
| status | Lifecycle status of the user record itself. | Enum (UserStatus) | invited | Yes | No | - | One of: invited, active, suspended | name: idx_users_status, type: btree (active records) | active |
| createdAt | Timestamp of when the record was created. Immutable after insert. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 | Cannot be null; cannot be modified after creation. | - | 2025-03-16T18:00:00Z |
| updatedAt | Timestamp of the last update. Set on insert (equal to createdAt) and updated on every change. | Timestamp with time zone | now() — set in code | Yes | No | ISO 8601 | Cannot be null. | - | 2025-03-16T18:00:00Z |
| deletedAt | Timestamp of soft deletion. Null means the record is active. Once set, immutable. | Timestamp with time zone | - | No | No | ISO 8601 | Immutable once set. Active records: WHERE deletedAt IS NULL | name: idx_users_deleted_at, type: btree (active records) | 2025-06-01T09:00:00Z |
| createdBy | Identifier of the actor who created the record. | String | - | Yes | No | type:actor — e.g. user:uuid or system:migration | Non-empty. | - | system:migration |
| updatedBy | Identifier of the actor who last updated the record. | String | - | Yes | No | type:actor — e.g. user:uuid or system:migration | Non-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 ownentityId/createdAt/createdBy, which already identify the record and the write.