TeamManager.java

  1. /*
  2.  * The coLAB project
  3.  * Copyright (C) 2021-2024 AlbaSim, MEI, HEIG-VD, HES-SO
  4.  *
  5.  * Licensed under the MIT License
  6.  */
  7. package ch.colabproject.colab.api.controller.team;

  8. import ch.colabproject.colab.api.controller.card.CardManager;
  9. import ch.colabproject.colab.api.controller.project.ProjectManager;
  10. import ch.colabproject.colab.api.controller.security.SecurityManager;
  11. import ch.colabproject.colab.api.controller.token.TokenManager;
  12. import ch.colabproject.colab.api.model.card.Card;
  13. import ch.colabproject.colab.api.model.project.Project;
  14. import ch.colabproject.colab.api.model.team.TeamMember;
  15. import ch.colabproject.colab.api.model.team.TeamRole;
  16. import ch.colabproject.colab.api.model.team.acl.HierarchicalPosition;
  17. import ch.colabproject.colab.api.model.user.User;
  18. import ch.colabproject.colab.api.persistence.jpa.team.TeamMemberDao;
  19. import ch.colabproject.colab.api.persistence.jpa.team.TeamRoleDao;
  20. import ch.colabproject.colab.generator.model.exceptions.HttpErrorMessage;
  21. import ch.colabproject.colab.generator.model.exceptions.MessageI18nKey;
  22. import org.slf4j.Logger;
  23. import org.slf4j.LoggerFactory;

  24. import javax.ejb.LocalBean;
  25. import javax.ejb.Stateless;
  26. import javax.inject.Inject;
  27. import java.util.List;
  28. import java.util.Objects;
  29. import java.util.stream.Collectors;

  30. /**
  31.  * Some logic to manage project teams
  32.  *
  33.  * @author maxence
  34.  */
  35. @Stateless
  36. @LocalBean
  37. public class TeamManager {

  38.     /** logger */
  39.     private static final Logger logger = LoggerFactory.getLogger(TeamManager.class);

  40.     /** Team members persistence */
  41.     @Inject
  42.     private TeamMemberDao teamMemberDao;

  43.     /** Team roles persistence */
  44.     @Inject
  45.     private TeamRoleDao teamRoleDao;

  46.     /** Project specific logic handling */
  47.     @Inject
  48.     private ProjectManager projectManager;

  49.     /** Card specific logic handling */
  50.     @Inject
  51.     private CardManager cardManager;

  52.     /** Token Facade */
  53.     @Inject
  54.     private TokenManager tokenManager;

  55.     /** Access control manager */
  56.     @Inject
  57.     private SecurityManager securityManager;

  58.     // *********************************************************************************************
  59.     // find team member
  60.     // *********************************************************************************************

  61.     /**
  62.      * Retrieve the team member. If not found, throw a {@link HttpErrorMessage}.
  63.      *
  64.      * @param memberId the id of the team member
  65.      *
  66.      * @return the team member if found
  67.      *
  68.      * @throws HttpErrorMessage if the team member was not found
  69.      */
  70.     public TeamMember assertAndGetMember(Long memberId) {
  71.         TeamMember member = teamMemberDao.findTeamMember(memberId);

  72.         if (member == null) {
  73.             logger.error("team member #{} not found", memberId);
  74.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_NOT_FOUND);
  75.         }

  76.         return member;
  77.     }

  78.     // *********************************************************************************************
  79.     // team members
  80.     // *********************************************************************************************
  81.     /**
  82.      * Add given user to the project teams
  83.      *
  84.      * @param project  the project
  85.      * @param user     the user
  86.      * @param position hierarchical position of the user
  87.      *
  88.      * @return the brand-new member
  89.      */
  90.     public TeamMember addMember(Project project, User user, HierarchicalPosition position) {
  91.         logger.debug("Add member {} in {}", user, project);

  92.         if (project == null) {
  93.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  94.         }

  95.         if (user != null && findMemberByProjectAndUser(project, user) != null) {
  96.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  97.         }

  98.         TeamMember teamMember = new TeamMember();

  99.         teamMember.setUser(user);
  100.         teamMember.setProject(project);
  101.         teamMember.setPosition(position);

  102.         teamMemberDao.persistTeamMember(teamMember);

  103.         project.getTeamMembers().add(teamMember);

  104.         return teamMember;
  105.     }

  106.     /**
  107.      * Get the members of the given project
  108.      *
  109.      * @param projectId the id of the project
  110.      *
  111.      * @return list of team members
  112.      */
  113.     public List<TeamMember> getTeamMembersForProject(Long projectId) {
  114.         logger.debug("Get team members of project #{}", projectId);

  115.         Project project = projectManager.assertAndGetProject(projectId);

  116.         return project.getTeamMembers();
  117.     }

  118.     /**
  119.      * Get all members of the given project
  120.      *
  121.      * @param id id of the project
  122.      *
  123.      * @return all members of the project team
  124.      */
  125.     public List<TeamMember> getTeamMembers(Long id) {
  126.         Project project = projectManager.assertAndGetProject(id);
  127.         logger.debug("Get team members: {}", project);

  128.         return project.getTeamMembers();
  129.     }

  130.     /**
  131.      * Find the teamMember who match the given project and the given user.
  132.      *
  133.      * @param project the project
  134.      * @param user    the user
  135.      *
  136.      * @return the teamMember or null
  137.      */
  138.     public TeamMember findMemberByProjectAndUser(Project project, User user) {
  139.         return teamMemberDao.findMemberByProjectAndUser(project, user);
  140.     }

  141.     /**
  142.      * Retrieve all users of the team members
  143.      *
  144.      * @param projectId the id of the project
  145.      *
  146.      * @return list of users
  147.      */
  148.     public List<User> getUsersForProject(Long projectId) {
  149.         return getTeamMembersForProject(projectId).stream()
  150.             .filter(m -> {
  151.                 return m.getUser() != null;
  152.             })
  153.             .map(m -> {
  154.                 return m.getUser();
  155.             })
  156.             .collect(Collectors.toList());
  157.     }

  158.     /**
  159.      * Are two user teammate?
  160.      *
  161.      * @param a a user
  162.      * @param b another user
  163.      *
  164.      * @return true if both user are both member of the same team
  165.      */
  166.     public boolean areUserTeammate(User a, User b) {
  167.         return teamMemberDao.findIfUserAreTeammate(a, b);
  168.     }

  169.     /**
  170.      * Update hierarchical position of a member
  171.      *
  172.      * @param memberId id of the member
  173.      * @param position new hierarchical position
  174.      */
  175.     public void updatePosition(Long memberId, HierarchicalPosition position) {
  176.         TeamMember member = teamMemberDao.findTeamMember(memberId);

  177.         if (member != null && position != null) {
  178.             if (position == HierarchicalPosition.OWNER
  179.                 || member.getPosition() == HierarchicalPosition.OWNER) {
  180.                 assertCurrentUserIsOwnerOfTheProject(member.getProject());
  181.             }

  182.             member.setPosition(position);
  183.             assertTeamIntegrity(member.getProject());

  184.         } else {
  185.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  186.         }
  187.     }

  188.     /**
  189.      * Make sure the team of a project make sense. It means:
  190.      * <ul>
  191.      * <li>at least one "owner"
  192.      * </ul>
  193.      *
  194.      * @param project the project to check
  195.      *
  196.      * @throws HttpErrorMessage if team is broken
  197.      */
  198.     public void assertTeamIntegrity(Project project) {

  199.         if (project.getTeamMembersByPosition(HierarchicalPosition.OWNER).isEmpty()) {
  200.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  201.         }
  202.     }

  203.     /**
  204.      * Make sure the current user is an owner of the given project
  205.      *
  206.      * @param project the project
  207.      *
  208.      * @throws HttpErrorMessage if not
  209.      */
  210.     private void assertCurrentUserIsOwnerOfTheProject(Project project) {
  211.         if (!securityManager.isCurrentUserOwnerOfTheProject(project)) {
  212.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  213.         }
  214.     }

  215.     /**
  216.      * Delete the given team member
  217.      *
  218.      * @param teamMemberId the id of the team member
  219.      */
  220.     public void deleteTeamMember(Long teamMemberId) {
  221.         TeamMember teamMember = assertAndGetMember(teamMemberId);

  222.         if (!checkDeletionAcceptability(teamMember)) {
  223.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  224.         }

  225.         // assignments deleted by cascade

  226.         // delete invitation token
  227.         tokenManager.deleteInvitationsByTeamMember(teamMember);

  228.         if (teamMember.getProject() != null) {
  229.             teamMember.getProject().getTeamMembers().remove(teamMember);
  230.         }

  231.         teamMemberDao.deleteTeamMember(teamMember);
  232.     }

  233.     /**
  234.      * Ascertain that the team member can be deleted
  235.      *
  236.      * @param teamMember the team member to check for deletion
  237.      *
  238.      * @return True iff it can be safely deleted
  239.      */
  240.     public boolean checkDeletionAcceptability(TeamMember teamMember) {
  241.         if (teamMember.getPosition() == HierarchicalPosition.OWNER &&
  242.             teamMember.getProject().getTeamMembersByPosition(HierarchicalPosition.OWNER)
  243.                 .size() < 2) {
  244.             return false;
  245.         }

  246.         return true;
  247.     }

  248.     // *********************************************************************************************
  249.     // Invitations and sharing
  250.     // *********************************************************************************************

  251.     /**
  252.      * Send invitation
  253.      *
  254.      * @param projectId id of the project
  255.      * @param email     send invitation to this address
  256.      *
  257.      * @return the pending new teamMember
  258.      */
  259.     public TeamMember invite(Long projectId, String email) {
  260.         Project project = projectManager.assertAndGetProject(projectId);
  261.         logger.debug("Invite {} to join {}", email, project);
  262.         return tokenManager.sendMembershipInvitation(project, email);
  263.     }

  264.     /**
  265.      * Create a token to share the project.
  266.      *
  267.      * @param projectId The id of the project that will become visible (mandatory)
  268.      * @param cardId    The id of the card that will become editable (optional)
  269.      *
  270.      * @return the URL to use to consume the token
  271.      */
  272.     public String generateSharingLinkToken(Long projectId, Long cardId) {
  273.         Project project = projectManager.assertAndGetProject(projectId);
  274.         Card card = cardManager.assertAndGetCard(cardId);
  275.         logger.debug("Generate sharing link token for project {} and card {}", project, card);
  276.         return tokenManager.generateSharingLinkToken(project, card);
  277.     }

  278.     /**
  279.      * Delete all sharing link tokens for the given project.
  280.      *
  281.      * @param projectId the id of the project
  282.      */
  283.     public void deleteSharingLinkTokensByProject(Long projectId) {
  284.         Project project = projectManager.assertAndGetProject(projectId);
  285.         logger.debug("Delete sharing link token for project {}", projectId);
  286.         tokenManager.deleteSharingLinkTokensByProject(project);
  287.     }

  288.     /**
  289.      * Delete all sharing link tokens for the given card.
  290.      *
  291.      * @param cardId the id of the card
  292.      */
  293.     public void deleteSharingLinkTokensByCard(Long cardId) {
  294.         Card card = cardManager.assertAndGetCard(cardId);
  295.         logger.debug("Delete sharing link token for card {}", cardId);
  296.         tokenManager.deleteSharingLinkTokensByCard(card);
  297.     }

  298.     // *********************************************************************************************
  299.     // Roles
  300.     // *********************************************************************************************

  301.     /**
  302.      * Retrieve the role. If not found, throw a {@link HttpErrorMessage}.
  303.      *
  304.      * @param roleId the id of the role
  305.      *
  306.      * @return the role if found
  307.      *
  308.      * @throws HttpErrorMessage if the role was not found
  309.      */
  310.     public TeamRole assertAndGetRole(Long roleId) {
  311.         TeamRole role = teamRoleDao.findRole(roleId);

  312.         if (role == null) {
  313.             logger.error("team role #{} not found", roleId);
  314.             throw HttpErrorMessage.dataError(MessageI18nKey.DATA_NOT_FOUND);
  315.         }

  316.         return role;
  317.     }

  318.     /**
  319.      * Get all the roles defined in the given project
  320.      *
  321.      * @param id the project
  322.      *
  323.      * @return list of roles
  324.      */
  325.     public List<TeamRole> getProjectRoles(Long id) {
  326.         Project project = projectManager.assertAndGetProject(id);
  327.         return project.getRoles();
  328.     }

  329.     /**
  330.      * Get the team roles defined in the given project
  331.      *
  332.      * @param projectId the id of the project
  333.      *
  334.      * @return list of team roles
  335.      */
  336.     public List<TeamRole> getTeamRolesForProject(Long projectId) {
  337.         logger.debug("Get team roles of project #{}", projectId);

  338.         Project project = projectManager.assertAndGetProject(projectId);

  339.         return project.getRoles();
  340.     }

  341.     /**
  342.      * Create a role. The role must have a projectId set.
  343.      *
  344.      * @param role role to create
  345.      *
  346.      * @return the brand new persisted role
  347.      */
  348.     public TeamRole createRole(TeamRole role) {
  349.         if (role.getProjectId() != null) {
  350.             Project project = projectManager.assertAndGetProject(role.getProjectId());
  351.             if (project.getRoleByName(role.getName()) == null) {
  352.                 project.getRoles().add(role);
  353.                 role.setProject(project);
  354.                 return role;
  355.             }
  356.         }
  357.         throw HttpErrorMessage.dataError(MessageI18nKey.DATA_INTEGRITY_FAILURE);
  358.     }

  359.     /**
  360.      * Delete role
  361.      *
  362.      * @param roleId id of the role to delete
  363.      */
  364.     public void deleteRole(Long roleId) {
  365.         TeamRole role = teamRoleDao.findRole(roleId);
  366.         if (role != null) {
  367.             Project project = role.getProject();
  368.             if (project != null) {
  369.                 project.getRoles().remove(role);
  370.             }
  371.             role.getMembers().forEach(member -> {
  372.                 List<TeamRole> roles = member.getRoles();
  373.                 if (roles != null) {
  374.                     roles.remove(role);
  375.                 }
  376.             });
  377.             teamRoleDao.deleteRole(role);
  378.         }
  379.     }

  380.     /**
  381.      * Give a role to someone. The role and the member must belong to the very same project.
  382.      *
  383.      * @param roleId   id of the role
  384.      * @param memberId id of the teamMember
  385.      */
  386.     public void giveRole(Long roleId, Long memberId) {
  387.         TeamRole role = teamRoleDao.findRole(roleId);
  388.         TeamMember member = teamMemberDao.findTeamMember(memberId);
  389.         if (role != null && member != null) {
  390.             if (Objects.equals(role.getProject(), member.getProject())) {
  391.                 List<TeamMember> members = role.getMembers();
  392.                 List<TeamRole> roles = member.getRoles();
  393.                 if (!members.contains(member)) {
  394.                     members.add(member);
  395.                 }
  396.                 if (!roles.contains(role)) {
  397.                     roles.add(role);
  398.                 }
  399.             } else {
  400.                 throw HttpErrorMessage.badRequest();
  401.             }
  402.         }
  403.     }

  404.     /**
  405.      * Remove a role from someone.
  406.      *
  407.      * @param roleId   id of the role
  408.      * @param memberId id of the member
  409.      */
  410.     public void removeRole(Long roleId, Long memberId) {
  411.         TeamRole role = teamRoleDao.findRole(roleId);
  412.         TeamMember member = teamMemberDao.findTeamMember(memberId);
  413.         if (role != null && member != null) {
  414.             List<TeamMember> members = role.getMembers();
  415.             List<TeamRole> roles = member.getRoles();
  416.             members.remove(member);
  417.             roles.remove(role);
  418.         }
  419.     }
  420. }