diff --git a/.github/workflows/coverage-back.yml b/.github/workflows/coverage-back.yml
new file mode 100644
index 0000000..a14508c
--- /dev/null
+++ b/.github/workflows/coverage-back.yml
@@ -0,0 +1,18 @@
+name: test-back
+
+on: push
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/checkout@v3
+ - name: Set up JDK 17
+ uses: actions/setup-java@v3
+ with:
+ java-version: '17'
+ distribution: 'temurin'
+ cache: maven
+ - name: build
+ run: mvn clean test -f backend/pom.xml
diff --git a/.github/workflows/coverage-front.yml b/.github/workflows/coverage-front.yml
new file mode 100644
index 0000000..fe8ebad
--- /dev/null
+++ b/.github/workflows/coverage-front.yml
@@ -0,0 +1,14 @@
+name: test-front
+
+on: push
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+
+ steps:
+ - uses: actions/setup-node@v4
+ with:
+ node-version: 20.x
+ - name: build
+ run: npm run test --prefix ./frontend-web
diff --git a/.github/workflows/npm.yml b/.github/workflows/npm.yml
index 0f8dd21..3717289 100644
--- a/.github/workflows/npm.yml
+++ b/.github/workflows/npm.yml
@@ -11,4 +11,4 @@ jobs:
with:
node-version: 20.x
- name: build
- run: npm run build --prefix ./frontend-web
+ run: npm run --prefix ./frontend-web build
diff --git a/README.md b/README.md
index 566b77e..e17f7e2 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
-![maven build status](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/build-back/badge.svg?branch=master)
-![npm build status](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/build-front/badge.svg?branch=master)
+![maven build status](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/build-back/badge.svg?branch=develop)
+![npm build status](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/build-front/badge.svg?branch=develop)
+![coverage back](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/test-back/badge.svg?branch=develop)
+![coverage front](https://github.com/Thibaut-Mouton/react-spring-messenger-project/workflows/test-front/badge.svg?branch=develop)
diff --git a/backend/pom.xml b/backend/pom.xml
index 95e15fc..60be53a 100644
--- a/backend/pom.xml
+++ b/backend/pom.xml
@@ -129,6 +129,24 @@
liquibase-maven-plugin
4.27.0
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+
+ org.mockito
+ mockito-inline
+ 5.2.0
+ compile
+
+
+
+ org.mockito
+ mockito-junit-jupiter
+ 5.2.0
+ compile
+
diff --git a/backend/src/main/java/com/mercure/config/JwtWebConfig.java b/backend/src/main/java/com/mercure/config/JwtWebConfig.java
index 6446b7b..5b21b95 100644
--- a/backend/src/main/java/com/mercure/config/JwtWebConfig.java
+++ b/backend/src/main/java/com/mercure/config/JwtWebConfig.java
@@ -8,6 +8,7 @@
import jakarta.servlet.http.Cookie;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
+import lombok.NonNull;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
@@ -28,7 +29,7 @@ public class JwtWebConfig extends OncePerRequestFilter {
private JwtUtil jwtUtil;
@Override
- protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws IOException, ServletException {
+ protected void doFilterInternal(@NonNull HttpServletRequest request,@NonNull HttpServletResponse response,@NonNull FilterChain filterChain) throws IOException, ServletException {
String jwtToken = null;
String username;
Cookie cookie = WebUtils.getCookie(request, StaticVariable.SECURE_COOKIE);
diff --git a/backend/src/main/java/com/mercure/config/SecurityConfig.java b/backend/src/main/java/com/mercure/config/SecurityConfig.java
index 559bbfd..ad4c4ad 100644
--- a/backend/src/main/java/com/mercure/config/SecurityConfig.java
+++ b/backend/src/main/java/com/mercure/config/SecurityConfig.java
@@ -4,9 +4,10 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
-import org.springframework.security.authentication.AuthenticationProvider;
+import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
import org.springframework.security.config.Customizer;
+import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configurers.AbstractHttpConfigurer;
import org.springframework.security.config.http.SessionCreationPolicy;
@@ -14,6 +15,8 @@
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
+import org.springframework.security.web.csrf.CookieCsrfTokenRepository;
+import org.springframework.security.web.csrf.CsrfTokenRepository;
@Configuration
public class SecurityConfig {
@@ -21,22 +24,34 @@ public class SecurityConfig {
@Autowired
public JwtWebConfig jwtWebConfig;
+ @Autowired
+ public CustomUserDetailsService customUserDetailsService;
+
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
+// @Bean
+// public CsrfTokenRepository csrfTokenRepository() {
+// CookieCsrfTokenRepository repository = CookieCsrfTokenRepository.withHttpOnlyFalse();
+// repository.setCookiePath("/");
+// repository.setCookieName("X-CSRF-TOKEN");
+// repository.setHeaderName("X-CSRF-TOKEN");
+// return repository;
+// }
+
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.csrf(AbstractHttpConfigurer::disable)
-// .csrf(httpSecurityCsrfConfigurer -> httpSecurityCsrfConfigurer.ignoringRequestMatchers("/api/csrf"))
- .cors(Customizer.withDefaults())
- .authorizeHttpRequests((request) -> request
- .requestMatchers("/api").permitAll()
- .requestMatchers("/api/csrf").permitAll()
- .requestMatchers("/api/auth").permitAll()
- .requestMatchers("/api/**").authenticated())
+// .csrf(csrf -> csrf.csrfTokenRepository(CookieCsrfTokenRepository.withHttpOnlyFalse()))
+ .cors(Customizer.withDefaults()).authorizeHttpRequests((request) -> request
+ .requestMatchers("/messenger", "/websocket", "/ws").permitAll()
+ .requestMatchers("/csrf").permitAll()
+ .requestMatchers("/auth").permitAll()
+ .requestMatchers("/health-check").permitAll()
+ .anyRequest().authenticated())
.sessionManagement(httpSecuritySessionManagementConfigurer -> httpSecuritySessionManagementConfigurer.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider())
.addFilterBefore(jwtWebConfig, UsernamePasswordAuthenticationFilter.class);
@@ -44,13 +59,19 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
}
@Bean
- public AuthenticationProvider authenticationProvider() {
- DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
- authenticationProvider.setUserDetailsService(new CustomUserDetailsService());
- authenticationProvider.setPasswordEncoder(passwordEncoder());
- return authenticationProvider;
+ public DaoAuthenticationProvider authenticationProvider() {
+ DaoAuthenticationProvider authProvider = new DaoAuthenticationProvider();
+ authProvider.setUserDetailsService(customUserDetailsService);
+ authProvider.setPasswordEncoder(passwordEncoder());
+ return authProvider;
}
+ @Bean
+ public AuthenticationManager authenticationManager(AuthenticationConfiguration authenticationConfiguration) throws Exception {
+ return authenticationConfiguration.getAuthenticationManager();
+ }
+
+
// @Bean
// public CorsConfigurationSource corsConfigurationSource() {
// CorsConfiguration configuration = new CorsConfiguration();
diff --git a/backend/src/main/java/com/mercure/config/WebSocketSecurityConfig.java b/backend/src/main/java/com/mercure/config/WebSocketSecurityConfig.java
index 1c8b4db..a7118e2 100644
--- a/backend/src/main/java/com/mercure/config/WebSocketSecurityConfig.java
+++ b/backend/src/main/java/com/mercure/config/WebSocketSecurityConfig.java
@@ -1,5 +1,6 @@
package com.mercure.config;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.Message;
@@ -9,6 +10,7 @@
@Configuration
@EnableWebSocketSecurity
+@ConditionalOnProperty(name = "websocket.csrf.enable", havingValue = "1")
public class WebSocketSecurityConfig {
@Bean
diff --git a/backend/src/main/java/com/mercure/controller/ApiController.java b/backend/src/main/java/com/mercure/controller/ApiController.java
index f9fb317..a7a73c5 100644
--- a/backend/src/main/java/com/mercure/controller/ApiController.java
+++ b/backend/src/main/java/com/mercure/controller/ApiController.java
@@ -25,7 +25,7 @@
import java.util.*;
@RestController
-@CrossOrigin
+@CrossOrigin(allowCredentials = "true", origins = "http://localhost:3000")
public class ApiController {
private final Logger log = LoggerFactory.getLogger(ApiController.class);
@@ -136,17 +136,19 @@ private ResponseEntity> doUserAction(HttpServletRequest request, Integer userI
}
if (userService.checkIfUserIsAdmin(adminUserId, groupId)) {
try {
- if (action.equals("grant")) {
- groupUserJoinService.grantUserAdminInConversation(userId, groupId);
- return ResponseEntity.ok().body(userToChange + " has been granted administrator to " + groupService.getGroupName(groupUrl));
- }
- if (action.equals("delete")) {
- groupUserJoinService.removeUserFromConversation(userId, groupId);
- return ResponseEntity.ok().body(userToChange + " has been removed from " + groupService.getGroupName(groupUrl));
- }
- if (action.equals("removeAdmin")) {
- groupUserJoinService.removeUserAdminFromConversation(userId, groupId);
- return ResponseEntity.ok().body(userToChange + " has been removed from administrators of " + groupService.getGroupName(groupUrl));
+ switch (action) {
+ case "grant" -> {
+ groupUserJoinService.grantUserAdminInConversation(userId, groupId);
+ return ResponseEntity.ok().body(userToChange + " has been granted administrator to " + groupService.getGroupName(groupUrl));
+ }
+ case "delete" -> {
+ groupUserJoinService.removeUserFromConversation(userId, groupId);
+ return ResponseEntity.ok().body(userToChange + " has been removed from " + groupService.getGroupName(groupUrl));
+ }
+ case "removeAdmin" -> {
+ groupUserJoinService.removeUserAdminFromConversation(userId, groupId);
+ return ResponseEntity.ok().body(userToChange + " has been removed from administrators of " + groupService.getGroupName(groupUrl));
+ }
}
} catch (Exception e) {
log.warn("Error during performing {} : {}", action, e.getMessage());
diff --git a/backend/src/main/java/com/mercure/controller/AuthenticationController.java b/backend/src/main/java/com/mercure/controller/AuthenticationController.java
index ef97741..0d5cda5 100644
--- a/backend/src/main/java/com/mercure/controller/AuthenticationController.java
+++ b/backend/src/main/java/com/mercure/controller/AuthenticationController.java
@@ -21,16 +21,17 @@
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
-import org.springframework.security.authentication.BadCredentialsException;
-import org.springframework.security.authentication.DisabledException;
+import org.springframework.security.authentication.AuthenticationManager;
+import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
+import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UserDetails;
+import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.csrf.CsrfToken;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.util.WebUtils;
-
@RestController
-@CrossOrigin(allowCredentials = "true", origins = "http://localhost:3000")
+@CrossOrigin(allowCredentials = "true", origins = "http://localhost:3000", methods = {RequestMethod.GET, RequestMethod.POST})
public class AuthenticationController {
private final Logger log = LoggerFactory.getLogger(AuthenticationController.class);
@@ -53,22 +54,29 @@ public class AuthenticationController {
@Autowired
private GroupMapper groupMapper;
+ @Autowired
+ private AuthenticationManager authenticationManager;
+
@PostMapping(value = "/auth")
- public AuthUserDTO createAuthenticationToken(@RequestBody JwtDTO authenticationRequest, HttpServletResponse response) throws Exception {
- authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());
- UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
- UserEntity user = userService.findByNameOrEmail(authenticationRequest.getUsername(), authenticationRequest.getUsername());
- String token = jwtTokenUtil.generateToken(userDetails);
- Cookie jwtAuthToken = new Cookie(StaticVariable.SECURE_COOKIE, token);
- jwtAuthToken.setHttpOnly(true);
- jwtAuthToken.setSecure(false);
- jwtAuthToken.setPath("/");
+ public AuthUserDTO createAuthenticationToken(@RequestBody JwtDTO authenticationRequest, HttpServletResponse response) {
+ Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword()));
+ if (authentication.isAuthenticated()) {
+ UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
+ UserEntity user = userService.findByNameOrEmail(authenticationRequest.getUsername(), authenticationRequest.getUsername());
+ String token = jwtTokenUtil.generateToken(userDetails);
+ Cookie jwtAuthToken = new Cookie(StaticVariable.SECURE_COOKIE, token);
+ jwtAuthToken.setHttpOnly(true);
+ jwtAuthToken.setSecure(false);
+ jwtAuthToken.setPath("/");
// cookie.setDomain("http://localhost");
-// 7 days
- jwtAuthToken.setMaxAge(7 * 24 * 60 * 60);
- response.addCookie(jwtAuthToken);
- log.debug("User authenticated successfully");
- return userMapper.toLightUserDTO(user);
+ // TODO add to env vars
+ jwtAuthToken.setMaxAge(2 * 60 * 60); // 2 hours
+ response.addCookie(jwtAuthToken);
+ log.debug("User authenticated successfully");
+ return userMapper.toLightUserDTO(user);
+ } else {
+ throw new UsernameNotFoundException("invalid user request");
+ }
}
@GetMapping(value = "/logout")
@@ -92,16 +100,6 @@ public InitUserDTO fetchInformation(HttpServletRequest request) {
return userMapper.toUserDTO(getUserEntity(request));
}
- private void authenticate(String username, String password) throws Exception {
- try {
- //authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
- } catch (DisabledException e) {
- throw new Exception("USER_DISABLED", e);
- } catch (BadCredentialsException e) {
- throw new Exception("INVALID_CREDENTIALS", e);
- }
- }
-
@PostMapping(value = "/create")
public GroupDTO createGroupChat(HttpServletRequest request, @RequestBody String payload) {
UserEntity user = getUserEntity(request);
diff --git a/backend/src/main/java/com/mercure/controller/MessageController.java b/backend/src/main/java/com/mercure/controller/MessageController.java
index 13dc6d8..e493d17 100644
--- a/backend/src/main/java/com/mercure/controller/MessageController.java
+++ b/backend/src/main/java/com/mercure/controller/MessageController.java
@@ -17,9 +17,9 @@ public class MessageController {
@Autowired
private MessageService messageService;
- @GetMapping(value = "/group/{groupUrl}")
- public WrapperMessageDTO fetchGroupMessages(@PathVariable String groupUrl) {
+ @GetMapping(value = "{offset}/group/{groupUrl}")
+ public WrapperMessageDTO fetchGroupMessages(@PathVariable String groupUrl, @PathVariable int offset) {
this.log.debug("Fetching messages from conversation");
- return this.messageService.getConversationMessage(groupUrl, -1);
+ return this.messageService.getConversationMessage(groupUrl, offset);
}
}
diff --git a/backend/src/main/java/com/mercure/controller/PingController.java b/backend/src/main/java/com/mercure/controller/PingController.java
index 112ff53..e1d577a 100644
--- a/backend/src/main/java/com/mercure/controller/PingController.java
+++ b/backend/src/main/java/com/mercure/controller/PingController.java
@@ -13,7 +13,7 @@ public class PingController {
private final Logger log = LoggerFactory.getLogger(PingController.class);
- @GetMapping
+ @GetMapping("health-check")
public String testRoute() {
log.debug("Ping base route");
return "Server status OK";
diff --git a/backend/src/main/java/com/mercure/controller/WsController.java b/backend/src/main/java/com/mercure/controller/WsController.java
index b1e76d4..436840b 100644
--- a/backend/src/main/java/com/mercure/controller/WsController.java
+++ b/backend/src/main/java/com/mercure/controller/WsController.java
@@ -87,13 +87,14 @@ public void mainChannel(InputTransportDTO dto, @Header("simpSessionId") String s
if (!"".equals(dto.getGroupUrl())) {
int messageId = messageService.findLastMessageIdByGroupId(groupService.findGroupByUrl(dto.getGroupUrl()));
MessageUserEntity messageUserEntity = seenMessageService.findByMessageId(messageId, dto.getUserId());
- if (messageUserEntity == null) break;
- messageUserEntity.setSeen(true);
+ if (messageUserEntity == null) {
+ break;
+ };
seenMessageService.saveMessageUserEntity(messageUserEntity);
}
break;
case LEAVE_GROUP:
- if (!dto.getGroupUrl().equals("")) {
+ if (!dto.getGroupUrl().isEmpty()) {
log.info("User id {} left group {}", dto.getUserId(), dto.getGroupUrl());
int groupId = groupService.findGroupByUrl(dto.getGroupUrl());
groupUserJoinService.removeUserFromConversation(dto.getUserId(), groupId);
@@ -183,7 +184,7 @@ public void webRtcChannel(@DestinationVariable String roomUrl, RtcTransportDTO d
String key = roomUrl + "_" + dto.getGroupUrl();
List userIds = groupService.getAllUsersIdByGroupUrl(dto.getGroupUrl());
HashMap> hostListsIndexedByRoomUrl = roomCacheService.getRoomByKey(key);
- if (hostListsIndexedByRoomUrl.size() == 0) {
+ if (hostListsIndexedByRoomUrl.isEmpty()) {
log.info("All users left the call, removing room from list");
OutputTransportDTO outputTransportDTO = new OutputTransportDTO();
outputTransportDTO.setAction(TransportActionEnum.END_CALL);
diff --git a/backend/src/main/java/com/mercure/controller/WsFileController.java b/backend/src/main/java/com/mercure/controller/WsFileController.java
index 8a0e6ac..d368965 100644
--- a/backend/src/main/java/com/mercure/controller/WsFileController.java
+++ b/backend/src/main/java/com/mercure/controller/WsFileController.java
@@ -20,13 +20,11 @@
import java.util.List;
-/**
- * API controller to handle file upload
- */
@RestController
+@CrossOrigin(allowCredentials = "true", origins = "http://localhost:3000")
public class WsFileController {
- private static Logger log = LoggerFactory.getLogger(WsFileController.class);
+ private static final Logger log = LoggerFactory.getLogger(WsFileController.class);
@Autowired
private MessageService messageService;
diff --git a/backend/src/main/java/com/mercure/dto/WrapperMessageDTO.java b/backend/src/main/java/com/mercure/dto/WrapperMessageDTO.java
index 993cba9..46c725a 100644
--- a/backend/src/main/java/com/mercure/dto/WrapperMessageDTO.java
+++ b/backend/src/main/java/com/mercure/dto/WrapperMessageDTO.java
@@ -17,5 +17,9 @@ public class WrapperMessageDTO {
private String groupName;
+ private boolean isActiveCall;
+
+ private String callUrl;
+
private List messages;
}
diff --git a/backend/src/main/java/com/mercure/entity/GroupEntity.java b/backend/src/main/java/com/mercure/entity/GroupEntity.java
index 405c802..f6b4ceb 100644
--- a/backend/src/main/java/com/mercure/entity/GroupEntity.java
+++ b/backend/src/main/java/com/mercure/entity/GroupEntity.java
@@ -9,7 +9,6 @@
import lombok.Setter;
import org.hibernate.annotations.CreationTimestamp;
-import java.io.Serializable;
import java.sql.Timestamp;
import java.util.HashSet;
import java.util.Set;
@@ -20,7 +19,7 @@
@Setter
@AllArgsConstructor
@NoArgsConstructor
-public class GroupEntity implements Serializable {
+public class GroupEntity {
public GroupEntity(String name) {
this.name = name;
@@ -41,6 +40,12 @@ public GroupEntity(int id, String name, String url) {
private String url;
+ @Column(name = "active_call")
+ private boolean activeCall;
+
+ @Column(name = "call_url")
+ private String callUrl;
+
@Column(name = "type")
@Enumerated(value = EnumType.STRING)
private GroupTypeEnum groupTypeEnum;
diff --git a/backend/src/main/java/com/mercure/entity/GroupRoleKey.java b/backend/src/main/java/com/mercure/entity/GroupRoleKey.java
index 50b6518..79e32d7 100644
--- a/backend/src/main/java/com/mercure/entity/GroupRoleKey.java
+++ b/backend/src/main/java/com/mercure/entity/GroupRoleKey.java
@@ -15,7 +15,7 @@
@Setter
@AllArgsConstructor
@NoArgsConstructor
-public class GroupRoleKey implements Serializable {
+public class GroupRoleKey {
@Column(name = "group_id")
private int groupId;
diff --git a/backend/src/main/java/com/mercure/entity/GroupUser.java b/backend/src/main/java/com/mercure/entity/GroupUser.java
index 805f886..6cf1563 100644
--- a/backend/src/main/java/com/mercure/entity/GroupUser.java
+++ b/backend/src/main/java/com/mercure/entity/GroupUser.java
@@ -6,7 +6,7 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
-import java.io.Serializable;
+import java.sql.Timestamp;
import java.util.Objects;
@Entity
@@ -16,7 +16,7 @@
@Setter
@AllArgsConstructor
@NoArgsConstructor
-public class GroupUser implements Serializable {
+public class GroupUser {
@Id
private int groupId;
@@ -36,6 +36,10 @@ public class GroupUser implements Serializable {
private int role;
+ @Temporal(TemporalType.TIMESTAMP)
+ @Column(name = "last_message_seen_date")
+ private Timestamp lastMessageSeenDate;
+
@Override
public int hashCode() {
return Objects.hash(groupId, userId);
diff --git a/backend/src/main/java/com/mercure/entity/MessageUserEntity.java b/backend/src/main/java/com/mercure/entity/MessageUserEntity.java
index 26fdb48..5a32d8e 100644
--- a/backend/src/main/java/com/mercure/entity/MessageUserEntity.java
+++ b/backend/src/main/java/com/mercure/entity/MessageUserEntity.java
@@ -9,7 +9,6 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
-import java.io.Serializable;
import java.util.Objects;
@Entity
@@ -19,7 +18,7 @@
@Setter
@AllArgsConstructor
@NoArgsConstructor
-public class MessageUserEntity implements Serializable {
+public class MessageUserEntity {
@Id
private int messageId;
@@ -27,8 +26,6 @@ public class MessageUserEntity implements Serializable {
@Id
private int userId;
- private boolean seen;
-
@Override
public int hashCode() {
return Objects.hash(messageId, userId);
diff --git a/backend/src/main/java/com/mercure/entity/MessageUserKey.java b/backend/src/main/java/com/mercure/entity/MessageUserKey.java
index e692536..c3aa08c 100644
--- a/backend/src/main/java/com/mercure/entity/MessageUserKey.java
+++ b/backend/src/main/java/com/mercure/entity/MessageUserKey.java
@@ -7,7 +7,6 @@
import lombok.NoArgsConstructor;
import lombok.Setter;
-import java.io.Serializable;
import java.util.Objects;
@Embeddable
@@ -15,7 +14,7 @@
@Setter
@NoArgsConstructor
@AllArgsConstructor
-public class MessageUserKey implements Serializable {
+public class MessageUserKey {
@Column(name = "message_id")
private int messageId;
@@ -23,22 +22,6 @@ public class MessageUserKey implements Serializable {
@Column(name = "user_id")
private int userId;
- public int getMessageId() {
- return messageId;
- }
-
- public void setMessageId(int messageId) {
- this.messageId = messageId;
- }
-
- public int getUserId() {
- return userId;
- }
-
- public void setUserId(int userId) {
- this.userId = userId;
- }
-
@Override
public int hashCode() {
return Objects.hash(messageId, userId);
diff --git a/backend/src/main/java/com/mercure/entity/UserEntity.java b/backend/src/main/java/com/mercure/entity/UserEntity.java
index 50b8a18..cb9210b 100644
--- a/backend/src/main/java/com/mercure/entity/UserEntity.java
+++ b/backend/src/main/java/com/mercure/entity/UserEntity.java
@@ -8,22 +8,15 @@
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
-import java.io.Serializable;
import java.util.*;
@Entity
-@Table(name = "user")
+@Table(name = "users")
@Getter
@Setter
@AllArgsConstructor
@NoArgsConstructor
-public class UserEntity implements UserDetails, Serializable {
-
- public UserEntity(int id, String firstName, String password) {
- this.id = id;
- this.firstName = firstName;
- this.password = password;
- }
+public class UserEntity implements UserDetails {
@Id
private int id;
@@ -41,6 +34,8 @@ public UserEntity(int id, String firstName, String password) {
private String jwt;
+ private String color;
+
@ManyToMany(fetch = FetchType.EAGER, mappedBy = "userEntities", cascade = CascadeType.ALL)
private Set groupSet = new HashSet<>();
diff --git a/backend/src/main/java/com/mercure/mapper/GroupCallMapper.java b/backend/src/main/java/com/mercure/mapper/GroupCallMapper.java
index e2edec3..60f62c5 100644
--- a/backend/src/main/java/com/mercure/mapper/GroupCallMapper.java
+++ b/backend/src/main/java/com/mercure/mapper/GroupCallMapper.java
@@ -3,6 +3,7 @@
import com.mercure.dto.user.GroupCallDTO;
import com.mercure.entity.GroupEntity;
import com.mercure.service.RoomCacheService;
+import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@@ -10,6 +11,7 @@
import java.util.Optional;
@Service
+@AllArgsConstructor
public class GroupCallMapper {
@Autowired
diff --git a/backend/src/main/java/com/mercure/mapper/GroupMapper.java b/backend/src/main/java/com/mercure/mapper/GroupMapper.java
index db798d0..a57ae04 100644
--- a/backend/src/main/java/com/mercure/mapper/GroupMapper.java
+++ b/backend/src/main/java/com/mercure/mapper/GroupMapper.java
@@ -17,6 +17,9 @@ public class GroupMapper {
@Autowired
private UserSeenMessageService seenMessageService;
+ @Autowired
+ private GroupUserJoinService groupUserJoinService;
+
@Autowired
private UserService userService;
@@ -26,6 +29,7 @@ public GroupDTO toGroupDTO(GroupEntity grp, int userId) {
grpDTO.setName(grp.getName());
grpDTO.setUrl(grp.getUrl());
grpDTO.setGroupType(grp.getGroupTypeEnum().toString());
+ GroupUser user = groupUserJoinService.findGroupUser(userId, grp.getId());
MessageEntity msg = messageService.findLastMessage(grp.getId());
if (msg != null) {
String sender = userService.findFirstNameById(msg.getUser_id());
@@ -43,7 +47,7 @@ public GroupDTO toGroupDTO(GroupEntity grp, int userId) {
grpDTO.setLastMessage(msg.getMessage());
}
grpDTO.setLastMessage(msg.getMessage());
- grpDTO.setLastMessageSeen(messageUserEntity.isSeen());
+ grpDTO.setLastMessageSeen(msg.getCreatedAt().after(user.getLastMessageSeenDate()));
grpDTO.setLastMessageDate(msg.getCreatedAt().toString());
}
} else {
diff --git a/backend/src/main/java/com/mercure/repository/GroupUserJoinRepository.java b/backend/src/main/java/com/mercure/repository/GroupUserJoinRepository.java
index c727f94..3e96f64 100644
--- a/backend/src/main/java/com/mercure/repository/GroupUserJoinRepository.java
+++ b/backend/src/main/java/com/mercure/repository/GroupUserJoinRepository.java
@@ -15,6 +15,9 @@ public interface GroupUserJoinRepository extends JpaRepository getAllByGroupId(@Param("groupId") int groupId);
+ @Query(value = "SELECT * FROM group_user WHERE group_id=:groupId and user_id = :userId", nativeQuery = true)
+ GroupUser getGroupUser(@Param("userId") int userId, @Param("groupId") int groupId);
+
@Query(value = "SELECT g.user_id FROM group_user g WHERE g.group_id = :groupId", nativeQuery = true)
List getUsersIdInGroup(@Param("groupId") int groupId);
}
diff --git a/backend/src/main/java/com/mercure/repository/UserRepository.java b/backend/src/main/java/com/mercure/repository/UserRepository.java
index 28dfa9b..7b44dc1 100644
--- a/backend/src/main/java/com/mercure/repository/UserRepository.java
+++ b/backend/src/main/java/com/mercure/repository/UserRepository.java
@@ -13,19 +13,19 @@ public interface UserRepository extends JpaRepository {
UserEntity getUserByFirstNameOrMail(String firstName, String mail);
- @Query(value = "SELECT u.firstname, u.lastname FROM user u WHERE u.id = :userId", nativeQuery = true)
+ @Query(value = "SELECT u.firstname, u.lastname FROM users u WHERE u.id = :userId", nativeQuery = true)
String getUsernameByUserId(@Param(value = "userId") int id);
- @Query(value = "SELECT u.firstname FROM user u WHERE u.id = :userId", nativeQuery = true)
+ @Query(value = "SELECT u.firstname FROM users u WHERE u.id = :userId", nativeQuery = true)
String getFirstNameByUserId(@Param(value = "userId") int id);
- @Query(value = "SELECT u.firstname FROM user u WHERE u.wstoken = :token", nativeQuery = true)
+ @Query(value = "SELECT u.firstname FROM users u WHERE u.wstoken = :token", nativeQuery = true)
String getUsernameWithWsToken(@Param(value = "token") String token);
- @Query(value = "SELECT u.id FROM user u WHERE u.wstoken = :token", nativeQuery = true)
+ @Query(value = "SELECT u.id FROM users u WHERE u.wstoken = :token", nativeQuery = true)
int getUserIdWithWsToken(@Param(value = "token") String token);
- @Query(value = "SELECT * FROM user u WHERE u.id NOT IN :ids", nativeQuery = true)
+ @Query(value = "SELECT * FROM users u WHERE u.id NOT IN :ids", nativeQuery = true)
List getAllUsersNotAlreadyInConversation(@Param(value = "ids") int[] ids);
int countAllByFirstNameOrMail(String firstName, String mail);
diff --git a/backend/src/main/java/com/mercure/service/CustomUserDetailsService.java b/backend/src/main/java/com/mercure/service/CustomUserDetailsService.java
index 32d47f1..17257a6 100644
--- a/backend/src/main/java/com/mercure/service/CustomUserDetailsService.java
+++ b/backend/src/main/java/com/mercure/service/CustomUserDetailsService.java
@@ -2,8 +2,6 @@
import com.mercure.entity.UserEntity;
-import org.slf4j.Logger;
-import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.core.userdetails.UserDetails;
diff --git a/backend/src/main/java/com/mercure/service/GroupService.java b/backend/src/main/java/com/mercure/service/GroupService.java
index c402260..e864b8d 100644
--- a/backend/src/main/java/com/mercure/service/GroupService.java
+++ b/backend/src/main/java/com/mercure/service/GroupService.java
@@ -10,6 +10,7 @@
import org.springframework.stereotype.Service;
import javax.swing.*;
+import java.sql.Timestamp;
import java.util.*;
import java.util.stream.Collectors;
@@ -61,6 +62,9 @@ public GroupMemberDTO addUserToConversation(int userId, int groupId) {
groupUser.setUserEntities(user);
groupUser.setGroupId(groupId);
groupUser.setUserId(userId);
+ Date date = new Date();
+ Timestamp ts = new Timestamp(date.getTime());
+ groupUser.setLastMessageSeenDate(ts);
groupUser.setRole(0);
GroupUser saved = groupUserJoinService.save(groupUser);
assert groupEntity.orElse(null) != null;
@@ -81,6 +85,9 @@ public GroupEntity createGroup(int userId, String name) {
groupRoleKey.setUserId(userId);
groupRoleKey.setGroupId(savedGroup.getId());
groupUser.setGroupId(savedGroup.getId());
+ Date date = new Date();
+ Timestamp ts = new Timestamp(date.getTime());
+ groupUser.setLastMessageSeenDate(ts);
groupUser.setUserId(userId);
groupUser.setRole(1);
groupUser.setUserEntities(user);
diff --git a/backend/src/main/java/com/mercure/service/GroupUserJoinService.java b/backend/src/main/java/com/mercure/service/GroupUserJoinService.java
index e2427dc..d8a5847 100644
--- a/backend/src/main/java/com/mercure/service/GroupUserJoinService.java
+++ b/backend/src/main/java/com/mercure/service/GroupUserJoinService.java
@@ -47,6 +47,10 @@ public List findAllByGroupId(int groupId) {
return groupUserJoinRepository.getAllByGroupId(groupId);
}
+ public GroupUser findGroupUser(int userId, int groupId) {
+ return groupUserJoinRepository.getGroupUser(userId, groupId);
+ }
+
public boolean checkIfUserIsAuthorizedInGroup(int userId, int groupId) {
List ids = groupUserJoinRepository.getUsersIdInGroup(groupId);
return ids.stream().noneMatch(id -> id == userId);
diff --git a/backend/src/main/java/com/mercure/service/MessageService.java b/backend/src/main/java/com/mercure/service/MessageService.java
index 1c76551..366a2b0 100644
--- a/backend/src/main/java/com/mercure/service/MessageService.java
+++ b/backend/src/main/java/com/mercure/service/MessageService.java
@@ -6,6 +6,7 @@
import com.mercure.entity.FileEntity;
import com.mercure.entity.GroupEntity;
import com.mercure.entity.MessageEntity;
+import com.mercure.entity.UserEntity;
import com.mercure.repository.MessageRepository;
import com.mercure.utils.MessageTypeEnum;
import org.springframework.beans.factory.annotation.Autowired;
@@ -33,28 +34,11 @@ public class MessageService {
@Autowired
private FileService fileService;
- private static final String[] colorsArray =
- {
- "#FFC194", "#9CE03F", "#62C555", "#3AD079",
- "#44CEC3", "#F772EE", "#FFAFD2", "#FFB4AF",
- "#FF9207", "#E3D530", "#D2FFAF", "FF5733"
- };
-
- private static final Map colors = new HashMap<>();
-
- public String getRandomColor() {
- return colorsArray[new Random().nextInt(colorsArray.length)];
- }
-
public MessageEntity createAndSaveMessage(int userId, int groupId, String type, String data) {
MessageEntity msg = new MessageEntity(userId, groupId, type, data);
return messageRepository.save(msg);
}
- public void flush() {
- messageRepository.flush();
- }
-
public MessageEntity save(MessageEntity messageEntity) {
return messageRepository.save(messageEntity);
}
@@ -62,9 +46,9 @@ public MessageEntity save(MessageEntity messageEntity) {
public List findByGroupId(int id, int offset) {
List list;
if (offset == -1) {
- list = messageRepository.findByGroupIdAndOffset(id, offset);
- } else {
list = messageRepository.findLastMessagesByGroupId(id);
+ } else {
+ list = messageRepository.findByGroupIdAndOffset(id, offset);
}
return list;
}
@@ -93,19 +77,17 @@ public int findLastMessageIdByGroupId(int groupId) {
* @return a {@link MessageDTO}
*/
public MessageDTO createMessageDTO(int id, String type, int userId, String date, int group_id, String message) {
- colors.putIfAbsent(userId, getRandomColor());
- String username = userService.findUsernameById(userId);
+ UserEntity user = userService.findById(userId);
String fileUrl = "";
- String[] arr = username.split(",");
- String initials = arr[0].substring(0, 1).toUpperCase() + arr[1].substring(0, 1).toUpperCase();
- String sender = StringUtils.capitalize(arr[0]) +
+ String initials = user.getFirstName().substring(0, 1).toUpperCase() + user.getLastName().substring(0, 1).toUpperCase();
+ String sender = StringUtils.capitalize(user.getFirstName()) +
" " +
- StringUtils.capitalize(arr[1]);
+ StringUtils.capitalize(user.getLastName());
if (type.equals(MessageTypeEnum.FILE.toString())) {
FileEntity fileEntity = fileService.findByFkMessageId(id);
fileUrl = fileEntity.getUrl();
}
- return new MessageDTO(id, type, message, userId, group_id, null, sender, date, initials, colors.get(userId), fileUrl, userId == id);
+ return new MessageDTO(id, type, message, userId, group_id, null, sender, date, initials, user.getColor(), fileUrl, userId == id);
}
public static String createUserInitials(String firstAndLastName) {
@@ -150,6 +132,7 @@ public NotificationDTO createNotificationDTO(MessageEntity msg) {
public MessageDTO createNotificationMessageDTO(MessageEntity msg, int userId) {
String groupUrl = groupService.getGroupUrlById(msg.getGroup_id());
+ UserEntity user = userService.findById(userId);
String firstName = userService.findFirstNameById(msg.getUser_id());
String initials = userService.findUsernameById(msg.getUser_id());
MessageDTO messageDTO = new MessageDTO();
@@ -166,31 +149,28 @@ public MessageDTO createNotificationMessageDTO(MessageEntity msg, int userId) {
messageDTO.setSender(firstName);
messageDTO.setTime(msg.getCreatedAt().toString());
messageDTO.setInitials(createUserInitials(initials));
- messageDTO.setColor(colors.get(msg.getUser_id()));
+ messageDTO.setColor(user.getColor());
messageDTO.setMessageSeen(msg.getUser_id() == userId);
return messageDTO;
}
- /**
- * Return history of group discussion
- *
- * @param url The group url to map
- * @return List of message
- */
public WrapperMessageDTO getConversationMessage(String url, int messageId) {
WrapperMessageDTO wrapper = new WrapperMessageDTO();
if (url != null) {
List messageDTOS = new ArrayList<>();
GroupEntity group = groupService.getGroupByUrl(url);
List newMessages = messageService.findByGroupId(group.getId(), messageId);
+ int lastMessageId = newMessages != null && !newMessages.isEmpty() ? newMessages.get(0).getId() : 0;
+ List afterMessages = messageService.findByGroupId(group.getId(), lastMessageId);
if (newMessages != null) {
newMessages.forEach(msg ->
messageDTOS.add(messageService
.createMessageDTO(msg.getId(), msg.getType(), msg.getUser_id(), msg.getCreatedAt().toString(), msg.getGroup_id(), msg.getMessage()))
);
}
-// wrapper.setLastMessage(afterMessages != null && afterMessages.isEmpty());
- wrapper.setLastMessage(true);
+ wrapper.setActiveCall(group.isActiveCall());
+ wrapper.setCallUrl(group.getCallUrl());
+ wrapper.setLastMessage(afterMessages != null && afterMessages.isEmpty());
wrapper.setMessages(messageDTOS);
wrapper.setGroupName(group.getName());
return wrapper;
diff --git a/backend/src/main/java/com/mercure/service/RoomCacheService.java b/backend/src/main/java/com/mercure/service/RoomCacheService.java
index dc562bd..f60300e 100644
--- a/backend/src/main/java/com/mercure/service/RoomCacheService.java
+++ b/backend/src/main/java/com/mercure/service/RoomCacheService.java
@@ -19,12 +19,11 @@ private Cache getCache() {
}
public void putNewRoom(String groupUrl, String roomUrl, ArrayList usersList) {
- StringBuilder key = new StringBuilder();
- key.append(groupUrl);
- key.append("_");
- key.append(roomUrl);
+ String key = groupUrl +
+ "_" +
+ roomUrl;
Cache roomsCache = this.getCache();
- roomsCache.put(key.toString(), usersList);
+ roomsCache.put(key, usersList);
}
public List getAllKeys() {
diff --git a/backend/src/main/java/com/mercure/service/UserSeenMessageService.java b/backend/src/main/java/com/mercure/service/UserSeenMessageService.java
index 943406f..2f20ef0 100644
--- a/backend/src/main/java/com/mercure/service/UserSeenMessageService.java
+++ b/backend/src/main/java/com/mercure/service/UserSeenMessageService.java
@@ -26,7 +26,6 @@ public void saveMessageNotSeen(MessageEntity msg, int groupId) {
MessageUserEntity message = new MessageUserEntity();
message.setMessageId(msg.getId());
message.setUserId(user.getId());
- message.setSeen(msg.getUser_id() == user.getId());
seenMessageRepository.save(message);
}));
}
diff --git a/backend/src/main/java/com/mercure/utils/ColorsUtils.java b/backend/src/main/java/com/mercure/utils/ColorsUtils.java
new file mode 100644
index 0000000..0a54f11
--- /dev/null
+++ b/backend/src/main/java/com/mercure/utils/ColorsUtils.java
@@ -0,0 +1,17 @@
+package com.mercure.utils;
+
+import java.util.Random;
+
+public class ColorsUtils {
+
+ private final String[] colorsArray =
+ {
+ "#FFC194", "#9CE03F", "#62C555", "#3AD079",
+ "#44CEC3", "#F772EE", "#FFAFD2", "#FFB4AF",
+ "#FF9207", "#E3D530", "#D2FFAF", "FF5733"
+ };
+
+ public String getRandomColor() {
+ return this.colorsArray[new Random().nextInt(colorsArray.length)];
+ }
+}
diff --git a/backend/src/main/java/com/mercure/utils/DbInit.java b/backend/src/main/java/com/mercure/utils/DbInit.java
index 8c9df45..2063010 100644
--- a/backend/src/main/java/com/mercure/utils/DbInit.java
+++ b/backend/src/main/java/com/mercure/utils/DbInit.java
@@ -32,16 +32,16 @@ public DbInit(UserService userService, PasswordEncoder passwordEncoder) {
@Override
public void run(String... args) {
try {
-
- if (userService.findAll().size() == 0) {
+ if (userService.findAll().isEmpty()) {
List sourceList = Arrays.asList("Thibaut", "Mark", "John", "Luke", "Steve");
sourceList.forEach(val -> {
UserEntity user = new UserEntity();
user.setFirstName(val);
- user.setLastName("Doe" + val.toLowerCase());
+ user.setLastName("Williams");
user.setPassword(passwordEncoder.encode("root"));
user.setMail(val.toLowerCase() + "@fastlitemessage.com");
user.setEnabled(true);
+ user.setColor(new ColorsUtils().getRandomColor());
user.setCredentialsNonExpired(true);
user.setAccountNonLocked(true);
user.setAccountNonExpired(true);
diff --git a/backend/src/main/java/com/mercure/utils/JwtUtil.java b/backend/src/main/java/com/mercure/utils/JwtUtil.java
index 64347bc..1775c27 100644
--- a/backend/src/main/java/com/mercure/utils/JwtUtil.java
+++ b/backend/src/main/java/com/mercure/utils/JwtUtil.java
@@ -20,12 +20,10 @@ public class JwtUtil implements Serializable {
// TODO generate key
public static final String JWT_TOKEN = "d95d7dc9-0d56-4ef3-8d03-263c23b5bce5";
- // retrieve username from jwt token
public String getUserNameFromJwtToken(String token) {
return Jwts.parser().setSigningKey(JWT_TOKEN).parseClaimsJws(token).getBody().getSubject();
}
- // retrieve expiration date from jwt token
public Date getExpirationDateFromToken(String token) {
return getClaimFromToken(token, Claims::getExpiration);
}
diff --git a/backend/src/main/resources/application.properties b/backend/src/main/resources/application.properties
index 03f92e6..a734105 100644
--- a/backend/src/main/resources/application.properties
+++ b/backend/src/main/resources/application.properties
@@ -3,6 +3,8 @@ server.port=9090
spring.main.allow-circular-references=true
spring.liquibase.enabled=true
+spring.banner.location=classpath:/banner/banner.txt
+
spring.liquibase.url=jdbc:mysql://localhost:3306/fastlitemessage_dev?createDatabaseIfNotExist=true
spring.liquibase.change-log=classpath:/db/changelog-master.xml
spring.liquibase.user=root
@@ -23,8 +25,8 @@ spring.servlet.multipart.max-file-size=5MB
spring.servlet.multipart.max-request-size=5MB
logging.level.web=debug
-logging.level.org.springframework.security=debug
-logging.level.org.springframework.web.cors.reactive.DefaultCorsProcessor=off
+logging.level.org.springframework.security=trace
+#logging.level.org.springframework.web.cors.reactive.DefaultCorsProcessor=off
server.servlet.context-path=/api
server.ssl.enabled=false
\ No newline at end of file
diff --git a/backend/src/main/resources/banner/banner.txt b/backend/src/main/resources/banner/banner.txt
new file mode 100644
index 0000000..bac8eb3
--- /dev/null
+++ b/backend/src/main/resources/banner/banner.txt
@@ -0,0 +1,6 @@
+ _____ _ _ _ _ __ __
+ | ___|_ _ ___| |_| | (_) |_ ___| \/ | ___ ___ ___ ___ _ __ __ _ ___ _ __
+ | |_ / _` / __| __| | | | __/ _ \ |\/| |/ _ \/ __/ __|/ _ \ '_ \ / _` |/ _ \ '__|
+ | _| (_| \__ \ |_| |___| | || __/ | | | __/\__ \__ \ __/ | | | (_| | __/ |
+ |_| \__,_|___/\__|_____|_|\__\___|_| |_|\___||___/___/\___|_| |_|\__, |\___|_|
+ |___/
\ No newline at end of file
diff --git a/backend/src/main/resources/db/changelog-master.xml b/backend/src/main/resources/db/changelog-master.xml
index 4147700..3e5f964 100644
--- a/backend/src/main/resources/db/changelog-master.xml
+++ b/backend/src/main/resources/db/changelog-master.xml
@@ -10,4 +10,7 @@
+
+
+
diff --git a/backend/src/main/resources/db/changelog/addGroupColumn.xml b/backend/src/main/resources/db/changelog/addGroupColumn.xml
new file mode 100644
index 0000000..9d643d6
--- /dev/null
+++ b/backend/src/main/resources/db/changelog/addGroupColumn.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/src/main/resources/db/changelog/addMessageSeenFlagGroupUser.xml b/backend/src/main/resources/db/changelog/addMessageSeenFlagGroupUser.xml
new file mode 100644
index 0000000..2dad347
--- /dev/null
+++ b/backend/src/main/resources/db/changelog/addMessageSeenFlagGroupUser.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/src/main/resources/db/changelog/createJoinTableMessageUserSeen.xml b/backend/src/main/resources/db/changelog/createJoinTableMessageUserSeen.xml
index 468501d..1a579ac 100644
--- a/backend/src/main/resources/db/changelog/createJoinTableMessageUserSeen.xml
+++ b/backend/src/main/resources/db/changelog/createJoinTableMessageUserSeen.xml
@@ -32,7 +32,7 @@
diff --git a/backend/src/main/resources/db/changelog/createJoinTableUserGroup.xml b/backend/src/main/resources/db/changelog/createJoinTableUserGroup.xml
index 1c4f9f6..a578854 100644
--- a/backend/src/main/resources/db/changelog/createJoinTableUserGroup.xml
+++ b/backend/src/main/resources/db/changelog/createJoinTableUserGroup.xml
@@ -32,7 +32,7 @@
diff --git a/backend/src/main/resources/db/changelog/createMessageWsTable.xml b/backend/src/main/resources/db/changelog/createMessageWsTable.xml
index 56c3356..7f4c082 100644
--- a/backend/src/main/resources/db/changelog/createMessageWsTable.xml
+++ b/backend/src/main/resources/db/changelog/createMessageWsTable.xml
@@ -38,7 +38,7 @@
baseTableName="message"
constraintName="fk_message_user"
referencedColumnNames="id"
- referencedTableName="user"/>
+ referencedTableName="users"/>
-
+
-
+
@@ -23,6 +23,7 @@
+
@@ -44,7 +45,7 @@
-
+
diff --git a/backend/src/main/resources/db/changelog/deleteSeenColumnInMessage.xml b/backend/src/main/resources/db/changelog/deleteSeenColumnInMessage.xml
new file mode 100644
index 0000000..c1ef229
--- /dev/null
+++ b/backend/src/main/resources/db/changelog/deleteSeenColumnInMessage.xml
@@ -0,0 +1,28 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/backend/src/test/java/com/mercure/mapper/GroupCallMapperTest.java b/backend/src/test/java/com/mercure/mapper/GroupCallMapperTest.java
new file mode 100644
index 0000000..01649ef
--- /dev/null
+++ b/backend/src/test/java/com/mercure/mapper/GroupCallMapperTest.java
@@ -0,0 +1,23 @@
+package com.mercure.mapper;
+
+import com.mercure.dto.user.GroupCallDTO;
+import com.mercure.entity.GroupEntity;
+import com.mercure.service.RoomCacheService;
+import org.junit.jupiter.api.*;
+
+
+import static org.junit.jupiter.api.Assertions.*;
+
+public class GroupCallMapperTest {
+
+ @Test
+ @DisplayName("GroupCallMapperTest")
+ public void compare() {
+ RoomCacheService roomCacheService = new RoomCacheService();
+ GroupCallMapper groupCallMapper = new GroupCallMapper(roomCacheService);
+ GroupEntity groupEntity = new GroupEntity();
+ GroupCallDTO groupCallDTO = groupCallMapper.toGroupCall(groupEntity);
+ assertNotEquals("", groupCallDTO.getActiveCallUrl());
+ assertTrue(true);
+ }
+}
\ No newline at end of file
diff --git a/frontend-web/jest.config.js b/frontend-web/jest.config.js
new file mode 100644
index 0000000..b413e10
--- /dev/null
+++ b/frontend-web/jest.config.js
@@ -0,0 +1,5 @@
+/** @type {import('ts-jest').JestConfigWithTsJest} */
+module.exports = {
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+};
\ No newline at end of file
diff --git a/frontend-web/package.json b/frontend-web/package.json
index 0aa6840..69bbf21 100644
--- a/frontend-web/package.json
+++ b/frontend-web/package.json
@@ -19,13 +19,14 @@
"@emotion/react": "11.11.4",
"@emotion/styled": "11.11.5",
"@mui/icons-material": "5.15.15",
+ "@mui/lab": "^5.0.0-alpha.170",
"@mui/material": "5.15.15",
"@stomp/stompjs": "7.0.0",
"axios": "1.6.8",
"history": "5.3.0",
+ "jest": "29.7.0",
"moment": "2.30.1",
"react": "18.2.0",
- "react-cookie": "7.1.4",
"react-dom": "18.2.0",
"react-router-dom": "6.22.3",
"react-scripts": "5.0.1",
@@ -37,6 +38,7 @@
"extends": "react-app"
},
"devDependencies": {
+ "@jest/globals": "^29.7.0",
"@types/react": "18.2.74",
"@types/react-dom": "18.2.24",
"@typescript-eslint/eslint-plugin": "7.5.0",
diff --git a/frontend-web/src/components/create-conversation/CreateConversationComponent.tsx b/frontend-web/src/components/create-conversation/CreateConversationComponent.tsx
new file mode 100644
index 0000000..b29e806
--- /dev/null
+++ b/frontend-web/src/components/create-conversation/CreateConversationComponent.tsx
@@ -0,0 +1,110 @@
+import React, {useContext, useState} from "react"
+import LoadingButton from "@mui/lab/LoadingButton"
+import NoteAddOutlinedIcon from "@mui/icons-material/NoteAddOutlined"
+import {Button, Dialog, DialogActions, DialogContent, DialogTitle, TextField} from "@mui/material"
+import {HttpGroupService} from "../../service/http-group-service"
+import {AlertAction, AlertContext} from "../../context/AlertContext"
+import {redirect} from "react-router-dom"
+import {GroupContext, GroupContextAction} from "../../context/GroupContext"
+
+export function CreateConversationComponent(): React.JSX.Element {
+ const [open, setOpen] = useState(false)
+ const [groupName, setGroupName] = useState("")
+ const [groupCreationLoading, setGroupCreationLoading] = useState(false)
+ const httpService = new HttpGroupService()
+ const {changeGroupState} = useContext(GroupContext)!
+ const {dispatch} = useContext(AlertContext)!
+
+ function handleClickOpen() {
+ setOpen(true)
+ }
+
+ function handleClose() {
+ setOpen(false)
+ }
+
+ async function createGroupByName() {
+ if (groupName !== "") {
+ setGroupCreationLoading(true)
+ try {
+ const {data} = await httpService.createGroup(groupName)
+ dispatch({
+ type: AlertAction.ADD_ALERT,
+ payload: {
+ id: crypto.randomUUID(),
+ isOpen: true,
+ alert: "success",
+ text: `Group "${groupName}" has been created successfully`
+ }
+ })
+ changeGroupState({type: GroupContextAction.ADD_GROUP, payload: data})
+ setOpen(false)
+ redirect(`/t/messages/${data.url}`)
+ } catch (error) {
+ dispatch({
+ type: AlertAction.ADD_ALERT,
+ payload: {
+ id: crypto.randomUUID(),
+ isOpen: true,
+ alert: "error",
+ text: `Cannot create group "${groupName}" : ${error}`
+ }
+ })
+ } finally {
+ setGroupCreationLoading(false)
+ }
+ }
+ }
+
+ function handleChange(event: any) {
+ event.preventDefault()
+ setGroupName(event.target.value)
+ }
+
+ function submitGroupCreation(event: any) {
+ if (event.key === undefined || event.key === "Enter") {
+ if (groupName === "") {
+ return
+ }
+ createGroupByName()
+ }
+ }
+
+
+ return (
+ <>
+
+
+ >
+ )
+}
diff --git a/frontend-web/src/components/create-group/create-group-component.tsx b/frontend-web/src/components/create-group/create-group-component.tsx
deleted file mode 100644
index 894480e..0000000
--- a/frontend-web/src/components/create-group/create-group-component.tsx
+++ /dev/null
@@ -1,111 +0,0 @@
-import {Button, Container, CssBaseline, Grid, Typography} from "@mui/material"
-import React, {useContext, useEffect, useState} from "react"
-import {useThemeContext} from "../../context/theme-context"
-import {CustomTextField} from "../partials/custom-material-textfield"
-import {HttpGroupService} from "../../service/http-group-service"
-import {AlertAction, AlertContext} from "../../context/AlertContext"
-
-export const CreateGroupComponent = () => {
- const [groupName, setGroupName] = useState("")
- const {theme} = useThemeContext()
- const httpService = new HttpGroupService()
- const {dispatch} = useContext(AlertContext)!
-
- useEffect(() => {
- document.title = "Create group | FLM"
- }, [])
-
- function handleChange(event: any) {
- event.preventDefault()
- setGroupName(event.target.value)
- }
-
- async function createGroupByName(event: any) {
- event.preventDefault()
- if (groupName !== "") {
- try {
- await httpService.createGroup(groupName)
- dispatch({
- type: AlertAction.ADD_ALERT,
- payload: {
- id: crypto.randomUUID(),
- isOpen: true,
- alert: "success",
- text: `Group "${groupName}" has been created successfully`
- }
- })
- } catch (error) {
- dispatch({
- type: AlertAction.ADD_ALERT,
- payload: {
- id: crypto.randomUUID(),
- isOpen: true,
- alert: "error",
- text: `Cannot create group "${groupName}" : ${error}`
- }
- })
- }
- // dispatch(createGroup({ group: res.data }))
- // history.push({
- // pathname: "/t/messages/" + res.data.url
- // })
- // setAlerts([...alerts, new FeedbackModel(UUIDv4(), `Cannot create group "${groupName}" : ${err.toString()}`, "error", true)])
- }
- }
-
- function submitGroupCreation(event: any) {
- if (event.key === undefined || event.key === "Enter") {
- if (groupName === "") {
- return
- }
- createGroupByName(event)
- }
- }
-
- return (
-
-
-
-
-
- Create a group
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- )
-}
diff --git a/frontend-web/src/components/home.tsx b/frontend-web/src/components/home.tsx
index 899ff1e..49e0873 100644
--- a/frontend-web/src/components/home.tsx
+++ b/frontend-web/src/components/home.tsx
@@ -3,12 +3,12 @@ import React, {useContext, useEffect} from "react"
import {generateColorMode} from "./utils/enable-dark-mode"
import {useThemeContext} from "../context/theme-context"
import {FooterComponent} from "./partials/footer-component"
-import {AuthUserContext} from "../context/AuthContext"
import {LoginComponent} from "./login/LoginComponent"
+import {UserContext} from "../context/UserContext"
export const HomeComponent = (): React.JSX.Element => {
const {theme} = useThemeContext()
- const {user} = useContext(AuthUserContext)!
+ const {user} = useContext(UserContext)!
useEffect(() => {
document.title = "Home | FLM"
diff --git a/frontend-web/src/components/messages/CreateMessageComponent.tsx b/frontend-web/src/components/messages/CreateMessageComponent.tsx
index 6c88097..1a661f2 100644
--- a/frontend-web/src/components/messages/CreateMessageComponent.tsx
+++ b/frontend-web/src/components/messages/CreateMessageComponent.tsx
@@ -1,5 +1,4 @@
-import {IconButton} from "@mui/material"
-import {CustomTextField} from "../partials/custom-material-textfield"
+import {Button, IconButton, styled, TextField} from "@mui/material"
import React, {useContext, useState} from "react"
import {getPayloadSize} from "../../utils/string-size-calculator"
import {TransportModel} from "../../interface-contract/transport-model"
@@ -7,7 +6,10 @@ import {TransportActionEnum} from "../../utils/transport-action-enum"
import {HttpGroupService} from "../../service/http-group-service"
import HighlightOffIcon from "@mui/icons-material/HighlightOff"
import {WebSocketContext} from "../../context/WebsocketContext"
-import {AuthUserContext} from "../../context/AuthContext"
+import {CallWindowComponent} from "../websocket/CallWindowComponent"
+import {UserContext} from "../../context/UserContext"
+import {GroupContext, GroupContextAction} from "../../context/GroupContext"
+import {InsertPhoto} from "@mui/icons-material"
interface CreateMessageComponentProps {
groupUrl: string
@@ -15,7 +17,8 @@ interface CreateMessageComponentProps {
export function CreateMessageComponent({groupUrl}: CreateMessageComponentProps): React.JSX.Element {
const {ws} = useContext(WebSocketContext)!
- const {user} = useContext(AuthUserContext)!
+ const {user} = useContext(UserContext)!
+ const {changeGroupState} = useContext(GroupContext)!
const [, setImageLoaded] = useState(false)
const [message, setMessage] = useState("")
const [file, setFile] = React.useState(null)
@@ -23,15 +26,12 @@ export function CreateMessageComponent({groupUrl}: CreateMessageComponentProps):
const [imagePreviewUrl, setImagePreviewUrl] = React.useState("")
function submitMessage(event: any) {
- if (message !== "") {
- if (event.key !== undefined && event.shiftKey && event.keyCode === 13) {
- return
- }
- if (event.key !== undefined && event.keyCode === 13) {
- event.preventDefault()
- sendMessage()
- setMessage("")
- }
+ if (event.key !== undefined && event.shiftKey && event.keyCode === 13) {
+ return
+ }
+ if (event.key !== undefined && event.keyCode === 13) {
+ event.preventDefault()
+ sendMessage()
}
}
@@ -65,7 +65,6 @@ export function CreateMessageComponent({groupUrl}: CreateMessageComponentProps):
if (message !== "") {
if (getPayloadSize(message) < 8192 && ws?.active) {
const transport = new TransportModel(user?.id || 0, TransportActionEnum.SEND_GROUP_MESSAGE, undefined, groupUrl, message)
- console.log("SENDING MESSAGE", transport)
ws.publish({
destination: "/message",
body: JSON.stringify(transport)
@@ -88,11 +87,22 @@ export function CreateMessageComponent({groupUrl}: CreateMessageComponentProps):
}
function markMessageSeen() {
- // dispatch(markMessageAsSeen({
- // groupUrl
- // }))
+ changeGroupState({
+ type: GroupContextAction.UPDATE_SEEN_MESSAGE, payload: {groupUrl, isMessageSeen: true}
+ })
}
+ const VisuallyHiddenInput = styled("input")({
+ clip: "rect(0 0 0 0)",
+ clipPath: "inset(50%)",
+ height: 1,
+ overflow: "hidden",
+ position: "absolute",
+ bottom: 0,
+ left: 0,
+ whiteSpace: "nowrap",
+ width: 1,
+ })
return (
<>
@@ -128,24 +138,26 @@ export function CreateMessageComponent({groupUrl}: CreateMessageComponentProps):
bottom: "0",
padding: "5px"
}}>
- previewFile(event)}
- />
- }
+ >
+
+
+
+ handleChange(event)}
+ onChange={(event: any) => handleChange(event)}
type={"text"}
- keyUp={submitMessage}
- isMultiline={true}
- isDarkModeEnable={"true"}
+ onKeyDown={submitMessage}
+ multiline={true}
name={"mainWriteMessage"}/>
{/*