fix: Keep dangling project references from blanking the home view

`getManagerProjectsModelArray` mapped every project manager to its project
without checking that the project is in the store, so one row pointing at a
deleted project produced a null the caller then read `id` off — taking down
the whole home view. `getMembershipProjectsModelArray` had the same hole one
level up, destructuring `board.project` before testing `board`.

The projects endpoint fed exactly that state: it derived the id list for the
included records from the manager rows, so a `project_manager` row whose
project no longer exists shipped a manager for a project missing from `items`.
Board memberships had the same gap. Both id lists now come from the records
that actually exist.
This commit is contained in:
Daniel Hiller
2026-08-26 09:22:53 +02:00
parent 266246e242
commit 4add7e43af
2 changed files with 18 additions and 6 deletions
+6 -2
View File
@@ -569,10 +569,12 @@ export default class extends BaseModel {
return this.notificationServices.orderBy(['id.length', 'id']);
}
// A manager or membership row can outlive the project or board it points to,
// and a single dangling reference must not take the whole view down
getManagerProjectsModelArray() {
return this.getProjectManagersQuerySet()
.toModelArray()
.map(({ project: projectModel }) => projectModel);
.flatMap(({ project: projectModel }) => projectModel || []);
}
getMembershipProjectsModelArray() {
@@ -580,7 +582,9 @@ export default class extends BaseModel {
return this.getBoardMembershipsQuerySet()
.toModelArray()
.flatMap(({ board: { project: projectModel } }) => {
.flatMap(({ board: boardModel }) => {
const projectModel = boardModel && boardModel.project;
if (!projectModel || projectIds.includes(projectModel.id)) {
return [];
}
+12 -4
View File
@@ -124,17 +124,25 @@ module.exports = {
true,
);
const projectIds = [...managerProjectIds, ...membershipProjectIds];
const projects = await Project.qm.getByIds(projectIds);
const projects = await Project.qm.getByIds([...managerProjectIds, ...membershipProjectIds]);
if (sharedProjectIds) {
projectIds.push(...sharedProjectIds);
projects.push(...sharedProjects);
}
// A manager row can outlive its project, so the ids for everything included
// below come from the projects that actually exist, never from the memberships
const projectIds = sails.helpers.utils.mapRecords(projects);
const fullyVisibleBoards = await Board.qm.getByProjectIds(fullyVisibleProjectIds);
const boards = [...fullyVisibleBoards, ...membershipBoards];
const boardIdsSet = new Set(sails.helpers.utils.mapRecords(boards));
const availableBoardMemberships = boardMemberships.filter(({ boardId }) =>
boardIdsSet.has(boardId),
);
const projectFavorites = await ProjectFavorite.qm.getByProjectIdsAndUserId(
projectIds,
currentUser.id,
@@ -183,7 +191,7 @@ module.exports = {
projectManagers,
baseCustomFieldGroups,
boards,
boardMemberships,
boardMemberships: availableBoardMemberships,
customFields,
notificationServices,
users: sails.helpers.users.presentMany(users, currentUser),