Проект - oyboy/Jora GitHub Wiki
Для работы с сущностью предусмотрен один контроллер - ProjectController, в котором определены два get- post-метода. Один служит для вывода списка проектов (см. п. Пользователь-Проект-Роль), newProject и createProject нужны для создания проекта, joinToProject - для присоединения к проекту.
Основная работа двух post-методов происходит через ProjectService.
public void saveProject(Project project, User user){
//Избежание ситуации, когда проект с данным хешем уже существует
while (projectRepository.findIdByHash(project.getHash()) != null) project.setHash(project.generateHash());
//Связывание проекта с пользователями
log.info("Попытка связать {} \n\t с {}", project, user);
UserProjectRole userProjectRole = new UserProjectRole();
userProjectRole.setProject(project);
userProjectRole.setUser(user);
userProjectRole.setRole(Role.ROLE_LEADER);
project.getUserProjectRoles().add(userProjectRole);
user.getUserProjectRoles().add(userProjectRole);
//Сохранение
projectRepository.save(project);
log.info("Saving project: {}", project);
userProjectRoleReposirory.save(userProjectRole);
}
Тут происходит сохранение проекта, а также связанной таблицы для проекта и пользователя. Поскольку создание проекта происходит при помощи отдельных форм, покрытых аннотацией @Valid, здесь нет необходимости создавать новые исключения. Другое дело обстоит с добавлением пользователя к проекту.
public void addUserToProject(String project_hash, User user) throws CustomException.UserAlreadyJoinedException,
CustomException.UserBannedException{
Project project = projectRepository.findProjectByHash(project_hash);
// Проверяем существует ли уже связь пользователя с проектом
if (userProjectRoleReposirory.existsByUserIdAndProjectId(user.getId(), project.getId())) {
if (userProjectRoleReposirory.isUserBanned(user.getId(), project.getId())) {
log.warn("User {} is banned in project {}", user.getId(), project.getId());
throw new CustomException.UserBannedException("Пользователь забанен в этом проекте");
}
log.warn("User {} is already a member of project {}", user.getId(), project.getId());
throw new CustomException.UserAlreadyJoinedException("Пользователь уже добавлен к проекту"); // Или выбрасываем исключение, если нужно
}
UserProjectRole userProjectRole = new UserProjectRole();
userProjectRole.setProject(project);
userProjectRole.setUser(user);
userProjectRole.setRole(Role.ROLE_PARTICIPANT);
log.info("Saving relation: {}", userProjectRole);
userProjectRoleReposirory.save(userProjectRole);
}
Здесь уже в контроллере нет аннотации @Valid, поэтому потенциальные проблемы придётся выявлять обращением к бд с помощью двух логических запросов и созданием собственных исключений (см. CustomException).
И последний метод - вывод доступных пользователю проектов:
public List<Project> getProjectsForUser(User user){
return userProjectRoleReposirory.findProjectsByUserId(user.getId());
}
Два запроса, проверяющих существование пользователя и не забанен ли он:
@Query("SELECT COUNT(*) > 0 " +
"FROM UserProjectRole upr " +
"WHERE upr.user.id = :userId AND upr.project.id = :projectId")
boolean existsByUserIdAndProjectId(@Param("userId") Long userId, @Param("projectId") Long projectId);
@Query("SELECT upr.banned " +
"FROM UserProjectRole upr " +
"WHERE upr.user.id = :userId AND upr.project.id = :projectId")
boolean isUserBanned(@Param("userId") Long userId, @Param("projectId") Long projectId);
В home реализована обычная итерация по списку проектов, переданных в Model.attribute, и вывод их полей. В create-project создано два поля под заголовок и описание. ВАЖНО! Для проверки наличия ошибок нужно использовать getFieldError, иначе будет непонятное исключение (якобы переданный объект - null):
<#if errors?? && errors.getFieldError("title")??>
<div class="error">${errors.getFieldError("title").defaultMessage}</div>
</#if>
Эта одна сущность, объединяющая в себе все перечисленные. Она нужна для создания n:n связи между пользователями и проектами и более упрощённого взаимодействия: достаточно обратиться всего к одной таблице, вместо того, чтобы объединять те, что привязаны только к одной сущности.
@Entity
@Data
public class UserProjectRole {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
private User user;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "project_id")
private Project project;
@Enumerated(EnumType.STRING)
private Role role;
private boolean banned;
}
Позднее было добавлено новое поле banned, которое используется для бана пользователя в некотором проекте. Зачем? Мне кажется это более упрощённая процедура взаимодействия с "нехорошими" пользователями, чем ручное подтверждение каждой заявки на вступление.
Тут участвуют HomeController и ProjectService. Контроллер
@ModelAttribute(name = "projects")
public List<Project> getProjects(){
User user = getUser();
return projectService.getProjectsForUser(user);
}
@GetMapping
public String home(){return "home";}
Сервис
public List<Project> getProjectsForUser(User user){
return userProjectRoleReposirory.findProjectsByUserId(user.getId());
}
Поиск проектов осуществляется новым репозиторием по id пользователя. В список попадают те, в которых пользователь не забанен:
@Query("SELECT p " +
"FROM Project p " +
"JOIN UserProjectRole upr " +
"ON p.id = upr.project.id " +
"WHERE upr.user.id = :userId AND upr.banned = false")
List<Project> findProjectsByUserId(@Param("userId") Long userId);