enrolInStarter method

Future<void> enrolInStarter(
  1. String domainId
)

Adds a curated starter to the learner's library. Nothing is copied — the enrolment is a reference, so progress is recorded against the shared content and fixes to the curriculum reach every learner.

Idempotent: re-enrolling is a no-op rather than a primary-key error.

Implementation

Future<void> enrolInStarter(String domainId) async {
  final userId = _client.auth.currentUser?.id;
  if (userId == null) {
    throw StateError('Cannot enrol in a starter: user is not authenticated');
  }
  // `ignoreDuplicates` makes this ON CONFLICT DO NOTHING rather than an
  // UPDATE. That matters: migration 072 deliberately grants no UPDATE policy
  // on this table ("the row is a membership fact with nothing mutable on
  // it"), so a merge-style upsert took the UPDATE path on the second call and
  // failed RLS with 42501 — i.e. tapping "add to my library" twice threw.
  // Re-enrolling is a genuine no-op, so DO NOTHING is also the honest
  // semantics.
  await _client
      .from('user_starter_enrollments')
      .upsert(
        {'user_id': userId, 'domain_id': domainId},
        onConflict: 'user_id,domain_id',
        ignoreDuplicates: true,
      );
}