Merge branch 'main' into feat/game-ui

This commit is contained in:
Julian Kropff
2026-04-10 21:05:53 +02:00
11 changed files with 498 additions and 2 deletions
@@ -0,0 +1,16 @@
## Milestone Achievement
<!-- The recommended type is: Task -->
### Category
<!-- Category of the milestone (Process, Product, Presentation) -->
### Title
<!-- Title of the milestone (equal to the title in the milestone catalog (https://p9.dmi.unibas.ch/cs108/2026) -->
### Rewarded points on completion
<!-- Number of points rewarded on completion of the milestone -->
### Description of milestone
<!-- Description of the milestone (equal to the description in the milestone catalog (https://p9.dmi.unibas.ch/cs108/2026) -->
/label ~achievement
+15
View File
@@ -12,6 +12,7 @@ Since this project is part of a course at the University of Basel, the [Code of
- [Creating an issue](#creating-an-issue)
- [During implementation](#during-implementation)
- [Collaborative work](#collaborative-work)
- [Milestone Achievements](#milestone-achievements)
- [Git Workflow](#git-workflow)
- [Creating a branch](#creating-a-branch)
- [Working on a branch](#working-on-a-branch)
@@ -49,6 +50,20 @@ Issue or Task in GitLab **before** any implementation begins.
centrally visible and searchable.
- Before starting work that overlaps with an existing issue, check its comment thread first to avoid duplicating effort.
### Milestone Achievements
For every process and product-related milestone achievement, there is a dedicated milestone task.
- **Do not create a branch directly from a milestone task.**
- Instead, create a normal implementation task (using the regular task template) and reference the milestone task there.
- In the merge request, reference the milestone task again.
- If the milestone condition is fully met, you may use `Closing #<id>` to close the milestone task.
- If it is only partially addressed, use `Relates to #<id>` or `Contributes to #<id>` so the milestone task stays open.
Milestone tasks may, but do not have to, be assigned to a specific person.
- If multiple people are involved, contribution is tracked through linked tasks that reference the milestone task.
- For small topics, a Milestone Achievement may be assigned to one person. Others should only contribute on request and should not modify components introduced under that achievement without coordination.
## Git Workflow
We use a **feature branch -> main** strategy. The `main` branch is always in a releasable state.
@@ -17,6 +17,10 @@ The concrete implementations for each command live in `app/commands/` and are wi
- [Command Infrastructure](./commands-deep-dive.md) -
Technical deep-dive into the `CommandParser`, `CommandParserDispatcher`, `CommandHandler`, `CommandRouter`, `Request`, and the response hierarchy.
### Protocol document
- [Protocol Document](./protocol-document.md) -
List of all supported commands by the server, along with descriptions, required pre-execution checks, and an example for both request and response.
## Key Concepts
**Each command is self-contained.**
A command's Parser, Request, Handler, and Response all live in the same package under `app/commands/<name>/`.
@@ -0,0 +1,344 @@
# Protocol Document
This document describes the protocol for client-server communication in our application. It defines the structure of requests and responses, the supported commands along with their request parameters, response formats, and possible errors.
# Table of Contents
- [Protocol Document](#protocol-document)
- [Table of Contents](#table-of-contents)
- [General structure of requests](#general-structure-of-requests)
- [Preconditions](#preconditions)
- [Parsing](#parsing)
- [Error Response](#error-response)
- [Command dispatching](#command-dispatching)
- [Error Response](#error-response-1)
- [Command parsing](#command-parsing)
- [Error Response](#error-response-2)
- [Pre-execution checks](#pre-execution-checks)
- [UserLoggedInCheck](#userloggedincheck)
- [Error Response](#error-response-3)
- [Commands](#commands)
- [PING command](#ping-command)
- [Required pre-execution checks](#required-pre-execution-checks)
- [Request Parameters](#request-parameters)
- [Success Response](#success-response)
- [Example Request](#example-request)
- [Example Response](#example-response)
- [CHECK\_USERNAME command](#check_username-command)
- [Required pre-execution checks](#required-pre-execution-checks-1)
- [Request Parameters](#request-parameters-1)
- [Success Response](#success-response-1)
- [Example Request](#example-request-1)
- [Example Response](#example-response-1)
- [LOGIN command](#login-command)
- [Required pre-execution checks](#required-pre-execution-checks-2)
- [Request Parameters](#request-parameters-2)
- [Success Response](#success-response-2)
- [Error Response](#error-response-4)
- [Example Request](#example-request-2)
- [Example Response](#example-response-2)
- [LOGOUT command](#logout-command)
- [Required pre-execution checks](#required-pre-execution-checks-3)
- [Request Parameters](#request-parameters-3)
- [Success Response](#success-response-3)
- [Error Response](#error-response-5)
- [Example Request](#example-request-3)
- [Example Response](#example-response-3)
- [LIST\_USERS command](#list_users-command)
- [Required pre-execution checks](#required-pre-execution-checks-4)
- [Request Parameters](#request-parameters-4)
- [Success Response](#success-response-4)
- [Error Response](#error-response-6)
- [Example Request](#example-request-4)
- [Example Response](#example-response-4)
<!-- Please see the comments for copy n' paste ready examples -->
# General structure of requests
As mentioned before, our protocol is based on POP3.
Each command is represented as a single line of text, starting with the command name followed by parameters. The server responds with a status line indicating success or failure, followed by the body.
Requests can have parameters that provide additional information for the command. Parameters are key-value pairs separated by an equal sign (`=`).
Responses are collections of key-value pairs, containing either a value or another collection, allowing for nested structures.
Each collection is ended with the `END` keyword.
# Preconditions
The serverside pipeline to process incoming requests consists of multiple stages.
Each of these stages can yield an error response if the request does not meet the requirements of that stage.
## Parsing
One of these stages is the parsing. It is responsible for parsing the raw request into a structured format that can be easily processed by the command handlers. It validates the syntax of the request as well.
### Error Response
| Code | Description |
| :-------------- | :---------------------------------------------------------------------------------------------- |
| `PARSING_ERROR` | The body of the request contains syntax errors (see message field of response for more details) |
## Command dispatching
After the request has been successfully parsed, the next stage is to dispatch the `PrimitiveRequest` to the appropriate `CommandParser`. This is done by the `CommandDispatcherDispatcher`, which uses the command name to determine which parser to use.
### Error Response
| Code | Description |
| :---------------- | :---------------------------------------------------------------- |
| `UNKNOWN_COMMAND` | The command is unknown to this server. No parser has been defined |
## Command parsing
Once the `PrimitiveRequest` has been dispatched to the appropriate `CommandParser`, the parser is responsible for parsing the parameters of the request and creating a `Request` that can be executed by the responsible `CommandHandler`.
### Error Response
| Code | Description |
| :------------------ | :------------------------------------------------------------------------------------------------ |
| `MISSING_PARAMETER` | A required parameter is missing from the request (see message field of response for more details) |
# Pre-execution checks
Pre-execution checks are reusable validation steps that can be registered on command handlers.
They are implemented as `HandlerCheck` instances and are executed by the `CommandHandlerExecutor` before the handler's main logic is invoked.
<!--
## Name of the check
Description of the check, what it does and when it should be used.
### Response if not met
| Code | Description |
| :----------- | :----------------------- |
| `ERROR_CODE` | Description of the error |
-->
## UserLoggedInCheck
The `UserLoggedInCheck` is a common pre-execution check that verifies whether the user is logged in (i.e. has a user associated with his session).
### Error Response
| Code | Description |
| :------------------- | :------------------------ |
| `USER_NOT_LOGGED_IN` | The user is not logged in |
# Commands
Commands are the core of our protocol, representing the various actions that clients can request from the server. Each command has a unique name and may require specific parameters in addition to pre-execution checks.
The server processes these commands and responds accordingly.
<!--
## Name of the command
Description of the command, what it does, and when it should be used.
### Required pre-execution checks
- [`Check1`](#check1)
### Request Parameters
| Parameter Name | Type | Optional | Description |
| :------------- | :----- | :---------------------------------- | :-------------------- |
| `param1` | `type` | If the parameter is optional or not | Description of param1 |
### Success Response
| Field | Type | Description |
| :------- | :------------------ | :-------------------- |
| `field1` | `type` | Description of field1 |
| `field2` | `Collection<type2>` | |
| `field3` | `Enum<type3>` | Description of field3 |
| Fields of `type2` | Type | Description |
| :---------------- | :----- | :-------------------- |
| `field1` | `type` | Description of field1 |
| Members of `type` | Description |
| :---------------- | :--------------------- |
| `MEMBER1` | Description of member1 |
### Error Response
| Code | Description |
| :----------- | :----------------------- |
| `ERROR_CODE` | Description of the error |
### Example Request
```
COMMAND_NAME PARAM1='value1' PARAM2='value2'
```
### Example Response
```
+OK
KEY1=VALUE1
FIELDS
FIELD
NESTED_KEY=NESTED_VALUE
END
KEY2=VALUE2
END
```
-->
## PING command
The `PING` command is a simple command that can be used to check if the server is responsive.
### Required pre-execution checks
None.
### Request Parameters
No parameters.
### Success Response
No response fields.
### Example Request
```
PING
```
### Example Response
```
+OK
END
```
## CHECK_USERNAME command
The `CHECK_USERNAME` command is used to check if a username is already taken by another user. Additional users can still log in with the same username, but their name will be substituted with a suffix.
### Required pre-execution checks
None.
### Request Parameters
| Parameter Name | Type | Optional | Description |
| :------------- | :------- | :------- | :------------------------------------- |
| `USERNAME` | `String` | no | The username to check for availability |
### Success Response
| Field | Type | Description |
| :------- | :--------------------------- | :---------------------------------------------------------------------- |
| `STATUS` | `Enum<UsernameAvailability>` | Member of enum indicating if the username is available or already taken |
| Members of `UsernameAvailability` | Description |
| :-------------------------------- | :------------------------- |
| `FREE` | Username is available |
| `TAKEN` | Username is already in use |
### Example Request
```
CHECK_USERNAME USERNAME='Lars'
```
### Example Response
```
+OK
STATUS=FREE
END
```
## LOGIN command
The `LOGIN` command is used to log in a user with a specified username. If the username is already taken by another user, the server will append a suffix to the username to make it unique.
### Required pre-execution checks
None.
### Request Parameters
| Parameter Name | Type | Optional | Description |
| :------------- | :------- | :------- | :----------------------------------- |
| `USERNAME` | `String` | no | The username to create the user with |
### Success Response
| Field | Type | Description |
| :--------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------- |
| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one |
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user |
### Error Response
| Code | Description |
| :------------------ | :----------------------------------------------------------------------------- |
| `ALREADY_LOGGED_IN` | The session is already associated with a user, logging in again is prohibited. |
### Example Request
```
LOGIN USERNAME='Lars'
```
### Example Response
```
+OK
USERNAME='Lars_1234'
ID=e47a671e-2b2a-42df-bb82-953fe2ebd307
END
```
## LOGOUT command
Description of the command, what it does, and when it should be used.
### Required pre-execution checks
None.
### Request Parameters
No parameters.
### Success Response
No response fields.
### Error Response
| Code | Description |
| :------------------- | :--------------------------------------------------------------- |
| `NO_USER_ASSOCIATED` | The session has no user associated, logging out is not possible. |
### Example Request
```
LOGOUT
```
### Example Response
```
+OK
END
```
## LIST_USERS command
The `LIST_USERS` command is used to retrieve a list of all currently logged-in users.
### Required pre-execution checks
None.
### Request Parameters
No parameters.
### Success Response
| Field | Type | Description |
| :------ | :----------------- | :--------------------------------------- |
| `USERS` | `Collection<User>` | Collection of all users currently online |
| Fields of `User` | Type | Description |
| :--------------- | :---------------------------------------------------------------------- | :-------------------------------------------------------------------- |
| `USERNAME` | `String` | Username of the newly created user, can differ from the requested one |
| `ID` | [`UUID`](https://docs.oracle.com/javase/8/docs/api/java/util/UUID.html) | The ID of the created user |
### Error Response
None.
### Example Request
```
LIST_USERS
```
### Example Response
```
+OK
USERS
USER
USERNAME=Lars_001
ID=56765d0f-8cd3-4eec-91b2-7e36265c1a5d
END
USER
USERNAME=Lars_002
ID=b7bbd9b3-0d49-4c92-8306-b1c8506d2ff0
END
USER
USERNAME=Lars
ID=982bc78e-547f-495a-a821-433a3603f92c
END
END
END
```
@@ -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.list_users.ListUsersHandler;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users.ListUsersParser;
import ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users.ListUsersRequest;
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;
@@ -106,5 +109,9 @@ public class ServerApp {
parserDispatcher.register("LOGOUT", new LogoutParser());
commandRouter.register(
LogoutRequest.class, new LogoutHandler(responseDispatcher, userRegistry));
parserDispatcher.register("LIST_USERS", new ListUsersParser());
commandRouter.register(
ListUsersRequest.class, new ListUsersHandler(responseDispatcher, userRegistry));
}
}
@@ -0,0 +1,40 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
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;
import java.util.Collection;
/**
* Handles {@link ListUsersRequest}s by retrieving all users from the {@link UserRegistry} and
* dispatching a {@link ListUsersResponse} containing the list of users.
*/
public class ListUsersHandler extends CommandHandler<ListUsersRequest> {
private final UserRegistry userRegistry;
/**
* Creates a new handler for listing all users
*
* @param responseDispatcher the dispatcher used to send the response
* @param userRegistry the registry used to look up existing users
*/
public ListUsersHandler(ResponseDispatcher responseDispatcher, UserRegistry userRegistry) {
super(responseDispatcher);
this.userRegistry = userRegistry;
}
/**
* Executes the list users request
*
* <p>All users are retrieved from the {@link UserRegistry} and returned in a {@link
* ListUsersResponse}.
*
* @param request the request to execute
*/
@Override
public void execute(ListUsersRequest request) {
Collection<User> users = userRegistry.getAllUsers();
responseDispatcher.dispatch(new ListUsersResponse(request.getContext(), users));
}
}
@@ -0,0 +1,18 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users;
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 ListUsersRequest}. */
public class ListUsersParser implements CommandParser<ListUsersRequest> {
/**
* Parses a primitive request into a ListUsersRequest.
*
* @param primitiveRequest the request to parse
* @return the created {@link ListUsersRequest}
*/
@Override
public ListUsersRequest parse(PrimitiveRequest primitiveRequest) {
return new ListUsersRequest(primitiveRequest.context());
}
}
@@ -0,0 +1,17 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users;
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 retrieve all existing users from the server. */
public class ListUsersRequest extends Request {
/**
* Constructs a new ListUsersRequest with the given context
*
* @param context the {@link RequestContext} containing information for responding to the
* request
*/
public ListUsersRequest(RequestContext context) {
super(context);
}
}
@@ -0,0 +1,35 @@
package ch.unibas.dmi.dbis.cs108.casono.server.app.commands.list_users;
import ch.unibas.dmi.dbis.cs108.casono.server.domain.user.User;
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.ResponseBody;
import java.util.Collection;
/** Response containing a list of all active users on the server */
public class ListUsersResponse extends SuccessResponse {
/**
* Creates a new ListUsersResponse containing the given list of users
*
* @param context the {@link RequestContext} associated with the request
* @param users the collection of users currently active on the server
*/
public ListUsersResponse(RequestContext context, Collection<User> users) {
super(
context,
ResponseBody.builder()
.block(
"USERS",
users_block -> {
for (User user : users) {
users_block.block(
"USER",
user_block -> {
user_block.param("USERNAME", user.getName());
user_block.param("ID", user.getId().value());
});
}
})
.build());
}
}
@@ -52,5 +52,5 @@ public abstract class CommandHandler<T extends Request> {
*
* @param request the request to execute, of type T
*/
public void execute(T request) {}
public abstract void execute(T request);
}
@@ -6,7 +6,7 @@ package ch.unibas.dmi.dbis.cs108.casono.server.network.protocol.request.accessor
* @param <T> target type produced by the parser
*/
@FunctionalInterface
interface ThrowingParser<T> {
public interface ThrowingParser<T> {
/**
* Parses the provided raw parameter value.
*