Skip to content

Commit c27b0db

Browse files
committed
added a chat service to handle chat api access and caching
1 parent d95239f commit c27b0db

File tree

6 files changed

+156
-52
lines changed

6 files changed

+156
-52
lines changed
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
using Jordnaer.Client.Models;
2+
using System.Net;
3+
using Jordnaer.Shared;
4+
using Microsoft.Extensions.Caching.Memory;
5+
using MudBlazor;
6+
using Refit;
7+
8+
namespace Jordnaer.Client.Features.Chat;
9+
10+
public interface IChatService
11+
{
12+
ValueTask<List<ChatDto>> GetChats(string userId);
13+
ValueTask<List<ChatMessageDto>> GetChatMessages(Guid chatId);
14+
ValueTask StartChat(ChatDto chat);
15+
ValueTask SendMessage(ChatMessageDto message);
16+
}
17+
18+
public class ChatService : IChatService
19+
{
20+
private readonly IMemoryCache _cache;
21+
private readonly IChatClient _chatClient;
22+
private readonly ISnackbar _snackbar;
23+
24+
public ChatService(IMemoryCache cache, IChatClient chatClient, ISnackbar snackbar)
25+
{
26+
_cache = cache;
27+
_chatClient = chatClient;
28+
_snackbar = snackbar;
29+
}
30+
31+
public async ValueTask<List<ChatDto>> GetChats(string userId)
32+
{
33+
string key = $"{userId}-chats";
34+
bool cacheWasEmpty = false;
35+
var cachedChats = await _cache.GetOrCreateAsync(key, async entry =>
36+
{
37+
cacheWasEmpty = true;
38+
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(7);
39+
40+
return HandleApiResponse(await _chatClient.GetChats(userId));
41+
});
42+
43+
if (cacheWasEmpty)
44+
{
45+
return cachedChats ?? new List<ChatDto>();
46+
}
47+
48+
var newChats = HandleApiResponse(await _chatClient.GetChats(userId, cachedChats?.Count ?? 0));
49+
if (cachedChats is null)
50+
{
51+
return newChats;
52+
}
53+
54+
cachedChats.AddRange(newChats);
55+
56+
return cachedChats;
57+
}
58+
59+
public async ValueTask<List<ChatMessageDto>> GetChatMessages(Guid chatId)
60+
{
61+
string key = $"{chatId}-chatmessages";
62+
bool cacheWasEmpty = false;
63+
var cachedMessages = await _cache.GetOrCreateAsync(key, async entry =>
64+
{
65+
cacheWasEmpty = true;
66+
67+
entry.AbsoluteExpirationRelativeToNow = TimeSpan.FromDays(7);
68+
69+
return HandleApiResponse(await _chatClient.GetChatMessages(chatId));
70+
});
71+
72+
if (cacheWasEmpty)
73+
{
74+
return cachedMessages ?? new List<ChatMessageDto>();
75+
}
76+
var newMessages = HandleApiResponse(await _chatClient.GetChatMessages(chatId, cachedMessages?.Count ?? 0));
77+
if (cachedMessages is null)
78+
{
79+
return newMessages;
80+
}
81+
82+
cachedMessages.AddRange(newMessages);
83+
84+
return cachedMessages;
85+
}
86+
87+
public async ValueTask StartChat(ChatDto chat) => HandleApiResponse(await _chatClient.StartChat(chat));
88+
89+
public async ValueTask SendMessage(ChatMessageDto message) => HandleApiResponse(await _chatClient.SendMessage(message));
90+
91+
/// <summary>
92+
/// Checks the <c>StatusCode</c> of the <see cref="IApiResponse"/> and shows a popup if <c>IsSuccessStatusCode</c> is false..
93+
/// </summary>
94+
/// <typeparam name="T"></typeparam>
95+
/// <param name="response"></param>
96+
private T HandleApiResponse<T>(IApiResponse<T> response) where T : new()
97+
{
98+
switch (response.StatusCode)
99+
{
100+
case { } when response.IsSuccessStatusCode:
101+
return response.Content!;
102+
103+
case HttpStatusCode.TooManyRequests:
104+
_snackbar.Add(ErrorMessages.High_Load, Severity.Info);
105+
break;
106+
107+
default:
108+
_snackbar.Add(ErrorMessages.Something_Went_Wrong_Refresh, Severity.Warning);
109+
break;
110+
}
111+
112+
return new T();
113+
}
114+
115+
116+
/// <summary>
117+
/// Checks the <c>StatusCode</c> of the <see cref="IApiResponse"/> and shows a popup if <c>IsSuccessStatusCode</c> is false..
118+
/// </summary>
119+
/// <param name="response"></param>
120+
private void HandleApiResponse(IApiResponse response)
121+
{
122+
switch (response.StatusCode)
123+
{
124+
case { } when response.IsSuccessStatusCode:
125+
return;
126+
127+
case HttpStatusCode.TooManyRequests:
128+
_snackbar.Add(ErrorMessages.High_Load, Severity.Info);
129+
break;
130+
131+
default:
132+
_snackbar.Add(ErrorMessages.Something_Went_Wrong_Refresh, Severity.Warning);
133+
break;
134+
}
135+
}
136+
}

src/web/Client/Features/Chat/IChatClient.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ public interface IChatClient
99
Task<IApiResponse<List<ChatDto>>> GetChats(string userId, int skip = 0, int take = int.MaxValue);
1010

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

1414
[Post($"/api/chat/{MessagingConstants.StartChat}")]
1515
Task<IApiResponse> StartChat([Body] ChatDto chat);

src/web/Client/Features/Chat/LargeChatComponent.razor

Lines changed: 12 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,7 @@
1-
@using System.Globalization
2-
@using System.Linq.Expressions
3-
@using System.Security.Claims
4-
@using MassTransit
5-
@using Microsoft.VisualBasic.CompilerServices
6-
@inject IChatClient ChatClient
1+
@inject IChatService ChatService
72
@inject IProfileCache ProfileCache
8-
@inject AuthenticationStateProvider AuthenticationStateProvider
93
@inject ISnackbar Snackbar
104
@inject IJSRuntime JsRuntime
11-
@inject IScrollManager ScrollManager
125
@attribute [Authorize]
136

147
@if (_currentUser is null)
@@ -26,11 +19,6 @@
2619
</MudListSubheader>
2720
@foreach (var chat in _chats)
2821
{
29-
@* TODO
30-
- Fetch all messages now found in cache
31-
- Fetch all chats not found in cache
32-
- Virtualize chat (load at the top with scroll listener)
33-
*@
3422
<MudListItem OnClick="@(() => SelectChat(chat))" Dense="true" Class="chat-selector">
3523

3624
<MudAvatar Size="Size.Large" Class="mr-3">
@@ -104,7 +92,6 @@
10492
private bool _isActiveChatPublished = true;
10593
private bool _isLoading = true;
10694

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

121108
_currentUserSlim = _currentUser.ToUserSlim();
122109

123-
var response = await ChatClient.GetChats(_currentUser.Id);
124-
switch (response.StatusCode)
125-
{
126-
case var _ when response.IsSuccessStatusCode:
127-
_chats = response.Content!;
128-
break;
129-
130-
case HttpStatusCode.TooManyRequests:
131-
Snackbar.Add(ErrorMessages.High_Load, Severity.Info);
132-
break;
133-
134-
default:
135-
Snackbar.Add(ErrorMessages.Something_Went_Wrong_Refresh, Severity.Warning);
136-
break;
137-
}
110+
_chats = await ChatService.GetChats(_currentUser.Id);
138111

139112
_isLoading = false;
140113
}
@@ -147,23 +120,11 @@
147120
return;
148121
}
149122

150-
var response = await ChatClient.GetChatMessages(_activeChat.Id, _activeChat.Messages.Count, int.MaxValue);
151-
switch (response.StatusCode)
152-
{
153-
case var _ when response.IsSuccessStatusCode:
154-
_activeChat.Messages.AddRange(response.Content!);
155-
StateHasChanged();
156-
await ScrollToBottom();
157-
break;
158-
159-
case HttpStatusCode.TooManyRequests:
160-
Snackbar.Add(ErrorMessages.High_Load, Severity.Info);
161-
break;
162-
163-
default:
164-
Snackbar.Add(ErrorMessages.Something_Went_Wrong_Refresh, Severity.Warning);
165-
break;
166-
}
123+
_activeChat.Messages = await ChatService.GetChatMessages(_activeChat.Id);
124+
125+
StateHasChanged();
126+
127+
await ScrollToBottom();
167128
}
168129

169130
private async Task SelectChat(ChatDto chat)
@@ -172,6 +133,8 @@
172133
if (_isActiveChatPublished)
173134
{
174135
await LoadMessages();
136+
// We call this twice to ensure we're at the very bottom. Silly but easy
137+
await ScrollToBottom();
175138
}
176139
}
177140

@@ -200,13 +163,12 @@
200163

201164
if (_isActiveChatPublished)
202165
{
203-
// TODO: Error handling
204-
await ChatClient.SendMessage(message);
166+
await ChatService.SendMessage(message);
205167
}
206168
else
207169
{
208-
// TODO: Error handling
209-
await ChatClient.StartChat(_activeChat);
170+
await ChatService.StartChat(_activeChat);
171+
_isActiveChatPublished = true;
210172
}
211173

212174
await ScrollToBottom();

src/web/Client/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,8 @@
6565
builder.Services.Configure<DataForsyningenOptions>(
6666
builder.Configuration.GetSection(DataForsyningenOptions.SectionName));
6767

68+
builder.Services.AddScoped<IChatService, ChatService>();
69+
6870
var host = builder.Build();
6971

7072
await host.RunAsync();

src/web/Client/_Imports.razor

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,3 +28,7 @@
2828
@using MudExtensions
2929
@using Blazored.LocalStorage
3030
@using Blazored.SessionStorage
31+
@using System.Globalization
32+
@using System.Linq.Expressions
33+
@using System.Security.Claims
34+
@using MassTransit

src/web/Shared/Chat/ChatDto.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public class ChatDto
1212
/// </summary>
1313
public string? DisplayName { get; init; }
1414

15-
public List<ChatMessageDto> Messages { get; init; } = new();
15+
public List<ChatMessageDto> Messages { get; set; } = new();
1616
public List<UserSlim> Recipients { get; init; } = new();
1717

1818
public DateTime LastMessageSentUtc { get; init; }

0 commit comments

Comments
 (0)