Legal — Code Map
API
| File | Role |
|---|---|
apps/api/src/legal/legal.module.ts | Imports UsersModule; provides and exports LegalService for consent recording/status |
apps/api/src/legal/legal.controller.ts | Public GET /legal/documents; authenticated POST /legal/consents; GET /legal/consents/status?organizationId=… adds server-resolved isRequired and organizationId per row |
apps/api/src/legal/legal.service.ts | Resolves current localized documents, records exact-version evidence, deduplicates external acknowledgement by organization, and returns version/scoped consent status |
apps/api/src/legal/dto/record-consent.dto.ts | Validates { types: LegalDocumentType[], context, organizationId?, locale? } |
apps/api/src/users/users.service.ts | getLegalRequirements(userId, organizationId?) returns { types, organizationId } for the selected role: member privacy/health/member terms, owner platform Terms, or no requirements for admins/coaches until staff-specific terms exist. Pending-only member onboarding can add organization-scoped external acknowledgement; active-member backstop never does. getMissingMemberJoinLegalDocumentTypes keeps member invitations pending until required consent; staff creation does not validate or record member acceptance |
apps/api/src/users/users.controller.ts | /users/me?organizationId=… uses the same selected-org resolution as web: valid explicit org, then membership role priority; stale/omitted selections fall back |
apps/api/src/join/join.service.ts | Public resolve returns required requiredLegalDocumentTypes; invitation request and authenticated direct accept independently resolve member terms and server payment configuration; reject missing acceptance before membership/invite mutation and record consent |
apps/api/src/testing/testing.service.ts | seedBase publishes the en base documents and records role-specific consent for owner and member personas; coaches have no required document until staff-specific terms exist, and the new-user owner consents inside onboarding. Without published documents the gate treats every required type as owed and affected e2e personas land on /accept-terms |
apps/api/src/join/join.controller.ts | Join resolve, invite request, and authenticated direct-accept endpoints |
DB
| File | Role |
|---|---|
libs/db/src/lib/schema/legal.ts | legal_documents, legal_consents + relations |
libs/db/src/lib/schema/enums.ts | legal_document_type (7 values, including member_terms and external_payment_acknowledgement), consent_context (4 values) |
libs/db/drizzle/0125_needy_zuras.sql | Transactional enum replacement plus three-locale publication of member terms and external-payment acknowledgement |
libs/db/drizzle/0127_legal_document_locales.sql | Adds missing English/Russian publications for existing required documents and reconstructs only the privacy/fitness rows omitted from proven unified member-onboarding acceptance |
libs/db/drizzle/meta/{_journal.json,0127_snapshot.json} | Registers the custom data migration after the schema migrations |
Shared
| File | Role |
|---|---|
libs/shared/src/lib/schemas/legal.schema.ts | LegalDocumentType, ConsentContext, exact-version consent response shapes including server isRequired and resolved organizationId |
libs/shared/src/lib/schemas/join-link.schema.ts | Required PublicJoinInfo.requiredLegalDocumentTypes; invite acceptance booleans/locale and independent analytics choice |
Web
| File | Role |
|---|---|
apps/web/src/components/legal/legal-acceptance-form.tsx | Reusable acceptance dialog. Modes: standalone (with submit) or embedded (parent controls; reports tick state through the optional onAcceptedChange(accepted) and calls onAccepted when all boxes are ticked, used by the onboarding setup sheet). Links out to usetaikan.com/{slug} for full document text |
apps/web/src/app/api/legal-document/route.ts | Web proxy for fetching the marketing-site canonical HTML |
apps/web/src/i18n/dictionaries/{en,he,ru}.json | legal.* keys for checkbox labels, errors, “Documents updated” banner copy |
apps/web/src/app/[lang]/(protected)/accept-terms/page.tsx | Uses server isRequired && requiresReconsent; folds member terms into the core required legal choice and keeps conditional external acknowledgement separate |
apps/marketing | Canonical member terms (member-terms) and external-payment responsibility (external-payment-responsibility) in Hebrew root, /en, and /ru; platform Terms remain a separate owner-only B2B document |
Mobile (taikan-mobile)
| Path | Responsibility |
|---|---|
src/components/legal/legal-consent-form.tsx | Dynamic server-required document list; one required member checkbox includes privacy, health acknowledgement, and member terms; only conditional external-payment responsibility remains a separate required control. Optional analytics/replay remains unchecked and separately disclosed |
src/hooks/use-legal.ts | Compatibility document/status types and canonical document URL mapping, including member terms/external responsibility |
src/lib/analytics.ts | Consent-gated PostHog construction and explicit replay masking/log/network settings |
app/accept-terms.tsx | Passes server-required types into the shared consent form |
i18n hot keys
legal.acceptTos, legal.acceptPrivacy, legal.viewDocument, legal.updatedBanner, legal.submit, plus localized unified member-agreement and separate external-payment-acknowledgement labels. Display fitness_waiver as Fitness & Health Acknowledgement without changing its type/slug or waiving mandatory rights. Analytics consent remains independent.