├── auth-service ├── src │ ├── test │ │ ├── resources │ │ │ ├── application.yml │ │ │ ├── bootstrap.yml │ │ │ └── logback-test.xml │ │ └── java │ │ │ └── pl │ │ │ └── kubaretip │ │ │ └── authservice │ │ │ ├── utils │ │ │ ├── JacksonIgnoreWriteOnlyAccess.java │ │ │ └── SpringSecurityWebTestConfig.java │ │ │ └── mapper │ │ │ └── UserMapperImplTest.java │ └── main │ │ ├── resources │ │ ├── application.yml │ │ ├── db │ │ │ └── migration │ │ │ │ ├── V1.0.4__insert_authority_records.sql │ │ │ │ ├── V1.0.1__create_authority_table.sql │ │ │ │ ├── V1.0.3__create_user_authority_table.sql │ │ │ │ └── V1.0.2__create_user_table.sql │ │ ├── bootstrap.yml │ │ └── bootstrap-docker.yml │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── authservice │ │ ├── constants │ │ ├── AuthorityConstants.java │ │ └── ApplicationConstants.java │ │ ├── security │ │ ├── model │ │ │ ├── AuthRequestModel.java │ │ │ └── TokenResponse.java │ │ ├── AuthenticationSuccessHandler.java │ │ ├── JWTBuilder.java │ │ ├── AuthenticationFailureHandler.java │ │ ├── JWTAuthenticationFilter.java │ │ └── UserDetailsServiceImpl.java │ │ ├── exception │ │ ├── controllerAdvice │ │ │ ├── CustomExceptionHandler.java │ │ │ └── ValidationExceptionHandler.java │ │ └── InvalidAuthenticationRequestException.java │ │ ├── repository │ │ ├── AuthorityRepository.java │ │ └── UserRepository.java │ │ ├── AuthServiceApplication.java │ │ ├── service │ │ └── UserService.java │ │ ├── config │ │ ├── RabbitMQConfig.java │ │ └── SecurityConfig.java │ │ ├── domain │ │ ├── Authority.java │ │ └── User.java │ │ ├── web │ │ ├── model │ │ │ └── ChangePassRequest.java │ │ └── rest │ │ │ ├── AuthorizationController.java │ │ │ └── UserController.java │ │ ├── mapper │ │ └── UserMapper.java │ │ └── messaging │ │ └── sender │ │ └── UserSender.java ├── Dockerfile └── pom.xml ├── config-server ├── src │ └── main │ │ ├── resources │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── bootstrap-docker.yml │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── configserver │ │ └── ConfigServerApplication.java ├── Dockerfile └── pom.xml ├── chat-service ├── src │ ├── main │ │ ├── resources │ │ │ ├── application.yml │ │ │ ├── bootstrap.yml │ │ │ ├── db │ │ │ │ └── migration │ │ │ │ │ ├── V1.0.1__create_chat_profile_table.sql │ │ │ │ │ ├── V1.0.3__create_friend_chat_table.sql │ │ │ │ │ └── V1.0.2__create_friend_request_table.sql │ │ │ └── bootstrap-docker.yml │ │ └── java │ │ │ └── pl │ │ │ └── kubaretip │ │ │ └── chatservice │ │ │ ├── exception │ │ │ └── handler │ │ │ │ └── CustomExceptionsHandler.java │ │ │ ├── dto │ │ │ ├── FriendChatDTO.java │ │ │ ├── ChatProfileDTO.java │ │ │ ├── FriendRequestDTO.java │ │ │ └── mapper │ │ │ │ ├── ChatProfileMapper.java │ │ │ │ ├── FriendChatMapper.java │ │ │ │ └── FriendRequestMapper.java │ │ │ ├── service │ │ │ ├── ChatProfileService.java │ │ │ ├── FriendChatService.java │ │ │ ├── FriendRequestService.java │ │ │ └── impl │ │ │ │ ├── ChatProfileServiceImpl.java │ │ │ │ ├── FriendChatServiceImpl.java │ │ │ │ └── FriendRequestServiceImpl.java │ │ │ ├── repository │ │ │ ├── ChatProfileRepository.java │ │ │ ├── FriendChatRepository.java │ │ │ └── FriendRequestRepository.java │ │ │ ├── util │ │ │ └── DateUtils.java │ │ │ ├── domain │ │ │ ├── ChatProfile.java │ │ │ ├── FriendChat.java │ │ │ └── FriendRequest.java │ │ │ ├── ChatServiceApplication.java │ │ │ ├── messaging │ │ │ ├── listener │ │ │ │ └── RabbitNewUserListener.java │ │ │ └── sender │ │ │ │ └── DeleteMessagesSender.java │ │ │ ├── config │ │ │ ├── RabbitMQConfig.java │ │ │ └── SecurityConfig.java │ │ │ └── web │ │ │ └── rest │ │ │ ├── ChatProfileController.java │ │ │ ├── FriendChatController.java │ │ │ └── FriendRequestController.java │ └── test │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── chatservice │ │ └── service │ │ └── impl │ │ ├── FriendChatServiceImplTest.java │ │ └── ChatProfileServiceImplTest.java ├── Dockerfile └── pom.xml ├── mail-service ├── src │ └── main │ │ ├── resources │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── bootstrap-docker.yml │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── mailservice │ │ ├── service │ │ ├── SendMailService.java │ │ └── impl │ │ │ └── MailServiceImpl.java │ │ ├── MailServiceApplication.java │ │ ├── messaging │ │ └── listener │ │ │ └── RabbitUserActivationListener.java │ │ └── config │ │ ├── RabbitMQConfig.java │ │ └── AsyncConfig.java ├── Dockerfile └── pom.xml ├── eureka-server ├── src │ └── main │ │ ├── resources │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── bootstrap-docker.yml │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── eurekaserver │ │ └── EurekaServerApplication.java ├── Dockerfile └── pom.xml ├── chat-messages-service ├── src │ └── main │ │ ├── resources │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── bootstrap-docker.yml │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── chatmessagesservice │ │ ├── constant │ │ ├── MessageStatus.java │ │ └── DateConstants.java │ │ ├── ChatMessagesServiceApplication.java │ │ ├── security │ │ ├── SecurityUtils.java │ │ ├── JWTAuthenticationManager.java │ │ └── JWTAuthenticationConverter.java │ │ ├── document │ │ └── ChatMessage.java │ │ ├── messaging │ │ └── listener │ │ │ ├── RabbitMessageDeletingListener.java │ │ │ └── RabbitMessageStoringListener.java │ │ ├── service │ │ ├── ChatMessageService.java │ │ └── impl │ │ │ └── ChatMessageServiceImpl.java │ │ ├── repository │ │ └── ChatMessageRepository.java │ │ ├── web │ │ └── rest │ │ │ └── ChatMessageController.java │ │ └── config │ │ ├── RabbitMQConfig.java │ │ └── SecurityConfig.java ├── Dockerfile └── pom.xml ├── cloud-gateway ├── src │ └── main │ │ ├── resources │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── bootstrap-docker.yml │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── cloudgateway │ │ ├── CloudGatewayApplication.java │ │ └── config │ │ ├── OpenAPIConfig.java │ │ ├── CorsConfig.java │ │ └── CloudConfig.java ├── Dockerfile └── pom.xml ├── screenshot └── diagram.png ├── messages-websocket-service ├── src │ └── main │ │ ├── resources │ │ ├── application.yml │ │ ├── bootstrap.yml │ │ └── bootstrap-docker.yml │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── messageswebsocketservice │ │ ├── log │ │ ├── WebSocketLogs.java │ │ └── WebSocketConnectionListener.java │ │ ├── MessagesWebSocketServiceApplication.java │ │ ├── config │ │ ├── RabbitMQConfig.java │ │ ├── WebSocketSecurityConfig.java │ │ ├── SecurityConfig.java │ │ └── WebSocketConfig.java │ │ ├── messaging │ │ └── sender │ │ │ └── StoringMessagesSender.java │ │ ├── security │ │ ├── WebSocketAuthService.java │ │ └── AuthChannelInterceptor.java │ │ └── web │ │ └── websocket │ │ └── MessagesController.java ├── Dockerfile └── pom.xml ├── .circleci └── config.yml ├── exception-utils ├── src │ └── main │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── exceptionutils │ │ ├── NotFoundException.java │ │ ├── InvalidDataException.java │ │ ├── AlreadyExistsException.java │ │ ├── error │ │ ├── Violation.java │ │ └── Error.java │ │ └── CommonExceptionHandler.java └── pom.xml ├── auth-utils ├── src │ └── main │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── authutils │ │ ├── jwt │ │ ├── JWTConstants.java │ │ ├── JWTConfig.java │ │ ├── JWTFilter.java │ │ └── JWTUtils.java │ │ ├── security │ │ ├── SecurityUserDetails.java │ │ └── SecurityUserDetailsImpl.java │ │ └── SecurityUtils.java └── pom.xml ├── docker ├── init-chat-messages-service-mongo.js ├── .env.dev ├── rabbitmq-isolated.conf ├── .env.docker ├── docker-compose-dev.yml └── docker-compose-docker.yml ├── .gitignore ├── dto-models ├── src │ └── main │ │ └── java │ │ └── pl │ │ └── kubaretip │ │ └── dtomodels │ │ ├── ChatMessageDTO.java │ │ └── UserDTO.java └── pom.xml └── README.md /auth-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /config-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8888 3 | -------------------------------------------------------------------------------- /auth-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: auth -------------------------------------------------------------------------------- /chat-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: chat -------------------------------------------------------------------------------- /mail-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: mail -------------------------------------------------------------------------------- /eureka-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: eureka -------------------------------------------------------------------------------- /auth-service/src/test/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | enabled: false -------------------------------------------------------------------------------- /chat-messages-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: chat-messages -------------------------------------------------------------------------------- /cloud-gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: cloud-gateway 4 | 5 | -------------------------------------------------------------------------------- /screenshot/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kubAretip/spring-micro-websocket-chat/HEAD/screenshot/diagram.png -------------------------------------------------------------------------------- /cloud-gateway/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | uri: http://localhost:8888 -------------------------------------------------------------------------------- /eureka-server/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | uri: http://localhost:8888 -------------------------------------------------------------------------------- /messages-websocket-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | application: 3 | name: messages-websocket -------------------------------------------------------------------------------- /auth-service/src/main/resources/db/migration/V1.0.4__insert_authority_records.sql: -------------------------------------------------------------------------------- 1 | insert into authority (name) 2 | VALUES ('ROLE_USER'); -------------------------------------------------------------------------------- /auth-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, auth 5 | uri: http://localhost:8888 -------------------------------------------------------------------------------- /chat-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, chat 5 | uri: http://localhost:8888 -------------------------------------------------------------------------------- /mail-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, mail 5 | uri: http://localhost:8888 6 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, chat-messages 5 | uri: http://localhost:8888 -------------------------------------------------------------------------------- /messages-websocket-service/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, messages-websocket 5 | uri: http://localhost:8888 -------------------------------------------------------------------------------- /auth-service/src/main/resources/db/migration/V1.0.1__create_authority_table.sql: -------------------------------------------------------------------------------- 1 | create table if not exists authority 2 | ( 3 | name varchar(50) not null unique primary key 4 | ); -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/constants/AuthorityConstants.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.constants; 2 | 3 | public enum AuthorityConstants { 4 | ROLE_USER 5 | } 6 | -------------------------------------------------------------------------------- /auth-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.2-jre-slim 2 | COPY target/auth-service-1.0-SNAPSHOT.jar . 3 | CMD /usr/bin/java -Dlogging.path=/log/ -Xmx400m -Xms400m -jar auth-service-1.0-SNAPSHOT.jar 4 | EXPOSE 8080 -------------------------------------------------------------------------------- /chat-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.2-jre-slim 2 | COPY target/chat-service-1.0-SNAPSHOT.jar . 3 | CMD /usr/bin/java -Dlogging.path=/log/ -Xmx400m -Xms400m -jar chat-service-1.0-SNAPSHOT.jar 4 | EXPOSE 8080 -------------------------------------------------------------------------------- /mail-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.2-jre-slim 2 | COPY target/mail-service-1.0-SNAPSHOT.jar . 3 | CMD /usr/bin/java -Dlogging.path=/log/ -Xmx400m -Xms400m -jar mail-service-1.0-SNAPSHOT.jar 4 | EXPOSE 8080 -------------------------------------------------------------------------------- /cloud-gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.2-jre-slim 2 | COPY target/cloud-gateway-1.0-SNAPSHOT.jar . 3 | CMD /usr/bin/java -Dlogging.path=/log/ -Xmx400m -Xms400m -jar cloud-gateway-1.0-SNAPSHOT.jar 4 | EXPOSE 8080 -------------------------------------------------------------------------------- /config-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.2-jre-slim 2 | COPY target/config-server-1.0-SNAPSHOT.jar . 3 | CMD /usr/bin/java -Dlogging.path=/log/ -Xmx400m -Xms400m -jar config-server-1.0-SNAPSHOT.jar 4 | EXPOSE 8888 -------------------------------------------------------------------------------- /eureka-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.2-jre-slim 2 | COPY target/eureka-server-1.0-SNAPSHOT.jar . 3 | CMD /usr/bin/java -Dlogging.path=/log/ -Xmx400m -Xms400m -jar eureka-server-1.0-SNAPSHOT.jar 4 | EXPOSE 8761 -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/constant/MessageStatus.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.constant; 2 | 3 | public enum MessageStatus { 4 | RECEIVED, DELIVERED 5 | } 6 | -------------------------------------------------------------------------------- /chat-messages-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.2-jre-slim 2 | COPY target/chat-messages-service-1.0-SNAPSHOT.jar . 3 | CMD /usr/bin/java -Dlogging.path=/log/ -Xmx400m -Xms400m -jar chat-messages-service-1.0-SNAPSHOT.jar 4 | EXPOSE 8080 -------------------------------------------------------------------------------- /messages-websocket-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM openjdk:11.0.2-jre-slim 2 | COPY target/messages-websocket-service-1.0-SNAPSHOT.jar . 3 | CMD /usr/bin/java -Dlogging.path=/log/ -Xmx400m -Xms400m -jar messages-websocket-service-1.0-SNAPSHOT.jar 4 | EXPOSE 8080 -------------------------------------------------------------------------------- /chat-service/src/main/resources/db/migration/V1.0.1__create_chat_profile_table.sql: -------------------------------------------------------------------------------- 1 | create table if not exists chat_profile 2 | ( 3 | user_id BINARY(16) not null unique primary key, 4 | friends_request_code varchar(64) not null unique 5 | ) -------------------------------------------------------------------------------- /auth-service/src/test/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /eureka-server/src/main/resources/bootstrap-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | uri: ${CLOUD_CONFIG_URI} 5 | fail-fast: true 6 | retry: 7 | max-attempts: 20 8 | initial-interval: 20000 9 | max-interval: 10000 -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2.1 2 | 3 | orbs: 4 | maven: circleci/maven@1.1 5 | 6 | workflows: 7 | maven_test: 8 | jobs: 9 | - maven/test: 10 | filters: 11 | branches: 12 | only: 13 | - master -------------------------------------------------------------------------------- /mail-service/src/main/java/pl/kubaretip/mailservice/service/SendMailService.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.mailservice.service; 2 | 3 | import pl.kubaretip.dtomodels.UserDTO; 4 | 5 | public interface SendMailService { 6 | 7 | void sendActivationEmail(UserDTO user); 8 | } 9 | -------------------------------------------------------------------------------- /exception-utils/src/main/java/pl/kubaretip/exceptionutils/NotFoundException.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.exceptionutils; 2 | 3 | public class NotFoundException extends RuntimeException { 4 | public NotFoundException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /exception-utils/src/main/java/pl/kubaretip/exceptionutils/InvalidDataException.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.exceptionutils; 2 | 3 | public class InvalidDataException extends RuntimeException { 4 | public InvalidDataException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /exception-utils/src/main/java/pl/kubaretip/exceptionutils/AlreadyExistsException.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.exceptionutils; 2 | 3 | public class AlreadyExistsException extends RuntimeException { 4 | public AlreadyExistsException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /auth-service/src/main/resources/bootstrap-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, auth 5 | uri: ${CLOUD_CONFIG_URI} 6 | fail-fast: true 7 | retry: 8 | max-attempts: 20 9 | initial-interval: 60000 10 | max-interval: 10000 -------------------------------------------------------------------------------- /auth-utils/src/main/java/pl/kubaretip/authutils/jwt/JWTConstants.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authutils.jwt; 2 | 3 | public final class JWTConstants { 4 | 5 | public static final String AUTHORITIES_KEY = "roles"; 6 | public static final String USERNAME_KEY = "preferred_username"; 7 | 8 | } 9 | -------------------------------------------------------------------------------- /chat-service/src/main/resources/bootstrap-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, chat 5 | uri: ${CLOUD_CONFIG_URI} 6 | fail-fast: true 7 | retry: 8 | max-attempts: 20 9 | initial-interval: 60000 10 | max-interval: 30000 -------------------------------------------------------------------------------- /mail-service/src/main/resources/bootstrap-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, mail 5 | uri: ${CLOUD_CONFIG_URI} 6 | fail-fast: true 7 | retry: 8 | max-attempts: 20 9 | initial-interval: 60000 10 | max-interval: 30000 -------------------------------------------------------------------------------- /auth-utils/src/main/java/pl/kubaretip/authutils/security/SecurityUserDetails.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authutils.security; 2 | 3 | import org.springframework.security.core.userdetails.UserDetails; 4 | 5 | public interface SecurityUserDetails extends UserDetails { 6 | 7 | String getId(); 8 | 9 | } 10 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/resources/bootstrap-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, chat-messages 5 | uri: ${CLOUD_CONFIG_URI} 6 | fail-fast: true 7 | retry: 8 | max-attempts: 20 9 | initial-interval: 60000 10 | max-interval: 30000 -------------------------------------------------------------------------------- /messages-websocket-service/src/main/resources/bootstrap-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | name: commons, messages-websocket 5 | uri: ${CLOUD_CONFIG_URI} 6 | fail-fast: true 7 | retry: 8 | max-attempts: 20 9 | initial-interval: 60000 10 | max-interval: 30000 -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/constant/DateConstants.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.constant; 2 | 3 | public final class DateConstants { 4 | 5 | public static final String UTC_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"; 6 | 7 | private DateConstants() { 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /docker/init-chat-messages-service-mongo.js: -------------------------------------------------------------------------------- 1 | db.createUser( 2 | { 3 | user : "chat-messages-service-user", 4 | pwd : "chat-messages-service-pass-123", 5 | roles : [ 6 | { 7 | role: "readWrite", 8 | db : "chat_messages_service_database" 9 | } 10 | ] 11 | } 12 | ); -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/security/model/AuthRequestModel.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.security.model; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | 6 | @Getter 7 | @NoArgsConstructor 8 | public class AuthRequestModel { 9 | 10 | private String username; 11 | private String password; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /exception-utils/src/main/java/pl/kubaretip/exceptionutils/error/Violation.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.exceptionutils.error; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | import java.util.List; 7 | 8 | @Getter 9 | @AllArgsConstructor 10 | public class Violation { 11 | private String field; 12 | private List message; 13 | } 14 | -------------------------------------------------------------------------------- /auth-service/src/main/resources/db/migration/V1.0.3__create_user_authority_table.sql: -------------------------------------------------------------------------------- 1 | create table if not exists user_authority 2 | ( 3 | user_id binary(16) not null, 4 | authority_name varchar(50) not null, 5 | primary key (user_id, authority_name), 6 | foreign key (user_id) references user (id), 7 | foreign key (authority_name) references authority (name) 8 | ); -------------------------------------------------------------------------------- /config-server/src/main/resources/bootstrap.yml: -------------------------------------------------------------------------------- 1 | encrypt: 2 | key: ${CONFIG_SERVER_SECRET} 3 | spring: 4 | cloud: 5 | config: 6 | server: 7 | git: 8 | uri: ${GIT_URI} 9 | search-paths: 10 | - 'files' 11 | - 'files/service/commons' 12 | - 'files/service/*-service' 13 | default-label: ${GIT_BRANCH} -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/exception/handler/CustomExceptionsHandler.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.exception.handler; 2 | 3 | import org.springframework.web.bind.annotation.RestControllerAdvice; 4 | import pl.kubaretip.exceptionutils.CommonExceptionHandler; 5 | 6 | @RestControllerAdvice 7 | public class CustomExceptionsHandler implements CommonExceptionHandler { 8 | } 9 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/exception/controllerAdvice/CustomExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.exception.controllerAdvice; 2 | 3 | import org.springframework.web.bind.annotation.RestControllerAdvice; 4 | import pl.kubaretip.exceptionutils.CommonExceptionHandler; 5 | 6 | @RestControllerAdvice 7 | public class CustomExceptionHandler implements CommonExceptionHandler { 8 | } 9 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/dto/FriendChatDTO.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | @Setter 8 | @Getter 9 | @NoArgsConstructor 10 | public class FriendChatDTO { 11 | 12 | private Long id; 13 | private Long chatWith; 14 | private ChatProfileDTO recipient; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/exception/InvalidAuthenticationRequestException.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.exception; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | 5 | public class InvalidAuthenticationRequestException extends AuthenticationException { 6 | public InvalidAuthenticationRequestException(String msg) { 7 | super(msg); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/constants/ApplicationConstants.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.constants; 2 | 3 | public final class ApplicationConstants { 4 | 5 | public final static int USER_PASSWORD_MIN_LENGTH = 6; 6 | public final static int USER_PASSWORD_MAX_LENGTH = 32; 7 | public final static String LOGIN_REGEXP = "^[a-zA-Z0-9]*$"; 8 | 9 | 10 | private ApplicationConstants() { 11 | } 12 | } -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/service/ChatProfileService.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.service; 2 | 3 | import pl.kubaretip.chatservice.domain.ChatProfile; 4 | 5 | public interface ChatProfileService { 6 | ChatProfile createChatProfile(String userId, String username); 7 | 8 | ChatProfile generateNewFriendsRequestCode(String userId, String username); 9 | 10 | ChatProfile getChatProfileById(String userId); 11 | } 12 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/repository/AuthorityRepository.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import pl.kubaretip.authservice.domain.Authority; 5 | 6 | import java.util.Optional; 7 | 8 | public interface AuthorityRepository extends JpaRepository { 9 | 10 | Optional findByNameIgnoreCase(String name); 11 | 12 | 13 | } 14 | -------------------------------------------------------------------------------- /cloud-gateway/src/main/resources/bootstrap-docker.yml: -------------------------------------------------------------------------------- 1 | spring: 2 | cloud: 3 | config: 4 | uri: ${CLOUD_CONFIG_URI} 5 | fail-fast: true 6 | retry: 7 | max-attempts: 20 8 | initial-interval: 15000 9 | max-interval: 10000 10 | management: 11 | endpoint: 12 | restart: 13 | enabled: true 14 | endpoints: 15 | web: 16 | exposure: 17 | include: 18 | - health 19 | - info 20 | - restart -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/security/model/TokenResponse.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.security.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | @Getter 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | public class TokenResponse { 12 | 13 | @JsonProperty("access_token") 14 | private String accessToken; 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/dto/ChatProfileDTO.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @JsonInclude(JsonInclude.Include.NON_NULL) 9 | @Getter 10 | @Setter 11 | @NoArgsConstructor 12 | public class ChatProfileDTO { 13 | 14 | private String userId; 15 | private String friendsRequestCode; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /auth-service/src/main/resources/db/migration/V1.0.2__create_user_table.sql: -------------------------------------------------------------------------------- 1 | create table if not exists user 2 | ( 3 | id binary(16) not null primary key, 4 | username varchar(50) not null unique, 5 | password_hash varchar(60) not null, 6 | first_name varchar(50) not null, 7 | last_name varchar(50) not null, 8 | activation_key varchar(124) unique, 9 | email varchar(100) not null unique, 10 | enabled boolean not null default 0 11 | ); -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/repository/ChatProfileRepository.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import pl.kubaretip.chatservice.domain.ChatProfile; 5 | 6 | import java.util.Optional; 7 | import java.util.UUID; 8 | 9 | public interface ChatProfileRepository extends JpaRepository { 10 | 11 | Optional findByFriendsRequestCode(String friendsRequestCode); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /docker/.env.dev: -------------------------------------------------------------------------------- 1 | # you can change this starting passwords but you must recrypt all in config repo 2 | AUTH_SERVICE_MYSQL_USER=auth-service-user 3 | AUTH_SERVICE_MYSQL_PASS=auth-service-pass-123 4 | AUTH_SERVICE_MYSQL_ROOT_PASS=auth-service-root-pass-123 5 | CHAT_SERVICE_MYSQL_USER=chat-service-user 6 | CHAT_SERVICE_MYSQL_PASS=chat-service-pass-123 7 | CHAT_SERVICE_MYSQL_ROOT_PASS=chat-service-root-pass-123 8 | CHAT_MESSAGES_SERVICE_ROOT_MONGO_USER=chat-messages-service-root 9 | CHAT_MESSAGES_SERVICE_ROOT_MONGO_PASS=chat-messages-service-root-pass-123 -------------------------------------------------------------------------------- /config-server/src/main/resources/bootstrap-docker.yml: -------------------------------------------------------------------------------- 1 | encrypt: 2 | key: ${CONFIG_SERVER_SECRET} 3 | 4 | spring: 5 | cloud: 6 | config: 7 | server: 8 | git: 9 | uri: ${GIT_URI} 10 | search-paths: 11 | - 'files' 12 | - 'files/service/commons' 13 | - 'files/service/*-service' 14 | default-label: ${GIT_BRANCH} 15 | ignore-local-ssh-settings: true 16 | host-key-algorithm: ssh-rsa 17 | host-key: ${HOST_KEY} 18 | private-key: ${PRIVATE_KEY} -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/AuthServiceApplication.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @EnableEurekaClient 8 | @SpringBootApplication 9 | public class AuthServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(AuthServiceApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/dto/FriendRequestDTO.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.dto; 2 | 3 | 4 | import com.fasterxml.jackson.annotation.JsonInclude; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | @JsonInclude(JsonInclude.Include.NON_NULL) 10 | @Getter 11 | @Setter 12 | @NoArgsConstructor 13 | public class FriendRequestDTO { 14 | 15 | private Long id; 16 | private String sentTime; 17 | private ChatProfileDTO sender; 18 | private ChatProfileDTO recipient; 19 | } 20 | -------------------------------------------------------------------------------- /eureka-server/src/main/java/pl/kubaretip/eurekaserver/EurekaServerApplication.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.eurekaserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.server.EnableEurekaServer; 6 | 7 | @EnableEurekaServer 8 | @SpringBootApplication 9 | public class EurekaServerApplication { 10 | public static void main(String[] args) { 11 | SpringApplication.run(EurekaServerApplication.class, args); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /mail-service/src/main/java/pl/kubaretip/mailservice/MailServiceApplication.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.mailservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @EnableEurekaClient 8 | @SpringBootApplication 9 | public class MailServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(MailServiceApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /chat-service/src/main/resources/db/migration/V1.0.3__create_friend_chat_table.sql: -------------------------------------------------------------------------------- 1 | create table if not exists friend_chat 2 | ( 3 | id bigint not null primary key auto_increment, 4 | chat_with_id bigint, 5 | sender_id binary(16) not null, 6 | recipient_id binary(16) not null, 7 | foreign key (chat_with_id) references friend_chat (id), 8 | foreign key (sender_id) references chat_profile (user_id), 9 | foreign key (recipient_id) references chat_profile (user_id), 10 | unique index (chat_with_id, sender_id, recipient_id) 11 | ); -------------------------------------------------------------------------------- /cloud-gateway/src/main/java/pl/kubaretip/cloudgateway/CloudGatewayApplication.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.cloudgateway; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @EnableEurekaClient 8 | @SpringBootApplication 9 | public class CloudGatewayApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(CloudGatewayApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /config-server/src/main/java/pl/kubaretip/configserver/ConfigServerApplication.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.configserver; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.config.server.EnableConfigServer; 6 | 7 | @EnableConfigServer 8 | @SpringBootApplication 9 | public class ConfigServerApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ConfigServerApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | HELP.md 2 | target/ 3 | !.mvn/wrapper/maven-wrapper.jar 4 | !**/src/main/**/target/ 5 | !**/src/test/**/target/ 6 | 7 | ### STS ### 8 | .apt_generated 9 | .classpath 10 | .factorypath 11 | .project 12 | .settings 13 | .springBeans 14 | .sts4-cache 15 | 16 | ### IntelliJ IDEA ### 17 | .idea 18 | *.iws 19 | *.iml 20 | *.ipr 21 | 22 | ### NetBeans ### 23 | /nbproject/private/ 24 | /nbbuild/ 25 | /dist/ 26 | /nbdist/ 27 | /.nb-gradle/ 28 | build/ 29 | !**/src/main/**/build/ 30 | !**/src/test/**/build/ 31 | 32 | ### VS Code ### 33 | .vscode/ 34 | 35 | docker/.env 36 | .env -------------------------------------------------------------------------------- /docker/rabbitmq-isolated.conf: -------------------------------------------------------------------------------- 1 | [ 2 | {rabbit, 3 | [ 4 | %% The default "guest" user is only permitted to access the server 5 | %% via a loopback interface (e.g. localhost). 6 | {loopback_users, [<<"rabbitmq-user">>]}, 7 | %% 8 | %% Uncomment the following line if you want to allow access to the 9 | %% guest user from anywhere on the network. 10 | {loopback_users, []}, 11 | {default_vhost, "/"}, 12 | {default_user, "rabbitmq-user"}, 13 | {default_pass, "rabbitmq-pass-123"}, 14 | {default_permissions, [".*", ".*", ".*"]} 15 | ]} 16 | ]. -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/service/FriendChatService.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.service; 2 | 3 | import pl.kubaretip.chatservice.domain.ChatProfile; 4 | import pl.kubaretip.chatservice.domain.FriendChat; 5 | 6 | import java.util.List; 7 | 8 | public interface FriendChatService { 9 | void createFriendChat(ChatProfile firstUserChatProfile, ChatProfile secondUserChatProfile); 10 | 11 | List getAllFriendsChatsBySender(String currentUser); 12 | 13 | void deleteFriendChat(long friendChatId, long friendChatWithId, String currentUserId); 14 | } 15 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/service/UserService.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.service; 2 | 3 | import pl.kubaretip.authservice.domain.User; 4 | 5 | public interface UserService { 6 | User createUser(String username, String password, String email, String firstName, String lastName); 7 | 8 | void activateUser(String activationKey); 9 | 10 | User findUserById(String userId); 11 | 12 | User modifyUser(String userId, String firstName, String lastName); 13 | 14 | void changeUserPassword(String userId, String currentPassword, String newPassword); 15 | } 16 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/ChatMessagesServiceApplication.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 6 | 7 | @EnableEurekaClient 8 | @SpringBootApplication 9 | public class ChatMessagesServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ChatMessagesServiceApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /dto-models/src/main/java/pl/kubaretip/dtomodels/ChatMessageDTO.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.dtomodels; 2 | 3 | 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | import java.io.Serializable; 10 | 11 | @ToString 12 | @Getter 13 | @Setter 14 | @NoArgsConstructor 15 | public class ChatMessageDTO implements Serializable { 16 | 17 | private String id; 18 | private Long friendChat; 19 | private String sender; 20 | private String recipient; 21 | private String content; 22 | private String time; 23 | private String status; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /exception-utils/src/main/java/pl/kubaretip/exceptionutils/error/Error.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.exceptionutils.error; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | 7 | import java.util.Date; 8 | import java.util.Set; 9 | 10 | @JsonInclude(JsonInclude.Include.NON_NULL) 11 | @Builder 12 | @Getter 13 | public class Error { 14 | 15 | @Builder.Default 16 | private final Date timestamp = new Date(); 17 | private int status; 18 | private String title; 19 | private String detail; 20 | private String path; 21 | private Set violations; 22 | } 23 | -------------------------------------------------------------------------------- /chat-service/src/main/resources/db/migration/V1.0.2__create_friend_request_table.sql: -------------------------------------------------------------------------------- 1 | create table if not exists friend_request 2 | ( 3 | id bigint not null auto_increment primary key, 4 | sender_chat_profile_id binary(16) not null, 5 | recipient_chat_profile_id binary(16) not null, 6 | sent_time datetime(6) not null, 7 | is_accepted boolean not null default 0, 8 | foreign key (sender_chat_profile_id) references chat_profile (user_id), 9 | foreign key (recipient_chat_profile_id) references chat_profile (user_id), 10 | unique index (sender_chat_profile_id, recipient_chat_profile_id) 11 | ) -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import pl.kubaretip.authservice.domain.User; 5 | 6 | 7 | import java.util.Optional; 8 | import java.util.UUID; 9 | 10 | public interface UserRepository extends JpaRepository { 11 | 12 | Optional findOneWithAuthoritiesByUsernameIgnoreCase(String username); 13 | 14 | boolean existsByUsernameIgnoreCase(String username); 15 | 16 | boolean existsByEmailIgnoreCase(String email); 17 | 18 | Optional findOneByActivationKey(String activationKey); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/security/SecurityUtils.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.security; 2 | 3 | import org.springframework.security.core.context.ReactiveSecurityContextHolder; 4 | import pl.kubaretip.authutils.security.SecurityUserDetails; 5 | import reactor.core.publisher.Mono; 6 | 7 | public class SecurityUtils { 8 | 9 | public static Mono getCurrentUser() { 10 | return ReactiveSecurityContextHolder.getContext() 11 | .map(context -> context.getAuthentication().getPrincipal()) 12 | .cast(SecurityUserDetails.class) 13 | .map(SecurityUserDetails::getId); 14 | } 15 | 16 | 17 | } 18 | -------------------------------------------------------------------------------- /auth-utils/src/main/java/pl/kubaretip/authutils/jwt/JWTConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authutils.jwt; 2 | 3 | import lombok.Getter; 4 | import org.springframework.beans.factory.annotation.Value; 5 | 6 | @Getter 7 | public class JWTConfig { 8 | 9 | @Value("${security.jwt.uri:/authenticate}") 10 | private String authEndpoint; 11 | 12 | @Value("${security.jwt.header:Authorization}") 13 | private String header; 14 | 15 | @Value("${security.jwt.prefix:Bearer }") 16 | private String tokenPrefix; 17 | 18 | @Value("${security.jwt.expiration:#{24*60*60}}") 19 | private int expiration; 20 | 21 | @Value("${security.jwt.secret:jwtSecretKey123}") 22 | private String secret; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/util/DateUtils.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.util; 2 | 3 | import java.time.LocalDateTime; 4 | import java.time.OffsetDateTime; 5 | import java.time.format.DateTimeFormatter; 6 | import java.util.TimeZone; 7 | 8 | public final class DateUtils { 9 | 10 | public static final String DATE_PATTERN = "dd.MM.yyyy HH:mm:ss"; 11 | 12 | public static OffsetDateTime convertStringDateToOffsetTime(String time) { 13 | var localDateTime = LocalDateTime.parse(time, DateTimeFormatter.ofPattern(DATE_PATTERN)); 14 | var zoneOffset = TimeZone.getDefault().toZoneId().getRules().getOffset(localDateTime); 15 | return localDateTime.atOffset(zoneOffset); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/domain/ChatProfile.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.*; 8 | import java.util.UUID; 9 | 10 | 11 | @Getter 12 | @Setter 13 | @Table(name = "chat_profile", schema = "chat_service_database") 14 | @Entity 15 | @NoArgsConstructor 16 | public class ChatProfile { 17 | 18 | @Id 19 | @Column(nullable = false, name = "user_id", unique = true, columnDefinition = "BINARY(16)") 20 | private UUID userId; 21 | 22 | @Column(name = "friends_request_code", nullable = false, length = 64, unique = true) 23 | private String friendsRequestCode; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/service/FriendRequestService.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.service; 2 | 3 | import pl.kubaretip.chatservice.domain.FriendRequest; 4 | 5 | import java.util.List; 6 | 7 | public interface FriendRequestService { 8 | FriendRequest createNewFriendRequest(String currentUserId, String friendRequestCode); 9 | 10 | void replyToFriendRequest(long friendRequestId, String currentUserId, boolean accept); 11 | 12 | void deleteFriendRequestBySender(String senderId, long friendRequestId); 13 | 14 | List getAllNotAcceptedFriendRequestsByRecipientId(String recipientId); 15 | 16 | List getAllNotAcceptedFriendRequestsBySenderId(String senderId); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /auth-utils/src/main/java/pl/kubaretip/authutils/SecurityUtils.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authutils; 2 | 3 | import org.springframework.security.core.context.SecurityContextHolder; 4 | import pl.kubaretip.authutils.security.SecurityUserDetails; 5 | 6 | public class SecurityUtils { 7 | 8 | public static String getCurrentUser() { 9 | var principal = (SecurityUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 10 | return principal.getId(); 11 | } 12 | 13 | public static String getCurrentUserPreferredUsername(){ 14 | var principal = (SecurityUserDetails) SecurityContextHolder.getContext().getAuthentication().getPrincipal(); 15 | return principal.getUsername(); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /auth-service/src/test/java/pl/kubaretip/authservice/utils/JacksonIgnoreWriteOnlyAccess.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.utils; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.databind.introspect.Annotated; 5 | import com.fasterxml.jackson.databind.introspect.JacksonAnnotationIntrospector; 6 | 7 | public class JacksonIgnoreWriteOnlyAccess extends JacksonAnnotationIntrospector { 8 | 9 | 10 | @Override 11 | public JsonProperty.Access findPropertyAccess(Annotated m) { 12 | JsonProperty.Access access = super.findPropertyAccess(m); 13 | if (access == JsonProperty.Access.WRITE_ONLY) { 14 | return JsonProperty.Access.AUTO; 15 | } 16 | return access; 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/dto/mapper/ChatProfileMapper.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.dto.mapper; 2 | 3 | import org.mapstruct.Mapper; 4 | import org.mapstruct.Mapping; 5 | import org.mapstruct.Named; 6 | import pl.kubaretip.chatservice.domain.ChatProfile; 7 | import pl.kubaretip.chatservice.dto.ChatProfileDTO; 8 | 9 | import java.util.UUID; 10 | 11 | @Mapper(componentModel = "spring") 12 | public interface ChatProfileMapper { 13 | 14 | @Named("chatProfileToChatProfileDTO") 15 | @Mapping(target = "userId", expression = "java(convertUUIDtoString(chatProfile.getUserId()))") 16 | ChatProfileDTO chatProfileToChatProfileDTO(ChatProfile chatProfile); 17 | 18 | default String convertUUIDtoString(UUID id) { 19 | return id.toString(); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/ChatServiceApplication.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; 7 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 8 | 9 | @EnableEurekaClient 10 | @SpringBootApplication(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class}) 11 | public class ChatServiceApplication { 12 | public static void main(String[] args) { 13 | SpringApplication.run(ChatServiceApplication.class, args); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/config/RabbitMQConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.config; 2 | 3 | import org.springframework.amqp.core.FanoutExchange; 4 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import pl.kubaretip.authservice.messaging.sender.UserSender; 8 | 9 | @Configuration 10 | public class RabbitMQConfig { 11 | 12 | @Bean 13 | public FanoutExchange fanoutExchange() { 14 | return new FanoutExchange("pl.kubaretip.authservice.fanout"); 15 | } 16 | 17 | 18 | @Bean 19 | public UserSender userSender(RabbitTemplate rabbitTemplate, FanoutExchange fanoutExchange) { 20 | return new UserSender(rabbitTemplate, fanoutExchange); 21 | } 22 | 23 | 24 | } 25 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/domain/Authority.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.ToString; 7 | import org.hibernate.annotations.Immutable; 8 | 9 | import javax.persistence.Column; 10 | import javax.persistence.Entity; 11 | import javax.persistence.Id; 12 | import javax.persistence.Table; 13 | import java.io.Serializable; 14 | 15 | @ToString 16 | @Entity 17 | @Table(schema = "auth_service_database") 18 | @Getter 19 | @Setter 20 | @NoArgsConstructor 21 | @Immutable 22 | public class Authority implements Serializable { 23 | 24 | public static final long serialVersionUID = -8053205789790776096L; 25 | 26 | @Id 27 | @Column(length = 50, nullable = false, unique = true) 28 | private String name; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /mail-service/src/main/java/pl/kubaretip/mailservice/messaging/listener/RabbitUserActivationListener.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.mailservice.messaging.listener; 2 | 3 | 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | import pl.kubaretip.dtomodels.UserDTO; 7 | import pl.kubaretip.mailservice.service.SendMailService; 8 | 9 | @Component 10 | public class RabbitUserActivationListener { 11 | 12 | private final SendMailService sendMailService; 13 | 14 | public RabbitUserActivationListener(SendMailService sendMailService) { 15 | this.sendMailService = sendMailService; 16 | 17 | } 18 | 19 | @RabbitListener(queues = "#{usersActivationQueue.name}") 20 | public void receiveNewUser(UserDTO userDTO) { 21 | sendMailService.sendActivationEmail(userDTO); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/log/WebSocketLogs.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.log; 2 | 3 | import org.springframework.context.annotation.Profile; 4 | import org.springframework.stereotype.Component; 5 | import org.springframework.web.socket.config.WebSocketMessageBrokerStats; 6 | 7 | import javax.annotation.PostConstruct; 8 | 9 | @Profile("dev") 10 | @Component 11 | public class WebSocketLogs { 12 | 13 | private final WebSocketMessageBrokerStats webSocketMessageBrokerStats; 14 | 15 | public WebSocketLogs(WebSocketMessageBrokerStats webSocketMessageBrokerStats) { 16 | this.webSocketMessageBrokerStats = webSocketMessageBrokerStats; 17 | } 18 | 19 | @PostConstruct 20 | public void init() { 21 | webSocketMessageBrokerStats.setLoggingPeriod(30 * 1000); 22 | } 23 | 24 | 25 | } 26 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/web/model/ChangePassRequest.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.web.model; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import pl.kubaretip.authservice.constants.ApplicationConstants; 8 | 9 | import javax.validation.constraints.NotBlank; 10 | import javax.validation.constraints.Size; 11 | 12 | @AllArgsConstructor 13 | @NoArgsConstructor 14 | @Getter 15 | @Setter 16 | public class ChangePassRequest { 17 | 18 | @NotBlank 19 | @Size(min = ApplicationConstants.USER_PASSWORD_MIN_LENGTH, max = ApplicationConstants.USER_PASSWORD_MAX_LENGTH) 20 | private String currentPassword; 21 | 22 | @NotBlank 23 | @Size(min = ApplicationConstants.USER_PASSWORD_MIN_LENGTH, max = ApplicationConstants.USER_PASSWORD_MAX_LENGTH) 24 | private String newPassword; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/MessagesWebSocketServiceApplication.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice; 2 | 3 | import org.springframework.boot.SpringApplication; 4 | import org.springframework.boot.autoconfigure.SpringBootApplication; 5 | import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration; 6 | import org.springframework.boot.autoconfigure.security.servlet.UserDetailsServiceAutoConfiguration; 7 | import org.springframework.cloud.netflix.eureka.EnableEurekaClient; 8 | 9 | @EnableEurekaClient 10 | @SpringBootApplication(exclude = {SecurityAutoConfiguration.class, UserDetailsServiceAutoConfiguration.class}) 11 | public class MessagesWebSocketServiceApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(MessagesWebSocketServiceApplication.class, args); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/document/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.document; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import org.springframework.data.annotation.Id; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | import org.springframework.data.mongodb.core.mapping.Field; 9 | import pl.kubaretip.chatmessagesservice.constant.MessageStatus; 10 | 11 | import java.util.Date; 12 | 13 | @Getter 14 | @Setter 15 | @NoArgsConstructor 16 | @Document(collection = "chat_message") 17 | public class ChatMessage { 18 | 19 | @Id 20 | private String id; 21 | 22 | @Field(name = "friend_chat") 23 | private Long friendChat; 24 | private String sender; 25 | private String recipient; 26 | private String content; 27 | private Date time; 28 | private MessageStatus status; 29 | 30 | } 31 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/messaging/listener/RabbitMessageDeletingListener.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.messaging.listener; 2 | 3 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 4 | import org.springframework.stereotype.Component; 5 | import pl.kubaretip.chatmessagesservice.service.ChatMessageService; 6 | 7 | import java.util.List; 8 | 9 | @Component 10 | public class RabbitMessageDeletingListener { 11 | 12 | private final ChatMessageService chatMessageService; 13 | 14 | public RabbitMessageDeletingListener(ChatMessageService chatMessageService) { 15 | this.chatMessageService = chatMessageService; 16 | } 17 | 18 | @RabbitListener(queues = "#{messageDeletingQueue.name}") 19 | public void receiveNewChatMessage(List friendChatIds) { 20 | chatMessageService.removeMessagesByFriendChat(friendChatIds).subscribe(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/config/RabbitMQConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.config; 2 | 3 | import org.springframework.amqp.core.FanoutExchange; 4 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import pl.kubaretip.messageswebsocketservice.messaging.sender.StoringMessagesSender; 8 | 9 | @Configuration 10 | public class RabbitMQConfig { 11 | 12 | @Bean 13 | public FanoutExchange fanoutExchange() { 14 | return new FanoutExchange("pl.kubaretip.chatmessagesservice.fanout"); 15 | } 16 | 17 | @Bean 18 | public StoringMessagesSender storingMessagesSender(RabbitTemplate rabbitTemplate, FanoutExchange fanoutExchange) { 19 | return new StoringMessagesSender(rabbitTemplate, fanoutExchange); 20 | } 21 | 22 | 23 | } 24 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/config/WebSocketSecurityConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.config; 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 WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer { 9 | 10 | @Override 11 | protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) { 12 | messages.anyMessage().authenticated(); 13 | } 14 | 15 | // TODO: Temporarily disabled CRSF. 16 | // In the future provide CRSF endpoint. 17 | @Override 18 | protected boolean sameOriginDisabled() { 19 | return true; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/mapper/UserMapper.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.mapper; 2 | 3 | import org.mapstruct.InheritConfiguration; 4 | import org.mapstruct.Mapper; 5 | import org.mapstruct.Mapping; 6 | import pl.kubaretip.authservice.domain.User; 7 | import pl.kubaretip.dtomodels.UserDTO; 8 | 9 | import java.util.UUID; 10 | 11 | @Mapper(componentModel = "spring") 12 | public interface UserMapper { 13 | 14 | @Mapping(target = "password", ignore = true) 15 | @Mapping(target = "id", expression = "java(convertIdToString(user.getId()))") 16 | UserDTO mapToUserDTO(User user); 17 | 18 | @InheritConfiguration(name = "mapToUserDTO") 19 | @Mapping(target = "activationKey", ignore = true) 20 | UserDTO mapToUserDTOWithoutActivationKey(User user); 21 | 22 | default String convertIdToString(UUID id) { 23 | if (id == null) { 24 | return null; 25 | } 26 | return id.toString(); 27 | } 28 | 29 | 30 | } 31 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/messaging/sender/UserSender.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.messaging.sender; 2 | 3 | import org.springframework.amqp.core.FanoutExchange; 4 | import org.springframework.amqp.core.MessageProperties; 5 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 6 | import pl.kubaretip.dtomodels.UserDTO; 7 | 8 | 9 | public class UserSender { 10 | 11 | private final RabbitTemplate template; 12 | private final FanoutExchange fanout; 13 | 14 | public UserSender(RabbitTemplate template, FanoutExchange fanout) { 15 | this.template = template; 16 | this.fanout = fanout; 17 | } 18 | 19 | 20 | public void send(UserDTO userDTO) { 21 | var converter = template.getMessageConverter(); 22 | var messageProperties = new MessageProperties(); 23 | var message = converter.toMessage(userDTO, messageProperties); 24 | template.send(fanout.getName(), "", message); 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/messaging/listener/RabbitNewUserListener.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.messaging.listener; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 5 | import org.springframework.stereotype.Component; 6 | import pl.kubaretip.chatservice.service.ChatProfileService; 7 | import pl.kubaretip.dtomodels.UserDTO; 8 | 9 | @Slf4j 10 | @Component 11 | public class RabbitNewUserListener { 12 | 13 | private final ChatProfileService chatProfileService; 14 | 15 | public RabbitNewUserListener(ChatProfileService chatProfileService) { 16 | this.chatProfileService = chatProfileService; 17 | } 18 | 19 | 20 | @RabbitListener(queues = "#{newUsersQueue.name}") 21 | public void receiveNewUser(UserDTO userDTO) { 22 | log.debug("New user {}", userDTO.getUsername()); 23 | chatProfileService.createChatProfile(userDTO.getId(), userDTO.getUsername()); 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/messaging/sender/DeleteMessagesSender.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.messaging.sender; 2 | 3 | import org.springframework.amqp.core.FanoutExchange; 4 | import org.springframework.amqp.core.MessageProperties; 5 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 6 | 7 | import java.util.List; 8 | 9 | public class DeleteMessagesSender { 10 | 11 | private final RabbitTemplate template; 12 | private final FanoutExchange fanout; 13 | 14 | public DeleteMessagesSender(RabbitTemplate template, FanoutExchange fanout) { 15 | this.template = template; 16 | this.fanout = fanout; 17 | } 18 | 19 | public void sendDeletingMessagesTask(List friendChatIds) { 20 | var converter = template.getMessageConverter(); 21 | var messageProperties = new MessageProperties(); 22 | var message = converter.toMessage(friendChatIds, messageProperties); 23 | template.send(fanout.getName(), "", message); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/service/ChatMessageService.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.service; 2 | 3 | import pl.kubaretip.chatmessagesservice.document.ChatMessage; 4 | import reactor.core.publisher.Flux; 5 | import reactor.core.publisher.Mono; 6 | 7 | import java.util.List; 8 | 9 | public interface ChatMessageService { 10 | 11 | Flux findLastUsersMessagesFromTime(long firstUserFriendChatId, long secondUserFriendChatId, 12 | String beforeTime, int numberOfMessagesToFetch); 13 | 14 | Flux getLastUserMessages(long friendChatId1, long friendChatId2, int numberOfMessagesToFetch); 15 | 16 | Mono setDeliveredStatusForAllRecipientMessagesInFriendChat(long friendChatId, String currentUser); 17 | 18 | Mono saveChatMessage(Long friendChat, String sender, String recipient, String content, String time); 19 | 20 | Mono removeMessagesByFriendChat(List ids); 21 | } 22 | -------------------------------------------------------------------------------- /docker/.env.docker: -------------------------------------------------------------------------------- 1 | # you can change this starting passwords but you must recrypt all in config repo 2 | AUTH_SERVICE_MYSQL_USER=auth-service-user 3 | AUTH_SERVICE_MYSQL_PASS=auth-service-pass-123 4 | AUTH_SERVICE_MYSQL_ROOT_PASS=auth-service-root-pass-123 5 | CHAT_SERVICE_MYSQL_USER=chat-service-user 6 | CHAT_SERVICE_MYSQL_PASS=chat-service-pass-123 7 | CHAT_SERVICE_MYSQL_ROOT_PASS=chat-service-root-pass-123 8 | CHAT_MESSAGES_SERVICE_ROOT_MONGO_USER=chat-messages-service-root 9 | CHAT_MESSAGES_SERVICE_ROOT_MONGO_PASS=chat-messages-service-root-pass-123 10 | # if you change config server secret you must recrypt all encypted password in config repo 11 | CONFIG_SERVER_SECRET=superSERCRET 12 | CONFIG_SERVER_URI=http://config-server:8888 13 | PROFILE=docker 14 | # repo with configuration files 15 | REPO_BRANCH= 16 | GIT_REPO_URI= 17 | # location .ssh/known_host key without ssh-rsa 18 | KNOWN_HOST_KEY= 19 | # generation ssh key ssh-keygen -m PEM -t rsa -b 4096 20 | PEM="| 21 | -----BEGIN RSA PRIVATE KEY----- 22 | ... 23 | -----END RSA PRIVATE KEY-----" -------------------------------------------------------------------------------- /mail-service/src/main/java/pl/kubaretip/mailservice/config/RabbitMQConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.mailservice.config; 2 | 3 | import org.springframework.amqp.core.Binding; 4 | import org.springframework.amqp.core.BindingBuilder; 5 | import org.springframework.amqp.core.FanoutExchange; 6 | import org.springframework.amqp.core.Queue; 7 | import org.springframework.context.annotation.Bean; 8 | import org.springframework.context.annotation.Configuration; 9 | 10 | @Configuration 11 | public class RabbitMQConfig { 12 | 13 | @Bean 14 | public FanoutExchange fanoutExchange() { 15 | return new FanoutExchange("pl.kubaretip.authservice.fanout"); 16 | } 17 | 18 | @Bean 19 | public Queue usersActivationQueue() { 20 | return new Queue("pl.kubaretip.mailservice.activation"); 21 | } 22 | 23 | @Bean 24 | public Binding bindingActivationMail(FanoutExchange fanoutExchange, Queue usersActivationQueue) { 25 | return BindingBuilder.bind(usersActivationQueue).to(fanoutExchange); 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/messaging/sender/StoringMessagesSender.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.messaging.sender; 2 | 3 | import org.springframework.amqp.core.FanoutExchange; 4 | import org.springframework.amqp.core.MessageProperties; 5 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 6 | import pl.kubaretip.dtomodels.ChatMessageDTO; 7 | 8 | public class StoringMessagesSender { 9 | 10 | private final RabbitTemplate template; 11 | private final FanoutExchange fanout; 12 | 13 | public StoringMessagesSender(RabbitTemplate template, FanoutExchange fanout) { 14 | this.template = template; 15 | this.fanout = fanout; 16 | } 17 | 18 | public void send(ChatMessageDTO chatMessageDTO){ 19 | var converter = template.getMessageConverter(); 20 | var messageProperties = new MessageProperties(); 21 | var message = converter.toMessage(chatMessageDTO, messageProperties); 22 | template.send(fanout.getName(), "", message); 23 | } 24 | 25 | 26 | } 27 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/domain/FriendChat.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.domain; 2 | 3 | 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import javax.persistence.*; 9 | 10 | @Getter 11 | @Setter 12 | @NoArgsConstructor 13 | @Table(schema = "chat_service_database", name = "friend_chat", 14 | uniqueConstraints = @UniqueConstraint(columnNames = {"chat_with_id", "sender_id", "recipient_id"})) 15 | @Entity 16 | public class FriendChat { 17 | 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Long id; 21 | 22 | @ManyToOne(fetch = FetchType.LAZY, cascade = {CascadeType.REMOVE}) 23 | @JoinColumn(name = "chat_with_id") 24 | private FriendChat chatWith; 25 | 26 | @ManyToOne(fetch = FetchType.LAZY) 27 | @JoinColumn(name = "sender_id", nullable = false) 28 | private ChatProfile sender; 29 | 30 | @ManyToOne(fetch = FetchType.LAZY) 31 | @JoinColumn(name = "recipient_id", nullable = false) 32 | private ChatProfile recipient; 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/log/WebSocketConnectionListener.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.log; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.context.annotation.Profile; 5 | import org.springframework.context.event.EventListener; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.web.socket.messaging.SessionConnectEvent; 8 | import org.springframework.web.socket.messaging.SessionDisconnectEvent; 9 | 10 | import java.util.Date; 11 | 12 | @Profile("dev") 13 | @Slf4j 14 | @Component 15 | public class WebSocketConnectionListener { 16 | 17 | 18 | @EventListener(SessionConnectEvent.class) 19 | public void handleWebsocketConnectListener(SessionConnectEvent event) { 20 | log.debug("Session connected : {}", new Date().toString()); 21 | } 22 | 23 | @EventListener(SessionDisconnectEvent.class) 24 | public void handleWebsocketDisconnectListener(SessionDisconnectEvent event) { 25 | log.debug("Session closed : {}", new Date().toString()); 26 | } 27 | 28 | } 29 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/dto/mapper/FriendChatMapper.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.dto.mapper; 2 | 3 | import org.mapstruct.IterableMapping; 4 | import org.mapstruct.Mapper; 5 | import org.mapstruct.Mapping; 6 | import org.mapstruct.Named; 7 | import pl.kubaretip.chatservice.domain.FriendChat; 8 | import pl.kubaretip.chatservice.dto.FriendChatDTO; 9 | 10 | import java.util.List; 11 | 12 | @Mapper(componentModel = "spring", uses = ChatProfileMapper.class) 13 | public interface FriendChatMapper { 14 | 15 | @Named("mapToFriendChatWithIdChatWith") 16 | @Mapping(target = "chatWith", expression = "java(convertChatWithToId(friendChat.getChatWith()))") 17 | @Mapping(target = "recipient", qualifiedByName = "chatProfileToChatProfileDTO") 18 | FriendChatDTO mapToFriendChatDTO(FriendChat friendChat); 19 | 20 | @IterableMapping(qualifiedByName = {"mapToFriendChatWithIdChatWith"}) 21 | List mapToFriendChatList(List friendChats); 22 | 23 | default Long convertChatWithToId(FriendChat chatWith) { 24 | return chatWith.getId(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/repository/ChatMessageRepository.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.repository; 2 | 3 | import org.springframework.data.domain.Pageable; 4 | import org.springframework.data.repository.reactive.ReactiveCrudRepository; 5 | import pl.kubaretip.chatmessagesservice.constant.MessageStatus; 6 | import pl.kubaretip.chatmessagesservice.document.ChatMessage; 7 | import reactor.core.publisher.Flux; 8 | import reactor.core.publisher.Mono; 9 | 10 | import java.util.Collection; 11 | import java.util.Date; 12 | import java.util.List; 13 | 14 | 15 | public interface ChatMessageRepository extends ReactiveCrudRepository { 16 | 17 | Flux findByTimeLessThanAndFriendChatIn(Date time, Collection ids, Pageable pageable); 18 | 19 | Flux findByFriendChatOrFriendChat(long firstUserChatId, long secondUserChatId, Pageable pageable); 20 | 21 | Flux findByFriendChatAndRecipientAndStatus(long friendChatId, String userId, MessageStatus status); 22 | 23 | Mono deleteByFriendChatIn(List ids); 24 | } 25 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/messaging/listener/RabbitMessageStoringListener.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.messaging.listener; 2 | 3 | import org.springframework.amqp.rabbit.annotation.RabbitListener; 4 | import org.springframework.stereotype.Component; 5 | import pl.kubaretip.chatmessagesservice.service.ChatMessageService; 6 | import pl.kubaretip.dtomodels.ChatMessageDTO; 7 | 8 | @Component 9 | public class RabbitMessageStoringListener { 10 | 11 | private final ChatMessageService chatMessageService; 12 | 13 | public RabbitMessageStoringListener(ChatMessageService chatMessageService) { 14 | this.chatMessageService = chatMessageService; 15 | } 16 | 17 | @RabbitListener(queues = "#{messageStoringQueue.name}") 18 | public void receiveNewChatMessage(ChatMessageDTO chatMessageDTO) { 19 | chatMessageService.saveChatMessage(chatMessageDTO.getFriendChat(), 20 | chatMessageDTO.getSender(), 21 | chatMessageDTO.getRecipient(), 22 | chatMessageDTO.getContent(), 23 | chatMessageDTO.getTime()).subscribe(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/domain/FriendRequest.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.domain; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | 7 | import javax.persistence.*; 8 | import java.time.OffsetDateTime; 9 | 10 | 11 | @Table(schema = "chat_service_database", 12 | name = "friend_request", 13 | uniqueConstraints = @UniqueConstraint(columnNames = {"sender_chat_profile_id", "recipient_chat_profile_id"})) 14 | @Entity 15 | @Getter 16 | @Setter 17 | @NoArgsConstructor 18 | public class FriendRequest { 19 | 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Long id; 23 | 24 | @Column(nullable = false, name = "sent_time") 25 | private OffsetDateTime sentTime; 26 | 27 | @ManyToOne(fetch = FetchType.LAZY) 28 | @JoinColumn(name = "sender_chat_profile_id", nullable = false) 29 | private ChatProfile sender; 30 | 31 | @ManyToOne(fetch = FetchType.LAZY) 32 | @JoinColumn(name = "recipient_chat_profile_id", nullable = false) 33 | private ChatProfile recipient; 34 | 35 | @Column(nullable = false, name = "is_accepted") 36 | private boolean isAccepted = false; 37 | 38 | } 39 | -------------------------------------------------------------------------------- /config-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | config-server 13 | 14 | 15 | 16 | 17 | org.springframework.boot 18 | spring-boot-starter-actuator 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-config-server 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-test 29 | test 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/security/WebSocketAuthService.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.security; 2 | 3 | import com.auth0.jwt.exceptions.JWTDecodeException; 4 | import com.auth0.jwt.exceptions.TokenExpiredException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.stereotype.Component; 8 | import pl.kubaretip.authutils.jwt.JWTUtils; 9 | 10 | @Slf4j 11 | @Component 12 | public class WebSocketAuthService { 13 | 14 | private final JWTUtils jwtUtils; 15 | 16 | public WebSocketAuthService(JWTUtils jwtUtils) { 17 | this.jwtUtils = jwtUtils; 18 | } 19 | 20 | 21 | public UsernamePasswordAuthenticationToken attemptAuthentication(String authorizationHeaderValue) { 22 | 23 | if (jwtUtils.isValidAuthorizationHeaderValue(authorizationHeaderValue)) { 24 | try { 25 | var token = authorizationHeaderValue.replace(jwtUtils.getJwtConfig().getTokenPrefix(), ""); 26 | return jwtUtils.getAuthentication(token); 27 | } catch (JWTDecodeException | TokenExpiredException ex) { 28 | log.error("Invalid token"); 29 | } 30 | } 31 | return null; 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /auth-service/src/test/java/pl/kubaretip/authservice/mapper/UserMapperImplTest.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.mapper; 2 | 3 | import org.junit.jupiter.api.BeforeAll; 4 | import org.junit.jupiter.api.Test; 5 | import pl.kubaretip.authservice.domain.User; 6 | 7 | import static org.hamcrest.MatcherAssert.assertThat; 8 | import static org.hamcrest.Matchers.nullValue; 9 | 10 | public class UserMapperImplTest { 11 | 12 | private static UserMapperImpl userMapper; 13 | 14 | @BeforeAll 15 | public static void setup() { 16 | userMapper = new UserMapperImpl(); 17 | } 18 | 19 | @Test 20 | public void shouldIgnorePasswordWhenMappingToDTO() { 21 | 22 | // given 23 | var user = new User(); 24 | user.setPassword("passs"); 25 | 26 | // when 27 | var userDTO = userMapper.mapToUserDTO(user); 28 | 29 | // then 30 | assertThat(userDTO.getPassword(), nullValue()); 31 | 32 | } 33 | 34 | @Test 35 | public void shouldIgnoreActivationKeyWhenMappingToDTO() { 36 | // given 37 | var user = new User(); 38 | user.setActivationKey("123"); 39 | 40 | //when 41 | var userDTO = userMapper.mapToUserDTOWithoutActivationKey(user); 42 | 43 | //then 44 | assertThat(userDTO.getActivationKey(), nullValue()); 45 | } 46 | 47 | 48 | } 49 | -------------------------------------------------------------------------------- /eureka-server/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | eureka-server 13 | 14 | 15 | 16 | org.springframework.cloud 17 | spring-cloud-starter-netflix-eureka-server 18 | 19 | 20 | 21 | org.springframework.cloud 22 | spring-cloud-starter-config 23 | 24 | 25 | 26 | org.springframework.boot 27 | spring-boot-starter-aop 28 | 29 | 30 | 31 | org.springframework.retry 32 | spring-retry 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/web/websocket/MessagesController.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.web.websocket; 2 | 3 | import org.springframework.messaging.handler.annotation.MessageMapping; 4 | import org.springframework.messaging.handler.annotation.Payload; 5 | 6 | import org.springframework.messaging.simp.SimpMessagingTemplate; 7 | import org.springframework.web.bind.annotation.RestController; 8 | import pl.kubaretip.dtomodels.ChatMessageDTO; 9 | import pl.kubaretip.messageswebsocketservice.messaging.sender.StoringMessagesSender; 10 | 11 | 12 | @RestController 13 | public class MessagesController { 14 | 15 | private final SimpMessagingTemplate simpMessagingTemplate; 16 | private final StoringMessagesSender storingMessagesSender; 17 | 18 | public MessagesController(SimpMessagingTemplate simpMessagingTemplate, 19 | StoringMessagesSender storingMessagesSender) { 20 | this.simpMessagingTemplate = simpMessagingTemplate; 21 | this.storingMessagesSender = storingMessagesSender; 22 | } 23 | 24 | @MessageMapping("/chat") 25 | public void processMessage(@Payload ChatMessageDTO chatMessage) { 26 | simpMessagingTemplate.convertAndSend("/topic/" + chatMessage.getRecipient() + ".messages", chatMessage); 27 | storingMessagesSender.send(chatMessage); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /mail-service/src/main/java/pl/kubaretip/mailservice/config/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.mailservice.config; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.boot.autoconfigure.task.TaskExecutionProperties; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | import org.springframework.scheduling.annotation.EnableAsync; 8 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 9 | 10 | import java.util.concurrent.Executor; 11 | 12 | @Slf4j 13 | @Configuration 14 | @EnableAsync 15 | public class AsyncConfig { 16 | 17 | private final TaskExecutionProperties taskExecutionProperties; 18 | 19 | public AsyncConfig(TaskExecutionProperties taskExecutionProperties) { 20 | this.taskExecutionProperties = taskExecutionProperties; 21 | } 22 | 23 | @Bean 24 | public Executor taskExecutor() { 25 | var executor = new ThreadPoolTaskExecutor(); 26 | executor.setCorePoolSize(taskExecutionProperties.getPool().getCoreSize()); 27 | executor.setMaxPoolSize(taskExecutionProperties.getPool().getMaxSize()); 28 | executor.setQueueCapacity(taskExecutionProperties.getPool().getQueueCapacity()); 29 | executor.setThreadNamePrefix(taskExecutionProperties.getThreadNamePrefix()); 30 | executor.initialize(); 31 | return executor; 32 | } 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/repository/FriendChatRepository.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Query; 5 | import pl.kubaretip.chatservice.domain.ChatProfile; 6 | import pl.kubaretip.chatservice.domain.FriendChat; 7 | 8 | import java.util.List; 9 | import java.util.Optional; 10 | import java.util.UUID; 11 | 12 | public interface FriendChatRepository extends JpaRepository { 13 | 14 | 15 | @Query("SELECT " + 16 | "CASE WHEN COUNT (fc) > 0 THEN true ELSE false END " + 17 | "FROM FriendChat fc WHERE (fc.sender = :chatProfile1 AND fc.recipient = :chatProfile2) " + 18 | "OR (fc.sender = :chatProfile2 AND fc.recipient = :chatProfile1)") 19 | boolean existsFriendChatForUsers(ChatProfile chatProfile1, ChatProfile chatProfile2); 20 | 21 | 22 | List findBySender(ChatProfile sender); 23 | 24 | 25 | @Query("SELECT fc from FriendChat fc WHERE fc.id = :friendChatId " + 26 | "AND fc.chatWith.id = :friendChatWithId " + 27 | "AND fc.sender.userId = :senderId") 28 | Optional findByIdAndFriendChatWithIdAndSenderId(long friendChatId, 29 | long friendChatWithId, 30 | UUID senderId); 31 | 32 | 33 | } 34 | -------------------------------------------------------------------------------- /cloud-gateway/src/main/java/pl/kubaretip/cloudgateway/config/OpenAPIConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.cloudgateway.config; 2 | 3 | import org.springdoc.core.GroupedOpenApi; 4 | import org.springdoc.core.SwaggerUiConfigParameters; 5 | import org.springframework.cloud.gateway.route.RouteLocator; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.context.annotation.Configuration; 8 | import org.springframework.context.annotation.Lazy; 9 | 10 | import java.util.ArrayList; 11 | import java.util.List; 12 | 13 | @Configuration 14 | public class OpenAPIConfig { 15 | 16 | private RouteLocator locator; 17 | 18 | public OpenAPIConfig(RouteLocator locator) { 19 | this.locator = locator; 20 | } 21 | 22 | @Bean 23 | @Lazy(false) 24 | public List apis(SwaggerUiConfigParameters swaggerUiConfigParameters) { 25 | List groups = new ArrayList<>(); 26 | locator.getRoutes() 27 | .subscribe(route -> { 28 | if (route.getId().toLowerCase().startsWith("ReactiveCompositeDiscoveryClient_".toLowerCase())) { 29 | String name = route.getId().replaceAll("ReactiveCompositeDiscoveryClient_", ""); 30 | swaggerUiConfigParameters.addGroup(name); 31 | groups.add(GroupedOpenApi.builder().pathsToMatch("/" + name + "/v3/api-docs").group(name).build()); 32 | } 33 | }); 34 | return groups; 35 | 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/config/RabbitMQConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.config; 2 | 3 | import org.springframework.amqp.core.Binding; 4 | import org.springframework.amqp.core.BindingBuilder; 5 | import org.springframework.amqp.core.FanoutExchange; 6 | import org.springframework.amqp.core.Queue; 7 | import org.springframework.amqp.rabbit.core.RabbitTemplate; 8 | import org.springframework.context.annotation.Bean; 9 | import org.springframework.context.annotation.Configuration; 10 | import pl.kubaretip.chatservice.messaging.sender.DeleteMessagesSender; 11 | 12 | @Configuration 13 | public class RabbitMQConfig { 14 | 15 | @Bean 16 | public FanoutExchange fanoutExchange() { 17 | return new FanoutExchange("pl.kubaretip.authservice.fanout"); 18 | } 19 | 20 | @Bean 21 | public Queue newUsersQueue() { 22 | return new Queue("pl.kubaretip.chatservice.account"); 23 | } 24 | 25 | @Bean 26 | public Binding bindingActivationMail(FanoutExchange fanoutExchange, Queue newUsersQueue) { 27 | return BindingBuilder.bind(newUsersQueue).to(fanoutExchange); 28 | } 29 | 30 | @Bean 31 | public FanoutExchange deletingMessageExchange() { 32 | return new FanoutExchange("pl.kubaretip.chatmessagesservice.fanout.deleting"); 33 | } 34 | 35 | @Bean 36 | public DeleteMessagesSender deleteMessagesSender(RabbitTemplate rabbitTemplate, FanoutExchange deletingMessageExchange) { 37 | return new DeleteMessagesSender(rabbitTemplate, deletingMessageExchange); 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /dto-models/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | dto-models 13 | 14 | 15 | 2.12.1 16 | 17 | 18 | 19 | 20 | 21 | org.springframework.boot 22 | spring-boot-starter-validation 23 | 24 | 25 | 26 | com.fasterxml.jackson.core 27 | jackson-annotations 28 | ${jackson-annotations.version} 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | org.springframework.boot 37 | spring-boot-maven-plugin 38 | 39 | true 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/security/JWTAuthenticationManager.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.security; 2 | 3 | import com.auth0.jwt.exceptions.JWTDecodeException; 4 | import com.auth0.jwt.exceptions.TokenExpiredException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.security.authentication.ReactiveAuthenticationManager; 7 | import org.springframework.security.core.Authentication; 8 | import org.springframework.stereotype.Component; 9 | import pl.kubaretip.authutils.jwt.JWTUtils; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Slf4j 13 | @Component 14 | public class JWTAuthenticationManager implements ReactiveAuthenticationManager { 15 | 16 | private JWTUtils jwtUtils; 17 | 18 | public JWTAuthenticationManager(JWTUtils jwtUtils) { 19 | this.jwtUtils = jwtUtils; 20 | } 21 | 22 | @Override 23 | public Mono authenticate(Authentication authentication) { 24 | 25 | return Mono.just(authentication) 26 | .map(Authentication::getCredentials) 27 | .cast(String.class) 28 | .flatMap(token -> { 29 | log.debug("Authenticate {}" , token); 30 | try { 31 | return Mono.just(jwtUtils.getAuthentication(token)); 32 | } catch (JWTDecodeException | TokenExpiredException ex) { 33 | log.error("Decode token exception"); 34 | return Mono.empty(); 35 | } 36 | }); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/repository/FriendRequestRepository.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.repository; 2 | 3 | import org.springframework.data.jpa.repository.JpaRepository; 4 | import org.springframework.data.jpa.repository.Modifying; 5 | import org.springframework.data.jpa.repository.Query; 6 | import pl.kubaretip.chatservice.domain.ChatProfile; 7 | import pl.kubaretip.chatservice.domain.FriendRequest; 8 | 9 | import java.util.List; 10 | import java.util.UUID; 11 | 12 | public interface FriendRequestRepository extends JpaRepository { 13 | 14 | @Query("SELECT " + 15 | "CASE WHEN COUNT(fr) > 0 THEN true ELSE false END " + 16 | "FROM FriendRequest fr WHERE (fr.sender = :user1 AND fr.recipient = :user2) " + 17 | "OR (fr.sender = :user2 AND fr.recipient = :user1)") 18 | boolean isFriendRequestAlreadyExists(ChatProfile user1, ChatProfile user2); 19 | 20 | @Query("SELECT fr FROM FriendRequest fr WHERE fr.recipient.userId = :recipientId AND fr.isAccepted = false") 21 | List findAllByRecipientIdAndNotAccepted(UUID recipientId); 22 | 23 | @Query("SELECT fr FROM FriendRequest fr WHERE fr.sender.userId = :senderId AND fr.isAccepted = false") 24 | List findAllBySenderIdAndNotAccepted(UUID senderId); 25 | 26 | @Modifying 27 | @Query("DELETE FROM FriendRequest WHERE (sender = :sender AND recipient = :recipient) " + 28 | "OR (sender = :recipient AND recipient = :sender)") 29 | void deleteFriendRequestByChatProfiles(ChatProfile sender, ChatProfile recipient); 30 | 31 | } 32 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 5 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 6 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 7 | import org.springframework.security.config.http.SessionCreationPolicy; 8 | import pl.kubaretip.authutils.jwt.JWTConfig; 9 | import pl.kubaretip.authutils.jwt.JWTUtils; 10 | 11 | @EnableWebSecurity 12 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 13 | 14 | @Override 15 | protected void configure(HttpSecurity http) throws Exception { 16 | // @formatter:off 17 | 18 | http 19 | .cors() 20 | .and() 21 | .csrf() 22 | .disable() 23 | .sessionManagement() 24 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 25 | .and() 26 | .httpBasic() 27 | .disable() 28 | .authorizeRequests() 29 | .mvcMatchers("/ws").permitAll() 30 | .anyRequest() 31 | .authenticated(); 32 | 33 | // @formatter:on 34 | } 35 | 36 | @Bean 37 | public JWTConfig jwtConfig() { 38 | return new JWTConfig(); 39 | } 40 | 41 | @Bean 42 | public JWTUtils jwtUtils() { 43 | return new JWTUtils(jwtConfig()); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/security/AuthenticationSuccessHandler.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.security; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.http.MediaType; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationSuccessHandler; 7 | import org.springframework.stereotype.Component; 8 | import pl.kubaretip.authservice.security.model.TokenResponse; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.nio.charset.StandardCharsets; 15 | 16 | @Component 17 | public class AuthenticationSuccessHandler extends SimpleUrlAuthenticationSuccessHandler { 18 | 19 | private final JWTBuilder jwtBuilder; 20 | 21 | public AuthenticationSuccessHandler(JWTBuilder jwtBuilder) { 22 | this.jwtBuilder = jwtBuilder; 23 | } 24 | 25 | @Override 26 | public void onAuthenticationSuccess(HttpServletRequest request, HttpServletResponse response, Authentication authentication) throws IOException, ServletException { 27 | var out = response.getWriter(); 28 | response.setContentType(MediaType.APPLICATION_JSON_VALUE); 29 | response.setCharacterEncoding(StandardCharsets.UTF_8.name()); 30 | var token = jwtBuilder.buildToken(authentication); 31 | var tokenJson = new ObjectMapper().writeValueAsString(new TokenResponse(token)); 32 | out.print(tokenJson); 33 | out.flush(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/security/JWTBuilder.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.security; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import org.springframework.security.core.Authentication; 6 | import org.springframework.security.core.GrantedAuthority; 7 | import pl.kubaretip.authutils.jwt.JWTConfig; 8 | import pl.kubaretip.authutils.security.SecurityUserDetailsImpl; 9 | 10 | 11 | import java.util.Date; 12 | import java.util.stream.Collectors; 13 | 14 | import static pl.kubaretip.authutils.jwt.JWTConstants.*; 15 | 16 | public class JWTBuilder { 17 | 18 | private final long tokenValidityTimeInMilliseconds; 19 | private final Algorithm sign; 20 | 21 | public JWTBuilder(JWTConfig jwtConfig) { 22 | this.tokenValidityTimeInMilliseconds = jwtConfig.getExpiration() * 1000L; 23 | this.sign = Algorithm.HMAC512(jwtConfig.getSecret()); 24 | } 25 | 26 | public String buildToken(Authentication authentication) { 27 | var user = (SecurityUserDetailsImpl) authentication.getPrincipal(); 28 | var authorities = user.getAuthorities() 29 | .stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList()); 30 | var expiresAt = new Date(System.currentTimeMillis() + this.tokenValidityTimeInMilliseconds); 31 | 32 | return JWT.create() 33 | .withSubject(user.getId()) 34 | .withExpiresAt(expiresAt) 35 | .withClaim(USERNAME_KEY, user.getUsername()) 36 | .withClaim(AUTHORITIES_KEY, authorities) 37 | .sign(sign); 38 | } 39 | 40 | 41 | } 42 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/security/AuthenticationFailureHandler.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.security; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.MediaType; 6 | import org.springframework.security.core.AuthenticationException; 7 | import org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler; 8 | import pl.kubaretip.exceptionutils.error.Error; 9 | 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | import java.nio.charset.StandardCharsets; 15 | 16 | public class AuthenticationFailureHandler extends SimpleUrlAuthenticationFailureHandler { 17 | 18 | @Override 19 | public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { 20 | response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); 21 | response.setContentType(MediaType.APPLICATION_JSON_VALUE); 22 | response.setCharacterEncoding(StandardCharsets.UTF_8.name()); 23 | var out = response.getWriter(); 24 | 25 | var error = Error.builder() 26 | .status(HttpStatus.UNAUTHORIZED.value()) 27 | .detail(exception.getMessage()) 28 | .title(HttpStatus.UNAUTHORIZED.getReasonPhrase()) 29 | .build(); 30 | 31 | var jsonErrorResponse = new ObjectMapper().writeValueAsString(error); 32 | out.print(jsonErrorResponse); 33 | out.flush(); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /auth-utils/src/main/java/pl/kubaretip/authutils/jwt/JWTFilter.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authutils.jwt; 2 | 3 | import com.auth0.jwt.exceptions.JWTDecodeException; 4 | import com.auth0.jwt.exceptions.TokenExpiredException; 5 | import lombok.extern.slf4j.Slf4j; 6 | import org.springframework.security.core.context.SecurityContextHolder; 7 | import org.springframework.web.filter.OncePerRequestFilter; 8 | 9 | import javax.servlet.FilterChain; 10 | import javax.servlet.ServletException; 11 | import javax.servlet.http.HttpServletRequest; 12 | import javax.servlet.http.HttpServletResponse; 13 | import java.io.IOException; 14 | 15 | @Slf4j 16 | public class JWTFilter extends OncePerRequestFilter { 17 | 18 | private final JWTUtils jwtUtils; 19 | 20 | public JWTFilter(JWTUtils jwtUtils) { 21 | this.jwtUtils = jwtUtils; 22 | } 23 | 24 | @Override 25 | protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws IOException, ServletException { 26 | var authorizationHeaderValue = request.getHeader(jwtUtils.getJwtConfig().getHeader()); 27 | if (jwtUtils.isValidAuthorizationHeaderValue(authorizationHeaderValue)) { 28 | var token = authorizationHeaderValue.replace(jwtUtils.getJwtConfig().getTokenPrefix(), ""); 29 | try { 30 | var authentication = jwtUtils.getAuthentication(token); 31 | SecurityContextHolder.getContext().setAuthentication(authentication); 32 | } catch (JWTDecodeException | TokenExpiredException ex) { 33 | SecurityContextHolder.clearContext(); 34 | } 35 | } 36 | chain.doFilter(request, response); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /dto-models/src/main/java/pl/kubaretip/dtomodels/UserDTO.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.dtomodels; 2 | 3 | import com.fasterxml.jackson.annotation.JsonInclude; 4 | import com.fasterxml.jackson.annotation.JsonProperty; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.Setter; 8 | 9 | import javax.validation.constraints.Email; 10 | import javax.validation.constraints.NotBlank; 11 | import javax.validation.constraints.Pattern; 12 | import javax.validation.constraints.Size; 13 | import java.io.Serializable; 14 | 15 | @JsonInclude(JsonInclude.Include.NON_NULL) 16 | @Getter 17 | @Setter 18 | @NoArgsConstructor 19 | public class UserDTO implements Serializable { 20 | 21 | private String id; 22 | 23 | @NotBlank 24 | @Size(min = 4, max = 50) 25 | @Pattern(regexp = "^[a-zA-Z0-9]*$") 26 | private String username; 27 | 28 | @JsonProperty(value = "password", access = JsonProperty.Access.WRITE_ONLY) 29 | @NotBlank 30 | @Size(min = 6, max = 32) 31 | private String password; 32 | 33 | @Pattern(regexp = "^[a-zA-Z]*$") 34 | @Size(min = 2, max = 50) 35 | @NotBlank 36 | private String firstName; 37 | 38 | @Pattern(regexp = "^[a-zA-Z]*$") 39 | @Size(min = 2, max = 50) 40 | @NotBlank 41 | private String lastName; 42 | 43 | @Email 44 | @NotBlank 45 | private String email; 46 | 47 | private String activationKey; 48 | 49 | 50 | public UserDTO(String username, String password, String firstName, String lastName, String email) { 51 | this.username = username; 52 | this.password = password; 53 | this.firstName = firstName; 54 | this.lastName = lastName; 55 | this.email = email; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/security/AuthChannelInterceptor.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.security; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.messaging.Message; 6 | import org.springframework.messaging.MessageChannel; 7 | import org.springframework.messaging.simp.stomp.StompCommand; 8 | import org.springframework.messaging.simp.stomp.StompHeaderAccessor; 9 | import org.springframework.messaging.support.ChannelInterceptor; 10 | import org.springframework.messaging.support.MessageHeaderAccessor; 11 | import org.springframework.stereotype.Component; 12 | 13 | @Slf4j 14 | @Component 15 | public class AuthChannelInterceptor implements ChannelInterceptor { 16 | 17 | private final WebSocketAuthService webSocketAuthService; 18 | 19 | public AuthChannelInterceptor(WebSocketAuthService webSocketAuthService) { 20 | this.webSocketAuthService = webSocketAuthService; 21 | } 22 | 23 | @Override 24 | public Message preSend(Message message, MessageChannel channel) { 25 | var accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class); 26 | 27 | if (accessor != null && StompCommand.CONNECT.equals(accessor.getCommand())) { 28 | var authorizationHeaderValue = (String) accessor.getFirstNativeHeader(HttpHeaders.AUTHORIZATION); 29 | final var authenticationToken = webSocketAuthService.attemptAuthentication(authorizationHeaderValue); 30 | 31 | if (authenticationToken != null) 32 | accessor.setUser(authenticationToken); 33 | } 34 | 35 | return message; 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /exception-utils/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | exception-utils 13 | 14 | 4.0.1 15 | 16 | 17 | 18 | 19 | com.fasterxml.jackson.core 20 | jackson-annotations 21 | 22 | 23 | org.springframework 24 | spring-web 25 | 26 | 27 | javax.servlet 28 | javax.servlet-api 29 | ${javax.servlet-api.version} 30 | provided 31 | 32 | 33 | 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-maven-plugin 39 | 40 | true 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/security/JWTAuthenticationFilter.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.security; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.core.AuthenticationException; 8 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 9 | import pl.kubaretip.authservice.exception.InvalidAuthenticationRequestException; 10 | import pl.kubaretip.authservice.security.model.AuthRequestModel; 11 | 12 | import javax.servlet.http.HttpServletRequest; 13 | import javax.servlet.http.HttpServletResponse; 14 | import java.io.IOException; 15 | 16 | @Slf4j 17 | public class JWTAuthenticationFilter extends UsernamePasswordAuthenticationFilter { 18 | 19 | @Override 20 | public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException { 21 | AuthRequestModel authenticationRequest; 22 | try { 23 | authenticationRequest = new ObjectMapper().readValue(request.getInputStream(), AuthRequestModel.class); 24 | } catch (IOException e) { 25 | throw new InvalidAuthenticationRequestException("Invalid login request"); 26 | } 27 | log.debug("Attempt authentication user: " + authenticationRequest.getUsername()); 28 | var authentication = new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()); 29 | return this.getAuthenticationManager().authenticate(authentication); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/security/JWTAuthenticationConverter.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.security; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.http.HttpHeaders; 5 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; 8 | import org.springframework.stereotype.Component; 9 | import org.springframework.web.server.ServerWebExchange; 10 | import pl.kubaretip.authutils.jwt.JWTUtils; 11 | import reactor.core.publisher.Mono; 12 | 13 | @Slf4j 14 | @Component 15 | public class JWTAuthenticationConverter implements ServerAuthenticationConverter { 16 | 17 | private final JWTUtils jwtUtils; 18 | 19 | public JWTAuthenticationConverter(JWTUtils jwtUtils) { 20 | this.jwtUtils = jwtUtils; 21 | } 22 | 23 | @Override 24 | public Mono convert(ServerWebExchange exchange) { 25 | 26 | return Mono.justOrEmpty(exchange.getRequest().getHeaders()) 27 | .flatMap(headers -> Mono.justOrEmpty(headers.getFirst(HttpHeaders.AUTHORIZATION))) 28 | .flatMap(authHeaderValue -> { 29 | if (jwtUtils.isValidAuthorizationHeaderValue(authHeaderValue)) { 30 | var token = authHeaderValue.replace(jwtUtils.getJwtConfig().getTokenPrefix(), ""); 31 | log.debug("Token {}", token); 32 | return Mono.just(new UsernamePasswordAuthenticationToken(token, token)); 33 | } else 34 | return Mono.empty(); 35 | }); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /auth-service/src/test/java/pl/kubaretip/authservice/utils/SpringSecurityWebTestConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.utils; 2 | 3 | import org.springframework.boot.test.context.TestConfiguration; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Primary; 6 | import org.springframework.security.core.userdetails.UserDetailsService; 7 | import pl.kubaretip.authservice.domain.Authority; 8 | import pl.kubaretip.authservice.domain.User; 9 | import pl.kubaretip.authservice.repository.UserRepository; 10 | import pl.kubaretip.authservice.security.UserDetailsServiceImpl; 11 | 12 | import java.util.Collections; 13 | import java.util.Optional; 14 | import java.util.UUID; 15 | 16 | import static org.mockito.ArgumentMatchers.anyString; 17 | import static org.mockito.BDDMockito.given; 18 | import static org.mockito.Mockito.mock; 19 | 20 | 21 | @TestConfiguration 22 | public class SpringSecurityWebTestConfig { 23 | 24 | @Bean 25 | @Primary 26 | public UserDetailsService customUserDetailsService() { 27 | 28 | var userRepositoryMock = mock(UserRepository.class); 29 | 30 | var user = new User(); 31 | user.setId(UUID.fromString("1062d618-64f6-401c-ab7c-ef050eb6f4b2")); 32 | user.setUsername("testUser"); 33 | user.setFirstName("userFirstName"); 34 | user.setLastName("userLastName"); 35 | user.setEnabled(true); 36 | user.setPassword("password"); 37 | var authority = new Authority(); 38 | authority.setName("ROLE_USER"); 39 | user.setAuthorities(Collections.singleton(authority)); 40 | 41 | given(userRepositoryMock.findOneWithAuthoritiesByUsernameIgnoreCase(anyString())).willReturn(Optional.of(user)); 42 | 43 | return new UserDetailsServiceImpl(userRepositoryMock); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/security/UserDetailsServiceImpl.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.security; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 5 | import org.springframework.security.core.userdetails.UserDetails; 6 | import org.springframework.security.core.userdetails.UserDetailsService; 7 | import org.springframework.security.core.userdetails.UsernameNotFoundException; 8 | import org.springframework.stereotype.Service; 9 | import pl.kubaretip.authservice.repository.UserRepository; 10 | import pl.kubaretip.authutils.security.SecurityUserDetailsImpl; 11 | 12 | import javax.transaction.Transactional; 13 | import java.util.stream.Collectors; 14 | 15 | @Slf4j 16 | @Service 17 | public class UserDetailsServiceImpl implements UserDetailsService { 18 | 19 | private final UserRepository userRepository; 20 | 21 | public UserDetailsServiceImpl(UserRepository userRepository) { 22 | this.userRepository = userRepository; 23 | } 24 | 25 | @Transactional 26 | @Override 27 | public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException { 28 | log.debug("Fetching given user {}", username); 29 | 30 | return userRepository.findOneWithAuthoritiesByUsernameIgnoreCase(username) 31 | .map(user -> new SecurityUserDetailsImpl(user.getId().toString(), 32 | user.getUsername(), user.getPassword(), user.getEnabled(), 33 | user.getAuthorities().stream() 34 | .map(authority -> new SimpleGrantedAuthority(authority.getName())) 35 | .collect(Collectors.toList())) 36 | ).orElseThrow(() -> new UsernameNotFoundException("User " + username + " not found")); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /cloud-gateway/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | cloud-gateway 13 | 14 | 15 | 16 | 17 | org.springframework.cloud 18 | spring-cloud-starter-gateway 19 | 20 | 21 | 22 | org.springframework.cloud 23 | spring-cloud-starter-netflix-eureka-client 24 | 25 | 26 | 27 | org.springframework.cloud 28 | spring-cloud-starter-config 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-actuator 34 | 35 | 36 | 37 | org.springframework.boot 38 | spring-boot-starter-aop 39 | 40 | 41 | 42 | org.springframework.retry 43 | spring-retry 44 | 45 | 46 | 47 | org.springdoc 48 | springdoc-openapi-webflux-ui 49 | 50 | 51 | 52 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/web/rest/ChatProfileController.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.web.rest; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.web.bind.annotation.*; 5 | import pl.kubaretip.authutils.SecurityUtils; 6 | import pl.kubaretip.chatservice.dto.ChatProfileDTO; 7 | import pl.kubaretip.chatservice.dto.mapper.ChatProfileMapper; 8 | import pl.kubaretip.chatservice.service.ChatProfileService; 9 | import pl.kubaretip.exceptionutils.InvalidDataException; 10 | 11 | @RestController 12 | @RequestMapping("/chat-profiles") 13 | public class ChatProfileController { 14 | 15 | private final ChatProfileService chatProfileService; 16 | private final ChatProfileMapper chatProfileMapper; 17 | 18 | public ChatProfileController(ChatProfileService chatProfileService, 19 | ChatProfileMapper chatProfileMapper) { 20 | this.chatProfileService = chatProfileService; 21 | this.chatProfileMapper = chatProfileMapper; 22 | } 23 | 24 | @GetMapping("/{id}") 25 | public ResponseEntity getChatProfileById(@PathVariable("id") String id) { 26 | return ResponseEntity.ok() 27 | .body(chatProfileMapper.chatProfileToChatProfileDTO(chatProfileService.getChatProfileById(id))); 28 | } 29 | 30 | @PatchMapping("/{id}/new-friends-request-code") 31 | public ResponseEntity generateNewFriendsRequestCode(@PathVariable("id") String userId) { 32 | 33 | if (!userId.equals(SecurityUtils.getCurrentUser())) { 34 | throw new InvalidDataException("Invalid user id"); 35 | } 36 | var chatProfile = chatProfileService.generateNewFriendsRequestCode(userId, SecurityUtils.getCurrentUserPreferredUsername()); 37 | return ResponseEntity.ok() 38 | .body(chatProfileMapper.chatProfileToChatProfileDTO(chatProfile)); 39 | } 40 | 41 | 42 | } 43 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/dto/mapper/FriendRequestMapper.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.dto.mapper; 2 | 3 | import org.mapstruct.*; 4 | import pl.kubaretip.chatservice.domain.FriendRequest; 5 | import pl.kubaretip.chatservice.dto.FriendRequestDTO; 6 | import pl.kubaretip.chatservice.util.DateUtils; 7 | 8 | import java.time.OffsetDateTime; 9 | import java.time.format.DateTimeFormatter; 10 | import java.util.List; 11 | 12 | 13 | @Mapper(componentModel = "spring", uses = ChatProfileMapper.class) 14 | public interface FriendRequestMapper { 15 | 16 | @Mapping(target = "sender", qualifiedByName = "chatProfileToChatProfileDTO") 17 | @Mapping(target = "recipient", qualifiedByName = "chatProfileToChatProfileDTO") 18 | @Mapping(target = "sentTime", expression = "java(convertOffsetDateToString(friendRequest.getSentTime()))") 19 | FriendRequestDTO mapToFriendRequestDTO(FriendRequest friendRequest); 20 | 21 | @Named("mapWithoutRecipient") 22 | @InheritConfiguration(name = "mapToFriendRequestDTO") 23 | @Mapping(target = "recipient", ignore = true) 24 | FriendRequestDTO mapToFriendRequestDTOWithoutRecipient(FriendRequest friendRequest); 25 | 26 | @Named("mapWithoutSender") 27 | @InheritConfiguration(name = "mapToFriendRequestDTO") 28 | @Mapping(target = "sender", ignore = true) 29 | FriendRequestDTO mapToFriendRequestDTOWithoutSender(FriendRequest friendRequest); 30 | 31 | @IterableMapping(qualifiedByName = "mapWithoutRecipient") 32 | List mapToFriendRequestDTOListWithoutRecipient(List friendRequests); 33 | 34 | @IterableMapping(qualifiedByName = "mapWithoutSender") 35 | List mapToFriendRequestDTOListWithoutSender(List friendRequests); 36 | 37 | 38 | default String convertOffsetDateToString(OffsetDateTime time) { 39 | return time.format(DateTimeFormatter.ofPattern(DateUtils.DATE_PATTERN)); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /exception-utils/src/main/java/pl/kubaretip/exceptionutils/CommonExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.exceptionutils; 2 | 3 | import org.springframework.http.HttpStatus; 4 | import org.springframework.web.bind.annotation.ExceptionHandler; 5 | import org.springframework.web.bind.annotation.ResponseStatus; 6 | import pl.kubaretip.exceptionutils.error.Error; 7 | 8 | import javax.servlet.http.HttpServletRequest; 9 | 10 | public interface CommonExceptionHandler { 11 | 12 | @ExceptionHandler(value = {NotFoundException.class}) 13 | @ResponseStatus(HttpStatus.NOT_FOUND) 14 | default Error handleNotFound(final NotFoundException ex, HttpServletRequest request) { 15 | return Error.builder() 16 | .status(HttpStatus.NOT_FOUND.value()) 17 | .detail(ex.getMessage()) 18 | .path(request.getRequestURI()) 19 | .title(HttpStatus.NOT_FOUND.getReasonPhrase()) 20 | .build(); 21 | } 22 | 23 | @ExceptionHandler(value = {AlreadyExistsException.class}) 24 | @ResponseStatus(HttpStatus.CONFLICT) 25 | default Error handleAlreadyExists(final AlreadyExistsException ex, HttpServletRequest request) { 26 | return Error.builder() 27 | .status(HttpStatus.CONFLICT.value()) 28 | .detail(ex.getMessage()) 29 | .path(request.getRequestURI()) 30 | .title(HttpStatus.CONFLICT.getReasonPhrase()) 31 | .build(); 32 | } 33 | 34 | @ExceptionHandler(value = {InvalidDataException.class}) 35 | @ResponseStatus(HttpStatus.BAD_REQUEST) 36 | default Error handleInvalidData(final InvalidDataException ex, HttpServletRequest request) { 37 | return Error.builder() 38 | .status(HttpStatus.BAD_REQUEST.value()) 39 | .detail(ex.getMessage()) 40 | .path(request.getRequestURI()) 41 | .title(HttpStatus.BAD_REQUEST.getReasonPhrase()) 42 | .build(); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /cloud-gateway/src/main/java/pl/kubaretip/cloudgateway/config/CorsConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.cloudgateway.config; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.web.cors.CorsConfiguration; 7 | import org.springframework.web.cors.reactive.CorsWebFilter; 8 | import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource; 9 | import org.springframework.web.reactive.config.CorsRegistry; 10 | import org.springframework.web.reactive.config.EnableWebFlux; 11 | import org.springframework.web.reactive.config.WebFluxConfigurer; 12 | 13 | import java.util.Arrays; 14 | 15 | @EnableWebFlux 16 | @Configuration 17 | public class CorsConfig implements WebFluxConfigurer { 18 | 19 | 20 | @Value("${cors.allowed-origins:*}") 21 | private String[] allowedOrigins; 22 | 23 | @Value("${cors.allowed-headers:*}") 24 | private String[] allowedHeaders; 25 | 26 | @Value("${allowed-methods:*}") 27 | private String[] allowedMethods; 28 | 29 | @Override 30 | public void addCorsMappings(CorsRegistry registry) { 31 | registry.addMapping("/**") 32 | .allowedOrigins(allowedOrigins) 33 | .allowedHeaders(allowedHeaders) 34 | .allowedMethods(allowedMethods); 35 | } 36 | 37 | @Bean 38 | public CorsWebFilter corsWebFilter() { 39 | var corsConfiguration = new CorsConfiguration(); 40 | corsConfiguration.setAllowedHeaders(Arrays.asList(allowedHeaders)); 41 | corsConfiguration.setAllowedMethods(Arrays.asList(allowedMethods)); 42 | corsConfiguration.setAllowedOrigins(Arrays.asList(allowedOrigins)); 43 | 44 | var corsConfigurationSource = new UrlBasedCorsConfigurationSource(); 45 | corsConfigurationSource.registerCorsConfiguration("/**", corsConfiguration); 46 | 47 | return new CorsWebFilter(corsConfigurationSource); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /auth-utils/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | jar 12 | auth-utils 13 | 14 | 15 | 5.4.5 16 | 4.0.1 17 | 18 | 19 | 20 | 21 | 22 | javax.servlet 23 | javax.servlet-api 24 | ${javax.servlet-api.version} 25 | provided 26 | 27 | 28 | 29 | com.auth0 30 | java-jwt 31 | 32 | 33 | 34 | org.springframework.security 35 | spring-security-core 36 | 37 | 38 | 39 | org.springframework 40 | spring-web 41 | 42 | 43 | 44 | org.slf4j 45 | slf4j-api 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-maven-plugin 55 | 56 | true 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /auth-utils/src/main/java/pl/kubaretip/authutils/security/SecurityUserDetailsImpl.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authutils.security; 2 | 3 | import org.springframework.security.core.GrantedAuthority; 4 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 5 | 6 | import java.util.Collection; 7 | import java.util.List; 8 | 9 | public class SecurityUserDetailsImpl implements SecurityUserDetails { 10 | 11 | private final String username; 12 | private final String password; 13 | private final String id; 14 | private final Collection authorities; 15 | private final boolean enabled; 16 | 17 | public SecurityUserDetailsImpl(String id, String username, String password, boolean enabled, List authorities) { 18 | this.username = username; 19 | this.password = password; 20 | this.id = id; 21 | this.authorities = authorities; 22 | this.enabled = enabled; 23 | } 24 | 25 | public SecurityUserDetailsImpl(String id, String username, String password, Collection authorities) { 26 | this.username = username; 27 | this.password = password; 28 | this.id = id; 29 | this.authorities = authorities; 30 | this.enabled = true; 31 | } 32 | 33 | @Override 34 | public Collection getAuthorities() { 35 | return this.authorities; 36 | } 37 | 38 | @Override 39 | public String getPassword() { 40 | return this.password; 41 | } 42 | 43 | @Override 44 | public String getUsername() { 45 | return this.username; 46 | } 47 | 48 | @Override 49 | public boolean isAccountNonExpired() { 50 | return true; 51 | } 52 | 53 | @Override 54 | public boolean isAccountNonLocked() { 55 | return true; 56 | } 57 | 58 | @Override 59 | public boolean isCredentialsNonExpired() { 60 | return true; 61 | } 62 | 63 | @Override 64 | public boolean isEnabled() { 65 | return this.enabled; 66 | } 67 | 68 | @Override 69 | public String getId() { 70 | return id; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /auth-utils/src/main/java/pl/kubaretip/authutils/jwt/JWTUtils.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authutils.jwt; 2 | 3 | import com.auth0.jwt.JWT; 4 | import com.auth0.jwt.algorithms.Algorithm; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; 7 | import org.springframework.security.core.authority.SimpleGrantedAuthority; 8 | import pl.kubaretip.authutils.security.SecurityUserDetailsImpl; 9 | 10 | import java.util.stream.Collectors; 11 | 12 | import static pl.kubaretip.authutils.jwt.JWTConstants.*; 13 | 14 | public class JWTUtils { 15 | 16 | private final Algorithm sign; 17 | private final JWTConfig jwtConfig; 18 | 19 | public JWTUtils(JWTConfig jwtConfig) { 20 | this.jwtConfig = jwtConfig; 21 | this.sign = Algorithm.HMAC512(jwtConfig.getSecret()); 22 | } 23 | 24 | public boolean isValidAuthorizationHeaderValue(String authHeaderValue) { 25 | return StringUtils.isNotBlank(authHeaderValue) 26 | && authHeaderValue.contains(jwtConfig.getTokenPrefix()) 27 | && StringUtils.isNotBlank(authHeaderValue.replace(jwtConfig.getTokenPrefix(), "")); 28 | } 29 | 30 | public UsernamePasswordAuthenticationToken getAuthentication(String token) { 31 | 32 | var decodedJWT = JWT 33 | .require(sign) 34 | .build() 35 | .verify(token); 36 | 37 | if (decodedJWT != null) { 38 | var authorities = decodedJWT.getClaim(AUTHORITIES_KEY) 39 | .asList(String.class) 40 | .stream() 41 | .map(SimpleGrantedAuthority::new) 42 | .collect(Collectors.toList()); 43 | 44 | var userId = decodedJWT.getSubject(); 45 | var username = decodedJWT.getClaim(USERNAME_KEY).asString(); 46 | var securityUser = new SecurityUserDetailsImpl(userId, username, "", authorities); 47 | 48 | return new UsernamePasswordAuthenticationToken(securityUser, token, authorities); 49 | 50 | } 51 | return null; 52 | } 53 | 54 | public JWTConfig getJwtConfig() { 55 | return jwtConfig; 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/web/rest/FriendChatController.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.web.rest; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.web.bind.annotation.*; 5 | import pl.kubaretip.authutils.SecurityUtils; 6 | import pl.kubaretip.chatservice.dto.FriendChatDTO; 7 | import pl.kubaretip.chatservice.dto.mapper.FriendChatMapper; 8 | import pl.kubaretip.chatservice.messaging.sender.DeleteMessagesSender; 9 | import pl.kubaretip.chatservice.service.FriendChatService; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/friends-chats") 15 | public class FriendChatController { 16 | 17 | private final FriendChatService friendChatService; 18 | private final FriendChatMapper friendChatMapper; 19 | private final DeleteMessagesSender deleteMessagesSender; 20 | 21 | public FriendChatController(FriendChatService friendChatService, 22 | FriendChatMapper friendChatMapper, 23 | DeleteMessagesSender deleteMessagesSender) { 24 | this.friendChatService = friendChatService; 25 | this.friendChatMapper = friendChatMapper; 26 | this.deleteMessagesSender = deleteMessagesSender; 27 | } 28 | 29 | @GetMapping 30 | public ResponseEntity> getAllFriendsChats() { 31 | 32 | var allFriendsChatsBySender = friendChatService.getAllFriendsChatsBySender(SecurityUtils.getCurrentUser()); 33 | 34 | return ResponseEntity.ok() 35 | .body(friendChatMapper.mapToFriendChatList(allFriendsChatsBySender)); 36 | } 37 | 38 | @DeleteMapping(params = {"friend_chat", "friend_chat_with"}) 39 | public ResponseEntity deleteUserFriendChat(@RequestParam("friend_chat") long friendChatId, 40 | @RequestParam("friend_chat_with") long friendChatWithId) { 41 | friendChatService.deleteFriendChat(friendChatId, friendChatWithId, SecurityUtils.getCurrentUser()); 42 | deleteMessagesSender.sendDeletingMessagesTask(List.of(friendChatId, friendChatWithId)); 43 | return ResponseEntity.noContent().build(); 44 | } 45 | 46 | 47 | } 48 | -------------------------------------------------------------------------------- /cloud-gateway/src/main/java/pl/kubaretip/cloudgateway/config/CloudConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.cloudgateway.config; 2 | 3 | import org.springframework.cloud.gateway.route.RouteLocator; 4 | import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder; 5 | import org.springframework.context.annotation.Bean; 6 | import org.springframework.context.annotation.Configuration; 7 | 8 | @Configuration 9 | public class CloudConfig { 10 | 11 | @Bean 12 | public RouteLocator gatewayRoutes(RouteLocatorBuilder builder) { 13 | 14 | return builder.routes() 15 | .route(predicateSpec -> predicateSpec.path("/auth-service/**") 16 | .filters(f -> f.rewritePath("/auth-service/(?.*)", "/${remaining}")) 17 | .uri("lb://AUTH") 18 | .id("AUTH") 19 | ) 20 | .route(predicateSpec -> predicateSpec.path("/chat-service/**") 21 | .filters(f -> f.rewritePath("/chat-service/(?.*)", "/${remaining}")) 22 | .uri("lb://CHAT") 23 | .id("CHAT") 24 | ) 25 | .route(predicateSpec -> predicateSpec.path("/chat-messages-service/**") 26 | .filters(f -> f.rewritePath("/chat-messages-service/(?.*)", "/${remaining}")) 27 | .uri("lb://CHAT-MESSAGES") 28 | .id("CHAT-MESSAGES") 29 | ) 30 | .route(predicateSpec -> predicateSpec.path("/messages-websocket-service/**") 31 | .filters(f -> f.rewritePath("/messages-websocket-service/(?.*)", "/${remaining}")) 32 | .uri("lb://MESSAGES-WEBSOCKET") 33 | .id("MESSAGES-WEBSOCKET") 34 | ) 35 | .route(predicateSpec -> predicateSpec.path("/v3/api-docs/**") 36 | .filters(f -> f.rewritePath("/v3/api-docs/(?.*)", "/${path}/v3/api-docs")) 37 | .uri("http://localhost:8080") 38 | .id("openapi") 39 | ) 40 | .build(); 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/web/rest/AuthorizationController.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.web.rest; 2 | 3 | import io.swagger.v3.oas.annotations.Operation; 4 | import io.swagger.v3.oas.annotations.Parameter; 5 | import io.swagger.v3.oas.annotations.media.ArraySchema; 6 | import io.swagger.v3.oas.annotations.media.Content; 7 | import io.swagger.v3.oas.annotations.media.Schema; 8 | import io.swagger.v3.oas.annotations.responses.ApiResponse; 9 | import io.swagger.v3.oas.annotations.responses.ApiResponses; 10 | import io.swagger.v3.oas.annotations.tags.Tag; 11 | import org.springframework.http.MediaType; 12 | import org.springframework.web.bind.annotation.PostMapping; 13 | import org.springframework.web.bind.annotation.RequestBody; 14 | import org.springframework.web.bind.annotation.RequestMapping; 15 | import org.springframework.web.bind.annotation.RestController; 16 | import pl.kubaretip.authservice.security.model.AuthRequestModel; 17 | import pl.kubaretip.authservice.security.model.TokenResponse; 18 | 19 | /** 20 | * Authentication API spec for Swagger doc 21 | * Implemented and automatically overridden by Spring Security filters. 22 | * Request mapping value coming from JWTConfig class 23 | * 24 | * @see pl.kubaretip.authutils.jwt.JWTConfig 25 | */ 26 | @Tag(name = "Authorization", description = "Login endpoint") 27 | @RequestMapping(value = "/authenticate", produces = MediaType.APPLICATION_JSON_VALUE) 28 | @RestController 29 | public class AuthorizationController { 30 | 31 | @Operation(summary = "Login", description = "Generating JWT tokens", tags = {"Authorization"}) 32 | @PostMapping 33 | @ApiResponses({ 34 | @ApiResponse(responseCode = "200", description = "JWT token", 35 | content = @Content(array = @ArraySchema(schema = @Schema(implementation = TokenResponse.class)))), 36 | @ApiResponse(responseCode = "401", description = "Unauthorized") 37 | }) 38 | public void fakeLogin(@Parameter(description = "Username and password", 39 | required = true, schema = @Schema(implementation = AuthRequestModel.class)) 40 | @RequestBody AuthRequestModel authRequestModel) { 41 | throw new IllegalStateException("Should not be called ! Implemented by Spring Security filters."); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /docker/docker-compose-dev.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | volumes: 4 | mysql_auth: 5 | name: auth_service_vol 6 | driver: local 7 | mongo_chat: 8 | name: chat_messages_service_vol 9 | driver: local 10 | mysql_chat: 11 | name: chat_service_vol 12 | driver: local 13 | rabbitmq: 14 | name: rabbitmq_vol 15 | driver: local 16 | 17 | services: 18 | auth_db: 19 | container_name: auth_service_mysql 20 | image: library/mysql:8.0.22 21 | environment: 22 | MYSQL_DATABASE: auth_service_database 23 | MYSQL_USER: ${AUTH_SERVICE_MYSQL_USER} 24 | MYSQL_PASSWORD: ${AUTH_SERVICE_MYSQL_PASS} 25 | MYSQL_ROOT_PASSWORD: ${AUTH_SERVICE_MYSQL_ROOT_PASS} 26 | ports: 27 | - 3306:3306 28 | volumes: 29 | - mysql_auth:/var/lib/mysql 30 | messages_db: 31 | container_name: chat_messages_service_mongo 32 | image: library/mongo:latest 33 | environment: 34 | MONGO_INITDB_DATABASE: chat_messages_service_database 35 | MONGO_INITDB_ROOT_USERNAME: ${CHAT_MESSAGES_SERVICE_ROOT_MONGO_USER} 36 | MONGO_INITDB_ROOT_PASSWORD: ${CHAT_MESSAGES_SERVICE_ROOT_MONGO_PASS} 37 | ports: 38 | - 27017:27017 39 | volumes: 40 | - mongo_chat:/var/lib/mongo 41 | - ./init-chat-messages-service-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro 42 | chat_db: 43 | container_name: chat_service_mysql 44 | image: library/mysql:8.0.22 45 | environment: 46 | MYSQL_DATABASE: chat_service_database 47 | MYSQL_USER: ${CHAT_SERVICE_MYSQL_USER} 48 | MYSQL_PASSWORD: ${CHAT_SERVICE_MYSQL_PASS} 49 | MYSQL_ROOT_PASSWORD: ${CHAT_SERVICE_MYSQL_ROOT_PASS} 50 | ports: 51 | - 3307:3306 52 | volumes: 53 | - mysql_chat:/var/lib/mysql 54 | rabbitmq: 55 | container_name: rabbitmq-service 56 | image: rabbitmq:3-management 57 | command: sh -c "rabbitmq-plugins enable rabbitmq_stomp; rabbitmq-server" 58 | ports: 59 | - 5672:5672 60 | - 15672:15672 61 | - 61613:61613 62 | volumes: 63 | - rabbitmq:/etc/rabbitmq/ 64 | - rabbitmq:/var/lib/rabbitmq/ 65 | - rabbitmq:/var/log/rabbitmq/ 66 | - ./rabbitmq-isolated.conf:/etc/rabbitmq/rabbitmq.config 67 | # Mail catcher for local debugging 68 | mailhog: 69 | image: mailhog/mailhog 70 | container_name: mailhog 71 | ports: 72 | - 1025:1025 # smtp port 73 | - 8025:8025 # web ui port -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/exception/controllerAdvice/ValidationExceptionHandler.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.exception.controllerAdvice; 2 | 3 | import org.springframework.http.HttpHeaders; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.MethodArgumentNotValidException; 7 | import org.springframework.web.bind.annotation.ControllerAdvice; 8 | import org.springframework.web.context.request.WebRequest; 9 | import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; 10 | import pl.kubaretip.exceptionutils.error.Error; 11 | import pl.kubaretip.exceptionutils.error.Violation; 12 | 13 | import java.util.ArrayList; 14 | import java.util.Collections; 15 | import java.util.HashSet; 16 | import java.util.Set; 17 | 18 | @ControllerAdvice 19 | public class ValidationExceptionHandler extends ResponseEntityExceptionHandler { 20 | 21 | @Override 22 | protected ResponseEntity handleMethodArgumentNotValid(MethodArgumentNotValidException ex, HttpHeaders headers, HttpStatus status, WebRequest request) { 23 | 24 | Set validationErrors = new HashSet<>(); 25 | ex.getBindingResult().getFieldErrors() 26 | .forEach(fieldError -> validationErrors.stream() 27 | .filter(validationError -> validationError.getField().equals(fieldError.getField())) 28 | .findAny() 29 | .ifPresentOrElse(validationError -> { 30 | validationError.getMessage().add(fieldError.getDefaultMessage()); 31 | }, 32 | () -> { 33 | validationErrors.add(new Violation(fieldError.getField(), 34 | new ArrayList<>(Collections.singletonList(fieldError.getDefaultMessage()))) 35 | ); 36 | } 37 | ) 38 | ); 39 | 40 | return ResponseEntity 41 | .badRequest() 42 | .body(Error.builder() 43 | .status(HttpStatus.BAD_REQUEST.value()) 44 | .title(HttpStatus.BAD_REQUEST.getReasonPhrase()) 45 | .violations(validationErrors) 46 | .build() 47 | ); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/web/rest/ChatMessageController.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.web.rest; 2 | 3 | import io.swagger.v3.oas.annotations.Operation; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.web.bind.annotation.*; 6 | import pl.kubaretip.chatmessagesservice.document.ChatMessage; 7 | import pl.kubaretip.chatmessagesservice.security.SecurityUtils; 8 | import pl.kubaretip.chatmessagesservice.service.ChatMessageService; 9 | import reactor.core.publisher.Flux; 10 | import reactor.core.publisher.Mono; 11 | 12 | @Slf4j 13 | @RestController 14 | @RequestMapping("/chat-messages") 15 | public class ChatMessageController { 16 | 17 | private final ChatMessageService chatMessageService; 18 | 19 | public ChatMessageController(ChatMessageService chatMessageService) { 20 | this.chatMessageService = chatMessageService; 21 | } 22 | 23 | @Operation(summary = "Get last users messages", 24 | description = "Get last messages in users friend chat.") 25 | @GetMapping 26 | public Flux getLastUsersMessagesFromTimeWithSize(@RequestParam("friend_chat_id1") long friendChatId1, 27 | @RequestParam("friend_chat_id2") long friendChatId2, 28 | @RequestParam(value = "from", required = false) String fromTime, 29 | @RequestParam("size") int numberOfMessagesToFetch) { 30 | if (fromTime != null) { 31 | return chatMessageService.findLastUsersMessagesFromTime(friendChatId1, friendChatId2, fromTime, numberOfMessagesToFetch); 32 | } else { 33 | return chatMessageService.getLastUserMessages(friendChatId1, friendChatId2, numberOfMessagesToFetch); 34 | } 35 | } 36 | 37 | @Operation(summary = "Set delivered status for messages by friend chat id.", 38 | description = "For authenticated user set delivered status for all chat messages by friend chat id.") 39 | @PatchMapping(params = "friend_chat_id") 40 | public Mono setDeliveredStatusForAllRecipientMessagesInFriendChat(@RequestParam("friend_chat_id") long friendChatId) { 41 | return SecurityUtils.getCurrentUser() 42 | .flatMap(currentUser -> chatMessageService.setDeliveredStatusForAllRecipientMessagesInFriendChat(friendChatId, currentUser)); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/service/impl/ChatProfileServiceImpl.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.service.impl; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.lang3.RandomStringUtils; 5 | import org.apache.commons.lang3.StringUtils; 6 | import org.springframework.stereotype.Service; 7 | import pl.kubaretip.chatservice.domain.ChatProfile; 8 | import pl.kubaretip.chatservice.repository.ChatProfileRepository; 9 | import pl.kubaretip.chatservice.service.ChatProfileService; 10 | import pl.kubaretip.exceptionutils.InvalidDataException; 11 | import pl.kubaretip.exceptionutils.NotFoundException; 12 | 13 | import java.util.UUID; 14 | 15 | @Slf4j 16 | @Service 17 | public class ChatProfileServiceImpl implements ChatProfileService { 18 | 19 | private final ChatProfileRepository chatProfileRepository; 20 | 21 | public ChatProfileServiceImpl(ChatProfileRepository chatProfileRepository) { 22 | this.chatProfileRepository = chatProfileRepository; 23 | } 24 | 25 | @Override 26 | public ChatProfile createChatProfile(String userId, String username) { 27 | 28 | if (!StringUtils.isNotBlank(userId)) { 29 | throw new InvalidDataException("Chat profile can't be created because user id is empty."); 30 | } 31 | if (!StringUtils.isNotBlank(username)) { 32 | throw new InvalidDataException("Chat profile can't be created because username is empty."); 33 | } 34 | 35 | var chatProfile = new ChatProfile(); 36 | chatProfile.setUserId(UUID.fromString(userId)); 37 | chatProfile.setFriendsRequestCode(generateFriendRequestCode(username)); 38 | return chatProfileRepository.save(chatProfile); 39 | } 40 | 41 | @Override 42 | public ChatProfile generateNewFriendsRequestCode(String userId, String username) { 43 | var chatProfile = getChatProfileById(userId); 44 | chatProfile.setFriendsRequestCode(generateFriendRequestCode(username)); 45 | return chatProfileRepository.save(chatProfile); 46 | } 47 | 48 | private String generateFriendRequestCode(String username) { 49 | return username.toLowerCase() + "-" + RandomStringUtils.randomNumeric(10); 50 | } 51 | 52 | @Override 53 | public ChatProfile getChatProfileById(String userId) { 54 | return chatProfileRepository.findById(UUID.fromString(userId)) 55 | .orElseThrow(() -> new NotFoundException("Not found chat profile for user " + userId)); 56 | } 57 | 58 | 59 | } 60 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.config; 2 | 3 | 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.security.config.http.SessionCreationPolicy; 10 | import org.springframework.security.web.access.AccessDeniedHandlerImpl; 11 | import org.springframework.security.web.authentication.HttpStatusEntryPoint; 12 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 13 | import pl.kubaretip.authutils.jwt.JWTConfig; 14 | import pl.kubaretip.authutils.jwt.JWTFilter; 15 | import pl.kubaretip.authutils.jwt.JWTUtils; 16 | 17 | @EnableWebSecurity 18 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 19 | 20 | private static final String[] SWAGGER_AUTH_WHITELIST = { 21 | "/v3/api-docs/**", 22 | "/swagger-resources/**", 23 | "/swagger-ui/**", 24 | "/swagger-ui.html" 25 | }; 26 | 27 | @Override 28 | protected void configure(HttpSecurity http) throws Exception { 29 | // @formatter:off 30 | 31 | http 32 | .cors() 33 | .and() 34 | .csrf() 35 | .disable() 36 | .httpBasic() 37 | .disable() 38 | .sessionManagement() 39 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 40 | .and() 41 | .authorizeRequests() 42 | .antMatchers(SWAGGER_AUTH_WHITELIST).permitAll() 43 | .anyRequest() 44 | .authenticated() 45 | .and() 46 | .exceptionHandling() 47 | .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) 48 | .accessDeniedHandler(new AccessDeniedHandlerImpl()) 49 | .and() 50 | .addFilterAfter(new JWTFilter(jwtUtils()), UsernamePasswordAuthenticationFilter.class); 51 | 52 | // @formatter:on 53 | } 54 | 55 | @Bean 56 | public JWTConfig jwtConfig() { 57 | return new JWTConfig(); 58 | } 59 | 60 | @Bean 61 | public JWTUtils jwtUtils() { 62 | return new JWTUtils(jwtConfig()); 63 | } 64 | 65 | 66 | } 67 | -------------------------------------------------------------------------------- /mail-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | mail-service 13 | 14 | 15 | 16 | 17 | 18 | pl.kubaretip 19 | dto-models 20 | 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-mail 25 | 26 | 27 | 28 | org.springframework.boot 29 | spring-boot-starter-thymeleaf 30 | 31 | 32 | 33 | org.springframework.cloud 34 | spring-cloud-starter-config 35 | 36 | 37 | 38 | org.springframework.cloud 39 | spring-cloud-starter-netflix-eureka-client 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-actuator 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-web 50 | 51 | 52 | 53 | org.springframework.boot 54 | spring-boot-starter-amqp 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-test 60 | test 61 | 62 | 63 | 64 | org.springframework.amqp 65 | spring-rabbit-test 66 | test 67 | 68 | 69 | 70 | 71 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/domain/User.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.domain; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import org.hibernate.annotations.GenericGenerator; 8 | 9 | import javax.persistence.*; 10 | import javax.validation.constraints.NotBlank; 11 | import javax.validation.constraints.Size; 12 | import java.util.HashSet; 13 | import java.util.Set; 14 | import java.util.UUID; 15 | 16 | @Getter 17 | @Setter 18 | @NoArgsConstructor 19 | @Table(schema = "auth_service_database") 20 | @Entity 21 | public class User { 22 | 23 | @Id 24 | @GeneratedValue(generator = "uuid2") 25 | @GenericGenerator(name = "uuid2", strategy = "uuid2") 26 | @Column(columnDefinition = "BINARY(16)") 27 | private UUID id; 28 | 29 | @Column(length = 50, unique = true, nullable = false) 30 | private String username; 31 | 32 | @JsonIgnore 33 | @Column(name = "password_hash", nullable = false, length = 60) 34 | private String password; 35 | 36 | @Column(name = "first_name", length = 50, nullable = false) 37 | private String firstName; 38 | 39 | @Column(name = "last_name", length = 50, nullable = false) 40 | private String lastName; 41 | 42 | @Column(name = "activation_key", length = 124, unique = true) 43 | private String activationKey; 44 | 45 | @Column(length = 100, unique = true, nullable = false) 46 | private String email; 47 | 48 | @Column(nullable = false) 49 | private Boolean enabled = false; 50 | 51 | @ManyToMany 52 | @JoinTable( 53 | name = "user_authority", 54 | schema = "auth_service_database", 55 | joinColumns = {@JoinColumn(name = "user_id", referencedColumnName = "id")}, 56 | inverseJoinColumns = {@JoinColumn(name = "authority_name", referencedColumnName = "name")} 57 | ) 58 | private Set authorities = new HashSet<>(); 59 | 60 | 61 | @Override 62 | public String toString() { 63 | return "User{" + 64 | "id=" + id.toString() + 65 | ", username='" + username + '\'' + 66 | ", firstName='" + firstName + '\'' + 67 | ", lastName='" + lastName + '\'' + 68 | ", activationKey='" + activationKey + '\'' + 69 | ", email='" + email + '\'' + 70 | ", enabled=" + enabled + 71 | ", authorities=" + authorities + 72 | '}'; 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/config/RabbitMQConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.config; 2 | 3 | import org.springframework.amqp.core.*; 4 | import org.springframework.context.annotation.Bean; 5 | import org.springframework.context.annotation.Configuration; 6 | 7 | @Configuration 8 | public class RabbitMQConfig { 9 | 10 | private static final String MESSAGE_STORING_QUEUE = "pl.kubaretip.chatmessagesservice.storing"; 11 | private static final String MESSAGE_STORING_EXCHANGE = "pl.kubaretip.chatmessagesservice.fanout"; 12 | private static final String MESSAGE_STORING_DLQ = MESSAGE_STORING_QUEUE + ".dlq"; 13 | private static final String MESSAGE_STORING_DLE = MESSAGE_STORING_QUEUE + ".dlx"; 14 | private static final String MESSAGE_DELETING_QUEUE = "pl.kubaretip.chatmessagesservice.deleting"; 15 | private static final String MESSAGE_DELETING_EXCHANGE = "pl.kubaretip.chatmessagesservice.fanout.deleting"; 16 | 17 | @Bean 18 | public FanoutExchange messageStoringExchange() { 19 | return new FanoutExchange(MESSAGE_STORING_EXCHANGE); 20 | } 21 | 22 | @Bean 23 | public Queue messageStoringQueue() { 24 | return QueueBuilder.durable(MESSAGE_STORING_QUEUE) 25 | .deadLetterExchange(MESSAGE_STORING_DLE) 26 | .build(); 27 | } 28 | 29 | @Bean 30 | public Binding messageStoringBinding(Queue messageStoringQueue, FanoutExchange messageStoringExchange) { 31 | return BindingBuilder.bind(messageStoringQueue).to(messageStoringExchange); 32 | } 33 | 34 | @Bean 35 | public FanoutExchange messageDeletingExchange() { 36 | return new FanoutExchange(MESSAGE_DELETING_EXCHANGE); 37 | } 38 | 39 | @Bean 40 | public Queue messageDeletingQueue() { 41 | return QueueBuilder.durable(MESSAGE_DELETING_QUEUE).build(); 42 | } 43 | 44 | @Bean 45 | public Binding messageDeletingBinding(Queue messageDeletingQueue, FanoutExchange messageDeletingExchange) { 46 | return BindingBuilder.bind(messageDeletingQueue).to(messageDeletingExchange); 47 | } 48 | 49 | @Bean 50 | public FanoutExchange deadLetterExchange() { 51 | return new FanoutExchange(MESSAGE_STORING_DLE); 52 | } 53 | 54 | @Bean 55 | public Queue deadLetterQueue() { 56 | return QueueBuilder.durable(MESSAGE_STORING_DLQ) 57 | .ttl(5000) 58 | .build(); 59 | } 60 | 61 | @Bean 62 | public Binding deadLetterBinding(Queue deadLetterQueue, FanoutExchange deadLetterExchange) { 63 | return BindingBuilder.bind(deadLetterQueue).to(deadLetterExchange); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microservice chat application 2 | 3 | [![CircleCI](https://circleci.com/gh/kubAretip/spring-micro-websocket-chat/tree/master.svg?style=shield)](https://app.circleci.com/pipelines/github/kubAretip/spring-micro-websocket-chat) 4 | 5 | ### Repository with configuration files 6 | * https://github.com/kubAretip/spring-micro-websocket-chat-config 7 | 8 | ### Repository with front-end 9 | * https://github.com/kubAretip/chat-me-angular 10 | 11 | 12 | This project creates a complete micro service chat system in Docker containers. The services are implemented in Java using Spring, Spring Cloud and RabbitMQ. 13 | It uses 5 microservices: 14 | * Mail service 15 | * Websocket service 16 | * Messages storing service 17 | * Chat profiles service 18 | * Auth service 19 | 20 | ### Technologies 21 | * Eureka 22 | * Spring Cloud Gateway 23 | * Spring Cloud Config Server 24 | * MySql and MongoDB 25 | * WebFlux 26 | * RabbitMQ (AMQP + STOMP) 27 | 28 | Requirements: 29 | * Install Maven, see https://maven.apache.org/download.cgi 30 | * Install Docker, see https://docs.docker.com/get-docker 31 | 32 | ## Diagram 33 |

34 | 35 |

36 | 37 | ## Steps to run in docker 38 | 39 | #### 1. Clone repository 40 | ``` git clone https://github.com/kubAretip/spring-micro-websocket-chat.git ``` 41 | 42 | #### 2. Configuration time 43 | * In this moment repositories with configuration files is public so you must remove configuration for privates repositories. 44 | * Go to config-server/src/main/resources open bootstrap-docker.yml and remove properties ```ignore-local-ssh-settings: ```, ```host-key-algorithm:```, ```host-key:``` and ```private-key:``` 45 | * Go to docker folder and open .env.docker. Next fill ```REPO_BRANCH=master``` and ```GIT_REPO_URI=```. Link to config repo below: 46 | https://github.com/kubAretip/spring-micro-websocket-chat-config 47 | * Go to docker folder and open docker-compose-docker.yml. In services section find config-server and remove environments variables: ```HOST_KEY: ${KNOWN_HOST_KEY}```, ```PRIVATE_KEY: ${PEM}``` 48 | 49 | #### 3. Package 50 | * In spring-micro-websocket-chat run ```mvn clean package``` 51 | 52 | #### 4. Run 53 | * Inside docker folder run ```docker-compose -f docker-compose-docker.yml --env-file .env.docker up -d``` and ... 54 | * ... wait a moment, docker compose builds the docker images and runs them. 55 | * You can access the application at http://localhost:8080 56 | * Documentation with application endpoints is available on http://localhost:8080/swagger-ui.html 57 | PS-1 If you don't see anything in Swagger UI, you must restart the gateway. 58 | It's happen because gateway has been in ready state before services, so gateway can't see Swagger endpoints from services. 59 | You can use Actuator to restart the gateway (POST http://localhost:8080/actuator/restart). 60 | 61 | 62 | -------------------------------------------------------------------------------- /mail-service/src/main/java/pl/kubaretip/mailservice/service/impl/MailServiceImpl.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.mailservice.service.impl; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.mail.MailException; 6 | import org.springframework.mail.javamail.JavaMailSender; 7 | import org.springframework.mail.javamail.MimeMessageHelper; 8 | import org.springframework.scheduling.annotation.Async; 9 | import org.springframework.stereotype.Service; 10 | import org.thymeleaf.TemplateEngine; 11 | import org.thymeleaf.context.Context; 12 | import pl.kubaretip.dtomodels.UserDTO; 13 | import pl.kubaretip.mailservice.service.SendMailService; 14 | 15 | import javax.mail.MessagingException; 16 | import java.nio.charset.StandardCharsets; 17 | 18 | @Slf4j 19 | @Service 20 | public class MailServiceImpl implements SendMailService { 21 | 22 | private final JavaMailSender sender; 23 | private final TemplateEngine templateEngine; 24 | 25 | @Value("${mail.links.baseUrl:none}") 26 | private String baseMailProcessingUrl; 27 | 28 | @Value("${mail.links.activation-resource-path:}") 29 | private String userActivationResourcePath; 30 | 31 | public MailServiceImpl(JavaMailSender sender, 32 | TemplateEngine templateEngine) { 33 | this.sender = sender; 34 | this.templateEngine = templateEngine; 35 | } 36 | 37 | @Async 38 | public void sendEmail(String to, String subject, String content, boolean isMultipart, boolean isHtml) { 39 | var mimeMessage = this.sender.createMimeMessage(); 40 | 41 | try { 42 | var mimeHelper = new MimeMessageHelper(mimeMessage, isMultipart, StandardCharsets.UTF_8.name()); 43 | mimeHelper.setTo(to); 44 | mimeHelper.setSubject(subject); 45 | mimeHelper.setText(content, isHtml); 46 | 47 | sender.send(mimeMessage); 48 | } catch (MailException | MessagingException e) { 49 | log.warn("Email wasn't send to user {}", to, e); 50 | } 51 | } 52 | 53 | 54 | @Async 55 | @Override 56 | public void sendActivationEmail(UserDTO user) { 57 | 58 | var userEmail = user.getEmail(); 59 | if (userEmail != null) { 60 | log.debug("Sending email template to {}", userEmail); 61 | 62 | var activationLink = baseMailProcessingUrl + userActivationResourcePath + user.getActivationKey(); 63 | var context = new Context(); 64 | context.setVariable("user", user); 65 | context.setVariable("activationUrl", activationLink); 66 | 67 | var subject = "Activate your account"; 68 | var content = templateEngine.process("mail/activationMail", context); 69 | sendEmail(userEmail, subject, content, false, true); 70 | } 71 | 72 | } 73 | 74 | 75 | } 76 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.security.authentication.ReactiveAuthenticationManager; 5 | import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity; 6 | import org.springframework.security.config.web.server.SecurityWebFiltersOrder; 7 | import org.springframework.security.config.web.server.ServerHttpSecurity; 8 | import org.springframework.security.web.server.SecurityWebFilterChain; 9 | import org.springframework.security.web.server.authentication.AuthenticationWebFilter; 10 | import org.springframework.security.web.server.authentication.ServerAuthenticationConverter; 11 | import org.springframework.security.web.server.savedrequest.NoOpServerRequestCache; 12 | import pl.kubaretip.authutils.jwt.JWTConfig; 13 | import pl.kubaretip.authutils.jwt.JWTUtils; 14 | 15 | 16 | @EnableWebFluxSecurity 17 | public class SecurityConfig { 18 | 19 | private static final String[] SWAGGER_AUTH_WHITELIST = { 20 | "/v3/api-docs/**", 21 | "/swagger-resources/**", 22 | "/swagger-ui/**", 23 | "/swagger-ui.html", 24 | "/webjars/**" 25 | }; 26 | 27 | @Bean 28 | public SecurityWebFilterChain securityWebFilterChain(ServerHttpSecurity http, 29 | ReactiveAuthenticationManager jwtAuthenticationManager, 30 | ServerAuthenticationConverter jwtAuthenticationConverter) { 31 | http 32 | .httpBasic().disable() 33 | .formLogin().disable() 34 | .logout().disable(); 35 | http.requestCache().requestCache(NoOpServerRequestCache.getInstance()); 36 | http.cors(); 37 | http.csrf().disable(); 38 | 39 | http.addFilterAt(authenticationWebFilter(jwtAuthenticationManager, jwtAuthenticationConverter), 40 | SecurityWebFiltersOrder.AUTHENTICATION); 41 | 42 | http.authorizeExchange() 43 | .pathMatchers(SWAGGER_AUTH_WHITELIST).permitAll() 44 | .anyExchange().authenticated(); 45 | 46 | return http.build(); 47 | } 48 | 49 | 50 | @Bean 51 | public AuthenticationWebFilter authenticationWebFilter(ReactiveAuthenticationManager jwtAuthenticationManager, 52 | ServerAuthenticationConverter jwtAuthenticationConverter) { 53 | var authenticationWebFilter = new AuthenticationWebFilter(jwtAuthenticationManager); 54 | authenticationWebFilter.setServerAuthenticationConverter(jwtAuthenticationConverter); 55 | return authenticationWebFilter; 56 | } 57 | 58 | @Bean 59 | public JWTConfig jwtConfig() { 60 | return new JWTConfig(); 61 | } 62 | 63 | @Bean 64 | public JWTUtils jwtUtils() { 65 | return new JWTUtils(jwtConfig()); 66 | } 67 | 68 | 69 | } 70 | -------------------------------------------------------------------------------- /messages-websocket-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | messages-websocket-service 13 | 14 | 15 | 16 | 17 | pl.kubaretip 18 | auth-utils 19 | 20 | 21 | 22 | pl.kubaretip 23 | dto-models 24 | 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-websocket 29 | 30 | 31 | 32 | org.springframework 33 | spring-messaging 34 | 35 | 36 | 37 | org.springframework.security 38 | spring-security-messaging 39 | 40 | 41 | 42 | org.springframework.cloud 43 | spring-cloud-starter-netflix-eureka-client 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-actuator 49 | 50 | 51 | 52 | org.springframework.cloud 53 | spring-cloud-starter-config 54 | 55 | 56 | 57 | org.springframework.boot 58 | spring-boot-starter-amqp 59 | 60 | 61 | 62 | org.springframework.boot 63 | spring-boot-starter-test 64 | test 65 | 66 | 67 | 68 | org.springframework.amqp 69 | spring-rabbit-test 70 | test 71 | 72 | 73 | 74 | org.springframework.boot 75 | spring-boot-starter-reactor-netty 76 | 77 | 78 | 79 | org.springframework.boot 80 | spring-boot-starter-security 81 | 82 | 83 | 84 | 85 | -------------------------------------------------------------------------------- /chat-messages-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | chat-messages-service 13 | 14 | 15 | 16 | 17 | pl.kubaretip 18 | dto-models 19 | 20 | 21 | 22 | pl.kubaretip 23 | auth-utils 24 | 25 | 26 | 27 | pl.kubaretip 28 | exception-utils 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-amqp 34 | 35 | 36 | 37 | org.springframework.amqp 38 | spring-rabbit-test 39 | test 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-data-mongodb-reactive 45 | 46 | 47 | 48 | org.springframework.boot 49 | spring-boot-starter-webflux 50 | 51 | 52 | 53 | org.springframework.cloud 54 | spring-cloud-starter-netflix-eureka-client 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-actuator 60 | 61 | 62 | 63 | org.springframework.cloud 64 | spring-cloud-starter-config 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-test 70 | test 71 | 72 | 73 | 74 | io.projectreactor 75 | reactor-test 76 | test 77 | 78 | 79 | 80 | org.springdoc 81 | springdoc-openapi-webflux-ui 82 | 83 | 84 | 85 | org.springframework.boot 86 | spring-boot-starter-security 87 | 88 | 89 | 90 | 91 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/web/rest/FriendRequestController.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.web.rest; 2 | 3 | import org.springframework.http.ResponseEntity; 4 | import org.springframework.web.bind.annotation.*; 5 | import org.springframework.web.util.UriComponentsBuilder; 6 | import pl.kubaretip.authutils.SecurityUtils; 7 | import pl.kubaretip.chatservice.dto.FriendRequestDTO; 8 | import pl.kubaretip.chatservice.dto.mapper.FriendRequestMapper; 9 | import pl.kubaretip.chatservice.service.FriendRequestService; 10 | 11 | import java.util.List; 12 | 13 | @RestController 14 | @RequestMapping("/friend-requests") 15 | public class FriendRequestController { 16 | 17 | private final FriendRequestService friendRequestService; 18 | private final FriendRequestMapper friendRequestMapper; 19 | 20 | public FriendRequestController(FriendRequestService friendRequestService, 21 | FriendRequestMapper friendRequestMapper) { 22 | this.friendRequestService = friendRequestService; 23 | this.friendRequestMapper = friendRequestMapper; 24 | } 25 | 26 | @PostMapping(params = {"invite_code"}) 27 | public ResponseEntity createNewFriendRequest(@RequestParam("invite_code") String inviteCode, 28 | UriComponentsBuilder uriComponentsBuilder) { 29 | var newFriendsRequest = friendRequestService.createNewFriendRequest(SecurityUtils.getCurrentUser(), inviteCode); 30 | var location = uriComponentsBuilder.path("/friends/{id}") 31 | .buildAndExpand(newFriendsRequest.getId()).toUri(); 32 | return ResponseEntity.created(location).body(friendRequestMapper.mapToFriendRequestDTO(newFriendsRequest)); 33 | } 34 | 35 | @PatchMapping(path = "/{id}", params = {"accept"}) 36 | public ResponseEntity replyToFriendRequest(@PathVariable("id") long friendRequestId, 37 | @RequestParam("accept") boolean accept) { 38 | friendRequestService.replyToFriendRequest(friendRequestId, SecurityUtils.getCurrentUser(), accept); 39 | return ResponseEntity.noContent().build(); 40 | } 41 | 42 | @DeleteMapping("/{id}") 43 | public ResponseEntity deleteSenderFriendRequestById(@PathVariable("id") long friendRequestId) { 44 | friendRequestService.deleteFriendRequestBySender(SecurityUtils.getCurrentUser(), friendRequestId); 45 | return ResponseEntity.noContent().build(); 46 | } 47 | 48 | 49 | @GetMapping("/received") 50 | public ResponseEntity> getNotAcceptedRecipientFriendRequests() { 51 | var friendRequests = friendRequestService.getAllNotAcceptedFriendRequestsByRecipientId(SecurityUtils.getCurrentUser()); 52 | return ResponseEntity.ok() 53 | .body(friendRequestMapper.mapToFriendRequestDTOListWithoutRecipient(friendRequests)); 54 | } 55 | 56 | @GetMapping("/sent") 57 | public ResponseEntity> getNotAcceptedSenderFriendRequests() { 58 | var friendRequests = friendRequestService.getAllNotAcceptedFriendRequestsBySenderId(SecurityUtils.getCurrentUser()); 59 | return ResponseEntity.ok() 60 | .body(friendRequestMapper.mapToFriendRequestDTOListWithoutSender(friendRequests)); 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /chat-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | chat-service 13 | 14 | 15 | 16 | 17 | pl.kubaretip 18 | dto-models 19 | 20 | 21 | 22 | pl.kubaretip 23 | auth-utils 24 | 25 | 26 | 27 | pl.kubaretip 28 | exception-utils 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-amqp 34 | 35 | 36 | 37 | org.springframework.amqp 38 | spring-rabbit-test 39 | test 40 | 41 | 42 | 43 | org.springframework.boot 44 | spring-boot-starter-data-jpa 45 | 46 | 47 | 48 | mysql 49 | mysql-connector-java 50 | 51 | 52 | 53 | org.flywaydb 54 | flyway-core 55 | 56 | 57 | 58 | org.springframework.boot 59 | spring-boot-starter-web 60 | 61 | 62 | 63 | org.springframework.cloud 64 | spring-cloud-starter-netflix-eureka-client 65 | 66 | 67 | 68 | org.springframework.boot 69 | spring-boot-starter-actuator 70 | 71 | 72 | 73 | org.springframework.cloud 74 | spring-cloud-starter-config 75 | 76 | 77 | 78 | org.mapstruct 79 | mapstruct 80 | 81 | 82 | 83 | org.springdoc 84 | springdoc-openapi-ui 85 | 86 | 87 | 88 | org.springframework.boot 89 | spring-boot-starter-test 90 | test 91 | 92 | 93 | 94 | org.springframework.boot 95 | spring-boot-starter-security 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/service/impl/FriendChatServiceImpl.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.service.impl; 2 | 3 | 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.stereotype.Service; 6 | import pl.kubaretip.chatservice.domain.ChatProfile; 7 | import pl.kubaretip.chatservice.domain.FriendChat; 8 | import pl.kubaretip.chatservice.repository.ChatProfileRepository; 9 | import pl.kubaretip.chatservice.repository.FriendChatRepository; 10 | import pl.kubaretip.chatservice.repository.FriendRequestRepository; 11 | import pl.kubaretip.chatservice.service.FriendChatService; 12 | import pl.kubaretip.exceptionutils.AlreadyExistsException; 13 | import pl.kubaretip.exceptionutils.NotFoundException; 14 | 15 | import javax.transaction.Transactional; 16 | import java.util.List; 17 | import java.util.UUID; 18 | 19 | @Slf4j 20 | @Service 21 | public class FriendChatServiceImpl implements FriendChatService { 22 | 23 | private final FriendChatRepository friendChatRepository; 24 | private final ChatProfileRepository chatProfileRepository; 25 | private final FriendRequestRepository friendRequestRepository; 26 | 27 | public FriendChatServiceImpl(FriendChatRepository friendChatRepository, 28 | ChatProfileRepository chatProfileRepository, 29 | FriendRequestRepository friendRequestRepository) { 30 | this.friendChatRepository = friendChatRepository; 31 | this.chatProfileRepository = chatProfileRepository; 32 | this.friendRequestRepository = friendRequestRepository; 33 | } 34 | 35 | @Transactional 36 | @Override 37 | public void createFriendChat(ChatProfile firstUserChatProfile, ChatProfile secondUserChatProfile) { 38 | 39 | if (friendChatRepository.existsFriendChatForUsers(firstUserChatProfile, secondUserChatProfile)) { 40 | throw new AlreadyExistsException("Chat for users already exists"); 41 | } 42 | 43 | var friendChatForFirstUser = new FriendChat(); 44 | var friendChatForSecondUser = new FriendChat(); 45 | 46 | friendChatForFirstUser.setSender(firstUserChatProfile); 47 | friendChatForFirstUser.setRecipient(secondUserChatProfile); 48 | 49 | friendChatForSecondUser.setSender(secondUserChatProfile); 50 | friendChatForSecondUser.setRecipient(firstUserChatProfile); 51 | friendChatRepository.save(friendChatForFirstUser); 52 | friendChatRepository.save(friendChatForSecondUser); 53 | 54 | friendChatForFirstUser.setChatWith(friendChatForSecondUser); 55 | friendChatForSecondUser.setChatWith(friendChatForFirstUser); 56 | 57 | friendChatRepository.save(friendChatForFirstUser); 58 | friendChatRepository.save(friendChatForSecondUser); 59 | 60 | } 61 | 62 | @Override 63 | public List getAllFriendsChatsBySender(String currentUserId) { 64 | return chatProfileRepository.findById(UUID.fromString(currentUserId)) 65 | .map(friendChatRepository::findBySender) 66 | .orElseThrow(() -> new NotFoundException("User with id " + currentUserId + " not found")); 67 | } 68 | 69 | @Transactional 70 | @Override 71 | public void deleteFriendChat(long friendChatId, long friendChatWithId, String currentUserId) { 72 | var friendChat = friendChatRepository.findByIdAndFriendChatWithIdAndSenderId(friendChatId, friendChatWithId, 73 | UUID.fromString(currentUserId)) 74 | .orElseThrow(() -> new NotFoundException("Friend chat not found")); 75 | friendRequestRepository.deleteFriendRequestByChatProfiles(friendChat.getSender(), friendChat.getRecipient()); 76 | friendChatRepository.delete(friendChat); 77 | } 78 | 79 | 80 | } 81 | -------------------------------------------------------------------------------- /auth-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 6 | spring-micro-websocket-chat 7 | pl.kubaretip 8 | 1.0-SNAPSHOT 9 | 10 | 4.0.0 11 | 12 | auth-service 13 | 14 | 15 | 16 | 17 | pl.kubaretip 18 | auth-utils 19 | 20 | 21 | 22 | pl.kubaretip 23 | exception-utils 24 | 25 | 26 | 27 | pl.kubaretip 28 | dto-models 29 | 30 | 31 | 32 | org.springframework.boot 33 | spring-boot-starter-actuator 34 | 35 | 36 | 37 | org.springframework.cloud 38 | spring-cloud-starter-config 39 | 40 | 41 | 42 | org.springframework.boot 43 | spring-boot-starter-web 44 | 45 | 46 | 47 | org.springframework.boot 48 | spring-boot-starter-data-jpa 49 | 50 | 51 | 52 | mysql 53 | mysql-connector-java 54 | 55 | 56 | 57 | org.flywaydb 58 | flyway-core 59 | 60 | 61 | 62 | org.springframework.cloud 63 | spring-cloud-starter-netflix-eureka-client 64 | 65 | 66 | 67 | org.springframework.boot 68 | spring-boot-starter-security 69 | 70 | 71 | 72 | org.mapstruct 73 | mapstruct 74 | 75 | 76 | 77 | org.springframework.boot 78 | spring-boot-starter-amqp 79 | 80 | 81 | 82 | org.springframework.boot 83 | spring-boot-starter-test 84 | test 85 | 86 | 87 | 88 | org.springframework.security 89 | spring-security-test 90 | test 91 | 92 | 93 | 94 | org.springframework.amqp 95 | spring-rabbit-test 96 | test 97 | 98 | 99 | 100 | org.springdoc 101 | springdoc-openapi-ui 102 | 103 | 104 | 105 | 106 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/web/rest/UserController.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.web.rest; 2 | 3 | import io.swagger.v3.oas.annotations.Operation; 4 | import lombok.extern.slf4j.Slf4j; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.security.core.Authentication; 7 | import org.springframework.web.bind.annotation.*; 8 | import org.springframework.web.util.UriComponentsBuilder; 9 | import pl.kubaretip.authservice.mapper.UserMapper; 10 | import pl.kubaretip.authservice.messaging.sender.UserSender; 11 | import pl.kubaretip.authservice.service.UserService; 12 | import pl.kubaretip.authservice.web.model.ChangePassRequest; 13 | import pl.kubaretip.authutils.security.SecurityUserDetails; 14 | import pl.kubaretip.dtomodels.UserDTO; 15 | import pl.kubaretip.exceptionutils.InvalidDataException; 16 | 17 | import javax.validation.Valid; 18 | 19 | 20 | @Slf4j 21 | @RestController 22 | @RequestMapping("/users") 23 | public class UserController { 24 | 25 | private final UserService userService; 26 | private final UserMapper userMapper; 27 | private final UserSender userSender; 28 | 29 | public UserController(UserService userService, 30 | UserMapper userMapper, 31 | UserSender userSender) { 32 | this.userService = userService; 33 | this.userMapper = userMapper; 34 | this.userSender = userSender; 35 | } 36 | 37 | @Operation(summary = "Create new user", description = "User registration endpoint", tags = "Registration") 38 | @PostMapping 39 | public ResponseEntity createNewUser(@Valid @RequestBody UserDTO userDTO, 40 | UriComponentsBuilder uriComponentsBuilder) { 41 | var user = userService.createUser(userDTO.getUsername(), userDTO.getPassword(), 42 | userDTO.getEmail(), userDTO.getFirstName(), userDTO.getLastName()); 43 | 44 | userSender.send(userMapper.mapToUserDTO(user)); 45 | 46 | var location = uriComponentsBuilder.path("/users/{id}") 47 | .buildAndExpand(user.getId()).toUri(); 48 | return ResponseEntity.created(location).body(userMapper.mapToUserDTOWithoutActivationKey(user)); 49 | } 50 | 51 | 52 | @PatchMapping("/activate") 53 | public ResponseEntity activateAccount(@RequestParam("data") String activationKey) { 54 | userService.activateUser(activationKey); 55 | return ResponseEntity.noContent().build(); 56 | } 57 | 58 | @GetMapping("/{id}") 59 | public ResponseEntity getUserById(@PathVariable("id") String userId) { 60 | return ResponseEntity.ok(userMapper.mapToUserDTO(userService.findUserById(userId))); 61 | } 62 | 63 | @PatchMapping("/{id}") 64 | public ResponseEntity editUser(@PathVariable("id") String userId, @RequestBody UserDTO userDTO) { 65 | var user = userService.modifyUser(userId, userDTO.getFirstName(), userDTO.getLastName()); 66 | return ResponseEntity.ok(userMapper.mapToUserDTO(user)); 67 | } 68 | 69 | 70 | @PatchMapping("/{id}/change-password") 71 | public ResponseEntity changeUserPassword(@PathVariable("id") String userId, 72 | @Valid @RequestBody ChangePassRequest request, 73 | Authentication authentication) { 74 | var currentUser = (SecurityUserDetails) authentication.getPrincipal(); 75 | if (!userId.equals(currentUser.getId())) { 76 | throw new InvalidDataException("Invalid user id"); 77 | } 78 | userService.changeUserPassword(currentUser.getId(), request.getCurrentPassword(), request.getNewPassword()); 79 | return ResponseEntity.noContent().build(); 80 | } 81 | 82 | } 83 | -------------------------------------------------------------------------------- /auth-service/src/main/java/pl/kubaretip/authservice/config/SecurityConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.authservice.config; 2 | 3 | import org.springframework.context.annotation.Bean; 4 | import org.springframework.http.HttpMethod; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.security.config.annotation.web.builders.HttpSecurity; 7 | import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; 8 | import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 9 | import org.springframework.security.config.http.SessionCreationPolicy; 10 | import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; 11 | import org.springframework.security.web.access.AccessDeniedHandlerImpl; 12 | import org.springframework.security.web.authentication.HttpStatusEntryPoint; 13 | import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; 14 | import pl.kubaretip.authservice.security.AuthenticationFailureHandler; 15 | import pl.kubaretip.authservice.security.AuthenticationSuccessHandler; 16 | import pl.kubaretip.authservice.security.JWTAuthenticationFilter; 17 | import pl.kubaretip.authservice.security.JWTBuilder; 18 | import pl.kubaretip.authutils.jwt.JWTConfig; 19 | import pl.kubaretip.authutils.jwt.JWTFilter; 20 | import pl.kubaretip.authutils.jwt.JWTUtils; 21 | 22 | 23 | @EnableWebSecurity 24 | public class SecurityConfig extends WebSecurityConfigurerAdapter { 25 | 26 | private static final String[] SWAGGER_AUTH_WHITELIST = { 27 | "/v3/api-docs/**", 28 | "/swagger-resources/**", 29 | "/swagger-ui/**", 30 | "/swagger-ui.html" 31 | }; 32 | 33 | @Override 34 | protected void configure(HttpSecurity http) throws Exception { 35 | // @formatter:off 36 | 37 | http 38 | .cors() 39 | .and() 40 | .csrf() 41 | .disable() 42 | .sessionManagement() 43 | .sessionCreationPolicy(SessionCreationPolicy.STATELESS) 44 | .and() 45 | .httpBasic() 46 | .disable() 47 | .authorizeRequests() 48 | .mvcMatchers(HttpMethod.POST, jwtConfig().getAuthEndpoint()).permitAll() 49 | .mvcMatchers(HttpMethod.POST,"/users").permitAll() 50 | .mvcMatchers(HttpMethod.PATCH,"/users/activate").permitAll() 51 | .antMatchers(SWAGGER_AUTH_WHITELIST).permitAll() 52 | .anyRequest().authenticated() 53 | .and() 54 | .exceptionHandling() 55 | .authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED)) 56 | .accessDeniedHandler(new AccessDeniedHandlerImpl()) 57 | .and() 58 | .addFilterBefore(jwtFilter(), UsernamePasswordAuthenticationFilter.class); 59 | 60 | // @formatter:on 61 | } 62 | 63 | @Bean 64 | public JWTAuthenticationFilter authenticationFilter() throws Exception { 65 | var filter = new JWTAuthenticationFilter(); 66 | filter.setAuthenticationSuccessHandler(new AuthenticationSuccessHandler(jwtBuilder())); 67 | filter.setAuthenticationFailureHandler(new AuthenticationFailureHandler()); 68 | filter.setAuthenticationManager(super.authenticationManager()); 69 | filter.setFilterProcessesUrl(jwtConfig().getAuthEndpoint()); 70 | return filter; 71 | } 72 | 73 | @Bean 74 | public JWTFilter jwtFilter() { 75 | return new JWTFilter(jwtUtils()); 76 | } 77 | 78 | @Bean 79 | public JWTUtils jwtUtils() { 80 | return new JWTUtils(jwtConfig()); 81 | } 82 | 83 | @Bean 84 | public JWTConfig jwtConfig() { 85 | return new JWTConfig(); 86 | } 87 | 88 | @Bean 89 | public JWTBuilder jwtBuilder() { 90 | return new JWTBuilder(jwtConfig()); 91 | } 92 | 93 | @Bean 94 | public BCryptPasswordEncoder passwordEncoder() { 95 | return new BCryptPasswordEncoder(); 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /messages-websocket-service/src/main/java/pl/kubaretip/messageswebsocketservice/config/WebSocketConfig.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.messageswebsocketservice.config; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.context.annotation.Configuration; 6 | import org.springframework.core.Ordered; 7 | import org.springframework.core.annotation.Order; 8 | import org.springframework.messaging.converter.DefaultContentTypeResolver; 9 | import org.springframework.messaging.converter.MappingJackson2MessageConverter; 10 | import org.springframework.messaging.converter.MessageConverter; 11 | import org.springframework.messaging.simp.config.ChannelRegistration; 12 | import org.springframework.messaging.simp.config.MessageBrokerRegistry; 13 | import org.springframework.util.MimeTypeUtils; 14 | import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; 15 | import org.springframework.web.socket.config.annotation.StompEndpointRegistry; 16 | import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer; 17 | import pl.kubaretip.messageswebsocketservice.security.AuthChannelInterceptor; 18 | 19 | import java.util.List; 20 | 21 | @Configuration 22 | @EnableWebSocketMessageBroker 23 | @Order(Ordered.HIGHEST_PRECEDENCE + 99) 24 | public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { 25 | 26 | private final AuthChannelInterceptor authChannelInterceptor; 27 | private final String clientLogin; 28 | private final String clientPasscode; 29 | private final String systemLogin; 30 | private final String systemPasscode; 31 | private final int relayPort; 32 | private final String relayHost; 33 | 34 | public WebSocketConfig(AuthChannelInterceptor authChannelInterceptor, 35 | @Value("${websocket.rabbitmq.stomp.clientLogin:guest}") String clientLogin, 36 | @Value("${websocket.rabbitmq.stomp.clientPasscode:guest}") String clientPasscode, 37 | @Value("${websocket.rabbitmq.stomp.systemLogin:guest}") String systemLogin, 38 | @Value("${websocket.rabbitmq.stomp.systemPasscode:guest}") String systemPasscode, 39 | @Value("${websocket.rabbitmq.stomp.relayPort:61613}") int relayPort, 40 | @Value("${websocket.rabbitmq.stomp.relayHost:127.0.0.1}") String relayHost) { 41 | this.authChannelInterceptor = authChannelInterceptor; 42 | this.clientLogin = clientLogin; 43 | this.clientPasscode = clientPasscode; 44 | this.systemLogin = systemLogin; 45 | this.systemPasscode = systemPasscode; 46 | this.relayPort = relayPort; 47 | this.relayHost = relayHost; 48 | } 49 | 50 | @Override 51 | public void registerStompEndpoints(StompEndpointRegistry registry) { 52 | registry.addEndpoint("/ws") 53 | .setAllowedOrigins("*"); 54 | } 55 | 56 | @Override 57 | public void configureMessageBroker(MessageBrokerRegistry registry) { 58 | registry 59 | .setApplicationDestinationPrefixes("/app") 60 | .enableStompBrokerRelay("/topic/") 61 | .setUserDestinationBroadcast("/topic/log-unresolved-user") 62 | .setUserRegistryBroadcast("/topic/log-user-registry") 63 | .setSystemLogin(this.systemLogin) 64 | .setSystemPasscode(this.systemPasscode) 65 | .setClientLogin(this.clientLogin) 66 | .setClientPasscode(this.clientPasscode) 67 | .setRelayHost(this.relayHost) 68 | .setRelayPort(this.relayPort); 69 | } 70 | 71 | 72 | @Override 73 | public void configureClientInboundChannel(ChannelRegistration registration) { 74 | registration.interceptors(authChannelInterceptor); 75 | } 76 | 77 | @Override 78 | public boolean configureMessageConverters(List messageConverters) { 79 | var resolver = new DefaultContentTypeResolver(); 80 | resolver.setDefaultMimeType(MimeTypeUtils.APPLICATION_JSON); 81 | 82 | var converter = new MappingJackson2MessageConverter(); 83 | converter.setObjectMapper(new ObjectMapper()); 84 | converter.setContentTypeResolver(resolver); 85 | 86 | messageConverters.add(converter); 87 | 88 | return false; 89 | } 90 | 91 | 92 | } 93 | -------------------------------------------------------------------------------- /chat-service/src/test/java/pl/kubaretip/chatservice/service/impl/FriendChatServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.service.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.jupiter.MockitoExtension; 8 | import pl.kubaretip.chatservice.domain.ChatProfile; 9 | import pl.kubaretip.chatservice.domain.FriendChat; 10 | import pl.kubaretip.chatservice.repository.ChatProfileRepository; 11 | import pl.kubaretip.chatservice.repository.FriendChatRepository; 12 | import pl.kubaretip.chatservice.repository.FriendRequestRepository; 13 | import pl.kubaretip.exceptionutils.AlreadyExistsException; 14 | import pl.kubaretip.exceptionutils.NotFoundException; 15 | 16 | import java.util.Optional; 17 | import java.util.UUID; 18 | 19 | import static java.util.UUID.randomUUID; 20 | import static org.junit.jupiter.api.Assertions.assertThrows; 21 | import static org.mockito.AdditionalAnswers.returnsFirstArg; 22 | import static org.mockito.ArgumentMatchers.any; 23 | import static org.mockito.ArgumentMatchers.anyLong; 24 | import static org.mockito.BDDMockito.given; 25 | import static org.mockito.Mockito.times; 26 | import static org.mockito.Mockito.verify; 27 | 28 | @ExtendWith(MockitoExtension.class) 29 | public class FriendChatServiceImplTest { 30 | 31 | @Mock 32 | private FriendChatRepository friendChatRepository; 33 | 34 | @Mock 35 | private ChatProfileRepository chatProfileRepository; 36 | 37 | @Mock 38 | private FriendRequestRepository friendRequestRepository; 39 | 40 | @InjectMocks 41 | private FriendChatServiceImpl friendChatService; 42 | 43 | 44 | @Test 45 | public void exceptionShouldBeThrownWhenFriendChatAlreadyExists() { 46 | 47 | // given 48 | given(friendChatRepository.existsFriendChatForUsers(any(ChatProfile.class), any(ChatProfile.class))) 49 | .willReturn(true); 50 | 51 | // when + then 52 | assertThrows(AlreadyExistsException.class, 53 | () -> friendChatService.createFriendChat(new ChatProfile(), new ChatProfile())); 54 | 55 | } 56 | 57 | @Test 58 | public void shouldCreateNewFriendChat() { 59 | 60 | //given 61 | var chatProfile1 = new ChatProfile(); 62 | var chatProfile2 = new ChatProfile(); 63 | given(friendChatRepository.existsFriendChatForUsers(any(ChatProfile.class), any(ChatProfile.class))) 64 | .willReturn(false); 65 | given(friendChatRepository.save(any(FriendChat.class))).willAnswer(returnsFirstArg()); 66 | 67 | // when 68 | friendChatService.createFriendChat(chatProfile1, chatProfile2); 69 | 70 | // then 71 | verify(friendChatRepository, times(4)).save(any(FriendChat.class)); 72 | 73 | } 74 | 75 | 76 | @Test 77 | public void exceptionShouldBeThrownGetFriendsChatsByNotExistingUser() { 78 | // given 79 | given(chatProfileRepository.findById(any(UUID.class))).willReturn(Optional.empty()); 80 | // when + then 81 | assertThrows(NotFoundException.class, 82 | () -> friendChatService.getAllFriendsChatsBySender(randomUUID().toString())); 83 | } 84 | 85 | @Test 86 | public void exceptionShouldBeThrownWhenDeleteNotExistingFriendChat() { 87 | 88 | // given 89 | given(friendChatRepository.findByIdAndFriendChatWithIdAndSenderId(anyLong(), anyLong(), any(UUID.class))) 90 | .willReturn(Optional.empty()); 91 | 92 | // when + then 93 | assertThrows(NotFoundException.class, 94 | () -> friendChatService.deleteFriendChat(1, 2, randomUUID().toString())); 95 | } 96 | 97 | @Test 98 | public void shouldDeleteFriendChat() { 99 | 100 | // given 101 | var sender = new ChatProfile(); 102 | var recipient = new ChatProfile(); 103 | var friendChat = new FriendChat(); 104 | friendChat.setSender(sender); 105 | friendChat.setRecipient(recipient); 106 | given(friendChatRepository.findByIdAndFriendChatWithIdAndSenderId(anyLong(), anyLong(), any(UUID.class))) 107 | .willReturn(Optional.of(friendChat)); 108 | 109 | // when 110 | friendChatService.deleteFriendChat(1, 2, randomUUID().toString()); 111 | 112 | // then 113 | verify(friendRequestRepository,times(1)).deleteFriendRequestByChatProfiles(sender,recipient); 114 | verify(friendChatRepository,times(1)).delete(friendChat); 115 | 116 | } 117 | 118 | 119 | } 120 | -------------------------------------------------------------------------------- /chat-service/src/main/java/pl/kubaretip/chatservice/service/impl/FriendRequestServiceImpl.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.service.impl; 2 | 3 | import org.springframework.stereotype.Service; 4 | import pl.kubaretip.chatservice.domain.FriendRequest; 5 | import pl.kubaretip.chatservice.repository.ChatProfileRepository; 6 | import pl.kubaretip.chatservice.repository.FriendRequestRepository; 7 | import pl.kubaretip.chatservice.service.FriendChatService; 8 | import pl.kubaretip.chatservice.service.FriendRequestService; 9 | import pl.kubaretip.exceptionutils.AlreadyExistsException; 10 | import pl.kubaretip.exceptionutils.InvalidDataException; 11 | import pl.kubaretip.exceptionutils.NotFoundException; 12 | 13 | import java.time.OffsetDateTime; 14 | import java.util.List; 15 | import java.util.UUID; 16 | 17 | @Service 18 | public class FriendRequestServiceImpl implements FriendRequestService { 19 | 20 | private final ChatProfileRepository chatProfileRepository; 21 | private final FriendRequestRepository friendRequestRepository; 22 | private final FriendChatService friendChatService; 23 | 24 | public FriendRequestServiceImpl(ChatProfileRepository chatProfileRepository, 25 | FriendRequestRepository friendRequestRepository, 26 | FriendChatService friendChatService) { 27 | this.chatProfileRepository = chatProfileRepository; 28 | this.friendRequestRepository = friendRequestRepository; 29 | this.friendChatService = friendChatService; 30 | } 31 | 32 | @Override 33 | public FriendRequest createNewFriendRequest(String senderUserId, String friendRequestCode) { 34 | 35 | var senderChatProfile = chatProfileRepository.findById(UUID.fromString(senderUserId)) 36 | .orElseThrow(() -> new NotFoundException("Chat profile not found.")); 37 | 38 | var recipientChatProfile = chatProfileRepository.findByFriendsRequestCode(friendRequestCode) 39 | .orElseThrow(() -> new NotFoundException("Friend request code not found")); 40 | 41 | if (senderChatProfile.getFriendsRequestCode().equals(friendRequestCode)) 42 | throw new InvalidDataException("You can't send friend request to yourself."); 43 | 44 | if (friendRequestRepository.isFriendRequestAlreadyExists(senderChatProfile, recipientChatProfile)) { 45 | throw new AlreadyExistsException("Friend request already registered."); 46 | } 47 | 48 | var newFriendsRequest = new FriendRequest(); 49 | newFriendsRequest.setSender(senderChatProfile); 50 | newFriendsRequest.setRecipient(recipientChatProfile); 51 | newFriendsRequest.setSentTime(OffsetDateTime.now()); 52 | return friendRequestRepository.save(newFriendsRequest); 53 | } 54 | 55 | @Override 56 | public void replyToFriendRequest(long friendId, String currentUserId, boolean accept) { 57 | 58 | var friendsRequest = friendRequestRepository.findById(friendId) 59 | .orElseThrow(() -> new NotFoundException("Friend request not found")); 60 | 61 | if (friendsRequest.isAccepted()) { 62 | throw new InvalidDataException("Friend request already accepted."); 63 | } 64 | 65 | if (!friendsRequest.getRecipient().getUserId().equals(UUID.fromString(currentUserId))) { 66 | throw new InvalidDataException("You are not recipient of this request request"); 67 | } 68 | 69 | if (accept) { 70 | friendsRequest.setAccepted(true); 71 | friendRequestRepository.save(friendsRequest); 72 | // create friend chat 73 | friendChatService.createFriendChat(friendsRequest.getSender(), friendsRequest.getRecipient()); 74 | } else { 75 | friendRequestRepository.delete(friendsRequest); 76 | } 77 | } 78 | 79 | @Override 80 | public void deleteFriendRequestBySender(String currentUserId, long friendRequestId) { 81 | friendRequestRepository.findById(friendRequestId) 82 | .ifPresentOrElse(friendRequest -> { 83 | if (!friendRequest.getSender().getUserId().equals(UUID.fromString(currentUserId))) { 84 | throw new InvalidDataException("You can't cancel friend request because you aren't the owner"); 85 | } 86 | 87 | if (friendRequest.isAccepted()) { 88 | throw new InvalidDataException("Friend request already accepted."); 89 | } 90 | friendRequestRepository.delete(friendRequest); 91 | }, () -> { 92 | throw new NotFoundException("Friend request not found"); 93 | }); 94 | } 95 | 96 | 97 | @Override 98 | public List getAllNotAcceptedFriendRequestsByRecipientId(String recipientId) { 99 | return friendRequestRepository.findAllByRecipientIdAndNotAccepted(UUID.fromString(recipientId)); 100 | } 101 | 102 | @Override 103 | public List getAllNotAcceptedFriendRequestsBySenderId(String senderId) { 104 | return friendRequestRepository.findAllBySenderIdAndNotAccepted(UUID.fromString(senderId)); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /docker/docker-compose-docker.yml: -------------------------------------------------------------------------------- 1 | version: '3.8' 2 | 3 | volumes: 4 | mysql_auth: 5 | name: auth_service_vol 6 | driver: local 7 | mongo_chat: 8 | name: chat_messages_service_vol 9 | driver: local 10 | mysql_chat: 11 | name: chat_service_vol 12 | driver: local 13 | rabbitmq: 14 | name: rabbitmq_vol 15 | driver: local 16 | 17 | networks: 18 | spring-chat-net: 19 | driver: bridge 20 | 21 | services: 22 | config-server: 23 | container_name: config-server 24 | build: 25 | context: ../config-server 26 | dockerfile: Dockerfile 27 | environment: 28 | SPRING_PROFILES_ACTIVE: ${PROFILE} 29 | CONFIG_SERVER_SECRET: ${CONFIG_SERVER_SECRET} 30 | GIT_BRANCH: ${REPO_BRANCH} 31 | GIT_URI: ${GIT_REPO_URI} 32 | HOST_KEY: ${KNOWN_HOST_KEY} 33 | PRIVATE_KEY: ${PEM} 34 | ports: 35 | - 8888:8888 36 | networks: 37 | - spring-chat-net 38 | eureka-server: 39 | container_name: eureka-server 40 | build: 41 | context: ../eureka-server 42 | dockerfile: Dockerfile 43 | environment: 44 | SPRING_PROFILES_ACTIVE: ${PROFILE} 45 | CLOUD_CONFIG_URI: ${CONFIG_SERVER_URI} 46 | ports: 47 | - 8761:8761 48 | networks: 49 | - spring-chat-net 50 | cloud-gateway: 51 | container_name: cloud-gateway 52 | build: 53 | context: ../cloud-gateway 54 | dockerfile: Dockerfile 55 | environment: 56 | SPRING_PROFILES_ACTIVE: ${PROFILE} 57 | CLOUD_CONFIG_URI: ${CONFIG_SERVER_URI} 58 | ports: 59 | - 8080:8080 60 | networks: 61 | - spring-chat-net 62 | auth-service: 63 | container_name: auth-service 64 | build: 65 | context: ../auth-service 66 | dockerfile: Dockerfile 67 | restart: always 68 | environment: 69 | SPRING_PROFILES_ACTIVE: ${PROFILE} 70 | CLOUD_CONFIG_URI: ${CONFIG_SERVER_URI} 71 | networks: 72 | - spring-chat-net 73 | mail-service: 74 | container_name: mail-service 75 | build: 76 | context: ../mail-service 77 | dockerfile: Dockerfile 78 | restart: always 79 | environment: 80 | SPRING_PROFILES_ACTIVE: ${PROFILE} 81 | CLOUD_CONFIG_URI: ${CONFIG_SERVER_URI} 82 | networks: 83 | - spring-chat-net 84 | messages-websocket-service: 85 | container_name: messages-websocket-service 86 | build: 87 | context: ../messages-websocket-service 88 | dockerfile: Dockerfile 89 | restart: always 90 | environment: 91 | SPRING_PROFILES_ACTIVE: ${PROFILE} 92 | CLOUD_CONFIG_URI: ${CONFIG_SERVER_URI} 93 | networks: 94 | - spring-chat-net 95 | chat-messages-service: 96 | container_name: chat-messages-service 97 | build: 98 | context: ../chat-messages-service 99 | dockerfile: Dockerfile 100 | restart: always 101 | environment: 102 | SPRING_PROFILES_ACTIVE: ${PROFILE} 103 | CLOUD_CONFIG_URI: ${CONFIG_SERVER_URI} 104 | networks: 105 | - spring-chat-net 106 | chat-service: 107 | container_name: chat-service 108 | build: 109 | context: ../chat-service 110 | dockerfile: Dockerfile 111 | restart: always 112 | environment: 113 | SPRING_PROFILES_ACTIVE: ${PROFILE} 114 | CLOUD_CONFIG_URI: ${CONFIG_SERVER_URI} 115 | networks: 116 | - spring-chat-net 117 | #DATABASES 118 | auth_db: 119 | container_name: auth-service-mysql 120 | image: library/mysql:8.0.22 121 | environment: 122 | MYSQL_DATABASE: auth_service_database 123 | MYSQL_USER: ${AUTH_SERVICE_MYSQL_USER} 124 | MYSQL_PASSWORD: ${AUTH_SERVICE_MYSQL_PASS} 125 | MYSQL_ROOT_PASSWORD: ${AUTH_SERVICE_MYSQL_ROOT_PASS} 126 | ports: 127 | - 3306:3306 128 | networks: 129 | - spring-chat-net 130 | volumes: 131 | - mysql_auth:/var/lib/mysql 132 | chat_db: 133 | container_name: chat-service-mysql 134 | image: library/mysql:8.0.22 135 | environment: 136 | MYSQL_DATABASE: chat_service_database 137 | MYSQL_USER: ${CHAT_SERVICE_MYSQL_USER} 138 | MYSQL_PASSWORD: ${CHAT_SERVICE_MYSQL_PASS} 139 | MYSQL_ROOT_PASSWORD: ${CHAT_SERVICE_MYSQL_ROOT_PASS} 140 | MYSQL_TCP_PORT: 3307 141 | ports: 142 | - 3307:3307 143 | networks: 144 | - spring-chat-net 145 | volumes: 146 | - mysql_chat:/var/lib/mysql 147 | messages_db: 148 | container_name: chat-messages-service-mongo 149 | image: library/mongo:latest 150 | environment: 151 | MONGO_INITDB_DATABASE: chat_messages_service_database 152 | MONGO_INITDB_ROOT_USERNAME: ${CHAT_MESSAGES_SERVICE_ROOT_MONGO_USER} 153 | MONGO_INITDB_ROOT_PASSWORD: ${CHAT_MESSAGES_SERVICE_ROOT_MONGO_PASS} 154 | ports: 155 | - 27017:27017 156 | volumes: 157 | - mongo_chat:/var/lib/mongo 158 | - ./init-chat-messages-service-mongo.js:/docker-entrypoint-initdb.d/init-mongo.js:ro 159 | networks: 160 | - spring-chat-net 161 | #RABBIT 162 | rabbitmq: 163 | container_name: rabbitmq-service 164 | image: rabbitmq:3-management 165 | hostname: rabbitmq-service 166 | command: sh -c "rabbitmq-plugins enable rabbitmq_stomp; rabbitmq-server" 167 | networks: 168 | - spring-chat-net 169 | ports: 170 | - 5672:5672 171 | - 15672:15672 172 | - 61613:61613 173 | volumes: 174 | - rabbitmq:/etc/rabbitmq/ 175 | - rabbitmq:/var/lib/rabbitmq/ 176 | - rabbitmq:/var/log/rabbitmq/ 177 | - ./rabbitmq-isolated.conf:/etc/rabbitmq/rabbitmq.config 178 | #MAIL 179 | mailhog: 180 | container_name: mailhog-service 181 | image: mailhog/mailhog 182 | ports: 183 | - 1025:1025 # smtp port 184 | - 8025:8025 # web ui port 185 | networks: 186 | - spring-chat-net -------------------------------------------------------------------------------- /chat-service/src/test/java/pl/kubaretip/chatservice/service/impl/ChatProfileServiceImplTest.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatservice.service.impl; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.junit.jupiter.api.extension.ExtendWith; 5 | import org.mockito.InjectMocks; 6 | import org.mockito.Mock; 7 | import org.mockito.junit.jupiter.MockitoExtension; 8 | import pl.kubaretip.chatservice.domain.ChatProfile; 9 | import pl.kubaretip.chatservice.repository.ChatProfileRepository; 10 | import pl.kubaretip.exceptionutils.InvalidDataException; 11 | import pl.kubaretip.exceptionutils.NotFoundException; 12 | 13 | import java.util.Optional; 14 | import java.util.UUID; 15 | 16 | import static java.util.UUID.randomUUID; 17 | import static org.hamcrest.MatcherAssert.assertThat; 18 | import static org.hamcrest.Matchers.*; 19 | import static org.junit.jupiter.api.Assertions.assertThrows; 20 | import static org.mockito.AdditionalAnswers.returnsFirstArg; 21 | import static org.mockito.ArgumentMatchers.any; 22 | import static org.mockito.BDDMockito.given; 23 | import static org.mockito.Mockito.times; 24 | import static org.mockito.Mockito.verify; 25 | 26 | @ExtendWith(MockitoExtension.class) 27 | public class ChatProfileServiceImplTest { 28 | 29 | @Mock 30 | private ChatProfileRepository chatProfileRepository; 31 | 32 | @InjectMocks 33 | private ChatProfileServiceImpl chatProfileService; 34 | 35 | @Test 36 | public void exceptionShouldBeThrownWhenCreateChatProfileWithEmptyUserId() { 37 | // given + when + then 38 | assertThrows(InvalidDataException.class, () -> { 39 | chatProfileService.createChatProfile("", null); 40 | }); 41 | } 42 | 43 | @Test 44 | public void exceptionShouldBeThrownWhenCreateChatProfileWithNullUserId() { 45 | // given + when + then 46 | assertThrows(InvalidDataException.class, () -> { 47 | chatProfileService.createChatProfile(null, null); 48 | }); 49 | } 50 | 51 | @Test 52 | public void exceptionShouldBeThrownWhenCreateChatProfileWithEmptyUsername() { 53 | // given + when + then 54 | assertThrows(InvalidDataException.class, () -> { 55 | chatProfileService.createChatProfile("id", ""); 56 | }); 57 | } 58 | 59 | @Test 60 | public void exceptionShouldBeThrownWhenCreateChatProfileWithNullUsername() { 61 | // given + when + then 62 | assertThrows(InvalidDataException.class, () -> { 63 | chatProfileService.createChatProfile("id", null); 64 | }); 65 | } 66 | 67 | @Test 68 | public void shouldCreateNewChatProfile() { 69 | // given 70 | var userId = randomUUID(); 71 | var username = "username"; 72 | given(chatProfileRepository.save(any(ChatProfile.class))).willAnswer(returnsFirstArg()); 73 | 74 | // when 75 | var result = chatProfileService.createChatProfile(userId.toString(), username); 76 | 77 | // then 78 | assertThat(result.getUserId(), is(equalTo(userId))); 79 | // assertThat(result.getFriendsRequestCode(), is(equalTo(username))); 80 | 81 | verify(chatProfileRepository, times(1)).save(any()); 82 | } 83 | 84 | 85 | @Test 86 | public void friendsRequestCodeForNewChatProfileShouldStartWithUsername() { 87 | // given 88 | var username = "username"; 89 | given(chatProfileRepository.save(any(ChatProfile.class))).willAnswer(returnsFirstArg()); 90 | 91 | // when 92 | var result = chatProfileService.createChatProfile(randomUUID().toString(), username); 93 | 94 | // then 95 | assertThat(result.getFriendsRequestCode(), is(notNullValue())); 96 | assertThat(result.getFriendsRequestCode(), startsWith(username)); 97 | verify(chatProfileRepository, times(1)).save(any()); 98 | } 99 | 100 | @Test 101 | public void shouldRegenerateFriendCode() { 102 | // given 103 | var userId = randomUUID(); 104 | var username = "test"; 105 | var friendsRequestCode = "123"; 106 | var chatProfile = new ChatProfile(); 107 | chatProfile.setUserId(userId); 108 | chatProfile.setFriendsRequestCode(friendsRequestCode); 109 | given(chatProfileRepository.findById(any(UUID.class))).willReturn(Optional.of(chatProfile)); 110 | given(chatProfileRepository.save(any(ChatProfile.class))).willAnswer(returnsFirstArg()); 111 | 112 | // when 113 | var result = chatProfileService.generateNewFriendsRequestCode(userId.toString(), username); 114 | 115 | // then 116 | assertThat(result.getFriendsRequestCode(), startsWith(username)); 117 | assertThat(result.getFriendsRequestCode(), is(not(equalTo(friendsRequestCode)))); 118 | verify(chatProfileRepository, times(1)).save(any()); 119 | 120 | } 121 | 122 | 123 | @Test 124 | public void shouldReturnChatProfileById() { 125 | 126 | // given 127 | given(chatProfileRepository.findById(any(UUID.class))).willReturn(Optional.of(new ChatProfile())); 128 | 129 | // when 130 | var result = chatProfileService.getChatProfileById(randomUUID().toString()); 131 | 132 | // then 133 | assertThat(result, is(notNullValue())); 134 | verify(chatProfileRepository, times(1)).findById(any()); 135 | } 136 | 137 | @Test 138 | public void exceptionShouldBeThrownWhereNotFoundUserById() { 139 | 140 | // given 141 | given(chatProfileRepository.findById(any(UUID.class))).willReturn(Optional.empty()); 142 | 143 | // when + then 144 | assertThrows(NotFoundException.class, 145 | () -> chatProfileService.getChatProfileById(randomUUID().toString())); 146 | } 147 | 148 | 149 | } 150 | -------------------------------------------------------------------------------- /chat-messages-service/src/main/java/pl/kubaretip/chatmessagesservice/service/impl/ChatMessageServiceImpl.java: -------------------------------------------------------------------------------- 1 | package pl.kubaretip.chatmessagesservice.service.impl; 2 | 3 | import lombok.extern.slf4j.Slf4j; 4 | import org.apache.commons.lang3.StringUtils; 5 | import org.springframework.data.domain.PageRequest; 6 | import org.springframework.data.domain.Sort; 7 | import org.springframework.stereotype.Service; 8 | import pl.kubaretip.chatmessagesservice.constant.DateConstants; 9 | import pl.kubaretip.chatmessagesservice.constant.MessageStatus; 10 | import pl.kubaretip.chatmessagesservice.document.ChatMessage; 11 | import pl.kubaretip.chatmessagesservice.repository.ChatMessageRepository; 12 | import pl.kubaretip.chatmessagesservice.service.ChatMessageService; 13 | import pl.kubaretip.exceptionutils.InvalidDataException; 14 | import reactor.core.publisher.Flux; 15 | import reactor.core.publisher.Mono; 16 | 17 | import java.time.LocalDateTime; 18 | import java.time.ZoneOffset; 19 | import java.time.format.DateTimeFormatter; 20 | import java.time.format.DateTimeParseException; 21 | import java.util.Date; 22 | import java.util.List; 23 | 24 | @Slf4j 25 | @Service 26 | public class ChatMessageServiceImpl implements ChatMessageService { 27 | 28 | private final ChatMessageRepository chatMessageRepository; 29 | 30 | public ChatMessageServiceImpl(ChatMessageRepository chatMessageRepository) { 31 | this.chatMessageRepository = chatMessageRepository; 32 | } 33 | 34 | @Override 35 | public Mono saveChatMessage(Long friendChat, String sender, String recipient, String content, String time) { 36 | 37 | return Mono.just(new ChatMessage()) 38 | .flatMap(chatMessage -> { 39 | if (StringUtils.isEmpty(content)) { 40 | return Mono.error(new InvalidDataException("Can not save empty message")); 41 | } 42 | 43 | if (friendChat == null) { 44 | return Mono.error(new InvalidDataException("Can not save message with empty friend chat field")); 45 | } 46 | 47 | if (StringUtils.isEmpty(sender) || StringUtils.isEmpty(recipient)) { 48 | return Mono.error(new InvalidDataException("Can not save message with empty sender or " + 49 | "recipient")); 50 | } 51 | 52 | if (StringUtils.isEmpty(time)) { 53 | return Mono.error(new InvalidDataException("Can not save message with empty date")); 54 | } 55 | try { 56 | var localDateTime = LocalDateTime.parse(time, DateTimeFormatter.ofPattern(DateConstants.UTC_DATE_FORMAT)); 57 | chatMessage.setTime(Date.from(localDateTime.toInstant(ZoneOffset.UTC))); 58 | } catch (DateTimeParseException ex) { 59 | return Mono.error(new InvalidDataException("Time format should be in UTC")); 60 | } 61 | chatMessage.setStatus(MessageStatus.RECEIVED); 62 | chatMessage.setContent(content); 63 | chatMessage.setFriendChat(friendChat); 64 | chatMessage.setRecipient(recipient); 65 | chatMessage.setSender(sender); 66 | return Mono.just(chatMessage); 67 | }) 68 | .flatMap(chatMessageRepository::save); 69 | } 70 | 71 | @Override 72 | public Flux findLastUsersMessagesFromTime(long firstUserFriendChatId, long secondUserFriendChatId, 73 | String beforeTime, int numberOfMessagesToFetch) { 74 | 75 | return Mono.just(beforeTime) 76 | .flatMap(beforeTimeInString -> { 77 | try { 78 | var localDateTime = LocalDateTime.parse(beforeTime, DateTimeFormatter.ofPattern(DateConstants.UTC_DATE_FORMAT)); 79 | return Mono.just(Date.from(localDateTime.toInstant(ZoneOffset.UTC))); 80 | } catch (DateTimeParseException ex) { 81 | return Mono.error(new InvalidDataException("Time format should be in UTC")); 82 | } 83 | }) 84 | .flatMapMany(formattedDate -> { 85 | var sortedByTimeDescWithSize = PageRequest.of(0, numberOfMessagesToFetch, Sort.by("time").descending()); 86 | return chatMessageRepository 87 | .findByTimeLessThanAndFriendChatIn( 88 | formattedDate, 89 | List.of(firstUserFriendChatId, secondUserFriendChatId), 90 | sortedByTimeDescWithSize); 91 | } 92 | ); 93 | } 94 | 95 | 96 | @Override 97 | public Flux getLastUserMessages(long friendChatId1, long friendChatId2, int numberOfMessagesToFetch) { 98 | var sortedByTimeDescWithSize = PageRequest.of(0, numberOfMessagesToFetch, Sort.by("time").descending()); 99 | return chatMessageRepository.findByFriendChatOrFriendChat(friendChatId1, friendChatId2, sortedByTimeDescWithSize); 100 | } 101 | 102 | @Override 103 | public Mono setDeliveredStatusForAllRecipientMessagesInFriendChat(long friendChatId, String currentUser) { 104 | return chatMessageRepository.findByFriendChatAndRecipientAndStatus(friendChatId, currentUser, MessageStatus.RECEIVED) 105 | .doOnNext(chatMessage -> chatMessage.setStatus(MessageStatus.DELIVERED)) 106 | .flatMap(chatMessageRepository::save) 107 | .then(); 108 | } 109 | 110 | @Override 111 | public Mono removeMessagesByFriendChat(List ids) { 112 | return chatMessageRepository.deleteByFriendChatIn(ids); 113 | } 114 | 115 | } 116 | --------------------------------------------------------------------------------