Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
**/.DS_Store
**/.idea/**
docs
2,989 changes: 2,989 additions & 0 deletions Doxyfile

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,14 @@

namespace AnalyticsService.API.Controllers;

/// <summary>
/// Provides REST API endpoints for getting all reserved trainings along with the corresponding reviews.
/// </summary>
/// <remarks>
/// This controller exposes operations for fetching individual and group trainings.
///
/// All routes are secured and require authorization with roles <c>Admin</c>, <c>Trainer</c>, or <c>Client</c>.
/// </remarks>
[Authorize]
[ApiController]
[Route("api/v1/[controller]")]
Expand All @@ -19,6 +27,11 @@ public AnalyticsController(IAnalyticsRepository repository)
}

// Individual Trainings
/// <summary>
/// API for fetching all individual trainings associated with a given trainer.
/// </summary>
/// <param name="trainerId">Unique identifier of the desired trainer</param>
/// <returns><c>IEnumerable</c> of individual trainings for the given trainer.</returns>
[Authorize(Roles = "Admin, Trainer")]
[HttpGet("individual/trainer/{trainerId}")]
[ProducesResponseType(typeof(IEnumerable<IndividualTraining>), StatusCodes.Status200OK)]
Expand All @@ -28,6 +41,11 @@ public async Task<ActionResult<IEnumerable<IndividualTraining>>> GetIndividualTr
return Ok(reservations);
}

/// <summary>
/// API for fetching all individual trainings associated with a given client.
/// </summary>
/// <param name="clientId">Unique identifier of the desired client</param>
/// <returns><c>IEnumerable</c> of individual trainings for the given client.</returns>
[Authorize(Roles = "Admin, Client")]
[HttpGet("individual/client/{clientId}")]
[ProducesResponseType(typeof(IEnumerable<IndividualTraining>), StatusCodes.Status200OK)]
Expand All @@ -38,6 +56,11 @@ public async Task<ActionResult<IEnumerable<IndividualTraining>>> GetIndividualTr
}

// Group Trainings
/// <summary>
/// API for fetching all group trainings associated with a given trainer.
/// </summary>
/// <param name="trainerId">Unique identifier of the desired trainer</param>
/// <returns><c>IEnumerable</c> of group trainings for the given trainer.</returns>
[Authorize(Roles = "Admin, Trainer")]
[HttpGet("group/trainer/{trainerId}")]
[ProducesResponseType(typeof(IEnumerable<GroupTraining>), StatusCodes.Status200OK)]
Expand All @@ -47,6 +70,11 @@ public async Task<ActionResult<IEnumerable<GroupTraining>>> GetGroupTrainingsByT
return Ok(reservations);
}

/// <summary>
/// API for fetching all group trainings associated with a given client.
/// </summary>
/// <param name="clientId">Unique identifier of the desired client</param>
/// <returns><c>IEnumerable</c> of group trainings for the given client.</returns>
[Authorize(Roles = "Admin, Client")]
[HttpGet("group/client/{clientId}")]
[ProducesResponseType(typeof(IEnumerable<GroupTraining>), StatusCodes.Status200OK)]
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,13 @@ public GroupReservationConsumer(IAnalyticsRepository repository)
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}

/// <summary>
/// Method for processing <c>GroupReservationEvent</c> object based on the event type.
/// In case of an event where a new group training was added,
/// a new group training is also registered in Analytics service. Otherwise, the
/// corresponding training to reflect the behavior in the Reservation service.
/// </summary>
/// <param name="context"><c>ConsumeContext</c> object representing the send message through the bus</param>
public async Task Consume(ConsumeContext<GroupReservationEvent> context)
{
GroupReservationEvent groupReservation = context.Message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ public IndividualReservationConsumer(IAnalyticsRepository repository)
{
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}

/// <summary>
/// Method for processing <c>IndividualReservationEvent</c> object based on the event type.
/// In case of the booking event, a new individual training is registered, otherwise, the
/// corresponding training is marked as cancelled.
/// </summary>
/// <param name="context"><c>ConsumeContext</c> object representing the send message through the bus</param>
public async Task Consume(ConsumeContext<IndividualReservationEvent> context)
{
IndividualReservationEvent individualReservation = context.Message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,12 @@ public ReviewConsumer(IAnalyticsRepository repository)
_repository = repository ?? throw new ArgumentNullException(nameof(repository));
}

/// <summary>
/// Method for processing <c>ReviewEvent</c> object based on the review type.
/// Behavior depends both on the sender of the review (i.e., trainer or client) and
/// on the type of the training that is reviewed (i.e., individual or group)
/// </summary>
/// <param name="context"><c>ConsumeContext</c> object representing the send message through the bus</param>
public async Task Consume(ConsumeContext<ReviewEvent> context)
{
ReviewEvent review = context.Message;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@

namespace ChatService.API.Controllers;

/// <summary>
/// Provides REST API endpoints for managing chat sessions and messages between clients and trainers.
/// </summary>
/// <remarks>
/// This controller exposes operations for:
/// - Creating, retrieving, and deleting chat sessions
/// - Sending and fetching messages between participants
/// - Extending chat sessions
///
/// All routes are secured and require authorization with roles <c>Admin</c>, <c>Trainer</c>, or <c>Client</c>.
/// </remarks>
[Authorize]
[ApiController]
[Route("api/v1/[controller]")]
Expand All @@ -17,6 +28,16 @@ public ChatController(IChatService chatService)
_chatService = chatService;
}

/// <summary>
/// Retrieves a summary of all chat sessions for a given user (trainer or client).
/// </summary>
/// <param name="userId">The ID of the user whose chat sessions are to be retrieved.</param>
/// <returns>
/// A list of basic session information for the specified user.
/// Returns <c>404 Not Found</c> if no sessions exist.
/// </returns>
/// <response code="200">List of chat sessions found.</response>
/// <response code="404">No chat sessions found for the user.</response>
[Authorize(Roles = "Admin, Trainer, Client")]
[HttpGet("sessions/{userId}/my-sessions-summary")]
public async Task<IActionResult> GetBasicInfoForSessions(string userId)
Expand All @@ -30,6 +51,17 @@ public async Task<IActionResult> GetBasicInfoForSessions(string userId)
return Ok(basicInfo);
}

/// <summary>
/// Adds a new message to a chat session between a trainer and a client.
/// </summary>
/// <param name="trainerId">The ID of the trainer participating in the chat session.</param>
/// <param name="clientId">The ID of the client participating in the chat session.</param>
/// <param name="content">The content of the message to be sent.</param>
/// <param name="senderType">Indicates who sent the message ("Trainer" or "Client").</param>
/// <returns>
/// Returns <c>200 OK</c> if the message was added successfully.
/// Returns <c>400 Bad Request</c> if an error occurred.
/// </returns>
[Authorize(Roles = "Trainer, Client")]
[HttpPost("sessions/messages")]
public async Task<IActionResult> AddMessageToSession([FromQuery] string trainerId, [FromQuery] string clientId, [FromBody] string content, [FromQuery] string senderType)
Expand All @@ -45,6 +77,15 @@ public async Task<IActionResult> AddMessageToSession([FromQuery] string trainerI
}
}

/// <summary>
/// Retrieves all messages exchanged between a trainer and a client in a specific chat session.
/// </summary>
/// <param name="trainerId">The ID of the trainer.</param>
/// <param name="clientId">The ID of the client.</param>
/// <returns>
/// A list of chat messages in the session.
/// Returns <c>404 Not Found</c> if the session or messages are missing.
/// </returns>
[Authorize(Roles = "Trainer, Client")]
[HttpGet("sessions/messages")]
public async Task<IActionResult> GetMessagesFromSession([FromQuery] string trainerId, [FromQuery] string clientId)
Expand All @@ -66,6 +107,15 @@ public async Task<IActionResult> GetMessagesFromSession([FromQuery] string train
return Ok(messages);
}

/// <summary>
/// Creates a new chat session between a trainer and a client.
/// </summary>
/// <param name="trainerId">The ID of the trainer.</param>
/// <param name="clientId">The ID of the client.</param>
/// <returns>
/// Returns <c>200 OK</c> if the session was created successfully.
/// Returns <c>400 Bad Request</c> if a session already exists or another error occurs.
/// </returns>
[Authorize(Roles = "Client")]
[HttpPost("sessions")]
public async Task<IActionResult> CreateChatSession([FromQuery] string trainerId, [FromQuery] string clientId)
Expand All @@ -81,6 +131,14 @@ public async Task<IActionResult> CreateChatSession([FromQuery] string trainerId,
}
}

/// <summary>
/// Retrieves an existing chat session between a trainer and a client.
/// </summary>
/// <param name="trainerId">The ID of the trainer.</param>
/// <param name="clientId">The ID of the client.</param>
/// <returns>
/// The chat session object, or <c>404 Not Found</c> if it does not exist.
/// </returns>
[Authorize(Roles = "Admin, Trainer, Client")]
[HttpGet("sessions")]
public async Task<IActionResult> GetChatSession([FromQuery] string trainerId, [FromQuery] string clientId)
Expand All @@ -89,6 +147,15 @@ public async Task<IActionResult> GetChatSession([FromQuery] string trainerId, [F
return session != null ? Ok(session) : NotFound(new { Message = "Chat session not found." });
}

/// <summary>
/// Deletes an existing chat session (admin-only operation).
/// </summary>
/// <param name="trainerId">The ID of the trainer.</param>
/// <param name="clientId">The ID of the client.</param>
/// <returns>
/// Returns <c>200 OK</c> if the session was deleted.
/// Returns <c>404 Not Found</c> if the session does not exist.
/// </returns>
[Authorize(Roles = "Admin")]
[HttpDelete("sessions")]
public async Task<IActionResult> DeleteChatSession([FromQuery] string trainerId, [FromQuery] string clientId)
Expand All @@ -98,6 +165,15 @@ public async Task<IActionResult> DeleteChatSession([FromQuery] string trainerId,
: NotFound(new { Message = "Session not found or already deleted." });
}

/// <summary>
/// Extends the duration of an existing chat session (client-only operation).
/// </summary>
/// <param name="trainerId">The ID of the trainer.</param>
/// <param name="clientId">The ID of the client.</param>
/// <returns>
/// Returns <c>204 No Content</c> if the session was extended successfully.
/// Returns <c>404 Not Found</c> if the session does not exist.
/// </returns>
[Authorize(Roles = "Client")]
[HttpPost("sessions/extend")]
public async Task<IActionResult> ExtendChatSession([FromQuery] string trainerId, [FromQuery] string clientId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,49 @@

namespace ChatService.API.Middleware;


/// <summary>
/// Middleware component responsible for handling WebSocket connections
/// for real-time communication between trainers and clients.
/// </summary>
/// <remarks>
/// This middleware intercepts requests to the <c>/ws</c> endpoint and establishes
/// a WebSocket connection if the request is valid.
/// It extracts the <c>trainerId</c> and <c>clientId</c> from the query string,
/// validates them, and delegates the handling of the connection to
/// the <see cref="WebSocketHandler"/> service.
///
/// If the request is not a WebSocket request, the middleware simply
/// forwards it to the next component in the ASP.NET Core pipeline.
/// </remarks>
public class WebSocketMiddleware
{
private readonly RequestDelegate _next;
private readonly WebSocketHandler _webSocketHandler;

/// <summary>
/// Initializes a new instance of the <see cref="WebSocketMiddleware"/> class.
/// </summary>
/// <param name="next">The next delegate in the middleware pipeline.</param>
/// <param name="webSocketHandler">The handler responsible for managing WebSocket sessions.</param>
public WebSocketMiddleware(RequestDelegate next, WebSocketHandler webSocketHandler)
{
_next = next;
_webSocketHandler = webSocketHandler;
}

/// <summary>
/// Invokes the middleware logic for handling incoming HTTP requests.
/// </summary>
/// <param name="context">The current HTTP context of the request.</param>
/// <remarks>
/// - If the request path starts with <c>/ws</c> and is a WebSocket request,
/// the connection is upgraded to a WebSocket.
/// - If <c>trainerId</c> or <c>clientId</c> are missing, a <c>400 Bad Request</c> response is returned.
/// - Otherwise, the request is passed to the next middleware in the pipeline.
/// </remarks>
/// <returns>A task that represents the asynchronous middleware operation.</returns>

public async Task Invoke(HttpContext context)
{
if (context.Request.Path.StartsWithSegments("/ws") && context.WebSockets.IsWebSocketRequest)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,15 @@ namespace ChatService.API.Publishers;

public interface INotificationPublisher
{
/// <summary>
/// Publishes a notification event to the event bus.
/// </summary>
/// <param name="title">The title of the notification.</param>
/// <param name="content">The main content of the notification message.</param>
/// <param name="type">The type of notification (e.g., Info, Warning, Error).</param>
/// <param name="email">Indicates whether an email should also be sent.</param>
/// <param name="users">Dictionary mapping user IDs to user roles/types.</param>
/// <returns>A task that represents the asynchronous publish operation.</returns>

Task PublishNotification(string title, string content, string type, bool email, IDictionary<string, string> users);
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,77 @@
using ChatService.API.Models;
using MongoDB.Bson;

/// <summary>
/// Defines the data access operations for chat sessions and messages
/// stored in MongoDB.
/// </summary>
namespace ChatService.API.Repositories
{
/// <summary>
/// Retrieves basic information about all chat sessions
/// associated with the specified user (as trainer or client).
/// </summary>
/// <param name="userId">The unique identifier of the user.</param>
/// <returns>
/// A collection of objects containing summary data for each chat session,
/// including trainer, client, expiration date, and unlock status.
/// </returns>
public interface IChatRepository
{

/// <summary>
/// Retrieves basic information about all chat sessions
/// associated with the specified user (as trainer or client).
/// </summary>
/// <param name="userId">The unique identifier of the user.</param>
/// <returns>
/// A collection of objects containing summary data for each chat session,
/// including trainer, client, expiration date, and unlock status.
/// </returns>
Task<IEnumerable<object>> GetBasicInfoForSessionsAsync(string userId);
/// <summary>
/// Retrieves the chat session between a given trainer and client.
/// </summary>
/// <param name="trainerId">Identifier of the trainer.</param>
/// <param name="clientId">Identifier of the client.</param>
/// <returns>
/// The chat session if found; otherwise, <c>null</c>.
/// </returns>
Task<ChatSession?> GetChatSessionAsync(string trainerId, string clientId);
/// <summary>
/// Inserts a new chat session document into the MongoDB collection.
/// </summary>
/// <param name="session">The chat session entity to insert.</param>
Task InsertChatSessionAsync(ChatSession session);
/// <summary>
/// Deletes the chat session for the given trainer and client.
/// </summary>
/// <param name="trainerId">Identifier of the trainer.</param>
/// <param name="clientId">Identifier of the client.</param>
/// <returns>
/// <c>true</c> if the session was successfully deleted;
/// otherwise, <c>false</c>.
/// </returns>
Task<bool> DeleteChatSessionAsync(string trainerId, string clientId);
/// <summary>
/// Extends the expiration date of a chat session by 30 days
/// and ensures it remains unlocked.
/// </summary>
/// <param name="sessionId">The unique identifier of the chat session.</param>
/// <returns>
/// <c>true</c> if the session was successfully extended;
/// otherwise, <c>false</c>.
/// </returns>
Task<bool> ExtendChatSessionAsync(string sessionId);
/// <summary>
/// Adds a new message to the message list of an existing chat session.
/// </summary>
/// <param name="sessionId">The unique identifier of the chat session.</param>
/// <param name="message">The message object to add.</param>
Task AddMessageToChatSessionAsync(string sessionId, Message message);
/// <summary>
/// Updates the unlocked status of an existing chat session.
/// </summary>
/// <param name="session">The chat session entity with updated status.</param>
Task updateChatSessionStatusAsync(ChatSession session);

}
Expand Down
Loading