From acecc02b2bd43476752af3214a02a445eff4070a Mon Sep 17 00:00:00 2001 From: = Date: Wed, 27 Feb 2019 22:04:15 +0100 Subject: [PATCH] Fixes Issue #151 --- .../controllers/login/LoginController.java | 13 +- .../com/viadee/sonarquest/entities/User.java | 507 +++++++++--------- .../sonarquest/services/UserService.java | 148 ++--- .../schema/V0_0_8__SQUser_login_timestamp.sql | 2 + .../src/app/Interfaces/User.ts | 3 +- .../admin-developer.component.ts | 2 + .../src/app/services/user.service.ts | 7 +- sonarQuest-frontend/src/assets/i18n/de.json | 3 +- sonarQuest-frontend/src/assets/i18n/en.json | 3 +- 9 files changed, 362 insertions(+), 326 deletions(-) create mode 100644 sonarQuest-backend/src/main/resources/db/schema/V0_0_8__SQUser_login_timestamp.sql diff --git a/sonarQuest-backend/src/main/java/com/viadee/sonarquest/controllers/login/LoginController.java b/sonarQuest-backend/src/main/java/com/viadee/sonarquest/controllers/login/LoginController.java index ce2ff2e8..40c02b1c 100644 --- a/sonarQuest-backend/src/main/java/com/viadee/sonarquest/controllers/login/LoginController.java +++ b/sonarQuest-backend/src/main/java/com/viadee/sonarquest/controllers/login/LoginController.java @@ -20,6 +20,7 @@ import com.viadee.sonarquest.controllers.PathConstants; import com.viadee.sonarquest.security.JwtHelper; +import com.viadee.sonarquest.services.UserService; @RestController @RequestMapping(PathConstants.LOGIN_URL) @@ -33,6 +34,9 @@ public class LoginController { @Autowired private JwtHelper jwtHelper; + @Autowired + private UserService userService; + @GetMapping public String info() { return "Dies ist eine Login Seite"; @@ -41,10 +45,11 @@ public String info() { @PostMapping public Token login(@Valid @RequestBody final UserCredentials credentials) { - String username = credentials.getUsername(); + final String username = credentials.getUsername(); LOGGER.info("Log-In request received from user {}", Objects.hashCode(username)); final User authenticatedUser = authentificateUser(credentials); final Token token = createTokenForUser(authenticatedUser); + userService.updateLastLogin(authenticatedUser.getUsername()); LOGGER.info("Log-In request successful for user {}", Objects.hashCode(username)); return token; } @@ -55,12 +60,12 @@ private User authentificateUser(final UserCredentials credentials) { } private Authentication authenticate(final UserCredentials credentials) { - String username = credentials.getUsername(); - UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, + final String username = credentials.getUsername(); + final UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, credentials.getPassword()); try { return authenticationManager.authenticate(authToken); - } catch (BadCredentialsException ex) { + } catch (final BadCredentialsException ex) { LOGGER.warn(String.format("Log-In request denied with bad credentials for user %s", Objects.hashCode(username))); throw ex; diff --git a/sonarQuest-backend/src/main/java/com/viadee/sonarquest/entities/User.java b/sonarQuest-backend/src/main/java/com/viadee/sonarquest/entities/User.java index d97dbadf..46e9436f 100644 --- a/sonarQuest-backend/src/main/java/com/viadee/sonarquest/entities/User.java +++ b/sonarQuest-backend/src/main/java/com/viadee/sonarquest/entities/User.java @@ -1,5 +1,6 @@ package com.viadee.sonarquest.entities; +import java.sql.Timestamp; import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; @@ -26,268 +27,278 @@ @Table(name = "SQUser") public class User { - @Id - @GeneratedValue - private Long id; + @Id + @GeneratedValue + private Long id; - @Column(name = "username") - private String username; + @Column(name = "username") + private String username; - @Column(name = "password") - private String password; + @Column(name = "password") + private String password; - @ManyToOne - @JoinColumn(name = "role_id") - private Role role; + @ManyToOne + @JoinColumn(name = "role_id") + private Role role; - @Column(name = "picture") - private String picture; + @Column(name = "picture") + private String picture; - @Column(name = "about_me") - private String aboutMe; + @Column(name = "about_me") + private String aboutMe; - @ManyToOne - @JoinColumn(name = "avatar_class_id") - private AvatarClass avatarClass; + @ManyToOne + @JoinColumn(name = "avatar_class_id") + private AvatarClass avatarClass; - @ManyToOne - @JoinColumn(name = "avatar_race_id") - private AvatarRace avatarRace; + @ManyToOne + @JoinColumn(name = "avatar_race_id") + private AvatarRace avatarRace; - @Column(name = "gold") - private Long gold; + @Column(name = "gold") + private Long gold; - @Column(name = "xp") - private Long xp; + @Column(name = "xp") + private Long xp; - @ManyToOne - @JoinColumn(name = "level_id") - private Level level; + @ManyToOne + @JoinColumn(name = "level_id") + private Level level; - @ManyToOne - @JoinColumn(name = "current_world_id") - private World currentWorld; + @ManyToOne + @JoinColumn(name = "current_world_id") + private World currentWorld; - @JsonIgnore - @ManyToMany(cascade = CascadeType.ALL) - @JoinTable(name = "User_To_World", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "world_id", referencedColumnName = "id")) - private List worlds = new ArrayList<>(0); + @Column(name = "last_login") + private Timestamp lastLogin; - @ManyToMany(cascade = CascadeType.ALL) - @JoinTable(name = "User_Artefact", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "artefact_id", referencedColumnName = "id")) - private List artefacts = new ArrayList<>(0); + @JsonIgnore + @ManyToMany(cascade = CascadeType.ALL) + @JoinTable(name = "User_To_World", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "world_id", referencedColumnName = "id")) + private List worlds = new ArrayList<>(0); - @JsonIgnore - @ManyToMany(mappedBy = "users", cascade = CascadeType.ALL) - private List adventures = new ArrayList<>(0); + @ManyToMany(cascade = CascadeType.ALL) + @JoinTable(name = "User_Artefact", joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"), inverseJoinColumns = @JoinColumn(name = "artefact_id", referencedColumnName = "id")) + private List artefacts = new ArrayList<>(0); - @JsonIgnore - @OneToMany(cascade = CascadeType.ALL, mappedBy = "user") - private List participations = new ArrayList<>(0); - - @OneToOne(mappedBy = "user", orphanRemoval = true) - private UiDesign uiDesign; - - public Long getId() { - return id; - } - - public void setId(final Long id) { - this.id = id; - } - - public String getUsername() { - return username; - } - - public void setUsername(final String username) { - this.username = username; - } - - public String getPassword() { - return password; - } - - public void setPassword(final String password) { - this.password = password; - } - - public Role getRole() { - return role; - } - - public void setRole(final Role role) { - this.role = role; - } - - public String getPicture() { - return picture; - } - - public void setPicture(final String picture) { - this.picture = picture; - } - - public String getAboutMe() { - return aboutMe; - } - - public void setAboutMe(final String aboutMe) { - this.aboutMe = aboutMe; - } - - public AvatarClass getAvatarClass() { - return avatarClass; - } - - public void setAvatarClass(final AvatarClass avatarClass) { - this.avatarClass = avatarClass; - } - - public AvatarRace getAvatarRace() { - return avatarRace; - } - - public void setAvatarRace(final AvatarRace avatarRace) { - this.avatarRace = avatarRace; - } - - public Long getGold() { - return gold; - } - - public void setGold(final Long gold) { - this.gold = gold; - } - - public Long getXp() { - return xp; - } - - public void setXp(final Long xp) { - this.xp = xp; - } - - /** - * Adds the specified amount of gold. - * - * @param gold - * the amount to add, must be positive or zero. - */ - public void addGold(final long gold) { - Validate.isTrue(gold >= 0); - this.gold += gold; - } - - /** - * Adds the specified amount of XPerience Points. - * - * @param xp - * the amount to add, must be positive or zero. - */ - public void addXp(final long xp) { - Validate.isTrue(xp >= 0); - this.xp += xp; - } - - public Level getLevel() { - return level; - } - - public void setLevel(final Level level) { - this.level = level; - } - - public List getWorlds() { - return worlds; - } - - public void setWorlds(final List worlds) { - this.worlds = worlds; - } - - public void addWorld(final World world) { - getWorlds().add(world); - } - - public void removeWorld(final World world) { - getWorlds().remove(world); - } - - public List getArtefacts() { - return artefacts; - } - - public void setArtefacts(final List artefacts) { - this.artefacts = artefacts; - } - - public List getAdventures() { - return adventures; - } - - public void setAdventures(final List adventures) { - this.adventures = adventures; - } - - public List getParticipations() { - return participations; - } - - public void setParticipations(final List participations) { - this.participations = participations; - } - - public World getCurrentWorld() { - return currentWorld; - } - - public void setCurrentWorld(final World currentWorld) { - this.currentWorld = currentWorld; - } - - public UiDesign getUiDesign() { - return uiDesign; - } - - public void setUiDesign(final UiDesign uiDesign) { - this.uiDesign = uiDesign; - } - - public boolean isGamemaster() { - return getRole().getName() == RoleName.GAMEMASTER; - } - - public boolean isAdmin() { - return getRole().getName() == RoleName.ADMIN; - } - - public boolean isDeveloper() { - return getRole().getName() == RoleName.DEVELOPER; - } - - /** - * Looks up the names of all joined ("active") worlds and returns them in a - * list. - */ - public List getJoinedWorlds() { - if (worlds != null) { - return worlds.stream().map(World::getName).collect(Collectors.toList()); - } else { - return new ArrayList<>(); - } - } - - @Override - public int hashCode() { - return this.getId() == null ? super.hashCode() : Objects.hashCode(this.getId()); - } - - @Override - public boolean equals(final Object that) { - return this.getId() == null ? this == that - : that != null && this.getClass().isInstance(that) - && Objects.equal(this.getId(), ((User) that).getId()); - } + @JsonIgnore + @ManyToMany(mappedBy = "users", cascade = CascadeType.ALL) + private List adventures = new ArrayList<>(0); + + @JsonIgnore + @OneToMany(cascade = CascadeType.ALL, mappedBy = "user") + private List participations = new ArrayList<>(0); + + @OneToOne(mappedBy = "user", orphanRemoval = true) + private UiDesign uiDesign; + + public Long getId() { + return id; + } + + public void setId(final Long id) { + this.id = id; + } + + public String getUsername() { + return username; + } + + public void setUsername(final String username) { + this.username = username; + } + + public String getPassword() { + return password; + } + + public void setPassword(final String password) { + this.password = password; + } + + public Role getRole() { + return role; + } + + public void setRole(final Role role) { + this.role = role; + } + + public String getPicture() { + return picture; + } + + public void setPicture(final String picture) { + this.picture = picture; + } + + public String getAboutMe() { + return aboutMe; + } + + public void setAboutMe(final String aboutMe) { + this.aboutMe = aboutMe; + } + + public AvatarClass getAvatarClass() { + return avatarClass; + } + + public void setAvatarClass(final AvatarClass avatarClass) { + this.avatarClass = avatarClass; + } + + public AvatarRace getAvatarRace() { + return avatarRace; + } + + public void setAvatarRace(final AvatarRace avatarRace) { + this.avatarRace = avatarRace; + } + + public Long getGold() { + return gold; + } + + public void setGold(final Long gold) { + this.gold = gold; + } + + public Long getXp() { + return xp; + } + + public void setXp(final Long xp) { + this.xp = xp; + } + + /** + * Adds the specified amount of gold. + * + * @param gold + * the amount to add, must be positive or zero. + */ + public void addGold(final long gold) { + Validate.isTrue(gold >= 0); + this.gold += gold; + } + + /** + * Adds the specified amount of XPerience Points. + * + * @param xp + * the amount to add, must be positive or zero. + */ + public void addXp(final long xp) { + Validate.isTrue(xp >= 0); + this.xp += xp; + } + + public Level getLevel() { + return level; + } + + public void setLevel(final Level level) { + this.level = level; + } + + public List getWorlds() { + return worlds; + } + + public void setWorlds(final List worlds) { + this.worlds = worlds; + } + + public void addWorld(final World world) { + getWorlds().add(world); + } + + public void removeWorld(final World world) { + getWorlds().remove(world); + } + + public List getArtefacts() { + return artefacts; + } + + public void setArtefacts(final List artefacts) { + this.artefacts = artefacts; + } + + public List getAdventures() { + return adventures; + } + + public void setAdventures(final List adventures) { + this.adventures = adventures; + } + + public List getParticipations() { + return participations; + } + + public void setParticipations(final List participations) { + this.participations = participations; + } + + public World getCurrentWorld() { + return currentWorld; + } + + public void setCurrentWorld(final World currentWorld) { + this.currentWorld = currentWorld; + } + + public UiDesign getUiDesign() { + return uiDesign; + } + + public void setUiDesign(final UiDesign uiDesign) { + this.uiDesign = uiDesign; + } + + public boolean isGamemaster() { + return getRole().getName() == RoleName.GAMEMASTER; + } + + public boolean isAdmin() { + return getRole().getName() == RoleName.ADMIN; + } + + public boolean isDeveloper() { + return getRole().getName() == RoleName.DEVELOPER; + } + + public Timestamp getLastLogin() { + return lastLogin; + } + + public void setLastLogin(final Timestamp lastLogin) { + this.lastLogin = lastLogin; + } + + /** + * Looks up the names of all joined ("active") worlds and returns them in a list. + */ + public List getJoinedWorlds() { + if (worlds != null) { + return worlds.stream().map(World::getName).collect(Collectors.toList()); + } else { + return new ArrayList<>(); + } + } + + @Override + public int hashCode() { + return this.getId() == null ? super.hashCode() : Objects.hashCode(this.getId()); + } + + @Override + public boolean equals(final Object that) { + return this.getId() == null ? this == that + : that != null && this.getClass().isInstance(that) + && Objects.equal(this.getId(), ((User) that).getId()); + } } diff --git a/sonarQuest-backend/src/main/java/com/viadee/sonarquest/services/UserService.java b/sonarQuest-backend/src/main/java/com/viadee/sonarquest/services/UserService.java index 3c4d8369..9134dcd9 100644 --- a/sonarQuest-backend/src/main/java/com/viadee/sonarquest/services/UserService.java +++ b/sonarQuest-backend/src/main/java/com/viadee/sonarquest/services/UserService.java @@ -1,5 +1,7 @@ package com.viadee.sonarquest.services; +import java.sql.Timestamp; +import java.time.LocalDateTime; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -44,108 +46,114 @@ public class UserService implements UserDetailsService { @Override public UserDetails loadUserByUsername(final String username) throws UsernameNotFoundException { - final User user = findByUsername(username); - final Set permissions = permissionService.getAccessPermissions(user); + final User user = findByUsername(username); + final Set permissions = permissionService.getAccessPermissions(user); - final List authoritys = permissions.stream() - .map(berechtigung -> new SimpleGrantedAuthority(berechtigung.getPermission())) - .collect(Collectors.toList()); + final List authoritys = permissions.stream() + .map(berechtigung -> new SimpleGrantedAuthority(berechtigung.getPermission())) + .collect(Collectors.toList()); - return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), true, - true, true, true, authoritys); + return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(), true, + true, true, true, authoritys); } public User findByUsername(final String username) { - return userRepository.findByUsername(username); + return userRepository.findByUsername(username); } private User findById(final Long id) { - return userRepository.findOne(id); + return userRepository.findOne(id); } public World updateUsersCurrentWorld(final User user, final Long worldId) { - final World world = worldService.findById(worldId); - user.setCurrentWorld(world); - userRepository.saveAndFlush(user); - return user.getCurrentWorld(); + final World world = worldService.findById(worldId); + user.setCurrentWorld(world); + userRepository.saveAndFlush(user); + return user.getCurrentWorld(); } public synchronized User save(final User user) { - User toBeSaved = null; - String username = user.getUsername(); - BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); - if (user.getId() == null) { - // Only the password hash needs to be saved - String password = encoder.encode(user.getPassword()); - Role role = user.getRole(); - RoleName roleName = role.getName(); - Role userRole = roleService.findByName(roleName); - toBeSaved = usernameFree(username) ? user : null; - if (toBeSaved != null) { - toBeSaved.setPassword(password); - toBeSaved.setRole(userRole); - toBeSaved.setCurrentWorld(user.getCurrentWorld()); - toBeSaved.setGold(0l); - toBeSaved.setXp(0l); - toBeSaved.setLevel(levelService.getLevelByUserXp(0l)); - } - } else { - toBeSaved = findById(user.getId()); - if (toBeSaved != null) { - if (!username.equals(toBeSaved.getUsername()) && usernameFree(username)) { - toBeSaved.setUsername(username); - } - // if there are identical hashes in the pw fields, do not touch them - if (!toBeSaved.getPassword().equals(user.getPassword())) { - // change password only if it differs from the old one - String oldPassHash = toBeSaved.getPassword(); - if (!oldPassHash.equals(user.getPassword()) || !encoder.matches(user.getPassword(), oldPassHash)) { - String password = encoder.encode(user.getPassword()); - toBeSaved.setPassword(password); - LOGGER.info("The password for user " + user.getUsername() + " (id: " + user.getId() - + ") has been changed."); - } - } - Role role = user.getRole(); - RoleName roleName = role.getName(); - Role userRole = roleService.findByName(roleName); - toBeSaved.setRole(userRole); - toBeSaved.setAboutMe(user.getAboutMe()); - toBeSaved.setPicture(user.getPicture()); - toBeSaved.setCurrentWorld(user.getCurrentWorld()); - toBeSaved.setWorlds(user.getWorlds()); - toBeSaved.setGold(user.getGold()); - toBeSaved.setXp(user.getXp()); - toBeSaved.setLevel(levelService.getLevelByUserXp(user.getXp())); - } - } - - return toBeSaved != null ? userRepository.saveAndFlush(toBeSaved) : null; + User toBeSaved = null; + final String username = user.getUsername(); + final BCryptPasswordEncoder encoder = new BCryptPasswordEncoder(); + if (user.getId() == null) { + // Only the password hash needs to be saved + final String password = encoder.encode(user.getPassword()); + final Role role = user.getRole(); + final RoleName roleName = role.getName(); + final Role userRole = roleService.findByName(roleName); + toBeSaved = usernameFree(username) ? user : null; + if (toBeSaved != null) { + toBeSaved.setPassword(password); + toBeSaved.setRole(userRole); + toBeSaved.setCurrentWorld(user.getCurrentWorld()); + toBeSaved.setGold(0l); + toBeSaved.setXp(0l); + toBeSaved.setLevel(levelService.getLevelByUserXp(0l)); + } + } else { + toBeSaved = findById(user.getId()); + if (toBeSaved != null) { + if (!username.equals(toBeSaved.getUsername()) && usernameFree(username)) { + toBeSaved.setUsername(username); + } + // if there are identical hashes in the pw fields, do not touch them + if (!toBeSaved.getPassword().equals(user.getPassword())) { + // change password only if it differs from the old one + final String oldPassHash = toBeSaved.getPassword(); + if (!oldPassHash.equals(user.getPassword()) || !encoder.matches(user.getPassword(), oldPassHash)) { + final String password = encoder.encode(user.getPassword()); + toBeSaved.setPassword(password); + LOGGER.info("The password for user " + user.getUsername() + " (id: " + user.getId() + + ") has been changed."); + } + } + final Role role = user.getRole(); + final RoleName roleName = role.getName(); + final Role userRole = roleService.findByName(roleName); + toBeSaved.setRole(userRole); + toBeSaved.setAboutMe(user.getAboutMe()); + toBeSaved.setPicture(user.getPicture()); + toBeSaved.setCurrentWorld(user.getCurrentWorld()); + toBeSaved.setWorlds(user.getWorlds()); + toBeSaved.setGold(user.getGold()); + toBeSaved.setXp(user.getXp()); + toBeSaved.setLevel(levelService.getLevelByUserXp(user.getXp())); + } + } + + return toBeSaved != null ? userRepository.saveAndFlush(toBeSaved) : null; } private boolean usernameFree(final String username) { - return userRepository.findByUsername(username) == null; + return userRepository.findByUsername(username) == null; } public void delete(final Long userId) { - final User user = findById(userId); - userRepository.delete(user); + final User user = findById(userId); + userRepository.delete(user); } public User findById(final long userId) { - return userRepository.findOne(userId); + return userRepository.findOne(userId); } public List findAll() { - return userRepository.findAll(); + return userRepository.findAll(); } public List findByRole(final RoleName roleName) { - return findAll().stream().filter(user -> user.getRole().getName() == roleName).collect(Collectors.toList()); + return findAll().stream().filter(user -> user.getRole().getName() == roleName).collect(Collectors.toList()); } public Level getLevel(final long xp) { - return levelService.getLevelByUserXp(xp); + return levelService.getLevelByUserXp(xp); + } + + public void updateLastLogin(final String username) { + final User user = findByUsername(username); + user.setLastLogin(Timestamp.valueOf(LocalDateTime.now())); + save(user); } } diff --git a/sonarQuest-backend/src/main/resources/db/schema/V0_0_8__SQUser_login_timestamp.sql b/sonarQuest-backend/src/main/resources/db/schema/V0_0_8__SQUser_login_timestamp.sql new file mode 100644 index 00000000..49494e94 --- /dev/null +++ b/sonarQuest-backend/src/main/resources/db/schema/V0_0_8__SQUser_login_timestamp.sql @@ -0,0 +1,2 @@ +-- Layout of quests can be set in the world properties (from the admin area) +ALTER TABLE SQUser ADD COLUMN last_login TIMESTAMP; diff --git a/sonarQuest-frontend/src/app/Interfaces/User.ts b/sonarQuest-frontend/src/app/Interfaces/User.ts index 58ebb94a..f322aa3c 100644 --- a/sonarQuest-frontend/src/app/Interfaces/User.ts +++ b/sonarQuest-frontend/src/app/Interfaces/User.ts @@ -19,7 +19,8 @@ export interface User { artefacts?: Artefact[], password?: string, currentWorld?: World, - joinedWorlds?: string[] + joinedWorlds?: string[], + lastLogin?: string } diff --git a/sonarQuest-frontend/src/app/pages/admin-page/components/admin-developer/admin-developer.component.ts b/sonarQuest-frontend/src/app/pages/admin-page/components/admin-developer/admin-developer.component.ts index 3434c4cc..65cef83a 100644 --- a/sonarQuest-frontend/src/app/pages/admin-page/components/admin-developer/admin-developer.component.ts +++ b/sonarQuest-frontend/src/app/pages/admin-page/components/admin-developer/admin-developer.component.ts @@ -25,6 +25,7 @@ export class AdminDeveloperComponent implements OnInit { { name: 'gold', label: 'Gold'}, { name: 'currentWorld.name', label: 'Current World' }, { name: 'joinedWorlds', label: 'Active Worlds' }, + { name: 'lastLogin', label: 'Last Login' }, { name: 'edit', label: '' } ]; @@ -60,6 +61,7 @@ export class AdminDeveloperComponent implements OnInit { { name: 'gold', label: col_names.GOLD }, { name: 'currentWorld.name', label: col_names.ACTIVE_WORLD }, { name: 'joinedWorlds', label: col_names.JOINED }, + { name: 'lastLogin', label: col_names.LAST_LOGIN }, { name: 'edit', label: '' }] }); } diff --git a/sonarQuest-frontend/src/app/services/user.service.ts b/sonarQuest-frontend/src/app/services/user.service.ts index fb2334b8..1eb98907 100644 --- a/sonarQuest-frontend/src/app/services/user.service.ts +++ b/sonarQuest-frontend/src/app/services/user.service.ts @@ -5,6 +5,7 @@ import {environment} from '../../environments/environment'; import {Observable} from 'rxjs/Observable'; import {AuthenticationService} from '../login/authentication.service'; import {Subscriber} from 'rxjs/Subscriber'; +import {map, tap} from 'rxjs/operators'; @Injectable() export class UserService { @@ -51,7 +52,11 @@ export class UserService { public getUsers(): Observable { const url = `${environment.endpoint}/user/all`; - return this.httpClient.get (url); + return this.httpClient.get (url).pipe(tap((users: User[]) => { + users.forEach(user => + user.lastLogin = user.lastLogin ? new Date(user.lastLogin).toTimeString() : null + ) + })); } public getImage(): Observable { diff --git a/sonarQuest-frontend/src/assets/i18n/de.json b/sonarQuest-frontend/src/assets/i18n/de.json index 37f88f5b..049bcf06 100644 --- a/sonarQuest-frontend/src/assets/i18n/de.json +++ b/sonarQuest-frontend/src/assets/i18n/de.json @@ -106,7 +106,8 @@ "PLAYERS": "Spieler", "ROLE": "Rolle", "VISIBLE": "Sichtbar", - "USE_QUEST_CARDS": "Questcards" + "USE_QUEST_CARDS": "Questcards", + "LAST_LOGIN": "Letzter Login" }, "INFO": { "NO_ENTRIES": "keine Einträge", diff --git a/sonarQuest-frontend/src/assets/i18n/en.json b/sonarQuest-frontend/src/assets/i18n/en.json index 4b1aff20..33951f42 100644 --- a/sonarQuest-frontend/src/assets/i18n/en.json +++ b/sonarQuest-frontend/src/assets/i18n/en.json @@ -106,7 +106,8 @@ "PLAYERS": "Players", "ROLE": "Role", "VISIBLE": "Visible", - "USE_QUEST_CARDS": "Questcards" + "USE_QUEST_CARDS": "Questcards", + "LAST_LOGIN": "Last Login" }, "INFO": { "NO_ENTRIES": "no entries",