Skip to content

ProjectTeamMember schema is the right join table but is bypassed by redundant fields on User and Project #2155

@trillium

Description

@trillium

Overview

The codebase tracks project membership and manager status in four separate places simultaneously. ProjectTeamMember was designed as the canonical join table for this relationship, but the app never fully committed to it — resulting in redundant, potentially out-of-sync fields on both User and Project that are used in its place.

Action Items

Research and document the current inconsistency

The four representations of the same two facts ("is a member" / "is a manager"):

Field Model Tracks
User.projects[] User basic project membership
User.managedProjects[] User manager status (denormalized on user)
Project.managedByUsers[] Project manager status (denormalized on project)
ProjectTeamMember.vrmsProjectAdmin ProjectTeamMember manager status (join table, currently unused)

The updateManagedProjects controller keeps User.managedProjects and Project.managedByUsers in sync with each other, but it never touches ProjectTeamMember.vrmsProjectAdmin. Any code reading vrmsProjectAdmin may see stale data.

Evaluate consolidating onto ProjectTeamMember as the single source of truth

ProjectTeamMember already has everything needed:

ProjectTeamMember {
  userId,
  projectId,
  teamMemberStatus,  // Active / Inactive
  vrmsProjectAdmin,  // Boolean — manager flag (already exists, currently bypassed)
  roleOnProject,     // Developer, PM, UX, etc.
}

If the app read/wrote through this model exclusively:

  • "Is user a member?" → ProjectTeamMember.findOne({ userId, projectId })
  • "Is user a manager?" → ProjectTeamMember.findOne({ userId, projectId, vrmsProjectAdmin: true })
  • "All of a user's projects with manager status?" → ProjectTeamMember.find({ userId }) — one query returns both facts per row

This would allow removing User.managedProjects, Project.managedByUsers, and User.projects as redundant fields.

If consolidation is approved, the migration would involve:

  1. Backfill — for every entry in User.managedProjects, find or create the corresponding ProjectTeamMember record and set vrmsProjectAdmin: true
  2. Update all write paths — the updateManagedProjects controller writes to ProjectTeamMember.vrmsProjectAdmin instead of the denormalized fields
  3. Update all read paths — anything reading User.managedProjects or Project.managedByUsers queries ProjectTeamMember instead
  4. Drop the redundant fields once all paths are migrated

Note: Issue #2149 (manager toggle feature) requires reading from ProjectTeamMember to know a user's non-manager memberships. That ticket should write to vrmsProjectAdmin to move toward the clean model rather than adding another consumer of the redundant fields.

Resources/Instructions

  • backend/models/projectTeamMember.model.js — join table schema
  • backend/models/user.model.jsprojects and managedProjects fields
  • backend/models/project.model.jsmanagedByUsers field
  • backend/controllers/user.controller.jsupdateManagedProjects (the current write path)
  • Related: As a manager, I want a Toggle Feature so that I can Enable Manager Access #2149 (manager toggle feature that surfaces this inconsistency)

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    Status

    New Issue Approval

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions