core PK: id 10 required 1 unique

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.

20
Attributes
9
Indexes
10
Validation Rules
29
CRUD Operations

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
btree

Columns: owner_id

idx_contacts_organization_id
btree

Columns: organization_id

idx_contacts_owner_org
btree

Columns: owner_id, organization_id

idx_contacts_status
btree

Columns: status

idx_contacts_org_status
btree

Columns: organization_id, status

idx_contacts_full_name_trgm
gin

Columns: full_name

idx_contacts_last_activity_at
btree

Columns: last_activity_at

idx_contacts_is_peer_mentor
btree

Columns: organization_id, is_peer_mentor

idx_contacts_created_at
btree

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
always

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
on_create

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
on_delete

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
on_create

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
on_create

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
on_update

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
always

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
always

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
always

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
on_create

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.

Storage Configuration

Storage Type
primary_table
Location
main_db
Partitioning
No Partitioning
Retention
Permanent Storage

Entity Relationships

activities
incoming many_to_one

An activity is typically linked to the specific contact who received peer mentor support

optional
contact_notes
outgoing one_to_many

Each contact may have multiple chronological notes documenting interactions and follow-up actions

optional cascade delete
organizations
outgoing many_to_one

Contacts are scoped to an organization for RLS data isolation and custom field resolution

required
event_participants
incoming many_to_one

Event participants may be linked to contact records for non-user attendees

optional
users
incoming one_to_many

A user maintains a list of contacts they work with as a peer mentor or coordinator

required
workshop_participants
incoming many_to_one

Workshop participants are linked to contact records representing the attendees

optional