Added UserLogin tracking

- Added UserLoginRepository
- Added latest UserLogin statistics
- Added entity
- Fixed user generator
- Added user UUID tracking
This commit is contained in:
Max W. 2024-06-02 15:21:27 +02:00
parent d50d6de466
commit 348bf8050c
6 changed files with 78 additions and 10 deletions

View File

@ -1,19 +1,40 @@
package de.w665.sharepulse.db.repo; package de.w665.sharepulse.db.repo;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.rethinkdb.RethinkDB; import com.rethinkdb.RethinkDB;
import com.rethinkdb.net.Connection; import com.rethinkdb.net.Connection;
import com.rethinkdb.net.Result;
import de.w665.sharepulse.db.RethinkDBConfig; import de.w665.sharepulse.db.RethinkDBConfig;
import de.w665.sharepulse.db.RethinkDBConnector; import de.w665.sharepulse.db.RethinkDBConnector;
import de.w665.sharepulse.model.FileUpload;
import de.w665.sharepulse.model.User;
import de.w665.sharepulse.model.UserLogin; import de.w665.sharepulse.model.UserLogin;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Repository; import org.springframework.stereotype.Repository;
import java.lang.reflect.Type;
import java.util.*;
import java.util.stream.Collectors;
@Slf4j
@Repository @Repository
public class UserLoginRepository { public class UserLoginRepository {
private final RethinkDB r; private final RethinkDB r;
private final Connection connection; private final Connection connection;
private final RethinkDBConfig config; private final RethinkDBConfig config;
private final String TABLE_NAME = "user_logins"; private final String TABLE_NAME = "user_logins";
private final Gson gson = new Gson();
private final ObjectMapper mapper = new ObjectMapper();
@Autowired @Autowired
public UserLoginRepository(RethinkDBConnector connector, RethinkDBConfig config) { public UserLoginRepository(RethinkDBConnector connector, RethinkDBConfig config) {
this.r = connector.getR(); this.r = connector.getR();
@ -22,6 +43,39 @@ public class UserLoginRepository {
} }
public void insertUserLogin(UserLogin userLogin) { public void insertUserLogin(UserLogin userLogin) {
String uuid = r.uuid().run(connection, String.class).first();
userLogin.setId(uuid);
r.db(config.getDatabase()).table(TABLE_NAME).insert(userLogin).run(connection); r.db(config.getDatabase()).table(TABLE_NAME).insert(userLogin).run(connection);
} }
public List<UserLogin> getRecentUserLoginsForUser(String userId, int limit) throws JsonProcessingException {
Result<Object> rawResult = r.db("sharepulse").table(TABLE_NAME)
.filter(r.hashMap("userId", userId))
.orderBy(r.desc("sort"))
.limit(limit)
.run(connection);
String jsonString = mapper.writeValueAsString(rawResult.single());
Type listType = new TypeToken<List<UserLogin>>() {}.getType();
return gson.fromJson(jsonString, listType);
}
private UserLogin convertToUserLogin(Object obj) {
ObjectMapper mapper = new ObjectMapper();
return mapper.convertValue(obj, UserLogin.class);
}
/*public List<UserLogin> getRecentUserLoginsForUser(String userId, int limit) {
Result<Object> results = r.db(config.getDatabase()).table(TABLE_NAME)
.filter(r.hashMap("userId", userId))
.orderBy(r.desc("loginTime"))
.limit(limit)
.run(connection);
List<Object> objectList = results.toList();
List<Object> obj = (List) objectList.get(0);
System.out.println(obj.getFirst());
UserLogin userLogin = (UserLogin) obj.getFirst();
System.out.println(userLogin.toString());
return null;
}*/
} }

View File

@ -49,7 +49,7 @@ public class UserRepository {
// If username is changed, this method must be used. Else the user will not be found // If username is changed, this method must be used. Else the user will not be found
public void updateUser(User user, String originalUsername) { public void updateUser(User user, String originalUsername) {
// TODO: Refactor this to use the userID instead of the username // TODO: Refactor this to use the userID instead of the username (remove this and use method above)
r.db(config.getDatabase()).table("users") r.db(config.getDatabase()).table("users")
.filter(r.hashMap("username", originalUsername)) .filter(r.hashMap("username", originalUsername))
.update(user) .update(user)
@ -57,6 +57,9 @@ public class UserRepository {
} }
public void insertUser(User user) { public void insertUser(User user) {
String optionalUuid = r.uuid().run(connection, String.class).first();
user.setId(optionalUuid);
System.out.println(user);
r.db(config.getDatabase()).table("users").insert(user).run(connection); r.db(config.getDatabase()).table("users").insert(user).run(connection);
} }
} }

View File

@ -1,15 +1,13 @@
package de.w665.sharepulse.model; package de.w665.sharepulse.model;
import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor; import lombok.*;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import java.util.Date; import java.util.Date;
@Getter @Getter
@Setter @Setter
@ToString
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class User { public class User {
@ -18,6 +16,4 @@ public class User {
private String password; private String password;
private String email; private String email;
private String role; private String role;
@JsonFormat(timezone = "ETC")
private Date lastLogin;
} }

View File

@ -1,5 +1,6 @@
package de.w665.sharepulse.model; package de.w665.sharepulse.model;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@ -12,7 +13,10 @@ import java.util.Date;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class UserLogin { public class UserLogin {
String id;
String userId; String userId;
@JsonFormat(timezone = "ETC")
Date loginTime; Date loginTime;
String loginIp; String loginIp;
int sort;
} }

View File

@ -1,6 +1,8 @@
package de.w665.sharepulse.rest.mappings; package de.w665.sharepulse.rest.mappings;
import com.fasterxml.jackson.core.JsonProcessingException;
import de.w665.sharepulse.SharepulseApplication; import de.w665.sharepulse.SharepulseApplication;
import de.w665.sharepulse.db.repo.UserLoginRepository;
import de.w665.sharepulse.db.repo.UserRepository; import de.w665.sharepulse.db.repo.UserRepository;
import de.w665.sharepulse.model.FileUpload; import de.w665.sharepulse.model.FileUpload;
import de.w665.sharepulse.model.User; import de.w665.sharepulse.model.User;
@ -25,11 +27,13 @@ public class Administration extends SecureApiRestController {
private final UserRepository userRepository; private final UserRepository userRepository;
private final AuthenticationService authenticationService; private final AuthenticationService authenticationService;
private final FileCleanupService fileCleanupService; private final FileCleanupService fileCleanupService;
private final UserLoginRepository userLoginRepository;
public Administration(UserRepository userRepository, AuthenticationService authenticationService, FileCleanupService fileCleanupService) { public Administration(UserRepository userRepository, AuthenticationService authenticationService, FileCleanupService fileCleanupService, UserLoginRepository userLoginRepository) {
this.userRepository = userRepository; this.userRepository = userRepository;
this.authenticationService = authenticationService; this.authenticationService = authenticationService;
this.fileCleanupService = fileCleanupService; this.fileCleanupService = fileCleanupService;
this.userLoginRepository = userLoginRepository;
} }
@GetMapping("/statistics") @GetMapping("/statistics")
@ -50,7 +54,11 @@ public class Administration extends SecureApiRestController {
Map<String, Object> response = new HashMap<>(); Map<String, Object> response = new HashMap<>();
response.put("applicationOnlineTime", System.currentTimeMillis() - SharepulseApplication.startTime.getTime()); response.put("applicationOnlineTime", System.currentTimeMillis() - SharepulseApplication.startTime.getTime());
response.put("lastUserLogin", user.getLastLogin()); try {
response.put("lastUserLogin", userLoginRepository.getRecentUserLoginsForUser(user.getId(), 2));
} catch (JsonProcessingException e) {
response.put("lastUserLogin", "Error retrieving last user login");
}
log.debug("Received statistics request"); log.debug("Received statistics request");
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} }

View File

@ -21,6 +21,8 @@ import java.util.Optional;
@Service @Service
public class AuthenticationService { public class AuthenticationService {
static int count = 0;
private final UserRepository userRepository; private final UserRepository userRepository;
private final UserLoginRepository userLoginRepository; private final UserLoginRepository userLoginRepository;
@ -51,7 +53,8 @@ public class AuthenticationService {
Optional<User> optionalUser = userRepository.retrieveUserByUsername(username); Optional<User> optionalUser = userRepository.retrieveUserByUsername(username);
if (optionalUser.isPresent() && passwordEncoder.matches(password, optionalUser.get().getPassword())) { if (optionalUser.isPresent() && passwordEncoder.matches(password, optionalUser.get().getPassword())) {
User user = optionalUser.get(); User user = optionalUser.get();
userLoginRepository.insertUserLogin(new UserLogin(user.getId(), new Date(), remoteAddr));
userLoginRepository.insertUserLogin(new UserLogin("", user.getId(), new Date(), remoteAddr, count++));
userRepository.updateLastLoginForUser(user.getUsername(), new Date()); userRepository.updateLastLoginForUser(user.getUsername(), new Date());
return generateToken(user); return generateToken(user);
} }