Skip to content

Commit

Permalink
Merge pull request #232 from SaintAngeLs/notifications_service
Browse files Browse the repository at this point in the history
(#126) (#125) (#229) inviting friends to events implementation
  • Loading branch information
SaintAngeLs committed May 31, 2024
2 parents 7cfa8e8 + 14e4d17 commit d050fe9
Show file tree
Hide file tree
Showing 12 changed files with 274 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
using MiniSpace.Services.Email.Application.Exceptions;
using MiniSpace.Services.Email.Core.Repositories;
using MiniSpace.Services.Email.Application.Dto;
using Microsoft.Extensions.Logging;

namespace MiniSpace.Services.Email.Application.Events.External.Handlers
{
Expand All @@ -18,24 +19,26 @@ public class NotificationCreatedHandler : IEventHandler<NotificationCreated>
private readonly IEmailService _emailService;
private readonly IMessageBroker _messageBroker;
private readonly IStudentEmailsRepository _studentEmailsRepository;
private readonly ILogger<NotificationCreatedHandler> _logger;

public NotificationCreatedHandler(
IStudentsServiceClient studentsServiceClient,
IMessageBroker messageBroker,
IEmailService emailService,
IStudentEmailsRepository studentEmailsRepository)
IStudentEmailsRepository studentEmailsRepository,
ILogger<NotificationCreatedHandler> logger)
{
_studentsServiceClient = studentsServiceClient;
_messageBroker = messageBroker;
_emailService = emailService;
_studentEmailsRepository = studentEmailsRepository;
_logger = logger;
}

public async Task HandleAsync(NotificationCreated @event, CancellationToken cancellationToken)
{
Console.WriteLine("*********************************************************************");
string jsonEvent = JsonSerializer.Serialize(@event);
Console.WriteLine($"Received Event: {jsonEvent}");
_logger.LogInformation($"Received Event: {jsonEvent}");


var student = await _studentsServiceClient.GetAsync(@event.UserId);
Expand All @@ -62,7 +65,7 @@ public async Task HandleAsync(NotificationCreated @event, CancellationToken canc
EmailNotificationStatus.Pending
);
await _emailService.SendEmailAsync(student.Email, subject, htmlContent);
Console.WriteLine($"Email sent to {student.Email}");
_logger.LogInformation($"Email sent to {student.Email}");

emailNotification.MarkAsSent();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum NotificationEventType
EventReminder,
PasswordResetRequest,
UserSignUp,
NewEventInvitaion,
Other
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@ public class CreateNotification : ICommand
public Guid NotificationId { get; }
public Guid UserId { get; }
public string Message { get; }

public CreateNotification(Guid notificationId, Guid userId, string message)
public IEnumerable<Guid> StudentIds { get; }
public Guid EventId { get; }
public CreateNotification(Guid notificationId, Guid userId, string message, IEnumerable<Guid> studentIds, Guid eventId)
{
NotificationId = notificationId;
UserId = userId;
Message = message;
StudentIds = studentIds;
EventId = eventId;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,39 +2,82 @@
using MiniSpace.Services.Notifications.Core.Repositories;
using MiniSpace.Services.Notifications.Core.Entities;
using MiniSpace.Services.Notifications.Application.Services;
using MiniSpace.Services.Notifications.Application.Services.Clients;
using MiniSpace.Services.Notifications.Application.Dto;
using MiniSpace.Services.Notifications.Application.Events.External;
using System;
using System.Text.Json;

namespace MiniSpace.Services.Notifications.Application.Commands.Handlers
{
public class CreateNotificationHandler : ICommandHandler<CreateNotification>
{
private readonly INotificationRepository _notificationRepository;
private readonly IEventMapper _eventMapper;
private readonly IStudentNotificationsRepository _studentNotificationsRepository;
private readonly IFriendsServiceClient _friendsServiceClient;
private readonly IEventsServiceClient _eventsServiceClient;
private readonly IMessageBroker _messageBroker;

public CreateNotificationHandler(INotificationRepository notificationRepository, IEventMapper eventMapper, IMessageBroker messageBroker)
public CreateNotificationHandler(
IStudentNotificationsRepository studentNotificationsRepository,
IFriendsServiceClient friendsServiceClient,
IEventsServiceClient eventsServiceClient,
IMessageBroker messageBroker
)
{
_notificationRepository = notificationRepository;
_eventMapper = eventMapper;
_studentNotificationsRepository = studentNotificationsRepository;
_friendsServiceClient = friendsServiceClient;
_eventsServiceClient = eventsServiceClient;
_messageBroker = messageBroker;
}

public async Task HandleAsync(CreateNotification command, CancellationToken cancellationToken = default)
{
var notification = new Notification(
command.NotificationId,
command.UserId,
command.Message,
NotificationStatus.Unread,
DateTime.UtcNow,
null,
NotificationEventType.Other,
command.UserId

);
await _notificationRepository.AddAsync(notification);

var events = _eventMapper.MapAll(notification.Events);
await _messageBroker.PublishAsync(events.ToArray());
var eventDetails = await _eventsServiceClient.GetEventAsync(command.EventId);
var eventLink = $"https://minispace.itsharppro.com/events/{eventDetails.Id}";

foreach (var userId in command.StudentIds)
{
var notificationNotDetailed = $"<p>You have been invited to the event '{eventDetails.Name}' " +
$"Learn more: <a href='{eventLink}'>Click here</a>.</p>";
var notificationMessage = $"<p>You have been invited to the event '{eventDetails.Name}' organized by {eventDetails.Organizer.Name}. " +
$"This event will take place from {eventDetails.StartDate:yyyy-MM-dd} to {eventDetails.EndDate:yyyy-MM-dd} at {eventDetails.Location.Street}, {eventDetails.Location.City}. " +
$"The event has a capacity of {eventDetails.Capacity} and a registration fee of ${eventDetails.Fee}. " +
$"Learn more: <a href='{eventLink}'>Click here</a>.</p>";

var notification = new Notification(
command.NotificationId,
userId,
notificationMessage,
NotificationStatus.Unread,
DateTime.UtcNow,
null,
NotificationEventType.NewEventInvitaion,
eventDetails.Id,
eventDetails?.Name
);

var studentNotifications = await _studentNotificationsRepository.GetByStudentIdAsync(userId);
if (studentNotifications == null)
{
studentNotifications = new StudentNotifications(userId);
}

studentNotifications.AddNotification(notification);

var notificationCreatedEvent = new NotificationCreated(
notificationId: notification.NotificationId,
userId: userId,
message: notificationNotDetailed,
createdAt: notification.CreatedAt,
eventType: notification.EventType.ToString(),
relatedEntityId: eventDetails.Id,
details: notificationMessage
);

await _messageBroker.PublishAsync(notificationCreatedEvent);

await _studentNotificationsRepository.UpdateAsync(studentNotifications);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum NotificationEventType
EventReminder,
PasswordResetRequest,
UserSignUp,
NewEventInvitaion,
Other
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,6 @@ public interface INotificationsService
Task UpdateNotificationStatusAsync(Guid notificationId, bool isActive);
Task DeleteNotificationAsync(Guid userId, Guid notificationId);
Task<NotificationDto> GetNotificationByIdAsync(Guid userId, Guid notificationId);
Task CreateNotificationAsync(NotificationToUsersDto notification);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -85,5 +85,13 @@ public async Task<PaginatedResponseDto<NotificationDto>> GetNotificationsByUserA

return response;
}
public async Task CreateNotificationAsync(NotificationToUsersDto notification)
{
string accessToken = await _identityService.GetAccessTokenAsync();
_httpClient.SetAccessToken(accessToken);
var url = "notifications";
await _httpClient.PostAsync<NotificationToUsersDto, HttpResponse<NotificationToUsersDto>>(url, notification);
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum NotificationEventType
EventReminder,
PasswordResetRequest,
UserSignUp,
NewEventInvitaion,
Other
}
}
10 changes: 10 additions & 0 deletions MiniSpace.Web/src/MiniSpace.Web/DTO/InvitationModel.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
using System;
using System.Collections.Generic;

namespace MiniSpace.Web.DTO
{
public class InvitationModel
{
public List<Guid> SelectedFriendIds { get; set; } = new List<Guid>();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

namespace MiniSpace.Web.DTO.Notifications
{
public class NotificationToUsersDto
{
public Guid NotificationId { get; set; }
public Guid UserId { get; set; }
public string Message { get; set; }
public IEnumerable<Guid> StudentIds { get; set; }
public Guid EventId { get; set; }
public NotificationToUsersDto() { }

public NotificationToUsersDto(Guid notificationId, Guid userId, string message, IEnumerable<Guid> studentIds, Guid eventId)
{
NotificationId = notificationId;
UserId = userId;
Message = message;
StudentIds = studentIds;
EventId = eventId;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
@page "/invite-friends/{eventId}"
@using MiniSpace.Web.Areas.Events
@using MiniSpace.Web.Areas.Friends
@using MiniSpace.Web.Areas.MediaFiles
@using MiniSpace.Web.Areas.Notifications
@using MiniSpace.Web.DTO;
@using MiniSpace.Web.DTO.Notifications;
@using Radzen
@inject IEventsService EventsService
@inject IFriendsService FriendsService
@inject IIdentityService IdentityService
@inject NavigationManager NavigationManager
@inject IMediaFilesService MediaFilesService
@inject INotificationsService NotificationService

<div class="rz-p-0 rz-p-md-12">
<RadzenStack Orientation="Orientation.Horizontal" AlignItems="AlignItems.Center" Gap="0.5rem" Class="rz-p-4 rz-mb-6 rz-border-radius-1" Style="border: var(--rz-grid-cell-border);">
<RadzenCheckBox @bind-Value="selectAll" Change="@((bool value) => ToggleSelectAll(value))" Text="Select All" />
<RadzenButton Text="Send Invitations" ButtonType="ButtonType.Submit" Click="@SendInvitations" Class="rz-ml-auto"/>
</RadzenStack>

@foreach (var friend in friends)
{
<RadzenCard Class="rz-my-3 rz-mx-auto" Style="max-width: 420px">
<RadzenStack Orientation="Orientation.Horizontal" JustifyContent="JustifyContent.Start" Gap="1rem" Class="rz-p-4">
<RadzenImage Path=@GetImage(friend.FriendId) Style="width: 100px; height: 100px; object-fit: cover; border-radius: 50%;" />
<RadzenStack Gap="0">
<RadzenText TextStyle="TextStyle.Overline" class="rz-display-flex rz-mt-2 rz-my-0">Friend</RadzenText>
<RadzenText TextStyle="TextStyle.Body1"><b>@($"{friend.StudentDetails.FirstName} {friend.StudentDetails.LastName}")</b></RadzenText>
<RadzenCheckBox Value="@IsFriendSelected(friend.FriendId)"
Change="@((bool value) => SetFriendSelected(friend.FriendId, value))" />
</RadzenStack>
</RadzenStack>
</RadzenCard>
}
</div>

@code {
[Parameter]
public Guid EventId { get; set; }
private List<FriendDto> friends = new List<FriendDto>();
private Dictionary<Guid, string> images = new Dictionary<Guid, string>();
private List<Guid> selectedFriends = new List<Guid>();
private bool selectAll = false;

protected override async Task OnInitializedAsync()
{
await LoadFriends();
}

private async Task LoadFriends()
{
Guid currentUserId = IdentityService.GetCurrentUserId();
var friendsResult = await FriendsService.GetAllFriendsAsync(currentUserId);
friends = friendsResult?.ToList() ?? new List<FriendDto>();
await LoadImages();
}

private async Task LoadImages()
{
foreach (var friend in friends)
{
var result = await MediaFilesService.GetFileAsync(friend.StudentDetails.ProfileImage);
images[friend.FriendId] = result?.Base64Content ?? "images/user_default.png";
}
}

private string GetImage(Guid friendId)
{
return images.TryGetValue(friendId, out var image) ? $"data:image/webp;base64,{image}" : "images/user_default.png";
}

private void ToggleSelectAll(bool isSelected)
{
selectAll = isSelected;
if (isSelected)
{
selectedFriends = friends.Select(f => f.FriendId).ToList();
}
else
{
selectedFriends.Clear();
}
}

private bool IsFriendSelected(Guid friendId)
{
return selectedFriends.Contains(friendId);
}

private void SetFriendSelected(Guid friendId, bool isSelected)
{
if (isSelected && !selectedFriends.Contains(friendId))
{
selectedFriends.Add(friendId);
}
else if (!isSelected && selectedFriends.Contains(friendId))
{
selectedFriends.Remove(friendId);
}
}

private async Task SendInvitations()
{
var notification = new NotificationToUsersDto
{
NotificationId = Guid.NewGuid(),
UserId = IdentityService.GetCurrentUserId(),
StudentIds = selectedFriends,
Message = "You have been invited to an event!",
EventId = EventId
};

await NotificationService.CreateNotificationAsync(notification);
NavigationManager.NavigateTo($"/events/{EventId}");
}
}
Loading

0 comments on commit d050fe9

Please sign in to comment.