diff --git a/README.md b/README.md new file mode 100644 index 0000000..5c4b4d5 --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# Page + diff --git a/SUMMARY.md b/SUMMARY.md new file mode 100644 index 0000000..3412688 --- /dev/null +++ b/SUMMARY.md @@ -0,0 +1,28 @@ +# Table of contents + +* [Page](README.md) + +## Endpoints + +* [Authentication](endpoints/authentication/README.md) + * [AuthController](endpoints/authentication/authcontroller.md) + * [RefreshController](endpoints/authentication/refreshcontroller.md) +* [FriendshipController](endpoints/friendshipcontroller.md) +* [FriendsRequestQueryController](endpoints/friendsrequestquerycontroller.md) +* [MediaController](endpoints/mediacontroller.md) +* [ChatLoadController](endpoints/chatloadcontroller.md) +* [OnlinePingingController(не работает)](endpoints/onlinepingingcontroller-ne-rabotaet.md) + +## SignalR + +* [PresenceHub](signalr/presencehub.md) +* [ChatHub](signalr/chathub.md) + +*** + +* [FriendsHub](friendshub/README.md) + * [FriendsHub Client (Java)](friendshub/friendshub-client-java.md) + +## Code Docs + +* [HubResult\](code-docs/hubresult-less-than-t-greater-than.md) diff --git a/code-docs/hubresult-less-than-t-greater-than.md b/code-docs/hubresult-less-than-t-greater-than.md new file mode 100644 index 0000000..8e1bf6d --- /dev/null +++ b/code-docs/hubresult-less-than-t-greater-than.md @@ -0,0 +1,164 @@ +--- +description: >- + Description: A generic class used to encapsulate the result of SignalR hub + operations, providing a standardized way to return status, data, and error + messages. It supports various HTTP-like status cod +--- + +# HubResult\ + +**Namespace**: `Govor.Contracts.Responses.SignalR` + +### Properties + +* **Status**: (`HubResultStatus`) Indicates the status of the hub operation, corresponding to HTTP-like status codes (e.g., Success, BadRequest). +* **Result**: (`T?`) The data returned by the hub operation, if applicable. Can be null for error cases or when no data is returned. +* **ErrorMessage**: (`string?`) A message describing the error, if the operation failed. Null for successful operations. + +### Static Methods + +#### Ok + +**Description**: Creates a successful result with an optional data payload.\ +**Parameters**: + +* `result`: (`T?`, optional) The data to include in the response (default: `null`).\ + **Returns**: A `HubResult` with `Status` set to `Success` (200) and the provided `result`.\ + **Example**: + +```csharp +HubResult result = HubResult.Ok("Operation completed"); +``` + +#### Created + +**Description**: Creates a result indicating a resource was created, with an optional data payload.\ +**Parameters**: + +* `result`: (`T?`, optional) The data to include in the response (default: `null`).\ + **Returns**: A `HubResult` with `Status` set to `Created` (201) and the provided `result`.\ + **Example**: + +```csharp +HubResult result = HubResult.Created("Resource created"); +``` + +#### NoContent + +**Description**: Creates a result indicating the operation was successful but no data is returned.\ +**Parameters**: None\ +**Returns**: A `HubResult` with `Status` set to `NoContent` (204) and `Result` set to `null`.\ +**Example**: + +```csharp +HubResult result = HubResult.NoContent(); +``` + +#### BadRequest + +**Description**: Creates a result indicating a client error due to invalid input.\ +**Parameters**: + +* `message`: (`string`) The error message describing the issue. +* `details`: (`T?`, optional) Additional details about the error (default: `null`).\ + **Returns**: A `HubResult` with `Status` set to `BadRequest` (400), the provided `message`, and optional `details`.\ + **Example**: + +```csharp +HubResult result = HubResult.BadRequest("Invalid input provided"); +``` + +#### NotFound + +**Description**: Creates a result indicating a requested resource was not found.\ +**Parameters**: + +* `message`: (`string`) The error message describing the issue. +* `details`: (`T?`, optional) Additional details about the error (default: `null`).\ + **Returns**: A `HubResult` with `Status` set to `NotFound` (404), the provided `message`, and optional `details`.\ + **Example**: + +```csharp +HubResult result = HubResult.NotFound("User not found"); +``` + +#### Unauthorized + +**Description**: Creates a result indicating the client is not authorized to perform the operation.\ +**Parameters**: + +* `message`: (`string`) The error message describing the issue.\ + **Returns**: A `HubResult` with `Status` set to `Unauthorized` (401) and the provided `message`.\ + **Example**: + +```csharp +HubResult result = HubResult.Unauthorized("Authentication required"); +``` + +#### Conflict + +**Description**: Creates a result indicating a conflict, such as a duplicate resource.\ +**Parameters**: + +* `message`: (`string`) The error message describing the issue.\ + **Returns**: A `HubResult` with `Status` set to `Conflict` (409) and the provided `message`.\ + **Example**: + +```csharp +HubResult result = HubResult.Conflict("Resource already exists"); +``` + +#### UnprocessableEntity + +**Description**: Creates a result indicating the request could not be processed due to semantic errors.\ +**Parameters**: + +* `message`: (`string`) The error message describing the issue.\ + **Returns**: A `HubResult` with `Status` set to `UnprocessableEntity` (422) and the provided `message`.\ + **Example**: + +```csharp +HubResult result = HubResult.UnprocessableEntity("Invalid data format"); +``` + +#### Error + +**Description**: Creates a result indicating an internal server error.\ +**Parameters**: + +* `message`: (`string`) The error message describing the issue.\ + **Returns**: A `HubResult` with `Status` set to `ServerError` (500) and the provided `message`.\ + **Example**: + +```csharp +HubResult result = HubResult.Error("Unexpected server error"); +``` + +### Enum: HubResultStatus + +**Description**: Enumerates the possible status codes for a `HubResult` response, aligning with HTTP status codes for consistency in SignalR hub operations. + +**Values**: + +* **Success** (200): The operation was successful. +* **Created** (201): A resource was successfully created. +* **NoContent** (204): The operation was successful, but no content is returned. +* **BadRequest** (400): The request was invalid or malformed. +* **Unauthorized** (401): The client is not authorized to perform the operation. +* **NotFound** (404): The requested resource was not found. +* **Conflict** (409): The request conflicts with an existing resource. +* **UnprocessableEntity** (422): The request could not be processed due to semantic errors. +* **ServerError** (500): An unexpected server error occurred. + +### Example Usage + +```csharp +// Successful operation with data +var successResult = HubResult.Ok(new UserDto { Id = Guid.NewGuid(), Username = "example" }); + +// Error due to invalid input +var errorResult = HubResult.BadRequest("Invalid username provided"); + +// Not found scenario +var notFoundResult = HubResult.NotFound("User not found in database"); +``` diff --git a/endpoints/authentication/README.md b/endpoints/authentication/README.md new file mode 100644 index 0000000..c891b49 --- /dev/null +++ b/endpoints/authentication/README.md @@ -0,0 +1,45 @@ +--- +description: How to work with jwt tokens +icon: user +--- + +# Authentication + +### Components + +* **AuthController**: Handles registration and login. + * **Route**: `/api/auth` +* **RefreshController**: Manages token refresh. + * **Route**: `/api/auth/token` + +### Authentication Process + +* **Registration**: + * **Endpoint**: `POST /api/auth/register` + * **Input**: `RegistrationRequest` (name, password, inviteLink, deviceInfo) + * **Output**: Access token + * **Action**: Validates invite, registers user, opens session. +* **Login**: + * **Endpoint**: `POST /api/auth/login` + * **Input**: `LoginRequest` (name, password, deviceInfo) + * **Output**: Access token + * **Action**: Authenticates user, opens session. +* **Token Refresh**: + * **Endpoint**: `POST /api/auth/token/refresh` + * **Input**: `RefreshTokenRequest` (refreshToken) + * **Output**: `RefreshTokenResponse` (accessToken, refreshToken) + * **Action**: Refreshes expired access token. + +### Token Usage + +* **Access Token**: + * **Request**: Obtain during registration or login. + * **Refresh**: Request via `/api/auth/token/refresh` when expired (e.g., 02:12 PM CEST, July 21, 2025). +* **Refresh Token**: + * **Request**: Received during registration or login. + * **Refresh**: Use to obtain new access token when current one expires. + +### Security + +* **Protocol**: Requires HTTPS. +* **Storage**: Store refresh token in HTTP-only cookie with `Secure` and `SameSite=Strict`. diff --git a/endpoints/authentication/authcontroller.md b/endpoints/authentication/authcontroller.md new file mode 100644 index 0000000..3cd6d03 --- /dev/null +++ b/endpoints/authentication/authcontroller.md @@ -0,0 +1,141 @@ +--- +description: >- + Controller for handling user authentication operations, including registration + and login, with session management and invite-based registration. +--- + +# AuthController + +### Controller Description + +* **Route**: `api/auth` +* **Authorize**: Allows anonymous access (`[AllowAnonymous]`). + +### Endpoints + +#### Register + +* **Description**: Registers a new user using a valid invite link and opens a user session, returning an authentication token. +* **Route**: `[POST] api/auth/register` +* **HTTP Method**: `POST` +* **Request**: + * **Content-Type**: `application/json` + * **Request Body**: `RegistrationRequest` object with the following structure: + + ```json + { + "name": "string", + "password": "string", + "inviteLink": "string", + "deviceInfo": "string" + } + ``` +* **Responses**: + * **200 OK**: Returns the authentication token upon successful registration and session creation. + + ```json + { + "refreshToken": "string", + "accessToken": "string" + } + ``` + * **400 Bad Request**: + * If model validation fails. + * If the user already exists: `"Registration failed: user already exists."` + * If the invite link is invalid: `"Invite link invalid."` + * If the username is invalid: `"Invalid username: "` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + "An unexpected error occurred. Please try again later." + ``` + +#### Login + +* **Description**: Authenticates an existing user and opens a session, returning an authentication token. +* **Route**: `[POST] api/auth/login` +* **HTTP Method**: `POST` +* **Request**: + * **Content-Type**: `application/json` + * **Request Body**: `LoginRequest` object with the following structure: + + ```json + { + "name": "string", + "password": "string", + "deviceInfo": "string" + } + ``` +* **Responses**: + * **200 OK**: Returns the authentication token upon successful login and session creation. + + ```json + { + "refreshToken": "string", + "accessToken": "string" + } + ``` + * **400 Bad Request**: + * If model validation fails. + * If the user does not exist: `"Login failed: user does not exist."` + * If the username or password is incorrect: `"Login failed: username or password is incorrect."` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + "An unexpected error occurred. Please try again later." + ``` + +### Data Models + +#### RegistrationRequest + +* **Description**: Model for registration request data. +* **Structure**: + + ```json + { + "name": "string", + "password": "string", + "inviteLink": "string", + "deviceInfo": "string" + } + ``` + +#### LoginRequest + +* **Description**: Model for login request data. +* **Structure**: + + ```json + { + "name": "string", + "password": "string", + "deviceInfo": "string" + } + ``` + +#### RefreshResult + +* **Description**: Model for refresh token response data. +* **Structure**: + + ```json + { + "refreshToken": "string", + "accessToken": "string" + } + ``` + +### Error Handling + +* **Invalid User ID**: Not applicable (handled by session service). +* **Invalid Operations**: Registration fails if the user already exists, invite link is invalid, or username is invalid, returning `400 Bad Request`. +* **Unauthorized Access**: Not applicable (endpoint is `[AllowAnonymous]`). +* **Unexpected Errors**: General exceptions are caught, logged, and returned as `500 Internal Server Error`. + +### Logging + +* Logs successful registration and login events with username and user ID. +* Logs session opening events with username and user ID. +* Logs warnings for specific exceptions (e.g., user already exists, invalid invite link, login failures). +* Logs errors for unexpected exceptions during registration and login. diff --git a/endpoints/authentication/refreshcontroller.md b/endpoints/authentication/refreshcontroller.md new file mode 100644 index 0000000..403b6b5 --- /dev/null +++ b/endpoints/authentication/refreshcontroller.md @@ -0,0 +1,90 @@ +--- +description: >- + Controller for managing token refresh operations, allowing users to refresh + their access tokens using a valid refresh token. +--- + +# RefreshController + +### Controller Description + +* **Route**: `api/auth/token` +* **Authorize**: Allows anonymous access (`[AllowAnonymous]`). + +### Endpoints + +#### Refresh + +* **Description**: Refreshes an access token using a provided refresh token and returns a new access token and refresh token pair. +* **Route**: `[POST] api/auth/token/refresh` +* **HTTP Method**: `POST` +* **Request**: + * **Content-Type**: `application/json` + * **Request Body**: `RefreshTokenRequest` object with the following structure: + + ```json + { + "refreshToken": "string" + } + ``` +* **Responses**: + * **200 OK**: Returns a new access token and refresh token upon successful refresh. + + ```json + { + "accessToken": "string", + "refreshToken": "string" + } + ``` + * **400 Bad Request**: If model validation fails or the refresh token is empty. + + ```json + "Refresh token cant be empty." + ``` + * **401 Unauthorized**: If the refresh token is invalid or authorization fails. + + ```json + "Invalid refresh token" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + "An unexpected error occurred." + ``` + +### Data Models + +#### RefreshTokenRequest + +* **Description**: Model for refresh token request data. +* **Structure**: + + ```json + { + "refreshToken": "string" + } + ``` + +#### RefreshTokenResponse + +* **Description**: Model for refresh token response data. +* **Structure**: + + ```json + { + "accessToken": "string", + "refreshToken": "string" + } + ``` + +### Error Handling + +* **Invalid User ID**: Not applicable (handled by session service). +* **Invalid Operations**: Returns `400 Bad Request` if the refresh token is empty or invalid. +* **Unauthorized Access**: Returns `401 Unauthorized` if the refresh token fails authorization. +* **Unexpected Errors**: General exceptions are caught, logged, and returned as `500 Internal Server Error`. + +### Logging + +* Logs warnings for invalid or failed refresh token attempts. +* Logs errors for unexpected exceptions during token refresh. diff --git a/endpoints/chatloadcontroller.md b/endpoints/chatloadcontroller.md new file mode 100644 index 0000000..5917f14 --- /dev/null +++ b/endpoints/chatloadcontroller.md @@ -0,0 +1,259 @@ +--- +description: Controller for loading chat messages for users and groups with pagination. +--- + +# ChatLoadController + +### Controller Description + +* **Route**: `api/chats` +* **Authorize**: Requires authenticated user with roles "Admin" or "User" (`[Authorize(Roles = "Admin, User")]`). + +### Endpoints + +#### GetGroupMessages + +* **Description**: Retrieves messages from a specified group chat with pagination. +* **Route**: `[GET] api/chats/group-messages` +* **HTTP Method**: `GET` +* **Request**: + * **Path Parameter**: `chatId` (Guid, required): ID of the group chat. + * **Query Parameter**: `query` (`MessageQuery`, required) with: + * `startMessageId` (Guid?, optional): ID of the starting message. + * `before` (int, default=20): Number of messages before the start. + * `after` (int, default=2): Number of messages after the start. +* **Responses**: + * **200 OK**: Returns a list of group chat messages. + + ```json + [ + { + "id": "Guid", + "senderId": "Guid", + "recipientId": "Guid", + "recipientType": "string", + "encryptedContent": "string", + "sentAt": "DateTime", + "isEdited": "boolean", + "editedAt": "DateTime?", + "replyToMessageId": "Guid?", + "mediaAttachments": [ + { + "id": "Guid", + "messageId": "Guid", + "mediaFileId": "Guid" + } + ], + "reactions": [ + { + "id": "Guid", + "messageId": "Guid", + "userId": "Guid", + "reactionCode": "string", + "reactedAt": "DateTime" + } + ], + "messageViews": [ + { + "id": "Guid", + "messageId": "Guid", + "userId": "Guid", + "viewedAt": "DateTime" + } + ] + } + ] + ``` + * **400 Bad Request**: + * If `before` or `after` is negative or total exceeds 100: `"Values must be non-negative and total must not exceed 100."` + * If an argument or resource is invalid: `"string"` + * **403 Forbidden**: If user lacks authorization. + + ```json + "string" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + "Unexpected Error! Please try again later." + ``` + +#### GetUserMessages + +* **Description**: Retrieves messages from a specified user chat with pagination. +* **Route**: `[GET] api/chats/user-messages` +* **HTTP Method**: `GET` +* **Request**: + * **Path Parameter**: `userId` (Guid, required): ID of the target user. + * **Query Parameter**: `query` (`MessageQuery`, required) with: + * `startMessageId` (Guid?, optional): ID of the starting message. + * `before` (int, default=20): Number of messages before the start. + * `after` (int, default=2): Number of messages after the start. +* **Responses**: + * **200 OK**: Returns a list of user chat messages. + + ```json + [ + { + "id": "Guid", + "senderId": "Guid", + "recipientId": "Guid", + "recipientType": "string", + "encryptedContent": "string", + "sentAt": "DateTime", + "isEdited": "boolean", + "editedAt": "DateTime?", + "replyToMessageId": "Guid?", + "mediaAttachments": [ + { + "id": "Guid", + "messageId": "Guid", + "mediaFileId": "Guid" + } + ], + "reactions": [ + { + "id": "Guid", + "messageId": "Guid", + "userId": "Guid", + "reactionCode": "string", + "reactedAt": "DateTime" + } + ], + "messageViews": [ + { + "id": "Guid", + "messageId": "Guid", + "userId": "Guid", + "viewedAt": "DateTime" + } + ] + } + ] + ``` + * **400 Bad Request**: + * If `before` or `after` is negative or total exceeds 100: `"Values must be non-negative and total must not exceed 100."` + * If an operation or resource is invalid: `"string"` + * **403 Forbidden**: If user lacks authorization. + + ```json + "string" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + "Unexpected Error! Please try again later." + ``` + +### Data Models + +#### MessageQuery + +* **Description**: Model for pagination query parameters. +* **Structure**: + + ```json + { + "startMessageId": "Guid?", + "before": "int", + "after": "int" + } + ``` + +#### MessageResponse + +* **Description**: Data transfer object for chat message details. +* **Structure**: + + ```json + { + "id": "Guid", + "senderId": "Guid", + "recipientId": "Guid", + "recipientType": "string", + "encryptedContent": "string", + "sentAt": "DateTime", + "isEdited": "boolean", + "editedAt": "DateTime?", + "replyToMessageId": "Guid?", + "mediaAttachments": [ + { + "id": "Guid", + "messageId": "Guid", + "mediaFileId": "Guid" + } + ], + "reactions": [ + { + "id": "Guid", + "messageId": "Guid", + "userId": "Guid", + "reactionCode": "string", + "reactedAt": "DateTime" + } + ], + "messageViews": [ + { + "id": "Guid", + "messageId": "Guid", + "userId": "Guid", + "viewedAt": "DateTime" + } + ] + } + ``` + +#### MediaAttachmentResponse + +* **Description**: Data transfer object for media attachments in messages. +* **Structure**: + + ```json + { + "id": "Guid", + "messageId": "Guid", + "mediaFileId": "Guid" + } + ``` + +#### MessageReactionResponse + +* **Description**: Data transfer object for message reactions. +* **Structure**: + + ```json + { + "id": "Guid", + "messageId": "Guid", + "userId": "Guid", + "reactionCode": "string", + "reactedAt": "DateTime" + } + ``` + +#### MessageViewResponse + +* **Description**: Data transfer object for message view tracking. +* **Structure**: + + ```json + { + "id": "Guid", + "messageId": "Guid", + "userId": "Guid", + "viewedAt": "DateTime" + } + ``` + +### Error Handling + +* **Invalid User ID**: Handled by `ICurrentUserService`, aborts if invalid. +* **Invalid Operations**: Returns `400 Bad Request` for negative values, total exceeding 100, or `InvalidOperationException`. +* **Unauthorized Access**: Returns `403 Forbidden` for `UnauthorizedAccessException`. +* **Resource Not Found**: Returns `400 Bad Request` for `NotFoundException`. +* **Unexpected Errors**: Caught and returned as `500 Internal Server Error`. + +### Logging + +* Logs warnings for `UnauthorizedAccessException`, `NotFoundException`, `ArgumentException`, and `InvalidOperationException`. +* Logs errors for unexpected exceptions. diff --git a/endpoints/friendshipcontroller.md b/endpoints/friendshipcontroller.md new file mode 100644 index 0000000..4ba6894 --- /dev/null +++ b/endpoints/friendshipcontroller.md @@ -0,0 +1,114 @@ +--- +description: >- + Description: Controller for managing friendship-related operations, including + searching for users and retrieving the list of friends for the current user. +--- + +# FriendshipController + +### Controller Description + +* **Route**: `api/friends` +* **Authorize**: Requires authenticated user (`[Authorize]`). + +### Endpoints + +#### Search + +* **Description**: Searches for users based on a query string. +* **Route**: `[GET] api/friends/search?query=` +* **HTTP Method**: `GET` +* **Request**: + * **Query Parameter**: `query` (string, required) +* **Responses**: + * **200 OK**: Returns a list of matching users. + + ```json + [ + { + "id": "Guid", + "username": "string", + "description": "string", + "wasOnline": "DateTime", + "iconId": "Guid" + } + ] + ``` + * **400 Bad Request**: If the query is empty. + + ```json + "Query cannot be empty" + ``` + * **403 Forbidden**: If user lacks authorization. + + ```json + "string" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + { + "error": "Internal error during user search." + } + ``` + +#### GetFriends + +* **Description**: Retrieves the list of friends for the authenticated user. +* **Route**: `[GET] api/friends` +* **HTTP Method**: `GET` +* **Request**: None +* **Responses**: + * **200 OK**: Returns a list of the user's friends or an empty list if none. + + ```json + [ + { + "id": "Guid", + "username": "string", + "description": "string", + "wasOnline": "DateTime", + "iconId": "Guid" + } + ] + ``` + * **403 Forbidden**: If user lacks authorization. + + ```json + "string" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + { + "error": "Internal server error." + } + ``` + +### Data Models + +#### UserDto + +* **Description**: Data transfer object for user information. +* **Structure**: + + ```json + { + "id": "Guid", + "username": "string", + "description": "string", + "wasOnline": "DateTime", + "iconId": "Guid" + } + ``` + +### Error Handling + +* **Invalid User ID**: Handled by `ICurrentUserService`, aborts if invalid. +* **Invalid Operations**: Returns `400 Bad Request` for empty query. +* **Unexpected Errors**: Caught and returned as `500 Internal Server Error`. + +### Logging + +* Logs warnings for `SearchUsersException`. +* Logs errors for unexpected exceptions and `InvalidOperationException`. diff --git a/endpoints/friendsrequestquerycontroller.md b/endpoints/friendsrequestquerycontroller.md new file mode 100644 index 0000000..0af2473 --- /dev/null +++ b/endpoints/friendsrequestquerycontroller.md @@ -0,0 +1,109 @@ +--- +description: >- + Description: Controller for querying friend request-related data, including + incoming friend requests and responses to sent friend requests. +--- + +# FriendsRequestQueryController + +### Controller Description + +* **Route**: `api/friends` +* **Authorize**: Requires authenticated user (`[Authorize]`). + +### Endpoints + +#### GetIncomingRequests + +* **Description**: Retrieves a list of incoming friend requests for the authenticated user. +* **Route**: `[GET] api/friends/requests` +* **HTTP Method**: `GET` +* **Request**: None +* **Responses**: + * **200 OK**: Returns a list of incoming friend requests or an empty list if none. + + ```json + [ + { + "id": "Guid", + "senderId": "Guid", + "receiverId": "Guid", + "status": "string", + "createdAt": "DateTime" + } + ] + ``` + * **403 Forbidden**: If user lacks authorization. + + ```json + "string" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + { + "error": "Internal server error." + } + ``` + +#### GetResponses + +* **Description**: Retrieves a list of responses to the authenticated user's sent friend requests. +* **Route**: `[GET] api/friends/responses` +* **HTTP Method**: `GET` +* **Request**: None +* **Responses**: + * **200 OK**: Returns a list of friend request responses or an empty list if none. + + ```json + [ + { + "id": "Guid", + "senderId": "Guid", + "receiverId": "Guid", + "status": "string", + "createdAt": "DateTime" + } + ] + ``` + * **403 Forbidden**: If user lacks authorization. + + ```json + "string" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + { + "error": "Internal server error." + } + ``` + +### Data Models + +#### FriendshipDto + +* **Description**: Data transfer object for friend request information. +* **Structure**: + + ```json + { + "id": "Guid", + "senderId": "Guid", + "receiverId": "Guid", + "status": "string", + "createdAt": "DateTime" + } + ``` + +### Error Handling + +* **Invalid User ID**: Handled by `ICurrentUserService`, aborts if invalid. +* **Invalid Operations**: Returns `200 OK` with an empty list for `InvalidOperationException`. +* **Unexpected Errors**: Caught and returned as `500 Internal Server Error`. + +### Logging + +* Logs warnings for `InvalidOperationException`. +* Logs information for successful response retrieval. +* Logs errors for unexpected exceptions. diff --git a/endpoints/mediacontroller.md b/endpoints/mediacontroller.md new file mode 100644 index 0000000..a9b47f5 --- /dev/null +++ b/endpoints/mediacontroller.md @@ -0,0 +1,107 @@ +--- +description: Controller for uploading and downloading media files. +--- + +# MediaController + +### Controller Description + +* **Route**: `api/media` +* **Authorize**: Requires authenticated user with roles "User" or "Admin" (`[Authorize(Roles = "User,Admin")]`). + +### Endpoints + +#### Upload + +* **Description**: Uploads a media file with associated metadata. +* **Route**: `[POST] api/media/upload` +* **HTTP Method**: `POST` +* **Request**: + * **Content-Type**: `multipart/form-data` + * **Request Body**: `MediaUploadRequest` object with the following structure: + + ```json + { + "fromFile": "IFormFile", + "type": "MediaType", + "mimeType": "string", + "encryptedKey": "string" + } + ``` + * **Constraints**: File size limit of 20 MB. +* **Responses**: + * **200 OK**: Returns the upload result. + + ```json + "string" // Media ID or similar result + ``` + * **400 Bad Request**: + * If model validation fails. + * If no file is uploaded: `"No file uploaded"` + * If file exceeds 20 MB: `"File is too large"` + * If MIME type is missing: `"Missing MIME type"` + * If operation is invalid: `"string"` + * **403 Forbidden**: If user lacks authorization. + + ```json + "string" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + "Internal server error" + ``` + +#### Download + +* **Description**: Downloads a media file by its ID. +* **Route**: `[GET] api/media/download/{id}` +* **HTTP Method**: `GET` +* **Request**: + * **Path Parameter**: `id` (Guid, required): ID of the media file. +* **Responses**: + * **200 OK**: Returns the media file as a stream. + * **Content-Type**: Matches `MimeType` of the media. + * **Content-Disposition**: Includes `filename` from `FileName`. + * **403 Forbidden**: If user lacks access. + * No body. + * **404 Not Found**: If media is not found. + + ```json + "Media not found" + ``` + * **500 Internal Server Error**: Indicates an unexpected error. + + ```json + "Internal server error" + ``` + +### Data Models + +#### MediaUploadRequest + +* **Description**: Model for media upload request data. +* **Structure**: + + ```json + { + "fromFile": "IFormFile", + "type": "MediaType", + "mimeType": "string", + "encryptedKey": "string" + } + ``` + +### Error Handling + +* **Invalid User ID**: Handled by `ICurrentUserService`, aborts if invalid. +* **Invalid Operations**: Returns `400 Bad Request` for `InvalidOperationException`. +* **Unauthorized Access**: Returns `403 Forbidden` for `UnauthorizedAccessException`. +* **Resource Not Found**: Returns `404 Not Found` for `KeyNotFoundException`. +* **Unexpected Errors**: Caught and returned as `500 Internal Server Error`. + +### Logging + +* Logs warnings for `UnauthorizedAccessException`, `InvalidOperationException`, and `KeyNotFoundException`. +* Logs information for successful file uploads. +* Logs errors for unexpected exceptions during upload and download. diff --git a/endpoints/onlinepingingcontroller-ne-rabotaet.md b/endpoints/onlinepingingcontroller-ne-rabotaet.md new file mode 100644 index 0000000..adb67f5 --- /dev/null +++ b/endpoints/onlinepingingcontroller-ne-rabotaet.md @@ -0,0 +1,50 @@ +--- +description: >- + Description: Controller for handling user online status updates by processing + ping requests. +--- + +# OnlinePingingController(не работает) + +**Route**: `api/online`\ +**Authorize**: Requires "User" or "Admin" role + +### End Point {PATCH} + +`[PATCH] api/online/ping` + +#### Description + +Updates the online status of the current user by sending a ping request. + +**Request** + +* **Content-Type**: `application/json` +* **Request Body**: None + +**Responses** + +* **200 OK**: Ping processed successfully. + + ```json + {} + ``` +* **400 Bad Request**: If the user cannot be found in the database. + + ```json + "User can't be found in our database." + ``` +* **403 Forbidden**: If the user is not authorized to perform the action. + + ```json + { + "error": "Not authorized to perform this action." + } + ``` +* **500 Internal Server Error**: Indicates an unexpected error. + + ```json + { + "error": "Failed to send friend request." + } + ``` diff --git a/friendshub/README.md b/friendshub/README.md new file mode 100644 index 0000000..c52a7a1 --- /dev/null +++ b/friendshub/README.md @@ -0,0 +1,235 @@ +--- +description: >- + Description: SignalR hub for managing real-time friend request operations, + including sending, accepting, and rejecting friend requests. +icon: wifi +--- + +# FriendsHub + +**Route** `hubs/friends` + +**Authorize**: Requires authentication (user must be logged in) + +### Hub Methods + +#### OnConnectedAsync + +**Description**: Automatically invoked when a client establishes a connection to the hub. Adds the user to their personal group for receiving friend request notifications. + +**Behavior**: + +* Retrieves the user's ID from the connection context. +* If the user ID is invalid (`Guid.Empty`), logs a warning and aborts the connection. +* Adds the user to a group named after their user ID (e.g., `userId.ToString()`). +* Logs the connection event with the user ID and connection ID. + +*** + +#### OnDisconnectedAsync + +**Description**: Automatically invoked when a client disconnects from the hub. Removes the user from their personal group. + +**Behavior**: + +* Retrieves the user's ID from the connection context, suppressing exceptions if the ID is unavailable (e.g., due to an early abort). +* If the user ID is valid, removes the user from their personal group. +* Logs the disconnection event with the user ID and connection ID. +* If an exception occurs, logs it as a warning along with the connection ID. +* If no exception occurs but the user ID is invalid, logs the disconnection with the connection ID. + +*** + +#### SendRequest + +**Description**: Sends a friend request to a specified user and notifies the recipient in real-time. + +**Request**: + +* **Method Name**: `SendRequest` +* **Parameters**: + * `targetUserId`: (`Guid`) The ID of the user to whom the friend request is sent. + +**Responses**: + +* **Success (201 Created)**: Friend request sent successfully. + + ```json + { + "status": 201, + "result": null, + "errorMessage": null + } + ``` +* **400 Bad Request**: If the operation is invalid (e.g., sending a request to oneself). + + ```json + { + "status": 400, + "result": null, + "errorMessage": "" + } + ``` +* **401 Unauthorized**: If the user is not authorized. + + ```json + { + "status": 401, + "result": null, + "errorMessage": "" + } + ``` +* **409 Conflict**: If a friend request was already sent. + + ```json + { + "status": 409, + "result": null, + "errorMessage": "" + } + ``` +* **500 Server Error**: Indicates an unexpected error. + + ```json + { + "status": 500, + "result": null, + "errorMessage": "Unexpected error! Please try later!" + } + ``` + +**Client-Side Events**: + +* **FriendRequestReceived**: Triggered on the recipient's client to notify them of a new friend request. + * Payload: `userId` (`Guid`) - The ID of the user who sent the request. + +*** + +#### AcceptRequest + +**Description**: Accepts a friend request and notifies the user in real-time. + +**Request**: + +* **Method Name**: `AcceptRequest` +* **Parameters**: + * `friendshipId`: (`Guid`) The ID of the friend request to accept. + +**Responses**: + +* **Success (200 OK)**: Friend request accepted successfully. + + ```json + { + "status": 200, + "result": null, + "errorMessage": null + } + ``` +* **400 Bad Request**: If the operation is invalid (e.g., invalid friendship ID). + + ```json + { + "status": 400, + "result": null, + "errorMessage": "" + } + ``` +* **401 Unauthorized**: If the user is not authorized to accept the request. + + ```json + { + "status": 401, + "result": null, + "errorMessage": "" + } + ``` +* **500 Server Error**: Indicates an unexpected error. + + ```json + { + "status": 500, + "result": null, + "errorMessage": "Unexpected error! Please try later!" + } + ``` + +**Client-Side Events**: + +* **FriendRequestAccepted**: Triggered on the user's client to confirm the friend request was accepted. + * Payload: `friendshipId` (`Guid`) - The ID of the accepted friend request. + +*** + +#### RejectRequest + +**Description**: Rejects a friend request and notifies the user in real-time. + +**Request**: + +* **Method Name**: `RejectRequest` +* **Parameters**: + * `friendshipId`: (`Guid`) The ID of the friend request to reject. + +**Responses**: + +* **Success (200 OK)**: Friend request rejected successfully. + + ```json + { + "status": 200, + "result": null, + "errorMessage": null + } + ``` +* **400 Bad Request**: If the operation is invalid (e.g., invalid friendship ID). + + ```json + { + "status": 400, + "result": null, + "errorMessage": "" + } + ``` +* **401 Unauthorized**: If the user is not authorized to reject the request. + + ```json + { + "status": 401, + "result": null, + "errorMessage": "" + } + ``` +* **500 Server Error**: Indicates an unexpected error. + + ```json + { + "status": 500, + "result": null, + "errorMessage": "Unexpected error! Please try later!" + } + ``` + +**Client-Side Events**: + +* **FriendRequestRejected**: Triggered on the user's client to confirm the friend request was rejected. + * Payload: `friendshipId` (`Guid`) - The ID of the rejected friend request. + +*** + +### Error Handling + +* **Invalid User ID**: If the user ID is `Guid.Empty` during connection, the connection is aborted with a logged warning. +* **Invalid Operations**: Operations like sending a request to oneself or accepting/rejecting an invalid request return a `BadRequest` result. +* **Unauthorized Access**: Unauthorized actions return an `Unauthorized` result with a logged warning. +* **Conflicts**: Attempting to send a duplicate friend request returns a `Conflict` result. +* **Unexpected Errors**: General exceptions are caught, logged, and returned as a `ServerError` result. + +*** + +### Logging + +* Logs connection and disconnection events with user ID and connection ID. +* Logs friend request operations (send, accept, reject) with success or failure details for monitoring and debugging. + +*** diff --git a/friendshub/friendshub-client-java.md b/friendshub/friendshub-client-java.md new file mode 100644 index 0000000..e9b616b --- /dev/null +++ b/friendshub/friendshub-client-java.md @@ -0,0 +1,384 @@ +--- +description: >- + Description: This sub-documentation provides example Java client code for + interacting with the FriendsHub SignalR hub using the microsoft/signalr + library. It covers connecting to the hub, handling con +icon: java +--- + +# FriendsHub Client (Java) + +**Prerequisites**: + +* Include the SignalR Java client library in your project. For Gradle, add the following dependency: + + ```gradle + implementation("com.microsoft.signalr:signalr:9.0.7") + ``` +* Ensure the client has a valid authentication token (JWT) for connecting to the hub, as it requires authentication. + +**Hub Route**: `hubs/friends`\ +**Authorize**: Requires a valid JWT token with a `userId` claim. + +### Setup and Connection + +#### Connecting to the Hub + +This example demonstrates how to establish a connection to the `FriendsHub` with authentication and handle connection events. + +```java +import com.microsoft.signalr.HubConnection; +import com.microsoft.signalr.HubConnectionBuilder; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; + +public class FriendsHubClient { + private final HubConnection hubConnection; + private final String accessToken; // JWT token with userId claim + + public FriendsHubClient(String hubUrl, String accessToken) { + this.accessToken = accessToken; + this.hubConnection = HubConnectionBuilder.create(hubUrl) + .withAccessTokenProvider(Single.just(accessToken)) + .build(); + + // Handle connection established + hubConnection.onConnected(() -> { + System.out.println("Connected to FriendsHub with connection ID: " + hubConnection.getConnectionId()); + }); + + // Handle connection closed + hubConnection.onClosed((exception) -> { + if (exception != null) { + System.err.println("Connection closed with error: " + exception.getMessage()); + } else { + System.out.println("Connection closed normally"); + } + }); + } + + public Completable startConnection() { + return hubConnection.start(); + } + + public Completable stopConnection() { + return hubConnection.stop(); + } +} +``` + +**Usage**: + +```java +String hubUrl = "https://your-api-url/hubs/friends"; +String jwtToken = "your-jwt-token-here"; +FriendsHubClient client = new FriendsHubClient(hubUrl, jwtToken); +client.startConnection() + .blockingAwait(); // Wait for connection to establish +``` + +**Notes**: + +* Replace `your-api-url` with the actual server URL. +* The `accessToken` must include a valid JWT with the `userId` claim to avoid connection abortion. +* Use `stopConnection()` to gracefully disconnect when done. + +### Subscribing to Hub Events + +The `FriendsHub` sends three client-side events: `FriendRequestReceived`, `FriendRequestAccepted`, and `FriendRequestRejected`. Below is an example of subscribing to these events. + +```java +public class FriendsHubClient { + // ... (constructor and connection methods as above) + + public void subscribeToEvents() { + // Handle FriendRequestReceived event + hubConnection.on("FriendRequestReceived", (userId) -> { + System.out.println("Received friend request from user ID: " + userId); + // Handle the friend request (e.g., update UI or notify user) + }, String.class); + + // Handle FriendRequestAccepted event + hubConnection.on("FriendRequestAccepted", (friendshipId) -> { + System.out.println("Friend request accepted, friendship ID: " + friendshipId); + // Update UI or local state to reflect new friendship + }, String.class); + + // Handle FriendRequestRejected event + hubConnection.on("FriendRequestRejected", (friendshipId) -> { + System.out.println("Friend request rejected, friendship ID: " + friendshipId); + // Update UI or notify user of rejection + }, String.class); + } +} +``` + +**Usage**: + +```java +FriendsHubClient client = new FriendsHubClient(hubUrl, jwtToken); +client.subscribeToEvents(); +client.startConnection().blockingAwait(); +``` + +**Notes**: + +* The `userId` and `friendshipId` parameters are received as strings (representing `Guid` values) due to JSON serialization. Parse them to `UUID` if needed: + + ```java + UUID uuid = UUID.fromString(userId); + ``` +* Call `subscribeToEvents()` before starting the connection to ensure event handlers are registered. + +### Invoking Hub Methods + +#### SendRequest + +Sends a friend request to another user. + +```java +public Completable sendFriendRequest(String targetUserId) { + return hubConnection.invoke(HubResult.class, "SendRequest", targetUserId) + .doOnSuccess(result -> { + switch (result.getStatus()) { + case 201: // Created + System.out.println("Friend request sent successfully"); + break; + case 400: // BadRequest + System.err.println("Bad request: " + result.getErrorMessage()); + break; + case 401: // Unauthorized + System.err.println("Unauthorized: " + result.getErrorMessage()); + break; + case 409: // Conflict + System.err.println("Conflict: " + result.getErrorMessage()); + break; + case 500: // ServerError + System.err.println("Server error: " + result.getErrorMessage()); + break; + default: + System.err.println("Unexpected status: " + result.getStatus()); + } + }) + .ignoreElement(); +} +``` + +**Usage**: + +```java +client.sendFriendRequest("target-user-guid-here") + .blockingAwait(); +``` + +**Notes**: + +* The `targetUserId` should be a valid `Guid` string (e.g., "550e8400-e29b-41d4-a716-446655440000"). +* The response is a `HubResult` with a status code and optional error message. + +#### AcceptRequest + +Accepts a friend request by its friendship ID. + +```java +public Completable acceptFriendRequest(String friendshipId) { + return hubConnection.invoke(HubResult.class, "AcceptRequest", friendshipId) + .doOnSuccess(result -> { + switch (result.getStatus()) { + case 200: // OK + System.out.println("Friend request accepted successfully"); + break; + case 400: // BadRequest + System.err.println("Bad request: " + result.getErrorMessage()); + break; + case 401: // Unauthorized + System.err.println("Unauthorized: " + result.getErrorMessage()); + break; + case 500: // ServerError + System.err.println("Server error: " + result.getErrorMessage()); + break; + default: + System.err.println("Unexpected status: " + result.getStatus()); + } + }) + .ignoreElement(); +} +``` + +**Usage**: + +```java +client.acceptFriendRequest("friendship-guid-here") + .blockingAwait(); +``` + +#### RejectRequest + +Rejects a friend request by its friendship ID. + +```java +public Completable rejectFriendRequest(String friendshipId) { + return hubConnection.invoke(HubResult.class, "RejectRequest", friendshipId) + .doOnSuccess(result -> { + switch (result.getStatus()) { + case 200: // OK + System.out.println("Friend request rejected successfully"); + break; + case 400: // BadRequest + System.err.println("Bad request: " + result.getErrorMessage()); + break; + case 401: // Unauthorized + System.err.println("Unauthorized: " + result.getErrorMessage()); + break; + case 500: // ServerError + System.err.println("Server error: " + result.getErrorMessage()); + break; + default: + System.err.println("Unexpected status: " + result.getStatus()); + } + }) + .ignoreElement(); +} +``` + +**Usage**: + +```java +client.rejectFriendRequest("friendship-guid-here") + .blockingAwait(); +``` + +### Data Model + +The client expects a `HubResult` response from hub methods, which is serialized as JSON: + +```json +{ + "status": "number", // Corresponds to HubResultStatus (e.g., 200, 400, 401, etc.) + "result": null, // Always null for FriendsHub methods + "errorMessage": "string" // Null for success, error message for failures +} +``` + +### Error Handling + +* **Connection Errors**: Handle connection failures in `onClosed` or by catching exceptions from `startConnection()`. +* **Invalid User ID**: If the JWT lacks a valid `userId` claim, the hub aborts the connection, and `onClosed` is triggered with an error. +* **Hub Method Errors**: Check the `status` and `errorMessage` fields in the `HubResult` response to handle specific errors (e.g., `BadRequest`, `Unauthorized`, `Conflict`). + +### Example Full Client + +Below is a complete example combining connection, event subscription, and method invocation. + +```java +import com.microsoft.signalr.HubConnection; +import com.microsoft.signalr.HubConnectionBuilder; +import io.reactivex.rxjava3.core.Completable; +import io.reactivex.rxjava3.core.Single; + +public class FriendsHubClient { + private final HubConnection hubConnection; + private final String accessToken; + + public FriendsHubClient(String hubUrl, String accessToken) { + this.accessToken = accessToken; + this.hubConnection = HubConnectionBuilder.create(hubUrl) + .withAccessTokenProvider(Single.just(accessToken)) + .build(); + + // Connection events + hubConnection.onConnected(() -> { + System.out.println("Connected to FriendsHub with connection ID: " + hubConnection.getConnectionId()); + }); + hubConnection.onClosed((exception) -> { + System.err.println(exception != null ? "Connection closed with error: " + exception.getMessage() : "Connection closed normally"); + }); + + // Subscribe to hub events + hubConnection.on("FriendRequestReceived", (userId) -> { + System.out.println("Received friend request from user ID: " + userId); + }, String.class); + hubConnection.on("FriendRequestAccepted", (friendshipId) -> { + System.out.println("Friend request accepted, friendship ID: " + friendshipId); + }, String.class); + hubConnection.on("FriendRequestRejected", (friendshipId) -> { + System.out.println("Friend request rejected, friendship ID: " + friendshipId); + }, String.class); + } + + public Completable startConnection() { + return hubConnection.start(); + } + + public Completable stopConnection() { + return hubConnection.stop(); + } + + public Completable sendFriendRequest(String targetUserId) { + return hubConnection.invoke(HubResult.class, "SendRequest", targetUserId) + .doOnSuccess(this::handleHubResult) + .ignoreElement(); + } + + public Completable acceptFriendRequest(String friendshipId) { + return hubConnection.invoke(HubResult.class, "AcceptRequest", friendshipId) + .doOnSuccess(this::handleHubResult) + .ignoreElement(); + } + + public Completable rejectFriendRequest(String friendshipId) { + return hubConnection.invoke(HubResult.class, "RejectRequest", friendshipId) + .doOnSuccess(this::handleHubResult) + .ignoreElement(); + } + + private void handleHubResult(HubResult result) { + switch (result.getStatus()) { + case 200: + System.out.println("Operation successful"); + break; + case 201: + System.out.println("Resource created successfully"); + break; + case 400: + System.err.println("Bad request: " + result.getErrorMessage()); + break; + case 401: + System.err.println("Unauthorized: " + result.getErrorMessage()); + break; + case 409: + System.err.println("Conflict: " + result.getErrorMessage()); + break; + case 500: + System.err.println("Server error: " + result.getErrorMessage()); + break; + default: + System.err.println("Unexpected status: " + result.getStatus()); + } + } + + public static void main(String[] args) { + String hubUrl = "https://your-api-url/hubs/friends"; + String jwtToken = "your-jwt-token-here"; + FriendsHubClient client = new FriendsHubClient(hubUrl, jwtToken); + + // Start connection and send a friend request + client.startConnection() + .andThen(client.sendFriendRequest("550e8400-e29b-41d4-a716-446655440000")) + .blockingAwait(); + + // Simulate accepting a friend request + client.acceptFriendRequest("friendship-guid-here") + .blockingAwait(); + + // Stop connection + client.stopConnection().blockingAwait(); + } +} +``` + +**Notes**: + +* Replace `your-api-url` and `your-jwt-token-here` with actual values. +* The `main` method demonstrates a complete workflow, but in a real application, you would integrate this with your UI or event loop. +* Use `UUID.fromString()` to convert string GUIDs to `UUID` objects if needed for internal logic. diff --git a/signalr/chathub.md b/signalr/chathub.md new file mode 100644 index 0000000..368938f --- /dev/null +++ b/signalr/chathub.md @@ -0,0 +1,162 @@ +--- +icon: wifi +--- + +# ChatHub + +**Route**: `hubs/chats`\ +**Authorize**: Requires authentication (user must be logged in) + +*** + +### Hub Methods + +#### OnConnectedAsync + +**Description**: Automatically invoked when a client establishes a connection to the hub. Adds the user to their personal group for private messages and notifications. + +**Behavior**: + +* Retrieves the user's ID from the connection context. +* If the user ID is invalid (`Guid.Empty`), logs a warning and aborts the connection. +* Adds the user to a group named after their user ID (e.g., `userId.ToString()`). +* Logs the connection event with the user ID and connection ID. + +**Notes**: + +* Future enhancements may include adding the user to their chat groups (currently a TODO in the code). + +*** + +#### OnDisconnectedAsync + +**Description**: Automatically invoked when a client disconnects from the hub. Removes the user from their personal group. + +**Behavior**: + +* Retrieves the user's ID from the connection context, suppressing exceptions if the ID is unavailable (e.g., due to an early abort). +* If the user ID is valid, removes the user from their personal group. +* Logs the disconnection event with the user ID and connection ID. +* If an exception occurs, logs it as a warning along with the connection ID. +* If no exception occurs but the user ID is invalid, logs the disconnection with the connection ID. + +**Notes**: + +* Future enhancements may include removing the user from their chat groups (currently a TODO in the code). + +*** + +#### Send + +**Description**: Allows a client to send a message to a recipient, which can be either a user or a group. + +**Request**: + +* **Method Name**: `Send` +* **Parameters**: + * `request`: An object of type `MessageRequest` with the following structure: + + ```json + { + "encryptedContent": "string", + "replyToMessageId": "Guid", + "recipientId": "Guid", + "recipientType": "string", // "User" or "Group" + "mediaAttachments": [ + { + "mediaId": "Guid", + "encryptedKey": "string", + "type": "string", + "mimeType": "string" + } + ] + } + ``` + +**Responses**: + +* **Success**: + * Sends the message to the recipient (user or group) via the `ReceiveMessage` event. + * Notifies the sender with a confirmation via the `MessageSent` event. + * Logs the successful message send with the message ID, sender ID, recipient ID, and recipient type. +* **Error**: + * If the message is empty (no `encryptedContent` and no `mediaAttachments`), logs a warning and throws an `ArgumentException`. + * If the message fails to send (e.g., due to an internal error), logs the error and throws a `HubException`. + +**Client-Side Events**: + +* **ReceiveMessage**: Triggered on the recipient's client(s) to deliver the message. + * Payload: `UserMessageResponse` object. +* **MessageSent**: Triggered on the sender's client to confirm the message was sent. + * Payload: `UserMessageResponse` object. + +**Notes**: + +* The message must contain either `encryptedContent` or `mediaAttachments`; otherwise, it is invalid. +* The recipient is determined by `recipientType`: + * `"User"`: Sends to the recipient’s personal group (e.g., `recipientId.ToString()`). + * `"Group"`: Sends to all members of the group (e.g., `group_{recipientId}`). + +*** + +### Data Models + +#### MessageRequest + +```json +{ + "encryptedContent": "string", + "replyToMessageId": "Guid", + "recipientId": "Guid", + "recipientType": "string", // "User" or "Group" + "mediaAttachments": [ + { + "mediaId": "Guid", + "encryptedKey": "string", + "type": "string", + "mimeType": "string" + } + ] +} +``` + +#### UserMessageResponse + +```json +{ + "messageId": "Guid", + "senderId": "Guid", + "recipientId": "Guid", + "recipientType": "string", + "encryptedContent": "string", + "sentAt": "DateTime", + "isEdited": "bool", + "mediaAttachments": [ + { + "mediaId": "Guid", + "encryptedKey": "string", + "type": "string", + "mimeType": "string" + } + ], + "replyToMessageId": "Guid" +} +``` + +*** + +### Error Handling + +* **Invalid User ID**: If the user ID is `Guid.Empty` during connection, the connection is aborted with a logged warning. +* **Empty Message**: If a message lacks both content and attachments, an `ArgumentException` is thrown with a logged warning. +* **Message Send Failure**: If sending fails (e.g., due to a service error), a `HubException` is thrown with the error logged. + +*** + +### Logging + +* Logs connection events with user ID and connection ID. +* Logs disconnection events, including exceptions if present. +* Logs message send attempts, successes, and failures for monitoring and debugging. + +*** diff --git a/signalr/presencehub.md b/signalr/presencehub.md new file mode 100644 index 0000000..eb29710 --- /dev/null +++ b/signalr/presencehub.md @@ -0,0 +1,53 @@ +--- +description: SignalR hub for managing user online presence and notifications. +icon: wifi +--- + +# PresenceHub + +### Controller Description + +* **Route**: `hubs/presence` +* **Authorize**: Requires authenticated user with roles "Admin" or "User" (`[Authorize(Roles = "Admin, User")]`). + +### Hub Methods + +#### OnConnectedAsync + +* **Description**: Invoked when a client connects, updates online status and notifies friends. +* **Behavior**: + * Retrieves user ID from context. + * Aborts connection if user ID is `Guid.Empty` or user does not exist. + * Adds user to online store and their personal group. + * Notifies friends of user going online via `UserOnline` event. +* **Client-Side Events**: + * **UserOnline**: Triggered for friends with payload `userId` (Guid). +* **Responses**: None (connection aborted on invalid user ID). + +#### OnDisconnectedAsync + +* **Description**: Invoked when a client disconnects, updates offline status and notifies friends. +* **Behavior**: + * Retrieves user ID from context, suppressing exceptions. + * Removes user from personal group if ID is valid. + * Updates user's `WasOnline` timestamp. + * Sets user as offline in store. + * Notifies friends of user going offline via `UserOffline` event. +* **Client-Side Events**: + * **UserOffline**: Triggered for friends with payload `userId` (Guid). +* **Responses**: None. + +### Data Models + +* **N/A**: No specific data models defined for hub methods. + +### Error Handling + +* **Invalid User ID**: Aborts connection with logged warning if `Guid.Empty` or user not found. +* **Unexpected Errors**: Logged, but no specific response (handled by base method). + +### Logging + +* Logs warnings for invalid user ID on connection. +* Logs information for disconnections with user ID and connection ID. +* Logs errors for unexpected exceptions during disconnection.