Skip to content

Commit

Permalink
added a chat service to handle chat api access and caching
Browse files Browse the repository at this point in the history
  • Loading branch information
NielsPilgaard committed Aug 18, 2023
1 parent d95239f commit c27b0db
Show file tree
Hide file tree
Showing 6 changed files with 156 additions and 52 deletions.
136 changes: 136 additions & 0 deletions src/web/Client/Features/Chat/ChatService.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
using Jordnaer.Client.Models;
using System.Net;
using Jordnaer.Shared;
using Microsoft.Extensions.Caching.Memory;
using MudBlazor;
using Refit;

namespace Jordnaer.Client.Features.Chat;

public interface IChatService
{
ValueTask<List<ChatDto>> GetChats(string userId);
ValueTask<List<ChatMessageDto>> GetChatMessages(Guid chatId);
ValueTask StartChat(ChatDto chat);
ValueTask SendMessage(ChatMessageDto message);
}

public class ChatService : IChatService
{
private readonly IMemoryCache _cache;
private readonly IChatClient _chatClient;
private readonly ISnackbar _snackbar;

public ChatService(IMemoryCache cache, IChatClient chatClient, ISnackbar snackbar)
{
_cache = cache;
_chatClient = chatClient;
_snackbar = snackbar;
}

public async ValueTask<List<ChatDto>> GetChats(string userId)
{
string key = $"{userId}-chats";
bool cacheWasEmpty = false;
var cachedChats = await _cache.GetOrCreateAsync(key, async entry =>
{
cacheWasEmpty = true;
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(7);
return HandleApiResponse(await _chatClient.GetChats(userId));
});

if (cacheWasEmpty)
{
return cachedChats ?? new List<ChatDto>();
}

var newChats = HandleApiResponse(await _chatClient.GetChats(userId, cachedChats?.Count ?? 0));
if (cachedChats is null)
{
return newChats;
}

cachedChats.AddRange(newChats);

return cachedChats;
}

public async ValueTask<List<ChatMessageDto>> GetChatMessages(Guid chatId)
{
string key = $"{chatId}-chatmessages";
bool cacheWasEmpty = false;
var cachedMessages = await _cache.GetOrCreateAsync(key, async entry =>
{
cacheWasEmpty = true;
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(7);
return HandleApiResponse(await _chatClient.GetChatMessages(chatId));
});

if (cacheWasEmpty)
{
return cachedMessages ?? new List<ChatMessageDto>();
}
var newMessages = HandleApiResponse(await _chatClient.GetChatMessages(chatId, cachedMessages?.Count ?? 0));
if (cachedMessages is null)
{
return newMessages;
}

cachedMessages.AddRange(newMessages);

return cachedMessages;
}

public async ValueTask StartChat(ChatDto chat) => HandleApiResponse(await _chatClient.StartChat(chat));

public async ValueTask SendMessage(ChatMessageDto message) => HandleApiResponse(await _chatClient.SendMessage(message));

/// <summary>
/// Checks the <c>StatusCode</c> of the <see cref="IApiResponse"/> and shows a popup if <c>IsSuccessStatusCode</c> is false..
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="response"></param>
private T HandleApiResponse<T>(IApiResponse<T> response) where T : new()
{
switch (response.StatusCode)
{
case { } when response.IsSuccessStatusCode:
return response.Content!;

case HttpStatusCode.TooManyRequests:
_snackbar.Add(ErrorMessages.High_Load, Severity.Info);
break;

default:
_snackbar.Add(ErrorMessages.Something_Went_Wrong_Refresh, Severity.Warning);
break;
}

return new T();
}


/// <summary>
/// Checks the <c>StatusCode</c> of the <see cref="IApiResponse"/> and shows a popup if <c>IsSuccessStatusCode</c> is false..
/// </summary>
/// <param name="response"></param>
private void HandleApiResponse(IApiResponse response)
{
switch (response.StatusCode)
{
case { } when response.IsSuccessStatusCode:
return;

case HttpStatusCode.TooManyRequests:
_snackbar.Add(ErrorMessages.High_Load, Severity.Info);
break;

default:
_snackbar.Add(ErrorMessages.Something_Went_Wrong_Refresh, Severity.Warning);
break;
}
}
}
2 changes: 1 addition & 1 deletion src/web/Client/Features/Chat/IChatClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ public interface IChatClient
Task<IApiResponse<List<ChatDto>>> GetChats(string userId, int skip = 0, int take = int.MaxValue);

[Get($"/api/chat/{MessagingConstants.GetChatMessages}/{{chatId}}")]
Task<IApiResponse<List<ChatMessageDto>>> GetChatMessages(Guid chatId, int skip = 0, int take = 15);
Task<IApiResponse<List<ChatMessageDto>>> GetChatMessages(Guid chatId, int skip = 0, int take = int.MaxValue);

[Post($"/api/chat/{MessagingConstants.StartChat}")]
Task<IApiResponse> StartChat([Body] ChatDto chat);
Expand Down
62 changes: 12 additions & 50 deletions src/web/Client/Features/Chat/LargeChatComponent.razor
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
@using System.Globalization
@using System.Linq.Expressions
@using System.Security.Claims
@using MassTransit
@using Microsoft.VisualBasic.CompilerServices
@inject IChatClient ChatClient
@inject IChatService ChatService
@inject IProfileCache ProfileCache
@inject AuthenticationStateProvider AuthenticationStateProvider
@inject ISnackbar Snackbar
@inject IJSRuntime JsRuntime
@inject IScrollManager ScrollManager
@attribute [Authorize]

@if (_currentUser is null)
Expand All @@ -26,11 +19,6 @@
</MudListSubheader>
@foreach (var chat in _chats)
{
@* TODO
- Fetch all messages now found in cache
- Fetch all chats not found in cache
- Virtualize chat (load at the top with scroll listener)
*@
<MudListItem OnClick="@(() => SelectChat(chat))" Dense="true" Class="chat-selector">

<MudAvatar Size="Size.Large" Class="mr-3">
Expand Down Expand Up @@ -104,7 +92,6 @@
private bool _isActiveChatPublished = true;
private bool _isLoading = true;


// TODO: Select currently displayed chat using this, when we update to dotnet 8
[SupplyParameterFromQuery]
public Guid? ChatId { get; set; }
Expand All @@ -120,21 +107,7 @@

_currentUserSlim = _currentUser.ToUserSlim();

var response = await ChatClient.GetChats(_currentUser.Id);
switch (response.StatusCode)
{
case var _ when response.IsSuccessStatusCode:
_chats = response.Content!;
break;

case HttpStatusCode.TooManyRequests:
Snackbar.Add(ErrorMessages.High_Load, Severity.Info);
break;

default:
Snackbar.Add(ErrorMessages.Something_Went_Wrong_Refresh, Severity.Warning);
break;
}
_chats = await ChatService.GetChats(_currentUser.Id);

_isLoading = false;
}
Expand All @@ -147,23 +120,11 @@
return;
}

var response = await ChatClient.GetChatMessages(_activeChat.Id, _activeChat.Messages.Count, int.MaxValue);
switch (response.StatusCode)
{
case var _ when response.IsSuccessStatusCode:
_activeChat.Messages.AddRange(response.Content!);
StateHasChanged();
await ScrollToBottom();
break;

case HttpStatusCode.TooManyRequests:
Snackbar.Add(ErrorMessages.High_Load, Severity.Info);
break;

default:
Snackbar.Add(ErrorMessages.Something_Went_Wrong_Refresh, Severity.Warning);
break;
}
_activeChat.Messages = await ChatService.GetChatMessages(_activeChat.Id);

StateHasChanged();

await ScrollToBottom();
}

private async Task SelectChat(ChatDto chat)
Expand All @@ -172,6 +133,8 @@
if (_isActiveChatPublished)
{
await LoadMessages();
// We call this twice to ensure we're at the very bottom. Silly but easy
await ScrollToBottom();
}
}

Expand Down Expand Up @@ -200,13 +163,12 @@

if (_isActiveChatPublished)
{
// TODO: Error handling
await ChatClient.SendMessage(message);
await ChatService.SendMessage(message);
}
else
{
// TODO: Error handling
await ChatClient.StartChat(_activeChat);
await ChatService.StartChat(_activeChat);
_isActiveChatPublished = true;
}

await ScrollToBottom();
Expand Down
2 changes: 2 additions & 0 deletions src/web/Client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,8 @@
builder.Services.Configure<DataForsyningenOptions>(
builder.Configuration.GetSection(DataForsyningenOptions.SectionName));

builder.Services.AddScoped<IChatService, ChatService>();

var host = builder.Build();

await host.RunAsync();
4 changes: 4 additions & 0 deletions src/web/Client/_Imports.razor
Original file line number Diff line number Diff line change
Expand Up @@ -28,3 +28,7 @@
@using MudExtensions
@using Blazored.LocalStorage
@using Blazored.SessionStorage
@using System.Globalization
@using System.Linq.Expressions
@using System.Security.Claims
@using MassTransit
2 changes: 1 addition & 1 deletion src/web/Shared/Chat/ChatDto.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ChatDto
/// </summary>
public string? DisplayName { get; init; }

public List<ChatMessageDto> Messages { get; init; } = new();
public List<ChatMessageDto> Messages { get; set; } = new();
public List<UserSlim> Recipients { get; init; } = new();

public DateTime LastMessageSentUtc { get; init; }
Expand Down

0 comments on commit c27b0db

Please sign in to comment.