Skip to content

bug(seed): marketing seed leaves members without role assignments — programme part comboboxes empty #252

Description

@mindsers

Reproduction

  1. pnpm tsx app/database/seed-marketing.ts
  2. Log in as marc.dupont@demo.unitae.app / demo1234.
  3. Open a programme event → Assign a part → the speaker (and reader) combobox is empty. Same for service roles.

Root cause

app/database/seed-marketing.ts:687 creates every publisher via prisma.member.create(...) directly, bypassing the Member aggregate at app/features/publishers/server/member.aggregate.ts.

The aggregate is the only place that calls syncBuiltInRoleAssignments (app/shared/domain/built-in-roles.server.ts:76), which reads a member's flags (isPublisher, isMale, baptismDate, isHelder, isServant, …) and populates MemberRoleAssignment rows for the built-in identity roles (member, publisher, brother, sister, elder, pioneer, …).

Net effect: seeded members have zero MemberRoleAssignment rows — not even the always-true member role.

The assign-part loader chain then falls over:

  • app/features/events/routes/programs/events/assign-part.tsx loader
  • loadPartAssignmentCandidates (app/features/events/server/assign-part-loader.server.ts)
  • resolveEligibleUserIds(db, allowedRoleIds=[], congregationId) (app/features/events/server/allowed-roles.server.ts:23-38)

With no explicit allowed roles on the template part, the fallback looks up the built-in member role and returns members with that role assignment. That query returns [], so speakerCandidates and readerCandidates end up empty.

Additional risk: if the marketing seed runs on a fresh DB (no prior pnpm prisma db seed), the built-in Role rows don't exist for the congregation either. seed.ts:47 calls seedBuiltInRoles(prisma, defaultCongregation.id); seed-marketing.ts doesn't.

Fix

Two additions to seed-marketing.ts:

  1. Call seedBuiltInRoles(prisma, congId) right after the congregation is created / updated so the built-in Role rows exist regardless of whether the regular seed ran first.
  2. Call syncBuiltInRoleAssignments(prisma, member.id, congId, null) after each prisma.member.create(...) so the MemberRoleAssignment rows exist.
import { seedBuiltInRoles } from '~/shared/domain/setup.server'
import { syncBuiltInRoleAssignments } from '~/shared/domain/built-in-roles.server'

// …after congregation exists
await seedBuiltInRoles(prisma, congId)

// …after each member.create
const member = await prisma.member.create({ data: {} })
await syncBuiltInRoleAssignments(prisma, member.id, congId, null)

Bonus: CI hole

scripts/check-aggregate-boundaries.ts:30 regex matches db.member.* writes but not prisma.member.*. That's why this bypass never showed up as a boundary violation. Worth a follow-up to either widen the regex or explicitly allowlist app/database/** in the script (today the seed exemption lives in prose in docs/development/architecture-conventions.md but not in the enforcement).

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    Status
    Wishlist

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions