Better leaving realm and chat

This commit is contained in:
LittleSheep 2025-05-18 20:31:46 +08:00
parent cf9084b8c0
commit 8d246a19ad
8 changed files with 3536 additions and 30 deletions

View File

@ -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,

View File

@ -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);

View File

@ -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();

File diff suppressed because it is too large Load Diff

View File

@ -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");
}
}
}

View File

@ -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");

View File

@ -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; }
}

View File

@ -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(
@ -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