├── gradle └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── src ├── main │ ├── resources │ │ ├── messages_fr.properties │ │ ├── templates │ │ │ └── email-template.html │ │ ├── application-production.properties │ │ ├── application-docker.properties │ │ ├── application-development.properties │ │ ├── application.properties │ │ └── messages.properties │ └── java │ │ └── com │ │ └── projecty │ │ └── projectyweb │ │ ├── message │ │ ├── MessageType.java │ │ ├── attachment │ │ │ ├── AttachmentRepository.java │ │ │ ├── Attachment.java │ │ │ ├── AttachmentService.java │ │ │ ├── AttachmentController.java │ │ │ └── AttachmentAspect.java │ │ ├── association │ │ │ ├── AssociationRepository.java │ │ │ ├── Association.java │ │ │ └── AssociationService.java │ │ ├── MessageRepository.java │ │ ├── Message.java │ │ ├── MessageValidator.java │ │ ├── MessageAspect.java │ │ ├── MessageEmailAspect.java │ │ └── MessageController.java │ │ ├── task │ │ ├── TaskStatus.java │ │ ├── dto │ │ │ ├── TaskData.java │ │ │ └── ProjectTasksData.java │ │ ├── TaskRepository.java │ │ ├── Task.java │ │ ├── TaskValidator.java │ │ └── TaskAspect.java │ │ ├── project │ │ ├── role │ │ │ ├── ProjectRoles.java │ │ │ ├── NoAdminsInProjectException.java │ │ │ ├── dto │ │ │ │ └── ProjectRoleData.java │ │ │ ├── ProjectRoleRepository.java │ │ │ ├── ProjectRole.java │ │ │ ├── ProjectRoleController.java │ │ │ └── ProjectRoleService.java │ │ ├── ProjectRepository.java │ │ ├── InputUsernameList.java │ │ ├── dto │ │ │ ├── ProjectsData.java │ │ │ ├── ProjectData.java │ │ │ └── ProjectsTeamData.java │ │ ├── ProjectValidator.java │ │ ├── ProjectErrorHandler.java │ │ ├── Project.java │ │ ├── ProjectPermissionAspect.java │ │ ├── ProjectNotificationAspect.java │ │ └── ProjectController.java │ │ ├── notifications │ │ ├── NotificationObjectType.java │ │ ├── NotificationType.java │ │ ├── NotificationRepository.java │ │ ├── Notification.java │ │ ├── NotificationSimpAspect.java │ │ ├── NotificationController.java │ │ └── NotificationEmailAspect.java │ │ ├── team │ │ ├── TeamRepository.java │ │ ├── misc │ │ │ ├── TeamSummary.java │ │ │ └── TeamSummaryService.java │ │ ├── role │ │ │ ├── NoManagersInTeamException.java │ │ │ ├── TeamRoles.java │ │ │ ├── dto │ │ │ │ ├── TeamProjectsData.java │ │ │ │ └── TeamRoleData.java │ │ │ ├── TeamRoleRepository.java │ │ │ ├── TeamRole.java │ │ │ └── TeamRoleController.java │ │ ├── TeamValidator.java │ │ ├── Team.java │ │ ├── TeamErrorHandler.java │ │ ├── TeamAspect.java │ │ ├── TeamNotificationAspect.java │ │ └── TeamService.java │ │ ├── user │ │ ├── avatar │ │ │ ├── AvatarRepository.java │ │ │ ├── AvatarService.java │ │ │ └── Avatar.java │ │ ├── UserNotFoundException.java │ │ ├── RegisterForm.java │ │ ├── UserRepository.java │ │ ├── UserSerializer.java │ │ ├── UserController.java │ │ ├── User.java │ │ └── UserService.java │ │ ├── settings │ │ ├── SettingsRepository.java │ │ ├── SettingsController.java │ │ ├── Settings.java │ │ └── SettingsService.java │ │ ├── configurations │ │ ├── AnyPermission.java │ │ ├── EditPermission.java │ │ ├── SocketSecurityConfig.java │ │ ├── LocaleConfiguration.java │ │ └── SocketBrokerConfig.java │ │ ├── misc │ │ ├── RedirectMessageTypes.java │ │ ├── RedirectMessage.java │ │ ├── ApiError.java │ │ └── RestErrorHandler.java │ │ ├── ProjectyWebApplication.java │ │ ├── chat │ │ ├── UsernameLastChatMessageIdDTO.java │ │ ├── UserIdChatMessageCountDTO.java │ │ ├── dto │ │ │ └── ChatHistoryData.java │ │ ├── socket │ │ │ ├── SocketChatMessage.java │ │ │ └── ChatSocketController.java │ │ ├── ChatMessage.java │ │ ├── OffsetBasedPageRequest.java │ │ ├── ChatController.java │ │ └── ChatMessageRepository.java │ │ └── email │ │ └── EmailService.java └── test │ ├── resources │ ├── application-containers.properties │ ├── application-test.properties │ └── init_oauth_table.sql │ └── java │ └── com │ └── projecty │ └── projectyweb │ ├── ProjectyWebApplicationTests.java │ ├── team │ ├── TeamSummaryServiceTests.java │ └── TeamServiceTests.java │ ├── task │ └── TaskValidatorTests.java │ ├── project │ ├── ProjectValidatorTests.java │ ├── ProjectServiceTests.java │ └── role │ │ └── ProjectRoleServiceTests.java │ ├── message │ ├── AttachmentServiceTests.java │ ├── association │ │ └── AssociationServiceTest.java │ ├── attachment │ │ └── AttachmentControllerTests.java │ └── MessageValidatorTests.java │ ├── user │ ├── UserServiceTests.java │ └── UserControllerTests.java │ ├── settings │ ├── SettingsServiceTests.java │ └── SettingsControllerTests.java │ ├── notification │ ├── TeamNotificationAspectTests.java │ ├── NotificationServiceTests.java │ └── ProjectNotificationAspectTests.java │ └── chat │ └── ChatControllerTests.java ├── settings.gradle ├── Dockerfile ├── .github └── workflows │ └── gradle.yml ├── docker-compose.yaml ├── gradlew.bat ├── .gitignore └── README.md /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcinadd/projecty-web/HEAD/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /src/main/resources/messages_fr.properties: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/marcinadd/projecty-web/HEAD/src/main/resources/messages_fr.properties -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | repositories { 3 | gradlePluginPortal() 4 | } 5 | } 6 | rootProject.name = 'projecty-web' 7 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/MessageType.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | public enum MessageType { 4 | RECEIVED, SENT, ANY 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/task/TaskStatus.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.task; 2 | 3 | public enum TaskStatus { 4 | TO_DO, IN_PROGRESS, DONE 5 | } 6 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/role/ProjectRoles.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.role; 2 | 3 | public enum ProjectRoles { 4 | OWNER, ADMIN, USER 5 | } 6 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:13-alpine 2 | EXPOSE 8080 3 | WORKDIR /usr/local/bin/ 4 | COPY ./build/libs/projecty-web-0.0.1-SNAPSHOT.jar projecty-web.jar 5 | CMD ["java", "-Dspring.profiles.active=docker", "-jar", "projecty-web.jar"] 6 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/notifications/NotificationObjectType.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notifications; 2 | 3 | public enum NotificationObjectType { 4 | USER, PROJECT, TEAM, PROJECT_ROLE_NAME, TEAM_ROLE_NAME 5 | } 6 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/TeamRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface TeamRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/avatar/AvatarRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user.avatar; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface AvatarRepository extends JpaRepository { 6 | } 7 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/settings/SettingsRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.settings; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | public interface SettingsRepository extends JpaRepository { 6 | 7 | } 8 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/configurations/AnyPermission.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.configurations; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface AnyPermission { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/configurations/EditPermission.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.configurations; 2 | 3 | import java.lang.annotation.Retention; 4 | import java.lang.annotation.RetentionPolicy; 5 | 6 | @Retention(RetentionPolicy.RUNTIME) 7 | public @interface EditPermission { 8 | } 9 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/notifications/NotificationType.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notifications; 2 | 3 | public enum NotificationType { 4 | ADDED_TO_PROJECT, 5 | ADDED_TO_TEAM, 6 | CHANGED_PROJECT_ROLE, 7 | CHANGED_TEAM_ROLE 8 | // ASSIGNED_TO_TASK 9 | } 10 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/misc/TeamSummary.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.misc; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | @Getter 7 | @Setter 8 | public class TeamSummary { 9 | private int userCount; 10 | private int projectCount; 11 | private int taskCount; 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | public class UserNotFoundException extends RuntimeException { 4 | private static final long serialVersionUID = 1L; 5 | 6 | public String toString() { 7 | return "User not found!"; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/templates/email-template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Projecty 5 | 6 | 7 | 8 |

Projecty

9 |

10 | 11 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/ProjectRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | 5 | import java.util.Optional; 6 | 7 | public interface ProjectRepository extends JpaRepository { 8 | Optional findById(Long id); 9 | } 10 | -------------------------------------------------------------------------------- /src/main/resources/application-production.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost:3306/projecty-prod 2 | spring.datasource.username=root 3 | spring.datasource.password=123 4 | spring.jpa.hibernate.ddl-auto=update 5 | spring.servlet.multipart.enabled=true 6 | spring.servlet.multipart.max-file-size=50MB 7 | spring.servlet.multipart.max-request-size=50MB -------------------------------------------------------------------------------- /src/test/resources/application-containers.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.username=sa 2 | spring.datasource.password=sa 3 | spring.datasource.url=jdbc:tc:mysql:5.7.22:///projecty?TC_INITSCRIPT=./init_oauth_table.sql 4 | spring.datasource.driver-class-name=org.testcontainers.jdbc.ContainerDatabaseDriver 5 | spring.jpa.hibernate.ddl-auto=update 6 | spring.flyway.baseline-on-migrate=true -------------------------------------------------------------------------------- /.github/workflows/gradle.yml: -------------------------------------------------------------------------------- 1 | name: Java CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Set up JDK 11 11 | uses: actions/setup-java@v1 12 | with: 13 | java-version: 11 14 | - name: Build with Gradle 15 | run: ./gradlew build 16 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/role/NoManagersInTeamException.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.role; 2 | 3 | public class NoManagersInTeamException extends RuntimeException { 4 | /** 5 | * 6 | */ 7 | private static final long serialVersionUID = 1L; 8 | 9 | public String toString() { 10 | return "Team require to have at least one manager"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/attachment/AttachmentRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.attachment; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.UUID; 7 | 8 | @Repository 9 | public interface AttachmentRepository extends JpaRepository { 10 | 11 | } 12 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/role/NoAdminsInProjectException.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.role; 2 | 3 | public class NoAdminsInProjectException extends RuntimeException { 4 | /** 5 | * 6 | */ 7 | private static final long serialVersionUID = 1L; 8 | 9 | public String toString() { 10 | return "Project require to have at least one admin"; 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/task/dto/TaskData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.task.dto; 2 | 3 | import com.projecty.projectyweb.task.Task; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import java.util.List; 8 | 9 | @Getter 10 | @Setter 11 | public class TaskData { 12 | private Task task; 13 | private Long projectId; 14 | private List notAssignedUsernames; 15 | } 16 | -------------------------------------------------------------------------------- /src/main/resources/application-docker.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://database:3306/projecty 2 | spring.datasource.username=root 3 | spring.datasource.password=password 4 | spring.jpa.hibernate.ddl-auto=update 5 | spring.jpa.database-platform=org.hibernate.dialect.MySQL8Dialect 6 | spring.servlet.multipart.enabled=true 7 | spring.servlet.multipart.max-file-size=50MB 8 | spring.servlet.multipart.max-request-size=50MB 9 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/InputUsernameList.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import java.util.List; 4 | 5 | public class InputUsernameList { 6 | private List usernames; 7 | 8 | public List getUsernames() { 9 | return usernames; 10 | } 11 | 12 | public void setUsernames(List usernames) { 13 | this.usernames = usernames; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/misc/RedirectMessageTypes.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.misc; 2 | 3 | public enum RedirectMessageTypes { 4 | SUCCESS("alert-success"), 5 | FAILED("alert-danger"); 6 | private final String type; 7 | 8 | RedirectMessageTypes(String type) { 9 | this.type = type; 10 | } 11 | 12 | @Override 13 | public String toString() { 14 | return type; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/role/TeamRoles.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.role; 2 | 3 | public enum TeamRoles { 4 | MANAGER("MANAGER"), // 5 | MEMBER("MEMBER"); 6 | 7 | private final String teamRole; 8 | 9 | private TeamRoles(String teamRole) { 10 | this.teamRole = teamRole; 11 | } 12 | 13 | @Override 14 | public String toString() { 15 | return teamRole; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/RegisterForm.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.springframework.web.multipart.MultipartFile; 6 | 7 | @Getter 8 | @Setter 9 | public class RegisterForm { 10 | private String username; 11 | private String email; 12 | private String password; 13 | private String passwordRepeat; 14 | private MultipartFile avatar; 15 | } 16 | -------------------------------------------------------------------------------- /src/test/resources/application-test.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.driver-class-name=org.h2.Driver 2 | spring.datasource.url=jdbc:h2:mem:db;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE 3 | spring.datasource.username=sa 4 | spring.datasource.password=sa 5 | spring.flyway.url = ${spring.datasource.url} 6 | spring.flyway.user = ${spring.datasource.username} 7 | spring.flyway.password = ${spring.datasource.password} 8 | spring.flyway.schemas=projecty 9 | spring.flyway.baseline-on-migrate=true 10 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/role/dto/TeamProjectsData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.role.dto; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.util.List; 9 | 10 | @Getter 11 | @Setter 12 | @Builder 13 | public class TeamProjectsData { 14 | private String teamName; 15 | private List projects; 16 | private Boolean isCurrentUserTeamManager; 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/dto/ProjectsData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.dto; 2 | 3 | import com.projecty.projectyweb.project.role.dto.ProjectRoleData; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import java.util.List; 9 | 10 | @Getter 11 | @Setter 12 | @AllArgsConstructor 13 | public class ProjectsData { 14 | private List projectRoles; 15 | private List teamProjects; 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/avatar/AvatarService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user.avatar; 2 | 3 | import org.springframework.stereotype.Service; 4 | 5 | @Service 6 | public class AvatarService { 7 | private final AvatarRepository avatarRepository; 8 | 9 | public AvatarService(AvatarRepository avatarRepository) { 10 | this.avatarRepository = avatarRepository; 11 | } 12 | 13 | public void delete(Avatar avatar) { 14 | avatarRepository.delete(avatar); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/association/AssociationRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.association; 2 | 3 | import com.projecty.projectyweb.message.Message; 4 | import com.projecty.projectyweb.user.User; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public interface AssociationRepository extends JpaRepository { 11 | Optional findFirstByUserAndMessage(User user, Message message); 12 | } 13 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/notifications/NotificationRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notifications; 2 | 3 | import com.projecty.projectyweb.user.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.List; 8 | 9 | @Repository 10 | public interface NotificationRepository extends JpaRepository { 11 | List findByUser(User user); 12 | 13 | Long countByUserAndSeenFalse(User user); 14 | } 15 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/role/TeamRoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.role; 2 | 3 | import com.projecty.projectyweb.team.Team; 4 | import com.projecty.projectyweb.user.User; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | public interface TeamRoleRepository extends JpaRepository { 11 | Optional findByTeamAndAndUser(Team team, User user); 12 | List findByTeam(Team team); 13 | } 14 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/ProjectyWebApplication.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.scheduling.annotation.EnableAsync; 6 | 7 | @SpringBootApplication 8 | //@EnableJpaAuditing 9 | @EnableAsync 10 | public class ProjectyWebApplication { 11 | 12 | public static void main(String[] args) { 13 | SpringApplication.run(ProjectyWebApplication.class, args); 14 | } 15 | 16 | } -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/ProjectyWebApplicationTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.boot.test.context.SpringBootTest; 6 | import org.springframework.test.context.ActiveProfiles; 7 | import org.springframework.test.context.junit4.SpringRunner; 8 | 9 | @ActiveProfiles("test") 10 | @RunWith(SpringRunner.class) 11 | @SpringBootTest 12 | public class ProjectyWebApplicationTests { 13 | 14 | @Test 15 | public void contextLoads() { 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/dto/ProjectData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.dto; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.project.role.ProjectRole; 5 | import com.projecty.projectyweb.user.User; 6 | import lombok.Builder; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | import java.util.List; 11 | 12 | @Getter 13 | @Setter 14 | @Builder 15 | public class ProjectData { 16 | private Project project; 17 | private List projectRoles; 18 | private User currentUser; 19 | } 20 | -------------------------------------------------------------------------------- /src/main/resources/application-development.properties: -------------------------------------------------------------------------------- 1 | spring.datasource.url=jdbc:mysql://localhost:3306/projecty?createDatabaseIfNotExist=true 2 | spring.datasource.username=root 3 | spring.datasource.password=password 4 | spring.jpa.hibernate.ddl-auto=update 5 | spring.jpa.properties.hibernate.dialect = org.hibernate.dialect.MySQL8Dialect 6 | spring.servlet.multipart.enabled=true 7 | spring.servlet.multipart.max-file-size=50MB 8 | spring.servlet.multipart.max-request-size=50MB 9 | #logging.level.org.springframework.web=VERBOSE 10 | # logging.level.org.springframework=DEBUG 11 | security.oauth2.resource.filter-order=3 -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/task/dto/ProjectTasksData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.task.dto; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.task.Task; 5 | import lombok.Builder; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import java.util.List; 10 | 11 | @Getter 12 | @Setter 13 | @Builder 14 | public class ProjectTasksData { 15 | private List toDoTasks; 16 | private List inProgressTasks; 17 | private List doneTasks; 18 | private Project project; 19 | private Boolean hasPermissionToEdit; 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/UsernameLastChatMessageIdDTO.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat; 2 | 3 | public class UsernameLastChatMessageIdDTO { 4 | private String username; 5 | private Long lastChatMessageId; 6 | 7 | public UsernameLastChatMessageIdDTO(String username, Long lastChatMessageId) { 8 | this.username = username; 9 | this.lastChatMessageId = lastChatMessageId; 10 | } 11 | 12 | public String getUsername() { 13 | return username; 14 | } 15 | 16 | public Long getLastChatMessageId() { 17 | return lastChatMessageId; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/test/resources/init_oauth_table.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE oauth_access_token 2 | ( 3 | token_id varchar(255) DEFAULT NULL, 4 | token mediumblob, 5 | authentication_id varchar(255) NOT NULL, 6 | user_name varchar(255) DEFAULT NULL, 7 | client_id varchar(255) DEFAULT NULL, 8 | authentication mediumblob, 9 | refresh_token varchar(255) DEFAULT NULL, 10 | PRIMARY KEY (authentication_id) 11 | ); 12 | CREATE TABLE oauth_refresh_token 13 | ( 14 | token_id varchar(255) DEFAULT NULL, 15 | token mediumblob, 16 | authentication mediumblob 17 | ); -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.List; 7 | import java.util.Optional; 8 | import java.util.Set; 9 | 10 | @Repository 11 | public interface UserRepository extends JpaRepository { 12 | Optional findById(Long id); 13 | Optional findByUsername(String username); 14 | Set findByUsernameIn(List usernames); 15 | 16 | List findByUsernameStartsWith(String usernameStartWith); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/role/dto/TeamRoleData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.role.dto; 2 | 3 | import com.projecty.projectyweb.team.Team; 4 | import com.projecty.projectyweb.team.role.TeamRole; 5 | import com.projecty.projectyweb.team.role.TeamRoles; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | public class TeamRoleData { 12 | private Long id; 13 | private Team team; 14 | private TeamRoles name; 15 | 16 | public TeamRoleData(TeamRole teamRole) { 17 | this.id = teamRole.getId(); 18 | this.team = teamRole.getTeam(); 19 | this.name = teamRole.getName(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/ProjectValidator.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.validation.Errors; 5 | import org.springframework.validation.ValidationUtils; 6 | import org.springframework.validation.Validator; 7 | 8 | @Component 9 | public class ProjectValidator implements Validator { 10 | @Override 11 | public boolean supports(Class clazz) { 12 | return false; 13 | } 14 | 15 | @Override 16 | public void validate(Object target, Errors errors) { 17 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.empty"); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/TeamValidator.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.validation.Errors; 6 | import org.springframework.validation.ValidationUtils; 7 | import org.springframework.validation.Validator; 8 | 9 | @Component 10 | public class TeamValidator implements Validator { 11 | @Override 12 | public boolean supports(Class clazz) { 13 | return false; 14 | } 15 | 16 | @Override 17 | public void validate(Object target, Errors errors) { 18 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.empty.team.name"); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/avatar/Avatar.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user.avatar; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.projecty.projectyweb.user.User; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | import java.io.Serializable; 10 | import java.sql.Blob; 11 | 12 | @Entity 13 | @Getter 14 | @Setter 15 | public class Avatar implements Serializable { 16 | @Id 17 | @GeneratedValue 18 | private Long id; 19 | 20 | private String contentType; 21 | 22 | @JsonIgnore 23 | private Blob file; 24 | 25 | @OneToOne 26 | @MapsId 27 | @JsonIgnore 28 | private User user; 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/UserSerializer.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | import com.fasterxml.jackson.core.JsonGenerator; 4 | import com.fasterxml.jackson.databind.JsonSerializer; 5 | import com.fasterxml.jackson.databind.SerializerProvider; 6 | 7 | import java.io.IOException; 8 | 9 | public class UserSerializer extends JsonSerializer { 10 | @Override 11 | public void serialize(User value, JsonGenerator gen, SerializerProvider serializers) throws IOException { 12 | gen.writeStartObject(); 13 | gen.writeNumberField("id", value.getId()); 14 | gen.writeStringField("username", value.getUsername()); 15 | gen.writeEndObject(); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/role/dto/ProjectRoleData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.role.dto; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.project.role.ProjectRole; 5 | import com.projecty.projectyweb.project.role.ProjectRoles; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | @Getter 10 | @Setter 11 | public class ProjectRoleData { 12 | private Long id; 13 | private Project project; 14 | private ProjectRoles name; 15 | 16 | public ProjectRoleData(ProjectRole projectRole) { 17 | this.id = projectRole.getId(); 18 | this.project = projectRole.getProject(); 19 | this.name = projectRole.getName(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/role/ProjectRoleRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.role; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.user.User; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | 11 | @Repository 12 | public interface ProjectRoleRepository extends JpaRepository { 13 | Optional findRoleByUserAndProject(User user, Project project); 14 | 15 | List findByProjectOrderByIdAsc(Project project); 16 | int countByProjectAndName(Project project, ProjectRoles name); 17 | } 18 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/settings/SettingsController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.settings; 2 | 3 | import org.springframework.web.bind.annotation.*; 4 | 5 | @RestController 6 | @RequestMapping("settings") 7 | public class SettingsController { 8 | private final SettingsService settingsService; 9 | 10 | public SettingsController(SettingsService settingsService) { 11 | this.settingsService = settingsService; 12 | } 13 | 14 | @GetMapping 15 | public Settings getSettings() { 16 | return settingsService.getSettingsForCurrentUser(); 17 | } 18 | 19 | @PatchMapping 20 | public Settings patchSettings(@RequestBody Settings patchedSettings) { 21 | return settingsService.patchSettings(patchedSettings); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/misc/RedirectMessage.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.misc; 2 | 3 | public class RedirectMessage { 4 | private String text; 5 | private RedirectMessageTypes type; 6 | 7 | public String getText() { 8 | return text; 9 | } 10 | 11 | public void setText(String text) { 12 | this.text = text; 13 | } 14 | 15 | public RedirectMessageTypes getType() { 16 | return type; 17 | } 18 | 19 | public void setType(RedirectMessageTypes type) { 20 | this.type = type; 21 | } 22 | 23 | @Override 24 | public String toString() { 25 | return "RedirectMessage{" + 26 | "text='" + text + '\'' + 27 | ", type=" + type + 28 | '}'; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/UserIdChatMessageCountDTO.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat; 2 | 3 | public class UserIdChatMessageCountDTO { 4 | private Long userId; 5 | private Long unreadMessageCount; 6 | 7 | public UserIdChatMessageCountDTO(Long userId, Long unreadMessageCount) { 8 | this.userId = userId; 9 | this.unreadMessageCount = unreadMessageCount; 10 | } 11 | 12 | public Long getUserId() { 13 | return userId; 14 | } 15 | 16 | public Long getUnreadMessageCount() { 17 | return unreadMessageCount; 18 | } 19 | 20 | @Override 21 | public String toString() { 22 | return "UserIdChatMessageCountDTO{" + 23 | "userId=" + userId + 24 | ", messageCount=" + unreadMessageCount + 25 | '}'; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/misc/ApiError.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.misc; 2 | 3 | import org.springframework.http.HttpStatus; 4 | 5 | import java.util.Collections; 6 | import java.util.List; 7 | 8 | public class ApiError { 9 | private HttpStatus status; 10 | private List errors; 11 | 12 | public ApiError(HttpStatus status, List errors) { 13 | super(); 14 | this.status = status; 15 | this.errors = errors; 16 | } 17 | 18 | public ApiError(HttpStatus status, String error) { 19 | super(); 20 | this.status = status; 21 | errors = Collections.singletonList(error); 22 | } 23 | 24 | public HttpStatus getStatus() { 25 | return status; 26 | } 27 | 28 | public List getErrors() { 29 | return errors; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/dto/ProjectsTeamData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.dto; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.team.Team; 5 | import com.projecty.projectyweb.team.role.TeamRole; 6 | import com.projecty.projectyweb.team.role.TeamRoles; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | import java.util.List; 11 | 12 | @Getter 13 | @Setter 14 | public class ProjectsTeamData { 15 | private Long id; 16 | private Team team; 17 | private List projects; 18 | private TeamRoles role; 19 | 20 | public ProjectsTeamData(TeamRole teamRole) { 21 | this.id = teamRole.getId(); 22 | this.team = teamRole.getTeam(); 23 | this.projects = teamRole.getTeam().getProjects(); 24 | this.role = teamRole.getName(); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/task/TaskRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.task; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.user.User; 5 | import org.springframework.data.jpa.repository.JpaRepository; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.List; 9 | 10 | @Repository 11 | public interface TaskRepository extends JpaRepository { 12 | List findByProjectAndStatus(Project project, TaskStatus status); 13 | 14 | List findByProjectAndStatusOrderByStartDate(Project project, TaskStatus status); 15 | 16 | List findByProjectAndStatusOrderByEndDate(Project project, TaskStatus status); 17 | 18 | List findByAssignedUsersContainsAndStatusIsNot(User assignedUser, TaskStatus notStatus); 19 | 20 | Long countByProjectAndStatus(Project project, TaskStatus status); 21 | } 22 | -------------------------------------------------------------------------------- /src/main/resources/application.properties: -------------------------------------------------------------------------------- 1 | #This is the primary Application Property 2 | spring.application.name=Projecty Web 3 | 4 | #This is a default profile to override this pass the active profile as parameter while running 5 | spring.profiles.active=docker 6 | 7 | #supported languages 8 | il8n.supported-languages=fr,en 9 | # Keycloak 10 | keycloak.auth-server-url=http://localhost:8081/auth 11 | keycloak.ssl-required=none 12 | keycloak.realm=Projecty 13 | keycloak.resource=api-services 14 | keycloak.bearer-only=true 15 | keycloak.confidential-port=0 16 | keycloak.use-resource-role-mappings=false 17 | keycloak.principal-attribute=preferred_username 18 | keycloak.cors=true 19 | # SMTP configuration 20 | spring.mail.host=localhost 21 | spring.mail.properties.mail.transport.protocol=smtp 22 | spring.mail.properties.mail.smtp.port=25 23 | spring.mail.properties.mail.smtp.auth=false 24 | spring.mail.username=projecty@localhost -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/configurations/SocketSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.configurations; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.security.config.annotation.web.messaging.MessageSecurityMetadataSourceRegistry; 5 | import org.springframework.security.config.annotation.web.socket.AbstractSecurityWebSocketMessageBrokerConfigurer; 6 | 7 | @Configuration 8 | public class SocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { 9 | 10 | @Override 11 | protected boolean sameOriginDisabled() { 12 | return true; 13 | } 14 | 15 | @Override 16 | protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { 17 | messages 18 | .simpDestMatchers("/secured/**", "/secured/**/**").authenticated() 19 | .anyMessage().authenticated(); 20 | } 21 | } -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/association/Association.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.association; 2 | 3 | import com.projecty.projectyweb.message.Message; 4 | import com.projecty.projectyweb.user.User; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | 10 | @Entity 11 | @Getter 12 | @Setter 13 | public class Association { 14 | @Id 15 | @GeneratedValue 16 | private Long id; 17 | 18 | @ManyToOne(fetch = FetchType.LAZY) 19 | @JoinColumn(name = "user_id") 20 | private User user; 21 | 22 | @ManyToOne(fetch = FetchType.LAZY) 23 | @JoinColumn(name = "message_id") 24 | private Message message; 25 | 26 | @PreRemove 27 | public void preRemove() { 28 | if(message.getSender().equals(user)) { 29 | message.setSender(null); 30 | }else { 31 | message.setRecipient(null); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/dto/ChatHistoryData.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat.dto; 2 | 3 | import com.projecty.projectyweb.chat.ChatMessage; 4 | 5 | public class ChatHistoryData { 6 | private final ChatMessage lastMessage; 7 | private final long unreadMessageCount; 8 | 9 | public ChatHistoryData(ChatMessage lastMessage, long unreadMessageCount) { 10 | this.lastMessage = lastMessage; 11 | this.unreadMessageCount = unreadMessageCount; 12 | } 13 | 14 | public ChatMessage getLastMessage() { 15 | return lastMessage; 16 | } 17 | 18 | public long getUnreadMessageCount() { 19 | return unreadMessageCount; 20 | } 21 | 22 | @Override 23 | public String toString() { 24 | return "ChatMessageProjection{" + 25 | "lastMessage=" + lastMessage + 26 | ", unreadMessageCount=" + unreadMessageCount + 27 | '}'; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/MessageRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | import com.projecty.projectyweb.user.User; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | 9 | import java.util.List; 10 | 11 | public interface MessageRepository extends JpaRepository { 12 | Page findByRecipientAndHasReplyIsFalseOrderBySendDateDesc(User recipient, Pageable pageable); 13 | 14 | Page findBySenderAndHasReplyIsFalseOrderBySendDateDesc(User sender, Pageable pageable); 15 | 16 | @Query("select m from Message m where (m.sender=?1 or m.recipient=?1) and m.hasReply=false order by m.sendDate desc") 17 | Page findBySenderOrRecipientAndHasReplyIsFalseOrderBySendDateDesc(User user, Pageable pageable); 18 | 19 | List findByRecipientAndSeenDateIsNull(User recipient); 20 | } 21 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/socket/SocketChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat.socket; 2 | 3 | public class SocketChatMessage { 4 | private String sender; 5 | private String recipient; 6 | private String text; 7 | 8 | public String getSender() { 9 | return sender; 10 | } 11 | 12 | void setSender(String sender) { 13 | this.sender = sender; 14 | } 15 | 16 | public String getRecipient() { 17 | return recipient; 18 | } 19 | 20 | public void setRecipient(String recipient) { 21 | this.recipient = recipient; 22 | } 23 | 24 | public String getText() { 25 | return text; 26 | } 27 | 28 | public void setText(String text) { 29 | this.text = text; 30 | } 31 | 32 | @Override 33 | public String toString() { 34 | return "Message{" + 35 | "from='" + sender + '\'' + 36 | ", to='" + recipient + '\'' + 37 | ", text='" + text + '\'' + 38 | '}'; 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat; 2 | 3 | import com.projecty.projectyweb.user.User; 4 | import lombok.Getter; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.*; 8 | import javax.validation.constraints.NotBlank; 9 | import java.util.Date; 10 | 11 | @Entity 12 | @Getter 13 | @Setter 14 | public class ChatMessage { 15 | 16 | @Id 17 | @GeneratedValue 18 | private Long id; 19 | @ManyToOne(fetch = FetchType.LAZY) 20 | @JoinColumn(name = "message_sender_id") 21 | private User sender; 22 | @ManyToOne(fetch = FetchType.LAZY) 23 | @JoinColumn(name = "message_recipient_id") 24 | private User recipient; 25 | private Date sendDate; 26 | private Date seenDate; 27 | @NotBlank 28 | private String text; 29 | 30 | public ChatMessage() { 31 | } 32 | 33 | public ChatMessage(User sender, User recipient, @NotBlank String text, Date sendDate) { 34 | this.sender = sender; 35 | this.recipient = recipient; 36 | this.sendDate = sendDate; 37 | this.text = text; 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/notifications/Notification.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notifications; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.projecty.projectyweb.user.User; 6 | import lombok.*; 7 | import org.hibernate.annotations.CreationTimestamp; 8 | 9 | import javax.persistence.*; 10 | import java.util.Date; 11 | import java.util.Map; 12 | 13 | @Entity 14 | @Getter 15 | @Setter 16 | @Builder 17 | @AllArgsConstructor 18 | @NoArgsConstructor 19 | public class Notification { 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | 24 | private NotificationType notificationType; 25 | 26 | @ManyToOne(fetch = FetchType.LAZY) 27 | @JsonIgnore 28 | private User user; 29 | 30 | @ElementCollection 31 | @MapKeyColumn(name = "object_class") 32 | @Column(name = "id") 33 | private Map values; 34 | 35 | @Transient 36 | private String stringValue; 37 | 38 | @CreationTimestamp 39 | @Temporal(TemporalType.TIMESTAMP) 40 | private Date date; 41 | 42 | private Boolean seen; 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/settings/Settings.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.settings; 2 | 3 | import lombok.*; 4 | 5 | import javax.persistence.Entity; 6 | import javax.persistence.GeneratedValue; 7 | import javax.persistence.Id; 8 | import javax.persistence.PrePersist; 9 | 10 | @Entity 11 | @Getter 12 | @Setter 13 | @AllArgsConstructor 14 | @NoArgsConstructor 15 | @Builder 16 | public class Settings { 17 | @Id 18 | @GeneratedValue 19 | private Long id; 20 | // Notifications 21 | private Boolean isEmailNotificationEnabled; 22 | private Boolean isMessageEmailNotificationEnabled; 23 | // Project/Teams 24 | private Boolean canBeAddedToProject; 25 | private Boolean canBeAddedToTeam; 26 | 27 | @PrePersist 28 | public void prePersist() { 29 | if (isEmailNotificationEnabled == null) isEmailNotificationEnabled = true; 30 | if (isMessageEmailNotificationEnabled == null) isMessageEmailNotificationEnabled = true; 31 | if (canBeAddedToProject == null) canBeAddedToProject = true; 32 | if (canBeAddedToTeam == null) canBeAddedToTeam = true; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/attachment/Attachment.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.attachment; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.projecty.projectyweb.message.Message; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.hibernate.annotations.Type; 8 | 9 | import javax.persistence.Entity; 10 | import javax.persistence.GeneratedValue; 11 | import javax.persistence.Id; 12 | import javax.persistence.ManyToOne; 13 | import java.sql.Blob; 14 | import java.util.UUID; 15 | 16 | @Entity 17 | @Getter 18 | @Setter 19 | public class Attachment { 20 | @ManyToOne 21 | @JsonIgnore 22 | private Message message; 23 | 24 | public Attachment() { 25 | 26 | } 27 | 28 | @Id 29 | @GeneratedValue 30 | @Type(type = "org.hibernate.type.UUIDCharType") 31 | private UUID id; 32 | 33 | private String fileName; 34 | 35 | @JsonIgnore 36 | private Blob file; 37 | 38 | public Attachment(String fileName, Blob file, Message message) { 39 | this.fileName = fileName; 40 | this.file = file; 41 | this.message = message; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/misc/TeamSummaryService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.misc; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.team.Team; 5 | 6 | import java.util.List; 7 | 8 | public class TeamSummaryService { 9 | public static void generateTeamSummary(Team team) { 10 | TeamSummary summary = new TeamSummary(); 11 | if (team.getTeamRoles() != null) 12 | summary.setUserCount(team.getTeamRoles().size()); 13 | if (team.getProjects() != null) 14 | summary.setProjectCount(team.getProjects().size()); 15 | if (team.getProjects() != null) 16 | summary.setTaskCount(countTasks(team.getProjects())); 17 | team.setTeamSummary(summary); 18 | } 19 | 20 | private static int countTasks(List projects) { 21 | return projects.stream().mapToInt(project -> 22 | { 23 | if (project.getTasks() != null) { 24 | return project.getTasks().size(); 25 | } 26 | return 0; 27 | } 28 | ).sum(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/role/TeamRole.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.role; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.projecty.projectyweb.team.Team; 5 | import com.projecty.projectyweb.user.User; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import javax.persistence.*; 10 | 11 | @Entity 12 | @Table(name = "teamrole") 13 | @Getter 14 | @Setter 15 | public class TeamRole { 16 | 17 | public TeamRole(TeamRoles name, User user, Team team) { 18 | this.name = name; 19 | this.user = user; 20 | this.team = team; 21 | } 22 | 23 | public TeamRole() { 24 | } 25 | 26 | @Id 27 | @GeneratedValue 28 | private Long id; 29 | 30 | private TeamRoles name; 31 | 32 | @ManyToOne(fetch = FetchType.LAZY) 33 | private User user; 34 | 35 | @ManyToOne 36 | @JsonIgnore 37 | private Team team; 38 | 39 | @Override 40 | public String toString() { 41 | return "TeamRole{" + 42 | "id=" + id + 43 | ", name=" + name + 44 | ", user=" + user + 45 | ", team=" + team + 46 | '}'; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/configurations/LocaleConfiguration.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.configurations; 2 | 3 | import java.util.List; 4 | import java.util.Locale; 5 | import java.util.stream.Collectors; 6 | 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import org.springframework.web.servlet.LocaleResolver; 11 | import org.springframework.web.servlet.i18n.AcceptHeaderLocaleResolver; 12 | 13 | @Configuration 14 | public class LocaleConfiguration { 15 | 16 | @Value("${il8n.supported-languages}") 17 | private List supportedLanguages; 18 | 19 | @Bean 20 | public LocaleResolver localeResolver() { 21 | final AcceptHeaderLocaleResolver resolver = new AcceptHeaderLocaleResolver(); 22 | if(supportedLanguages != null) { 23 | List supportedLocales = supportedLanguages.stream().map(l->new Locale(l)).collect(Collectors.toList()); 24 | resolver.setSupportedLocales(supportedLocales); 25 | } 26 | resolver.setDefaultLocale(Locale.getDefault()); 27 | return resolver; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/Team.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.projecty.projectyweb.project.Project; 5 | import com.projecty.projectyweb.team.misc.TeamSummary; 6 | import com.projecty.projectyweb.team.role.TeamRole; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | import javax.persistence.*; 11 | import java.util.List; 12 | 13 | @Entity 14 | @Getter 15 | @Setter 16 | public class Team { 17 | @Id 18 | @GeneratedValue 19 | private Long id; 20 | 21 | private String name; 22 | 23 | @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) 24 | private List teamRoles; 25 | 26 | @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) 27 | @JsonIgnore 28 | private List projects; 29 | 30 | @Transient 31 | private List usernames; 32 | 33 | @Transient 34 | private TeamSummary teamSummary; 35 | 36 | @Override 37 | public String toString() { 38 | return "Team{" + 39 | "id=" + id + 40 | ", name='" + name + '\'' + 41 | // ", teamRoles=" + teamRoles + 42 | '}'; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/configurations/SocketBrokerConfig.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.configurations; 2 | 3 | import org.springframework.context.annotation.Configuration; 4 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 5 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 6 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 7 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 8 | 9 | @Configuration 10 | @EnableWebSocketMessageBroker 11 | public class SocketBrokerConfig implements WebSocketMessageBrokerConfigurer { 12 | public void configureMessageBroker(MessageBrokerRegistry config) { 13 | config.enableSimpleBroker("/secured/user/queue/specific-user"); 14 | config.setApplicationDestinationPrefixes("/spring-security-mvc-socket"); 15 | config.setUserDestinationPrefix("/secured/user"); 16 | } 17 | 18 | @Override 19 | public void registerStompEndpoints(StompEndpointRegistry registry) { 20 | registry.addEndpoint("/secured/room").setAllowedOrigins("*").withSockJS(); 21 | registry.addEndpoint("/secured/notifications").setAllowedOrigins("*").withSockJS(); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/role/ProjectRole.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.role; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonIgnore; 5 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 6 | import com.projecty.projectyweb.project.Project; 7 | import com.projecty.projectyweb.user.User; 8 | import lombok.Getter; 9 | import lombok.Setter; 10 | 11 | import javax.persistence.*; 12 | 13 | @Entity 14 | @JsonSerialize 15 | @Getter 16 | @Setter 17 | public class ProjectRole { 18 | public ProjectRole(ProjectRoles name, User user, Project project) { 19 | this.name = name; 20 | this.user = user; 21 | this.project = project; 22 | } 23 | 24 | public ProjectRole() { 25 | } 26 | 27 | @Id 28 | @GeneratedValue 29 | private Long id; 30 | 31 | // TODO Remove this redundancy 32 | private ProjectRoles name; 33 | 34 | @ManyToOne(fetch = FetchType.LAZY) 35 | private User user; 36 | 37 | @ManyToOne 38 | @JsonIgnore 39 | private Project project; 40 | 41 | @Override 42 | public String toString() { 43 | return "ProjectRole{" + 44 | "id=" + id + 45 | ", name='" + name + '\'' + 46 | ", user=" + user; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/task/Task.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.task; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 5 | import com.projecty.projectyweb.project.Project; 6 | import com.projecty.projectyweb.user.User; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | import javax.persistence.*; 11 | import java.sql.Date; 12 | import java.util.List; 13 | 14 | @Entity 15 | @JsonSerialize 16 | @Getter 17 | @Setter 18 | public class Task { 19 | @Id 20 | @GeneratedValue 21 | private Long id; 22 | 23 | private String name; 24 | 25 | private Date startDate; 26 | 27 | private Date endDate; 28 | 29 | private TaskStatus status; 30 | 31 | @ManyToOne 32 | @JsonIgnore 33 | private Project project; 34 | 35 | @ManyToMany 36 | private List assignedUsers; 37 | 38 | private Integer importance; 39 | 40 | @Override 41 | public String toString() { 42 | return "Task{" + 43 | "id=" + id + 44 | ", name='" + name + '\'' + 45 | ", startDate=" + startDate + 46 | ", endDate=" + endDate + 47 | ", status=" + status + 48 | '}'; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/notifications/NotificationSimpAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notifications; 2 | 3 | import org.aspectj.lang.annotation.AfterReturning; 4 | import org.aspectj.lang.annotation.Aspect; 5 | import org.springframework.messaging.simp.SimpMessagingTemplate; 6 | import org.springframework.stereotype.Component; 7 | 8 | @Aspect 9 | @Component 10 | public class NotificationSimpAspect { 11 | private final SimpMessagingTemplate messagingTemplate; 12 | private final NotificationService notificationService; 13 | 14 | public NotificationSimpAspect(SimpMessagingTemplate messagingTemplate, NotificationService notificationService) { 15 | this.messagingTemplate = messagingTemplate; 16 | this.notificationService = notificationService; 17 | } 18 | 19 | 20 | @AfterReturning(value = "execution (* com.projecty.projectyweb.notifications.NotificationService.createNotificationAndSave(..))", returning = "notification") 21 | public void sendSimpNotificationToSpecificUser(Notification notification) { 22 | notification.setStringValue(notificationService.buildNotificationString(notification)); 23 | messagingTemplate.convertAndSendToUser(notification.getUser().getUsername(), "/secured/user/queue/specific-user", notification); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/task/TaskValidator.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.task; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.validation.Errors; 5 | import org.springframework.validation.ValidationUtils; 6 | import org.springframework.validation.Validator; 7 | 8 | @Component 9 | public class TaskValidator implements Validator { 10 | @Override 11 | public boolean supports(Class clazz) { 12 | return Task.class.equals(clazz); 13 | } 14 | 15 | @Override 16 | public void validate(Object target, Errors errors) { 17 | Task task = (Task) target; 18 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "name", "name.empty"); 19 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "startDate", "startDate.empty"); 20 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "endDate", "endDate.empty"); 21 | 22 | if (task.getStartDate() != null && task.getEndDate() != null && task.getStartDate().after(task.getEndDate())) { 23 | errors.rejectValue("startDate", "start.date.greater.than.end.date"); 24 | } 25 | if (task.getImportance() != null && (task.getImportance() < 1 || task.getImportance() > 3)) { 26 | errors.rejectValue("importance", "importance.invalid"); 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/ProjectErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import com.projecty.projectyweb.misc.ApiError; 4 | import com.projecty.projectyweb.project.role.NoAdminsInProjectException; 5 | import org.springframework.context.MessageSource; 6 | import org.springframework.context.i18n.LocaleContextHolder; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.ControllerAdvice; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | 13 | @ControllerAdvice 14 | public class ProjectErrorHandler { 15 | private final MessageSource messageSource; 16 | 17 | public ProjectErrorHandler(MessageSource messageSource) { 18 | this.messageSource = messageSource; 19 | } 20 | 21 | @ExceptionHandler(NoAdminsInProjectException.class) 22 | public ResponseEntity handleNoAdminsInProjectException() { 23 | ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, 24 | messageSource.getMessage( 25 | "project.no_admins_in_project_exception", 26 | null, 27 | LocaleContextHolder.getLocale() 28 | )); 29 | return new ResponseEntity<>( 30 | apiError, new HttpHeaders(), apiError.getStatus()); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/TeamErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | import com.projecty.projectyweb.misc.ApiError; 4 | import com.projecty.projectyweb.team.role.NoManagersInTeamException; 5 | import org.springframework.context.MessageSource; 6 | import org.springframework.context.i18n.LocaleContextHolder; 7 | import org.springframework.http.HttpHeaders; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.http.ResponseEntity; 10 | import org.springframework.web.bind.annotation.ControllerAdvice; 11 | import org.springframework.web.bind.annotation.ExceptionHandler; 12 | 13 | import java.util.Locale; 14 | 15 | @ControllerAdvice 16 | public class TeamErrorHandler { 17 | private final MessageSource messageSource; 18 | 19 | public TeamErrorHandler(MessageSource messageSource) { 20 | this.messageSource = messageSource; 21 | } 22 | 23 | @ExceptionHandler(NoManagersInTeamException.class) 24 | public ResponseEntity handleNoManagersInTeamException(NoManagersInTeamException ex) { 25 | ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, 26 | messageSource.getMessage( 27 | "team.no_managers_in_team_exception", 28 | null, 29 | LocaleContextHolder.getLocale() 30 | )); 31 | return new ResponseEntity<>( 32 | apiError, new HttpHeaders(), apiError.getStatus()); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: "3" 2 | 3 | services: 4 | projecty: 5 | image: projecty-web:latest 6 | restart: always 7 | networks: 8 | - projecty-network 9 | depends_on: 10 | - database 11 | ports: 12 | - 8080:8080 13 | 14 | database: 15 | image: mysql:8 16 | restart: always 17 | networks: 18 | - projecty-network 19 | environment: 20 | - MYSQL_ROOT_PASSWORD=password 21 | - MYSQL_DATABASE=projecty 22 | ports: 23 | - 3306:3306 24 | volumes: 25 | - db-data:/var/lib/mysql 26 | 27 | # Keycloak 28 | postgres: 29 | image: postgres 30 | volumes: 31 | - postgres_data:/var/lib/postgresql/data 32 | environment: 33 | POSTGRES_DB: keycloak 34 | POSTGRES_USER: keycloak 35 | POSTGRES_PASSWORD: password 36 | 37 | keycloak: 38 | image: quay.io/keycloak/keycloak:latest 39 | volumes: 40 | - ./config/realm.json:/tmp/config/realm.json 41 | environment: 42 | DB_VENDOR: POSTGRES 43 | DB_ADDR: postgres 44 | DB_DATABASE: keycloak 45 | DB_USER: keycloak 46 | DB_SCHEMA: public 47 | DB_PASSWORD: password 48 | KEYCLOAK_USER: admin 49 | KEYCLOAK_PASSWORD: admin 50 | KEYCLOAK_IMPORT: '/tmp/config/realm.json -Dkeycloak.profile.feature.upload_scripts=enabled' 51 | ports: 52 | - 8081:8080 53 | depends_on: 54 | - postgres 55 | 56 | 57 | networks: 58 | projecty-network: 59 | 60 | volumes: 61 | db-data: 62 | postgres_data: 63 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/Message.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | import com.projecty.projectyweb.message.attachment.Attachment; 4 | import com.projecty.projectyweb.user.User; 5 | import lombok.*; 6 | import org.hibernate.annotations.CreationTimestamp; 7 | 8 | import javax.persistence.*; 9 | import javax.validation.constraints.NotBlank; 10 | import java.util.Date; 11 | import java.util.List; 12 | 13 | @Entity 14 | @Getter 15 | @Setter 16 | @AllArgsConstructor 17 | @NoArgsConstructor 18 | @Builder 19 | public class Message { 20 | @Id 21 | @GeneratedValue 22 | private Long id; 23 | 24 | @ManyToOne(fetch = FetchType.LAZY) 25 | @JoinColumn(name = "message_sender_id") 26 | private User sender; 27 | 28 | @ManyToOne(fetch = FetchType.LAZY) 29 | @JoinColumn(name = "message_recipient_id") 30 | private User recipient; 31 | 32 | @CreationTimestamp 33 | @Temporal(TemporalType.TIMESTAMP) 34 | private Date sendDate; 35 | private Date seenDate; 36 | 37 | @NotBlank 38 | private String title; 39 | 40 | @NotBlank 41 | private String text; 42 | 43 | @OneToMany( 44 | cascade = CascadeType.ALL, 45 | orphanRemoval = true, 46 | mappedBy = "message" 47 | ) 48 | private List attachments; 49 | 50 | @ManyToOne 51 | @JoinColumn(name = "reply_to_message_id") 52 | private Message replyTo; 53 | 54 | private Boolean hasReply; 55 | @Transient 56 | private String recipientUsername; 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/attachment/AttachmentService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.attachment; 2 | 3 | import com.projecty.projectyweb.message.Message; 4 | import org.apache.commons.io.IOUtils; 5 | import org.springframework.stereotype.Service; 6 | import org.springframework.web.multipart.MultipartFile; 7 | 8 | import javax.sql.rowset.serial.SerialBlob; 9 | import java.io.IOException; 10 | import java.io.InputStream; 11 | import java.sql.Blob; 12 | import java.sql.SQLException; 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | @Service 17 | public class AttachmentService { 18 | 19 | public byte[] getByteArrayFromAttachment(Attachment attachment) throws SQLException, IOException { 20 | Blob blob = attachment.getFile(); 21 | InputStream inputStream = blob.getBinaryStream(); 22 | return IOUtils.toByteArray(inputStream); 23 | } 24 | 25 | public void addFilesToMessage(List multipartFiles, Message message) { 26 | List attachments = new ArrayList<>(); 27 | multipartFiles.forEach(multipartFile -> { 28 | try { 29 | attachments.add(new Attachment( 30 | multipartFile.getOriginalFilename(), 31 | new SerialBlob(multipartFile.getBytes()), 32 | message 33 | )); 34 | } catch (SQLException | IOException ignored) { 35 | } 36 | }); 37 | message.setAttachments(attachments); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/MessageValidator.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | import com.projecty.projectyweb.user.User; 4 | import com.projecty.projectyweb.user.UserService; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.validation.*; 8 | 9 | import java.util.Optional; 10 | 11 | @Component 12 | public class MessageValidator implements Validator { 13 | 14 | @Autowired 15 | private UserService userService; 16 | 17 | @Override 18 | public boolean supports(Class clazz) { 19 | return Message.class.equals(clazz); 20 | } 21 | 22 | @Override 23 | public void validate(Object target, Errors errors) { 24 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "text", "message.text.empty"); 25 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "title", "message.title.empty"); 26 | ValidationUtils.rejectIfEmptyOrWhitespace(errors, "recipientUsername", "message.recipient.invalid"); 27 | 28 | Message message = (Message) target; 29 | 30 | Optional recipient = userService.findByByUsername(message.getRecipientUsername()); 31 | 32 | if (!recipient.isPresent()) { 33 | errors.rejectValue("recipientUsername", "message.recipient.invalid"); 34 | } else { 35 | User sender = userService.getCurrentUser(); 36 | if (sender.equals(recipient.get())) { 37 | errors.rejectValue("recipientUsername", "message.recipient.yourself"); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/notifications/NotificationController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notifications; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.*; 5 | import org.springframework.web.server.ResponseStatusException; 6 | 7 | import java.util.List; 8 | import java.util.Optional; 9 | 10 | @RestController 11 | @RequestMapping("notifications") 12 | @CrossOrigin() 13 | public class NotificationController { 14 | 15 | private final NotificationService notificationService; 16 | private final NotificationRepository notificationRepository; 17 | 18 | public NotificationController(NotificationService notificationService, NotificationRepository notificationRepository) { 19 | this.notificationService = notificationService; 20 | this.notificationRepository = notificationRepository; 21 | } 22 | 23 | @GetMapping 24 | public List getNotifications() { 25 | return notificationService.getNotifications(); 26 | } 27 | 28 | @GetMapping("unseenCount") 29 | public Long getUnseenNotificationsCount() { 30 | return notificationService.getUnseenNotificationCount(); 31 | } 32 | 33 | @DeleteMapping("{id}") 34 | public void deleteNotification(@PathVariable Long id) { 35 | Optional optionalNotification = notificationRepository.findById(id); 36 | if (optionalNotification.isPresent()) { 37 | notificationService.deleteNotification(optionalNotification.get()); 38 | } else { 39 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/Project.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | 4 | import com.projecty.projectyweb.project.role.ProjectRole; 5 | import com.projecty.projectyweb.task.Task; 6 | import com.projecty.projectyweb.task.TaskStatus; 7 | import com.projecty.projectyweb.team.Team; 8 | import lombok.*; 9 | import org.hibernate.annotations.UpdateTimestamp; 10 | 11 | import javax.persistence.*; 12 | import javax.validation.constraints.NotBlank; 13 | import java.util.Date; 14 | import java.util.List; 15 | import java.util.Map; 16 | 17 | @Entity 18 | @Getter 19 | @Setter 20 | @AllArgsConstructor 21 | @NoArgsConstructor 22 | @Builder 23 | public class Project { 24 | @Id 25 | @GeneratedValue 26 | private Long id; 27 | 28 | @NotBlank 29 | private String name; 30 | 31 | @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, mappedBy = "project") 32 | private List tasks; 33 | 34 | @Transient 35 | private List usernames; 36 | 37 | @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true) 38 | private List projectRoles; 39 | 40 | @ManyToOne 41 | private Team team; 42 | 43 | @UpdateTimestamp 44 | @Temporal(TemporalType.TIMESTAMP) 45 | private Date modifyDate; 46 | 47 | @Transient 48 | private Map taskSummary; 49 | 50 | @Override 51 | public String toString() { 52 | return "Project{" + 53 | "id=" + id + 54 | ", name='" + name + '\'' + 55 | ", tasks=" + tasks + 56 | ", projectRoles=" + projectRoles + 57 | '}'; 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/MessageAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | import org.aspectj.lang.JoinPoint; 4 | import org.aspectj.lang.annotation.Aspect; 5 | import org.aspectj.lang.annotation.Before; 6 | import org.aspectj.lang.annotation.Pointcut; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.server.ResponseStatusException; 10 | 11 | import java.util.Optional; 12 | 13 | @Aspect 14 | @Component 15 | public class MessageAspect { 16 | private final MessageRepository messageRepository; 17 | private final MessageService messageService; 18 | 19 | public MessageAspect(MessageRepository messageRepository, MessageService messageService) { 20 | this.messageRepository = messageRepository; 21 | this.messageService = messageService; 22 | } 23 | 24 | @Pointcut("execution (* com.projecty.projectyweb.message.MessageController.*(Long,..))" + 25 | "&&@annotation(com.projecty.projectyweb.configurations.AnyPermission)") 26 | private void inMessageControllerAndWithAnyPermission() { 27 | } 28 | 29 | @Before("inMessageControllerAndWithAnyPermission()") 30 | public void checkIfUserHasPermissionToView(JoinPoint joinPoint) { 31 | Long messageId = (Long) joinPoint.getArgs()[0]; 32 | Optional optionalMessage = messageRepository.findById(messageId); 33 | if (!(optionalMessage.isPresent() 34 | && messageService.checkIfCurrentUserHasPermissionToView(optionalMessage.get()))) { 35 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/socket/ChatSocketController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat.socket; 2 | 3 | import com.projecty.projectyweb.chat.ChatMessage; 4 | import com.projecty.projectyweb.chat.ChatService; 5 | import com.projecty.projectyweb.user.UserNotFoundException; 6 | import org.springframework.messaging.handler.annotation.Header; 7 | import org.springframework.messaging.handler.annotation.MessageMapping; 8 | import org.springframework.messaging.handler.annotation.Payload; 9 | import org.springframework.messaging.simp.SimpMessagingTemplate; 10 | import org.springframework.stereotype.Controller; 11 | import org.springframework.web.bind.annotation.CrossOrigin; 12 | 13 | import java.security.Principal; 14 | 15 | @Controller 16 | @CrossOrigin() 17 | public class ChatSocketController { 18 | private final ChatService chatService; 19 | 20 | private final SimpMessagingTemplate simpMessagingTemplate; 21 | 22 | public ChatSocketController(ChatService chatService, SimpMessagingTemplate simpMessagingTemplate) { 23 | this.chatService = chatService; 24 | this.simpMessagingTemplate = simpMessagingTemplate; 25 | } 26 | 27 | @MessageMapping("/secured/room") 28 | public void sendSpecific( 29 | @Payload SocketChatMessage msg, 30 | Principal user, 31 | @Header("simpSessionId") String sessionId) throws Exception { 32 | try { 33 | ChatMessage chatMessage = chatService.saveInDatabase(msg, user); 34 | simpMessagingTemplate.convertAndSendToUser( 35 | msg.getRecipient(), "/secured/user/queue/specific-user", chatMessage); 36 | } catch (UserNotFoundException ignored) { 37 | } 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/team/TeamSummaryServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.task.Task; 5 | import com.projecty.projectyweb.team.misc.TeamSummary; 6 | import com.projecty.projectyweb.team.misc.TeamSummaryService; 7 | import com.projecty.projectyweb.team.role.TeamRole; 8 | import org.junit.Test; 9 | 10 | import java.util.ArrayList; 11 | import java.util.Collections; 12 | import java.util.List; 13 | 14 | import static org.hamcrest.CoreMatchers.is; 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | 17 | public class TeamSummaryServiceTests { 18 | @Test 19 | public void whenGenerateTeamSummary_shouldReturnTeamWithTeamSummary() { 20 | Team team = new Team(); 21 | List roles = new ArrayList<>(); 22 | roles.add(new TeamRole()); 23 | 24 | List projects = new ArrayList<>(); 25 | Project project1 = new Project(); 26 | project1.setTasks(Collections.singletonList(new Task())); 27 | Project project2 = new Project(); 28 | List tasks = new ArrayList<>(); 29 | tasks.add(new Task()); 30 | tasks.add(new Task()); 31 | project2.setTasks(tasks); 32 | 33 | projects.add(project1); 34 | projects.add(project2); 35 | 36 | team.setProjects(projects); 37 | team.setTeamRoles(roles); 38 | TeamSummaryService.generateTeamSummary(team); 39 | TeamSummary summary = team.getTeamSummary(); 40 | assertThat(summary.getUserCount(), is(1)); 41 | assertThat(summary.getProjectCount(), is(2)); 42 | assertThat(summary.getTaskCount(), is(3)); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/attachment/AttachmentController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.attachment; 2 | 3 | import com.projecty.projectyweb.configurations.AnyPermission; 4 | import org.springframework.web.bind.annotation.*; 5 | 6 | import javax.servlet.http.HttpServletResponse; 7 | import java.io.IOException; 8 | import java.sql.SQLException; 9 | import java.util.Optional; 10 | import java.util.UUID; 11 | 12 | @CrossOrigin() 13 | @RestController 14 | @RequestMapping("attachments") 15 | public class AttachmentController { 16 | private final AttachmentRepository attachmentRepository; 17 | private final AttachmentService attachmentService; 18 | 19 | public AttachmentController(AttachmentRepository attachmentRepository, AttachmentService attachmentService) { 20 | this.attachmentRepository = attachmentRepository; 21 | this.attachmentService = attachmentService; 22 | } 23 | 24 | @GetMapping("{attachmentId}") 25 | @AnyPermission 26 | public @ResponseBody 27 | byte[] downloadAttachment( 28 | @PathVariable UUID attachmentId, 29 | HttpServletResponse response 30 | ) throws IOException, SQLException { 31 | Optional optionalAttachment = attachmentRepository.findById(attachmentId); 32 | if (optionalAttachment.isPresent()) { 33 | Attachment attachment = optionalAttachment.get(); 34 | response.setContentType("application/octet-stream"); 35 | response.setHeader("Content-Disposition", "attachment; filename=" + attachment.getFileName()); 36 | response.flushBuffer(); 37 | return attachmentService.getByteArrayFromAttachment(attachment); 38 | } 39 | return null; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/email/EmailService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.email; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.mail.javamail.JavaMailSender; 5 | import org.springframework.mail.javamail.MimeMessageHelper; 6 | import org.springframework.stereotype.Service; 7 | import org.thymeleaf.context.Context; 8 | import org.thymeleaf.spring5.SpringTemplateEngine; 9 | 10 | import javax.mail.MessagingException; 11 | import javax.mail.internet.MimeMessage; 12 | import java.util.Map; 13 | 14 | @Service 15 | public class EmailService { 16 | private final JavaMailSender javaMailSender; 17 | private final SpringTemplateEngine springTemplateEngine; 18 | @Value("${spring.mail.username}") 19 | private String from; 20 | 21 | public EmailService(JavaMailSender javaMailSender, SpringTemplateEngine springTemplateEngine) { 22 | this.javaMailSender = javaMailSender; 23 | this.springTemplateEngine = springTemplateEngine; 24 | } 25 | 26 | public void sendEmail(String to, String subject, String text) throws MessagingException { 27 | MimeMessage message = javaMailSender.createMimeMessage(); 28 | MimeMessageHelper helper = new MimeMessageHelper(message, true, "UTF-8"); 29 | helper.setFrom(from); 30 | helper.setTo(to); 31 | helper.setSubject(subject); 32 | helper.setText(text, true); 33 | javaMailSender.send(message); 34 | } 35 | 36 | public void sendMessageThymeleafTemplate(String to, String subject, Map templateModel) throws MessagingException { 37 | Context context = new Context(); 38 | context.setVariables(templateModel); 39 | String htmlBody = springTemplateEngine.process("email-template.html", context); 40 | sendEmail(to, subject, htmlBody); 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/attachment/AttachmentAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.attachment; 2 | 3 | import com.projecty.projectyweb.message.MessageService; 4 | import org.aspectj.lang.JoinPoint; 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.aspectj.lang.annotation.Before; 7 | import org.aspectj.lang.annotation.Pointcut; 8 | import org.springframework.http.HttpStatus; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.web.server.ResponseStatusException; 11 | 12 | import java.util.Optional; 13 | import java.util.UUID; 14 | 15 | @Aspect 16 | @Component 17 | public class AttachmentAspect { 18 | private final AttachmentRepository attachmentRepository; 19 | private final MessageService messageService; 20 | 21 | public AttachmentAspect(AttachmentRepository attachmentRepository, MessageService messageService) { 22 | this.attachmentRepository = attachmentRepository; 23 | this.messageService = messageService; 24 | } 25 | 26 | @Pointcut("execution (* com.projecty.projectyweb.message.attachment.AttachmentController.*(..))" + 27 | "&&@annotation(com.projecty.projectyweb.configurations.AnyPermission)") 28 | private void inAttachmentControllerAndWithAnyPermission() { 29 | } 30 | 31 | @Before("inAttachmentControllerAndWithAnyPermission()") 32 | public void checkIfUserHasPermissionToView(JoinPoint joinPoint) { 33 | UUID attachmentId = (UUID) joinPoint.getArgs()[0]; 34 | Optional optionalAttachment = attachmentRepository.findById(attachmentId); 35 | if (!(optionalAttachment.isPresent() 36 | && messageService.checkIfCurrentUserHasPermissionToView(optionalAttachment.get().getMessage()))) { 37 | throw new ResponseStatusException(HttpStatus.UNAUTHORIZED); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/OffsetBasedPageRequest.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.domain.Sort; 5 | 6 | import java.io.Serializable; 7 | 8 | public class OffsetBasedPageRequest implements Pageable, Serializable { 9 | private final Sort sort; 10 | private long limit; 11 | private long offset; 12 | 13 | public OffsetBasedPageRequest(long offset, long limit, Sort sort) { 14 | this.limit = limit; 15 | this.offset = offset; 16 | this.sort = sort; 17 | } 18 | 19 | public OffsetBasedPageRequest(long offset, long limit) { 20 | this(offset, limit, Sort.by(Sort.Direction.ASC, "id")); 21 | } 22 | 23 | @Override 24 | public int getPageNumber() { 25 | return (int) (offset / limit); 26 | } 27 | 28 | @Override 29 | public int getPageSize() { 30 | return (int) limit; 31 | } 32 | 33 | @Override 34 | public long getOffset() { 35 | return offset; 36 | } 37 | 38 | @Override 39 | public Sort getSort() { 40 | return sort; 41 | } 42 | 43 | @Override 44 | public Pageable next() { 45 | return new OffsetBasedPageRequest(getOffset() + getPageSize(), getPageSize(), getSort()); 46 | } 47 | 48 | public OffsetBasedPageRequest previous() { 49 | return hasPrevious() ? new OffsetBasedPageRequest(getOffset() - getPageSize(), getPageSize(), getSort()) : this; 50 | } 51 | 52 | @Override 53 | public Pageable previousOrFirst() { 54 | return hasPrevious() ? previous() : first(); 55 | } 56 | 57 | @Override 58 | public Pageable first() { 59 | return new OffsetBasedPageRequest(0, getPageSize(), getSort()); 60 | } 61 | 62 | @Override 63 | public boolean hasPrevious() { 64 | return offset > limit; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/ChatController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat; 2 | 3 | import com.projecty.projectyweb.chat.dto.ChatHistoryData; 4 | import com.projecty.projectyweb.user.User; 5 | import com.projecty.projectyweb.user.UserService; 6 | import org.springframework.data.domain.Page; 7 | import org.springframework.http.HttpStatus; 8 | import org.springframework.web.bind.annotation.*; 9 | import org.springframework.web.server.ResponseStatusException; 10 | 11 | import java.util.List; 12 | import java.util.Optional; 13 | 14 | @RestController 15 | @RequestMapping("chat") 16 | public class ChatController { 17 | private final ChatService chatService; 18 | private final UserService userService; 19 | 20 | public ChatController(ChatService chatService, UserService userService) { 21 | this.chatService = chatService; 22 | this.userService = userService; 23 | } 24 | 25 | @GetMapping("/{username}") 26 | public Page getChatMessages( 27 | @PathVariable("username") String username, 28 | @RequestParam(required = false, defaultValue = "0") Integer offset, 29 | @RequestParam(required = false, defaultValue = "10") Integer limit) { 30 | Optional optionalRecipient = userService.findByByUsername(username); 31 | if (optionalRecipient.isPresent()) { 32 | chatService.setAllReadForChat(optionalRecipient.get()); 33 | return chatService.findByRecipientAndSenderOrderById(optionalRecipient.get(), offset, limit); 34 | } 35 | throw new ResponseStatusException(HttpStatus.BAD_REQUEST); 36 | } 37 | 38 | @GetMapping("") 39 | public List getChatHistory() { 40 | return chatService.getChatHistory(); 41 | } 42 | 43 | @GetMapping("unreadChatMessageCount") 44 | public int getUnreadChatMessageCount() { 45 | return chatService.getUnreadChatMessageCount(); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/notifications/NotificationEmailAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notifications; 2 | 3 | import com.projecty.projectyweb.email.EmailService; 4 | import org.aspectj.lang.annotation.AfterReturning; 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.slf4j.Logger; 7 | import org.slf4j.LoggerFactory; 8 | import org.springframework.context.annotation.Profile; 9 | import org.springframework.scheduling.annotation.Async; 10 | import org.springframework.stereotype.Component; 11 | 12 | import java.util.HashMap; 13 | import java.util.Map; 14 | 15 | @Aspect 16 | @Component 17 | @Profile({"docker", "development"}) 18 | public class NotificationEmailAspect { 19 | private static final Logger logger = LoggerFactory.getLogger(NotificationEmailAspect.class); 20 | private final NotificationService notificationService; 21 | private final EmailService emailService; 22 | 23 | public NotificationEmailAspect(NotificationService notificationService, EmailService emailService) { 24 | this.notificationService = notificationService; 25 | this.emailService = emailService; 26 | } 27 | 28 | @AfterReturning(value = "execution (* com.projecty.projectyweb.notifications.NotificationService.createNotificationAndSave(..))", returning = "notification") 29 | @Async 30 | public void sendEmailNotificationToSpecificUser(Notification notification) { 31 | if (notification.getUser().getSettings().getIsEmailNotificationEnabled()) { 32 | notification.setStringValue(notificationService.buildNotificationString(notification)); 33 | Map values = new HashMap<>(); 34 | values.put("text", notification.getStringValue()); 35 | try { 36 | emailService.sendMessageThymeleafTemplate(notification.getUser().getEmail(), notification.getStringValue(), values); 37 | } catch (Exception e) { 38 | logger.warn(String.valueOf(e)); 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/task/TaskValidatorTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.task; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.TestConfiguration; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | import org.springframework.validation.BeanPropertyBindingResult; 10 | import org.springframework.validation.Errors; 11 | 12 | import java.sql.Date; 13 | 14 | import static org.hamcrest.MatcherAssert.assertThat; 15 | import static org.hamcrest.Matchers.greaterThanOrEqualTo; 16 | import static org.hamcrest.Matchers.is; 17 | 18 | @RunWith(SpringRunner.class) 19 | public class TaskValidatorTests { 20 | @Autowired 21 | TaskValidator taskValidator; 22 | 23 | @Test 24 | public void givenCorrectDate_shouldNotReturnAnyErrors() { 25 | Task task = new Task(); 26 | task.setName("Name"); 27 | task.setStartDate(Date.valueOf("2020-12-31")); 28 | task.setEndDate(Date.valueOf("2021-12-31")); 29 | Errors errors = new BeanPropertyBindingResult(task, "task"); 30 | taskValidator.validate(task, errors); 31 | assertThat(errors.hasErrors(), is(false)); 32 | } 33 | 34 | @Test 35 | public void givenDateEndAfterStart_shouldReturnErrorOnStartDateField() { 36 | Task task = new Task(); 37 | task.setName("Name"); 38 | task.setStartDate(Date.valueOf("2020-12-31")); 39 | task.setEndDate(Date.valueOf("2019-12-31")); 40 | Errors errors = new BeanPropertyBindingResult(task, "task"); 41 | taskValidator.validate(task, errors); 42 | assertThat(errors.getFieldErrorCount("startDate"), greaterThanOrEqualTo(1)); 43 | } 44 | 45 | @TestConfiguration 46 | static class TaskValidatorTestConfiguration { 47 | @Bean 48 | public TaskValidator taskValidator() { 49 | return new TaskValidator(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/association/AssociationService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.association; 2 | 3 | import com.projecty.projectyweb.message.Message; 4 | import com.projecty.projectyweb.user.User; 5 | import com.projecty.projectyweb.user.UserService; 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.Optional; 10 | @Service 11 | public class AssociationService { 12 | 13 | private final UserService userService; 14 | 15 | private final AssociationRepository associationRepository; 16 | 17 | 18 | public AssociationService(UserService userService , AssociationRepository associationRepository){ 19 | this.userService = userService; 20 | this.associationRepository = associationRepository; 21 | } 22 | 23 | public void recordMessage(Message mesage){ 24 | Association senderAssociation = new Association(); 25 | senderAssociation.setMessage(mesage); 26 | senderAssociation.setUser(mesage.getSender()); 27 | 28 | associationRepository.save(senderAssociation); 29 | 30 | Association recipientAssociation = new Association(); 31 | recipientAssociation.setMessage(mesage); 32 | recipientAssociation.setUser(mesage.getRecipient()); 33 | 34 | associationRepository.save(recipientAssociation); 35 | } 36 | 37 | public void deleteMessageForUser(Message messageToBeDeleted, User user){ 38 | Optional messageOptional = associationRepository.findFirstByUserAndMessage(user,messageToBeDeleted); 39 | messageOptional.ifPresent(associationRepository::delete); 40 | } 41 | public boolean isVisibleForUser(Message message,User user){ 42 | if(message.getRecipient().equals(user) || message.getSender().equals(user)){ 43 | Optional messageOptional = associationRepository.findFirstByUserAndMessage(user,message); 44 | return messageOptional.isPresent(); 45 | } 46 | return false; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/project/ProjectValidatorTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import org.junit.Test; 4 | import org.junit.runner.RunWith; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.boot.test.context.TestConfiguration; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.test.context.junit4.SpringRunner; 9 | import org.springframework.validation.BeanPropertyBindingResult; 10 | import org.springframework.validation.Errors; 11 | 12 | import static org.hamcrest.MatcherAssert.assertThat; 13 | import static org.hamcrest.Matchers.greaterThanOrEqualTo; 14 | import static org.hamcrest.Matchers.is; 15 | 16 | @RunWith(SpringRunner.class) 17 | public class ProjectValidatorTests { 18 | @Autowired 19 | ProjectValidator projectValidator; 20 | 21 | @Test 22 | public void givenWhitespaceName_shouldReturnError() { 23 | Project project = new Project(); 24 | project.setName(" "); 25 | Errors errors = new BeanPropertyBindingResult(project, "project"); 26 | projectValidator.validate(project, errors); 27 | assertThat(errors.getFieldErrorCount("name"), greaterThanOrEqualTo(1)); 28 | } 29 | 30 | @Test 31 | public void givenNullName_shouldReturnError() { 32 | Project project = new Project(); 33 | Errors errors = new BeanPropertyBindingResult(project, "project"); 34 | projectValidator.validate(project, errors); 35 | assertThat(errors.getFieldErrorCount("name"), greaterThanOrEqualTo(1)); 36 | } 37 | 38 | @Test 39 | public void givenCorrectProject_shouldPass() { 40 | Project project = new Project(); 41 | project.setName("Name"); 42 | Errors errors = new BeanPropertyBindingResult(project, "project"); 43 | projectValidator.validate(project, errors); 44 | assertThat(errors.hasErrors(), is(false)); 45 | } 46 | 47 | @TestConfiguration 48 | static class TaskValidatorTestConfiguration { 49 | @Bean 50 | public ProjectValidator projectValidator() { 51 | return new ProjectValidator(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/chat/ChatMessageRepository.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat; 2 | 3 | import com.projecty.projectyweb.user.User; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.data.jpa.repository.JpaRepository; 7 | import org.springframework.data.jpa.repository.Query; 8 | import org.springframework.data.repository.query.Param; 9 | 10 | import java.util.List; 11 | import java.util.Set; 12 | 13 | public interface ChatMessageRepository extends JpaRepository { 14 | @Query("select m from ChatMessage m where (m.sender=?1 and m.recipient=?2) or (m.recipient=?1 and m.sender=?2) order by m.id desc") 15 | Page findByRecipientAndSenderOrderById(User user1, User user2, Pageable pageable); 16 | 17 | @Query(value = "select new com.projecty.projectyweb.chat.UsernameLastChatMessageIdDTO(m.sender.username, max (id)) " + 18 | "from ChatMessage m where m.recipient=?1 group by m.sender.username") 19 | List findMaxMessageIdGroupBySenderUsername(User user); 20 | 21 | @Query(value = "select new com.projecty.projectyweb.chat.UsernameLastChatMessageIdDTO(m.recipient.username, max (id)) " + 22 | "from ChatMessage m where m.sender=?1 group by m.recipient.username") 23 | List findMaxMessageIdGroupByRecipientUsername(User user); 24 | 25 | @Query("select m from ChatMessage m where m.id in :Ids") 26 | List findByIdInIds(@Param("Ids") Set Ids); 27 | 28 | @Query("select m from ChatMessage m where m.sender=?1 and m.recipient=?2 and m.seenDate is null ") 29 | List findBySenderAndCurrentUserWhereSeenDateIsNull(User sender, User currentUser); 30 | 31 | @Query(value = "select new com.projecty.projectyweb.chat.UserIdChatMessageCountDTO(m.sender.id, count (id)) " + 32 | "from ChatMessage m where m.recipient=?1 and m.seenDate is null group by m.sender.id") 33 | List countMessagesBySenderWhereSeenDateIsNullGroupBySender(User currentUser); 34 | 35 | int countByRecipientAndSeenDateIsNull(User user); 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/misc/RestErrorHandler.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.misc; 2 | 3 | 4 | import java.util.ArrayList; 5 | import java.util.List; 6 | 7 | import org.springframework.context.MessageSource; 8 | import org.springframework.context.i18n.LocaleContextHolder; 9 | import org.springframework.http.HttpHeaders; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.http.ResponseEntity; 12 | import org.springframework.validation.BindException; 13 | import org.springframework.web.bind.MissingServletRequestParameterException; 14 | import org.springframework.web.bind.annotation.ControllerAdvice; 15 | import org.springframework.web.context.request.WebRequest; 16 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 17 | 18 | @ControllerAdvice 19 | public class RestErrorHandler extends ResponseEntityExceptionHandler { 20 | private final MessageSource messageSource; 21 | 22 | public RestErrorHandler(MessageSource messageSource) { 23 | this.messageSource = messageSource; 24 | } 25 | 26 | protected ResponseEntity handleMissingServletRequestParameter( 27 | MissingServletRequestParameterException ex, HttpHeaders headers, 28 | HttpStatus status, WebRequest request) { 29 | String error = ex.getParameterName() + " parameter is missing"; 30 | 31 | ApiError apiError = 32 | new ApiError(HttpStatus.BAD_REQUEST, error); 33 | return new ResponseEntity<>( 34 | apiError, new HttpHeaders(), apiError.getStatus()); 35 | } 36 | 37 | @Override 38 | protected ResponseEntity handleBindException(final BindException ex, final HttpHeaders headers, final HttpStatus status, final WebRequest request) { 39 | final List errors = new ArrayList<>(); 40 | 41 | ex.getBindingResult().getFieldErrors().forEach(fieldError -> errors.add(messageSource.getMessage(fieldError, LocaleContextHolder.getLocale()))); 42 | final ApiError apiError = new ApiError(HttpStatus.BAD_REQUEST, errors); 43 | return handleExceptionInternal(ex, apiError, headers, apiError.getStatus(), request); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/MessageEmailAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | import com.projecty.projectyweb.email.EmailService; 4 | import com.projecty.projectyweb.notifications.NotificationEmailAspect; 5 | import org.aspectj.lang.annotation.AfterReturning; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.slf4j.Logger; 8 | import org.slf4j.LoggerFactory; 9 | import org.springframework.context.MessageSource; 10 | import org.springframework.context.i18n.LocaleContextHolder; 11 | import org.springframework.scheduling.annotation.Async; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.util.HashMap; 15 | import java.util.Map; 16 | 17 | @Component 18 | @Aspect 19 | public class MessageEmailAspect { 20 | private static final Logger logger = LoggerFactory.getLogger(NotificationEmailAspect.class); 21 | private final MessageSource messageSource; 22 | private final EmailService emailService; 23 | 24 | public MessageEmailAspect(MessageSource messageSource, EmailService emailService) { 25 | this.messageSource = messageSource; 26 | this.emailService = emailService; 27 | } 28 | 29 | @AfterReturning(value = "execution (* com.projecty.projectyweb.message.MessageService.sendMessage(..))", returning = "message") 30 | @Async 31 | public void sendEmailNotificationAboutMessage(Message message) { 32 | if (message.getRecipient().getSettings().getIsMessageEmailNotificationEnabled()) { 33 | String title = messageSource.getMessage( 34 | "received.message.title", 35 | new String[]{message.getSender().getUsername()}, 36 | LocaleContextHolder.getLocale()); 37 | Map values = new HashMap<>(); 38 | values.put("text", messageSource.getMessage( 39 | "message.text.hidden", 40 | null, 41 | LocaleContextHolder.getLocale())); 42 | try { 43 | emailService.sendMessageThymeleafTemplate(message.getRecipient().getEmail(), title, values); 44 | } catch (Exception e) { 45 | logger.warn(String.valueOf(e)); 46 | } 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/role/TeamRoleController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team.role; 2 | 3 | import com.projecty.projectyweb.user.User; 4 | import com.projecty.projectyweb.user.UserService; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.*; 7 | import org.springframework.web.server.ResponseStatusException; 8 | 9 | import java.util.Optional; 10 | 11 | @CrossOrigin() 12 | @RestController 13 | @RequestMapping("teamRoles") 14 | public class TeamRoleController { 15 | private final TeamRoleService teamRoleService; 16 | 17 | private final UserService userService; 18 | 19 | public TeamRoleController(TeamRoleService teamRoleService, UserService userService) { 20 | this.teamRoleService = teamRoleService; 21 | this.userService = userService; 22 | } 23 | 24 | @DeleteMapping("/{teamRoleId}") 25 | public void deleteTeamRole( 26 | @PathVariable Long teamRoleId 27 | ) { 28 | // TODO: 6/28/19 Prevent from delete current user from team 29 | User current = userService.getCurrentUser(); 30 | Optional optionalTeamRole = teamRoleService.findById(teamRoleId); 31 | if (optionalTeamRole.isPresent() && teamRoleService.isCurrentUserTeamManager(optionalTeamRole.get().getTeam()) && 32 | !optionalTeamRole.get().getUser().equals(current)) { 33 | teamRoleService.delete(optionalTeamRole.get()); 34 | } else { 35 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 36 | } 37 | } 38 | 39 | @PatchMapping("/{teamRoleId}") 40 | public TeamRole patchTeamRole( 41 | @PathVariable Long teamRoleId, 42 | @RequestBody TeamRole patchedValues 43 | ) { 44 | User current = userService.getCurrentUser(); 45 | Optional optionalTeamRole = teamRoleService.findById(teamRoleId); 46 | if (optionalTeamRole.isPresent() && teamRoleService.isCurrentUserTeamManager(optionalTeamRole.get().getTeam()) && 47 | !optionalTeamRole.get().getUser().equals(current) 48 | ) { 49 | return teamRoleService.patchTeamRole(optionalTeamRole.get(), patchedValues); 50 | } else { 51 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/role/ProjectRoleController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.role; 2 | 3 | import com.projecty.projectyweb.configurations.EditPermission; 4 | import com.projecty.projectyweb.user.User; 5 | import com.projecty.projectyweb.user.UserService; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.*; 8 | import org.springframework.web.server.ResponseStatusException; 9 | 10 | import java.util.Optional; 11 | 12 | @CrossOrigin() 13 | @RestController 14 | @RequestMapping("projectRoles") 15 | public class ProjectRoleController { 16 | private final UserService userService; 17 | private final ProjectRoleRepository projectRoleRepository; 18 | private final ProjectRoleService projectRoleService; 19 | 20 | public ProjectRoleController(UserService userService, ProjectRoleRepository projectRoleRepository, ProjectRoleService projectRoleService) { 21 | this.userService = userService; 22 | this.projectRoleRepository = projectRoleRepository; 23 | this.projectRoleService = projectRoleService; 24 | } 25 | 26 | @DeleteMapping("/{roleId}") 27 | @EditPermission 28 | public void deleteUserPost( 29 | @PathVariable Long roleId 30 | ) { 31 | User current = userService.getCurrentUser(); 32 | Optional toDeleteOptionalRole = projectRoleRepository.findById(roleId); 33 | if (toDeleteOptionalRole.isPresent() && !toDeleteOptionalRole.get().getUser().equals(current)) { 34 | projectRoleService.deleteRoleFromProject(toDeleteOptionalRole.get()); 35 | } else { 36 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 37 | } 38 | } 39 | 40 | @PatchMapping("/{roleId}") 41 | @EditPermission 42 | public ProjectRole changeRolePatch( 43 | @PathVariable Long roleId, 44 | @RequestBody ProjectRole patchedProjectRole 45 | ) { 46 | Optional optionalRole = projectRoleRepository.findById(roleId); 47 | User current = userService.getCurrentUser(); 48 | if (optionalRole.isPresent() && !optionalRole.get().getUser().equals(current)) { 49 | return projectRoleService.patchProjectRole(optionalRole.get(), patchedProjectRole); 50 | } else { 51 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/UserController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | import com.projecty.projectyweb.configurations.AnyPermission; 4 | import org.apache.commons.io.IOUtils; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.*; 7 | import org.springframework.web.multipart.MultipartFile; 8 | import org.springframework.web.server.ResponseStatusException; 9 | 10 | import javax.servlet.http.HttpServletResponse; 11 | import java.io.IOException; 12 | import java.sql.SQLException; 13 | import java.util.List; 14 | import java.util.Optional; 15 | 16 | @CrossOrigin() 17 | @RestController 18 | public class UserController { 19 | private final UserService userService; 20 | private final UserRepository userRepository; 21 | 22 | public UserController(UserService userService, 23 | UserRepository userRepository) { 24 | this.userService = userService; 25 | this.userRepository = userRepository; 26 | } 27 | 28 | @GetMapping("auth") 29 | public User getUser() { 30 | return userService.getCurrentUser(); 31 | } 32 | 33 | @GetMapping("user/{username}/avatar") 34 | @AnyPermission 35 | public @ResponseBody 36 | byte[] getAvatar( 37 | @PathVariable String username, 38 | HttpServletResponse response 39 | ) throws IOException, SQLException { 40 | Optional maybeUser = userRepository.findByUsername(username); 41 | User user = maybeUser.orElseThrow(() -> new ResponseStatusException(HttpStatus.NOT_FOUND)); 42 | if (user.getAvatar() == null) { 43 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 44 | } 45 | response.setContentType(user.getAvatar().getContentType()); 46 | response.setHeader("Content-Disposition", "attachment; filename=avatar-" + username); 47 | response.flushBuffer(); 48 | return IOUtils.toByteArray(user.getAvatar().getFile().getBinaryStream()); 49 | } 50 | 51 | @PostMapping("user/avatar") 52 | public void setAvatar(@RequestParam("avatar") MultipartFile multipartFile) throws IOException, SQLException { 53 | userService.setUserAvatar(multipartFile); 54 | } 55 | 56 | @GetMapping("users/usernames") 57 | public List getUsernamesStartWith(@RequestParam String usernameStartsWith) { 58 | return userService.getUsernamesStartWith(usernameStartsWith); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/message/AttachmentServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.message.attachment.Attachment; 5 | import com.projecty.projectyweb.message.attachment.AttachmentService; 6 | import org.junit.Test; 7 | import org.junit.runner.RunWith; 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.boot.test.context.SpringBootTest; 10 | import org.springframework.mock.web.MockMultipartFile; 11 | import org.springframework.test.context.ActiveProfiles; 12 | import org.springframework.test.context.junit4.SpringRunner; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | import javax.sql.rowset.serial.SerialBlob; 16 | import java.io.IOException; 17 | import java.sql.SQLException; 18 | import java.util.Arrays; 19 | 20 | import static org.hamcrest.MatcherAssert.assertThat; 21 | import static org.hamcrest.Matchers.*; 22 | 23 | @ActiveProfiles("test") 24 | @RunWith(SpringRunner.class) 25 | @SpringBootTest(classes = ProjectyWebApplication.class) 26 | public class AttachmentServiceTests { 27 | 28 | @Autowired 29 | AttachmentService attachmentService; 30 | 31 | @Test 32 | public void whenGetByteArrayFromAttachment_shouldReturnByteArray() throws SQLException, IOException { 33 | Attachment attachment = new Attachment(); 34 | byte[] array = {0, 1, 2, 3, 4, 5, 6}; 35 | attachment.setFile(new SerialBlob(array)); 36 | assertThat(attachmentService.getByteArrayFromAttachment(attachment), is(array)); 37 | } 38 | 39 | @Test 40 | public void whenAddFilesToMessage_shouldReturnMessageWithFiles() throws IOException, SQLException { 41 | Message message = new Message(); 42 | String name = "file.txt"; 43 | String originalFileName = "file.txt"; 44 | String contentType = "text/plain"; 45 | byte[] content = {0, 1, 2, 3, 4, 5, 6}; 46 | MultipartFile multipartFile = new MockMultipartFile(name, originalFileName, contentType, content); 47 | MultipartFile multipartFile1 = new MockMultipartFile(name, originalFileName, contentType, content); 48 | attachmentService.addFilesToMessage(Arrays.asList(multipartFile, multipartFile1), message); 49 | assertThat(message.getAttachments(), hasSize(greaterThan(0))); 50 | assertThat(message.getAttachments().get(1).getFileName(), is(name)); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/user/UserServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import org.junit.Before; 5 | import org.junit.Test; 6 | import org.junit.runner.RunWith; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.test.context.SpringBootTest; 9 | import org.springframework.security.test.context.support.WithMockUser; 10 | import org.springframework.test.context.ActiveProfiles; 11 | import org.springframework.test.context.junit4.SpringRunner; 12 | 13 | import java.util.List; 14 | 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.hamcrest.Matchers.is; 17 | import static org.hamcrest.Matchers.notNullValue; 18 | import static org.junit.Assert.assertFalse; 19 | import static org.junit.Assert.assertTrue; 20 | 21 | @ActiveProfiles("test") 22 | @RunWith(SpringRunner.class) 23 | @SpringBootTest(classes = ProjectyWebApplication.class) 24 | public class UserServiceTests { 25 | @Autowired 26 | private UserService userService; 27 | 28 | @Autowired 29 | private UserRepository userRepository; 30 | 31 | @Before 32 | public void init() { 33 | if (!userRepository.findByUsername("user").isPresent()) 34 | userRepository.save(User.builder().username("user").build()); 35 | } 36 | 37 | 38 | @SuppressWarnings("OptionalGetWithoutIsPresent") 39 | @Test 40 | public void whenCreateNewUser_shouldReturnNewUser() { 41 | String username = "newUser"; 42 | User user = userService.createUserAndGet(username, "projecty@example.com"); 43 | assertThat(user.getUsername(), is(username)); 44 | assertThat(user.getProjectRoles(), is(notNullValue())); 45 | assertThat(userRepository.findById(user.getId()).get(), is(user)); 46 | } 47 | 48 | @Test 49 | @WithMockUser 50 | public void getUsernameStartWith() { 51 | String username1 = "userX"; 52 | String username2 = "auser"; 53 | String username3 = "userY"; 54 | userRepository.save(User.builder().username(username1).build()); 55 | userRepository.save(User.builder().username(username2).build()); 56 | userRepository.save(User.builder().username(username3).build()); 57 | List usernames = userService.getUsernamesStartWith("user"); 58 | assertTrue(usernames.contains(username1)); 59 | assertTrue(usernames.contains(username3)); 60 | assertFalse(usernames.contains(username2)); 61 | assertFalse(usernames.contains("user")); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @if "%DEBUG%" == "" @echo off 2 | @rem ########################################################################## 3 | @rem 4 | @rem Gradle startup script for Windows 5 | @rem 6 | @rem ########################################################################## 7 | 8 | @rem Set local scope for the variables with windows NT shell 9 | if "%OS%"=="Windows_NT" setlocal 10 | 11 | set DIRNAME=%~dp0 12 | if "%DIRNAME%" == "" set DIRNAME=. 13 | set APP_BASE_NAME=%~n0 14 | set APP_HOME=%DIRNAME% 15 | 16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 17 | set DEFAULT_JVM_OPTS="-Xmx64m" 18 | 19 | @rem Find java.exe 20 | if defined JAVA_HOME goto findJavaFromJavaHome 21 | 22 | set JAVA_EXE=java.exe 23 | %JAVA_EXE% -version >NUL 2>&1 24 | if "%ERRORLEVEL%" == "0" goto init 25 | 26 | echo. 27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 28 | echo. 29 | echo Please set the JAVA_HOME variable in your environment to match the 30 | echo location of your Java installation. 31 | 32 | goto fail 33 | 34 | :findJavaFromJavaHome 35 | set JAVA_HOME=%JAVA_HOME:"=% 36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 37 | 38 | if exist "%JAVA_EXE%" goto init 39 | 40 | echo. 41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 42 | echo. 43 | echo Please set the JAVA_HOME variable in your environment to match the 44 | echo location of your Java installation. 45 | 46 | goto fail 47 | 48 | :init 49 | @rem Get command-line arguments, handling Windows variants 50 | 51 | if not "%OS%" == "Windows_NT" goto win9xME_args 52 | 53 | :win9xME_args 54 | @rem Slurp the command line arguments. 55 | set CMD_LINE_ARGS= 56 | set _SKIP=2 57 | 58 | :win9xME_args_slurp 59 | if "x%~1" == "x" goto execute 60 | 61 | set CMD_LINE_ARGS=%* 62 | 63 | :execute 64 | @rem Setup the command line 65 | 66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 67 | 68 | @rem Execute Gradle 69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% 70 | 71 | :end 72 | @rem End local scope for the variables with windows NT shell 73 | if "%ERRORLEVEL%"=="0" goto mainEnd 74 | 75 | :fail 76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 77 | rem the _cmd.exe /c_ return code! 78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 79 | exit /b 1 80 | 81 | :mainEnd 82 | if "%OS%"=="Windows_NT" endlocal 83 | 84 | :omega 85 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/settings/SettingsService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.settings; 2 | 3 | import com.projecty.projectyweb.user.User; 4 | import com.projecty.projectyweb.user.UserRepository; 5 | import com.projecty.projectyweb.user.UserService; 6 | import org.springframework.stereotype.Service; 7 | 8 | @Service 9 | public class SettingsService { 10 | private final UserService userService; 11 | private final UserRepository userRepository; 12 | private final SettingsRepository settingsRepository; 13 | 14 | public SettingsService(UserService userService, UserRepository userRepository, SettingsRepository settingsRepository) { 15 | this.userService = userService; 16 | this.userRepository = userRepository; 17 | this.settingsRepository = settingsRepository; 18 | } 19 | 20 | public Settings getSettingsForCurrentUser() { 21 | User currentUser = userService.getCurrentUser(); 22 | if (currentUser.getSettings() != null) { 23 | return currentUser.getSettings(); 24 | } 25 | return addSettingsForUser(currentUser); 26 | } 27 | 28 | private Settings addSettingsForUser(User user) { 29 | user.setSettings(new Settings()); 30 | user = userRepository.save(user); 31 | return user.getSettings(); 32 | } 33 | 34 | public Settings patchSettings(Settings patchedSettings) { 35 | User currentUser = userService.getCurrentUser(); 36 | Settings settings = currentUser.getSettings() != null ? currentUser.getSettings() : addSettingsForUser(currentUser); 37 | 38 | // TODO Change this ugly code 39 | Boolean isMessageEmailNotificationsEnabled = patchedSettings.getIsMessageEmailNotificationEnabled(); 40 | if (isMessageEmailNotificationsEnabled != null) { 41 | settings.setIsMessageEmailNotificationEnabled(isMessageEmailNotificationsEnabled); 42 | } 43 | Boolean isEmailNotificationsEnabled = patchedSettings.getIsEmailNotificationEnabled(); 44 | if (isEmailNotificationsEnabled != null) { 45 | settings.setIsEmailNotificationEnabled(isEmailNotificationsEnabled); 46 | } 47 | Boolean canBeAddedToProject = patchedSettings.getCanBeAddedToProject(); 48 | if (canBeAddedToProject != null) { 49 | settings.setCanBeAddedToProject(canBeAddedToProject); 50 | } 51 | Boolean canBeAddedToTeam = patchedSettings.getCanBeAddedToTeam(); 52 | if (canBeAddedToTeam != null) { 53 | settings.setCanBeAddedToTeam(canBeAddedToTeam); 54 | } 55 | return settingsRepository.save(settings); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/resources/messages.properties: -------------------------------------------------------------------------------- 1 | # User error messages 2 | username.empty.user.username=Username can't be empty 3 | password.empty.user.password=Password can't be empty 4 | passwordRepeat.empty.user.passwordRepeat=Retype Password can't be empty 5 | passwordRepeat.diff.user.passwordRepeat=Passwords doesn't match 6 | exists.user.username=Username already taken 7 | password.short.user.password=Password should have at least 8 characters 8 | password.long.user.password=Password shouldn't have more than 30 characters 9 | email.invalid.user.email=Email is invalid 10 | 11 | # Task error messages 12 | start.date.greater.than.end.date=Start Date is greater than End Date 13 | name.empty.task.name=Task name can't be empty 14 | startDate.empty.task.startDate=Task start date can't be empty 15 | endDate.empty.task.endDate=Task end date can't be empty 16 | importance.invalid=Importance must be integer between 1 and 3 17 | 18 | # Project error messages 19 | name.empty.project.name=Project name can't be empty 20 | # Message error messages 21 | message.recipient.invalid=Invalid recipient name 22 | message.recipient.yourself=Can't send message to yourself 23 | message.title.empty=Invalid message title 24 | message.text.empty=Invalid message body 25 | 26 | # Redirect messages 27 | user.register.success=Hi {0}. You can now login to your account 28 | project.add.success=Project added 29 | project.delete.success=Project deleted 30 | projectRole.add.success=User {0} added to project {1} 31 | projectRole.add.not.found=Error while adding user {0}. User not found. 32 | projectRole.add.exists=Error while adding user {0}. User already member of project. 33 | projectRole.delete.success=User {0} deleted from project {1} 34 | projectRole.change.success=Role for user {0} changed 35 | task.add.success=Task {0} added to project {1} 36 | task.delete.success=Task {0} deleted 37 | task.status.change.success=Task {0} status changed 38 | message.send.success=Message send 39 | user.password.change.success=Password changed 40 | user.password.authorize.failed=Failed to authorize user. Current password doesn't match. 41 | 42 | # Team error messages 43 | name.empty.team.name=Team name cannot be empty or blank. 44 | # Exceptions 45 | team.no_managers_in_team_exception=You cannot leave this team. Team must have at least one other team manager. 46 | project.no_admins_in_project_exception=You cannot leave this project. Project must have at least one other project admin. 47 | # Notifications 48 | added.to.project=User {0} added you to project {1} 49 | added.to.team=User {0} added you to team {1} 50 | changed.project.role=User {0} changed your role in project {1} to {2} 51 | changed.team.role=User {0} changed your role in team {1} to {2} 52 | # Messages 53 | received.message.title=User {0} send you a message 54 | message.text.hidden=Text of this message is hidden to protect your privacy. Login to Projecty to read your messages. -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/settings/SettingsServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.settings; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.user.User; 5 | import com.projecty.projectyweb.user.UserRepository; 6 | import com.projecty.projectyweb.user.UserService; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.springframework.beans.factory.annotation.Autowired; 10 | import org.springframework.boot.test.context.SpringBootTest; 11 | import org.springframework.security.test.context.support.WithMockUser; 12 | import org.springframework.test.context.ActiveProfiles; 13 | import org.springframework.test.context.junit4.SpringRunner; 14 | 15 | import static org.hamcrest.MatcherAssert.assertThat; 16 | import static org.hamcrest.Matchers.is; 17 | import static org.hamcrest.Matchers.notNullValue; 18 | import static org.junit.jupiter.api.Assertions.assertFalse; 19 | import static org.junit.jupiter.api.Assertions.assertTrue; 20 | 21 | 22 | @ActiveProfiles("test") 23 | @RunWith(SpringRunner.class) 24 | @SpringBootTest(classes = ProjectyWebApplication.class) 25 | public class SettingsServiceTests { 26 | @Autowired 27 | private SettingsService settingsService; 28 | @Autowired 29 | private UserRepository userRepository; 30 | private static final String USERNAME_1 = "settingsServiceUsername1"; 31 | private static final String USERNAME_2 = "settingsServiceUsername2"; 32 | @Autowired 33 | private UserService userService; 34 | 35 | @Test 36 | @WithMockUser(USERNAME_1) 37 | public void whenGetSettingsWhichDoesNotExist_shouldReturnNewSettings() { 38 | userRepository.save(User.builder().username(USERNAME_1).build()); 39 | Settings settings = settingsService.getSettingsForCurrentUser(); 40 | assertThat(settings.getId(), is(notNullValue())); 41 | assertTrue(settings.getCanBeAddedToProject()); 42 | } 43 | 44 | @Test 45 | @WithMockUser(USERNAME_2) 46 | public void whenPatchSettings_shouldReturnPatchedSettings() { 47 | userRepository.save(User.builder().username(USERNAME_2).build()); 48 | Settings patched = Settings.builder() 49 | .canBeAddedToProject(false) 50 | .canBeAddedToTeam(false) 51 | .isMessageEmailNotificationEnabled(false) 52 | .isEmailNotificationEnabled(false) 53 | .build(); 54 | settingsService.patchSettings(patched); 55 | User user = userService.getCurrentUser(); 56 | assertFalse(user.getSettings().getCanBeAddedToProject()); 57 | assertFalse(user.getSettings().getCanBeAddedToTeam()); 58 | assertFalse(user.getSettings().getIsEmailNotificationEnabled()); 59 | assertFalse(user.getSettings().getIsMessageEmailNotificationEnabled()); 60 | assertFalse(userService.getCurrentUser().getSettings().getCanBeAddedToProject()); 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/settings/SettingsControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.settings; 2 | 3 | import com.google.gson.Gson; 4 | import com.projecty.projectyweb.ProjectyWebApplication; 5 | import com.projecty.projectyweb.user.User; 6 | import com.projecty.projectyweb.user.UserRepository; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mockito; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.boot.test.mock.mockito.MockBean; 15 | import org.springframework.http.MediaType; 16 | import org.springframework.security.test.context.support.WithMockUser; 17 | import org.springframework.test.context.ActiveProfiles; 18 | import org.springframework.test.context.junit4.SpringRunner; 19 | import org.springframework.test.web.servlet.MockMvc; 20 | 21 | import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; 22 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 23 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.patch; 24 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 26 | 27 | @ActiveProfiles("test") 28 | @RunWith(SpringRunner.class) 29 | @SpringBootTest(classes = ProjectyWebApplication.class) 30 | @AutoConfigureMockMvc 31 | public class SettingsControllerTests { 32 | @Autowired 33 | private MockMvc mockMvc; 34 | 35 | @MockBean 36 | private UserRepository userRepository; 37 | 38 | @Before 39 | public void init() { 40 | User user = User.builder() 41 | .id(1L) 42 | .username("user") 43 | .settings(Settings.builder().isEmailNotificationEnabled(true).build()) 44 | .build(); 45 | Mockito.when(userRepository.findByUsername(user.getUsername())) 46 | .thenReturn(java.util.Optional.of(user)); 47 | } 48 | 49 | 50 | @Test 51 | @WithMockUser() 52 | public void givenRequestOnGetSettings_shouldReturnSettings() throws Exception { 53 | mockMvc.perform(get("/settings")) 54 | .andExpect(jsonPath("$.isEmailNotificationEnabled").isBoolean()) 55 | .andExpect(status().isOk()); 56 | } 57 | 58 | @Test 59 | @WithMockUser 60 | public void givenRequestOnPatchSettings_shouldReturnPatchedSettings() throws Exception { 61 | mockMvc.perform(patch("/settings").with(csrf()) 62 | .contentType(MediaType.APPLICATION_JSON) 63 | .content(new Gson().toJson(new Settings()))) 64 | .andExpect(status().isOk()); 65 | } 66 | 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/project/ProjectServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.project.role.ProjectRole; 5 | import com.projecty.projectyweb.project.role.ProjectRoles; 6 | import com.projecty.projectyweb.project.role.dto.ProjectRoleData; 7 | import com.projecty.projectyweb.user.User; 8 | import com.projecty.projectyweb.user.UserRepository; 9 | import org.hamcrest.core.Is; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.springframework.beans.factory.annotation.Autowired; 13 | import org.springframework.boot.test.context.SpringBootTest; 14 | import org.springframework.security.test.context.support.WithMockUser; 15 | import org.springframework.test.context.ActiveProfiles; 16 | import org.springframework.test.context.junit4.SpringRunner; 17 | 18 | import javax.transaction.Transactional; 19 | import java.util.Collections; 20 | import java.util.List; 21 | 22 | import static org.hamcrest.MatcherAssert.assertThat; 23 | import static org.hamcrest.Matchers.is; 24 | 25 | @ActiveProfiles("test") 26 | @RunWith(SpringRunner.class) 27 | @SpringBootTest(classes = ProjectyWebApplication.class) 28 | public class ProjectServiceTests { 29 | @Autowired 30 | private ProjectService projectService; 31 | 32 | @Autowired 33 | private ProjectRepository projectRepository; 34 | 35 | @Autowired 36 | private UserRepository userRepository; 37 | 38 | private static final String USERNAME_1 = "projectServiceUser1"; 39 | private static final String USERNAME_2 = "projectServiceUser2"; 40 | 41 | @SuppressWarnings("OptionalGetWithoutIsPresent") 42 | @Test 43 | @WithMockUser(USERNAME_2) 44 | @Transactional 45 | public void whenAddRolesToProjectSByUsernames_shouldReturnSavedProjectRoles() { 46 | userRepository.save(User.builder().username(USERNAME_1).build()); 47 | Project project = new Project(); 48 | project = projectRepository.save(project); 49 | List usernames = Collections.singletonList(USERNAME_1); 50 | List savedRoles = projectService.addProjectRolesByUsernames(project, usernames); 51 | assertThat(savedRoles.size(), is(1)); 52 | assertThat(projectRepository.findById(project.getId()).get().getProjectRoles().size(), is(1)); 53 | } 54 | 55 | @Test 56 | @WithMockUser(USERNAME_2) 57 | public void whenGetProjectRoleForCurrentUserByProjectId_shouldReturnProjectRoleData() { 58 | userRepository.save(User.builder().username(USERNAME_2).build()); 59 | Project project = projectService.createNewProjectAndSave(new Project(), null); 60 | ProjectRoleData projectRoleData = projectService.getProjectRoleForCurrentUserByProjectId(project.getId()); 61 | assertThat(projectRoleData.getProject().getId(), Is.is(project.getId())); 62 | assertThat(projectRoleData.getName(), Is.is(ProjectRoles.ADMIN)); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | .gradle 3 | /build/ 4 | !gradle/wrapper/gradle-wrapper.jar 5 | 6 | ### STS ### 7 | .apt_generated 8 | .classpath 9 | .factorypath 10 | .project 11 | .settings 12 | .springBeans 13 | .sts4-cache 14 | 15 | ### IntelliJ IDEA ### 16 | .idea 17 | *.iws 18 | *.iml 19 | *.ipr 20 | /out/ 21 | 22 | ### NetBeans ### 23 | **/nbproject/private/ 24 | **/nbproject/Makefile-*.mk 25 | **/nbproject/Package-*.bash 26 | build/ 27 | nbbuild/ 28 | dist/ 29 | nbdist/ 30 | .nb-gradle/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | 36 | # User-specific stuff 37 | .idea/**/workspace.xml 38 | .idea/**/tasks.xml 39 | .idea/**/usage.statistics.xml 40 | .idea/**/dictionaries 41 | .idea/**/shelf 42 | 43 | # Generated files 44 | .idea/**/contentModel.xml 45 | 46 | # Sensitive or high-churn files 47 | .idea/**/dataSources/ 48 | .idea/**/dataSources.ids 49 | .idea/**/dataSources.local.xml 50 | .idea/**/sqlDataSources.xml 51 | .idea/**/dynamic.xml 52 | .idea/**/uiDesigner.xml 53 | .idea/**/dbnavigator.xml 54 | 55 | # Gradle 56 | .idea/**/gradle.xml 57 | .idea/**/libraries 58 | 59 | # Gradle and Maven with auto-import 60 | # When using Gradle or Maven with auto-import, you should exclude module files, 61 | # since they will be recreated, and may cause churn. Uncomment if using 62 | # auto-import. 63 | # .idea/modules.xml 64 | # .idea/*.iml 65 | # .idea/modules 66 | # *.iml 67 | # *.ipr 68 | 69 | # CMake 70 | cmake-build-*/ 71 | 72 | # Mongo Explorer plugin 73 | .idea/**/mongoSettings.xml 74 | 75 | # File-based project format 76 | *.iws 77 | 78 | # IntelliJ 79 | out/ 80 | 81 | # mpeltonen/sbt-idea plugin 82 | .idea_modules/ 83 | 84 | # JIRA plugin 85 | atlassian-ide-plugin.xml 86 | 87 | # Cursive Clojure plugin 88 | .idea/replstate.xml 89 | 90 | # Crashlytics plugin (for Android Studio and IntelliJ) 91 | com_crashlytics_export_strings.xml 92 | crashlytics.properties 93 | crashlytics-build.properties 94 | fabric.properties 95 | 96 | # Editor-based Rest Client 97 | .idea/httpRequests 98 | 99 | # Android studio 3.1+ serialized cache file 100 | .idea/caches/build_file_checksums.ser 101 | 102 | ### Intellij Patch ### 103 | # Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 104 | 105 | # *.iml 106 | # modules.xml 107 | # .idea/misc.xml 108 | # *.ipr 109 | 110 | # Sonarlint plugin 111 | .idea/**/sonarlint/ 112 | 113 | # SonarQube Plugin 114 | .idea/**/sonarIssues.xml 115 | 116 | # Markdown Navigator plugin 117 | .idea/**/markdown-navigator.xml 118 | .idea/**/markdown-navigator/ 119 | 120 | ### Java ### 121 | # Compiled class file 122 | *.class 123 | 124 | # Log file 125 | *.log 126 | 127 | # BlueJ files 128 | *.ctxt 129 | 130 | # Mobile Tools for Java (J2ME) 131 | .mtj.tmp/ 132 | 133 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 134 | hs_err_pid* 135 | 136 | # Cache of project 137 | .gradletasknamecache 138 | 139 | # # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 140 | # gradle/wrapper/gradle-wrapper.properties 141 | 142 | ### Gradle Patch ### 143 | **/build/ 144 | /bin/ 145 | /.DS_Store 146 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/TeamAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | import com.projecty.projectyweb.team.role.TeamRoleService; 4 | import com.projecty.projectyweb.user.User; 5 | import com.projecty.projectyweb.user.UserService; 6 | import org.aspectj.lang.JoinPoint; 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.aspectj.lang.annotation.Before; 9 | import org.aspectj.lang.annotation.Pointcut; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.web.server.ResponseStatusException; 13 | 14 | import java.util.Optional; 15 | import java.util.logging.Logger; 16 | 17 | @Aspect 18 | @Component 19 | public class TeamAspect { 20 | private final TeamRepository teamRepository; 21 | private final TeamRoleService teamRoleService; 22 | private final UserService userService; 23 | private Logger logger = Logger.getLogger(getClass().getName()); 24 | 25 | public TeamAspect(TeamRepository teamRepository, TeamRoleService teamRoleService, UserService userService) { 26 | this.teamRepository = teamRepository; 27 | this.teamRoleService = teamRoleService; 28 | this.userService = userService; 29 | } 30 | 31 | @Pointcut("execution (* com.projecty.projectyweb.team.TeamController.*(Long,..))" + 32 | "&&@annotation(com.projecty.projectyweb.configurations.EditPermission)") 33 | private void inTeamControllerAndWithEditPermission() { 34 | } 35 | 36 | @Pointcut("execution (* com.projecty.projectyweb.team.TeamController.*(Long,..))" + 37 | "&&@annotation(com.projecty.projectyweb.configurations.AnyPermission)") 38 | private void inTeamControllerAndWithAnyPermission() { 39 | } 40 | 41 | @Before("inTeamControllerAndWithEditPermission()") 42 | public void checkEditPermission(JoinPoint joinPoint) { 43 | Long teamId = (Long) joinPoint.getArgs()[0]; 44 | User current = userService.getCurrentUser(); 45 | Optional optionalTeam = teamRepository.findById(teamId); 46 | if (!(optionalTeam.isPresent() && teamRoleService.isCurrentUserTeamManager(optionalTeam.get()))) { 47 | logger.warning("User: " + current.getUsername() 48 | + " tried to execute " + joinPoint.getSignature().toString() 49 | + " without edit permission"); 50 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 51 | } 52 | } 53 | 54 | @Before("inTeamControllerAndWithAnyPermission()") 55 | public void checkAnyPermission(JoinPoint joinPoint) { 56 | Long teamId = (Long) joinPoint.getArgs()[0]; 57 | User current = userService.getCurrentUser(); 58 | Optional optionalTeam = teamRepository.findById(teamId); 59 | if (!(optionalTeam.isPresent() && teamRoleService.hasCurrentUserRoleInTeam(optionalTeam.get()))) { 60 | logger.warning("User: " + current.getUsername() 61 | + " tried to execute " 62 | + joinPoint.getSignature().toString() 63 | + " without any permission"); 64 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/task/TaskAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.task; 2 | 3 | import com.projecty.projectyweb.project.ProjectService; 4 | import com.projecty.projectyweb.user.User; 5 | import com.projecty.projectyweb.user.UserService; 6 | import org.aspectj.lang.JoinPoint; 7 | import org.aspectj.lang.annotation.Aspect; 8 | import org.aspectj.lang.annotation.Before; 9 | import org.aspectj.lang.annotation.Pointcut; 10 | import org.springframework.http.HttpStatus; 11 | import org.springframework.stereotype.Component; 12 | import org.springframework.web.server.ResponseStatusException; 13 | 14 | import java.util.Optional; 15 | import java.util.logging.Logger; 16 | 17 | @Aspect 18 | @Component 19 | public class TaskAspect { 20 | private final UserService userService; 21 | private final TaskRepository taskRepository; 22 | private final ProjectService projectService; 23 | private Logger logger = Logger.getLogger(getClass().getName()); 24 | 25 | public TaskAspect(UserService userService, TaskRepository taskRepository, ProjectService projectService) { 26 | this.userService = userService; 27 | this.taskRepository = taskRepository; 28 | this.projectService = projectService; 29 | } 30 | 31 | @Pointcut("execution (* com.projecty.projectyweb.task.TaskController.*(Long,..))" + 32 | "&&@annotation(com.projecty.projectyweb.configurations.EditPermission)") 33 | private void inTaskControllerAndWithEditPermission() { 34 | } 35 | 36 | @Pointcut("execution (* com.projecty.projectyweb.task.TaskController.*(Long,..))" + 37 | "&&@annotation(com.projecty.projectyweb.configurations.AnyPermission)") 38 | private void inTaskControllerAndWithAnyPermission() { 39 | } 40 | 41 | @Before("inTaskControllerAndWithEditPermission()") 42 | public void checkEditPermission(JoinPoint joinPoint) { 43 | Long taskId = (Long) joinPoint.getArgs()[0]; 44 | User current = userService.getCurrentUser(); 45 | Optional optionalTask = taskRepository.findById(taskId); 46 | if (!(optionalTask.isPresent() && projectService.hasCurrentUserPermissionToEdit(optionalTask.get().getProject()))) { 47 | logger.warning("User: " 48 | + current.getUsername() 49 | + " tried to execute " 50 | + joinPoint.getSignature().toString() 51 | + " edit permission"); 52 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 53 | } 54 | } 55 | 56 | @Before("inTaskControllerAndWithAnyPermission()") 57 | public void checkAnyPermission(JoinPoint joinPoint) { 58 | Long taskId = (Long) joinPoint.getArgs()[0]; 59 | User current = userService.getCurrentUser(); 60 | Optional optionalTask = taskRepository.findById(taskId); 61 | if (!(optionalTask.isPresent() && projectService.hasCurrentUserPermissionToView(optionalTask.get().getProject()))) { 62 | logger.warning("User: " + current.getUsername() 63 | + " tried to execute " 64 | + joinPoint.getSignature().toString() 65 | + " without any permission"); 66 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/ProjectPermissionAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import com.projecty.projectyweb.user.User; 4 | import com.projecty.projectyweb.user.UserService; 5 | import org.aspectj.lang.JoinPoint; 6 | import org.aspectj.lang.annotation.Aspect; 7 | import org.aspectj.lang.annotation.Before; 8 | import org.aspectj.lang.annotation.Pointcut; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.stereotype.Component; 11 | import org.springframework.web.server.ResponseStatusException; 12 | 13 | import java.util.Optional; 14 | import java.util.logging.Logger; 15 | 16 | @Aspect 17 | @Component 18 | public class ProjectPermissionAspect { 19 | private final UserService userService; 20 | private final ProjectRepository projectRepository; 21 | private final ProjectService projectService; 22 | private final Logger logger = Logger.getLogger(getClass().getName()); 23 | 24 | public ProjectPermissionAspect(UserService userService, ProjectRepository projectRepository, ProjectService projectService) { 25 | this.userService = userService; 26 | this.projectRepository = projectRepository; 27 | this.projectService = projectService; 28 | } 29 | 30 | @Pointcut("execution (* com.projecty.projectyweb.project.ProjectController.*(Long,..))" + 31 | "&&@annotation(com.projecty.projectyweb.configurations.EditPermission)") 32 | private void inProjectControllerAndWithEditPermission() { 33 | } 34 | 35 | @Pointcut("execution (* com.projecty.projectyweb.project.ProjectController.*(Long,..))" + 36 | "&&@annotation(com.projecty.projectyweb.configurations.AnyPermission)") 37 | private void inProjectControllerAndWithAnyPermission() { 38 | } 39 | 40 | @Before("inProjectControllerAndWithEditPermission()") 41 | public void checkEditPermission(JoinPoint joinPoint) { 42 | Long projectId = (Long) joinPoint.getArgs()[0]; 43 | User current = userService.getCurrentUser(); 44 | Optional optionalProject = projectRepository.findById(projectId); 45 | if (!(optionalProject.isPresent() && projectService.hasCurrentUserPermissionToEdit(optionalProject.get()))) { 46 | logger.warning("User: " 47 | + current.getUsername() 48 | + " tried to execute " 49 | + joinPoint.getSignature().toString() 50 | + " without edit permission"); 51 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 52 | } 53 | } 54 | 55 | @Before("inProjectControllerAndWithAnyPermission()") 56 | public void checkAnyPermission(JoinPoint joinPoint) { 57 | Long projectId = (Long) joinPoint.getArgs()[0]; 58 | User current = userService.getCurrentUser(); 59 | Optional optionalProject = projectRepository.findById(projectId); 60 | if (!(optionalProject.isPresent() && projectService.hasCurrentUserPermissionToView(optionalProject.get()))) { 61 | logger.warning("User: " + current.getUsername() 62 | + " tried to execute " 63 | + joinPoint.getSignature().toString() 64 | + " without any permission"); 65 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/project/role/ProjectRoleServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.role; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.project.Project; 5 | import com.projecty.projectyweb.project.ProjectRepository; 6 | import com.projecty.projectyweb.user.User; 7 | import com.projecty.projectyweb.user.UserRepository; 8 | import com.projecty.projectyweb.user.UserService; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.Mockito; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.boot.test.mock.mockito.MockBean; 16 | import org.springframework.test.context.ActiveProfiles; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | 19 | import javax.transaction.Transactional; 20 | import java.util.ArrayList; 21 | import java.util.List; 22 | import java.util.Optional; 23 | 24 | import static org.hamcrest.CoreMatchers.is; 25 | import static org.hamcrest.CoreMatchers.notNullValue; 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | 28 | @ActiveProfiles("test") 29 | @RunWith(SpringRunner.class) 30 | @SpringBootTest(classes = ProjectyWebApplication.class) 31 | public class ProjectRoleServiceTests { 32 | @Autowired 33 | private ProjectRoleService projectRoleService; 34 | @Autowired 35 | private ProjectRepository projectRepository; 36 | 37 | @Autowired 38 | private UserRepository userRepository; 39 | 40 | @Autowired 41 | private ProjectRoleRepository projectRoleRepository; 42 | 43 | 44 | @MockBean 45 | private UserService userService; 46 | 47 | private User user; 48 | private User user1; 49 | 50 | @Before 51 | public void init() { 52 | user = new User(); 53 | user.setUsername("user"); 54 | userRepository.save(user); 55 | 56 | user1 = new User(); 57 | user1.setUsername("user1"); 58 | userRepository.save(user1); 59 | 60 | Mockito.when(userService.getCurrentUser()) 61 | .thenReturn(user); 62 | } 63 | 64 | @Test 65 | @Transactional 66 | public void whenAddTeamRolesByUsernames_shouldReturnTeamWithTeamRoles() { 67 | Project project = new Project(); 68 | project.setName("Sample project"); 69 | project = projectRepository.save(project); 70 | List usernames = new ArrayList<>(); 71 | usernames.add(user.getUsername()); 72 | usernames.add(user.getUsername()); 73 | usernames.add(user1.getUsername()); 74 | usernames.add(user1.getUsername()); 75 | List unsavedProjectRoles = projectRoleService.addRolesToProjectByUsernames(project, usernames); 76 | projectRoleService.saveProjectRoles(unsavedProjectRoles); 77 | 78 | Optional optionalProject = projectRepository.findById(project.getId()); 79 | if (optionalProject.isPresent()) { 80 | assertThat(projectRoleRepository.findRoleByUserAndProject(user, optionalProject.get()), is(notNullValue())); 81 | assertThat(projectRoleRepository.findRoleByUserAndProject(user, optionalProject.get()), is(notNullValue())); 82 | } else { 83 | assert false; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Projecty Web v2.0 2 | [![Hits](https://hits.seeyoufarm.com/api/count/incr/badge.svg?url=https%3A%2F%2Fgithub.com%2Fmarcinadd%2Fprojecty-web&count_bg=%2379C83D&title_bg=%23555555&icon=&icon_color=%23E7E7E7&title=PAGE+VIEWS&edge_flat=false)](https://hits.seeyoufarm.com) 3 | 4 | Projecty is a project management app based on spring. 5 | 6 | ## Motivation 7 | My main objective is to create a free and open-source privacy project management application for everyone. 8 | I know that project data are really sensitive, so you feel better when you are the owner of them. 9 | And for, those who do not have a server, 10 | Projecty will be available on the hosted server completely for free (in the future). 11 | 12 | ## Features 13 | ### Tasks 14 | * Tasks management (mark tasks as To do, In progress, Done) 15 | * Assign tasks to users 16 | * Task importance levels 17 | * Group tasks into projects 18 | ### Projects 19 | * Project user management 20 | * Project roles (Admin/User) 21 | ### Teams 22 | * Team user management 23 | * Team Roles (Manager/Member) 24 | ### Chat 25 | * Real time chat via WebSockets 26 | ### Messages 27 | * Send messages to users 28 | * Reply to messages 29 | * Send messages with multiple attachments 30 | ### Notifications 31 | * Notify user about important activities 32 | * Optionally send E-mail notifications 33 | ### Dashboard 34 | * Get assigned tasks for current user 35 | ### Users 36 | * Avatars 37 | * Username completion 38 | * Personalize notifications 39 | * Block adding to new projects/teams 40 | 41 | ### …and more 42 | 43 | 44 | ## Note 45 | **Projecty v2.0 is compatible only with Angular front-end so far. You cannot use this version with 46 | Android or Vue.js clients due to differences in user authentication (and other minor things).** 47 | [Projecty Angular](https://github.com/marcinadd/projecty-angular) 48 | 49 | ## Getting Started 50 | ### Via Docker 51 | 1. After changes run `./build_image.sh` in a project root directory to build application image. 52 | 1. Run`docker-compose up` in a project root directory. 53 | 54 | Data is stored in a volume `db-data`. 55 | 56 | ### Set up project manually 57 | You can use `development` profile to set up project manually. 58 | 1. Switch profile to development in `application.properties`: 59 | ``` 60 | spring.profiles.active=development 61 | ``` 62 | 63 | #### Datasource and Keycloak configuration 64 | Check configuration and edit if you need. 65 | 1. Set database credentials in `application-development.properties`: 66 | ``` 67 | spring.datasource.url=jdbc:mysql://localhost:3306/projecty 68 | spring.datasource.username=root 69 | spring.datasource.password=password 70 | ``` 71 | 1. Set Keycloak server url: 72 | ``` 73 | keycloak.auth-server-url=http://localhost:8081/auth 74 | ``` 75 | ## Prerequisites 76 | * JRE ≥ 11 77 | * Docker or (MySQL 8.0 compatible database and Keycloak 10.0.0 server) 78 | 79 | ## Contributing 80 | Your contribution is welcome. No matter who you are, you can help anyway. 81 | The most helpful is help with coding but graphic designers are also needed. 82 | If you are not a developer or graphic designer don't worry, 83 | you can help with translations, post on a blog, make a video, or tell your friend about Projecty. 84 | Your contribution will be appreciated. 85 | 86 | ## License 87 | Projecty is licensed under GNU GPL v3.0 http://www.gnu.org/licenses/gpl-3.0.html 88 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/User.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import com.fasterxml.jackson.databind.annotation.JsonSerialize; 5 | import com.projecty.projectyweb.message.Message; 6 | import com.projecty.projectyweb.notifications.Notification; 7 | import com.projecty.projectyweb.project.role.ProjectRole; 8 | import com.projecty.projectyweb.settings.Settings; 9 | import com.projecty.projectyweb.task.Task; 10 | import com.projecty.projectyweb.team.role.TeamRole; 11 | import com.projecty.projectyweb.user.avatar.Avatar; 12 | import lombok.*; 13 | import org.springframework.data.annotation.CreatedDate; 14 | 15 | import javax.persistence.*; 16 | import java.io.Serializable; 17 | import java.time.LocalDateTime; 18 | import java.util.List; 19 | 20 | @Entity 21 | @Getter 22 | @Setter 23 | @Builder 24 | @AllArgsConstructor 25 | @NoArgsConstructor 26 | @EqualsAndHashCode(onlyExplicitlyIncluded = true) 27 | @JsonSerialize(using = UserSerializer.class) 28 | public class User implements Serializable { 29 | private static final long serialVersionUID = 1L; 30 | @Id 31 | @GeneratedValue 32 | @EqualsAndHashCode.Include 33 | private Long id; 34 | 35 | @CreatedDate 36 | private LocalDateTime registrationDate; 37 | 38 | private String username; 39 | 40 | // Currently not used 41 | private String email; 42 | 43 | @OneToMany( 44 | mappedBy = "user", 45 | cascade = CascadeType.ALL, 46 | orphanRemoval = true 47 | ) 48 | @JsonIgnore 49 | private List projectRoles; 50 | 51 | @OneToMany( 52 | mappedBy = "user", 53 | cascade = CascadeType.ALL, 54 | orphanRemoval = true 55 | ) 56 | @JsonIgnore 57 | private List teamRoles; 58 | 59 | @OneToMany( 60 | mappedBy = "sender", 61 | cascade = CascadeType.ALL, 62 | orphanRemoval = true 63 | ) 64 | private List messagesFrom; 65 | 66 | @OneToMany( 67 | mappedBy = "recipient", 68 | cascade = CascadeType.ALL, 69 | orphanRemoval = true 70 | ) 71 | private List messagesTo; 72 | 73 | @OneToMany(cascade = CascadeType.ALL, 74 | mappedBy = "user", 75 | orphanRemoval = true) 76 | private List notifications; 77 | 78 | @ManyToMany( 79 | mappedBy = "assignedUsers", 80 | cascade = CascadeType.ALL 81 | ) 82 | private List assignedTasks; 83 | 84 | 85 | @OneToOne( 86 | mappedBy = "user", 87 | cascade = CascadeType.MERGE, 88 | orphanRemoval = true 89 | ) 90 | private Avatar avatar; 91 | 92 | @OneToOne(cascade = CascadeType.ALL, orphanRemoval = true) 93 | private Settings settings; 94 | 95 | @PrePersist 96 | public void prePersist() { 97 | if (settings == null) { 98 | settings = new Settings(); 99 | } 100 | } 101 | 102 | @Override 103 | public String toString() { 104 | return "User{" + 105 | "id=" + id + 106 | ", registrationDate=" + registrationDate + 107 | ", username='" + username + '\'' + 108 | ", email='" + email + '\'' + 109 | '}'; 110 | } 111 | } 112 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/TeamNotificationAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | import com.projecty.projectyweb.notifications.NotificationObjectType; 4 | import com.projecty.projectyweb.notifications.NotificationService; 5 | import com.projecty.projectyweb.notifications.NotificationType; 6 | import com.projecty.projectyweb.team.role.TeamRole; 7 | import com.projecty.projectyweb.user.User; 8 | import com.projecty.projectyweb.user.UserRepository; 9 | import com.projecty.projectyweb.user.UserService; 10 | import org.aspectj.lang.annotation.AfterReturning; 11 | import org.aspectj.lang.annotation.Aspect; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.util.LinkedHashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | @Aspect 19 | @Component 20 | public class TeamNotificationAspect { 21 | private final UserService userService; 22 | private final NotificationService notificationService; 23 | 24 | public TeamNotificationAspect(UserService userService, UserRepository userRepository, NotificationService notificationService) { 25 | this.userService = userService; 26 | this.notificationService = notificationService; 27 | } 28 | 29 | @AfterReturning(value = "execution (* com.projecty.projectyweb.team.TeamService.createTeamAndSave(..))", returning = "team") 30 | public void afterNewTeamCreated(Team team) { 31 | User currentUser = userService.getCurrentUser(); 32 | if (team.getTeamRoles() != null) { 33 | team.getTeamRoles().forEach(teamRole -> { 34 | User teamRoleUser = teamRole.getUser(); 35 | if (!teamRoleUser.equals(currentUser)) { 36 | createAddedToTeamNotification(currentUser, team, teamRoleUser); 37 | } 38 | }); 39 | } 40 | } 41 | 42 | @AfterReturning(value = "execution (* com.projecty.projectyweb.team.role.TeamRoleService.addTeamRolesByUsernames(..))", returning = "teamRoles") 43 | public void afterTeamRolesAdded(List teamRoles) { 44 | User currentUser = userService.getCurrentUser(); 45 | teamRoles.forEach(teamRole -> { 46 | if (teamRole != null) { 47 | createAddedToTeamNotification(currentUser, teamRole.getTeam(), teamRole.getUser()); 48 | } 49 | }); 50 | } 51 | 52 | public void createAddedToTeamNotification(User currentUser, Team team, User notifiedUser) { 53 | Map map = new LinkedHashMap<>(); 54 | map.put(NotificationObjectType.USER, String.valueOf(currentUser.getId())); 55 | map.put(NotificationObjectType.TEAM, String.valueOf(team.getId())); 56 | notificationService.createNotificationAndSave(notifiedUser, NotificationType.ADDED_TO_TEAM, map); 57 | } 58 | 59 | @AfterReturning(value = "execution (* com.projecty.projectyweb.team.role.TeamRoleService.patchTeamRole(..))", returning = "teamRole") 60 | public void afterTeamRolePatched(TeamRole teamRole) { 61 | User currentUser = userService.getCurrentUser(); 62 | if (teamRole != null) { 63 | Map map = new LinkedHashMap<>(); 64 | map.put(NotificationObjectType.USER, String.valueOf(currentUser.getId())); 65 | map.put(NotificationObjectType.TEAM, String.valueOf(teamRole.getTeam().getId())); 66 | map.put(NotificationObjectType.TEAM_ROLE_NAME, teamRole.getName().toString()); 67 | notificationService.createNotificationAndSave(teamRole.getUser(), NotificationType.CHANGED_TEAM_ROLE, map); 68 | } 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/message/association/AssociationServiceTest.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.association; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.message.Message; 5 | import com.projecty.projectyweb.message.MessageRepository; 6 | import com.projecty.projectyweb.user.User; 7 | import org.junit.Before; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | import org.mockito.Mockito; 11 | import org.springframework.beans.factory.annotation.Autowired; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | import org.springframework.test.context.ActiveProfiles; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.test.web.servlet.MockMvc; 17 | 18 | import java.util.Optional; 19 | 20 | import static org.junit.Assert.*; 21 | import static org.mockito.Mockito.times; 22 | import static org.mockito.Mockito.verify; 23 | 24 | @ActiveProfiles("test") 25 | @RunWith(SpringRunner.class) 26 | @SpringBootTest(classes = ProjectyWebApplication.class) 27 | public class AssociationServiceTest { 28 | @MockBean(name = "messageRepository") 29 | MessageRepository messageRepository; 30 | 31 | @MockBean(name="associationRepository") 32 | AssociationRepository associationRepository; 33 | 34 | 35 | 36 | @Autowired 37 | private AssociationService service; 38 | 39 | private Association associationForRecipient; 40 | private Association associationForSender; 41 | 42 | 43 | private Message message; 44 | 45 | 46 | private User user; 47 | private User user2; 48 | @Before 49 | public void init(){ 50 | user = new User(); 51 | user.setId(2L); 52 | user.setUsername("user"); 53 | user.setEmail("user@example.com"); 54 | 55 | user2 = new User(); 56 | user2.setId(3L); 57 | user2.setUsername("user2"); 58 | 59 | message = new Message(); 60 | message.setId(1L); 61 | message.setText("This is sample message"); 62 | message.setTitle("sample title"); 63 | 64 | message.setRecipient(user2); 65 | message.setSender(user); 66 | 67 | 68 | associationForRecipient = new Association(); 69 | associationForRecipient.setUser(user2); 70 | associationForRecipient.setMessage(message); 71 | associationForRecipient.setId(1L); 72 | 73 | associationForSender = new Association(); 74 | associationForSender.setUser(user); 75 | associationForSender.setMessage(message); 76 | associationForSender.setId(2L); 77 | } 78 | 79 | 80 | 81 | @Test 82 | public void deleteMessageForUser() { 83 | Mockito.when(associationRepository.findFirstByUserAndMessage(Mockito.eq(user), Mockito.eq(message))) 84 | .thenReturn(Optional.of(associationForRecipient)); 85 | service.deleteMessageForUser(message,user); 86 | verify(associationRepository, times(1)).delete(associationForRecipient); 87 | } 88 | 89 | @Test 90 | public void isVisibleForUser() { 91 | Mockito.when(associationRepository.findFirstByUserAndMessage(Mockito.eq(user), Mockito.eq(message))) 92 | .thenReturn(Optional.of(associationForRecipient)); 93 | Mockito.when(associationRepository.findFirstByUserAndMessage(Mockito.eq(user2), Mockito.eq(message))) 94 | .thenReturn(Optional.of(associationForSender)); 95 | assertTrue(service.isVisibleForUser(message,user)); 96 | assertTrue(service.isVisibleForUser(message,user2)); 97 | assertFalse(service.isVisibleForUser(message,new User())); 98 | } 99 | } -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/ProjectNotificationAspect.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import com.projecty.projectyweb.notifications.NotificationObjectType; 4 | import com.projecty.projectyweb.notifications.NotificationService; 5 | import com.projecty.projectyweb.notifications.NotificationType; 6 | import com.projecty.projectyweb.project.role.ProjectRole; 7 | import com.projecty.projectyweb.user.User; 8 | import com.projecty.projectyweb.user.UserRepository; 9 | import com.projecty.projectyweb.user.UserService; 10 | import org.aspectj.lang.annotation.AfterReturning; 11 | import org.aspectj.lang.annotation.Aspect; 12 | import org.springframework.stereotype.Component; 13 | 14 | import java.util.LinkedHashMap; 15 | import java.util.List; 16 | import java.util.Map; 17 | 18 | @Aspect 19 | @Component 20 | public class ProjectNotificationAspect { 21 | private final UserService userService; 22 | private final NotificationService notificationService; 23 | 24 | public ProjectNotificationAspect(UserService userService, UserRepository userRepository, NotificationService notificationService) { 25 | this.userService = userService; 26 | this.notificationService = notificationService; 27 | } 28 | 29 | @AfterReturning(value = "execution (* com.projecty.projectyweb.project.ProjectController.addProjectPost(..))", returning = "project") 30 | public void afterNewProjectCreated(Project project) { 31 | User currentUser = userService.getCurrentUser(); 32 | if (project.getProjectRoles() != null) { 33 | project.getProjectRoles().forEach(projectRole -> { 34 | User projectRoleUser = projectRole.getUser(); 35 | if (!projectRoleUser.equals(currentUser)) { 36 | createAddedToProjectNotification(currentUser, project, projectRoleUser); 37 | } 38 | }); 39 | } 40 | } 41 | 42 | @AfterReturning(value = "execution (* com.projecty.projectyweb.project.ProjectController.addUsersToExistingProjectPost(..))", returning = "projectRoles") 43 | public void afterProjectRolesAdded(List projectRoles) { 44 | User currentUser = userService.getCurrentUser(); 45 | projectRoles.forEach(projectRole -> { 46 | createAddedToProjectNotification(currentUser, projectRole.getProject(), projectRole.getUser()); 47 | }); 48 | } 49 | 50 | public void createAddedToProjectNotification(User currentUser, Project project, User notifiedUser) { 51 | Map map = new LinkedHashMap<>(); 52 | map.put(NotificationObjectType.USER, String.valueOf(currentUser.getId())); 53 | map.put(NotificationObjectType.PROJECT, String.valueOf(project.getId())); 54 | notificationService.createNotificationAndSave(notifiedUser, NotificationType.ADDED_TO_PROJECT, map); 55 | } 56 | 57 | @AfterReturning(value = "execution (* com.projecty.projectyweb.project.role.ProjectRoleService.patchProjectRole(..))", returning = "projectRole") 58 | public void afterProjectRolePatched(ProjectRole projectRole) { 59 | User currentUser = userService.getCurrentUser(); 60 | if (projectRole != null) { 61 | Map map = new LinkedHashMap<>(); 62 | map.put(NotificationObjectType.USER, String.valueOf(currentUser.getId())); 63 | map.put(NotificationObjectType.PROJECT, String.valueOf(projectRole.getProject().getId())); 64 | map.put(NotificationObjectType.PROJECT_ROLE_NAME, projectRole.getName().toString()); 65 | notificationService.createNotificationAndSave(projectRole.getUser(), NotificationType.CHANGED_PROJECT_ROLE, map); 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/team/TeamService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | 4 | import com.projecty.projectyweb.project.Project; 5 | import com.projecty.projectyweb.project.ProjectRepository; 6 | import com.projecty.projectyweb.team.misc.TeamSummaryService; 7 | import com.projecty.projectyweb.team.role.TeamRole; 8 | import com.projecty.projectyweb.team.role.TeamRoleRepository; 9 | import com.projecty.projectyweb.team.role.TeamRoleService; 10 | import com.projecty.projectyweb.team.role.dto.TeamProjectsData; 11 | import com.projecty.projectyweb.team.role.dto.TeamRoleData; 12 | import com.projecty.projectyweb.user.User; 13 | import com.projecty.projectyweb.user.UserService; 14 | import org.springframework.beans.factory.annotation.Autowired; 15 | import org.springframework.stereotype.Service; 16 | 17 | import java.util.ArrayList; 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | 22 | @Service 23 | public class TeamService { 24 | private final TeamRepository teamRepository; 25 | private final TeamRoleService teamRoleService; 26 | private final TeamRoleRepository teamRoleRepository; 27 | private final ProjectRepository projectRepository; 28 | private final UserService userService; 29 | 30 | @Autowired 31 | public TeamService(TeamRepository teamRepository, TeamRoleService teamRoleService, TeamRoleRepository teamRoleRepository, ProjectRepository projectRepository, UserService userService) { 32 | this.teamRepository = teamRepository; 33 | this.teamRoleService = teamRoleService; 34 | this.teamRoleRepository = teamRoleRepository; 35 | this.projectRepository = projectRepository; 36 | this.userService = userService; 37 | } 38 | 39 | public Team createTeamAndSave(Team team, List usernames) { 40 | teamRoleService.addCurrentUserAsTeamManager(team); 41 | teamRoleService.addTeamMembersByUsernames(team, usernames); 42 | return teamRepository.save(team); 43 | } 44 | 45 | public Project createProjectForTeam(Team team, Project project) { 46 | project.setTeam(team); 47 | project = projectRepository.save(project); 48 | team.getProjects().add(project); 49 | teamRepository.save(team); 50 | return project; 51 | } 52 | 53 | public TeamProjectsData getTeamProjects(Team team) { 54 | return TeamProjectsData.builder() 55 | .teamName(team.getName()) 56 | .projects(team.getProjects()) 57 | .isCurrentUserTeamManager(teamRoleService.isCurrentUserTeamManager(team)) 58 | .build(); 59 | } 60 | 61 | public Team editTeam(Team team, Team patchedTeam) { 62 | if (!patchedTeam.getName().isEmpty()) { 63 | team.setName(patchedTeam.getName()); 64 | } 65 | return teamRepository.save(team); 66 | } 67 | 68 | public Optional findById(Long teamId) { 69 | return teamRepository.findById(teamId); 70 | } 71 | 72 | public Team save(Team team) { 73 | return teamRepository.save(team); 74 | } 75 | 76 | public void delete(Team team) { 77 | teamRepository.delete(team); 78 | } 79 | 80 | public List getTeams() { 81 | List teamRoles = userService.getCurrentUser().getTeamRoles(); 82 | teamRoles.forEach(t -> TeamSummaryService.generateTeamSummary(t.getTeam())); 83 | List teamRoleData = new ArrayList<>(); 84 | teamRoles.forEach(t -> teamRoleData.add(new TeamRoleData(t))); 85 | return teamRoleData; 86 | } 87 | 88 | public TeamRoleData getTeamRoleForCurrentUserByTeamId(Long teamId) { 89 | User currentUser = userService.getCurrentUser(); 90 | Optional optionalTeam = teamRepository.findById(teamId); 91 | if (optionalTeam.isPresent()) { 92 | Optional optionalTeamRole = teamRoleRepository.findByTeamAndAndUser(optionalTeam.get(), currentUser); 93 | return optionalTeamRole.map(TeamRoleData::new).orElse(null); 94 | } 95 | return null; 96 | } 97 | } 98 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/message/attachment/AttachmentControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message.attachment; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.message.Message; 5 | import com.projecty.projectyweb.message.association.Association; 6 | import com.projecty.projectyweb.message.association.AssociationRepository; 7 | import com.projecty.projectyweb.user.User; 8 | import com.projecty.projectyweb.user.UserRepository; 9 | import org.junit.Before; 10 | import org.junit.Test; 11 | import org.junit.runner.RunWith; 12 | import org.mockito.Mockito; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 15 | import org.springframework.boot.test.context.SpringBootTest; 16 | import org.springframework.boot.test.mock.mockito.MockBean; 17 | import org.springframework.security.test.context.support.WithMockUser; 18 | import org.springframework.test.context.ActiveProfiles; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | import org.springframework.test.web.servlet.MockMvc; 21 | 22 | import javax.sql.rowset.serial.SerialBlob; 23 | import java.sql.SQLException; 24 | import java.util.Optional; 25 | import java.util.UUID; 26 | 27 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 28 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 29 | 30 | @ActiveProfiles("test") 31 | @RunWith(SpringRunner.class) 32 | @SpringBootTest(classes = ProjectyWebApplication.class) 33 | @AutoConfigureMockMvc 34 | public class AttachmentControllerTests { 35 | private final UUID uuid = UUID.randomUUID(); 36 | @Autowired 37 | MockMvc mockMvc; 38 | @MockBean 39 | AttachmentRepository attachmentRepository; 40 | 41 | @MockBean 42 | AssociationRepository associationRepository; 43 | 44 | @MockBean 45 | UserRepository userRepository; 46 | 47 | @Before 48 | public void init() throws SQLException { 49 | User user = User.builder() 50 | .username("user") 51 | .build(); 52 | User user1 = User.builder() 53 | .username("user1") 54 | .build(); 55 | User user2 = User.builder() 56 | .username("user2") 57 | .build(); 58 | 59 | Message message = Message.builder() 60 | .sender(user) 61 | .recipient(user1) 62 | .build(); 63 | Attachment attachment = new Attachment(); 64 | attachment.setMessage(message); 65 | attachment.setId(uuid); 66 | attachment.setFile(new SerialBlob("Text".getBytes())); 67 | attachment.setFileName("test.txt"); 68 | 69 | Association association = new Association(); 70 | association.setMessage(message); 71 | association.setUser(user); 72 | Mockito.when(attachmentRepository.findById(uuid)) 73 | .thenReturn(Optional.of(attachment)); 74 | Mockito.when(userRepository.findByUsername(user.getUsername())) 75 | .thenReturn(Optional.of(user)); 76 | Mockito.when(userRepository.findByUsername(user1.getUsername())) 77 | .thenReturn(Optional.of(user1)); 78 | Mockito.when(userRepository.findByUsername(user2.getUsername())) 79 | .thenReturn(Optional.of(user2)); 80 | Mockito.when(associationRepository.findFirstByUserAndMessage(user, message)) 81 | .thenReturn(Optional.of(association)); 82 | } 83 | 84 | @Test 85 | @WithMockUser 86 | public void givenRequestOnDownloadAttachment_shouldReturnAttachementToDownload() throws Exception { 87 | mockMvc.perform(get("/attachments/" + uuid)) 88 | .andExpect(status().isOk()); 89 | } 90 | 91 | @Test 92 | @WithMockUser("user2") 93 | public void givenRequestOnDownloadAttachmentWhichNotBelongsToUser_shouldReturnUnauthorized() throws Exception { 94 | mockMvc.perform(get("/attachments/" + uuid)) 95 | .andExpect(status().isOk()); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/notification/TeamNotificationAspectTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notification; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.notifications.NotificationRepository; 5 | import com.projecty.projectyweb.team.Team; 6 | import com.projecty.projectyweb.team.TeamNotificationAspect; 7 | import com.projecty.projectyweb.team.TeamRepository; 8 | import com.projecty.projectyweb.team.role.TeamRole; 9 | import com.projecty.projectyweb.team.role.TeamRoleRepository; 10 | import com.projecty.projectyweb.team.role.TeamRoles; 11 | import com.projecty.projectyweb.user.User; 12 | import com.projecty.projectyweb.user.UserRepository; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.security.test.context.support.WithMockUser; 18 | import org.springframework.test.context.ActiveProfiles; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | 21 | import javax.transaction.Transactional; 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | import static org.hamcrest.Matchers.is; 28 | 29 | @ActiveProfiles("test") 30 | @RunWith(SpringRunner.class) 31 | @SpringBootTest(classes = ProjectyWebApplication.class) 32 | public class TeamNotificationAspectTests { 33 | @Autowired 34 | private TeamNotificationAspect teamNotificationAspect; 35 | 36 | @Autowired 37 | private TeamRepository teamRepository; 38 | 39 | @Autowired 40 | private TeamRoleRepository teamRoleRepository; 41 | 42 | @Autowired 43 | private UserRepository userRepository; 44 | 45 | @Autowired 46 | private NotificationRepository notificationRepository; 47 | 48 | @Test 49 | @WithMockUser("aspectCurrentUser") 50 | @Transactional 51 | public void whenCreateTeam_shouldSendNotifications() { 52 | Team team = teamRepository.save(new Team()); 53 | User savedUser = userRepository.save(User.builder().username("aspectTeamUser1").build()); 54 | TeamRole teamRole = new TeamRole(); 55 | teamRole.setTeam(team); 56 | teamRole.setUser(savedUser); 57 | teamRole = teamRoleRepository.save(teamRole); 58 | team.setTeamRoles(Collections.singletonList(teamRole)); 59 | team = teamRepository.findById(team.getId()).get(); 60 | teamNotificationAspect.afterNewTeamCreated(team); 61 | assertThat(notificationRepository.findByUser(savedUser).size(), is(1)); 62 | } 63 | 64 | @Test 65 | @WithMockUser("aspectCurrentUser") 66 | public void whenAddRolesToExistingTeam_shouldSendNotifications() { 67 | Team team = teamRepository.save(new Team()); 68 | User savedUser = userRepository.save(User.builder().username("aspectTeamUser2").build()); 69 | TeamRole teamRole = new TeamRole(); 70 | teamRole.setTeam(team); 71 | teamRole.setUser(savedUser); 72 | teamRole = teamRoleRepository.save(teamRole); 73 | List teamRoles = new ArrayList<>(); 74 | teamRoles.add(teamRole); 75 | teamNotificationAspect.afterTeamRolesAdded(teamRoles); 76 | assertThat(notificationRepository.findByUser(savedUser).size(), is(1)); 77 | } 78 | 79 | @Test 80 | @WithMockUser("aspectCurrentUser") 81 | @Transactional 82 | public void whenChangeTeamRole_shouldSendNotifications() { 83 | Team team = teamRepository.save(new Team()); 84 | User savedUser = userRepository.save(User.builder().username("aspectTeamUser3").build()); 85 | TeamRole teamRole = new TeamRole(); 86 | teamRole.setTeam(team); 87 | teamRole.setUser(savedUser); 88 | teamRole.setName(TeamRoles.MANAGER); 89 | teamRole = teamRoleRepository.save(teamRole); 90 | team.setTeamRoles(Collections.singletonList(teamRole)); 91 | teamNotificationAspect.afterTeamRolePatched(teamRole); 92 | assertThat(notificationRepository.findByUser(savedUser).size(), is(1)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/notification/NotificationServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notification; 2 | 3 | 4 | import com.projecty.projectyweb.ProjectyWebApplication; 5 | import com.projecty.projectyweb.notifications.*; 6 | import com.projecty.projectyweb.project.Project; 7 | import com.projecty.projectyweb.project.ProjectRepository; 8 | import com.projecty.projectyweb.user.User; 9 | import com.projecty.projectyweb.user.UserRepository; 10 | import org.junit.Before; 11 | import org.junit.Test; 12 | import org.junit.runner.RunWith; 13 | import org.springframework.beans.factory.annotation.Autowired; 14 | import org.springframework.boot.test.context.SpringBootTest; 15 | import org.springframework.security.test.context.support.WithMockUser; 16 | import org.springframework.test.context.ActiveProfiles; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | 19 | import javax.transaction.Transactional; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | import static org.hamcrest.CoreMatchers.is; 24 | import static org.hamcrest.MatcherAssert.assertThat; 25 | import static org.junit.Assert.assertTrue; 26 | 27 | @ActiveProfiles("test") 28 | @RunWith(SpringRunner.class) 29 | @SpringBootTest(classes = ProjectyWebApplication.class) 30 | public class NotificationServiceTests { 31 | private static final String PROJECT_NAME = "projectName"; 32 | private static final String USERNAME_B = "notificationServiceUsernameB"; 33 | private static final String USERNAME_C = "notificationServiceUsernameC"; 34 | @Autowired 35 | private NotificationService notificationService; 36 | @Autowired 37 | private UserRepository userRepository; 38 | @Autowired 39 | private ProjectRepository projectRepository; 40 | @Autowired 41 | private NotificationRepository notificationRepository; 42 | 43 | @Before 44 | public void init() { 45 | 46 | } 47 | 48 | @Test 49 | public void whenBuildNotificationString_shouldReturnNotificationString() { 50 | final String USERNAME = "notificationServiceUsernameA"; 51 | User user = userRepository.save(User.builder().username(USERNAME).build()); 52 | Project project = projectRepository.save(Project.builder().name(PROJECT_NAME).build()); 53 | Map ids = new LinkedHashMap<>(); 54 | ids.put(NotificationObjectType.USER, String.valueOf(user.getId())); 55 | ids.put(NotificationObjectType.PROJECT, String.valueOf(project.getId())); 56 | Notification notification = notificationService.createNotificationAndSave(user, NotificationType.ADDED_TO_PROJECT, ids); 57 | String stringValue = notificationService.buildNotificationString(notification); 58 | assertThat(stringValue, is("User " + USERNAME + " added you to project " + PROJECT_NAME)); 59 | } 60 | 61 | @Test 62 | @WithMockUser(USERNAME_B) 63 | @Transactional 64 | public void whenGetAllNotifications_shouldReturnAllNotifications() { 65 | User user = userRepository.save(User.builder().username(USERNAME_B).build()); 66 | Project project = projectRepository.save(Project.builder().name(PROJECT_NAME).build()); 67 | Map ids = new LinkedHashMap<>(); 68 | ids.put(NotificationObjectType.USER, String.valueOf(user.getId())); 69 | ids.put(NotificationObjectType.PROJECT, String.valueOf(project.getId())); 70 | Notification notification = notificationService.createNotificationAndSave(user, NotificationType.ADDED_TO_PROJECT, ids); 71 | assertTrue(notificationService.getNotifications().contains(notification)); 72 | } 73 | 74 | @Test 75 | @WithMockUser(USERNAME_C) 76 | public void whenGetUnseenNotificationCount_shouldReturnUnseenNotificationCount() { 77 | User user = userRepository.save(User.builder().username(USERNAME_C).build()); 78 | notificationRepository.save(Notification.builder().user(user).seen(false).build()); 79 | notificationRepository.save(Notification.builder().user(user).seen(false).build()); 80 | notificationRepository.save(Notification.builder().user(user).seen(false).build()); 81 | assertThat(notificationService.getUnseenNotificationCount(), is(3L)); 82 | 83 | } 84 | } 85 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/notification/ProjectNotificationAspectTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.notification; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.notifications.NotificationRepository; 5 | import com.projecty.projectyweb.project.Project; 6 | import com.projecty.projectyweb.project.ProjectNotificationAspect; 7 | import com.projecty.projectyweb.project.ProjectRepository; 8 | import com.projecty.projectyweb.project.role.ProjectRole; 9 | import com.projecty.projectyweb.project.role.ProjectRoleRepository; 10 | import com.projecty.projectyweb.project.role.ProjectRoles; 11 | import com.projecty.projectyweb.user.User; 12 | import com.projecty.projectyweb.user.UserRepository; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.springframework.beans.factory.annotation.Autowired; 16 | import org.springframework.boot.test.context.SpringBootTest; 17 | import org.springframework.security.test.context.support.WithMockUser; 18 | import org.springframework.test.context.ActiveProfiles; 19 | import org.springframework.test.context.junit4.SpringRunner; 20 | 21 | import javax.transaction.Transactional; 22 | import java.util.ArrayList; 23 | import java.util.Collections; 24 | import java.util.List; 25 | 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | import static org.hamcrest.Matchers.is; 28 | 29 | @ActiveProfiles("test") 30 | @RunWith(SpringRunner.class) 31 | @SpringBootTest(classes = ProjectyWebApplication.class) 32 | public class ProjectNotificationAspectTests { 33 | @Autowired 34 | private ProjectNotificationAspect projectNotificationAspect; 35 | 36 | @Autowired 37 | private ProjectRepository projectRepository; 38 | 39 | @Autowired 40 | private ProjectRoleRepository projectRoleRepository; 41 | 42 | @Autowired 43 | private UserRepository userRepository; 44 | 45 | @Autowired 46 | private NotificationRepository notificationRepository; 47 | 48 | @Test 49 | @WithMockUser("aspectCurrentUser") 50 | @Transactional 51 | public void whenCreateProject_shouldSendNotifications() { 52 | Project project = projectRepository.save(new Project()); 53 | User savedUser = userRepository.save(User.builder().username("aspectProjectUser1").build()); 54 | ProjectRole projectRole = new ProjectRole(); 55 | projectRole.setProject(project); 56 | projectRole.setUser(savedUser); 57 | projectRole = projectRoleRepository.save(projectRole); 58 | project.setProjectRoles(Collections.singletonList(projectRole)); 59 | project = projectRepository.findById(project.getId()).get(); 60 | projectNotificationAspect.afterNewProjectCreated(project); 61 | assertThat(notificationRepository.findByUser(savedUser).size(), is(1)); 62 | } 63 | 64 | @Test 65 | @WithMockUser("aspectCurrentUser") 66 | public void whenAddRolesToExistingProject_shouldSendNotifications() { 67 | Project project = projectRepository.save(new Project()); 68 | User savedUser = userRepository.save(User.builder().username("aspectProjectUser2").build()); 69 | ProjectRole projectRole = new ProjectRole(); 70 | projectRole.setProject(project); 71 | projectRole.setUser(savedUser); 72 | projectRole = projectRoleRepository.save(projectRole); 73 | List projectRoles = new ArrayList<>(); 74 | projectRoles.add(projectRole); 75 | projectNotificationAspect.afterProjectRolesAdded(projectRoles); 76 | assertThat(notificationRepository.findByUser(savedUser).size(), is(1)); 77 | } 78 | 79 | @Test 80 | @WithMockUser("aspectCurrentUser") 81 | @Transactional 82 | public void whenChangeProjectRole_shouldSendNotifications() { 83 | Project project = projectRepository.save(new Project()); 84 | User savedUser = userRepository.save(User.builder().username("aspectProjectUser3").build()); 85 | ProjectRole projectRole = new ProjectRole(); 86 | projectRole.setProject(project); 87 | projectRole.setUser(savedUser); 88 | projectRole.setName(ProjectRoles.ADMIN); 89 | projectRole = projectRoleRepository.save(projectRole); 90 | project.setProjectRoles(Collections.singletonList(projectRole)); 91 | projectNotificationAspect.afterProjectRolePatched(projectRole); 92 | assertThat(notificationRepository.findByUser(savedUser).size(), is(1)); 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/user/UserControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.message.MessageRepository; 5 | import com.projecty.projectyweb.user.avatar.Avatar; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mockito; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | import org.springframework.mock.web.MockMultipartFile; 15 | import org.springframework.security.test.context.support.WithMockUser; 16 | import org.springframework.test.context.ActiveProfiles; 17 | import org.springframework.test.context.junit4.SpringRunner; 18 | import org.springframework.test.web.servlet.MockMvc; 19 | 20 | import javax.sql.rowset.serial.SerialBlob; 21 | import java.sql.SQLException; 22 | 23 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 24 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 25 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 26 | 27 | @ActiveProfiles("test") 28 | @RunWith(SpringRunner.class) 29 | @SpringBootTest(classes = ProjectyWebApplication.class) 30 | @AutoConfigureMockMvc 31 | public class UserControllerTests { 32 | @Autowired 33 | private MockMvc mockMvc; 34 | 35 | @MockBean 36 | UserRepository userRepository; 37 | 38 | @MockBean 39 | MessageRepository messageRepository; 40 | 41 | private User user; 42 | private User userWithAvatar; 43 | 44 | @Before 45 | public void init() { 46 | byte[] bytes = new byte[]{0, 1, 2, 3, 4, 5}; 47 | 48 | user = User.builder() 49 | .username("user") 50 | .email("admin@example.com") 51 | .build(); 52 | user.setId(1L); 53 | Mockito.when(userRepository.findByUsername(user.getUsername())) 54 | .thenReturn(java.util.Optional.ofNullable(user)); 55 | 56 | userWithAvatar = new User(); 57 | userWithAvatar = User.builder() 58 | .username("userWithAvatar") 59 | .email("adminWithAvatar@example.com") 60 | .build(); 61 | userWithAvatar.setId(12L); 62 | Avatar avatar = new Avatar(); 63 | avatar.setUser(userWithAvatar); 64 | avatar.setContentType("image/jpeg"); 65 | try { 66 | avatar.setFile(new SerialBlob(bytes)); 67 | } catch (SQLException ignored) { 68 | } 69 | userWithAvatar.setAvatar(avatar); 70 | Mockito.when(userRepository.findByUsername(userWithAvatar.getUsername())) 71 | .thenReturn(java.util.Optional.ofNullable(userWithAvatar)); 72 | } 73 | 74 | @Test 75 | @WithMockUser 76 | public void givenRequestForAvatarOnMissingUser_shouldReturnNotFound() throws Exception { 77 | mockMvc.perform(get("/user/nouser/avatar")) 78 | .andExpect(status().isNotFound()); 79 | } 80 | 81 | @Test 82 | @WithMockUser 83 | public void givenRequestForAvatarWithNoAvatar_shouldReturnNotFound() throws Exception { 84 | mockMvc.perform(get("/user/user/avatar")) 85 | .andExpect(status().isNotFound()); 86 | } 87 | 88 | @Test 89 | @WithMockUser 90 | public void givenRequestForAvatarWithAvatar_shouldReturnAvatar() throws Exception { 91 | mockMvc.perform(get("/user/userWithAvatar/avatar")) 92 | .andExpect(status().isOk()); 93 | } 94 | 95 | @Test 96 | @WithMockUser("user2") 97 | public void givenRequestForAvatarByAnotherUser_shouldReturnAvatar() throws Exception { 98 | mockMvc.perform(get("/user/userWithAvatar/avatar")) 99 | .andExpect(status().isOk()); 100 | } 101 | 102 | @Test 103 | @WithMockUser 104 | public void whenGetUsernamesStartWith_shouldReturnUsernamesStartWith() throws Exception { 105 | mockMvc.perform(get("/users/usernames") 106 | .param("usernameStartsWith", "user")) 107 | .andExpect(status().isOk()) 108 | .andExpect(jsonPath("$").isArray()); 109 | } 110 | } 111 | 112 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/message/MessageController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | import com.projecty.projectyweb.configurations.AnyPermission; 4 | import com.projecty.projectyweb.message.association.AssociationService; 5 | import com.projecty.projectyweb.message.attachment.AttachmentRepository; 6 | import com.projecty.projectyweb.message.attachment.AttachmentService; 7 | import com.projecty.projectyweb.user.UserService; 8 | import org.springframework.data.domain.Page; 9 | import org.springframework.http.HttpStatus; 10 | import org.springframework.http.ResponseEntity; 11 | import org.springframework.validation.BindException; 12 | import org.springframework.web.bind.annotation.*; 13 | import org.springframework.web.multipart.MultipartFile; 14 | 15 | import java.util.List; 16 | import java.util.Optional; 17 | 18 | 19 | @CrossOrigin() 20 | @RestController 21 | @RequestMapping("messages") 22 | public class MessageController { 23 | private final UserService userService; 24 | private final MessageRepository messageRepository; 25 | private final MessageService messageService; 26 | private final AssociationService associationService; 27 | private final AttachmentService attachmentService; 28 | private final AttachmentRepository attachmentRepository; 29 | 30 | public MessageController(UserService userService, MessageRepository messageRepository, MessageService messageService, AttachmentService attachmentService, AssociationService associationService, AttachmentRepository attachmentRepository) { 31 | this.userService = userService; 32 | this.messageRepository = messageRepository; 33 | this.messageService = messageService; 34 | this.attachmentService = attachmentService; 35 | this.associationService = associationService; 36 | this.attachmentRepository = attachmentRepository; 37 | } 38 | 39 | @GetMapping 40 | public Page getPageOfMessages( 41 | @RequestParam(defaultValue = "ALL") MessageType type, 42 | @RequestParam(defaultValue = "0") int page, 43 | @RequestParam(defaultValue = "25") int itemsPerPage 44 | ) { 45 | return messageService.getPageOfMessagesForCurrentUser(type, page, itemsPerPage); 46 | } 47 | 48 | @PostMapping 49 | public Message sendMessagePost( 50 | @RequestParam String recipientUsername, 51 | @RequestParam String title, 52 | @RequestParam String text, 53 | @RequestParam(required = false) List multipartFiles 54 | 55 | ) throws BindException { 56 | Message message = Message.builder() 57 | .recipientUsername(recipientUsername) 58 | .title(title) 59 | .text(text) 60 | .recipientUsername(recipientUsername) 61 | .build(); 62 | return messageService.sendMessage(message, multipartFiles); 63 | } 64 | 65 | @GetMapping("/{messageId}") 66 | @AnyPermission 67 | public ResponseEntity viewMessage( 68 | @PathVariable Long messageId 69 | ) { 70 | Optional optMessage = messageRepository.findById(messageId); 71 | if(optMessage.isPresent()) { 72 | final Message message = optMessage.get(); 73 | if (associationService.isVisibleForUser(message, userService.getCurrentUser())) { 74 | messageService.updateSeenDate(message); 75 | return ResponseEntity.ok(message); 76 | } 77 | return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); 78 | } 79 | return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(null); 80 | } 81 | 82 | @GetMapping("getUnreadMessageCount") 83 | public int getUnreadMessageCount() { 84 | return messageService.getUnreadMessageCountForCurrentUser(); 85 | } 86 | 87 | @PostMapping("{replyToMessageId}/reply") 88 | public Message replyToMessage( 89 | @PathVariable Long replyToMessageId, 90 | @RequestParam String title, 91 | @RequestParam String text, 92 | @RequestParam(required = false) List multipartFiles 93 | 94 | ) throws BindException { 95 | Message message = Message.builder() 96 | .title(title) 97 | .text(text) 98 | .build(); 99 | return messageService.reply(replyToMessageId, message, multipartFiles); 100 | } 101 | 102 | @DeleteMapping(value = "{id}") 103 | public void deleteMessage( 104 | @PathVariable("id") long messageId 105 | ) { 106 | Optional optMessage = messageRepository.findById(messageId); 107 | optMessage.ifPresent(messageService::deleteMessage); 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/user/UserService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.user; 2 | 3 | import com.projecty.projectyweb.user.avatar.Avatar; 4 | import com.projecty.projectyweb.user.avatar.AvatarService; 5 | import org.keycloak.KeycloakPrincipal; 6 | import org.keycloak.KeycloakSecurityContext; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | import org.springframework.security.core.userdetails.UserDetails; 9 | import org.springframework.stereotype.Service; 10 | import org.springframework.web.multipart.MultipartFile; 11 | 12 | import javax.sql.rowset.serial.SerialBlob; 13 | import java.io.IOException; 14 | import java.sql.SQLException; 15 | import java.util.ArrayList; 16 | import java.util.List; 17 | import java.util.Optional; 18 | import java.util.Set; 19 | import java.util.stream.Collectors; 20 | 21 | 22 | @Service 23 | public class UserService { 24 | private final UserRepository userRepository; 25 | private final AvatarService avatarService; 26 | 27 | public UserService(UserRepository userRepository, AvatarService avatarService) { 28 | this.userRepository = userRepository; 29 | this.avatarService = avatarService; 30 | } 31 | 32 | public User getCurrentUser() { 33 | String currentUsername = null; 34 | String email = null; 35 | Object principal = SecurityContextHolder 36 | .getContext().getAuthentication().getPrincipal(); 37 | if (principal instanceof KeycloakPrincipal) { 38 | KeycloakPrincipal keycloakPrincipal = (KeycloakPrincipal) principal; 39 | currentUsername = keycloakPrincipal.getName(); 40 | email = getEmailFromKeycloakPrincipal(keycloakPrincipal); 41 | } else if (principal instanceof UserDetails) { 42 | // Legacy code for testing 43 | //TODO Move this part of code to test package 44 | currentUsername = ((UserDetails) principal).getUsername(); 45 | } 46 | Optional user = userRepository.findByUsername(currentUsername); 47 | String finalCurrentUsername = currentUsername; 48 | String finalEmail = email; 49 | return user.orElseGet(() -> createUserAndGet(finalCurrentUsername, finalEmail)); 50 | } 51 | 52 | private String getEmailFromKeycloakPrincipal(KeycloakPrincipal keycloakPrincipal) { 53 | return keycloakPrincipal.getKeycloakSecurityContext().getToken().getEmail(); 54 | } 55 | 56 | @SuppressWarnings("OptionalGetWithoutIsPresent") 57 | public User createUserAndGet(String username, String email) { 58 | User user = userRepository.save(User.builder().username(username).email(email).build()); 59 | return userRepository.findById(user.getId()).get(); 60 | } 61 | 62 | public Set getUserSetByUsernames(List usernames) { 63 | usernames = usernames.stream().distinct().collect(Collectors.toList()); 64 | return userRepository.findByUsernameIn(usernames); 65 | } 66 | 67 | public Set getUserSetByUsernamesWithoutCurrentUser(List usernames) { 68 | Set users = getUserSetByUsernames(usernames); 69 | users.remove(getCurrentUser()); 70 | return users; 71 | } 72 | 73 | public List getUsernamesFromUserList(List users) { 74 | List usernames = new ArrayList<>(); 75 | users.forEach(user -> usernames.add(user.getUsername())); 76 | return usernames; 77 | } 78 | 79 | public void setUserAvatar(MultipartFile multipartFile) throws IOException, SQLException { 80 | if (!multipartFile.isEmpty()) { 81 | User current = getCurrentUser(); 82 | Avatar currentAvatar = current.getAvatar(); 83 | if (currentAvatar != null) { 84 | avatarService.delete(currentAvatar); 85 | } 86 | Avatar avatar = new Avatar(); 87 | avatar.setContentType(multipartFile.getContentType()); 88 | avatar.setFile(new SerialBlob(multipartFile.getBytes())); 89 | avatar.setUser(current); 90 | current.setAvatar(avatar); 91 | userRepository.save(current); 92 | } 93 | } 94 | 95 | public Optional findByByUsername(String username) { 96 | return userRepository.findByUsername(username); 97 | } 98 | 99 | public List getUsernamesStartWith(String usernameStartsWith) { 100 | User current = getCurrentUser(); 101 | List usernames = new ArrayList<>(); 102 | if (usernameStartsWith.length() >= 4) { 103 | userRepository.findByUsernameStartsWith(usernameStartsWith).forEach(user -> usernames.add(user.getUsername())); 104 | } 105 | usernames.remove(current.getUsername()); 106 | return usernames; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/team/TeamServiceTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.team; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.project.Project; 5 | import com.projecty.projectyweb.team.role.TeamRole; 6 | import com.projecty.projectyweb.team.role.TeamRoleRepository; 7 | import com.projecty.projectyweb.team.role.TeamRoles; 8 | import com.projecty.projectyweb.team.role.dto.TeamRoleData; 9 | import com.projecty.projectyweb.user.User; 10 | import com.projecty.projectyweb.user.UserRepository; 11 | import com.projecty.projectyweb.user.UserService; 12 | import org.junit.Before; 13 | import org.junit.Test; 14 | import org.junit.runner.RunWith; 15 | import org.mockito.Mockito; 16 | import org.springframework.beans.factory.annotation.Autowired; 17 | import org.springframework.boot.test.context.SpringBootTest; 18 | import org.springframework.boot.test.mock.mockito.MockBean; 19 | import org.springframework.security.test.context.support.WithMockUser; 20 | import org.springframework.test.context.ActiveProfiles; 21 | import org.springframework.test.context.junit4.SpringRunner; 22 | 23 | import javax.transaction.Transactional; 24 | import java.util.*; 25 | 26 | import static org.hamcrest.MatcherAssert.assertThat; 27 | import static org.hamcrest.core.Is.is; 28 | import static org.hamcrest.core.IsNull.notNullValue; 29 | 30 | @ActiveProfiles("test") 31 | @RunWith(SpringRunner.class) 32 | @SpringBootTest(classes = ProjectyWebApplication.class) 33 | public class TeamServiceTests { 34 | @Autowired 35 | private TeamService teamService; 36 | 37 | @Autowired 38 | private TeamRoleRepository teamRoleRepository; 39 | 40 | @Autowired 41 | private TeamRepository teamRepository; 42 | 43 | @Autowired 44 | private UserRepository userRepository; 45 | 46 | @MockBean 47 | private UserService userService; 48 | 49 | private User user; 50 | private Team team; 51 | 52 | @Before 53 | public void init() { 54 | user = new User(); 55 | user.setUsername("user"); 56 | user = userRepository.save(user); 57 | 58 | team = teamRepository.save(new Team()); 59 | 60 | TeamRole teamRole = new TeamRole(); 61 | teamRole.setUser(user); 62 | teamRole.setTeam(team); 63 | teamRoleRepository.save(teamRole); 64 | 65 | user.setTeamRoles(Collections.singletonList(teamRole)); 66 | Mockito.when(userService.getCurrentUser()) 67 | .thenReturn(user); 68 | } 69 | 70 | @Test 71 | public void whenChangeName_shouldReturnTeamWithNewName() { 72 | Team team = new Team(); 73 | team.setName("Old name"); 74 | Map fields = new HashMap<>(); 75 | Team patched = new Team(); 76 | patched.setName("test"); 77 | teamService.editTeam(team, patched); 78 | Optional team1 = teamRepository.findById(team.getId()); 79 | if (team1.isPresent()) { 80 | assertThat(team1.get().getName(), is("test")); 81 | } else { 82 | assert false; 83 | } 84 | } 85 | 86 | @Test 87 | @Transactional 88 | public void whenCreateNewTeamWithoutOtherRoles_shouldReturnTeamWithOnlyOneRole() { 89 | Team team1 = new Team(); 90 | team1.setName("My team name"); 91 | team1 = teamService.createTeamAndSave(team1, null); 92 | assertThat(team1, is(notNullValue())); 93 | assertThat(teamRoleRepository.findByTeamAndAndUser(team1, user), is(notNullValue())); 94 | } 95 | 96 | @Test 97 | public void whenAddProject_shouldReturnTeamWithProject() { 98 | Optional team1 = teamRepository.findById(1L); 99 | Project project = new Project(); 100 | project.setName("Sample project"); 101 | team1.ifPresent(team -> teamService.createProjectForTeam(team, project)); 102 | team1 = teamRepository.findById(1L); 103 | team1.ifPresent(team -> assertThat(team.getProjects(), is(notNullValue()))); 104 | } 105 | 106 | @Test 107 | @Transactional 108 | public void whenGetTeams_shouldReturnListOfTeamRoleData() { 109 | List teamRoles = teamService.getTeams(); 110 | assertThat(teamRoles.size(), is(1)); 111 | assertThat(teamRoles.get(0).getTeam(), is(teamRepository.findById(team.getId()).get())); 112 | } 113 | 114 | private static final String USERNAME_1 = "teamServiceUser1"; 115 | 116 | @Test 117 | @WithMockUser(USERNAME_1) 118 | public void whenGetTeamRoleForCurrentUserByTeamId_shouldReturnTeamRoleData() { 119 | userRepository.save(User.builder().username(USERNAME_1).build()); 120 | Team team = teamService.createTeamAndSave(new Team(), null); 121 | TeamRoleData teamRoleData = teamService.getTeamRoleForCurrentUserByTeamId(team.getId()); 122 | assertThat(teamRoleData.getTeam().getId(), is(team.getId())); 123 | assertThat(teamRoleData.getName(), is(TeamRoles.MANAGER)); 124 | } 125 | 126 | } 127 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/role/ProjectRoleService.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project.role; 2 | 3 | import com.projecty.projectyweb.project.Project; 4 | import com.projecty.projectyweb.project.ProjectRepository; 5 | import com.projecty.projectyweb.user.User; 6 | import com.projecty.projectyweb.user.UserService; 7 | import org.springframework.stereotype.Service; 8 | 9 | import java.util.*; 10 | 11 | @Service 12 | public class ProjectRoleService { 13 | private final ProjectRoleRepository projectRoleRepository; 14 | private final UserService userService; 15 | private final ProjectRepository projectRepository; 16 | 17 | public ProjectRoleService(ProjectRoleRepository projectRoleRepository, UserService userService, ProjectRepository projectRepository) { 18 | this.projectRoleRepository = projectRoleRepository; 19 | this.userService = userService; 20 | this.projectRepository = projectRepository; 21 | } 22 | 23 | public void save(ProjectRole projectRole) { 24 | projectRoleRepository.save(projectRole); 25 | } 26 | 27 | private void removeExistingUsersInProjectFromSet(Set users, Project project) { 28 | if (project.getId() != null) { 29 | Set existingUsers = getProjectRoleUsers(project); 30 | users.removeAll(existingUsers); 31 | } 32 | } 33 | 34 | public List addRolesToProjectByUsernames(Project project, List usernames) { 35 | List projectRoles = new ArrayList<>(); 36 | if (usernames != null) { 37 | Set users = userService.getUserSetByUsernamesWithoutCurrentUser(usernames); 38 | removeExistingUsersInProjectFromSet(users, project); 39 | users.forEach(user -> { 40 | if (user.getSettings().getCanBeAddedToProject()) { 41 | projectRoles.add(new ProjectRole(ProjectRoles.USER, user, project)); 42 | } 43 | }); 44 | } 45 | if (project.getProjectRoles() == null) { 46 | project.setProjectRoles(projectRoles); 47 | } else if (projectRoles.size() > 0) { 48 | project.getProjectRoles().addAll(projectRoles); 49 | } 50 | return projectRoles; 51 | } 52 | 53 | public List saveProjectRoles(List projectRoles) { 54 | List savedProjectRoles = new ArrayList<>(); 55 | projectRoles.forEach(projectRole -> savedProjectRoles.add(projectRoleRepository.save(projectRole))); 56 | return savedProjectRoles; 57 | } 58 | 59 | public Set getProjectRoleUsers(Project project) { 60 | List projectRoles = projectRoleRepository.findByProjectOrderByIdAsc(project); 61 | Set users = new HashSet<>(); 62 | projectRoles.forEach(projectRole -> users.add(projectRole.getUser())); 63 | return users; 64 | } 65 | 66 | public void addCurrentUserToProjectAsAdmin(Project project) { 67 | User current = userService.getCurrentUser(); 68 | ProjectRole projectRole = new ProjectRole(ProjectRoles.ADMIN, current, project); 69 | if (project.getProjectRoles() == null) { 70 | List projectRoles = new ArrayList<>(); 71 | projectRoles.add(projectRole); 72 | project.setProjectRoles(projectRoles); 73 | } else { 74 | project.getProjectRoles().add(projectRole); 75 | } 76 | } 77 | 78 | public void deleteRoleFromProject(ProjectRole role) { 79 | Project project = role.getProject(); 80 | List projectRoles = project.getProjectRoles(); 81 | projectRoles.remove(role); 82 | project.setProjectRoles(projectRoles); 83 | projectRepository.save(project); 84 | } 85 | 86 | public void leaveProject(Project project) throws NoAdminsInProjectException { 87 | User user = userService.getCurrentUser(); 88 | Optional optionalProjectRole = projectRoleRepository.findRoleByUserAndProject(user, project); 89 | if (optionalProjectRole.isPresent()) { 90 | ProjectRole projectRole = optionalProjectRole.get(); 91 | int admins = projectRoleRepository.countByProjectAndName(project, ProjectRoles.ADMIN); 92 | if ((projectRole.getName().equals(ProjectRoles.ADMIN) && admins - 1 > 0) || project.getName().equals(ProjectRoles.USER)) { 93 | project.getProjectRoles().remove(optionalProjectRole.get()); 94 | projectRepository.save(project); 95 | } else { 96 | throw new NoAdminsInProjectException(); 97 | } 98 | } 99 | } 100 | 101 | public ProjectRole patchProjectRole(ProjectRole projectRole, ProjectRole patchedData) { 102 | if (!projectRole.getName().equals(patchedData.getName())) { 103 | projectRole.setName(patchedData.getName()); 104 | return projectRoleRepository.save(projectRole); 105 | } 106 | return projectRole; 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/message/MessageValidatorTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.message; 2 | 3 | 4 | import com.projecty.projectyweb.user.User; 5 | import com.projecty.projectyweb.user.UserService; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mockito; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.context.TestConfiguration; 12 | import org.springframework.boot.test.mock.mockito.MockBean; 13 | import org.springframework.context.annotation.Bean; 14 | import org.springframework.security.test.context.support.WithMockUser; 15 | import org.springframework.test.context.junit4.SpringRunner; 16 | import org.springframework.validation.BeanPropertyBindingResult; 17 | import org.springframework.validation.Errors; 18 | 19 | import java.util.Optional; 20 | 21 | import static org.hamcrest.Matchers.greaterThanOrEqualTo; 22 | import static org.junit.Assert.assertFalse; 23 | import static org.junit.Assert.assertThat; 24 | 25 | @RunWith(SpringRunner.class) 26 | public class MessageValidatorTests { 27 | 28 | @Autowired 29 | private MessageValidator messageValidator; 30 | 31 | @MockBean 32 | private UserService userService; 33 | 34 | @Before 35 | public void init() { 36 | User user = new User(); 37 | user.setId(1L); 38 | user.setEmail("test@test.com"); 39 | user.setUsername("testUser"); 40 | 41 | User user1 = new User(); 42 | user.setId(2L); 43 | user1.setEmail("test1@test.com"); 44 | user1.setUsername("testUser1"); 45 | 46 | User currentUser = new User(); 47 | currentUser.setId(3L); 48 | currentUser.setEmail("currentUser@test.com"); 49 | currentUser.setUsername("currentUser"); 50 | Mockito.when(userService.getCurrentUser()).thenReturn(currentUser); 51 | Mockito.when(userService.findByByUsername(user.getUsername())).thenReturn(Optional.of(user)); 52 | Mockito.when(userService.findByByUsername(user1.getUsername())).thenReturn(Optional.of(user1)); 53 | } 54 | 55 | @Test 56 | @WithMockUser("currentUser") 57 | public void messageValidationOk() { 58 | Message message = new Message(); 59 | message.setTitle("Test title"); 60 | message.setText("Test body"); 61 | message.setRecipientUsername("testUser"); 62 | Errors errors = new BeanPropertyBindingResult(message, "message"); 63 | 64 | messageValidator.validate(message, errors); 65 | assertFalse(errors.hasErrors()); 66 | } 67 | 68 | @Test 69 | @WithMockUser("currentUser") 70 | public void messageValidationWithEmptyOrNullText() { 71 | Message message = new Message(); 72 | message.setTitle("Test title"); 73 | message.setRecipientUsername("testUser"); 74 | Errors errors = new BeanPropertyBindingResult(message, "message"); 75 | messageValidator.validate(message, errors); 76 | assertThat(errors.getFieldErrorCount("text"), greaterThanOrEqualTo(1)); 77 | 78 | Message message1 = new Message(); 79 | message.setText(" "); 80 | message.setTitle("Test title"); 81 | message.setRecipientUsername("testUser"); 82 | errors = new BeanPropertyBindingResult(message1, "message"); 83 | messageValidator.validate(message1, errors); 84 | assertThat(errors.getFieldErrorCount("text"), greaterThanOrEqualTo(1)); 85 | } 86 | 87 | @Test 88 | @WithMockUser("currentUser") 89 | public void messageValidationWithEmptyOrNullTitle() { 90 | Message message = new Message(); 91 | message.setText("Test text"); 92 | message.setRecipientUsername("testUser"); 93 | Errors errors = new BeanPropertyBindingResult(message, "message"); 94 | messageValidator.validate(message, errors); 95 | assertThat(errors.getFieldErrorCount("title"), greaterThanOrEqualTo(1)); 96 | 97 | Message message1 = new Message(); 98 | message.setText("Test text"); 99 | message.setTitle(" "); 100 | message.setRecipientUsername("testUser"); 101 | errors = new BeanPropertyBindingResult(message1, "message"); 102 | messageValidator.validate(message1, errors); 103 | assertThat(errors.getFieldErrorCount("title"), greaterThanOrEqualTo(1)); 104 | } 105 | 106 | @Test 107 | @WithMockUser("currentUser") 108 | public void messageValidationForSendOwnSelf() { 109 | Message message = new Message(); 110 | message.setText("Test text"); 111 | message.setTitle("Test title"); 112 | message.setRecipientUsername("currentUser"); 113 | Errors errors = new BeanPropertyBindingResult(message, "message"); 114 | messageValidator.validate(message, errors); 115 | assertThat(errors.getFieldErrorCount("recipientUsername"), greaterThanOrEqualTo(1)); 116 | } 117 | 118 | 119 | @TestConfiguration 120 | static class MessageValidatorTestConfiguration { 121 | @Bean 122 | public MessageValidator messageValidator() { 123 | return new MessageValidator(); 124 | } 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/com/projecty/projectyweb/chat/ChatControllerTests.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.chat; 2 | 3 | import com.projecty.projectyweb.ProjectyWebApplication; 4 | import com.projecty.projectyweb.user.User; 5 | import com.projecty.projectyweb.user.UserRepository; 6 | import org.junit.Before; 7 | import org.junit.Test; 8 | import org.junit.runner.RunWith; 9 | import org.mockito.Mockito; 10 | import org.springframework.beans.factory.annotation.Autowired; 11 | import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; 12 | import org.springframework.boot.test.context.SpringBootTest; 13 | import org.springframework.boot.test.mock.mockito.MockBean; 14 | import org.springframework.data.domain.Page; 15 | import org.springframework.data.domain.PageImpl; 16 | import org.springframework.data.domain.PageRequest; 17 | import org.springframework.data.domain.Pageable; 18 | import org.springframework.security.test.context.support.WithMockUser; 19 | import org.springframework.test.context.ActiveProfiles; 20 | import org.springframework.test.context.junit4.SpringRunner; 21 | import org.springframework.test.web.servlet.MockMvc; 22 | 23 | import java.util.ArrayList; 24 | import java.util.Date; 25 | import java.util.List; 26 | import java.util.Optional; 27 | 28 | import static org.mockito.ArgumentMatchers.any; 29 | import static org.mockito.ArgumentMatchers.anySet; 30 | import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; 31 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; 32 | import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; 33 | 34 | @ActiveProfiles("test") 35 | @RunWith(SpringRunner.class) 36 | @SpringBootTest(classes = ProjectyWebApplication.class) 37 | @AutoConfigureMockMvc 38 | public class ChatControllerTests { 39 | @MockBean 40 | UserRepository userRepository; 41 | 42 | @MockBean 43 | ChatMessageRepository chatMessageRepository; 44 | 45 | @Autowired 46 | private MockMvc mockMvc; 47 | 48 | @Before 49 | public void init() { 50 | User admin = new User(); 51 | admin.setId(1L); 52 | admin.setUsername("admin"); 53 | User user = new User(); 54 | user.setUsername("user"); 55 | user.setId(2L); 56 | Mockito.when(userRepository.findByUsername("user")) 57 | .thenReturn(java.util.Optional.of(user)); 58 | Mockito.when(userRepository.findByUsername("admin")) 59 | .thenReturn(java.util.Optional.of(admin)); 60 | 61 | List list = new ArrayList<>(); 62 | for (int i = 0; i < 50; i++) { 63 | ChatMessage chatMessage = new ChatMessage(admin, user, "text" + i, new Date()); 64 | chatMessage.setId((long) i); 65 | list.add(chatMessage); 66 | Mockito.when(chatMessageRepository.findById((long) i)) 67 | .thenReturn(Optional.of(chatMessage)); 68 | } 69 | 70 | Pageable pageable = PageRequest.of(0, 10); 71 | Page page = new PageImpl<>(list, pageable, list.size()); 72 | Mockito.when(chatMessageRepository 73 | .findByRecipientAndSenderOrderById(any(User.class), any(User.class), any(Pageable.class))) 74 | .thenReturn(page); 75 | 76 | List lastChatMessages = new ArrayList<>(); 77 | ChatMessage lastMessage = new ChatMessage(admin, user, "text", new Date()); 78 | lastMessage.setId(100L); 79 | lastChatMessages.add(lastMessage); 80 | Mockito.when(chatMessageRepository.findByIdInIds(anySet())) 81 | .thenReturn(lastChatMessages); 82 | 83 | List userIdList = new ArrayList<>(); 84 | userIdList.add(new UserIdChatMessageCountDTO(1L, 50L)); 85 | Mockito.when(chatMessageRepository.countMessagesBySenderWhereSeenDateIsNullGroupBySender(user)) 86 | .thenReturn(userIdList); 87 | } 88 | 89 | @Test 90 | @WithMockUser 91 | public void onGetChatMessages_shouldReturnChatMessages() throws Exception { 92 | mockMvc.perform(get("/chat/admin")) 93 | .andExpect(status().isOk()) 94 | .andExpect(jsonPath("$.content").isArray()) 95 | .andExpect(jsonPath("$.totalPages").value(5)); 96 | } 97 | 98 | @Test 99 | @WithMockUser 100 | public void onGetChatMessagesToNotExistingUSer_shouldReturnBadRequest() throws Exception { 101 | mockMvc.perform(get("/chat/not-existing-username")) 102 | .andExpect(status().isBadRequest()); 103 | } 104 | 105 | @Test 106 | @WithMockUser 107 | public void onGetChatHistory_shouldReturnChatHistory() throws Exception { 108 | mockMvc.perform(get("/chat/")) 109 | .andExpect(status().isOk()) 110 | .andExpect(jsonPath("$[0].unreadMessageCount").value(50)) 111 | .andExpect(jsonPath("$[0].lastMessage.id").value(100L)); 112 | } 113 | 114 | @Test 115 | @WithMockUser 116 | public void onGetUnreadChatMessageCount_shouldReturnUnreadChatMessageCount() throws Exception { 117 | mockMvc.perform(get("/chat/unreadChatMessageCount")) 118 | .andExpect(status().isOk()); 119 | } 120 | 121 | } 122 | -------------------------------------------------------------------------------- /src/main/java/com/projecty/projectyweb/project/ProjectController.java: -------------------------------------------------------------------------------- 1 | package com.projecty.projectyweb.project; 2 | 3 | import com.projecty.projectyweb.configurations.AnyPermission; 4 | import com.projecty.projectyweb.configurations.EditPermission; 5 | import com.projecty.projectyweb.project.dto.ProjectData; 6 | import com.projecty.projectyweb.project.dto.ProjectsData; 7 | import com.projecty.projectyweb.project.role.ProjectRole; 8 | import com.projecty.projectyweb.project.role.ProjectRoleService; 9 | import com.projecty.projectyweb.project.role.dto.ProjectRoleData; 10 | import com.projecty.projectyweb.user.UserService; 11 | import org.springframework.http.HttpStatus; 12 | import org.springframework.validation.BindException; 13 | import org.springframework.validation.BindingResult; 14 | import org.springframework.web.bind.annotation.*; 15 | import org.springframework.web.server.ResponseStatusException; 16 | 17 | import javax.validation.Valid; 18 | import java.util.List; 19 | import java.util.Optional; 20 | 21 | @SuppressWarnings("OptionalGetWithoutIsPresent") 22 | @CrossOrigin() 23 | @RestController 24 | @RequestMapping("projects") 25 | public class ProjectController { 26 | private final ProjectService projectService; 27 | 28 | private final ProjectRepository projectRepository; 29 | 30 | private final UserService userService; 31 | 32 | private final ProjectValidator projectValidator; 33 | 34 | private final ProjectRoleService projectRoleService; 35 | 36 | public ProjectController(ProjectService projectService, ProjectRepository projectRepository, UserService userService, ProjectValidator projectValidator, ProjectRoleService projectRoleService) { 37 | this.projectService = projectService; 38 | this.projectRepository = projectRepository; 39 | this.userService = userService; 40 | this.projectValidator = projectValidator; 41 | this.projectRoleService = projectRoleService; 42 | } 43 | 44 | @GetMapping("") 45 | public ProjectsData myProjects() { 46 | return projectService.getProjectsForCurrentUser(); 47 | } 48 | 49 | @PostMapping("") 50 | public Project addProjectPost( 51 | @Valid @RequestBody Project project, 52 | BindingResult bindingResult 53 | ) throws BindException { 54 | projectValidator.validate(project, bindingResult); 55 | if (bindingResult.hasErrors()) { 56 | throw new BindException(bindingResult); 57 | } 58 | return projectService.createNewProjectAndSave(project, project.getUsernames()); 59 | } 60 | 61 | @DeleteMapping("/{projectId}") 62 | @EditPermission 63 | public void deleteProject(@PathVariable Long projectId) { 64 | Optional project = projectRepository.findById(projectId); 65 | projectRepository.delete(project.get()); 66 | } 67 | 68 | @PostMapping("/{projectId}/roles") 69 | @EditPermission 70 | public List addUsersToExistingProjectPost( 71 | @PathVariable Long projectId, 72 | @RequestBody List usernames) { 73 | Project project = projectRepository.findById(projectId).get(); 74 | return projectService.addProjectRolesByUsernames(project, usernames); 75 | } 76 | 77 | @GetMapping(value = "/{projectId}", params = "roles") 78 | @EditPermission 79 | public ProjectData getProjectWithProjectRoles( 80 | @PathVariable Long projectId 81 | ) { 82 | Optional optionalProject = projectRepository.findById(projectId); 83 | return projectService.getProjectData(optionalProject.get()); 84 | } 85 | 86 | @PostMapping("/{projectId}/leave") 87 | @AnyPermission 88 | public void leaveProject(@PathVariable Long projectId) { 89 | Optional optionalProject = projectRepository.findById(projectId); 90 | projectRoleService.leaveProject(optionalProject.get()); 91 | } 92 | 93 | @PatchMapping("/{projectId}") 94 | public Project patchProject( 95 | @PathVariable("projectId") Long projectId, 96 | @RequestBody Project patchedProject 97 | ) { 98 | Optional optionalProject = projectRepository.findById(projectId); 99 | if (optionalProject.isPresent() && projectService.hasCurrentUserPermissionToEdit(optionalProject.get())) { 100 | return projectService.patchProject(optionalProject.get(), patchedProject); 101 | } else { 102 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 103 | } 104 | } 105 | 106 | @GetMapping("/{projectId}") 107 | public Project getProjectData( 108 | @PathVariable Long projectId 109 | ) { 110 | Optional optionalProject = projectRepository.findById(projectId); 111 | if (optionalProject.isPresent() && projectService.hasCurrentUserPermissionToEdit(optionalProject.get())) { 112 | return optionalProject.get(); 113 | } else { 114 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 115 | } 116 | } 117 | 118 | @GetMapping("/{projectId}/projectRole") 119 | @AnyPermission 120 | public ProjectRoleData getProjectRoleForCurrentUserByProjectId(@PathVariable Long projectId) { 121 | ProjectRoleData projectRoleData = projectService.getProjectRoleForCurrentUserByProjectId(projectId); 122 | if (projectRoleData != null) { 123 | return projectRoleData; 124 | } 125 | throw new ResponseStatusException(HttpStatus.NOT_FOUND); 126 | } 127 | } 128 | --------------------------------------------------------------------------------