Fixed user last login query

- Added frontend logic for first login
- Added table index for user_logins table
- Updated statistics rest endpoint
This commit is contained in:
Max W. 2024-06-02 23:13:31 +02:00
parent 348bf8050c
commit 9ebbcc00ea
8 changed files with 29 additions and 57 deletions

View File

@ -38,7 +38,8 @@
<div class="shadow stats bg-white"> <div class="shadow stats bg-white">
<div class="stat"> <div class="stat">
<div class="stat-title">Last Admin Login</div> <div class="stat-title">Last Admin Login</div>
<div class="stat-value">{{ statistics.lastUserLogin | relativeTime }}</div> <div *ngIf="statistics.lastUserLogin" class="stat-value">{{ statistics.lastUserLogin.loginTime | relativeTime }}</div>
<div *ngIf="!statistics.lastUserLogin" class="stat-value">First login...</div>
<div class="stat-desc">Most recent login</div> <div class="stat-desc">Most recent login</div>
</div> </div>
</div> </div>

View File

@ -1,5 +1,5 @@
import {Component, ElementRef, ViewChild} from '@angular/core'; import {Component, ElementRef, ViewChild} from '@angular/core';
import {DatePipe, DecimalPipe, NgForOf} from "@angular/common"; import {DatePipe, DecimalPipe, NgForOf, NgIf} from "@angular/common";
import axios from "axios"; import axios from "axios";
import {firstValueFrom} from "rxjs"; import {firstValueFrom} from "rxjs";
import {DevelopmentStore} from "../../store/DevelopmentStore"; import {DevelopmentStore} from "../../store/DevelopmentStore";
@ -22,7 +22,8 @@ import {EdituserComponent} from "./edituser/edituser.component";
DurationPipe, DurationPipe,
RelativeTimePipe, RelativeTimePipe,
FormsModule, FormsModule,
EdituserComponent EdituserComponent,
NgIf
], ],
templateUrl: './adminui.component.html', templateUrl: './adminui.component.html',
styleUrl: './adminui.component.scss' styleUrl: './adminui.component.scss'
@ -35,7 +36,7 @@ export class AdminuiComponent {
expiredFileUploads: any[] = []; expiredFileUploads: any[] = [];
totalFileSizeOnDisk: number = 0; totalFileSizeOnDisk: number = 0;
totalFileDownloads = 0; totalFileDownloads = 0;
statistics: any = {}; statistics: any = "";
username: string = ""; username: string = "";
constructor(private developmentStore: DevelopmentStore, private authStore: AuthStore, private router: Router) { constructor(private developmentStore: DevelopmentStore, private authStore: AuthStore, private router: Router) {

View File

@ -16,6 +16,8 @@ public class MvcConfig implements WebMvcConfigurer {
registry.addViewController("/upload").setViewName("forward:/index.html"); registry.addViewController("/upload").setViewName("forward:/index.html");
registry.addViewController("/credits").setViewName("forward:/index.html"); registry.addViewController("/credits").setViewName("forward:/index.html");
registry.addViewController("/licenses").setViewName("forward:/index.html"); registry.addViewController("/licenses").setViewName("forward:/index.html");
registry.addViewController("/login").setViewName("forward:/index.html");
registry.addViewController("/secure/administration").setViewName("forward:/index.html");
} }
} }

View File

@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.Optional; import java.util.Optional;
@Slf4j @Slf4j
@ -116,6 +117,15 @@ public class RethinkDBService {
r.db(config.getDatabase()).table("user_logins").delete().run(connection); r.db(config.getDatabase()).table("user_logins").delete().run(connection);
log.debug("Table 'user_logins' cleared successfully."); log.debug("Table 'user_logins' cleared successfully.");
} }
} finally {
try {
r.db(config.getDatabase()).table("user_logins").indexCreate("loginTime").run(connection);
log.debug("Secondary index 'loginTime' on table 'user_logins' successfully created.");
} catch (ReqlOpFailedError e) {
log.debug("Secondary index 'loginTime' already exists.");
} finally {
r.db(config.getDatabase()).table("user_logins").indexWait("loginTime").run(connection);
}
} }
initializeAdminUser(); initializeAdminUser();

View File

@ -1,28 +1,17 @@
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.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson; 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 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 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 @Slf4j
@Repository @Repository
public class UserLoginRepository { public class UserLoginRepository {
@ -48,34 +37,14 @@ public class UserLoginRepository {
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 { public UserLogin getLastLogin(String userId) {
Result<Object> rawResult = r.db("sharepulse").table(TABLE_NAME) // Get the second most recent login (the most recent is the current one)
Result<UserLogin> result = r.db(config.getDatabase()).table(TABLE_NAME)
.orderBy().optArg("index", r.desc("loginTime"))
.filter(r.hashMap("userId", userId)) .filter(r.hashMap("userId", userId))
.orderBy(r.desc("sort")) .skip(1).limit(1)
.limit(limit) .run(connection, UserLogin.class);
.run(connection); // Return the second most recent login if exists
return result.hasNext() ? result.next() : null;
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

@ -18,5 +18,4 @@ public class UserLogin {
@JsonFormat(timezone = "ETC") @JsonFormat(timezone = "ETC")
Date loginTime; Date loginTime;
String loginIp; String loginIp;
int sort;
} }

View File

@ -1,6 +1,5 @@
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.UserLoginRepository;
import de.w665.sharepulse.db.repo.UserRepository; import de.w665.sharepulse.db.repo.UserRepository;
@ -38,7 +37,6 @@ public class Administration extends SecureApiRestController {
@GetMapping("/statistics") @GetMapping("/statistics")
public ResponseEntity<Object> getStatistics(HttpServletRequest request) { public ResponseEntity<Object> getStatistics(HttpServletRequest request) {
// TODO: FIX LAST LOGIN // TODO: FIX LAST LOGIN
String token = request.getHeader("Authorization"); String token = request.getHeader("Authorization");
@ -54,12 +52,7 @@ 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());
try { response.put("lastUserLogin", userLoginRepository.getLastLogin(user.getId()));
response.put("lastUserLogin", userLoginRepository.getRecentUserLoginsForUser(user.getId(), 2));
} catch (JsonProcessingException e) {
response.put("lastUserLogin", "Error retrieving last user login");
}
log.debug("Received statistics request");
return ResponseEntity.ok(response); return ResponseEntity.ok(response);
} }

View File

@ -20,9 +20,6 @@ import java.util.Optional;
@Slf4j @Slf4j
@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;
@ -54,7 +51,7 @@ public class AuthenticationService {
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, count++)); userLoginRepository.insertUserLogin(new UserLogin(""/*Auto generated*/, user.getId(), new Date(), remoteAddr));
userRepository.updateLastLoginForUser(user.getUsername(), new Date()); userRepository.updateLastLoginForUser(user.getUsername(), new Date());
return generateToken(user); return generateToken(user);
} }