✨ Better leaving realm and chat
This commit is contained in:
parent
cf9084b8c0
commit
8d246a19ad
@ -64,6 +64,7 @@ public class ChatMember : ModelBase
|
||||
public ChatMemberRole Role { get; set; } = ChatMemberRole.Member;
|
||||
public ChatMemberNotify Notify { get; set; } = ChatMemberNotify.All;
|
||||
public Instant? JoinedAt { get; set; }
|
||||
public Instant? LeaveAt { get; set; }
|
||||
public bool IsBot { get; set; } = false;
|
||||
}
|
||||
|
||||
@ -79,6 +80,7 @@ public class ChatMemberTransmissionObject : ModelBase
|
||||
public ChatMemberRole Role { get; set; } = ChatMemberRole.Member;
|
||||
public ChatMemberNotify Notify { get; set; } = ChatMemberNotify.All;
|
||||
public Instant? JoinedAt { get; set; }
|
||||
public Instant? LeaveAt { get; set; }
|
||||
public bool IsBot { get; set; } = false;
|
||||
|
||||
public static ChatMemberTransmissionObject FromEntity(ChatMember member)
|
||||
@ -93,6 +95,7 @@ public class ChatMemberTransmissionObject : ModelBase
|
||||
Role = member.Role,
|
||||
Notify = member.Notify,
|
||||
JoinedAt = member.JoinedAt,
|
||||
LeaveAt = member.LeaveAt,
|
||||
IsBot = member.IsBot,
|
||||
CreatedAt = member.CreatedAt,
|
||||
UpdatedAt = member.UpdatedAt,
|
||||
|
@ -6,6 +6,7 @@ using DysonNetwork.Sphere.Permission;
|
||||
using DysonNetwork.Sphere.Realm;
|
||||
using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Chat;
|
||||
|
||||
@ -47,6 +48,7 @@ public class ChatRoomController(
|
||||
var chatRooms = await db.ChatMembers
|
||||
.Where(m => m.AccountId == userId)
|
||||
.Where(m => m.JoinedAt != null)
|
||||
.Where(m => m.LeaveAt == null)
|
||||
.Include(m => m.ChatRoom)
|
||||
.Select(m => m.ChatRoom)
|
||||
.ToListAsync();
|
||||
@ -338,6 +340,7 @@ public class ChatRoomController(
|
||||
|
||||
var query = db.ChatMembers
|
||||
.Where(m => m.ChatRoomId == roomId)
|
||||
.Where(m => m.LeaveAt == null) // Add this condition to exclude left members
|
||||
.Include(m => m.Account)
|
||||
.Include(m => m.Account.Profile);
|
||||
|
||||
@ -399,6 +402,15 @@ public class ChatRoomController(
|
||||
return StatusCode(403, "You cannot invite member with higher permission than yours.");
|
||||
}
|
||||
|
||||
// Check if a user has previously left the chat
|
||||
var hasExistingMember = await db.ChatMembers
|
||||
.Where(m => m.AccountId == request.RelatedUserId)
|
||||
.Where(m => m.ChatRoomId == roomId)
|
||||
.AnyAsync();
|
||||
|
||||
if (hasExistingMember)
|
||||
return BadRequest("This user has been joined the chat or leave cannot be invited again.");
|
||||
|
||||
var newMember = new ChatMember
|
||||
{
|
||||
AccountId = relatedUser.Id,
|
||||
@ -485,7 +497,7 @@ public class ChatRoomController(
|
||||
.FirstOrDefaultAsync();
|
||||
if (member is null) return NotFound();
|
||||
|
||||
db.ChatMembers.Remove(member);
|
||||
member.LeaveAt = SystemClock.Instance.GetCurrentInstant();
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
return NoContent();
|
||||
@ -496,6 +508,7 @@ public class ChatRoomController(
|
||||
public async Task<ActionResult<ChatMember>> UpdateChatMemberRole(Guid roomId, Guid memberId,
|
||||
[FromBody] ChatMemberRole newRole)
|
||||
{
|
||||
if (newRole >= ChatMemberRole.Owner) return BadRequest("Unable to set chat member to owner or greater role.");
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
var chatRoom = await db.ChatRooms
|
||||
@ -572,16 +585,16 @@ public class ChatRoomController(
|
||||
return StatusCode(403, "You need at least be a moderator to remove members.");
|
||||
|
||||
// Find the target member
|
||||
var targetMember = await db.ChatMembers
|
||||
var member = await db.ChatMembers
|
||||
.Where(m => m.AccountId == memberId && m.ChatRoomId == roomId)
|
||||
.FirstOrDefaultAsync();
|
||||
if (targetMember is null) return NotFound();
|
||||
if (member is null) return NotFound();
|
||||
|
||||
// Check if the current user has sufficient permissions
|
||||
if (!await crs.IsMemberWithRole(chatRoom.Id, memberId, targetMember.Role))
|
||||
if (!await crs.IsMemberWithRole(chatRoom.Id, memberId, member.Role))
|
||||
return StatusCode(403, "You cannot remove members with equal or higher roles.");
|
||||
|
||||
db.ChatMembers.Remove(targetMember);
|
||||
member.LeaveAt = SystemClock.Instance.GetCurrentInstant();
|
||||
await db.SaveChangesAsync();
|
||||
crs.PurgeRoomMembersCache(roomId);
|
||||
|
||||
@ -660,7 +673,7 @@ public class ChatRoomController(
|
||||
return BadRequest("The last owner cannot leave the chat. Transfer ownership first or delete the chat.");
|
||||
}
|
||||
|
||||
db.ChatMembers.Remove(member);
|
||||
member.LeaveAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow);
|
||||
await db.SaveChangesAsync();
|
||||
crs.PurgeRoomMembersCache(roomId);
|
||||
|
||||
|
@ -17,6 +17,7 @@ public class ChatRoomService(AppDatabase db, IMemoryCache cache)
|
||||
var members = await db.ChatMembers
|
||||
.Where(m => m.ChatRoomId == roomId)
|
||||
.Where(m => m.JoinedAt != null)
|
||||
.Where(m => m.LeaveAt == null)
|
||||
.ToListAsync();
|
||||
|
||||
var cacheOptions = new MemoryCacheEntryOptions()
|
||||
@ -44,6 +45,7 @@ public class ChatRoomService(AppDatabase db, IMemoryCache cache)
|
||||
? await db.ChatMembers
|
||||
.Where(m => directRoomsId.Contains(m.ChatRoomId))
|
||||
.Where(m => m.AccountId != userId)
|
||||
.Where(m => m.LeaveAt == null)
|
||||
.Include(m => m.Account)
|
||||
.Include(m => m.Account.Profile)
|
||||
.GroupBy(m => m.ChatRoomId)
|
||||
@ -63,6 +65,7 @@ public class ChatRoomService(AppDatabase db, IMemoryCache cache)
|
||||
if (room.Type != ChatRoomType.DirectMessage) return room;
|
||||
var members = await db.ChatMembers
|
||||
.Where(m => m.ChatRoomId == room.Id && m.AccountId != userId)
|
||||
.Where(m => m.LeaveAt == null)
|
||||
.Include(m => m.Account)
|
||||
.Include(m => m.Account.Profile)
|
||||
.ToListAsync();
|
||||
|
3426
DysonNetwork.Sphere/Migrations/20250518123138_BetterLeavingChatAndRealm.Designer.cs
generated
Normal file
3426
DysonNetwork.Sphere/Migrations/20250518123138_BetterLeavingChatAndRealm.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,50 @@
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using NodaTime;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace DysonNetwork.Sphere.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class BetterLeavingChatAndRealm : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<Instant>(
|
||||
name: "leave_at",
|
||||
table: "realm_members",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<bool>(
|
||||
name: "is_community",
|
||||
table: "chat_rooms",
|
||||
type: "boolean",
|
||||
nullable: false,
|
||||
defaultValue: false);
|
||||
|
||||
migrationBuilder.AddColumn<Instant>(
|
||||
name: "leave_at",
|
||||
table: "chat_members",
|
||||
type: "timestamp with time zone",
|
||||
nullable: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropColumn(
|
||||
name: "leave_at",
|
||||
table: "realm_members");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "is_community",
|
||||
table: "chat_rooms");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "leave_at",
|
||||
table: "chat_members");
|
||||
}
|
||||
}
|
||||
}
|
@ -922,6 +922,10 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("joined_at");
|
||||
|
||||
b.Property<Instant?>("LeaveAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("leave_at");
|
||||
|
||||
b.Property<string>("Nick")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("character varying(1024)")
|
||||
@ -976,6 +980,10 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("character varying(4096)")
|
||||
.HasColumnName("description");
|
||||
|
||||
b.Property<bool>("IsCommunity")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_community");
|
||||
|
||||
b.Property<bool>("IsPublic")
|
||||
.HasColumnType("boolean")
|
||||
.HasColumnName("is_public");
|
||||
@ -2073,6 +2081,10 @@ namespace DysonNetwork.Sphere.Migrations
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("joined_at");
|
||||
|
||||
b.Property<Instant?>("LeaveAt")
|
||||
.HasColumnType("timestamp with time zone")
|
||||
.HasColumnName("leave_at");
|
||||
|
||||
b.Property<int>("Role")
|
||||
.HasColumnType("integer")
|
||||
.HasColumnName("role");
|
||||
|
@ -47,4 +47,5 @@ public class RealmMember : ModelBase
|
||||
|
||||
public RealmMemberRole Role { get; set; } = RealmMemberRole.Normal;
|
||||
public Instant? JoinedAt { get; set; }
|
||||
public Instant? LeaveAt { get; set; }
|
||||
}
|
@ -4,6 +4,7 @@ using DysonNetwork.Sphere.Storage;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using NodaTime;
|
||||
|
||||
namespace DysonNetwork.Sphere.Realm;
|
||||
|
||||
@ -34,6 +35,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
var members = await db.RealmMembers
|
||||
.Where(m => m.AccountId == userId)
|
||||
.Where(m => m.JoinedAt != null)
|
||||
.Where(m => m.LeaveAt == null)
|
||||
.Include(e => e.Realm)
|
||||
.Select(m => m.Realm)
|
||||
.ToListAsync();
|
||||
@ -144,7 +146,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
.FirstOrDefaultAsync();
|
||||
if (member is null) return NotFound();
|
||||
|
||||
db.RealmMembers.Remove(member);
|
||||
member.LeaveAt = SystemClock.Instance.GetCurrentInstant();
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
als.CreateActionLogFromRequest(
|
||||
@ -177,7 +179,8 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
}
|
||||
|
||||
var query = db.RealmMembers
|
||||
.Where(m => m.RealmId == realm.Id);
|
||||
.Where(m => m.RealmId == realm.Id)
|
||||
.Where(m => m.LeaveAt == null);
|
||||
|
||||
var total = await query.CountAsync();
|
||||
Response.Headers["X-Total"] = total.ToString();
|
||||
@ -228,7 +231,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
if (member.Role == RealmMemberRole.Owner)
|
||||
return StatusCode(403, "Owner cannot leave their own realm.");
|
||||
|
||||
db.RealmMembers.Remove(member);
|
||||
member.LeaveAt = SystemClock.Instance.GetCurrentInstant();
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
als.CreateActionLogFromRequest(
|
||||
@ -239,7 +242,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
|
||||
return NoContent();
|
||||
}
|
||||
|
||||
|
||||
public class RealmRequest
|
||||
{
|
||||
[MaxLength(1024)] public string? Slug { get; set; }
|
||||
@ -376,21 +379,21 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
public async Task<ActionResult<RealmMember>> JoinRealm(string slug)
|
||||
{
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
|
||||
var realm = await db.Realms
|
||||
.Where(r => r.Slug == slug)
|
||||
.FirstOrDefaultAsync();
|
||||
if (realm is null) return NotFound();
|
||||
|
||||
|
||||
if (!realm.IsCommunity)
|
||||
return StatusCode(403, "Only community realms can be joined without invitation.");
|
||||
|
||||
|
||||
var existingMember = await db.RealmMembers
|
||||
.Where(m => m.AccountId == currentUser.Id && m.RealmId == realm.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (existingMember is not null)
|
||||
if (existingMember is not null)
|
||||
return BadRequest("You are already a member of this realm.");
|
||||
|
||||
|
||||
var member = new RealmMember
|
||||
{
|
||||
AccountId = currentUser.Id,
|
||||
@ -398,19 +401,19 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
Role = RealmMemberRole.Normal,
|
||||
JoinedAt = NodaTime.Instant.FromDateTimeUtc(DateTime.UtcNow)
|
||||
};
|
||||
|
||||
|
||||
db.RealmMembers.Add(member);
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
|
||||
als.CreateActionLogFromRequest(
|
||||
ActionLogType.RealmJoin,
|
||||
new Dictionary<string, object> { { "realm_id", realm.Id }, { "account_id", currentUser.Id } },
|
||||
Request
|
||||
);
|
||||
|
||||
|
||||
return Ok(member);
|
||||
}
|
||||
|
||||
|
||||
[HttpDelete("{slug}/members/{memberId:guid}")]
|
||||
[Authorize]
|
||||
public async Task<ActionResult> RemoveMember(string slug, Guid memberId)
|
||||
@ -422,21 +425,15 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
.FirstOrDefaultAsync();
|
||||
if (realm is null) return NotFound();
|
||||
|
||||
var currentMember = await db.RealmMembers
|
||||
.Where(m => m.AccountId == currentUser.Id && m.RealmId == realm.Id && m.JoinedAt != null)
|
||||
.FirstOrDefaultAsync();
|
||||
if (currentMember is null || currentMember.Role < RealmMemberRole.Moderator)
|
||||
return StatusCode(403, "You do not have permission to remove members from this realm.");
|
||||
|
||||
var memberToRemove = await db.RealmMembers
|
||||
var member = await db.RealmMembers
|
||||
.Where(m => m.AccountId == memberId && m.RealmId == realm.Id)
|
||||
.FirstOrDefaultAsync();
|
||||
if (memberToRemove is null) return NotFound();
|
||||
if (member is null) return NotFound();
|
||||
|
||||
if (memberToRemove.Role >= currentMember.Role)
|
||||
return StatusCode(403, "You cannot remove members with equal or higher roles.");
|
||||
if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Moderator, member.Role))
|
||||
return StatusCode(403, "You do not have permission to remove members from this realm.");
|
||||
|
||||
db.RealmMembers.Remove(memberToRemove);
|
||||
member.LeaveAt = SystemClock.Instance.GetCurrentInstant();
|
||||
await db.SaveChangesAsync();
|
||||
|
||||
als.CreateActionLogFromRequest(
|
||||
@ -453,6 +450,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
|
||||
public async Task<ActionResult<RealmMember>> UpdateMemberRole(string slug, Guid memberId,
|
||||
[FromBody] RealmMemberRole newRole)
|
||||
{
|
||||
if (newRole >= RealmMemberRole.Owner) return BadRequest("Unable to set realm member to owner or greater role.");
|
||||
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
|
||||
|
||||
var realm = await db.Realms
|
||||
|
Loading…
x
Reference in New Issue
Block a user