diff --git a/src/shared/Jordnaer.Shared/Database/GroupMembership.cs b/src/shared/Jordnaer.Shared/Database/GroupMembership.cs
index 10cbc2de..63eeb2a1 100644
--- a/src/shared/Jordnaer.Shared/Database/GroupMembership.cs
+++ b/src/shared/Jordnaer.Shared/Database/GroupMembership.cs
@@ -2,20 +2,22 @@ namespace Jordnaer.Shared;
public class GroupMembership
{
- public required Guid GroupId { get; set; }
- public required string UserProfileId { get; set; }
+ public required Guid GroupId { get; set; }
+ public required string UserProfileId { get; set; }
- public Group Group { get; set; } = null!;
+ public Group Group { get; set; } = null!;
- ///
- /// Whether the user requested to join the group or was invited.
- ///
- public bool UserInitiatedMembership { get; set; }
+ public UserProfile UserProfile { get; set; } = null!;
- public DateTime CreatedUtc { get; set; }
- public DateTime LastUpdatedUtc { get; set; }
+ ///
+ /// Whether the user requested to join the group or was invited.
+ ///
+ public bool UserInitiatedMembership { get; set; }
- public MembershipStatus MembershipStatus { get; set; }
- public PermissionLevel PermissionLevel { get; set; } = PermissionLevel.None;
- public OwnershipLevel OwnershipLevel { get; set; } = OwnershipLevel.None;
+ public DateTime CreatedUtc { get; set; }
+ public DateTime LastUpdatedUtc { get; set; }
+
+ public MembershipStatus MembershipStatus { get; set; }
+ public PermissionLevel PermissionLevel { get; set; } = PermissionLevel.None;
+ public OwnershipLevel OwnershipLevel { get; set; } = OwnershipLevel.None;
}
diff --git a/src/web/Jordnaer/Consumers/SendMessageConsumer.cs b/src/web/Jordnaer/Consumers/SendMessageConsumer.cs
index fcda8c2e..6cef2a80 100644
--- a/src/web/Jordnaer/Consumers/SendMessageConsumer.cs
+++ b/src/web/Jordnaer/Consumers/SendMessageConsumer.cs
@@ -23,6 +23,8 @@ public SendMessageConsumer(JordnaerDbContext context, ILogger consumeContext)
{
+ _logger.LogDebug("Consuming SendMessage message. ChatId: {ChatId}", consumeContext.Message.ChatId);
+
var chatMessage = consumeContext.Message;
_context.ChatMessages.Add(
diff --git a/src/web/Jordnaer/Consumers/StartChatConsumer.cs b/src/web/Jordnaer/Consumers/StartChatConsumer.cs
index ac4ae6a0..f15af806 100644
--- a/src/web/Jordnaer/Consumers/StartChatConsumer.cs
+++ b/src/web/Jordnaer/Consumers/StartChatConsumer.cs
@@ -21,6 +21,8 @@ public StartChatConsumer(JordnaerDbContext context, ILogger l
public async Task Consume(ConsumeContext consumeContext)
{
+ _logger.LogInformation("Consuming StartChat message. ChatId: {ChatId}", consumeContext.Message.Id);
+
var chat = consumeContext.Message;
_context.Chats.Add(new Chat
diff --git a/src/web/Jordnaer/Features/GroupSearch/GroupCard.razor b/src/web/Jordnaer/Features/GroupSearch/GroupCard.razor
index 7407b449..e867d7b2 100644
--- a/src/web/Jordnaer/Features/GroupSearch/GroupCard.razor
+++ b/src/web/Jordnaer/Features/GroupSearch/GroupCard.razor
@@ -1,10 +1,10 @@
-
-
-
-
+
+
+
+
@Group.ShortDescription
diff --git a/src/web/Jordnaer/Features/Groups/GroupService.cs b/src/web/Jordnaer/Features/Groups/GroupService.cs
index 737002ec..ac9fe36d 100644
--- a/src/web/Jordnaer/Features/Groups/GroupService.cs
+++ b/src/web/Jordnaer/Features/Groups/GroupService.cs
@@ -5,6 +5,8 @@
using OneOf;
using OneOf.Types;
using Serilog;
+using System.Linq.Expressions;
+using Jordnaer.Features.Authentication;
using NotFound = OneOf.Types.NotFound;
namespace Jordnaer.Features.Groups;
@@ -17,28 +19,23 @@ public interface IGroupService
Task, NotFound>> UpdateGroupAsync(string userId, Group group, CancellationToken cancellationToken = default);
Task> DeleteGroupAsync(string userId, Guid id, CancellationToken cancellationToken = default);
Task> GetSlimGroupsForUserAsync(string userId, CancellationToken cancellationToken = default);
+
+ Task> GetGroupMembersByPredicateAsync(Expression> predicate, CancellationToken cancellationToken = default);
+ Task IsGroupMemberAsync(Guid groupId, CancellationToken cancellationToken = default);
}
-public class GroupService : IGroupService
+public class GroupService(
+ IDbContextFactory contextFactory,
+ ILogger logger,
+ IDiagnosticContext diagnosticContext,
+ CurrentUser currentUser)
+ : IGroupService
{
- private readonly IDbContextFactory _contextFactory;
- private readonly ILogger _logger;
- private readonly IDiagnosticContext _diagnosticContext;
-
- public GroupService(IDbContextFactory contextFactory,
- ILogger logger,
- IDiagnosticContext diagnosticContext)
- {
- _contextFactory = contextFactory;
- _logger = logger;
- _diagnosticContext = diagnosticContext;
- }
-
public async Task> GetGroupByIdAsync(Guid id, CancellationToken cancellationToken = default)
{
- _logger.LogFunctionBegan();
+ logger.LogFunctionBegan();
- await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken);
+ await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var group = await context.Groups
.AsNoTracking()
.FirstOrDefaultAsync(group => group.Id == id, cancellationToken: cancellationToken);
@@ -50,9 +47,9 @@ public async Task> GetGroupByIdAsync(Guid id, Cancellatio
public async Task> GetSlimGroupByNameAsync(string name, CancellationToken cancellationToken = default)
{
- _logger.LogFunctionBegan();
+ logger.LogFunctionBegan();
- await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken);
+ await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var group = await context.Groups
.AsNoTracking()
.Select(x => new GroupSlim
@@ -74,9 +71,9 @@ public async Task> GetSlimGroupByNameAsync(string nam
}
public async Task> GetSlimGroupsForUserAsync(string userId, CancellationToken cancellationToken = default)
{
- _logger.LogFunctionBegan();
+ logger.LogFunctionBegan();
- await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken);
+ await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
var groups = await context.GroupMemberships
.AsNoTracking()
.Where(membership => membership.UserProfileId == userId &&
@@ -109,11 +106,52 @@ public async Task> GetSlimGroupsForUserAsync(string userId
return groups;
}
+ public async Task> GetGroupMembersByPredicateAsync(Expression> predicate, CancellationToken cancellationToken = default)
+ {
+ logger.LogFunctionBegan();
+
+ await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
+ var members = await context.GroupMemberships
+ .AsNoTracking()
+ .Where(predicate)
+ .OrderByDescending(x => x.OwnershipLevel)
+ .ThenByDescending(x => x.PermissionLevel)
+ .Select(x => new UserSlim
+ {
+ DisplayName = x.UserProfile.DisplayName,
+ Id = x.UserProfileId,
+ ProfilePictureUrl = x.UserProfile.ProfilePictureUrl,
+ UserName = x.UserProfile.UserName
+ })
+ .ToListAsync(cancellationToken);
+
+ return members;
+ }
+
+ public async Task IsGroupMemberAsync(Guid groupId, CancellationToken cancellationToken = default)
+ {
+ logger.LogFunctionBegan();
+
+ if (currentUser.Id is null)
+ {
+ return false;
+ }
+
+ await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
+ var isGroupMember = await context.GroupMemberships
+ .AsNoTracking()
+ .AnyAsync(x => x.UserProfileId == currentUser.Id &&
+ x.GroupId == groupId,
+ cancellationToken);
+
+ return isGroupMember;
+ }
+
public async Task>> CreateGroupAsync(string userId, Group group, CancellationToken cancellationToken = default)
{
- _logger.LogFunctionBegan();
+ logger.LogFunctionBegan();
- await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken);
+ await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (await context.Groups.AsNoTracking().AnyAsync(x => x.Name == group.Name, cancellationToken))
{
return new Error($"Gruppenavnet '{group.Name}' er allerede taget.");
@@ -155,16 +193,16 @@ public async Task>> CreateGroupAsync(string userId,
context.Groups.Add(group);
await context.SaveChangesAsync(cancellationToken);
- _logger.LogInformation("{UserId} created group '{groupName}'", userId, group.Name);
+ logger.LogInformation("{UserId} created group '{groupName}'", userId, group.Name);
return new Success();
}
public async Task, NotFound>> UpdateGroupAsync(string userId, Group group, CancellationToken cancellationToken = default)
{
- _logger.LogFunctionBegan();
+ logger.LogFunctionBegan();
- await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken);
+ await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
if (await context.Groups.AsNoTracking().AnyAsync(x => x.Name == group.Name, cancellationToken))
{
return new Error($"Gruppenavnet '{group.Name}' er allerede taget.");
@@ -198,19 +236,19 @@ public async Task, NotFound>> UpdateGroupAsync(stri
public async Task> DeleteGroupAsync(string userId, Guid id, CancellationToken cancellationToken = default)
{
- _logger.LogFunctionBegan();
+ logger.LogFunctionBegan();
- _diagnosticContext.Set("group_id", id);
+ diagnosticContext.Set("group_id", id);
- await using var context = await _contextFactory.CreateDbContextAsync(cancellationToken);
- var group = await context.Groups.FindAsync(id);
+ await using var context = await contextFactory.CreateDbContextAsync(cancellationToken);
+ var group = await context.Groups.FindAsync([id], cancellationToken);
if (group is null)
{
- _logger.LogInformation("Failed to find group by id.");
+ logger.LogInformation("Failed to find group by id.");
return new NotFound();
}
- _diagnosticContext.Set("group_name", group.Name);
+ diagnosticContext.Set("group_name", group.Name);
var groupOwner = await context.GroupMemberships
.SingleOrDefaultAsync(e => e.UserProfileId == userId &&
@@ -219,13 +257,13 @@ public async Task> DeleteGroupAsync(string userI
if (groupOwner is null)
{
- _logger.LogError("Failed to delete group because it has no owner.");
+ logger.LogError("Failed to delete group because it has no owner.");
return new Error();
}
if (groupOwner.UserProfileId != userId)
{
- _logger.LogError("Failed to delete group because the request came from someone other than the owner. " +
+ logger.LogError("Failed to delete group because the request came from someone other than the owner. " +
"The deletion was requested by the user: {@UserId}", userId);
return new Error();
}
@@ -233,7 +271,7 @@ public async Task> DeleteGroupAsync(string userI
context.Groups.Remove(group);
await context.SaveChangesAsync(cancellationToken);
- _logger.LogInformation("Successfully deleted group");
+ logger.LogInformation("Successfully deleted group");
return new Success();
}
diff --git a/src/web/Jordnaer/Features/Groups/GroupSummaryCard.razor b/src/web/Jordnaer/Features/Groups/GroupSummaryCard.razor
index 2272c83d..6630ca91 100644
--- a/src/web/Jordnaer/Features/Groups/GroupSummaryCard.razor
+++ b/src/web/Jordnaer/Features/Groups/GroupSummaryCard.razor
@@ -2,7 +2,7 @@
- @UserGroupAccess.Group.Name
+ @UserGroupAccess.Group.Name
@if (UserGroupAccess.Group.ProfilePictureUrl is not null)
{
diff --git a/src/web/Jordnaer/Features/Profile/OpenChat.razor b/src/web/Jordnaer/Features/Profile/OpenChat.razor
index 220622d4..65c280a0 100644
--- a/src/web/Jordnaer/Features/Profile/OpenChat.razor
+++ b/src/web/Jordnaer/Features/Profile/OpenChat.razor
@@ -1,6 +1,9 @@
@inject IChatService ChatService
@inject NavigationManager NavigationManager
@inject IDialogService DialogService
+@inject CurrentUser CurrentUser
+
+@attribute [Authorize]
Recipients { get; set; }
+ public required IEnumerable Recipients { get; set; }
- ///
- /// This is the current user's id
- ///
[Parameter]
- public required string InitiatorId { get; set; }
+ public bool Disabled { get; set; }
[Parameter]
- public bool Disabled { get; set; }
+ public string? ChatName { get; set; }
+
+ [Parameter]
+ public string? Title { get; set; }
private bool _isMessageSent = false;
private async Task OpenOrStartChat()
{
- var getChatResponse = await ChatService.GetChatByUserIdsAsync(InitiatorId, Recipients.Select(recipient => recipient.Id).ToArray());
+ var getChatResponse = await ChatService.GetChatByUserIdsAsync(CurrentUser.Id!, Recipients.Select(recipient => recipient.Id).ToArray());
await getChatResponse.Match(chatId =>
{
NavigationManager.NavigateTo($"/chat/{chatId}");
@@ -37,10 +40,11 @@
}, async notFound =>
{
var parameters = new DialogParameters
- {
- { dialog => dialog.InitiatorId, InitiatorId },
- { dialog => dialog.Recipients, Recipients }
- };
+ {
+ { dialog => dialog.InitiatorId, CurrentUser.Id! },
+ { dialog => dialog.Recipients, Recipients },
+ { dialog => dialog.ChatName, ChatName }
+ };
var dialogReference = await DialogService.ShowAsync("Send besked", parameters);
diff --git a/src/web/Jordnaer/Features/Profile/SendMessageDialog.razor b/src/web/Jordnaer/Features/Profile/SendMessageDialog.razor
index bda9a076..1eecdd59 100644
--- a/src/web/Jordnaer/Features/Profile/SendMessageDialog.razor
+++ b/src/web/Jordnaer/Features/Profile/SendMessageDialog.razor
@@ -23,6 +23,7 @@
[CascadingParameter] MudDialogInstance MudDialog { get; set; } = null!;
[Parameter] public required string InitiatorId { get; set; }
[Parameter] public required List Recipients { get; set; }
+ [Parameter] public string? ChatName { get; set; }
private bool _startingChat = false;
private string _userText = string.Empty;
@@ -50,19 +51,23 @@
var chatId = NewId.NextGuid();
await ChatService.StartChatAsync(new StartChat
{
+ DisplayName = ChatName,
LastMessageSentUtc = DateTime.UtcNow,
StartedUtc = DateTime.UtcNow,
Id = chatId,
InitiatorId = InitiatorId,
Recipients = Recipients,
- Messages = new List {new()
- {
- ChatId = chatId,
- Id = NewId.NextGuid(),
- Text = _userText,
- SentUtc = DateTime.UtcNow,
- SenderId = InitiatorId
- }}
+ Messages =
+ [
+ new ChatMessageDto
+ {
+ ChatId = chatId,
+ Id = NewId.NextGuid(),
+ Text = _userText,
+ SentUtc = DateTime.UtcNow,
+ SenderId = InitiatorId
+ }
+ ]
});
MudDialog.Close(DialogResult.Ok(true));
diff --git a/src/web/Jordnaer/Pages/GroupSearch/GroupDetails.razor b/src/web/Jordnaer/Pages/GroupSearch/GroupDetails.razor
index d2587c60..e598441e 100644
--- a/src/web/Jordnaer/Pages/GroupSearch/GroupDetails.razor
+++ b/src/web/Jordnaer/Pages/GroupSearch/GroupDetails.razor
@@ -1,8 +1,9 @@
@page "/groups/{GroupName}"
@inject IGroupService GroupService
-@inject ISnackbar Snackbar
@inject IJSRuntime JsRuntime
+@inject IProfileCache ProfileCache
+
@@ -22,11 +23,24 @@
- @*// TODO: Request to join button*@
+
+
+
+
+
+ Anmod om Medlemskab
+
+
+
-
+
@if (_group.Categories.Length > 0)
@@ -52,8 +66,12 @@
public string? GroupName { get; set; }
private GroupSlim? _group;
+ private List _groupAdmins = [];
private bool _isLoading = true;
+ private UserProfile? _currentUser;
+ private bool _isMemberOfGroup = true;
+ private IEnumerable _recipients = [];
protected override async Task OnInitializedAsync()
{
@@ -63,8 +81,34 @@
response.Switch(
groupSlim => _group = groupSlim,
_ => { });
+
+ if (_group is null)
+ {
+ return;
+ }
+
+ _currentUser = await ProfileCache.GetProfileAsync();
+ if (_currentUser is null)
+ {
+ return;
+ }
+
+ _isMemberOfGroup = await GroupService
+ .IsGroupMemberAsync(_group.Id);
+
+ if (_isMemberOfGroup is false)
+ {
+ _groupAdmins = await GroupService
+ .GetGroupMembersByPredicateAsync(x =>
+ x.GroupId == _group.Id &&
+ x.PermissionLevel.HasFlag(PermissionLevel.Admin));
+
+ _recipients = _groupAdmins.Concat([_currentUser.ToUserSlim()]);
+ }
+
}
_isLoading = false;
+
}
}
diff --git a/src/web/Jordnaer/Pages/Groups/MyGroups.razor b/src/web/Jordnaer/Pages/Groups/MyGroups.razor
index d67aeb5c..01f15bf0 100644
--- a/src/web/Jordnaer/Pages/Groups/MyGroups.razor
+++ b/src/web/Jordnaer/Pages/Groups/MyGroups.razor
@@ -3,9 +3,11 @@
@attribute [Authorize]
@inject IGroupService GroupService
-@inject ISnackbar Snackbar
@inject CurrentUser CurrentUser
+
+
Opret gruppe
@@ -26,7 +28,7 @@
@foreach (var group in _memberOf)
{
-
+
}
diff --git a/src/web/Jordnaer/Pages/Profile/PublicProfile.razor b/src/web/Jordnaer/Pages/Profile/PublicProfile.razor
index 13c54bb2..a283cd96 100644
--- a/src/web/Jordnaer/Pages/Profile/PublicProfile.razor
+++ b/src/web/Jordnaer/Pages/Profile/PublicProfile.razor
@@ -1,10 +1,11 @@
@page "/{userName}"
-@inject ISnackbar Snackbar
@inject IProfileService ProfileService
@inject IJSRuntime JsRuntime
@inject IProfileCache ProfileCache
+@attribute [StreamRendering]
+
@if (_profile is null && _isLoading is false)
{