Contact
Data Entity
Description
Represents individuals that peer mentors and coordinators work with, including people who receive peer mentor support. Contacts hold standard and organization-specific custom field data and are the primary target entities referenced in activity registrations.
Data Structure
| Name | Type | Description | Constraints |
|---|---|---|---|
id |
uuid |
Immutable primary key generated at insert time via gen_random_uuid(). Used as the stable reference in all foreign key relationships (activities, contact_notes, event_participants, workshop_participants). | PKrequiredunique |
owner_id |
uuid |
The user (peer mentor or coordinator) who created and owns this contact record. Drives RLS policy: non-coordinators can only read and mutate contacts they own. | required |
organization_id |
uuid |
Scopes the contact to a single organization for RLS data isolation and custom field resolution. A contact is always visible within the owning organization's data boundary. | required |
full_name |
string |
The contact's full display name. Used as the primary identifier across contact lists, activity records, and reports. Organization-specific label overrides may rename the displayed field label but this column name is fixed. | required |
phone |
string |
Primary phone number for the contact. Stored as a normalized E.164 string where possible. Used for push notification targeting when contact is also a registered user. Either phone or email must be present. | - |
email |
string |
Email address for the contact. Optional but at least one of phone or email must be provided. Used for coordinator outreach and, in some organizations, for digital document delivery. | - |
date_of_birth |
datetime |
Optional date of birth, stored as a date-only value (time portion ignored). Used for age-based statistics in Bufdir reporting and for determining eligibility for certain programme types. | - |
area |
string |
Free-text geographic area label (city, district, municipality) indicating where the contact is located. Used for geographic filtering in the contact list and for coordinator area reporting breakdowns. Not a structured geography — no FK to a regions table. | - |
status |
enum |
Lifecycle status of the contact. Active contacts appear in search results and can be linked to new activities. Inactive contacts are hidden from default list views but preserved for historical records. Archived contacts are soft-deleted and excluded from all reporting. | required |
custom_fields |
json |
Organization-specific field values stored as a flat key-value JSON object. Keys correspond to field definitions in the org_labels table. Types and validation constraints are resolved at runtime from org_labels. Supports string, boolean, date, and select (single/multi) field types. | - |
address_line_1 |
string |
First line of the contact's street address. Optional. Used primarily for Blindeforbundet assignment dispatch where home address is included in encrypted assignment payloads. | - |
address_line_2 |
string |
Second address line (apartment, floor, c/o). Optional supplement to address_line_1. | - |
postal_code |
string |
Norwegian postal code (4 digits). Stored as a string to preserve leading zeros. Used together with area for geographic display and assignment matching. | - |
notes_count |
integer |
Denormalized count of associated contact_notes records. Updated via database trigger on contact_notes insert/delete. Avoids COUNT(*) queries on high-frequency contact list renders. | required |
last_activity_at |
datetime |
Timestamp of the most recently registered activity linked to this contact. Updated via trigger on activities insert. Enables sorting contact lists by recency of interaction and surfacing contacts who have not been visited recently. | - |
is_peer_mentor |
boolean |
Denormalized flag indicating whether this contact is also registered as a peer mentor in the peer_mentors table. Supports fast filtering in the contact list view switcher (Contacts vs Peer Mentors) without a join. | required |
language_preference |
string |
Preferred communication language for the contact (IETF language tag, e.g. 'nb', 'nn', 'se' for Northern Sami). Informs communication preferences and future multi-language report generation. NHF has noted Sami language support as a future requirement. | - |
created_by |
uuid |
The user ID of the person who created this contact record. Immutable after insert. Used in audit trails and attribution. May differ from owner_id if a coordinator created the contact on behalf of a peer mentor. | required |
created_at |
datetime |
UTC timestamp set by the database server at row insertion. Immutable. Used for chronological ordering in admin views and for Bufdir reporting period scoping. | required |
updated_at |
datetime |
UTC timestamp automatically updated by a database trigger on any field change. Used to detect stale cache entries in the Contact Repository's local SQLite cache on mobile. | required |
Database Indexes
idx_contacts_owner_id
Columns: owner_id
idx_contacts_organization_id
Columns: organization_id
idx_contacts_owner_org
Columns: owner_id, organization_id
idx_contacts_status
Columns: status
idx_contacts_org_status
Columns: organization_id, status
idx_contacts_full_name_trgm
Columns: full_name
idx_contacts_last_activity_at
Columns: last_activity_at
idx_contacts_is_peer_mentor
Columns: organization_id, is_peer_mentor
idx_contacts_created_at
Columns: created_at
Validation Rules
full_name_not_empty
error
Validation failed
phone_or_email_required
error
Validation failed
email_format_valid
error
Validation failed
phone_format_valid
error
Validation failed
organization_id_valid
error
Validation failed
owner_id_in_same_organization
error
Validation failed
date_of_birth_range_valid
error
Validation failed
custom_fields_type_validation
error
Validation failed
archived_contact_immutable
error
Validation failed
postal_code_norwegian_format
warning
Validation failed
Business Rules
organization_scope_isolation
Every contact record is scoped to exactly one organization. Supabase RLS policies enforce that a user can only read or mutate contacts belonging to organizations they are a member of. Coordinators can access all contacts within their organizational scope; peer mentors can only access contacts they own (owner_id = auth.uid()).
contact_required_for_activity
Standard individual peer mentor activities must reference a valid contact_id. The contact must exist, be active, and belong to the same organization as the activity. Group events (event_participants) may use contacts without this restriction.
no_delete_with_activity_history
A contact record that has one or more associated activities, notes, or event_participants rows cannot be hard-deleted. The status must be set to 'archived' instead. This preserves historical activity records for Bufdir reporting integrity.
coordinator_can_create_on_behalf
Coordinators may create a contact with an owner_id pointing to a peer mentor within their organizational scope, enabling proxy contact management. The created_by field records the coordinator's ID. Non-coordinator roles cannot set owner_id to a different user.
custom_fields_match_org_schema
Values stored in the custom_fields JSON object must correspond to field keys defined in the org_labels table for the contact's organization_id. Keys not present in org_labels are stripped before persistence. Field value types are validated against the org_labels field_type definition (string, boolean, date, select).
status_transition_validation
Contact status transitions follow a defined lifecycle: active ↔ inactive (reversible), active/inactive → archived (irreversible without coordinator role). Only users with coordinator or admin roles can archive contacts or reverse an inactive status. Archived contacts cannot be restored to active by peer mentors.
peer_mentor_flag_consistency
The is_peer_mentor denormalized flag is kept in sync with the peer_mentors table via a database trigger. When a row is inserted into peer_mentors referencing a user whose user ID matches a contact's owner_id within the same organization, the flag is set to true. Deletion from peer_mentors resets the flag.
notes_count_trigger_consistency
The notes_count denormalized column is maintained by a database trigger on the contact_notes table (INSERT increments, DELETE decrements). Application code must not directly modify this column; all updates go through the trigger.
last_activity_at_trigger_update
The last_activity_at column is updated by a database trigger whenever an activity is inserted with a matching contact_id. This enables contact list sorting by most recently visited without scanning the activities table.
multi_org_contact_deduplication_check
For users who are members of multiple organizations (NHF multi-membership scenario), the system warns coordinators if a new contact record appears to duplicate an existing one in a sibling organization based on full_name + phone or email match. This is advisory, not blocking. Enforced as a warning to prevent accidental double-creation.
CRUD Operations
Storage Configuration
Entity Relationships
An activity is typically linked to the specific contact who received peer mentor support
Each contact may have multiple chronological notes documenting interactions and follow-up actions
Contacts are scoped to an organization for RLS data isolation and custom field resolution
Event participants may be linked to contact records for non-user attendees
A user maintains a list of contacts they work with as a peer mentor or coordinator
Workshop participants are linked to contact records representing the attendees