database WIP

This commit is contained in:
Patrick Haßel 2024-10-24 16:37:15 +02:00
parent d1521417cf
commit e45117b57f
8 changed files with 116 additions and 69 deletions

13
pom.xml
View File

@ -32,6 +32,19 @@
<artifactId>spring-boot-starter-websocket</artifactId> <artifactId>spring-boot-starter-websocket</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
<dependency> <dependency>
<groupId>org.projectlombok</groupId> <groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId> <artifactId>lombok</artifactId>

View File

@ -2,10 +2,8 @@ package de.ph87.tools.session;
import de.ph87.tools.user.User; import de.ph87.tools.user.User;
import de.ph87.tools.web.IWebSocketMessage; import de.ph87.tools.web.IWebSocketMessage;
import lombok.Getter; import jakarta.persistence.*;
import lombok.NonNull; import lombok.*;
import lombok.Setter;
import lombok.ToString;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.HashSet; import java.util.HashSet;
@ -13,36 +11,47 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@Entity
@Getter @Getter
@ToString @ToString
@NoArgsConstructor
public abstract class AbstractSession implements IWebSocketMessage { public abstract class AbstractSession implements IWebSocketMessage {
@Id
@NonNull @NonNull
public final String uuid = UUID.randomUUID().toString(); @Column(nullable = false)
private String uuid = UUID.randomUUID().toString();
@NonNull @NonNull
public final User owner; @ManyToOne(optional = false)
private User owner;
@NonNull @NonNull
public final ZonedDateTime created = ZonedDateTime.now(); @Column(nullable = false)
private ZonedDateTime created = ZonedDateTime.now();
@NonNull @NonNull
@ManyToMany
@ToString.Exclude @ToString.Exclude
private final Set<User> users = new HashSet<>(); private Set<User> users = new HashSet<>();
@Setter @Setter
@NonNull @NonNull
public String title = "Spiel ohne Namen"; @Column(nullable = false)
private String title = "Spiel ohne Namen";
@Setter @Setter
@NonNull @NonNull
@ToString.Exclude @ToString.Exclude
@Column(nullable = false)
private String password = UUID.randomUUID().toString().substring(0, 4); private String password = UUID.randomUUID().toString().substring(0, 4);
@Setter @Setter
@Column(nullable = false)
private boolean initial = true; private boolean initial = true;
@NonNull @NonNull
@Column(nullable = false)
private ZonedDateTime lastAccess = created; private ZonedDateTime lastAccess = created;
protected AbstractSession(@NonNull final User user) { protected AbstractSession(@NonNull final User user) {
@ -50,16 +59,12 @@ public abstract class AbstractSession implements IWebSocketMessage {
} }
public void join(@NonNull final User user) { public void join(@NonNull final User user) {
synchronized (uuid) { users.add(user);
users.add(user); touch();
touch();
}
} }
public void leave(@NonNull final User user) { public void leave(@NonNull final User user) {
synchronized (uuid) { users.remove(user);
users.remove(user);
}
} }
private void touch() { private void touch() {
@ -71,4 +76,21 @@ public abstract class AbstractSession implements IWebSocketMessage {
return List.of("Number", uuid); return List.of("Number", uuid);
} }
public boolean isOwnedBy(@NonNull final User user) {
return owner.equals(user);
}
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof final AbstractSession session)) {
return false;
}
return session.uuid.equals(this.uuid);
}
@Override
public int hashCode() {
return uuid.hashCode();
}
} }

View File

@ -40,9 +40,7 @@ public abstract class AbstractSessionController<SESSION extends AbstractSession>
@Scheduled(timeUnit = TimeUnit.MINUTES, initialDelay = 5, fixedRate = 5) @Scheduled(timeUnit = TimeUnit.MINUTES, initialDelay = 5, fixedRate = 5)
public void cleanUp() { public void cleanUp() {
final ZonedDateTime deadline = ZonedDateTime.now().minusDays(30); final ZonedDateTime deadline = ZonedDateTime.now().minusDays(30);
synchronized (sessions) { sessions.stream().filter(session -> session.getLastAccess().isBefore(deadline)).forEach(this::delete);
sessions.stream().filter(session -> session.getLastAccess().isBefore(deadline)).forEach(this::delete);
}
} }
private void delete(@NonNull final SESSION session) { private void delete(@NonNull final SESSION session) {
@ -63,9 +61,7 @@ public abstract class AbstractSessionController<SESSION extends AbstractSession>
final SESSION session = create(user); final SESSION session = create(user);
log.info("Session CREATED: {}", session); log.info("Session CREATED: {}", session);
synchronized (sessions) { sessions.add(session);
sessions.add(session);
}
return join(session, user); return join(session, user);
} }
@ -110,7 +106,7 @@ public abstract class AbstractSessionController<SESSION extends AbstractSession>
public AbstractSessionDto changeTitle(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final SessionChangeTitleInbound inbound) { public AbstractSessionDto changeTitle(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final SessionChangeTitleInbound inbound) {
final User user = userService.getByPrivateUuidOrThrow(userUuid); final User user = userService.getByPrivateUuidOrThrow(userUuid);
final SESSION session = getUserSession(user, inbound.uuid); final SESSION session = getUserSession(user, inbound.uuid);
if (session.owner != user) { if (session.isOwnedBy(user)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN); throw new ResponseStatusException(HttpStatus.FORBIDDEN);
} }
session.setTitle(inbound.title); session.setTitle(inbound.title);
@ -122,7 +118,7 @@ public abstract class AbstractSessionController<SESSION extends AbstractSession>
public AbstractSessionDto changePassword(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final SessionChangePasswordInbound inbound) { public AbstractSessionDto changePassword(@CookieValue(name = USER_UUID_COOKIE_NAME) @NonNull final String userUuid, @RequestBody final SessionChangePasswordInbound inbound) {
final User user = userService.getByPrivateUuidOrThrow(userUuid); final User user = userService.getByPrivateUuidOrThrow(userUuid);
final SESSION session = getUserSession(user, inbound.uuid); final SESSION session = getUserSession(user, inbound.uuid);
if (session.owner != user) { if (session.isOwnedBy(user)) {
throw new ResponseStatusException(HttpStatus.FORBIDDEN); throw new ResponseStatusException(HttpStatus.FORBIDDEN);
} }
session.setPassword(inbound.password); session.setPassword(inbound.password);
@ -147,14 +143,12 @@ public abstract class AbstractSessionController<SESSION extends AbstractSession>
@NonNull @NonNull
private Optional<SESSION> findUserSession(@NonNull final User user, @NonNull final String sessionUuid) { private Optional<SESSION> findUserSession(@NonNull final User user, @NonNull final String sessionUuid) {
return user.getSessions().stream().filter(s -> s.uuid.equals(sessionUuid)).filter(sessionClazz::isInstance).map(sessionClazz::cast).findFirst(); return user.getSessions().stream().filter(s -> s.getUuid().equals(sessionUuid)).filter(sessionClazz::isInstance).map(sessionClazz::cast).findFirst();
} }
@NonNull @NonNull
private SESSION getSessionByUuid(@NonNull final String uuid) { private SESSION getSessionByUuid(@NonNull final String uuid) {
synchronized (sessions) { return sessions.stream().filter(u -> u.getUuid().equals(uuid)).findFirst().orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
return sessions.stream().filter(u -> u.getUuid().equals(uuid)).findFirst().orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
}
} }
@NonNull @NonNull

View File

@ -40,11 +40,11 @@ public abstract class AbstractSessionDto {
protected AbstractSessionDto(@NonNull final AbstractSession session, @NonNull final String type) { protected AbstractSessionDto(@NonNull final AbstractSession session, @NonNull final String type) {
this.type = type; this.type = type;
this.uuid = session.uuid; this.uuid = session.getUuid();
this.title = session.title; this.title = session.getTitle();
this.created = session.created; this.created = session.getCreated();
this.password = session.getPassword(); this.password = session.getPassword();
this.owner = new UserPublicDto(session.owner); this.owner = new UserPublicDto(session.getOwner());
this.users = session.getUsers().stream().map(UserPublicDto::new).collect(Collectors.toSet()); this.users = session.getUsers().stream().map(UserPublicDto::new).collect(Collectors.toSet());
this.initial = session.isInitial(); this.initial = session.isInitial();
} }

View File

@ -1,12 +1,12 @@
package de.ph87.tools.user; package de.ph87.tools.user;
import com.fasterxml.jackson.annotation.JsonIgnore;
import de.ph87.tools.session.AbstractSession; import de.ph87.tools.session.AbstractSession;
import de.ph87.tools.web.IWebSocketMessage; import de.ph87.tools.web.IWebSocketMessage;
import lombok.Getter; import jakarta.persistence.Column;
import lombok.NonNull; import jakarta.persistence.Entity;
import lombok.Setter; import jakarta.persistence.Id;
import lombok.ToString; import jakarta.persistence.ManyToMany;
import lombok.*;
import java.time.ZonedDateTime; import java.time.ZonedDateTime;
import java.util.HashSet; import java.util.HashSet;
@ -14,43 +14,49 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import java.util.UUID; import java.util.UUID;
@Entity
@Getter @Getter
@ToString @ToString
@NoArgsConstructor
public class User implements IWebSocketMessage { public class User implements IWebSocketMessage {
@Id
@NonNull @NonNull
public final String privateUuid = UUID.randomUUID().toString(); @Column(nullable = false)
private String privateUuid = UUID.randomUUID().toString();
@NonNull @NonNull
public final String publicUuid = UUID.randomUUID().toString(); @Column(nullable = false)
private String publicUuid = UUID.randomUUID().toString();
public final ZonedDateTime created = ZonedDateTime.now(); @NonNull
@Column(nullable = false)
private ZonedDateTime created = ZonedDateTime.now();
@JsonIgnore @ManyToMany
@ToString.Exclude @ToString.Exclude
private final Set<AbstractSession> sessions = new HashSet<>(); private Set<AbstractSession> sessions = new HashSet<>();
@NonNull
@Column(nullable = false)
private ZonedDateTime lastAccess = created; private ZonedDateTime lastAccess = created;
@Setter @Setter
@NonNull @NonNull
public String name = "unnamed"; @Column(nullable = false)
private String name = "Neuer Benutzer";
private void touch() { private void touch() {
lastAccess = ZonedDateTime.now(); lastAccess = ZonedDateTime.now();
} }
public void join(@NonNull final AbstractSession session) { public void join(@NonNull final AbstractSession session) {
synchronized (privateUuid) { sessions.add(session);
sessions.add(session); touch();
touch();
}
} }
public void leave(@NonNull final AbstractSession session) { public void leave(@NonNull final AbstractSession session) {
synchronized (sessions) { sessions.remove(session);
sessions.remove(session);
}
} }
@Override @Override
@ -58,4 +64,17 @@ public class User implements IWebSocketMessage {
return List.of("User", privateUuid); return List.of("User", privateUuid);
} }
@Override
public boolean equals(final Object obj) {
if (!(obj instanceof final User user)) {
return false;
}
return user.privateUuid.equals(this.privateUuid);
}
@Override
public int hashCode() {
return privateUuid.hashCode();
}
} }

View File

@ -32,8 +32,8 @@ public class UserPrivateDto {
public UserPrivateDto(@NonNull final User user) { public UserPrivateDto(@NonNull final User user) {
this.publicUuid = user.getPublicUuid(); this.publicUuid = user.getPublicUuid();
this.name = user.getName(); this.name = user.getName();
this.privateUuid = user.privateUuid; this.privateUuid = user.getPrivateUuid();
this.created = user.created; this.created = user.getCreated();
this.sessions = user.getSessions().stream().map(AbstractSessionDto::toDto).collect(Collectors.toSet()); this.sessions = user.getSessions().stream().map(AbstractSessionDto::toDto).collect(Collectors.toSet());
} }

View File

@ -0,0 +1,7 @@
package de.ph87.tools.user;
import org.springframework.data.repository.ListCrudRepository;
public interface UserRepository extends ListCrudRepository<User, String> {
}

View File

@ -30,11 +30,9 @@ public class UserService {
@NonNull @NonNull
public User getUserByUuidOrElseCreate(@Nullable final String uuid, @NonNull final HttpServletResponse response) { public User getUserByUuidOrElseCreate(@Nullable final String uuid, @NonNull final HttpServletResponse response) {
synchronized (users) { final User user = Optional.ofNullable(uuid).map(this::findByPrivateUuid).filter(Optional::isPresent).map(Optional::get).orElseGet(this::create);
final User user = Optional.ofNullable(uuid).map(this::findByPrivateUuid).filter(Optional::isPresent).map(Optional::get).orElseGet(this::create); writeUserUuidCookie(response, user);
writeUserUuidCookie(response, user); return user;
return user;
}
} }
@Nullable @Nullable
@ -48,26 +46,20 @@ public class UserService {
} }
public void delete(@NonNull final String userUuid, final @NonNull HttpServletResponse response) { public void delete(@NonNull final String userUuid, final @NonNull HttpServletResponse response) {
synchronized (users) { final User user = findByPrivateUuid(userUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
final User user = findByPrivateUuid(userUuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST)); users.remove(user);
users.remove(user); log.info("User DELETED: {}", user);
log.info("User DELETED: {}", user);
}
writeUserUuidCookie(response, null); writeUserUuidCookie(response, null);
} }
@NonNull @NonNull
public User getByPrivateUuidOrThrow(@NonNull final String uuid) { public User getByPrivateUuidOrThrow(@NonNull final String uuid) {
synchronized (users) { return findByPrivateUuid(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
return findByPrivateUuid(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
}
} }
@NonNull @NonNull
public User getByPublicUuid(@NonNull final String uuid) { public User getByPublicUuid(@NonNull final String uuid) {
synchronized (users) { return findByPublicUuid(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
return findByPublicUuid(uuid).orElseThrow(() -> new ResponseStatusException(HttpStatus.BAD_REQUEST));
}
} }
@NonNull @NonNull
@ -91,7 +83,7 @@ public class UserService {
private static void writeUserUuidCookie(@NonNull final HttpServletResponse response, @Nullable final User user) { private static void writeUserUuidCookie(@NonNull final HttpServletResponse response, @Nullable final User user) {
final Cookie cookie = new Cookie(USER_UUID_COOKIE_NAME, ""); final Cookie cookie = new Cookie(USER_UUID_COOKIE_NAME, "");
if (user != null) { if (user != null) {
cookie.setValue(user.privateUuid); cookie.setValue(user.getPrivateUuid());
} }
cookie.setMaxAge(10 * 365 * 24 * 60 * 60); cookie.setMaxAge(10 * 365 * 24 * 60 * 60);
cookie.setPath("/"); cookie.setPath("/");