diff --git a/frontend/src/app/adminui/adminui.component.html b/frontend/src/app/adminui/adminui.component.html index bf44c56..08a082e 100644 --- a/frontend/src/app/adminui/adminui.component.html +++ b/frontend/src/app/adminui/adminui.component.html @@ -38,7 +38,8 @@
Last Admin Login
-
{{ statistics.lastUserLogin | relativeTime }}
+
{{ statistics.lastUserLogin.loginTime | relativeTime }}
+
First login...
Most recent login
diff --git a/frontend/src/app/adminui/adminui.component.ts b/frontend/src/app/adminui/adminui.component.ts index 35e00f7..00244c6 100644 --- a/frontend/src/app/adminui/adminui.component.ts +++ b/frontend/src/app/adminui/adminui.component.ts @@ -1,5 +1,5 @@ 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 {firstValueFrom} from "rxjs"; import {DevelopmentStore} from "../../store/DevelopmentStore"; @@ -22,7 +22,8 @@ import {EdituserComponent} from "./edituser/edituser.component"; DurationPipe, RelativeTimePipe, FormsModule, - EdituserComponent + EdituserComponent, + NgIf ], templateUrl: './adminui.component.html', styleUrl: './adminui.component.scss' @@ -35,7 +36,7 @@ export class AdminuiComponent { expiredFileUploads: any[] = []; totalFileSizeOnDisk: number = 0; totalFileDownloads = 0; - statistics: any = {}; + statistics: any = ""; username: string = ""; constructor(private developmentStore: DevelopmentStore, private authStore: AuthStore, private router: Router) { diff --git a/src/main/java/de/w665/sharepulse/config/MvcConfig.java b/src/main/java/de/w665/sharepulse/config/MvcConfig.java index 7e1d869..3e3aac3 100644 --- a/src/main/java/de/w665/sharepulse/config/MvcConfig.java +++ b/src/main/java/de/w665/sharepulse/config/MvcConfig.java @@ -16,6 +16,8 @@ public class MvcConfig implements WebMvcConfigurer { registry.addViewController("/upload").setViewName("forward:/index.html"); registry.addViewController("/credits").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"); } } diff --git a/src/main/java/de/w665/sharepulse/db/RethinkDBService.java b/src/main/java/de/w665/sharepulse/db/RethinkDBService.java index e3a67f8..b065186 100644 --- a/src/main/java/de/w665/sharepulse/db/RethinkDBService.java +++ b/src/main/java/de/w665/sharepulse/db/RethinkDBService.java @@ -12,6 +12,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; + import java.util.Optional; @Slf4j @@ -116,6 +117,15 @@ public class RethinkDBService { r.db(config.getDatabase()).table("user_logins").delete().run(connection); 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(); diff --git a/src/main/java/de/w665/sharepulse/db/repo/UserLoginRepository.java b/src/main/java/de/w665/sharepulse/db/repo/UserLoginRepository.java index 30f1107..6085c59 100644 --- a/src/main/java/de/w665/sharepulse/db/repo/UserLoginRepository.java +++ b/src/main/java/de/w665/sharepulse/db/repo/UserLoginRepository.java @@ -1,28 +1,17 @@ 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.net.Connection; import com.rethinkdb.net.Result; import de.w665.sharepulse.db.RethinkDBConfig; 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 lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Repository; - -import java.lang.reflect.Type; -import java.util.*; -import java.util.stream.Collectors; - @Slf4j @Repository public class UserLoginRepository { @@ -48,34 +37,14 @@ public class UserLoginRepository { r.db(config.getDatabase()).table(TABLE_NAME).insert(userLogin).run(connection); } - public List getRecentUserLoginsForUser(String userId, int limit) throws JsonProcessingException { - Result 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>() {}.getType(); - return gson.fromJson(jsonString, listType); - } - - private UserLogin convertToUserLogin(Object obj) { - ObjectMapper mapper = new ObjectMapper(); - return mapper.convertValue(obj, UserLogin.class); - } - - /*public List getRecentUserLoginsForUser(String userId, int limit) { - Result results = r.db(config.getDatabase()).table(TABLE_NAME) + public UserLogin getLastLogin(String userId) { + // Get the second most recent login (the most recent is the current one) + Result result = r.db(config.getDatabase()).table(TABLE_NAME) + .orderBy().optArg("index", r.desc("loginTime")) .filter(r.hashMap("userId", userId)) - .orderBy(r.desc("loginTime")) - .limit(limit) - .run(connection); - List objectList = results.toList(); - List obj = (List) objectList.get(0); - System.out.println(obj.getFirst()); - UserLogin userLogin = (UserLogin) obj.getFirst(); - System.out.println(userLogin.toString()); - return null; - }*/ + .skip(1).limit(1) + .run(connection, UserLogin.class); + // Return the second most recent login if exists + return result.hasNext() ? result.next() : null; + } } diff --git a/src/main/java/de/w665/sharepulse/model/UserLogin.java b/src/main/java/de/w665/sharepulse/model/UserLogin.java index 2e1f261..854f6cc 100644 --- a/src/main/java/de/w665/sharepulse/model/UserLogin.java +++ b/src/main/java/de/w665/sharepulse/model/UserLogin.java @@ -18,5 +18,4 @@ public class UserLogin { @JsonFormat(timezone = "ETC") Date loginTime; String loginIp; - int sort; } diff --git a/src/main/java/de/w665/sharepulse/rest/mappings/Administration.java b/src/main/java/de/w665/sharepulse/rest/mappings/Administration.java index 9fb374e..6632c92 100644 --- a/src/main/java/de/w665/sharepulse/rest/mappings/Administration.java +++ b/src/main/java/de/w665/sharepulse/rest/mappings/Administration.java @@ -1,6 +1,5 @@ package de.w665.sharepulse.rest.mappings; -import com.fasterxml.jackson.core.JsonProcessingException; import de.w665.sharepulse.SharepulseApplication; import de.w665.sharepulse.db.repo.UserLoginRepository; import de.w665.sharepulse.db.repo.UserRepository; @@ -38,7 +37,6 @@ public class Administration extends SecureApiRestController { @GetMapping("/statistics") public ResponseEntity getStatistics(HttpServletRequest request) { - // TODO: FIX LAST LOGIN String token = request.getHeader("Authorization"); @@ -54,12 +52,7 @@ public class Administration extends SecureApiRestController { Map response = new HashMap<>(); response.put("applicationOnlineTime", System.currentTimeMillis() - SharepulseApplication.startTime.getTime()); - 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"); + response.put("lastUserLogin", userLoginRepository.getLastLogin(user.getId())); return ResponseEntity.ok(response); } diff --git a/src/main/java/de/w665/sharepulse/service/AuthenticationService.java b/src/main/java/de/w665/sharepulse/service/AuthenticationService.java index 11bc828..6a39d09 100644 --- a/src/main/java/de/w665/sharepulse/service/AuthenticationService.java +++ b/src/main/java/de/w665/sharepulse/service/AuthenticationService.java @@ -20,9 +20,6 @@ import java.util.Optional; @Slf4j @Service public class AuthenticationService { - - static int count = 0; - private final UserRepository userRepository; private final UserLoginRepository userLoginRepository; @@ -54,7 +51,7 @@ public class AuthenticationService { if (optionalUser.isPresent() && passwordEncoder.matches(password, optionalUser.get().getPassword())) { 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()); return generateToken(user); }