Skip to content

Contributor

Purpose

TBD by human

Identity & key fields

  • Primary key: id (uuid, default gen_random_uuid()).
  • activityId (uuid, FK → activities.activities.id, on-delete cascade).
  • memberId (uuid, FK → companies.company_member.id, on-delete cascade) — contributors are always backed by a companyMembers row, see ADR contributors-must-be-members (v4) and ADR global-user-identity D9.
  • roles (array of enum contributor_role: ACTOR, DIRECTOR, SPEAKER, COACH, HOST) — non-empty.
  • order (integer, default 0).

business meaning: TBD by human

Invariants

  • memberId is NOT NULL — every contributor IS a companyMembers row (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/activities.schema.ts).
  • UNIQUE constraint contributors_activity_id_member_id_unique on (activityId, memberId) — 1:1 mapping between activity and member (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/activities.schema.ts).
  • CHECK constraint contributors_roles_not_emptyarray_length(roles, 1) >= 1 (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/activities.schema.ts).
  • Both FKs use ON DELETE CASCADE (enforced in tktspace-backend/libs/shared/data-access-db/src/lib/schema/activities.schema.ts).
  • The schema comment explicitly notes that personName, the singular role, description, and avatarUrl columns are intentionally absent — see ADR contributors-must-be-members.
  • Identity reads via member.user.* and member.user.publicProfile.* (per ADR contributors-must-be-members v4, amended by ADR global-user-identity D9). The previous “every name/avatar/bio render goes through member.*” rule is updated to “through member.user.* and member.user.publicProfile.*”. Reads cross the join contributor → companyMember → users → user_public_profile — projection is in libs/features/activities/src/lib/services/activity-contributors.service.ts.

business invariants: TBD by human

Lifecycle

No explicit lifecycle / status column.

Relationships

  • Activity (ENT-005) — activityIdactivities.activities.id, on-delete cascade. N:1.
  • Company member (ENT-019) — memberIdcompanies.company_member.id, on-delete cascade. N:1. The contributor → member FK with cascade still stands; v4 of the contributors ADR only changes the read path, not the graph topology.
  • User (ENT-021) — read-only join through member.user. Source of globalName and avatarUrl for renders. No FK on this table.
  • User public profile (ENT-042) — read-only join through member.user.publicProfile. Source of bio, specializations, links, slug, verifiedAt, coverPhotoUrl. No FK on this table.

API surfaces

SurfaceExposedNotes
clientyes — ContributorPreviewDto, ContributorMemberPreviewDto referenced from activity detail. Field-stable after the v4 ADR — keys publicName, avatarUrl, bio, specializations, links preserved; values now projected from users.* and user_public_profile.*. Ratings addition (AC-9): ContributorPreviewDto.ratingSummary?: RatingSummaryPreviewDto added — optional, absent for offline contributors (member.userId IS NULL); see Contributor review. Search (Phase C): a contributor’s global identity (member.user.id = users.id) is the key for contributor-aware activity search (?contributorUserId=) and session filter; users.full_name (globalName) is matched in search Tier B via pg_trgm. See Activity API surfaces and ADR contributor-aware-search-and-session-filter. Autocomplete endpoint (trainer filter): GET /api/client/contributors (operationId: contributorsClientList) returns ContributorAutocompleteItemDto[] = { userId, publicName, avatarUrl } — ranked by future-session count (empty q) or pg_trgm / ILIKE name match (non-empty q). Scope: published contributors only. Auth: public. See ADR activities-trainer-autocomplete.Swagger UI
businessyes — /activities/{activityId}/contributors, /activities/{activityId}/contributors/{contributorId}, CreateContributorDto, UpdateContributorDto, ContributorMemberPreviewAdminDto. Restructured — identity nested under user.globalName, user.avatarUrl, user.publicProfile.*. Autocomplete endpoint (trainer filter): GET /api/business/contributors (operationId: contributorsAdminList) returns the same ContributorAutocompleteItemDto[] shape, scoped to the active company (from @ActiveCompany() header via CompanyRolesGuard). Auth: JWT + READ_ACTIVITIES. Scope: contributors on any activity of the active company regardless of publication status. See ADR activities-trainer-autocomplete.Swagger UI
super-adminno

The client surface exposes display-oriented previews (flat shape preserved for zero template churn); the business surface exposes write operations and the admin-only nested member preview shape. Per-surface divergence is intentional — see ADR global-user-identity D9 “Field-shape per surface”.

Known gotchas / open questions

  • Per schema comment, the model is “members-only”: there is no support for guest / external contributors — they would have to be created as companyMembers first.
  • Roles array is intentionally an array (the singular role was dropped) — multi-role contributors are explicit.
  • Identity reads cross-context. After ADR contributors-must-be-members v4 (amended by ADR global-user-identity D9), the identity read path is contributor → member → user → user_public_profile. The four-step join is encapsulated in activity-contributors.service.ts; consumers should not re-invent it.
  • Client vs business DTO divergence. The client-surface preview keeps flat field names (publicName, bio, …); the business-surface preview nests them under user.publicProfile.*. Same logical entity, two intentional JSON shapes — per the “never share types between surfaces” rule.