-
-
Notifications
You must be signed in to change notification settings - Fork 1
Feature/group invite display #485
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
13 commits
Select commit
Hold shift + click to select a range
7332ac6
improve group invite flow and display
NielsPilgaard 4e20b42
add signalr notifs
NielsPilgaard df53474
fix review comments
NielsPilgaard 3df3f51
Update GroupServiceTests.cs
NielsPilgaard 399f502
fix facebook+cloud icon colors
NielsPilgaard 8579529
fix cookie banner colors
NielsPilgaard db07186
allow leaving groups
NielsPilgaard ace35f0
revamp signalr approach to be sturdier
NielsPilgaard 50f27c1
finish group membership signal r impl
NielsPilgaard b34738a
resolve signalr / db issue
NielsPilgaard 4b80e25
Update GroupService.cs
NielsPilgaard 3924be2
Update CookieBanner.razor
NielsPilgaard 63373ec
Update GroupServiceTests.cs
NielsPilgaard File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
12 changes: 12 additions & 0 deletions
12
src/shared/Jordnaer.Shared/Groups/Events/GroupPostCreated.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,12 @@ | ||
| namespace Jordnaer.Shared; | ||
|
|
||
| public class GroupPostCreated | ||
| { | ||
| public required Guid PostId { get; init; } | ||
| public required Guid GroupId { get; init; } | ||
| public required string GroupName { get; init; } | ||
| public required string AuthorId { get; init; } | ||
| public required string AuthorDisplayName { get; init; } | ||
| public required string PostText { get; init; } | ||
| public DateTimeOffset CreatedUtc { get; init; } = DateTimeOffset.UtcNow; | ||
| } |
7 changes: 7 additions & 0 deletions
7
src/shared/Jordnaer.Shared/Groups/GroupMembershipStatusChanged.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| namespace Jordnaer.Shared; | ||
|
|
||
| public class GroupMembershipStatusChanged | ||
| { | ||
| public required Guid GroupId { get; init; } | ||
| public int PendingCountChange { get; init; } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,101 @@ | ||
| using Jordnaer.Database; | ||
| using Jordnaer.Features.Email; | ||
| using Jordnaer.Shared; | ||
| using MassTransit; | ||
| using Microsoft.AspNetCore.Components; | ||
| using Microsoft.EntityFrameworkCore; | ||
|
|
||
| namespace Jordnaer.Consumers; | ||
|
|
||
| public class GroupPostCreatedConsumer( | ||
| IDbContextFactory<JordnaerDbContext> contextFactory, | ||
| ILogger<GroupPostCreatedConsumer> logger, | ||
| IPublishEndpoint publishEndpoint, | ||
| NavigationManager navigationManager) : IConsumer<GroupPostCreated> | ||
| { | ||
| public async Task Consume(ConsumeContext<GroupPostCreated> consumeContext) | ||
| { | ||
| logger.LogInformation("Consuming GroupPostCreated message. PostId: {PostId}, GroupId: {GroupId}", | ||
| consumeContext.Message.PostId, consumeContext.Message.GroupId); | ||
|
|
||
| var message = consumeContext.Message; | ||
|
|
||
| try | ||
| { | ||
| await using var context = await contextFactory.CreateDbContextAsync(consumeContext.CancellationToken); | ||
|
|
||
| // Get all active members excluding the post author | ||
| var activeMembers = context.GroupMemberships | ||
| .AsNoTracking() | ||
| .Where(x => x.GroupId == message.GroupId && | ||
| x.MembershipStatus == MembershipStatus.Active && | ||
| x.UserProfileId != message.AuthorId) | ||
| .Select(x => x.UserProfileId); | ||
|
|
||
| // Get their email addresses | ||
| var emails = await context.Users | ||
| .AsNoTracking() | ||
| .Where(user => activeMembers.Any(userId => userId == user.Id) && | ||
| !string.IsNullOrEmpty(user.Email)) | ||
| .Select(user => new EmailRecipient | ||
| { | ||
| Email = user.Email!, | ||
| DisplayName = user.UserName | ||
| }) | ||
| .ToListAsync(consumeContext.CancellationToken); | ||
|
|
||
| if (emails.Count == 0) | ||
| { | ||
| logger.LogInformation("No members to notify for new post in group {GroupName}", message.GroupName); | ||
| return; | ||
| } | ||
|
|
||
| logger.LogInformation("Sending new post notification to {Count} members in group {GroupName}", | ||
| emails.Count, message.GroupName); | ||
|
|
||
| var groupUrl = $"{navigationManager.BaseUri}groups/{message.GroupName}"; | ||
| var postPreview = GetPostPreview(message.PostText); | ||
|
|
||
| var email = new SendEmail | ||
| { | ||
| Subject = $"Nyt opslag i {message.GroupName}", | ||
| HtmlContent = CreateNewPostEmailContent(message.AuthorDisplayName, postPreview, groupUrl), | ||
| Bcc = emails | ||
| }; | ||
|
|
||
| await publishEndpoint.Publish(email, consumeContext.CancellationToken); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| logger.LogError(ex, "Failed to send new post notifications for post {PostId} in group {GroupId}", | ||
| message.PostId, message.GroupId); | ||
| // Don't rethrow - we don't want email failures to break post creation | ||
| } | ||
| } | ||
|
|
||
| private static string GetPostPreview(string text) | ||
| { | ||
| // Strip HTML tags and limit to 200 characters | ||
| var plainText = System.Text.RegularExpressions.Regex.Replace(text, "<.*?>", string.Empty); | ||
| return plainText.Length <= 200 | ||
| ? plainText | ||
| : plainText.Substring(0, 200) + "..."; | ||
| } | ||
|
|
||
| private static string CreateNewPostEmailContent(string authorName, string postPreview, string groupUrl) | ||
| { | ||
| return $""" | ||
| <h4>Nyt opslag i din gruppe</h4> | ||
|
|
||
| <p><b>{authorName}</b> har oprettet et nyt opslag:</p> | ||
|
|
||
| <blockquote style="border-left: 3px solid #ccc; padding-left: 10px; color: #666;"> | ||
| {postPreview} | ||
| </blockquote> | ||
|
|
||
| <p><a href="{groupUrl}">Klik her for at se opslaget</a></p> | ||
|
|
||
| {EmailConstants.Signature} | ||
| """; | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,47 @@ | ||
| using Jordnaer.Shared; | ||
| using Microsoft.AspNetCore.Authorization; | ||
| using Microsoft.AspNetCore.SignalR; | ||
|
|
||
| namespace Jordnaer.Features.Groups; | ||
|
|
||
| public interface IGroupMembershipHub | ||
| { | ||
| Task MembershipStatusChanged(GroupMembershipStatusChanged notification); | ||
| } | ||
|
|
||
| [Authorize] | ||
| public class GroupMembershipHub(ILogger<GroupMembershipHub> logger) : Hub<IGroupMembershipHub> | ||
| { | ||
| public override async Task OnConnectedAsync() | ||
| { | ||
| logger.LogDebug("User {userId} connected to {hubName}", Context.User?.GetId(), nameof(GroupMembershipHub)); | ||
|
|
||
| await base.OnConnectedAsync(); | ||
| } | ||
|
|
||
| public override async Task OnDisconnectedAsync(Exception? exception) | ||
| { | ||
| if (exception is not null) | ||
| { | ||
| logger.LogError(exception, "User {userId} disconnected from {hubName}. " + | ||
| "Exception message: {exceptionMessage}", | ||
| Context.User?.GetId(), nameof(GroupMembershipHub), exception.Message); | ||
| } | ||
| else | ||
| { | ||
| logger.LogDebug("User {userId} disconnected from {hubName}", Context.User?.GetId(), nameof(GroupMembershipHub)); | ||
| } | ||
|
|
||
| await base.OnDisconnectedAsync(exception); | ||
| } | ||
|
|
||
| public async Task JoinAdminGroups(List<Guid> groupIds) | ||
| { | ||
| logger.LogDebug("User {userId} joining {count} admin groups", Context.User?.GetId(), groupIds.Count); | ||
|
|
||
| foreach (var groupId in groupIds) | ||
| { | ||
| await Groups.AddToGroupAsync(Context.ConnectionId, $"group-admins-{groupId}"); | ||
| } | ||
| } | ||
|
coderabbitai[bot] marked this conversation as resolved.
Outdated
|
||
| } | ||
35 changes: 35 additions & 0 deletions
35
src/web/Jordnaer/Features/Groups/GroupMembershipSignalRClient.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,35 @@ | ||
| using Jordnaer.Features.Authentication; | ||
| using Jordnaer.Shared; | ||
| using Jordnaer.SignalR; | ||
| using Microsoft.AspNetCore.Components; | ||
| using Microsoft.AspNetCore.SignalR.Client; | ||
|
|
||
| namespace Jordnaer.Features.Groups; | ||
|
|
||
| public class GroupMembershipSignalRClient( | ||
| CurrentUser currentUser, | ||
| ILogger<AuthenticatedSignalRClientBase> logger, | ||
| NavigationManager navigationManager) | ||
| : AuthenticatedSignalRClientBase(logger, currentUser, navigationManager, "/hubs/group-membership") | ||
| { | ||
| public void OnMembershipStatusChanged(Func<GroupMembershipStatusChanged, Task> action) | ||
| { | ||
| if (HubConnection is null) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| HubConnection.Remove(nameof(IGroupMembershipHub.MembershipStatusChanged)); | ||
| HubConnection.On(nameof(IGroupMembershipHub.MembershipStatusChanged), action); | ||
| } | ||
|
|
||
| public async Task JoinAdminGroupsAsync(List<Guid> groupIds) | ||
| { | ||
| if (HubConnection is null || !IsConnected) | ||
| { | ||
| return; | ||
| } | ||
|
|
||
| await HubConnection.InvokeAsync(nameof(GroupMembershipHub.JoinAdminGroups), groupIds); | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.