`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.
203 lines
7.3 KiB
JavaScript
Executable File
203 lines
7.3 KiB
JavaScript
Executable File
/*!
|
|
* Copyright (c) 2024 PLANKA Software GmbH
|
|
* Licensed under the Fair Use License: https://github.com/plankanban/planka/blob/master/LICENSE.md
|
|
*/
|
|
|
|
/**
|
|
* @swagger
|
|
* /projects:
|
|
* get:
|
|
* summary: Get all accessible projects
|
|
* description: Retrieves all projects the current user has access to, including managed projects, membership projects, and shared projects (for admins).
|
|
* tags:
|
|
* - Projects
|
|
* operationId: getProjects
|
|
* responses:
|
|
* 200:
|
|
* description: Projects retrieved successfully
|
|
* content:
|
|
* application/json:
|
|
* schema:
|
|
* type: object
|
|
* required:
|
|
* - items
|
|
* - included
|
|
* properties:
|
|
* items:
|
|
* type: array
|
|
* items:
|
|
* allOf:
|
|
* - $ref: '#/components/schemas/Project'
|
|
* - type: object
|
|
* properties:
|
|
* isFavorite:
|
|
* type: boolean
|
|
* description: Whether the project is marked as favorite by the current user
|
|
* example: true
|
|
* included:
|
|
* type: object
|
|
* required:
|
|
* - users
|
|
* - projectManagers
|
|
* - backgroundImages
|
|
* - baseCustomFieldGroups
|
|
* - boards
|
|
* - boardMemberships
|
|
* - customFields
|
|
* - notificationServices
|
|
* properties:
|
|
* users:
|
|
* type: array
|
|
* description: Related users
|
|
* items:
|
|
* $ref: '#/components/schemas/User'
|
|
* projectManagers:
|
|
* type: array
|
|
* description: Related project managers
|
|
* items:
|
|
* $ref: '#/components/schemas/ProjectManager'
|
|
* backgroundImages:
|
|
* type: array
|
|
* description: Related background images
|
|
* items:
|
|
* $ref: '#/components/schemas/BackgroundImage'
|
|
* baseCustomFieldGroups:
|
|
* type: array
|
|
* description: Related base custom field groups
|
|
* items:
|
|
* $ref: '#/components/schemas/BaseCustomFieldGroup'
|
|
* boards:
|
|
* type: array
|
|
* description: Related boards
|
|
* items:
|
|
* $ref: '#/components/schemas/Board'
|
|
* boardMemberships:
|
|
* type: array
|
|
* description: Related board memberships (for current user)
|
|
* items:
|
|
* $ref: '#/components/schemas/BoardMembership'
|
|
* customFields:
|
|
* type: array
|
|
* description: Related custom fields
|
|
* items:
|
|
* $ref: '#/components/schemas/CustomField'
|
|
* notificationServices:
|
|
* type: array
|
|
* description: Related notification services (for managed projects)
|
|
* items:
|
|
* $ref: '#/components/schemas/NotificationService'
|
|
* 400:
|
|
* $ref: '#/components/responses/ValidationError'
|
|
* 401:
|
|
* $ref: '#/components/responses/Unauthorized'
|
|
*/
|
|
|
|
module.exports = {
|
|
async fn() {
|
|
const { currentUser } = this.req;
|
|
|
|
let sharedProjects;
|
|
let sharedProjectIds;
|
|
|
|
const managerProjectIds = await sails.helpers.users.getManagerProjectIds(currentUser.id);
|
|
const fullyVisibleProjectIds = [...managerProjectIds];
|
|
|
|
if (currentUser.role === User.Roles.ADMIN) {
|
|
sharedProjects = await Project.qm.getShared({
|
|
exceptIdOrIds: managerProjectIds,
|
|
});
|
|
|
|
sharedProjectIds = sails.helpers.utils.mapRecords(sharedProjects);
|
|
fullyVisibleProjectIds.push(...sharedProjectIds);
|
|
}
|
|
|
|
const boardMemberships = await BoardMembership.qm.getByUserId(currentUser.id);
|
|
const membershipBoardIds = sails.helpers.utils.mapRecords(boardMemberships, 'boardId');
|
|
|
|
const membershipBoards = await Board.qm.getByIds(membershipBoardIds, {
|
|
exceptProjectIdOrIds: fullyVisibleProjectIds,
|
|
});
|
|
|
|
const membershipProjectIds = sails.helpers.utils.mapRecords(
|
|
membershipBoards,
|
|
'projectId',
|
|
true,
|
|
);
|
|
|
|
const projects = await Project.qm.getByIds([...managerProjectIds, ...membershipProjectIds]);
|
|
|
|
if (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,
|
|
);
|
|
|
|
const projectManagers = await ProjectManager.qm.getByProjectIds(projectIds);
|
|
|
|
const userIds = sails.helpers.utils.mapRecords(projectManagers, 'userId', true);
|
|
const users = await User.qm.getByIds(userIds);
|
|
|
|
const backgroundImages = await BackgroundImage.qm.getByProjectIds(projectIds);
|
|
|
|
const baseCustomFieldGroups = await BaseCustomFieldGroup.qm.getByProjectIds(projectIds);
|
|
const baseCustomFieldGroupsIds = sails.helpers.utils.mapRecords(baseCustomFieldGroups);
|
|
|
|
const customFields =
|
|
await CustomField.qm.getByBaseCustomFieldGroupIds(baseCustomFieldGroupsIds);
|
|
|
|
let notificationServices = [];
|
|
if (managerProjectIds.length > 0) {
|
|
const managerProjectIdsSet = new Set(managerProjectIds);
|
|
|
|
const managerBoardIds = boards.flatMap((board) =>
|
|
managerProjectIdsSet.has(board.projectId) ? board.id : [],
|
|
);
|
|
|
|
notificationServices = await NotificationService.qm.getByBoardIds(managerBoardIds);
|
|
}
|
|
|
|
const isFavoriteByProjectId = projectFavorites.reduce(
|
|
(result, projectFavorite) => ({
|
|
...result,
|
|
[projectFavorite.projectId]: true,
|
|
}),
|
|
{},
|
|
);
|
|
|
|
projects.forEach((project) => {
|
|
// eslint-disable-next-line no-param-reassign
|
|
project.isFavorite = isFavoriteByProjectId[project.id] || false;
|
|
});
|
|
|
|
return {
|
|
items: projects,
|
|
included: {
|
|
projectManagers,
|
|
baseCustomFieldGroups,
|
|
boards,
|
|
boardMemberships: availableBoardMemberships,
|
|
customFields,
|
|
notificationServices,
|
|
users: sails.helpers.users.presentMany(users, currentUser),
|
|
backgroundImages: sails.helpers.backgroundImages.presentMany(backgroundImages),
|
|
},
|
|
};
|
|
},
|
|
};
|