From 101e21d5682d61a0d356807991fe394a56a68789 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sun, 5 Apr 2026 14:58:29 +0200 Subject: [PATCH 01/22] Add: TeX to gitignore --- .gitignore | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 56624a0..7637ac8 100644 --- a/.gitignore +++ b/.gitignore @@ -122,4 +122,29 @@ $RECYCLE.BIN/ ## bin bin/ -gradle.properties \ No newline at end of file +gradle.properties + +# LaTeX (TeX) +## Core latex/pdflatex auxiliary files: +*.aux +*.lof +*.log +*.lot +*.fls +*.out +*.toc +*.fmt +*.fot +*.cb +*.cb2 +.*.lb + +## Build tool auxiliary files: +*.fdb_latexmk +*.synctex +*.synctex(busy) +*.synctex.gz +*.synctex.gz(busy) +*.pdfsync +*.rubbercache +rubber.cache From 2152560dea7473d215045f18e3cedb0fe839cba9 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sun, 5 Apr 2026 14:58:45 +0200 Subject: [PATCH 02/22] Add: Move gradle to own section --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 7637ac8..ae53327 100644 --- a/.gitignore +++ b/.gitignore @@ -122,6 +122,8 @@ $RECYCLE.BIN/ ## bin bin/ + +# Gradle gradle.properties # LaTeX (TeX) From d757c2a31711f7786c800fc31476cbfe533e6d7b Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Sun, 5 Apr 2026 15:01:07 +0200 Subject: [PATCH 03/22] Add: Move gradle.properties to gradle section --- .gitignore | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index ae53327..b9d312b 100644 --- a/.gitignore +++ b/.gitignore @@ -48,6 +48,9 @@ gradle-app.setting # JDT-specific (Eclipse Java Development Tools) .classpath +# Gradle properties +gradle.properties + ## MacOS # General .DS_Store @@ -123,9 +126,6 @@ $RECYCLE.BIN/ ## bin bin/ -# Gradle -gradle.properties - # LaTeX (TeX) ## Core latex/pdflatex auxiliary files: *.aux From 68a69b865ee7ee9c6a3c336b7bd5eba59a480788 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 11:51:39 +0200 Subject: [PATCH 04/22] Add: LoginRequest storing requested username for login --- .../server/app/commands/login/LoginRequest.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginRequest.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginRequest.java new file mode 100644 index 0000000..791a71a --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginRequest.java @@ -0,0 +1,17 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; + +public class LoginRequest extends Request { + private final String username; + + public LoginRequest(RequestContext context, String username) { + super(context); + this.username = username; + } + + public String getUsername() { + return username; + } +} From 8580a2803c2f4f10edbb023b815913c5cd8d406f Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 11:54:54 +0200 Subject: [PATCH 05/22] Add: LoginParser to check for required parameter and construct LoginRequest --- .../server/app/commands/login/LoginParser.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginParser.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginParser.java new file mode 100644 index 0000000..52e2518 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginParser.java @@ -0,0 +1,14 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor; + +public class LoginParser implements CommandParser { + @Override + public LoginRequest parse(PrimitiveRequest primitiveRequest) { + RequestParameterAccessor accessor = + new RequestParameterAccessor(primitiveRequest.parameters()); + return new LoginRequest(primitiveRequest.context(), accessor.require("USERNAME")); + } +} From 7eda64b5c31007fd3211af531797a6a78512bd04 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 11:58:43 +0200 Subject: [PATCH 06/22] Add: LoginResponse containing assigned username and UserId --- .../app/commands/login/LoginResponse.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginResponse.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginResponse.java new file mode 100644 index 0000000..d1226b1 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginResponse.java @@ -0,0 +1,17 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserId; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder; + +public class LoginResponse extends SuccessResponse { + public LoginResponse(RequestContext context, String assignedUsername, UserId id) { + super( + context, + new ResponseBodyBuilder() + .param("USERNAME", assignedUsername) + .param("ID", id.value()) + .build()); + } +} From be173f28477dc4a058a0484dba7856e1d57825ce Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 12:10:45 +0200 Subject: [PATCH 07/22] Add: LoginHandler to execute and respond to LoginRequest --- .../app/commands/login/LoginHandler.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java new file mode 100644 index 0000000..b28ba3a --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java @@ -0,0 +1,24 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserFactory; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; + +public class LoginHandler implements CommandHandler { + private final ResponseDispatcher responseDispatcher; + private final UserFactory userFactory; + + public LoginHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) { + this.responseDispatcher = responseDispatcher; + this.userFactory = new UserFactory(userRegistry); + } + + @Override + public void execute(LoginRequest request) { + User user = userFactory.create(request.getUsername(), request.getContext().sessionId()); + responseDispatcher.dispatch( + new LoginResponse(request.getContext(), user.getName(), user.getId())); + } +} From 708f6c5773acefedcd72f2257834235a7b2b9c26 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 12:12:33 +0200 Subject: [PATCH 08/22] Add: Integrate LOGIN command in composition root --- .../ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index 48ac98f..4f22358 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -3,6 +3,9 @@ package ch.unibas.dmi.dbis.cs108.casono.server; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameHandler; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameParser; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsernameRequest; +import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginParser; +import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginRequest; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingHandler; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingParser; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest; @@ -91,5 +94,9 @@ public class ServerApp { commandRouter.register( CheckUsernameRequest.class, new CheckUsernameHandler(responseDispatcher, userRegistry)); + + parserDispatcher.register("LOGIN", new LoginParser()); + commandRouter.register( + LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry)); } } From 4ff28932b446731261dc01569acad66c0a79a6d5 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 12:30:40 +0200 Subject: [PATCH 09/22] Add: LogoutRequest for handling logout commands --- .../server/app/commands/logout/LogoutRequest.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutRequest.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutRequest.java new file mode 100644 index 0000000..ee58c0b --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutRequest.java @@ -0,0 +1,10 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; + +public class LogoutRequest extends Request { + public LogoutRequest(RequestContext context) { + super(context); + } +} From fa0ac684f17961bc544c27ac61b6770310525a86 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 12:31:48 +0200 Subject: [PATCH 10/22] Add: LogoutParser for parsing logout requests --- .../server/app/commands/logout/LogoutParser.java | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutParser.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutParser.java new file mode 100644 index 0000000..de8fcfe --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutParser.java @@ -0,0 +1,11 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout; + +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest; + +public class LogoutParser implements CommandParser { + @Override + public LogoutRequest parse(PrimitiveRequest primitiveRequest) { + return new LogoutRequest(primitiveRequest.context()); + } +} From 8376f681b12074f3b73726f8cbbbdad1a0677cd1 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 12:40:51 +0200 Subject: [PATCH 11/22] Add: LogoutHandler for processing logout commands --- .../app/commands/logout/LogoutHandler.java | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java new file mode 100644 index 0000000..7c21229 --- /dev/null +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java @@ -0,0 +1,22 @@ +package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout; + +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; +import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; + +public class LogoutHandler implements CommandHandler { + private final ResponseDispatcher responseDispatcher; + private final UserRegistry userRegistry; + + public LogoutHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) { + this.responseDispatcher = responseDispatcher; + this.userRegistry = userRegistry; + } + + @Override + public void execute(LogoutRequest request) { + userRegistry.onDisconnect(request.getSessionId()); + responseDispatcher.dispatch(new OkResponse(request.getContext())); + } +} From 9a7b1c4deaec1100630e96fd6b87e2e1814a8c26 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 12:45:52 +0200 Subject: [PATCH 12/22] Add: LOGOUT command in composition root --- .../ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java index 4f22358..1b5f62a 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/ServerApp.java @@ -6,6 +6,9 @@ import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.check_nick.CheckUsern import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginHandler; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginParser; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login.LoginRequest; +import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout.LogoutHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout.LogoutParser; +import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout.LogoutRequest; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingHandler; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingParser; import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.ping.PingRequest; @@ -98,5 +101,9 @@ public class ServerApp { parserDispatcher.register("LOGIN", new LoginParser()); commandRouter.register( LoginRequest.class, new LoginHandler(responseDispatcher, userRegistry)); + + parserDispatcher.register("LOGOUT", new LogoutParser()); + commandRouter.register( + LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry)); } } From 22dc2c7e54ae012ba38ba6a0ef4ff65575365b93 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 13:27:12 +0200 Subject: [PATCH 13/22] Add: Methods to remove user by UserId, SessionId and Username from SessionRegistry --- .../server/domain/user/UserRegistry.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java index bc0cd36..436c965 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java @@ -31,6 +31,43 @@ public class UserRegistry { return Optional.of(user); } + public synchronized boolean removeByUserId(UserId userId) { + User user = byId.get(userId); + if (user == null) { + return false; + } + + byName.remove(user.getName()); + user.getSessionId().ifPresent(bySessionId::remove); + return true; + } + + public synchronized boolean removeBySessionId(SessionId sessionId) { + User user = bySessionId.get(sessionId); + if (user == null) { + return false; + } + + remove(user); + return true; + } + + public synchronized boolean removeByUsername(String username) { + User user = byName.get(username); + if (user == null) { + return false; + } + + remove(user); + return true; + } + + private synchronized void remove(User user) { + byId.remove(user.getId()); + byName.remove(user.getName()); + user.getSessionId().ifPresent(bySessionId::remove); + } + /** * Removes the user with the given ID, but only if they are still disconnected. This prevents * removing a user who has reconnected between the cleanup job's check and its removal call. From 36f07880e521e88d10a42e6c30004f2aed22f8ad Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 13:28:12 +0200 Subject: [PATCH 14/22] Refactor: Use internal remove method --- .../dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java index 436c965..b79bf69 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java @@ -84,8 +84,7 @@ public class UserRegistry { return false; } - byId.remove(user.getId()); - byName.remove(user.getName()); + remove(user); return true; } From ee694b8168dcdd5f58c76b55ae471a203fd84709 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 13:30:09 +0200 Subject: [PATCH 15/22] Docs: Write JavaDoc for newly added methods --- .../server/domain/user/UserRegistry.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java index b79bf69..2264805 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java @@ -31,6 +31,12 @@ public class UserRegistry { return Optional.of(user); } + /** + * Removes a user from the registry by user ID. + * + * @param userId the ID of the user to remove + * @return true if a user was removed, false if no user with that ID exists + */ public synchronized boolean removeByUserId(UserId userId) { User user = byId.get(userId); if (user == null) { @@ -42,6 +48,12 @@ public class UserRegistry { return true; } + /** + * Removes a user from the registry by session ID. + * + * @param sessionId the session ID associated with the user to remove + * @return true if a user was removed, false if no user with that session exists + */ public synchronized boolean removeBySessionId(SessionId sessionId) { User user = bySessionId.get(sessionId); if (user == null) { @@ -52,6 +64,12 @@ public class UserRegistry { return true; } + /** + * Removes a user from the registry by username. + * + * @param username the username associated with the user to remove + * @return true if a user was removed, false if no user with that session exists + */ public synchronized boolean removeByUsername(String username) { User user = byName.get(username); if (user == null) { @@ -62,6 +80,11 @@ public class UserRegistry { return true; } + /** + * Removes a user from the internals of the registry. + * + * @param user the user to remove + */ private synchronized void remove(User user) { byId.remove(user.getId()); byName.remove(user.getName()); From 822dfe778d2d2ec2778fbc3618451186cdd5f61a Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 13:31:26 +0200 Subject: [PATCH 16/22] Style: Apply Spotless --- .../dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java index 2264805..b3cd551 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/domain/user/UserRegistry.java @@ -82,7 +82,7 @@ public class UserRegistry { /** * Removes a user from the internals of the registry. - * + * * @param user the user to remove */ private synchronized void remove(User user) { From 4c10bc7ab03428b07cd5c5b38c25f505db05db1b Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 13:39:44 +0200 Subject: [PATCH 17/22] Refactor: Use removeBySessionId method instead of onDisconnect handler --- .../cs108/casono/server/app/commands/logout/LogoutHandler.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java index 7c21229..af08e0c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java @@ -16,7 +16,7 @@ public class LogoutHandler implements CommandHandler { @Override public void execute(LogoutRequest request) { - userRegistry.onDisconnect(request.getSessionId()); + userRegistry.removeBySessionId(request.getSessionId()); responseDispatcher.dispatch(new OkResponse(request.getContext())); } } From 4789273d8ff86c77ed3f1f7c079c3112a86f34d2 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 14:02:18 +0200 Subject: [PATCH 18/22] Add: Send ErrorResponse with 'NO_USER_ASSOCIATED' code when no user is associated with the session --- .../server/app/commands/logout/LogoutHandler.java | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java index af08e0c..c8e00b6 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java @@ -16,7 +16,15 @@ public class LogoutHandler implements CommandHandler { @Override public void execute(LogoutRequest request) { - userRegistry.removeBySessionId(request.getSessionId()); - responseDispatcher.dispatch(new OkResponse(request.getContext())); + boolean was_removed = userRegistry.removeBySessionId(request.getSessionId()); + if (was_removed) { + responseDispatcher.dispatch(new OkResponse(request.getContext())); + } else { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), + "NO_USER_ASSOCIATED", + "No user is associated with your session. Did you login before?")); + } } } From 5ab29a3337074dbba79f2f49dc58d4e841448eec Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 14:02:44 +0200 Subject: [PATCH 19/22] Docs: Write JavaDoc for added components --- .../server/app/commands/logout/LogoutHandler.java | 15 +++++++++++++++ .../server/app/commands/logout/LogoutParser.java | 8 ++++++++ .../server/app/commands/logout/LogoutRequest.java | 8 ++++++++ 3 files changed, 31 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java index c8e00b6..5876fde 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java @@ -2,18 +2,33 @@ package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.OkResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +/** Handles {@link LogoutRequest} to logout connected user. */ public class LogoutHandler implements CommandHandler { private final ResponseDispatcher responseDispatcher; private final UserRegistry userRegistry; + /** + * Creates a new logout handler to logout user. + * + * @param responseDispatcher dispatcher used to send the logout result back to the client + * @param userRegistry registry responsible for tracking connected user sessions + */ public LogoutHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) { this.responseDispatcher = responseDispatcher; this.userRegistry = userRegistry; } + /** + * Executes the logout request for the given request. + * + *

The user is removed from the {@link UserRegistry}. + * + * @param request the request to execute + */ @Override public void execute(LogoutRequest request) { boolean was_removed = userRegistry.removeBySessionId(request.getSessionId()); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutParser.java index de8fcfe..72cdf1c 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutParser.java @@ -3,7 +3,15 @@ package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandParser; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest; +/** Parses a primitive request into a {@link LogoutRequest}. */ public class LogoutParser implements CommandParser { + + /** + * Parses a primitive request into a LogoutRequest. + * + * @param primitiveRequest the request to parse + * @return the created {@link LogoutRequest} + */ @Override public LogoutRequest parse(PrimitiveRequest primitiveRequest) { return new LogoutRequest(primitiveRequest.context()); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutRequest.java index ee58c0b..77be0b4 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutRequest.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutRequest.java @@ -3,7 +3,15 @@ package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.logout; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; +/** Request implementation used to logout a currently logged in user */ public class LogoutRequest extends Request { + + /** + * Constructs a new LogoutRequest with the given context. + * + * @param context the {@link RequestContext} containing information for responding to the + * request + */ public LogoutRequest(RequestContext context) { super(context); } From 7c9cf3e00d6158f94a16a55310ac2298bccf1dfc Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 14:09:35 +0200 Subject: [PATCH 20/22] Style: Variable naming --- .../casono/server/app/commands/logout/LogoutHandler.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java index 5876fde..c5ca9fd 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/logout/LogoutHandler.java @@ -31,8 +31,8 @@ public class LogoutHandler implements CommandHandler { */ @Override public void execute(LogoutRequest request) { - boolean was_removed = userRegistry.removeBySessionId(request.getSessionId()); - if (was_removed) { + boolean wasRemoved = userRegistry.removeBySessionId(request.getSessionId()); + if (wasRemoved) { responseDispatcher.dispatch(new OkResponse(request.getContext())); } else { responseDispatcher.dispatch( From d4722989d90daf06f8eba131b0d3607b249e61b0 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 14:22:09 +0200 Subject: [PATCH 21/22] Add: Prevent multiple logins by checking for existing user session --- .../server/app/commands/login/LoginHandler.java | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java index b28ba3a..8051153 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java @@ -4,20 +4,34 @@ import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserFactory; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; +import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; +import java.util.Optional; public class LoginHandler implements CommandHandler { private final ResponseDispatcher responseDispatcher; + private final UserRegistry userRegistry; private final UserFactory userFactory; public LoginHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) { this.responseDispatcher = responseDispatcher; + this.userRegistry = userRegistry; this.userFactory = new UserFactory(userRegistry); } @Override public void execute(LoginRequest request) { - User user = userFactory.create(request.getUsername(), request.getContext().sessionId()); + Optional existingUser = userRegistry.getBySessionId(request.getSessionId()); + if (existingUser.isPresent()) { + responseDispatcher.dispatch( + new ErrorResponse( + request.getContext(), + "ALREADY_LOGGED_IN", + "This session is already associated with an active user.")); + return; + } + + User user = userFactory.create(request.getUsername(), request.getSessionId()); responseDispatcher.dispatch( new LoginResponse(request.getContext(), user.getName(), user.getId())); } From 160ad8f2bbd4ebc8390a55395171803e7882e323 Mon Sep 17 00:00:00 2001 From: Lars Simon Winzer Date: Tue, 7 Apr 2026 20:12:22 +0200 Subject: [PATCH 22/22] Docs: Write JavaDoc for components related to LOGIN command --- .../app/commands/login/LoginHandler.java | 20 +++++++++++++++++++ .../app/commands/login/LoginParser.java | 7 +++++++ .../app/commands/login/LoginRequest.java | 13 ++++++++++++ .../app/commands/login/LoginResponse.java | 6 ++++++ 4 files changed, 46 insertions(+) diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java index 8051153..5d139f8 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginHandler.java @@ -2,23 +2,43 @@ package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserFactory; +import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserId; import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.UserRegistry; import ch.unibas.dmi.dbis.cs108.casono.server.network.command.execution.CommandHandler; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.ErrorResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.dispatcher.ResponseDispatcher; import java.util.Optional; +/** + * Handles {@link LoginRequest}s to create a user for a session, if the session has not assigned one + * already + */ public class LoginHandler implements CommandHandler { private final ResponseDispatcher responseDispatcher; private final UserRegistry userRegistry; private final UserFactory userFactory; + /** + * Creates a new handler for checking for existing user and creating a new one + * + * @param responseDispatcher the dispatcher used to send the response + * @param userRegistry the registry used to look up existing users and create the new one + */ public LoginHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) { this.responseDispatcher = responseDispatcher; this.userRegistry = userRegistry; this.userFactory = new UserFactory(userRegistry); } + /** + * Executes the login request + * + *

If no user is already assigned to the session, a new user is created and its name and + * {@link UserId} returned in the response. If the session has already a user assigned, a {@code + * ALREADY_LOGGED_IN} error is responded with. + * + * @param request the request to execute + */ @Override public void execute(LoginRequest request) { Optional existingUser = userRegistry.getBySessionId(request.getSessionId()); diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginParser.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginParser.java index 52e2518..c3dd5db 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginParser.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginParser.java @@ -4,7 +4,14 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.command.parsing.CommandPar import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.PrimitiveRequest; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor.RequestParameterAccessor; +/** Parses a primitive request into a {@link LoginRequest}. */ public class LoginParser implements CommandParser { + /** + * Extracts the required {@code USERNAME} parameter from the incoming request. + * + * @param primitiveRequest the request to parse + * @return {@link LoginRequest} containing the username + */ @Override public LoginRequest parse(PrimitiveRequest primitiveRequest) { RequestParameterAccessor accessor = diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginRequest.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginRequest.java index 791a71a..9dec2be 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginRequest.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginRequest.java @@ -3,14 +3,27 @@ package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.login; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.Request; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestContext; +/** Request implementation used to create server-side user instance based on provided username */ public class LoginRequest extends Request { private final String username; + /** + * Constructs a new LoginRequest with the given context and desired username + * + * @param context the {@link RequestContext} containing information for responding to the + * request + * @param username the desired username when creating the user + */ public LoginRequest(RequestContext context, String username) { super(context); this.username = username; } + /** + * Returns the desired username requested for login + * + * @return the desired username + */ public String getUsername() { return username; } diff --git a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginResponse.java b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginResponse.java index d1226b1..b907fab 100644 --- a/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginResponse.java +++ b/src/main/java/ch/unibas/dmi/dbis/cs108/casono/server/app/commands/login/LoginResponse.java @@ -5,7 +5,13 @@ import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.RequestCo import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.SuccessResponse; import ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.response.builder.ResponseBodyBuilder; +/** Response containing the assigned username and id of said user */ public class LoginResponse extends SuccessResponse { + /** + * @param context the {@link RequestContext} associated with the request + * @param assignedUsername the assigned username to this user + * @param id of the created user + */ public LoginResponse(RequestContext context, String assignedUsername, UserId id) { super( context,