├── .github └── workflows │ └── maven.yml ├── .gitignore ├── LICENSE ├── backend ├── .env ├── Dockerfile ├── api-gateway │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── linkwave │ │ │ │ └── apigateway │ │ │ │ ├── ApiGatewayApplication.java │ │ │ │ ├── ApiGatewayRouterConfig.java │ │ │ │ └── security │ │ │ │ ├── AuthenticationHeaderGatewayFilter.java │ │ │ │ ├── AuthenticationParameterGatewayFilter.java │ │ │ │ └── utils │ │ │ │ └── GatewayUtils.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ └── java │ │ └── org │ │ └── linkwave │ │ └── apigateway │ │ └── ApiGatewayApplicationTests.java ├── auth-service │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── linkwave │ │ │ │ └── auth │ │ │ │ ├── AuthApplication.java │ │ │ │ ├── controller │ │ │ │ └── AuthController.java │ │ │ │ ├── dto │ │ │ │ ├── TokensDto.java │ │ │ │ ├── UserDeleteRequest.java │ │ │ │ └── UserLoginRequest.java │ │ │ │ ├── entity │ │ │ │ ├── DeactivatedToken.java │ │ │ │ ├── Role.java │ │ │ │ └── User.java │ │ │ │ ├── exception │ │ │ │ └── UserNotFoundException.java │ │ │ │ ├── repository │ │ │ │ ├── DeactivatedTokenRepository.java │ │ │ │ ├── RoleRepository.java │ │ │ │ └── UserRepository.java │ │ │ │ ├── security │ │ │ │ ├── CredentialsAuthenticationConverter.java │ │ │ │ ├── DefaultUserDetails.java │ │ │ │ ├── DefaultUserDetailsService.java │ │ │ │ ├── JwtAuthFiltersConfigurer.java │ │ │ │ ├── SecurityConfig.java │ │ │ │ ├── UnAuthorizedAuthenticationEntryPoint.java │ │ │ │ ├── filter │ │ │ │ │ ├── JwtDeleteUserFilter.java │ │ │ │ │ ├── JwtLogoutFilter.java │ │ │ │ │ ├── JwtTokensInitializerFilter.java │ │ │ │ │ └── JwtTokensRefreshFilter.java │ │ │ │ ├── jwt │ │ │ │ │ ├── AccessTokenFactory.java │ │ │ │ │ ├── AccessTokenFactoryImpl.java │ │ │ │ │ ├── JwtRefreshParser.java │ │ │ │ │ ├── JwtRefreshSerializer.java │ │ │ │ │ ├── RefreshTokenFactory.java │ │ │ │ │ └── RefreshTokenFactoryImpl.java │ │ │ │ └── utils │ │ │ │ │ ├── Cookies.java │ │ │ │ │ └── TokenAuthorities.java │ │ │ │ └── service │ │ │ │ ├── TokenCleaner.java │ │ │ │ └── UserService.java │ │ └── resources │ │ │ ├── application.yml │ │ │ ├── banner.txt │ │ │ ├── keys │ │ │ ├── access_private_key.pem │ │ │ ├── access_public_key.pem │ │ │ ├── refresh_private_key.pem │ │ │ └── refresh_public_key.pem │ │ │ ├── schema.sql │ │ │ └── static │ │ │ └── openapi.json │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── linkwave │ │ │ └── auth │ │ │ └── AuthApplicationTests.java │ │ └── resources │ │ ├── application.yml │ │ ├── banner.txt │ │ └── keys │ │ ├── access_private_key.pem │ │ ├── access_public_key.pem │ │ ├── refresh_private_key.pem │ │ └── refresh_public_key.pem ├── chat-service │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── linkwave │ │ │ │ └── chatservice │ │ │ │ ├── ChatServiceApplication.java │ │ │ │ ├── api │ │ │ │ ├── ApiResponseClientErrorException.java │ │ │ │ ├── ExceptionErrorDecoder.java │ │ │ │ ├── ServiceErrorException.java │ │ │ │ ├── users │ │ │ │ │ ├── ContactDto.java │ │ │ │ │ ├── UserDto.java │ │ │ │ │ └── UserServiceClient.java │ │ │ │ └── ws │ │ │ │ │ ├── LoadChatRequest.java │ │ │ │ │ └── WSServiceClient.java │ │ │ │ ├── chat │ │ │ │ ├── ChatController.java │ │ │ │ ├── ChatDto.java │ │ │ │ ├── ChatMember.java │ │ │ │ ├── ChatMemberDetailsDto.java │ │ │ │ ├── ChatMemberDto.java │ │ │ │ ├── ChatMemberPermissionsDenied.java │ │ │ │ ├── ChatNotFoundException.java │ │ │ │ ├── ChatRepository.java │ │ │ │ ├── ChatRole.java │ │ │ │ ├── ChatService.java │ │ │ │ ├── ChatServiceImpl.java │ │ │ │ ├── MessageAuthorDto.java │ │ │ │ ├── duo │ │ │ │ │ ├── Chat.java │ │ │ │ │ ├── CompanionDto.java │ │ │ │ │ ├── DuoChatDto.java │ │ │ │ │ └── NewChatRequest.java │ │ │ │ └── group │ │ │ │ │ ├── GroupChat.java │ │ │ │ │ ├── GroupChatDetailedDto.java │ │ │ │ │ ├── GroupChatDetailsDto.java │ │ │ │ │ ├── GroupChatDto.java │ │ │ │ │ ├── NewGroupChatRequest.java │ │ │ │ │ └── UpdateGroupChat.java │ │ │ │ ├── common │ │ │ │ ├── BadRequestDataException.java │ │ │ │ ├── ChatOptionsViolationException.java │ │ │ │ ├── DtoConverter.java │ │ │ │ ├── DtoViews.java │ │ │ │ ├── ListUtils.java │ │ │ │ ├── PrivacyViolationException.java │ │ │ │ ├── RequestInitiator.java │ │ │ │ ├── RequestUtils.java │ │ │ │ ├── ResourceNotFoundException.java │ │ │ │ ├── RestExceptionHandler.java │ │ │ │ └── UnacceptableRequestDataException.java │ │ │ │ ├── message │ │ │ │ ├── Action.java │ │ │ │ ├── ChatMessageCursor.java │ │ │ │ ├── FetchMessageMapping.java │ │ │ │ ├── Message.java │ │ │ │ ├── MessageController.java │ │ │ │ ├── MessageDto.java │ │ │ │ ├── MessageNotFoundException.java │ │ │ │ ├── MessageReaction.java │ │ │ │ ├── MessageRepository.java │ │ │ │ ├── MessageService.java │ │ │ │ ├── MessageServiceImpl.java │ │ │ │ ├── ReadMessages.java │ │ │ │ ├── RemovedMessage.java │ │ │ │ ├── file │ │ │ │ │ ├── CreatedFileMessage.java │ │ │ │ │ ├── FileMessage.java │ │ │ │ │ └── FileMessageDto.java │ │ │ │ ├── member │ │ │ │ │ ├── MemberMessage.java │ │ │ │ │ └── MemberMessageDto.java │ │ │ │ ├── poll │ │ │ │ │ ├── PollMessage.java │ │ │ │ │ ├── PollMessageDto.java │ │ │ │ │ └── PollOption.java │ │ │ │ └── text │ │ │ │ │ ├── EditTextMessage.java │ │ │ │ │ ├── NewTextMessage.java │ │ │ │ │ ├── TextMessage.java │ │ │ │ │ ├── TextMessageDto.java │ │ │ │ │ └── UpdatedTextMessage.java │ │ │ │ ├── security │ │ │ │ ├── JwtAuthConfigurer.java │ │ │ │ ├── JwtAuthenticationFilter.java │ │ │ │ └── SecurityConfiguration.java │ │ │ │ └── user │ │ │ │ ├── User.java │ │ │ │ ├── UserRepository.java │ │ │ │ ├── UserService.java │ │ │ │ └── UserServiceImpl.java │ │ └── resources │ │ │ ├── application.yml │ │ │ ├── docker-init │ │ │ └── mongo_rs_init.js │ │ │ ├── keys │ │ │ └── access_public_key.pem │ │ │ └── static │ │ │ └── openapi.json │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── linkwave │ │ │ └── chatservice │ │ │ ├── ChatServiceApplicationTests.java │ │ │ └── unit │ │ │ └── ListUtilsTest.java │ │ └── resources │ │ ├── application.yml │ │ └── keys │ │ ├── access_private_key.pem │ │ └── access_public_key.pem ├── discovery-service │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── linkwave │ │ │ │ └── discovery │ │ │ │ └── DiscoveryServiceApplication.java │ │ └── resources │ │ │ └── application.yml │ │ └── test │ │ └── java │ │ └── org │ │ └── linkwave │ │ └── discovery │ │ └── DiscoveryServiceApplicationTests.java ├── docker-compose.yml ├── pom.xml ├── shared │ ├── .gitignore │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── org │ │ └── linkwave │ │ └── shared │ │ ├── Main.java │ │ ├── auth │ │ ├── BearerAuthenticationConverter.java │ │ ├── DefaultUserDetails.java │ │ ├── JwtAccessParser.java │ │ ├── JwtAccessSerializer.java │ │ ├── Token.java │ │ ├── TokenParser.java │ │ └── TokenSerializer.java │ │ ├── dto │ │ └── ApiError.java │ │ ├── storage │ │ ├── FileStorageService.java │ │ └── LocalFileStorageService.java │ │ └── utils │ │ ├── Bearers.java │ │ └── Headers.java ├── user-service │ ├── .gitignore │ ├── .mvn │ │ └── wrapper │ │ │ ├── maven-wrapper.jar │ │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── org │ │ │ │ └── linkwave │ │ │ │ └── userservice │ │ │ │ ├── UserServiceApplication.java │ │ │ │ ├── controller │ │ │ │ ├── ConstraintErrorMessages.java │ │ │ │ ├── ContactController.java │ │ │ │ ├── RestControllerExceptionHandler.java │ │ │ │ └── UserController.java │ │ │ │ ├── dto │ │ │ │ ├── ContactDto.java │ │ │ │ ├── NewContactRequest.java │ │ │ │ ├── UserDto.java │ │ │ │ └── UserRegisterRequest.java │ │ │ │ ├── entity │ │ │ │ ├── ContactEntity.java │ │ │ │ ├── RoleEntity.java │ │ │ │ └── UserEntity.java │ │ │ │ ├── exception │ │ │ │ ├── LimitExceededException.java │ │ │ │ ├── ResourceNotFoundException.java │ │ │ │ └── UnacceptableRequestDataException.java │ │ │ │ ├── repository │ │ │ │ ├── ContactRepository.java │ │ │ │ ├── RoleRepository.java │ │ │ │ └── UserRepository.java │ │ │ │ ├── security │ │ │ │ ├── JwtAuthConfigurer.java │ │ │ │ ├── JwtAuthenticationFilter.java │ │ │ │ └── SecurityConfiguration.java │ │ │ │ └── service │ │ │ │ ├── ContactService.java │ │ │ │ ├── UserService.java │ │ │ │ └── impl │ │ │ │ ├── ContactServiceImpl.java │ │ │ │ └── DefaultUserService.java │ │ └── resources │ │ │ ├── application.yml │ │ │ ├── dataset.sql │ │ │ ├── docker-init │ │ │ └── users_db.sql │ │ │ ├── keys │ │ │ └── access_public_key.pem │ │ │ └── static │ │ │ └── openapi.json │ │ └── test │ │ ├── java │ │ └── org │ │ │ └── linkwave │ │ │ └── userservice │ │ │ ├── it │ │ │ └── UserControllerTests.java │ │ │ ├── unit │ │ │ ├── ContactServiceImplTest.java │ │ │ └── UserServiceUnitTest.java │ │ │ └── utils │ │ │ ├── TokenGenerator.java │ │ │ └── UsersUtils.java │ │ └── resources │ │ ├── application.yml │ │ └── keys │ │ ├── access_private_key.pem │ │ └── access_public_key.pem └── ws-server │ ├── .gitignore │ ├── .mvn │ └── wrapper │ │ ├── maven-wrapper.jar │ │ └── maven-wrapper.properties │ ├── Dockerfile │ ├── README.MD │ ├── mvnw │ ├── mvnw.cmd │ ├── pom.xml │ └── src │ ├── main │ ├── java │ │ └── org │ │ │ └── linkwave │ │ │ └── ws │ │ │ ├── WebSocketServerApplication.java │ │ │ ├── api │ │ │ ├── ApiErrorDecoder.java │ │ │ ├── ApiErrorException.java │ │ │ ├── chat │ │ │ │ ├── ChatMember.java │ │ │ │ ├── ChatMemberDetailsDto.java │ │ │ │ ├── ChatMemberDto.java │ │ │ │ ├── ChatMessageCursor.java │ │ │ │ ├── ChatRole.java │ │ │ │ ├── ChatServiceClient.java │ │ │ │ ├── CreatedFileMessage.java │ │ │ │ ├── GroupChatDto.java │ │ │ │ ├── MessageDto.java │ │ │ │ ├── NewChatRequest.java │ │ │ │ ├── NewTextMessage.java │ │ │ │ ├── ReadMessages.java │ │ │ │ ├── RemovedMessage.java │ │ │ │ └── UpdatedTextMessage.java │ │ │ └── users │ │ │ │ └── UserServiceClient.java │ │ │ ├── config │ │ │ ├── RedisConfiguration.java │ │ │ └── SecurityConfiguration.java │ │ │ ├── controller │ │ │ ├── LoadChatRequest.java │ │ │ └── WSChatsController.java │ │ │ ├── repository │ │ │ ├── ChatRepository.java │ │ │ ├── RedisChatRepository.java │ │ │ └── SessionRepository.java │ │ │ ├── security │ │ │ ├── JwtAuthConfigurer.java │ │ │ └── JwtAuthenticationFilter.java │ │ │ ├── utils │ │ │ ├── RedisTemplateUtils.java │ │ │ └── RouteUtils.java │ │ │ └── websocket │ │ │ ├── RootWebSocketHandler.java │ │ │ ├── WebSocketConfig.java │ │ │ ├── WebSocketSessionConfigurer.java │ │ │ ├── WebSocketSessionConfigurerImpl.java │ │ │ ├── dto │ │ │ ├── Action.java │ │ │ ├── BaseMessage.java │ │ │ ├── BindMessage.java │ │ │ ├── ChatMessage.java │ │ │ ├── ChatRoleMessage.java │ │ │ ├── ErrorMessage.java │ │ │ ├── IdentifiedMessage.java │ │ │ ├── IncomeMessage.java │ │ │ ├── LastReadMessage.java │ │ │ ├── MemberMessage.java │ │ │ ├── NewChatRole.java │ │ │ ├── NewGroupChat.java │ │ │ ├── OutcomeFileMessage.java │ │ │ ├── OutcomeMessage.java │ │ │ ├── ReadMessage.java │ │ │ ├── StatusMessage.java │ │ │ └── UnreadMessages.java │ │ │ ├── jwt │ │ │ ├── JwtHandshakeHandler.java │ │ │ ├── JwtHandshakeInterceptor.java │ │ │ └── UserPrincipal.java │ │ │ ├── route │ │ │ ├── ChatRoutes.java │ │ │ ├── ClientConnectionHandler.java │ │ │ ├── GroupChatRoutes.java │ │ │ ├── UnreadMessagesRoutes.java │ │ │ └── condition │ │ │ │ └── ChatMembership.java │ │ │ ├── routing │ │ │ ├── Box.java │ │ │ ├── ConditionalRouteHandlerInvocator.java │ │ │ ├── EndpointCondition.java │ │ │ ├── MessageContext.java │ │ │ ├── Payload.java │ │ │ ├── RouteComponent.java │ │ │ ├── RouteHandlerInvocator.java │ │ │ ├── RoutingAutoConfig.java │ │ │ ├── RoutingMessage.java │ │ │ ├── WebSocketRouter.java │ │ │ ├── WebSocketRouterImpl.java │ │ │ ├── args │ │ │ │ ├── ArgumentResolverStrategy.java │ │ │ │ ├── DefaultRouteHandlerArgumentResolver.java │ │ │ │ ├── PathVariableResolverStrategy.java │ │ │ │ ├── PayloadResolverStrategy.java │ │ │ │ └── RouteHandlerArgumentResolver.java │ │ │ ├── bpp │ │ │ │ ├── Broadcast.java │ │ │ │ ├── Broadcasts.java │ │ │ │ ├── Endpoint.java │ │ │ │ ├── WebSocketRoute.java │ │ │ │ └── WebSocketRouterBeanPostProcessor.java │ │ │ ├── broadcast │ │ │ │ ├── BroadcastManager.java │ │ │ │ ├── BroadcastRepositoryResolver.java │ │ │ │ ├── BroadcastRepositoryResolverImpl.java │ │ │ │ ├── FlexBroadcastManager.java │ │ │ │ ├── SimpleBroadcastManager.java │ │ │ │ ├── SimpleMessageBroadcast.java │ │ │ │ ├── WebSocketMessageBroadcast.java │ │ │ │ └── instances │ │ │ │ │ ├── MessageDelegate.java │ │ │ │ │ └── MessageDelegateImpl.java │ │ │ ├── exception │ │ │ │ ├── ConditionViolatedException.java │ │ │ │ ├── InvalidMessageFormatException.java │ │ │ │ ├── InvalidPathException.java │ │ │ │ └── RoutingException.java │ │ │ └── parser │ │ │ │ ├── MessageParser.java │ │ │ │ └── TextMessageParser.java │ │ │ └── session │ │ │ ├── AbstractSessionManager.java │ │ │ ├── SessionManager.java │ │ │ └── callback │ │ │ ├── AfterConnectionClosed.java │ │ │ ├── AfterConnectionEstablished.java │ │ │ └── DefaultSessionManager.java │ └── resources │ │ ├── application.yml │ │ ├── banner.txt │ │ └── keys │ │ └── access_public_key.pem │ └── test │ ├── java │ └── org │ │ └── linkwave │ │ └── ws │ │ └── unit │ │ ├── BroadcastRepositoryResolverTest.java │ │ ├── ConditionalRouteHandlerInvocatorTest.java │ │ ├── DefaultRouteHandlerArgumentResolverTest.java │ │ ├── FlexBroadcastManagerTest.java │ │ ├── GroupChatRoutesTest.java │ │ ├── SessionTestUtils.java │ │ ├── SimpleBroadcastManagerTest.java │ │ ├── SimpleMessageBroadcastTest.java │ │ ├── TextMessageParserTest.java │ │ ├── WebSocketRouterBroadcastTest.java │ │ └── WebSocketRouterImplTest.java │ └── resources │ └── access_public_key.pem └── frontend ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .prettierrc.js ├── README.md ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── avatars │ ├── group.png │ └── user │ │ ├── avatar1.png │ │ ├── avatar2.png │ │ ├── avatar3.png │ │ ├── avatar4.png │ │ ├── avatar5.png │ │ └── avatar6.png ├── favicon.ico ├── fonts │ └── gg-sans │ │ ├── gg_sans_Bold.woff │ │ ├── gg_sans_Medium.woff │ │ ├── gg_sans_Regular.woff │ │ └── gg_sans_Semibold.woff ├── icons │ ├── add-chat-outline.svg │ ├── add-circle-outline.svg │ ├── angle-down.svg │ ├── broken-link-outline.svg │ ├── chat-plus-outline.svg │ ├── check.svg │ ├── clock-outline.svg │ ├── close-outline.svg │ ├── crown-outline.svg │ ├── cut-check.svg │ ├── edit-outline.svg │ ├── exit-outline.svg │ ├── find-people-outline.svg │ ├── folder-outline.svg │ ├── form-outline.svg │ ├── group-outline.svg │ ├── index.ts │ ├── left-angle.svg │ ├── line-horizontal.svg │ ├── link-outline.svg │ ├── list-outline.svg │ ├── lock-outline.svg │ ├── minus-circle-outline.svg │ ├── pen-with-message.svg │ ├── pin-outline.svg │ ├── remove-circle-outline.svg │ ├── search-outline.svg │ ├── send-outline.svg │ ├── setting-outline.svg │ ├── sign-out-circle.svg │ ├── time-outline.svg │ ├── trash-bucket-outline.svg │ ├── user-fill.svg │ ├── user-outline.svg │ ├── user-plus-outline.svg │ ├── users-outline.svg │ └── сurved-arrow-icon.svg ├── images │ ├── ChatPage │ │ └── backend-fall.gif │ ├── HomePageParallax │ │ ├── back-wave.svg │ │ ├── front-wave.svg │ │ ├── link.svg │ │ ├── middle-wave.svg │ │ └── stroke.svg │ ├── logo.svg │ └── tutorial │ │ ├── tutorial1.png │ │ ├── tutorial2.png │ │ ├── tutorial3.png │ │ ├── tutorial4.png │ │ ├── tutorial5.png │ │ ├── tutorial6.png │ │ ├── tutorial7.png │ │ ├── tutorial8.png │ │ └── tutorial9.png └── sound │ └── message.mp3 ├── src ├── api │ ├── http │ │ ├── auth │ │ │ ├── auth.ts │ │ │ └── auth.types.ts │ │ ├── chat │ │ │ └── chat.ts │ │ ├── contacts │ │ │ ├── contacts.ts │ │ │ └── contacts.types.ts │ │ ├── index.ts │ │ ├── index.types.ts │ │ └── user │ │ │ └── user.ts │ └── socket │ │ ├── index.ts │ │ └── index.types.ts ├── app │ ├── chat │ │ └── page.tsx │ ├── globals.css │ ├── layout.tsx │ ├── page.tsx │ ├── sign-in │ │ └── page.tsx │ ├── sign-up │ │ └── page.tsx │ └── tutorialCards.ts ├── components │ ├── Auth │ │ ├── SignInForm │ │ │ └── SignInForm.tsx │ │ ├── SignUpForm │ │ │ └── SignUpForm.tsx │ │ ├── auth.config.tsx │ │ ├── auth.types.ts │ │ └── auth.utils.ts │ ├── Avatar │ │ ├── Avatar.tsx │ │ └── avatar.types.ts │ ├── Card │ │ ├── Card.tsx │ │ └── card.types.ts │ ├── Chat │ │ ├── Chat.tsx │ │ ├── InteractiveList │ │ │ ├── InteractiveList.tsx │ │ │ ├── interactiveList.types.ts │ │ │ └── variants │ │ │ │ ├── ChatList │ │ │ │ └── ChatList.tsx │ │ │ │ ├── ContactList │ │ │ │ └── ContactList.tsx │ │ │ │ ├── GlobalContactList │ │ │ │ └── GlobalContactList.tsx │ │ │ │ └── Settings │ │ │ │ ├── Settings.tsx │ │ │ │ ├── settings.config.ts │ │ │ │ └── settings.types.ts │ │ ├── MainBox │ │ │ ├── MainBox.tsx │ │ │ ├── mainBox.types.ts │ │ │ └── variants │ │ │ │ ├── ChatBox │ │ │ │ ├── ChatBox.tsx │ │ │ │ ├── ChatHeader │ │ │ │ │ ├── ChatHeader.tsx │ │ │ │ │ └── chatHeader.types.ts │ │ │ │ ├── MessageBox │ │ │ │ │ ├── Message │ │ │ │ │ │ ├── FileMessage.tsx │ │ │ │ │ │ ├── Message.tsx │ │ │ │ │ │ └── fileMessage.styles.css │ │ │ │ │ └── MessageContainer │ │ │ │ │ │ ├── MessageContainer.tsx │ │ │ │ │ │ ├── messageContainer.styles.css │ │ │ │ │ │ └── messageContainer.types.ts │ │ │ │ ├── MessageInput │ │ │ │ │ └── MessageInput.tsx │ │ │ │ ├── chatBox.config.ts │ │ │ │ └── chatBox.types.ts │ │ │ │ ├── EmptyBox │ │ │ │ └── EmptyBox.tsx │ │ │ │ ├── ProfileSettings │ │ │ │ ├── ProfileSettings.tsx │ │ │ │ └── profileSettingsProps.types.ts │ │ │ │ └── UserInfoBox │ │ │ │ ├── InfoBox │ │ │ │ └── InfoTextBox.tsx │ │ │ │ ├── InfoIconShape │ │ │ │ ├── InfoIconShape.tsx │ │ │ │ └── infoIconShape.types.ts │ │ │ │ ├── UserInfoBox.tsx │ │ │ │ └── userInfoBox.types.ts │ │ ├── SideBar │ │ │ ├── SideBar.tsx │ │ │ ├── sidebar.config.ts │ │ │ └── sidebar.types.ts │ │ ├── UserItem │ │ │ ├── userItem.types.ts │ │ │ └── variants │ │ │ │ ├── ChatItem.tsx │ │ │ │ └── ContactItem.tsx │ │ ├── chat.socketHandlers.ts │ │ ├── chat.types.ts │ │ └── types │ │ │ └── handlers.types.ts │ ├── Container │ │ ├── Container.tsx │ │ └── container.types.ts │ ├── CreateGroupChatModal │ │ └── CreateGroupChatModal.tsx │ ├── CustomButton │ │ ├── CustomButton.tsx │ │ ├── customButton.config.ts │ │ └── customButton.types.ts │ ├── CustomInput │ │ ├── CustomInput.tsx │ │ └── customInput.types.ts │ ├── DoubleCheckIcon │ │ ├── DoubleCheckIcon.tsx │ │ └── doubleCheckIcon.types.ts │ ├── Form │ │ ├── Form.tsx │ │ └── form.types.ts │ ├── GroupDetailsModal │ │ ├── GroupDetailsButton │ │ │ └── GroupDetailsButton.tsx │ │ ├── GroupDetailsModal.tsx │ │ └── MemberItem │ │ │ └── MemberItem.tsx │ ├── Header │ │ ├── Header.tsx │ │ └── header.types.ts │ ├── HomeContainer │ │ ├── HomeContainer.tsx │ │ └── homeContainer.types.ts │ ├── HomePageParallax │ │ ├── HomePageParallax.tsx │ │ └── homePageParallax.styles.css │ ├── Icon │ │ ├── Icon.tsx │ │ └── icon.settings.ts │ ├── LastSeen │ │ ├── LastSeen.tsx │ │ ├── lastSeen.config.ts │ │ └── lastSeen.types.ts │ ├── Modal │ │ └── Modal.tsx │ ├── ParallaxImage │ │ ├── ParallaxImage.tsx │ │ └── parallaxImage.config.ts │ ├── ScrollList │ │ ├── ScrollList.tsx │ │ ├── scrollList.styles.css │ │ └── scrollList.types.ts │ ├── Status │ │ ├── Status.tsx │ │ └── status.types.ts │ ├── SystemToastContainer │ │ ├── SystemToastContainer.tsx │ │ ├── systemToastContainer.config.ts │ │ └── systemToastContainer.styles.css │ ├── ThemeToggle │ │ ├── ThemeToggle.tsx │ │ └── themeToggle.config.ts │ ├── ToastManager │ │ └── ToastManager.tsx │ └── TutorialCard │ │ └── TutorialCard.tsx ├── constants │ └── colors.ts ├── context │ ├── SocketContext │ │ ├── SocketContext.tsx │ │ ├── SocketProvider │ │ │ ├── SocketProvider.tsx │ │ │ └── socketProvider.config.ts │ │ └── socketContext.types.ts │ ├── SoundContext │ │ ├── SoundContext.tsx │ │ └── SoundProvider.tsx │ ├── StoreProvider │ │ └── StoreProvider.tsx │ └── ThemeProvider │ │ └── ThemeProvider.tsx ├── helpers │ ├── DecodeToken │ │ ├── decodeToken.ts │ │ └── decodeToken.types.ts │ ├── FormatDate │ │ ├── formatDate.ts │ │ └── formatDate.types.ts │ ├── contactHelpers.ts │ ├── defaultUserAvatar.ts │ └── formatSystemAlertMessage.ts ├── hooks │ └── useAccessTokenEffect.ts └── lib │ ├── features │ ├── chat │ │ ├── chatSlice.config.ts │ │ ├── chatSlice.ts │ │ └── chatSlice.types.ts │ └── user │ │ ├── userSlice.config.ts │ │ ├── userSlice.ts │ │ └── userSlice.types.ts │ ├── hooks.ts │ └── store.ts ├── tailwind.config.ts └── tsconfig.json /.github/workflows/maven.yml: -------------------------------------------------------------------------------- 1 | # This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time 2 | # For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-maven 3 | 4 | # This workflow uses actions that are not certified by GitHub. 5 | # They are provided by a third-party and are governed by 6 | # separate terms of service, privacy policy, and support 7 | # documentation. 8 | 9 | name: Java CI with Maven 10 | 11 | on: 12 | pull_request: 13 | branches: [ "main", "dev" ] 14 | 15 | jobs: 16 | build: 17 | name: Backend testing 18 | runs-on: ubuntu-latest 19 | 20 | steps: 21 | - name: Checkout Repository 22 | uses: actions/checkout@v3 23 | 24 | - name: Set up JDK 18 25 | uses: actions/setup-java@v3 26 | with: 27 | java-version: '18' 28 | distribution: 'temurin' 29 | cache: maven 30 | - name: Build & test Spring Boot modules with Maven 31 | run: mvn -f backend/pom.xml package spring-boot:repackage 32 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 L1nkWave 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /backend/.env: -------------------------------------------------------------------------------- 1 | USERS_DB_URL=jdbc:postgresql://host.docker.internal:15432/lw-users 2 | DB_USER=postgres 3 | USERS_DB_PASSWORD=postgres 4 | 5 | CHAT_DB_URL=mongodb://host.docker.internal:27017/linkwave 6 | 7 | DISCOVERY_URL=http://host.docker.internal:8761/eureka/ 8 | ORIGINS=http://26.245.248.82,http://localhost,http://localhost:3000 -------------------------------------------------------------------------------- /backend/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM maven:3.8.6-openjdk-18 2 | WORKDIR /app 3 | COPY . . 4 | RUN mvn clean package spring-boot:repackage -DskipTests 5 | -------------------------------------------------------------------------------- /backend/api-gateway/.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 | -------------------------------------------------------------------------------- /backend/api-gateway/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/backend/api-gateway/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /backend/api-gateway/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /backend/api-gateway/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:18-jre-alpine 2 | WORKDIR /app 3 | COPY --from=linkwave/backend:latest ./app/api-gateway/target/api-gateway-0.0.1-SNAPSHOT.jar ./api-gateway.jar 4 | CMD ["java", "-jar", "api-gateway.jar"] -------------------------------------------------------------------------------- /backend/api-gateway/src/main/java/org/linkwave/apigateway/ApiGatewayApplication.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.apigateway; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | 8 | @SpringBootApplication 9 | public class ApiGatewayApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(ApiGatewayApplication.class, args); 13 | } 14 | 15 | @Bean 16 | public ObjectMapper objectMapper() { 17 | final var mapper = new ObjectMapper(); 18 | mapper.findAndRegisterModules(); 19 | return mapper; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/api-gateway/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8080 3 | 4 | spring: 5 | application: 6 | name: api-gateway 7 | 8 | cloud: 9 | gateway: 10 | globalcors: 11 | cors-configurations: 12 | '[/**]': 13 | allowedOrigins: http://localhost 14 | allowedMethods: '*' 15 | allowedHeaders: '*' 16 | allowCredentials: true 17 | exposed-headers: 18 | - 'X-Total-Count' 19 | 20 | eureka: 21 | client: 22 | service-url: 23 | defaultZone: 'http://localhost:8761/eureka/' 24 | -------------------------------------------------------------------------------- /backend/api-gateway/src/test/java/org/linkwave/apigateway/ApiGatewayApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.apigateway; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class ApiGatewayApplicationTests { 6 | 7 | @Test 8 | void contextLoads() { 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/auth-service/.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 | -------------------------------------------------------------------------------- /backend/auth-service/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/backend/auth-service/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /backend/auth-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /backend/auth-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:18-jre-alpine 2 | WORKDIR /app 3 | COPY --from=linkwave/backend:latest ./app/auth-service/target/auth-service-0.0.1-SNAPSHOT.jar ./auth-service.jar 4 | CMD ["java", "-jar", "auth-service.jar"] -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/AuthApplication.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.context.annotation.Bean; 7 | import org.springframework.scheduling.annotation.EnableScheduling; 8 | 9 | @EnableScheduling 10 | @SpringBootApplication 11 | public class AuthApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(AuthApplication.class, args); 15 | } 16 | 17 | @Bean 18 | public ObjectMapper objectMapper() { 19 | var objectMapper = new ObjectMapper(); 20 | objectMapper.findAndRegisterModules(); 21 | return objectMapper; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/dto/TokensDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.dto; 2 | 3 | import java.time.Instant; 4 | 5 | public record TokensDto(Instant refreshExpiration, String accessToken) { 6 | } 7 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/dto/UserDeleteRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.dto; 2 | 3 | public record UserDeleteRequest(String password) { 4 | } 5 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/dto/UserLoginRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.dto; 2 | 3 | public record UserLoginRequest(String username, String password) { 4 | } 5 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/entity/DeactivatedToken.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.entity; 2 | 3 | import jakarta.persistence.Entity; 4 | import jakarta.persistence.Id; 5 | import jakarta.persistence.Table; 6 | import lombok.AllArgsConstructor; 7 | import lombok.Getter; 8 | import lombok.NoArgsConstructor; 9 | 10 | import java.time.Instant; 11 | import java.util.UUID; 12 | 13 | @Entity 14 | @Table(name = "deactivated_tokens") 15 | @NoArgsConstructor 16 | @AllArgsConstructor 17 | @Getter 18 | public class DeactivatedToken { 19 | 20 | @Id 21 | private UUID id; 22 | 23 | private Instant expiration; 24 | 25 | } 26 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/entity/Role.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | import lombok.*; 6 | 7 | import java.util.LinkedList; 8 | import java.util.List; 9 | 10 | @Entity 11 | @Table(name = "roles") 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Getter 15 | @Builder 16 | public class Role { 17 | 18 | @Id 19 | @GeneratedValue(strategy = GenerationType.IDENTITY) 20 | private Integer id; 21 | 22 | @Column(unique = true, nullable = false) 23 | private String name; 24 | 25 | @JsonIgnore 26 | @ManyToMany(mappedBy = "roles") 27 | @Builder.Default 28 | private List users = new LinkedList<>(); 29 | 30 | @Getter 31 | @RequiredArgsConstructor 32 | public enum Roles { 33 | 34 | USER("ROLE_USER"), 35 | ADMIN("ROLE_ADMIN"); 36 | 37 | private final String name; 38 | 39 | } 40 | 41 | } -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/exception/UserNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.exception; 2 | 3 | import org.springframework.security.core.AuthenticationException; 4 | 5 | public class UserNotFoundException extends AuthenticationException { 6 | public UserNotFoundException() { 7 | super("User not found"); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/repository/DeactivatedTokenRepository.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.repository; 2 | 3 | import org.linkwave.auth.entity.DeactivatedToken; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Modifying; 6 | import org.springframework.data.jpa.repository.Query; 7 | import org.springframework.stereotype.Repository; 8 | 9 | import java.time.Instant; 10 | import java.util.UUID; 11 | 12 | @Repository 13 | public interface DeactivatedTokenRepository extends JpaRepository { 14 | 15 | @Modifying 16 | @Query( 17 | value = "delete from deactivated_tokens where expiration < :timeAgo", 18 | nativeQuery = true 19 | ) 20 | void removeAllExpiredTokens(Instant timeAgo); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.repository; 2 | 3 | import org.linkwave.auth.entity.Role; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | @Repository 8 | public interface RoleRepository extends JpaRepository { 9 | } -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/repository/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.repository; 2 | 3 | import org.linkwave.auth.entity.User; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.data.jpa.repository.Query; 6 | import org.springframework.stereotype.Repository; 7 | 8 | import java.util.Optional; 9 | 10 | @Repository 11 | public interface UserRepository extends JpaRepository { 12 | 13 | @Query("from User u join fetch u.roles where u.username = :username") 14 | Optional findByUsername(String username); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/security/DefaultUserDetails.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.security; 2 | 3 | import lombok.Getter; 4 | import org.springframework.security.core.GrantedAuthority; 5 | import org.springframework.security.core.userdetails.User; 6 | 7 | import java.util.Collection; 8 | 9 | @Getter 10 | public class DefaultUserDetails extends User { 11 | 12 | private final Long id; 13 | private final boolean isDeleted; 14 | private final boolean isBlocked; 15 | 16 | public DefaultUserDetails(Long id, String username, String password, boolean isDeleted, boolean isBlocked, 17 | Collection authorities) { 18 | super(username, password, authorities); 19 | this.id = id; 20 | this.isDeleted = isDeleted; 21 | this.isBlocked = isBlocked; 22 | } 23 | 24 | @Override 25 | public boolean isEnabled() { 26 | return !isDeleted && !isBlocked; 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/security/jwt/AccessTokenFactory.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.security.jwt; 2 | 3 | import org.springframework.lang.NonNull; 4 | 5 | import org.linkwave.shared.auth.Token; 6 | 7 | public interface AccessTokenFactory { 8 | Token build(@NonNull Token refreshToken); 9 | } 10 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/security/jwt/JwtRefreshParser.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.security.jwt; 2 | 3 | import com.auth0.jwt.algorithms.Algorithm; 4 | import org.linkwave.shared.auth.Token; 5 | import org.linkwave.shared.auth.TokenParser; 6 | 7 | import java.security.interfaces.RSAPublicKey; 8 | 9 | public class JwtRefreshParser implements TokenParser { 10 | 11 | private final Algorithm algorithm; 12 | 13 | public JwtRefreshParser(RSAPublicKey publicKey) { 14 | this.algorithm = Algorithm.RSA256(publicKey, null); 15 | } 16 | 17 | @Override 18 | public Token parse(String token) { 19 | return parse(token, algorithm); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/security/jwt/RefreshTokenFactory.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.security.jwt; 2 | 3 | import org.springframework.lang.NonNull; 4 | import org.springframework.security.core.Authentication; 5 | import org.springframework.security.core.GrantedAuthority; 6 | 7 | import org.linkwave.shared.auth.Token; 8 | 9 | import java.util.Collection; 10 | 11 | public interface RefreshTokenFactory { 12 | Token build(@NonNull Authentication authentication); 13 | Token refreshWith(@NonNull Token existingToken, @NonNull Collection authorities); 14 | } 15 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/security/utils/Cookies.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.security.utils; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.springframework.http.ResponseCookie; 5 | import org.springframework.lang.NonNull; 6 | 7 | import java.time.Duration; 8 | 9 | @UtilityClass 10 | public class Cookies { 11 | public static final String REFRESH_TOKEN = "Refresh-Token"; 12 | public static final String STRICT_SAME_SITE = "Strict"; 13 | 14 | public static ResponseCookie createRefreshCookie(String value, @NonNull Duration ttl) { 15 | return createRefreshCookie(value, ttl.toSeconds()); 16 | } 17 | 18 | public static ResponseCookie createRefreshCookie(String value, long ttlInSeconds) { 19 | return ResponseCookie.from(REFRESH_TOKEN, value) 20 | .httpOnly(true) 21 | .sameSite(STRICT_SAME_SITE) 22 | .maxAge(ttlInSeconds) 23 | .path("/api/v1/auth/refresh-tokens") 24 | .build(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/java/org/linkwave/auth/security/utils/TokenAuthorities.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.auth.security.utils; 2 | 3 | public class TokenAuthorities { 4 | public static final String AUTHORITY_PREFIX = "GRANT_"; 5 | public static final String JWT_REFRESH = "GRANT_JWT_REFRESH"; 6 | } 7 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | eureka: 5 | client: 6 | service-url: 7 | defaultZone: 'http://localhost:8761/eureka/' 8 | 9 | management: 10 | endpoints: 11 | web: 12 | exposure: 13 | include: health 14 | 15 | spring: 16 | application: 17 | name: 'auth-service' 18 | 19 | datasource: 20 | url: 'jdbc:postgresql://localhost:5432/lw-users' 21 | username: 'root' 22 | password: '123' 23 | driver-class-name: org.postgresql.Driver 24 | 25 | jpa: 26 | open-in-view: false 27 | properties: 28 | hibernate: 29 | show_sql: false 30 | format_sql: true 31 | highlight_sql: true 32 | 33 | hibernate: 34 | ddl-auto: update 35 | naming: 36 | physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy 37 | 38 | 39 | jwt: 40 | issuer: 'LW-auth' 41 | 42 | logging: 43 | level: 44 | org.linkwave.auth: INFO -------------------------------------------------------------------------------- /backend/auth-service/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ,--. ,--. ,--. ,---. ,--. ,--. ,-.,-.,-. 2 | | | | | | |,-----. / O \ ,--.,--.,-' '-.| ,---. \ \ \ \ 3 | | | | |.'.| |'-----'| .-. || || |'-. .-'| .-. | \ \ \ \ 4 | | '--.| ,'. | | | | |' '' ' | | | | | | / / / / 5 | `-----''--' '--' `--' `--' `----' `--' `--' `--'/ / / / 6 | `-'`-'`-' 7 | <<< ${spring.application.name} ${application.version}>>> 8 | Powered by Spring Boot ${spring-boot.version} 9 | -------------------------------------------------------------------------------- /backend/auth-service/src/main/resources/keys/access_private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 3 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /backend/auth-service/src/main/resources/keys/access_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | 3 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/auth-service/src/main/resources/keys/refresh_private_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | 3 | -----END PRIVATE KEY----- -------------------------------------------------------------------------------- /backend/auth-service/src/main/resources/keys/refresh_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | 3 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/auth-service/src/main/resources/schema.sql: -------------------------------------------------------------------------------- 1 | drop table if exists deactivated_tokens; 2 | 3 | create table deactivated_tokens 4 | ( 5 | id uuid primary key, 6 | expiration timestamptz not null check ( expiration > now() ) 7 | ); -------------------------------------------------------------------------------- /backend/auth-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8081 3 | 4 | eureka: 5 | client: 6 | register-with-eureka: false 7 | fetch-registry: false 8 | 9 | spring: 10 | application: 11 | name: 'auth-service' 12 | 13 | datasource: 14 | url: 'jdbc:h2:~/test' 15 | username: 'sa' 16 | password: '' 17 | driver-class-name: org.h2.Driver 18 | 19 | jpa: 20 | properties: 21 | hibernate: 22 | show_sql: false 23 | format_sql: true 24 | highlight_sql: true 25 | 26 | hibernate: 27 | ddl-auto: create 28 | naming: 29 | physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy 30 | 31 | jwt: 32 | issuer: 'LW-auth' 33 | 34 | logging: 35 | level: 36 | org.linkwave.auth: INFO 37 | -------------------------------------------------------------------------------- /backend/auth-service/src/test/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ,--. ,--. ,--. ,---. ,--. ,--. ,-.,-.,-. 2 | | | | | | |,-----. / O \ ,--.,--.,-' '-.| ,---. \ \ \ \ 3 | | | | |.'.| |'-----'| .-. || || |'-. .-'| .-. | \ \ \ \ 4 | | '--.| ,'. | | | | |' '' ' | | | | | | / / / / 5 | `-----''--' '--' `--' `--' `----' `--' `--' `--'/ / / / 6 | `-'`-'`-' 7 | <<< ${spring.application.name} ${application.version}>>> 8 | Powered by Spring Boot ${spring-boot.version} 9 | -------------------------------------------------------------------------------- /backend/auth-service/src/test/resources/keys/access_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz2yidb7uzIwVPfMm8Ox1 3 | wTagUv2WYGa+plFHHvKBVpB7AiOd+aCzBl6MO/L/xwsaP8jYWt2TDMDy/K+AgIK1 4 | 1xjZfCy0zafhZb9q9rgeiPClyq1GdsT1Vhd0ETZ6nNt+e0WQYKAXaUx1LfETOctO 5 | f2RDhC9ZxeyTn2/fkrw0JO2X9WZuYmpDW2sqMKC9YnmlTsU1tR7wJkAcvu2X4Obe 6 | 1P68ajY6V1neWlAbdpuDW0E8V9dlqH8BT6OZaFAqqgqj9x976xzMEr47DuPoW7AC 7 | 1JjXB8GkADPOJNlyfQ3g99NjQNW3KIQB5jQ20kJp5irWSb6FhS7/atzF/7ammR6y 8 | GwIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/auth-service/src/test/resources/keys/refresh_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtLtJAfsXTSWdX76+WEpJ 3 | Zoke90I7jTvDEafgpEGgrUZCV3TOGa3cIcUZ5Ojy3MXq5fCjbYJN0P86u9UEvhfA 4 | 3xfyCTEo/u9k6STlbw7dMR1CU03ow82It8HspGuT450mvsEROhOhHbtd7VSlMukZ 5 | Q6tm/GW8Nl7QRAcEFavlpHtgnWL38HfKKAdZJEdhZW3bgSDx9D6wlYspj3B8Vf6A 6 | 2B2LUNyjbTQqHhkYLM+AwyO+eJmzbzKbUhla06EHOwCQA7KpN5BAvgnE7NIMxuKQ 7 | 4ATmmUukrw8izcct4r+c6z3wiks/JRUSD8pA+RAsUdCO1mhbfXLgv9ncFXiCl8pT 8 | EQIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/chat-service/.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 | -------------------------------------------------------------------------------- /backend/chat-service/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/backend/chat-service/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /backend/chat-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /backend/chat-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:18-jre-alpine 2 | WORKDIR /app 3 | COPY --from=linkwave/backend:latest ./app/chat-service/target/chat-service-0.0.1-SNAPSHOT.jar ./chat-service.jar 4 | CMD ["java", "-jar", "chat-service.jar"] -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/api/ApiResponseClientErrorException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.api; 2 | 3 | public class ApiResponseClientErrorException extends RuntimeException { 4 | public ApiResponseClientErrorException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/api/ServiceErrorException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.api; 2 | 3 | public class ServiceErrorException extends RuntimeException { 4 | 5 | public ServiceErrorException() { 6 | super(); 7 | } 8 | 9 | public ServiceErrorException(String message) { 10 | super(message); 11 | } 12 | 13 | public ServiceErrorException(Throwable cause) { 14 | super(cause); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/api/users/ContactDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.api.users; 2 | 3 | import lombok.*; 4 | 5 | import java.time.ZonedDateTime; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | @Setter 11 | @EqualsAndHashCode 12 | @Builder 13 | public class ContactDto { 14 | private UserDto user; 15 | private ZonedDateTime addedAt; 16 | private String alias; 17 | } 18 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/api/users/UserDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.api.users; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.Builder; 5 | import lombok.EqualsAndHashCode; 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | 9 | import java.time.ZonedDateTime; 10 | 11 | @Getter 12 | @Setter 13 | @EqualsAndHashCode 14 | @Builder 15 | public class UserDto { 16 | 17 | private Long id; 18 | private String username; 19 | private String name; 20 | private ZonedDateTime createdAt; 21 | private ZonedDateTime lastSeen; 22 | 23 | @JsonProperty("online") 24 | private boolean isOnline; 25 | 26 | private String avatarPath; 27 | 28 | @JsonProperty("deleted") 29 | private boolean isDeleted; 30 | 31 | @JsonProperty("blocked") 32 | private boolean isBlocked; 33 | 34 | private String bio; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/api/ws/LoadChatRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.api.ws; 2 | 3 | import lombok.*; 4 | 5 | @NoArgsConstructor 6 | @AllArgsConstructor 7 | @Setter 8 | @Getter 9 | @Builder 10 | public class LoadChatRequest { 11 | 12 | private String chatId; 13 | private Long recipientId; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/api/ws/WSServiceClient.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.api.ws; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.*; 5 | 6 | import static org.springframework.http.HttpHeaders.AUTHORIZATION; 7 | 8 | @FeignClient(value = "ws-server", path = "/api/v1/ws-chats") 9 | public interface WSServiceClient { 10 | 11 | @PostMapping 12 | void loadNewChat( 13 | @RequestHeader(AUTHORIZATION) String authHeader, 14 | @RequestBody LoadChatRequest loadChatRequest 15 | ); 16 | 17 | @PostMapping("/group/{chatId}") 18 | void loadNewGroupChat( 19 | @RequestHeader(AUTHORIZATION) String authHeader, 20 | @PathVariable String chatId 21 | ); 22 | 23 | @PatchMapping("/{id}/unread_messages") 24 | void addUnreadMessage( 25 | @RequestHeader(AUTHORIZATION) String authHeader, 26 | @PathVariable String id, 27 | @RequestParam Long senderId 28 | ); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/ChatMember.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | @Setter 14 | public class ChatMember { 15 | 16 | private Long id; 17 | private ChatRole role; 18 | private Instant joinedAt; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/ChatMemberDetailsDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat; 2 | 3 | import lombok.*; 4 | 5 | @NoArgsConstructor 6 | @AllArgsConstructor 7 | @Getter 8 | @Setter 9 | @Builder 10 | public class ChatMemberDetailsDto { 11 | 12 | private String username; 13 | private String name; 14 | private boolean isAvatarAvailable; 15 | private boolean isOnline; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/ChatMemberDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat; 2 | 3 | import lombok.*; 4 | 5 | import java.time.Instant; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | @Setter 11 | @Builder 12 | public class ChatMemberDto { 13 | 14 | private Long id; 15 | private ChatRole role; 16 | private Instant joinedAt; 17 | private ChatMemberDetailsDto details; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/ChatMemberPermissionsDenied.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat; 2 | 3 | public class ChatMemberPermissionsDenied extends RuntimeException { 4 | public ChatMemberPermissionsDenied() { 5 | super("Permissions denied"); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/ChatNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat; 2 | 3 | public class ChatNotFoundException extends RuntimeException { 4 | 5 | public ChatNotFoundException() { 6 | this("Requested chat not found"); 7 | } 8 | 9 | public ChatNotFoundException(String message) { 10 | super(message); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/ChatRole.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat; 2 | 3 | public enum ChatRole { 4 | MEMBER, 5 | ADMIN 6 | } 7 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/MessageAuthorDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.*; 5 | 6 | @NoArgsConstructor 7 | @AllArgsConstructor 8 | @Getter 9 | @Setter 10 | @Builder 11 | public class MessageAuthorDto { 12 | 13 | private Long id; 14 | private String username; 15 | private String name; 16 | 17 | @JsonProperty("deleted") 18 | private Boolean isDeleted; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/duo/Chat.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.duo; 2 | 3 | import lombok.*; 4 | import lombok.experimental.SuperBuilder; 5 | import org.linkwave.chatservice.chat.ChatMember; 6 | import org.linkwave.chatservice.message.Message; 7 | import org.springframework.data.mongodb.core.mapping.Document; 8 | import org.springframework.lang.NonNull; 9 | 10 | import java.time.Instant; 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @Document("chats") 15 | @NoArgsConstructor 16 | @Data 17 | @EqualsAndHashCode 18 | @ToString 19 | @SuperBuilder 20 | public class Chat { 21 | 22 | private String id; 23 | 24 | @Builder.Default 25 | private List members = new ArrayList<>(); 26 | 27 | @Builder.Default 28 | private Instant createdAt = Instant.now(); 29 | 30 | private Message lastMessage; 31 | 32 | public enum Type { 33 | DUO, 34 | GROUP 35 | } 36 | 37 | public void addMessage(@NonNull Message message) { 38 | this.lastMessage = message; 39 | message.setChat(this); 40 | } 41 | 42 | } 43 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/duo/CompanionDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.duo; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.*; 5 | 6 | import java.time.ZonedDateTime; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Getter 11 | @Setter 12 | @Builder 13 | public class CompanionDto { 14 | 15 | private Long id; 16 | private String username; 17 | private String name; 18 | private ZonedDateTime lastSeen; 19 | 20 | @JsonProperty("online") 21 | private boolean isOnline; 22 | 23 | @JsonProperty("deleted") 24 | private boolean isDeleted; 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/duo/DuoChatDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.duo; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.experimental.SuperBuilder; 7 | import org.linkwave.chatservice.chat.ChatDto; 8 | 9 | @NoArgsConstructor 10 | @Setter 11 | @Getter 12 | @SuperBuilder 13 | public class DuoChatDto extends ChatDto { 14 | 15 | private CompanionDto user; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/duo/NewChatRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.duo; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotNull; 6 | import lombok.Getter; 7 | 8 | @Getter 9 | public class NewChatRequest { 10 | 11 | @NotNull(message = "must be present") 12 | @Min(value = 1, message = "must be bigger than 0") 13 | @Max(value = Long.MAX_VALUE, message = "max limit is exceeded") 14 | private Long recipient; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/group/GroupChatDetailedDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.group; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.experimental.SuperBuilder; 7 | import org.linkwave.chatservice.chat.ChatMemberDto; 8 | 9 | import java.util.List; 10 | 11 | @NoArgsConstructor 12 | @Setter 13 | @Getter 14 | @SuperBuilder 15 | public class GroupChatDetailedDto extends GroupChatDto { 16 | 17 | private String description; 18 | private List members; 19 | private int membersLimit; 20 | private boolean isPrivate; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/group/GroupChatDetailsDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.group; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.linkwave.chatservice.chat.ChatMemberDto; 6 | 7 | import java.util.List; 8 | 9 | @Getter 10 | @Setter 11 | public class GroupChatDetailsDto { 12 | 13 | private String description; 14 | private List members; 15 | private int membersLimit; 16 | private boolean isPrivate; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/group/GroupChatDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.group; 2 | 3 | import com.fasterxml.jackson.annotation.JsonView; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.experimental.SuperBuilder; 8 | import org.linkwave.chatservice.chat.ChatDto; 9 | import org.linkwave.chatservice.chat.duo.Chat; 10 | 11 | import static org.linkwave.chatservice.chat.duo.Chat.Type.GROUP; 12 | import static org.linkwave.chatservice.common.DtoViews.Detailed; 13 | import static org.linkwave.chatservice.common.DtoViews.New; 14 | 15 | @NoArgsConstructor 16 | @Setter 17 | @Getter 18 | @SuperBuilder 19 | public class GroupChatDto extends ChatDto { 20 | 21 | @JsonView(Detailed.class) 22 | private Chat.Type type = GROUP; 23 | 24 | @JsonView({New.class, Detailed.class}) 25 | private String name; 26 | 27 | } 28 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/group/NewGroupChatRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.group; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import lombok.Getter; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | @Getter 8 | public class NewGroupChatRequest { 9 | 10 | @NotNull(message = "must be present") 11 | @Length(min = 1, max = 32, message = "length should be in range [1, 32]") 12 | private String name; 13 | 14 | @Length(max = 256, message = "length limit of 256 is exceeded") 15 | private String description; 16 | 17 | @NotNull(message = "must be present") 18 | private Boolean isPrivate; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/chat/group/UpdateGroupChat.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.chat.group; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotNull; 6 | import org.hibernate.validator.constraints.Length; 7 | 8 | public record UpdateGroupChat( 9 | @NotNull @Length(min = 1, max = 32) 10 | String name, 11 | 12 | @Length(max = 256) 13 | String description, 14 | 15 | @NotNull @Min(0) @Max(1000) 16 | Integer membersLimit, 17 | 18 | @NotNull 19 | Boolean isPrivate 20 | ) { 21 | } 22 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/BadRequestDataException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | public class BadRequestDataException extends RuntimeException { 4 | public BadRequestDataException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/ChatOptionsViolationException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | public class ChatOptionsViolationException extends RuntimeException { 4 | public ChatOptionsViolationException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/DtoConverter.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | import org.modelmapper.ModelMapper; 4 | import org.springframework.lang.NonNull; 5 | 6 | public interface DtoConverter { 7 | T convert(@NonNull ModelMapper modelMapper); 8 | } 9 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/DtoViews.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | public interface DtoViews { 4 | 5 | interface New { 6 | } 7 | 8 | interface Detailed { 9 | } 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/PrivacyViolationException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | public class PrivacyViolationException extends RuntimeException { 4 | 5 | public PrivacyViolationException() { 6 | this("Do not have permissions to access the resource"); 7 | } 8 | 9 | public PrivacyViolationException(String message) { 10 | super(message); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/RequestInitiator.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | public record RequestInitiator(Long userId, String bearer) { 4 | } 5 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/RequestUtils.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | import jakarta.servlet.http.HttpServletRequest; 4 | import lombok.experimental.UtilityClass; 5 | import org.linkwave.shared.auth.DefaultUserDetails; 6 | import org.springframework.lang.NonNull; 7 | import org.springframework.security.core.context.SecurityContextHolder; 8 | 9 | import static org.springframework.http.HttpHeaders.AUTHORIZATION; 10 | 11 | @UtilityClass 12 | public class RequestUtils { 13 | 14 | public static DefaultUserDetails userDetails() { 15 | return (DefaultUserDetails) SecurityContextHolder.getContext() 16 | .getAuthentication() 17 | .getPrincipal(); 18 | } 19 | 20 | @NonNull 21 | public static RequestInitiator requestInitiator(@NonNull HttpServletRequest request) { 22 | return new RequestInitiator(userDetails().id(), request.getHeader(AUTHORIZATION)); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | public class ResourceNotFoundException extends RuntimeException { 4 | 5 | public ResourceNotFoundException() { 6 | this("Requested resource not found"); 7 | } 8 | 9 | public ResourceNotFoundException(String message) { 10 | super(message); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/common/UnacceptableRequestDataException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.common; 2 | 3 | public class UnacceptableRequestDataException extends RuntimeException { 4 | 5 | public UnacceptableRequestDataException() { 6 | super(); 7 | } 8 | 9 | public UnacceptableRequestDataException(String message) { 10 | super(message); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/Action.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message; 2 | 3 | public enum Action { 4 | MESSAGE, POLL, FORWARD, REMOVE, FILE, 5 | JOIN, LEAVE, ADD, KICK, 6 | } -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/ChatMessageCursor.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message; 2 | 3 | import lombok.*; 4 | 5 | import java.time.Instant; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Data 10 | @Builder 11 | public class ChatMessageCursor { 12 | 13 | private String chatId; 14 | private Instant timestamp; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/FetchMessageMapping.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message; 2 | 3 | import org.linkwave.chatservice.api.users.UserDto; 4 | import org.modelmapper.ModelMapper; 5 | import org.springframework.lang.NonNull; 6 | 7 | import java.util.Map; 8 | 9 | public interface FetchMessageMapping { 10 | 11 | MessageDto mapForFetch(@NonNull ModelMapper modelMapper, Long fetcherUserId, Map users); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/MessageDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import com.fasterxml.jackson.annotation.JsonView; 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import org.linkwave.chatservice.chat.MessageAuthorDto; 8 | import static org.linkwave.chatservice.common.DtoViews.*; 9 | 10 | import java.time.Instant; 11 | import java.util.List; 12 | 13 | @Getter 14 | @Setter 15 | public class MessageDto { 16 | 17 | @JsonView({Detailed.class, New.class}) 18 | private String id; 19 | 20 | @JsonView({Detailed.class}) 21 | private Action action; 22 | 23 | @JsonView({Detailed.class, New.class}) 24 | private Instant createdAt; 25 | 26 | @JsonView({Detailed.class}) 27 | private MessageAuthorDto author; 28 | 29 | @JsonProperty("isRead") 30 | @JsonView({Detailed.class}) 31 | private Boolean isRead; 32 | 33 | @JsonView({Detailed.class}) 34 | private List reactions; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/MessageNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message; 2 | 3 | public class MessageNotFoundException extends RuntimeException { 4 | 5 | public MessageNotFoundException() { 6 | this("Requested message not found"); 7 | } 8 | 9 | public MessageNotFoundException(String message) { 10 | super(message); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/MessageReaction.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | @Setter 14 | public class MessageReaction { 15 | 16 | private Long memberId; 17 | private String reaction; 18 | private Instant reactedAt; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/ReadMessages.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Getter 14 | @Builder 15 | public class ReadMessages { 16 | 17 | private ChatMessageCursor cursor; 18 | 19 | /** 20 | * General amount of messages that user has not read yet. 21 | */ 22 | private int readCount; 23 | 24 | /** 25 | * Messages that no one has read yet. 26 | */ 27 | @Builder.Default 28 | private List unreadMessages = new ArrayList<>(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/RemovedMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | @Builder 14 | public class RemovedMessage { 15 | 16 | private String messageId; 17 | private String chatId; 18 | private Instant createdAt; 19 | 20 | } -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/file/CreatedFileMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.file; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @Setter 12 | @Getter 13 | @SuperBuilder 14 | public class CreatedFileMessage { 15 | private String id; 16 | private Instant createdAt; 17 | private String filename; 18 | private String contentType; 19 | private long size; 20 | } 21 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/file/FileMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.file; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.experimental.SuperBuilder; 7 | import org.linkwave.chatservice.message.Message; 8 | import org.linkwave.chatservice.message.MessageDto; 9 | import org.modelmapper.ModelMapper; 10 | import org.springframework.data.mongodb.core.mapping.Document; 11 | import org.springframework.lang.NonNull; 12 | 13 | @Document("messages") 14 | @NoArgsConstructor 15 | @Getter 16 | @Setter 17 | @SuperBuilder 18 | public class FileMessage extends Message { 19 | 20 | private String storageFilename; 21 | private String filename; 22 | private String contentType; 23 | private long size; 24 | 25 | @Override 26 | public MessageDto convert(@NonNull ModelMapper modelMapper) { 27 | return modelMapper.map(this, FileMessageDto.class); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/file/FileMessageDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.file; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.linkwave.chatservice.message.MessageDto; 6 | 7 | @Getter 8 | @Setter 9 | public class FileMessageDto extends MessageDto { 10 | 11 | private String filename; 12 | private String contentType; 13 | private long size; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/member/MemberMessageDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.member; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import lombok.*; 5 | import org.linkwave.chatservice.message.MessageDto; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | @Setter 11 | @Builder 12 | public class MemberMessageDto extends MessageDto { 13 | 14 | private Long memberId; 15 | private String username; 16 | private String name; 17 | 18 | @JsonProperty("deleted") 19 | private boolean isDeleted; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/poll/PollMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.poll; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | import lombok.experimental.SuperBuilder; 8 | import org.linkwave.chatservice.message.Message; 9 | import org.springframework.data.mongodb.core.mapping.Document; 10 | 11 | import java.util.ArrayList; 12 | import java.util.List; 13 | 14 | @Document("messages") 15 | @NoArgsConstructor 16 | @Getter 17 | @Setter 18 | @SuperBuilder 19 | public class PollMessage extends Message { 20 | 21 | private String statement; 22 | 23 | @Builder.Default 24 | private List options = new ArrayList<>(); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/poll/PollMessageDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.poll; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.linkwave.chatservice.message.MessageDto; 6 | 7 | import java.util.Map; 8 | 9 | @Getter 10 | @Setter 11 | public class PollMessageDto extends MessageDto { 12 | 13 | private String statement; 14 | private Map options; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/poll/PollOption.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.poll; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | import java.util.List; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | @Setter 14 | public class PollOption { 15 | 16 | private String title; 17 | private List votersIds; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/text/EditTextMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.text; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import lombok.Getter; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | @Getter 8 | public class EditTextMessage { 9 | 10 | @NotNull(message = "must be present") 11 | @Length( 12 | min = 1, max = 512, 13 | message = "length must be in range [1, 512]" 14 | ) 15 | private String text; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/text/NewTextMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.text; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import lombok.Getter; 5 | import org.hibernate.validator.constraints.Length; 6 | 7 | @Getter 8 | public class NewTextMessage { 9 | 10 | @NotNull(message = "must be present") 11 | @Length( 12 | min = 1, max = 512, 13 | message = "length must be in range [1, 512]" 14 | ) 15 | private String text; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/text/TextMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.text; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.Setter; 6 | import lombok.experimental.SuperBuilder; 7 | import org.linkwave.chatservice.message.Message; 8 | import org.linkwave.chatservice.message.MessageDto; 9 | import org.modelmapper.ModelMapper; 10 | import org.springframework.data.mongodb.core.mapping.Document; 11 | import org.springframework.lang.NonNull; 12 | 13 | import java.time.Instant; 14 | 15 | @Document("messages") 16 | @NoArgsConstructor 17 | @Getter 18 | @Setter 19 | @SuperBuilder 20 | public class TextMessage extends Message { 21 | 22 | private String text; 23 | private boolean isEdited; 24 | private Instant editedAt; 25 | 26 | @Override 27 | public MessageDto convert(@NonNull ModelMapper modelMapper) { 28 | return modelMapper.map(this, TextMessageDto.class); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/text/TextMessageDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.text; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | import org.linkwave.chatservice.message.MessageDto; 6 | 7 | @Getter 8 | @Setter 9 | public class TextMessageDto extends MessageDto { 10 | 11 | private String text; 12 | private boolean isEdited; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/message/text/UpdatedTextMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.message.text; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | @Builder 14 | public class UpdatedTextMessage { 15 | 16 | private String messageId; 17 | private String chatId; 18 | private String text; 19 | private Boolean isEdited; 20 | private Instant editedAt; 21 | 22 | } -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/user/UserRepository.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.user; 2 | 3 | import org.springframework.data.mongodb.repository.MongoRepository; 4 | import org.springframework.stereotype.Repository; 5 | 6 | import java.util.Optional; 7 | 8 | @Repository 9 | public interface UserRepository extends MongoRepository { 10 | 11 | Optional findByUserId(Long userId); 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/user/UserService.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.user; 2 | 3 | import java.util.Optional; 4 | 5 | public interface UserService { 6 | Optional getUser(Long userId); 7 | 8 | User createUserIfNeed(Long userId); 9 | 10 | User save(User user); 11 | } 12 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/java/org/linkwave/chatservice/user/UserServiceImpl.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice.user; 2 | 3 | import lombok.RequiredArgsConstructor; 4 | import org.springframework.stereotype.Service; 5 | import org.springframework.transaction.annotation.Transactional; 6 | 7 | import java.util.Optional; 8 | 9 | @Service 10 | @RequiredArgsConstructor 11 | public class UserServiceImpl implements UserService { 12 | 13 | private final UserRepository userRepository; 14 | 15 | @Override 16 | public Optional getUser(Long userId) { 17 | return userRepository.findByUserId(userId); 18 | } 19 | 20 | @Transactional 21 | @Override 22 | public User createUserIfNeed(final Long userId) { 23 | return getUser(userId) 24 | .orElseGet(() -> userRepository.save(User.builder().userId(userId).build())); 25 | } 26 | 27 | @Transactional 28 | @Override 29 | public User save(User user) { 30 | return userRepository.save(user); 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8083 3 | 4 | eureka: 5 | client: 6 | service-url: 7 | defaultZone: 'http://localhost:8761/eureka/' 8 | 9 | spring: 10 | application: 11 | name: 'chat-service' 12 | 13 | web: 14 | locale: en 15 | 16 | cloud: 17 | openfeign: 18 | okhttp: 19 | enabled: true 20 | 21 | servlet: 22 | multipart: 23 | max-file-size: 32MB 24 | max-request-size: 32MB 25 | 26 | data: 27 | mongodb: 28 | database: 'linkwave' 29 | replica-set-name: 'rs0' 30 | 31 | files: 32 | storage-folder: 'D:\Files\storage' 33 | 34 | logging: 35 | level: 36 | org.linkwave.chatservice: DEBUG 37 | org.springframework.data.mongodb.core.MongoTemplate: DEBUG 38 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/resources/docker-init/mongo_rs_init.js: -------------------------------------------------------------------------------- 1 | // Wait for the MongoDB replica set to become available 2 | function waitForMongoDB(timeout) { 3 | 4 | print("Waiting for MongoDB to initialize..."); 5 | 6 | var startTime = new Date(); 7 | var status; 8 | while (true) { 9 | status = rs.status(); 10 | if (status.ok) { 11 | print("MongoDB is initialized!"); 12 | break; 13 | } 14 | 15 | if ((new Date()) - startTime > timeout) { 16 | print("Timeout waiting for MongoDB to initialize"); 17 | break; 18 | } 19 | 20 | print("Waiting for MongoDB to initialize..."); 21 | sleep(1000); 22 | } 23 | } 24 | 25 | // Initialize replica set 26 | rs.initiate({ 27 | _id: "rs0", 28 | members: [ 29 | { _id: 0, host: "localhost:27017" } 30 | ] 31 | }); 32 | 33 | // Wait for MongoDB to initialize 34 | waitForMongoDB(5000); // Adjust timeout as needed 35 | -------------------------------------------------------------------------------- /backend/chat-service/src/main/resources/keys/access_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | 3 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/chat-service/src/test/java/org/linkwave/chatservice/ChatServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.chatservice; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class ChatServiceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/chat-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | fetch-registry: false 4 | register-with-eureka: false 5 | 6 | spring: 7 | application: 8 | name: 'chat-service-test' 9 | 10 | files: 11 | storage-folder: 'D:\Files\storage' -------------------------------------------------------------------------------- /backend/chat-service/src/test/resources/keys/access_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz2yidb7uzIwVPfMm8Ox1 3 | wTagUv2WYGa+plFHHvKBVpB7AiOd+aCzBl6MO/L/xwsaP8jYWt2TDMDy/K+AgIK1 4 | 1xjZfCy0zafhZb9q9rgeiPClyq1GdsT1Vhd0ETZ6nNt+e0WQYKAXaUx1LfETOctO 5 | f2RDhC9ZxeyTn2/fkrw0JO2X9WZuYmpDW2sqMKC9YnmlTsU1tR7wJkAcvu2X4Obe 6 | 1P68ajY6V1neWlAbdpuDW0E8V9dlqH8BT6OZaFAqqgqj9x976xzMEr47DuPoW7AC 7 | 1JjXB8GkADPOJNlyfQ3g99NjQNW3KIQB5jQ20kJp5irWSb6FhS7/atzF/7ammR6y 8 | GwIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/discovery-service/.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 | -------------------------------------------------------------------------------- /backend/discovery-service/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/backend/discovery-service/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /backend/discovery-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /backend/discovery-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:18-jre-alpine 2 | WORKDIR /app 3 | COPY --from=linkwave/backend:latest ./app/discovery-service/target/discovery-service-0.0.1-SNAPSHOT.jar ./discovery-service.jar 4 | CMD ["java", "-jar", "discovery-service.jar"] -------------------------------------------------------------------------------- /backend/discovery-service/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | org.linkwave 8 | backend 9 | 1.0-SNAPSHOT 10 | 11 | 12 | com.example 13 | discovery-service 14 | 0.0.1-SNAPSHOT 15 | discovery-service 16 | Discovery Service application built with Spring Boot 17 | 18 | 19 | 20 | org.springframework.cloud 21 | spring-cloud-starter-netflix-eureka-server 22 | 23 | 24 | org.springframework.boot 25 | spring-boot-starter-actuator 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /backend/discovery-service/src/main/java/org/linkwave/discovery/DiscoveryServiceApplication.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.discovery; 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 DiscoveryServiceApplication { 10 | 11 | public static void main(String[] args) { 12 | SpringApplication.run(DiscoveryServiceApplication.class, args); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/discovery-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8761 3 | 4 | management: 5 | endpoints: 6 | web: 7 | exposure: 8 | include: health 9 | 10 | eureka: 11 | client: 12 | register-with-eureka: false 13 | fetch-registry: false -------------------------------------------------------------------------------- /backend/discovery-service/src/test/java/org/linkwave/discovery/DiscoveryServiceApplicationTests.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.discovery; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.springframework.boot.test.context.SpringBootTest; 5 | 6 | @SpringBootTest 7 | class DiscoveryServiceApplicationTests { 8 | 9 | @Test 10 | void contextLoads() { 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/shared/.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 | -------------------------------------------------------------------------------- /backend/shared/src/main/java/org/linkwave/shared/Main.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.shared; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/shared/src/main/java/org/linkwave/shared/auth/JwtAccessParser.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.shared.auth; 2 | 3 | import com.auth0.jwt.algorithms.Algorithm; 4 | 5 | import java.security.interfaces.RSAPublicKey; 6 | 7 | public class JwtAccessParser implements TokenParser { 8 | 9 | private final Algorithm algorithm; 10 | 11 | public JwtAccessParser(RSAPublicKey publicKey) { 12 | this.algorithm = Algorithm.RSA256(publicKey, null); 13 | } 14 | 15 | @Override 16 | public Token parse(String token) { 17 | return parse(token, algorithm); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/shared/src/main/java/org/linkwave/shared/auth/Token.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.shared.auth; 2 | 3 | import lombok.Builder; 4 | 5 | import java.time.Instant; 6 | import java.util.List; 7 | import java.util.UUID; 8 | 9 | @Builder 10 | public record Token( 11 | UUID id, 12 | Instant createdAt, 13 | Instant expireAt, 14 | Long userId, 15 | String username, 16 | List authorities) { 17 | } 18 | -------------------------------------------------------------------------------- /backend/shared/src/main/java/org/linkwave/shared/auth/TokenSerializer.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.shared.auth; 2 | 3 | import org.springframework.lang.NonNull; 4 | 5 | public interface TokenSerializer { 6 | String serialize(@NonNull Token token); 7 | } 8 | -------------------------------------------------------------------------------- /backend/shared/src/main/java/org/linkwave/shared/dto/ApiError.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.shared.dto; 2 | 3 | import lombok.Builder; 4 | 5 | import java.time.Instant; 6 | 7 | @Builder 8 | public record ApiError( 9 | String path, 10 | String message, 11 | int status, 12 | Instant timestamp) { 13 | } -------------------------------------------------------------------------------- /backend/shared/src/main/java/org/linkwave/shared/utils/Bearers.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.shared.utils; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.springframework.lang.NonNull; 5 | 6 | @UtilityClass 7 | public class Bearers { 8 | 9 | public static final int TOKEN_START_POSITION = 7; 10 | public static final String BEARER_PREFIX = "Bearer "; 11 | 12 | public static String extract(@NonNull String bearer) { 13 | return bearer.substring(TOKEN_START_POSITION); 14 | } 15 | 16 | public static String append(@NonNull String token) { 17 | return "%s%s".formatted(BEARER_PREFIX, token); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/shared/src/main/java/org/linkwave/shared/utils/Headers.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.shared.utils; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | 6 | @AllArgsConstructor 7 | @Getter 8 | public enum Headers { 9 | 10 | TOTAL_COUNT("X-Total-Count"); 11 | 12 | private final String value; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/user-service/.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 | -------------------------------------------------------------------------------- /backend/user-service/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/backend/user-service/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /backend/user-service/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /backend/user-service/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:18-jre-alpine 2 | WORKDIR /app 3 | COPY --from=linkwave/backend:latest ./app/user-service/target/user-service-0.0.1-SNAPSHOT.jar ./user-service.jar 4 | CMD ["java", "-jar", "user-service.jar"] -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/controller/ConstraintErrorMessages.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.controller; 2 | 3 | public class ConstraintErrorMessages { 4 | public static final String PAGINATION_PARAM_MIN_MSG = "must be greater than 0"; 5 | public static final String PAGINATION_PARAM_MAX_MSG = "must be less than 100"; 6 | } 7 | -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/dto/ContactDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.dto; 2 | 3 | import lombok.*; 4 | 5 | import java.time.ZonedDateTime; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Getter 10 | @Setter 11 | @EqualsAndHashCode 12 | @Builder 13 | public class ContactDto { 14 | private UserDto user; 15 | private ZonedDateTime addedAt; 16 | private String alias; 17 | } 18 | -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/dto/NewContactRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.dto; 2 | 3 | import jakarta.validation.constraints.Min; 4 | import jakarta.validation.constraints.NotNull; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | import org.hibernate.validator.constraints.Length; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | public class NewContactRequest { 14 | 15 | @NotNull(message = "must be present") 16 | @Min(value = 1, message = "value must be bigger than 0") 17 | private Long userId; 18 | 19 | @NotNull(message = "must be present") 20 | @Length(min = 3, max = 64, message = "length must be from 3 to 64") 21 | private String alias; 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/dto/UserDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.dto; 2 | 3 | import lombok.*; 4 | 5 | import java.time.ZonedDateTime; 6 | 7 | @NoArgsConstructor 8 | @AllArgsConstructor 9 | @Setter 10 | @Getter 11 | @EqualsAndHashCode 12 | @Builder 13 | public class UserDto { 14 | 15 | private Long id; 16 | private String username; 17 | private String name; 18 | private ZonedDateTime createdAt; 19 | private ZonedDateTime lastSeen; 20 | private boolean isOnline; 21 | private String avatarPath; 22 | private boolean isDeleted; 23 | private boolean isBlocked; 24 | private String bio; 25 | 26 | } -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/dto/UserRegisterRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.dto; 2 | 3 | import jakarta.validation.constraints.NotNull; 4 | import jakarta.validation.constraints.Size; 5 | import lombok.AllArgsConstructor; 6 | import lombok.Getter; 7 | import lombok.NoArgsConstructor; 8 | 9 | @NoArgsConstructor 10 | @AllArgsConstructor 11 | @Getter 12 | public class UserRegisterRequest { 13 | 14 | @NotNull(message = "can't be null") 15 | @Size(min = 3, max = 32, message = "length should be from 3 to 32 characters") 16 | private String username; 17 | 18 | @NotNull(message = "can't be null") 19 | @Size(min = 3, max = 64, message = "length should be from 3 to 64 characters") 20 | private String password; 21 | 22 | @NotNull(message = "can't be null") 23 | @Size(min = 3, max = 64, message = "length should be from 3 to 64 characters") 24 | private String name; 25 | 26 | } -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/entity/ContactEntity.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.entity; 2 | 3 | import jakarta.persistence.*; 4 | import lombok.*; 5 | 6 | import java.time.ZonedDateTime; 7 | 8 | @Entity 9 | @Table(name = "contacts") 10 | @Getter 11 | @Setter 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Builder 15 | public class ContactEntity { 16 | 17 | @Id 18 | @GeneratedValue(strategy = GenerationType.IDENTITY) 19 | private Long id; 20 | 21 | @Column(name = "user_id_1") 22 | private Long ownerId; 23 | 24 | @ManyToOne(fetch = FetchType.LAZY) 25 | @JoinColumn(name = "user_id_2") 26 | private UserEntity user; 27 | 28 | @Builder.Default 29 | private ZonedDateTime addedAt = ZonedDateTime.now(); 30 | 31 | @Column(nullable = false, length = 64) 32 | private String alias; 33 | 34 | } 35 | -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/entity/RoleEntity.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.entity; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnore; 4 | import jakarta.persistence.*; 5 | import lombok.*; 6 | 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | @Entity 11 | @Table(name = "roles") 12 | @NoArgsConstructor 13 | @AllArgsConstructor 14 | @Data 15 | @Builder 16 | @ToString(exclude = "users") 17 | @EqualsAndHashCode(exclude = "users") 18 | public class RoleEntity { 19 | 20 | @Id 21 | @GeneratedValue(strategy = GenerationType.IDENTITY) 22 | private Integer id; 23 | 24 | @Column(unique = true, nullable = false) 25 | private String name; 26 | 27 | @JsonIgnore 28 | @ManyToMany(mappedBy = "roles") 29 | @Builder.Default 30 | private List users = new ArrayList<>(); 31 | 32 | @Getter 33 | @RequiredArgsConstructor 34 | public enum Roles { 35 | 36 | USER("ROLE_USER"), 37 | ADMIN("ROLE_ADMIN"); 38 | 39 | private final String value; 40 | 41 | } 42 | 43 | } -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/exception/LimitExceededException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.exception; 2 | 3 | public class LimitExceededException extends RuntimeException { 4 | public LimitExceededException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/exception/ResourceNotFoundException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.exception; 2 | 3 | public class ResourceNotFoundException extends RuntimeException { 4 | 5 | public ResourceNotFoundException() { 6 | this("Requested resource not found"); 7 | } 8 | 9 | public ResourceNotFoundException(String message) { 10 | super(message); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/exception/UnacceptableRequestDataException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.exception; 2 | 3 | public class UnacceptableRequestDataException extends RuntimeException { 4 | public UnacceptableRequestDataException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/user-service/src/main/java/org/linkwave/userservice/repository/RoleRepository.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.userservice.repository; 2 | 3 | import org.linkwave.userservice.entity.RoleEntity; 4 | import org.springframework.data.jpa.repository.JpaRepository; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import java.util.Optional; 8 | 9 | @Repository 10 | public interface RoleRepository extends JpaRepository { 11 | Optional findByName(String name); 12 | } -------------------------------------------------------------------------------- /backend/user-service/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 8082 3 | 4 | eureka: 5 | client: 6 | service-url: 7 | defaultZone: 'http://localhost:8761/eureka/' 8 | 9 | management: 10 | endpoints: 11 | web: 12 | exposure: 13 | include: health 14 | 15 | spring: 16 | application: 17 | name: 'user-service' 18 | 19 | servlet: 20 | multipart: 21 | max-file-size: 32MB 22 | max-request-size: 32MB 23 | 24 | datasource: 25 | url: jdbc:postgresql://localhost:5432/lw-users 26 | username: root 27 | password: 123 28 | 29 | jpa: 30 | properties: 31 | hibernate: 32 | show_sql: false 33 | format_sql: true 34 | highlight_sql: true 35 | 36 | hibernate: 37 | ddl-auto: update 38 | naming: 39 | physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy 40 | 41 | files: 42 | storage-folder: 'D:\Files\storage' 43 | -------------------------------------------------------------------------------- /backend/user-service/src/main/resources/docker-init/users_db.sql: -------------------------------------------------------------------------------- 1 | start transaction; 2 | 3 | create table roles 4 | ( 5 | id serial primary key, 6 | name varchar(32) unique not null 7 | ); 8 | 9 | insert into roles (name) 10 | values ('ROLE_USER'), 11 | ('ROLE_ADMIN'); 12 | 13 | commit; -------------------------------------------------------------------------------- /backend/user-service/src/main/resources/keys/access_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | 3 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/user-service/src/test/resources/application.yml: -------------------------------------------------------------------------------- 1 | eureka: 2 | client: 3 | fetch-registry: false 4 | register-with-eureka: false 5 | 6 | spring: 7 | application: 8 | name: 'user-service-test' 9 | 10 | datasource: 11 | url: jdbc:h2:mem:lw-chat-test 12 | 13 | sql: 14 | init: 15 | mode: never 16 | 17 | jpa: 18 | show-sql: true 19 | properties: 20 | hibernate: 21 | format_sql: true 22 | highlight_sql: true 23 | 24 | hibernate: 25 | ddl-auto: create 26 | naming: 27 | physical-strategy: org.hibernate.boot.model.naming.CamelCaseToUnderscoresNamingStrategy 28 | 29 | files: 30 | storage-folder: 'D:\Files\storage' 31 | -------------------------------------------------------------------------------- /backend/user-service/src/test/resources/keys/access_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz2yidb7uzIwVPfMm8Ox1 3 | wTagUv2WYGa+plFHHvKBVpB7AiOd+aCzBl6MO/L/xwsaP8jYWt2TDMDy/K+AgIK1 4 | 1xjZfCy0zafhZb9q9rgeiPClyq1GdsT1Vhd0ETZ6nNt+e0WQYKAXaUx1LfETOctO 5 | f2RDhC9ZxeyTn2/fkrw0JO2X9WZuYmpDW2sqMKC9YnmlTsU1tR7wJkAcvu2X4Obe 6 | 1P68ajY6V1neWlAbdpuDW0E8V9dlqH8BT6OZaFAqqgqj9x976xzMEr47DuPoW7AC 7 | 1JjXB8GkADPOJNlyfQ3g99NjQNW3KIQB5jQ20kJp5irWSb6FhS7/atzF/7ammR6y 8 | GwIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/ws-server/.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 | -------------------------------------------------------------------------------- /backend/ws-server/.mvn/wrapper/maven-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/backend/ws-server/.mvn/wrapper/maven-wrapper.jar -------------------------------------------------------------------------------- /backend/ws-server/.mvn/wrapper/maven-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.5/apache-maven-3.9.5-bin.zip 2 | wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar 3 | -------------------------------------------------------------------------------- /backend/ws-server/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:18-jre-alpine 2 | WORKDIR /app 3 | COPY --from=linkwave/backend:latest ./app/ws-server/target/ws-0.0.1-SNAPSHOT.jar ./ws.jar 4 | CMD ["java", "-jar", "ws.jar"] -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/WebSocketServerApplication.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.springframework.boot.SpringApplication; 5 | import org.springframework.boot.autoconfigure.SpringBootApplication; 6 | import org.springframework.cloud.openfeign.EnableFeignClients; 7 | import org.springframework.context.annotation.Bean; 8 | 9 | @SpringBootApplication 10 | @EnableFeignClients 11 | public class WebSocketServerApplication { 12 | 13 | public static void main(String[] args) { 14 | SpringApplication.run(WebSocketServerApplication.class, args); 15 | } 16 | 17 | @Bean 18 | public ObjectMapper objectMapper() { 19 | ObjectMapper objectMapper = new ObjectMapper(); 20 | objectMapper.findAndRegisterModules(); 21 | return objectMapper; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/ApiErrorDecoder.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import feign.Response; 5 | import feign.codec.ErrorDecoder; 6 | import lombok.RequiredArgsConstructor; 7 | import lombok.SneakyThrows; 8 | import org.linkwave.shared.dto.ApiError; 9 | import org.springframework.lang.NonNull; 10 | import org.springframework.stereotype.Component; 11 | 12 | @Component 13 | @RequiredArgsConstructor 14 | public class ApiErrorDecoder implements ErrorDecoder { 15 | 16 | private final ObjectMapper objectMapper; 17 | 18 | @SneakyThrows 19 | @Override 20 | public Exception decode(String s, @NonNull Response response) { 21 | final String message; 22 | if (response.body() == null) { 23 | message = ""; 24 | } else { 25 | final var apiError = objectMapper.readValue(response.body().asInputStream(), ApiError.class); 26 | message = apiError.message(); 27 | } 28 | return new ApiErrorException(message); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/ApiErrorException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api; 2 | 3 | public class ApiErrorException extends RuntimeException { 4 | public ApiErrorException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/ChatMember.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | @Builder 14 | public class ChatMember { 15 | 16 | private Long id; 17 | private String role; 18 | private Instant joinedAt; 19 | 20 | } 21 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/ChatMemberDetailsDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.*; 4 | 5 | @NoArgsConstructor 6 | @AllArgsConstructor 7 | @Getter 8 | @Setter 9 | @Builder 10 | public class ChatMemberDetailsDto { 11 | 12 | private String username; 13 | private String name; 14 | private boolean isAvatarAvailable; 15 | private boolean isOnline; 16 | 17 | } 18 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/ChatMemberDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @Getter 12 | @AllArgsConstructor 13 | @Builder 14 | public class ChatMemberDto { 15 | 16 | private Long id; 17 | private String role; 18 | private Instant joinedAt; 19 | private ChatMemberDetailsDto details; 20 | 21 | } 22 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/ChatMessageCursor.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.*; 4 | 5 | import java.time.Instant; 6 | 7 | @Getter 8 | public class ChatMessageCursor { 9 | 10 | private String chatId; 11 | private Instant timestamp; 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/ChatRole.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | public enum ChatRole { 4 | MEMBER, 5 | ADMIN 6 | } 7 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/CreatedFileMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.time.Instant; 7 | 8 | @Setter 9 | @Getter 10 | public class CreatedFileMessage { 11 | private String id; 12 | private Instant createdAt; 13 | private String filename; 14 | private String contentType; 15 | private long size; 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/GroupChatDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.*; 5 | 6 | import java.time.Instant; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Getter 11 | @Setter 12 | @JsonIgnoreProperties(ignoreUnknown = true) 13 | @EqualsAndHashCode 14 | @Builder 15 | public class GroupChatDto { 16 | 17 | private String id; 18 | private Instant createdAt; 19 | private String name; 20 | private String avatarPath; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/MessageDto.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.Getter; 4 | import lombok.Setter; 5 | 6 | import java.time.Instant; 7 | 8 | @Getter 9 | @Setter 10 | public class MessageDto { 11 | 12 | private String id; 13 | private Instant createdAt; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/NewChatRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Getter 11 | @Setter 12 | public class NewChatRequest { 13 | 14 | private Long recipient; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/NewTextMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.Setter; 7 | 8 | @NoArgsConstructor 9 | @AllArgsConstructor 10 | @Getter 11 | @Setter 12 | public class NewTextMessage { 13 | 14 | private String text; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/ReadMessages.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @NoArgsConstructor 12 | @AllArgsConstructor 13 | @Getter 14 | @Builder 15 | public class ReadMessages { 16 | 17 | private ChatMessageCursor cursor; 18 | 19 | /** 20 | * General amount of messages that user has not read yet. 21 | */ 22 | private int readCount; 23 | 24 | /** 25 | * Messages that no one has read yet. 26 | */ 27 | @Builder.Default 28 | private List unreadMessages = new ArrayList<>(); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/RemovedMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | @Builder 14 | public class RemovedMessage { 15 | 16 | private String messageId; 17 | private String chatId; 18 | private Instant createdAt; 19 | 20 | } -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/chat/UpdatedTextMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.chat; 2 | 3 | import lombok.AllArgsConstructor; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | 8 | import java.time.Instant; 9 | 10 | @NoArgsConstructor 11 | @AllArgsConstructor 12 | @Getter 13 | @Builder 14 | public class UpdatedTextMessage { 15 | 16 | private String messageId; 17 | private String chatId; 18 | private String text; 19 | private Boolean isEdited; 20 | private Instant editedAt; 21 | 22 | } -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/api/users/UserServiceClient.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.api.users; 2 | 3 | import org.springframework.cloud.openfeign.FeignClient; 4 | import org.springframework.web.bind.annotation.PatchMapping; 5 | import org.springframework.web.bind.annotation.PathVariable; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | 8 | @FeignClient(value = "user-service", path = "/api/v1/users") 9 | public interface UserServiceClient { 10 | 11 | @PatchMapping("/{id}/online") 12 | void updateUserStatus(@PathVariable Long id, @RequestParam Boolean value); 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/controller/LoadChatRequest.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.controller; 2 | 3 | import jakarta.validation.constraints.Max; 4 | import jakarta.validation.constraints.Min; 5 | import jakarta.validation.constraints.NotBlank; 6 | import jakarta.validation.constraints.NotNull; 7 | import lombok.Getter; 8 | import lombok.Setter; 9 | 10 | @Setter 11 | @Getter 12 | public class LoadChatRequest { 13 | 14 | @NotBlank(message = "must be present") 15 | private String chatId; 16 | 17 | @NotNull(message = "must be present") 18 | @Min(value = 1, message = "must be bigger than 0") 19 | @Max(value = Long.MAX_VALUE, message = "max limit is exceeded") 20 | private Long recipientId; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/repository/SessionRepository.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.repository; 2 | 3 | import java.util.Set; 4 | 5 | public interface SessionRepository { 6 | Set getUserSessions(T userId); 7 | 8 | Set getUserSessions(String customKey); 9 | 10 | void saveSession(T userId, String sessionId); 11 | 12 | void removeSession(T userId, String sessionId); 13 | 14 | boolean hasSessions(T userId); 15 | 16 | Set getSessions(String customKey); 17 | } 18 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/utils/RouteUtils.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.utils; 2 | 3 | import lombok.experimental.UtilityClass; 4 | import org.springframework.lang.NonNull; 5 | 6 | @UtilityClass 7 | public class RouteUtils { 8 | 9 | public static final String ROUTE_DELIMITER = "/"; 10 | public static final String PATH_VAR_PREFIX = "{"; 11 | public static final String PATH_VAR_POSTFIX = "}"; 12 | 13 | public static boolean isPathVariable(@NonNull String s) { 14 | return s.startsWith(PATH_VAR_PREFIX) && s.endsWith(PATH_VAR_POSTFIX); 15 | } 16 | 17 | @NonNull 18 | public static String getPathVariable(@NonNull String s) { 19 | return s.substring(1, s.length() - 1); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/WebSocketSessionConfigurer.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket; 2 | 3 | import org.springframework.lang.NonNull; 4 | import org.springframework.web.socket.WebSocketSession; 5 | 6 | public interface WebSocketSessionConfigurer { 7 | WebSocketSession configure(@NonNull WebSocketSession session); 8 | } 9 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/WebSocketSessionConfigurerImpl.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket; 2 | 3 | import org.springframework.beans.factory.annotation.Value; 4 | import org.springframework.lang.NonNull; 5 | import org.springframework.stereotype.Component; 6 | import org.springframework.web.socket.WebSocketSession; 7 | import org.springframework.web.socket.handler.ConcurrentWebSocketSessionDecorator; 8 | 9 | @Component 10 | public class WebSocketSessionConfigurerImpl implements WebSocketSessionConfigurer { 11 | 12 | @Value("${ws.session.concurrent.send-time-limit}") 13 | private int sendTimeLimit; 14 | 15 | @Value("${ws.session.concurrent.buffer-size-limit}") 16 | private int bufferSizeLimit; 17 | 18 | @Override 19 | public WebSocketSession configure(@NonNull WebSocketSession session) { 20 | return new ConcurrentWebSocketSessionDecorator(session, sendTimeLimit, bufferSizeLimit); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/Action.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | public enum Action { 4 | 5 | // Chat 6 | JOIN, LEAVE, ADD, KICK, SET_ROLE, 7 | 8 | // User status 9 | ONLINE, OFFLINE, 10 | 11 | // Messages 12 | MESSAGE, FILE, BIND, UPD_MESSAGE, 13 | READ, UNREAD_MESSAGES, 14 | REMOVE, CLEAR_HISTORY, CHAT_DELETED, 15 | 16 | // Error 17 | ERROR 18 | } 19 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/BaseMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Builder; 5 | import lombok.Getter; 6 | import lombok.NoArgsConstructor; 7 | import lombok.experimental.SuperBuilder; 8 | 9 | import java.time.Instant; 10 | 11 | @NoArgsConstructor 12 | @Getter 13 | @SuperBuilder 14 | @JsonIgnoreProperties(ignoreUnknown = true) 15 | public class BaseMessage { 16 | 17 | private Action action; 18 | 19 | @Builder.Default 20 | private Instant timestamp = Instant.now(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/BindMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | public record BindMessage(Action action, String chatId, String tmpMessageId, String messageId) { 4 | } 5 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/ChatMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | @JsonIgnoreProperties(ignoreUnknown = true) 11 | @SuperBuilder 12 | public class ChatMessage extends StatusMessage { 13 | 14 | private String chatId; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/ChatRoleMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.experimental.SuperBuilder; 6 | import org.linkwave.ws.api.chat.ChatRole; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | @SuperBuilder 11 | public class ChatRoleMessage extends ChatMessage { 12 | 13 | private Long memberId; 14 | private ChatRole role; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/ErrorMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.EqualsAndHashCode; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | @EqualsAndHashCode(callSuper = true) 11 | @SuperBuilder 12 | public class ErrorMessage extends BaseMessage { 13 | 14 | private String error; 15 | private String path; 16 | 17 | public static ErrorMessage create(String error, String path) { 18 | return ErrorMessage.builder() 19 | .action(Action.ERROR) 20 | .error(error) 21 | .path(path) 22 | .build(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/IdentifiedMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.experimental.SuperBuilder; 6 | 7 | @NoArgsConstructor 8 | @Getter 9 | @SuperBuilder 10 | public class IdentifiedMessage extends ChatMessage { 11 | 12 | private String id; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/IncomeMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | public record IncomeMessage(String tmpMessageId, String text) { 4 | } 5 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/LastReadMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Getter; 4 | 5 | import java.time.Instant; 6 | 7 | @Getter 8 | public class LastReadMessage { 9 | 10 | private Instant timestamp; 11 | 12 | } 13 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/MemberMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.experimental.SuperBuilder; 6 | import org.linkwave.ws.api.chat.ChatMemberDetailsDto; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | @SuperBuilder 11 | public class MemberMessage extends ChatMessage { 12 | 13 | private Long memberId; 14 | private ChatMemberDetailsDto memberDetails; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/NewChatRole.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Getter; 4 | import org.linkwave.ws.api.chat.ChatRole; 5 | 6 | @Getter 7 | public class NewChatRole { 8 | 9 | private ChatRole role; 10 | 11 | } 12 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/NewGroupChat.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.*; 4 | 5 | @NoArgsConstructor 6 | @AllArgsConstructor 7 | @Getter 8 | @Setter 9 | @Builder 10 | public class NewGroupChat { 11 | 12 | private String name; 13 | private String description; 14 | private Boolean isPrivate; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/OutcomeFileMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | @SuperBuilder 11 | public class OutcomeFileMessage extends IdentifiedMessage { 12 | 13 | @Builder.Default 14 | private Action action = Action.FILE; 15 | private String filename; 16 | private String contentType; 17 | private long size; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/OutcomeMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Getter; 4 | import lombok.NoArgsConstructor; 5 | import lombok.experimental.SuperBuilder; 6 | 7 | @NoArgsConstructor 8 | @Getter 9 | @SuperBuilder 10 | public class OutcomeMessage extends IdentifiedMessage { 11 | 12 | private String text; 13 | 14 | } 15 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/ReadMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | import java.util.ArrayList; 9 | import java.util.List; 10 | 11 | @NoArgsConstructor 12 | @Getter 13 | @SuperBuilder 14 | public class ReadMessage extends ChatMessage { 15 | 16 | @Builder.Default 17 | private Action action = Action.READ; 18 | 19 | @Builder.Default 20 | private List messages = new ArrayList<>(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/StatusMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import com.fasterxml.jackson.annotation.JsonIgnoreProperties; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | @NoArgsConstructor 9 | @Getter 10 | @JsonIgnoreProperties(ignoreUnknown = true) 11 | @SuperBuilder 12 | public class StatusMessage extends BaseMessage { 13 | 14 | private Long senderId; 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/dto/UnreadMessages.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.dto; 2 | 3 | import lombok.Builder; 4 | import lombok.Getter; 5 | import lombok.NoArgsConstructor; 6 | import lombok.experimental.SuperBuilder; 7 | 8 | import java.util.HashMap; 9 | import java.util.Map; 10 | 11 | @NoArgsConstructor 12 | @Getter 13 | @SuperBuilder 14 | public class UnreadMessages extends BaseMessage { 15 | 16 | @Builder.Default 17 | private Action action = Action.UNREAD_MESSAGES; 18 | 19 | @Builder.Default 20 | private Map chats = new HashMap<>(); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/jwt/UserPrincipal.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.jwt; 2 | 3 | import org.springframework.lang.NonNull; 4 | 5 | import org.linkwave.shared.auth.Token; 6 | 7 | import java.security.Principal; 8 | 9 | public record UserPrincipal(String rawAccessToken, @NonNull Token token) implements Principal { 10 | 11 | @Override 12 | public String getName() { 13 | return token.username(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/Box.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing; 2 | 3 | import lombok.AccessLevel; 4 | import lombok.AllArgsConstructor; 5 | import lombok.Getter; 6 | import org.springframework.lang.NonNull; 7 | 8 | @AllArgsConstructor(access = AccessLevel.PRIVATE) 9 | @Getter 10 | public class Box { 11 | 12 | private T value; 13 | private Object errorValue; 14 | 15 | public boolean isEmpty() { 16 | return value == null && !hasError(); 17 | } 18 | 19 | public boolean hasError() { 20 | return errorValue != null; 21 | } 22 | 23 | @NonNull 24 | public static Box ok(T value) { 25 | return new Box<>(value, null); 26 | } 27 | 28 | @NonNull 29 | public static Box ok() { 30 | return new Box<>(null, null); 31 | } 32 | 33 | @NonNull 34 | public static Box error(Object errorValue) { 35 | return new Box<>(null, errorValue); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/EndpointCondition.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing; 2 | 3 | import org.linkwave.ws.websocket.routing.bpp.Endpoint; 4 | import org.linkwave.ws.websocket.routing.exception.ConditionViolatedException; 5 | import org.springframework.lang.NonNull; 6 | 7 | @FunctionalInterface 8 | public interface EndpointCondition { 9 | 10 | /** 11 | * Defines the condition that message should pass through 12 | * in order to get handled by route handler. The condition can be set up in {@link Endpoint#conditions()}. 13 | * 14 | * @param context message context 15 | * @throws ConditionViolatedException if condition is violated 16 | */ 17 | void check(@NonNull MessageContext context) throws ConditionViolatedException; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/MessageContext.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing; 2 | 3 | import org.springframework.web.socket.WebSocketSession; 4 | 5 | import java.util.Map; 6 | import java.util.Map.Entry; 7 | 8 | /** 9 | * 10 | * @param route defined route 11 | * @param pathVariables resolved parts of defined route 12 | * @param routingMessage raw message is needed to be handled 13 | * @param session message initiator 14 | */ 15 | public record MessageContext( 16 | Entry route, 17 | Map pathVariables, 18 | RoutingMessage routingMessage, 19 | WebSocketSession session) { 20 | } 21 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/Payload.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import org.linkwave.ws.websocket.routing.args.RouteHandlerArgumentResolver; 9 | import org.linkwave.ws.websocket.routing.bpp.Endpoint; 10 | 11 | /** 12 | * Used to indicate parameter as message payload in method annotated with {@link Endpoint} (i.e. route handler). 13 | * Moreover, the annotated parameter is involved in {@link RouteHandlerArgumentResolver} 14 | * in message injection. 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target(ElementType.PARAMETER) 18 | public @interface Payload { 19 | } 20 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/RouteComponent.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.List; 5 | 6 | import org.linkwave.ws.websocket.routing.bpp.WebSocketRoute; 7 | import org.linkwave.ws.websocket.routing.bpp.Endpoint; 8 | 9 | /** 10 | * Just a convenient container that holds reference to object or bean annotated 11 | * with {@link WebSocketRoute} is responsible for handling several routes and 12 | * one of handler that's declared in that bean. 13 | * 14 | * @param beanRoute component with route handlers declaration 15 | * @param routeHandler one of the declared handlers ({@link Endpoint}) 16 | * @param conditions list of object that define conditions to invoke handler 17 | */ 18 | public record RouteComponent( 19 | Object beanRoute, 20 | Method routeHandler, 21 | List conditions) { 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/RouteHandlerInvocator.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing; 2 | 3 | import org.linkwave.ws.websocket.routing.exception.InvalidMessageFormatException; 4 | import org.linkwave.ws.websocket.routing.exception.InvalidPathException; 5 | import org.springframework.lang.NonNull; 6 | 7 | @FunctionalInterface 8 | public interface RouteHandlerInvocator { 9 | 10 | /** 11 | * Handles the router handler invocation process, including resolving route's arguments. 12 | * 13 | * @param context message context 14 | * @return handler invocation result 15 | * @throws InvalidPathException if the message has invalid structure such as 16 | * path representation or payload type, etc. 17 | * @throws InvalidMessageFormatException if path is unavailable to parse 18 | */ 19 | Object delegateInvocation(@NonNull MessageContext context) 20 | throws InvalidPathException, InvalidMessageFormatException; 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/RoutingMessage.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing; 2 | 3 | /** 4 | * @param path that message needs to reach to be handled 5 | * @param payload raw content of received message 6 | */ 7 | public record RoutingMessage(String path, String payload) { 8 | } 9 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/WebSocketRouter.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing; 2 | 3 | import org.linkwave.ws.websocket.routing.exception.InvalidMessageFormatException; 4 | import org.linkwave.ws.websocket.routing.exception.InvalidPathException; 5 | import org.springframework.web.socket.WebSocketSession; 6 | 7 | public interface WebSocketRouter { 8 | 9 | /** 10 | * 11 | * @param message that needs to be delivered to specific route handler 12 | * @param session message initiator 13 | * @throws InvalidMessageFormatException if message is unavailable to route 14 | * @throws InvalidPathException if path is incorrect or does not exist 15 | */ 16 | void route(String message, WebSocketSession session) 17 | throws InvalidMessageFormatException, InvalidPathException; 18 | 19 | } 20 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/args/ArgumentResolverStrategy.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.args; 2 | 3 | import org.linkwave.ws.websocket.routing.MessageContext; 4 | import org.linkwave.ws.websocket.routing.exception.InvalidMessageFormatException; 5 | import org.linkwave.ws.websocket.routing.exception.InvalidPathException; 6 | import org.springframework.lang.NonNull; 7 | 8 | import java.lang.reflect.Parameter; 9 | 10 | public interface ArgumentResolverStrategy { 11 | 12 | Object resolve(@NonNull MessageContext context, @NonNull Parameter param) 13 | throws InvalidMessageFormatException, InvalidPathException; 14 | 15 | } 16 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/bpp/Broadcasts.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.bpp; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * Container for {@link Broadcast} annotations in order to 10 | * support broadcast for different destinations. 11 | */ 12 | @Retention(RetentionPolicy.RUNTIME) 13 | @Target(ElementType.METHOD) 14 | public @interface Broadcasts { 15 | 16 | Broadcast[] value() default {}; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/bpp/Endpoint.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.bpp; 2 | 3 | import org.linkwave.ws.websocket.routing.EndpointCondition; 4 | 5 | import java.lang.annotation.ElementType; 6 | import java.lang.annotation.Retention; 7 | import java.lang.annotation.RetentionPolicy; 8 | import java.lang.annotation.Target; 9 | 10 | /** 11 | * This annotation is used to mark methods of {@code @WebSocketRoute} beans that are 12 | * aimed to handle received messages for specific endpoint. 13 | * 14 | * @see WebSocketRoute 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target(ElementType.METHOD) 18 | public @interface Endpoint { 19 | String value() default ""; 20 | 21 | /** 22 | * Prevents route handler from registration. 23 | */ 24 | boolean disabled() default false; 25 | 26 | /** 27 | * List of conditions that should be passed through to get route handler invoked. 28 | */ 29 | Class[] conditions() default {}; 30 | } 31 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/bpp/WebSocketRoute.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.bpp; 2 | 3 | import org.springframework.core.annotation.AliasFor; 4 | import org.springframework.stereotype.Component; 5 | 6 | import java.lang.annotation.ElementType; 7 | import java.lang.annotation.Retention; 8 | import java.lang.annotation.RetentionPolicy; 9 | import java.lang.annotation.Target; 10 | 11 | /** 12 | * This annotation is used to define bean that can hold 13 | * route handlers, i.e. methods marked with {@link Endpoint} 14 | * 15 | * @see Endpoint 16 | */ 17 | @Component 18 | @Retention(RetentionPolicy.RUNTIME) 19 | @Target(ElementType.TYPE) 20 | public @interface WebSocketRoute { 21 | 22 | @AliasFor(annotation=Component.class) 23 | String value(); 24 | 25 | } 26 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/broadcast/BroadcastManager.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.broadcast; 2 | 3 | import org.springframework.lang.NonNull; 4 | 5 | import java.lang.reflect.Method; 6 | import java.util.Map; 7 | 8 | import org.linkwave.ws.websocket.routing.bpp.Broadcast; 9 | 10 | /** 11 | * Built-in component that is used by router to automatically broadcast message 12 | * using {@link Broadcast} annotation. Broadcast manager is informed about what, how and where 13 | * the message must be delivered. 14 | * 15 | * @see Broadcast 16 | */ 17 | public interface BroadcastManager { 18 | 19 | String KEY_SEPARATOR = ":"; 20 | 21 | /** 22 | * @param routeHandler object with corresponding route 23 | * @param serializedMessage text message to deliver 24 | */ 25 | void process(@NonNull Method routeHandler, @NonNull Map pathVariables, 26 | @NonNull Object message, @NonNull String serializedMessage); 27 | 28 | boolean isBroadcast(@NonNull Method routeHandler); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/broadcast/BroadcastRepositoryResolver.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.broadcast; 2 | 3 | import org.linkwave.ws.websocket.routing.bpp.Broadcast; 4 | import org.springframework.lang.NonNull; 5 | 6 | import java.util.Set; 7 | 8 | /** 9 | * Defines which sessions should be retrieved that corresponds to
10 | * key-pattern set in {@link Broadcast#value()}. 11 | */ 12 | public interface BroadcastRepositoryResolver { 13 | 14 | /** 15 | * Retrieves a set of sessions ids based on key-pattern. 16 | * @param broadcastKeyPattern key-pattern set in {@link Broadcast#value()} 17 | * @param resolvedKeyPattern key-pattern with resolved key variables 18 | * @return set of sessions ids that matched the specified criteria 19 | */ 20 | Set resolve(@NonNull String broadcastKeyPattern, @NonNull String resolvedKeyPattern); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/broadcast/WebSocketMessageBroadcast.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.broadcast; 2 | 3 | import org.springframework.lang.NonNull; 4 | 5 | import java.io.IOException; 6 | import java.util.Set; 7 | 8 | /** 9 | * Core component under the hood in {@link BroadcastManager} that does low-level job 10 | * to deliver messages directly to clients via web socket sessions. 11 | * 12 | * @see org.springframework.web.socket.WebSocketSession 13 | */ 14 | public interface WebSocketMessageBroadcast { 15 | 16 | /** 17 | * @param sessionIds ids of connected clients that are needed to be delivered message to 18 | * @param serializedMessage serialized object (message) that should be delivered 19 | * @return true if all users received message (all session are present in current instance) 20 | * @throws IOException when message sending is failed to at least one session 21 | */ 22 | boolean share(@NonNull Set sessionIds, String serializedMessage) throws IOException; 23 | 24 | } 25 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/broadcast/instances/MessageDelegate.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.broadcast.instances; 2 | 3 | import org.springframework.data.redis.listener.adapter.MessageListenerAdapter; 4 | import org.springframework.lang.NonNull; 5 | 6 | /** 7 | * Interface used in {@code MessageListenerAdapter} in order to delegate 8 | * handling messages received from topic using {@code Redis Pub/Sub} 9 | * 10 | * @see MessageListenerAdapter 11 | */ 12 | public interface MessageDelegate { 13 | void handleMessage(@NonNull String message); 14 | } 15 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/exception/ConditionViolatedException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.exception; 2 | 3 | public class ConditionViolatedException extends RuntimeException { 4 | public ConditionViolatedException(String message) { 5 | super(message); 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/exception/InvalidMessageFormatException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.exception; 2 | 3 | public class InvalidMessageFormatException extends Exception { 4 | 5 | public InvalidMessageFormatException(String message) { 6 | super(message); 7 | } 8 | 9 | public InvalidMessageFormatException(Throwable cause) { 10 | super(cause); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/exception/InvalidPathException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.exception; 2 | 3 | public class InvalidPathException extends Exception { 4 | 5 | public InvalidPathException(String message) { 6 | super(message); 7 | } 8 | 9 | public InvalidPathException(Throwable cause) { 10 | super(cause); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/exception/RoutingException.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.exception; 2 | 3 | public class RoutingException extends RuntimeException { 4 | 5 | public RoutingException(String message) { 6 | super(message); 7 | } 8 | 9 | public RoutingException(String message, Throwable cause) { 10 | super(message, cause); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/routing/parser/MessageParser.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.routing.parser; 2 | 3 | import org.linkwave.ws.websocket.routing.RoutingMessage; 4 | import org.linkwave.ws.websocket.routing.exception.InvalidMessageFormatException; 5 | 6 | /** 7 | * Parses not encoded raw message referring to specified message structure of protocol. 8 | */ 9 | public interface MessageParser { 10 | 11 | /** 12 | * @param raw not encoded message 13 | * @return non-null component that contains divided message by path 14 | * @throws InvalidMessageFormatException if message is invalid and can not be parsed 15 | * based on specified protocol 16 | */ 17 | RoutingMessage parse(String raw) throws InvalidMessageFormatException; 18 | } 19 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/session/callback/AfterConnectionClosed.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.session.callback; 2 | 3 | import org.springframework.lang.NonNull; 4 | import org.springframework.web.socket.WebSocketSession; 5 | 6 | /** 7 | * Callback that is invoked when user disconnected from server. 8 | */ 9 | @FunctionalInterface 10 | public interface AfterConnectionClosed { 11 | void afterDisconnected(@NonNull WebSocketSession session); 12 | } 13 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/java/org/linkwave/ws/websocket/session/callback/AfterConnectionEstablished.java: -------------------------------------------------------------------------------- 1 | package org.linkwave.ws.websocket.session.callback; 2 | 3 | import org.springframework.lang.NonNull; 4 | import org.springframework.web.socket.WebSocketSession; 5 | 6 | /** 7 | * Callback that is invoked when user connected to server. 8 | */ 9 | @FunctionalInterface 10 | public interface AfterConnectionEstablished { 11 | void afterConnected(@NonNull WebSocketSession session); 12 | } 13 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/resources/application.yml: -------------------------------------------------------------------------------- 1 | server: 2 | port: 9090 3 | id: 'E1' 4 | instances: 5 | list: 'E2' 6 | enabled: true 7 | 8 | spring: 9 | application: 10 | name: ws-server 11 | 12 | data: 13 | redis: 14 | host: 'localhost' 15 | port: 6379 16 | 17 | cloud: 18 | openfeign: 19 | okhttp: 20 | enabled: true 21 | 22 | eureka: 23 | client: 24 | service-url: 25 | defaultZone: 'http://localhost:8761/eureka/' 26 | 27 | ws: 28 | session: 29 | exp: 10 30 | concurrent: 31 | send-time-limit: 2000 32 | buffer-size-limit: 10240 33 | 34 | logging: 35 | level: 36 | org.linkwave.ws: DEBUG -------------------------------------------------------------------------------- /backend/ws-server/src/main/resources/banner.txt: -------------------------------------------------------------------------------- 1 | ,--. ,--. ,--. ,--. ,--. ,-.,-.,-. 2 | | | / |,--,--, | |,-.| | | | ,--,--.,--. ,--.,---. \ \ \ \ 3 | | | `| || \| /| |.'.| |' ,-. | \ `' /| .-. : \ \ \ \ 4 | | '--.| || || || \ \| ,'. |\ '-' | \ / \ --. / / / / 5 | `-----'`--'`--''--'`--'`--'--' '--' `--`--' `--' `----'/ / / / 6 | `-'`-'`-' 7 | <<< ${spring.application.name} ${application.version}>>> 8 | Powered by Spring Boot ${spring-boot.version} 9 | Server id: ${server.id} 10 | -------------------------------------------------------------------------------- /backend/ws-server/src/main/resources/keys/access_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | 3 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /backend/ws-server/src/test/resources/access_public_key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAz2yidb7uzIwVPfMm8Ox1 3 | wTagUv2WYGa+plFHHvKBVpB7AiOd+aCzBl6MO/L/xwsaP8jYWt2TDMDy/K+AgIK1 4 | 1xjZfCy0zafhZb9q9rgeiPClyq1GdsT1Vhd0ETZ6nNt+e0WQYKAXaUx1LfETOctO 5 | f2RDhC9ZxeyTn2/fkrw0JO2X9WZuYmpDW2sqMKC9YnmlTsU1tR7wJkAcvu2X4Obe 6 | 1P68ajY6V1neWlAbdpuDW0E8V9dlqH8BT6OZaFAqqgqj9x976xzMEr47DuPoW7AC 7 | 1JjXB8GkADPOJNlyfQ3g99NjQNW3KIQB5jQ20kJp5irWSb6FhS7/atzF/7ammR6y 8 | GwIDAQAB 9 | -----END PUBLIC KEY----- -------------------------------------------------------------------------------- /frontend/.eslintignore: -------------------------------------------------------------------------------- 1 | tailwind.config.ts 2 | -------------------------------------------------------------------------------- /frontend/.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | node_modules 5 | /.pnp 6 | .pnp.js 7 | .yarn/install-state.gz 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /frontend/.prettierrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | tabWidth: 2, 3 | printWidth: 120, 4 | endOfLine: "auto", 5 | arrowParens: "avoid", 6 | trailingComma: "es5", 7 | semi: true, 8 | useTabs: false, 9 | singleQuote: false, 10 | bracketSpacing: true, 11 | }; 12 | -------------------------------------------------------------------------------- /frontend/next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: false, 4 | webpack(config) { 5 | config.module.rules.push({ 6 | test: /\.svg$/, 7 | use: ["@svgr/webpack"], 8 | }); 9 | return config; 10 | }, 11 | images: { 12 | domains: ["26.26.114.136"], 13 | }, 14 | }; 15 | module.exports = nextConfig; 16 | -------------------------------------------------------------------------------- /frontend/postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/public/avatars/group.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/avatars/group.png -------------------------------------------------------------------------------- /frontend/public/avatars/user/avatar1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/avatars/user/avatar1.png -------------------------------------------------------------------------------- /frontend/public/avatars/user/avatar2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/avatars/user/avatar2.png -------------------------------------------------------------------------------- /frontend/public/avatars/user/avatar3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/avatars/user/avatar3.png -------------------------------------------------------------------------------- /frontend/public/avatars/user/avatar4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/avatars/user/avatar4.png -------------------------------------------------------------------------------- /frontend/public/avatars/user/avatar5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/avatars/user/avatar5.png -------------------------------------------------------------------------------- /frontend/public/avatars/user/avatar6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/avatars/user/avatar6.png -------------------------------------------------------------------------------- /frontend/public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/favicon.ico -------------------------------------------------------------------------------- /frontend/public/fonts/gg-sans/gg_sans_Bold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/fonts/gg-sans/gg_sans_Bold.woff -------------------------------------------------------------------------------- /frontend/public/fonts/gg-sans/gg_sans_Medium.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/fonts/gg-sans/gg_sans_Medium.woff -------------------------------------------------------------------------------- /frontend/public/fonts/gg-sans/gg_sans_Regular.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/fonts/gg-sans/gg_sans_Regular.woff -------------------------------------------------------------------------------- /frontend/public/fonts/gg-sans/gg_sans_Semibold.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/fonts/gg-sans/gg_sans_Semibold.woff -------------------------------------------------------------------------------- /frontend/public/icons/add-chat-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/public/icons/add-circle-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/public/icons/angle-down.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/icons/check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/icons/clock-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/close-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/icons/crown-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/public/icons/cut-check.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/icons/edit-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/public/icons/exit-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /frontend/public/icons/find-people-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/public/icons/folder-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/icons/form-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /frontend/public/icons/left-angle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/icons/line-horizontal.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/icons/link-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/list-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/public/icons/lock-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/minus-circle-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/remove-circle-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/search-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/send-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/public/icons/setting-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/sign-out-circle.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/time-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/user-fill.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/user-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /frontend/public/icons/user-plus-outline.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /frontend/public/icons/сurved-arrow-icon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /frontend/public/images/ChatPage/backend-fall.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/ChatPage/backend-fall.gif -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial1.png -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial2.png -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial3.png -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial4.png -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial5.png -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial6.png -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial7.png -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial8.png -------------------------------------------------------------------------------- /frontend/public/images/tutorial/tutorial9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/images/tutorial/tutorial9.png -------------------------------------------------------------------------------- /frontend/public/sound/message.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/L1nkWave/Chat/3b998fe79ac3355ffbc113f4b5a7cae550622e33/frontend/public/sound/message.mp3 -------------------------------------------------------------------------------- /frontend/src/api/http/auth/auth.ts: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | import { AuthTypes } from "@/api/http/auth/auth.types"; 4 | 5 | export const authInstance = axios.create({ 6 | baseURL: process.env.NEXT_PUBLIC_API_URL, 7 | timeout: 10000, 8 | headers: { 9 | "Content-Type": "application/json", 10 | }, 11 | }); 12 | 13 | export async function signUp(name: string, username: string, password: string) { 14 | const body = { 15 | name, 16 | username, 17 | password, 18 | }; 19 | 20 | const { data } = await authInstance.post("users/register", body); 21 | return data; 22 | } 23 | 24 | export async function signIn(username: string, password: string) { 25 | const body = { 26 | username, 27 | password, 28 | }; 29 | 30 | const data = await authInstance.post("auth/login", body, { withCredentials: true }); 31 | return data.data; 32 | } 33 | 34 | export async function refreshToken() { 35 | const { data } = await authInstance.post( 36 | "auth/refresh-tokens", 37 | {}, 38 | { 39 | withCredentials: true, 40 | } 41 | ); 42 | return data; 43 | } 44 | -------------------------------------------------------------------------------- /frontend/src/api/http/auth/auth.types.ts: -------------------------------------------------------------------------------- 1 | export type AuthTypes = { 2 | accessToken: string; 3 | refreshExpiration: number; 4 | }; 5 | -------------------------------------------------------------------------------- /frontend/src/api/http/contacts/contacts.ts: -------------------------------------------------------------------------------- 1 | import { instance, LIST_PAGINATION_LIMIT } from "@/api/http"; 2 | import { ContactParams, UserParams } from "@/api/http/contacts/contacts.types"; 3 | import { ContactsMap } from "@/components/Chat/InteractiveList/interactiveList.types"; 4 | 5 | export async function getContacts(search: string = "", limit = LIST_PAGINATION_LIMIT, offset = 0) { 6 | const { data } = await instance.get( 7 | `users/contacts?search=${search}&limit=${limit}&offset=${offset}` 8 | ); 9 | const contacts = new Map() as ContactsMap; 10 | data.forEach(contact => { 11 | contacts.set(contact.user.id, contact); 12 | }); 13 | 14 | return contacts; 15 | } 16 | 17 | export async function addContact(userId: string, alias: string) { 18 | const body = { 19 | userId, 20 | alias, 21 | }; 22 | 23 | const { data } = await instance.post(`users/contacts`, body); 24 | return data; 25 | } 26 | 27 | export async function removeContact(userId: string) { 28 | const { data } = await instance.delete(`users/contacts/${userId}`); 29 | return data; 30 | } 31 | -------------------------------------------------------------------------------- /frontend/src/api/http/index.types.ts: -------------------------------------------------------------------------------- 1 | export type ApiError = { 2 | path: string; 3 | message: string; 4 | status: number; 5 | timestamp: number; 6 | }; 7 | -------------------------------------------------------------------------------- /frontend/src/api/socket/index.types.ts: -------------------------------------------------------------------------------- 1 | export enum ChatType { 2 | GROUP = "GROUP", 3 | DUO = "DUO", 4 | } 5 | -------------------------------------------------------------------------------- /frontend/src/app/chat/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { Chat } from "@/components/Chat/Chat"; 4 | import { Container } from "@/components/Container/Container"; 5 | import { SocketProvider } from "@/context/SocketContext/SocketProvider/SocketProvider"; 6 | 7 | export default function ChatPage() { 8 | return ( 9 | 10 | 11 | 12 | 13 | 14 | ); 15 | } 16 | -------------------------------------------------------------------------------- /frontend/src/app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | :root { 6 | --primaryColor: #ffffff; 7 | --secondaryColor: #000000; 8 | --foreground-rgb: 0, 0, 0; 9 | --background-start-rgb: 214, 219, 220; 10 | --background-end-rgb: 255, 255, 255; 11 | } 12 | 13 | @media (prefers-color-scheme: dark) { 14 | :root { 15 | --foreground-rgb: 255, 255, 255; 16 | --background-start-rgb: 0, 0, 0; 17 | --background-end-rgb: 0, 0, 0; 18 | } 19 | } 20 | 21 | @layer utilities { 22 | .no-scrollbar::-webkit-scrollbar { 23 | display: none; 24 | } 25 | 26 | .no-scrollbar { 27 | -ms-overflow-style: none; 28 | scrollbar-width: none; 29 | } 30 | } 31 | 32 | body { 33 | background-color: var(--primaryColor); 34 | color: var(--secondaryColor); 35 | } 36 | 37 | -------------------------------------------------------------------------------- /frontend/src/app/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { tutorialCards } from "@/app/tutorialCards"; 4 | import { Header } from "@/components/Header/Header"; 5 | import { HomeContainer } from "@/components/HomeContainer/HomeContainer"; 6 | import { HomePageParallax } from "@/components/HomePageParallax/HomePageParallax"; 7 | import { TutorialCard } from "@/components/TutorialCard/TutorialCard"; 8 | 9 | export default function Home() { 10 | return ( 11 | <> 12 |
13 | 14 | 15 | {tutorialCards.map((card, index) => ( 16 | 17 | ))} 18 | 19 | 20 | ); 21 | } 22 | -------------------------------------------------------------------------------- /frontend/src/app/sign-in/page.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { SignInForm } from "@/components/Auth/SignInForm/SignInForm"; 4 | import { Container } from "@/components/Container/Container"; 5 | 6 | export default function SignIn() { 7 | return ( 8 | 9 | 10 | 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /frontend/src/app/sign-up/page.tsx: -------------------------------------------------------------------------------- 1 | import { SignUpForm } from "@/components/Auth/SignUpForm/SignUpForm"; 2 | import { Container } from "@/components/Container/Container"; 3 | 4 | export default function SignUp() { 5 | return ( 6 | 7 | 8 | 9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /frontend/src/components/Auth/auth.types.ts: -------------------------------------------------------------------------------- 1 | import { IconName } from "@/components/Icon/Icon"; 2 | 3 | export type FormParams = { 4 | title: string; 5 | description: string; 6 | titleIcon: IconName; 7 | buttonTitle: string; 8 | }; 9 | 10 | export type InputParams = { 11 | type?: string; 12 | placeholder: string; 13 | name: string; 14 | label: string; 15 | icon: IconName; 16 | }; 17 | -------------------------------------------------------------------------------- /frontend/src/components/Auth/auth.utils.ts: -------------------------------------------------------------------------------- 1 | import { AxiosError } from "axios"; 2 | import { FormikValues } from "formik"; 3 | import { toast } from "react-toastify"; 4 | 5 | import { messages, usernameInput } from "@/components/Auth/auth.config"; 6 | import { formatSystemAlertMessage } from "@/helpers/formatSystemAlertMessage"; 7 | 8 | export const handleUsernameBlur = (formik: FormikValues) => { 9 | if (!formik.values.username.startsWith("@")) { 10 | formik.setFieldValue(usernameInput.name, `@${formik.values.username}`); 11 | } 12 | return formik.handleBlur(usernameInput.name); 13 | }; 14 | 15 | export const axiosErrorHandler = (error: unknown) => { 16 | if (error instanceof AxiosError) { 17 | toast.error(formatSystemAlertMessage(error.response?.data.message) ?? messages.DEFAULT_ERROR_MESSAGE); 18 | } else { 19 | toast.error(messages.DEFAULT_ERROR_MESSAGE); 20 | } 21 | toast.clearWaitingQueue(); 22 | }; 23 | -------------------------------------------------------------------------------- /frontend/src/components/Avatar/avatar.types.ts: -------------------------------------------------------------------------------- 1 | import type { ImageProps } from "next/dist/shared/lib/get-img-props"; 2 | 3 | export type ItemParams = { 4 | id: number | string; 5 | avatarPath?: string; 6 | avatarAvailable?: boolean; 7 | }; 8 | 9 | export type AvatarProps = { 10 | preview?: string; 11 | isGroupAvatar?: boolean; 12 | defaultAvatar?: string; 13 | item: ItemParams; 14 | status?: boolean; 15 | online?: boolean; 16 | isAvatarAvailable?: boolean; 17 | statusClassName?: string; 18 | } & Omit; 19 | -------------------------------------------------------------------------------- /frontend/src/components/Card/Card.tsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { CardProps } from "@/components/Card/card.types"; 4 | 5 | export function Card({ children, className, ...props }: CardProps) { 6 | return ( 7 |
11 | {children} 12 |
13 | ); 14 | } 15 | -------------------------------------------------------------------------------- /frontend/src/components/Card/card.types.ts: -------------------------------------------------------------------------------- 1 | import { DetailedHTMLProps, FormHTMLAttributes, PropsWithChildren } from "react"; 2 | 3 | export type CardProps = DetailedHTMLProps, HTMLFormElement> & PropsWithChildren; 4 | -------------------------------------------------------------------------------- /frontend/src/components/Chat/InteractiveList/variants/Settings/settings.config.ts: -------------------------------------------------------------------------------- 1 | import { SettingsVariant } from "@/components/Chat/InteractiveList/variants/Settings/settings.types"; 2 | 3 | export const configList = [SettingsVariant.PROFILE]; 4 | -------------------------------------------------------------------------------- /frontend/src/components/Chat/InteractiveList/variants/Settings/settings.types.ts: -------------------------------------------------------------------------------- 1 | export enum SettingsVariant { 2 | PROFILE = "Profile", 3 | } 4 | -------------------------------------------------------------------------------- /frontend/src/components/Chat/MainBox/variants/ChatBox/ChatHeader/chatHeader.types.ts: -------------------------------------------------------------------------------- 1 | import { ChatParams, ContactParams, GroupChatDetails } from "@/api/http/contacts/contacts.types"; 2 | import { ContactsMap } from "@/components/Chat/InteractiveList/interactiveList.types"; 3 | import { ContactClickHandler } from "@/components/Chat/types/handlers.types"; 4 | 5 | export type ChatHeaderProps = { 6 | contact?: ContactParams; 7 | chat: ChatParams; 8 | onChatHeaderClick?: ContactClickHandler; 9 | groupDetails?: GroupChatDetails; 10 | onAddMemberClick?: (currentChat: ChatParams, currentContact: ContactParams) => void; 11 | contacts: ContactsMap; 12 | }; 13 | -------------------------------------------------------------------------------- /frontend/src/components/Chat/MainBox/variants/ChatBox/MessageBox/Message/fileMessage.styles.css: -------------------------------------------------------------------------------- 1 | /* styles/globals.css or in a