✨ Chat timeout APIs
🐛 Fix member listing in chat
This commit is contained in:
@@ -53,8 +53,10 @@ public enum ChatTimeoutCauseType
|
|||||||
|
|
||||||
public class ChatTimeoutCause
|
public class ChatTimeoutCause
|
||||||
{
|
{
|
||||||
|
[MaxLength(4096)] public string? Reason { get; set; } = null;
|
||||||
public ChatTimeoutCauseType Type { get; set; }
|
public ChatTimeoutCauseType Type { get; set; }
|
||||||
public Guid? SenderId { get; set; }
|
public Guid? SenderId { get; set; }
|
||||||
|
public Instant? Since { get; set; }
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SnChatMember : ModelBase
|
public class SnChatMember : ModelBase
|
||||||
|
|||||||
@@ -538,8 +538,7 @@ public class ChatRoomController(
|
|||||||
{
|
{
|
||||||
if (currentUser is null) return Unauthorized();
|
if (currentUser is null) return Unauthorized();
|
||||||
var member = await db.ChatMembers
|
var member = await db.ChatMembers
|
||||||
.Where(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id) && m.JoinedAt != null &&
|
.Where(m => m.ChatRoomId == roomId && m.AccountId == Guid.Parse(currentUser.Id) && m.LeaveAt == null)
|
||||||
m.LeaveAt == null)
|
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (member is null) return StatusCode(403, "You need to be a member to see members of private chat room.");
|
if (member is null) return StatusCode(403, "You need to be a member to see members of private chat room.");
|
||||||
}
|
}
|
||||||
@@ -630,7 +629,8 @@ public class ChatRoomController(
|
|||||||
var operatorMember = await db.ChatMembers
|
var operatorMember = await db.ChatMembers
|
||||||
.Where(p => p.AccountId == accountId && p.ChatRoomId == chatRoom.Id)
|
.Where(p => p.AccountId == accountId && p.ChatRoomId == chatRoom.Id)
|
||||||
.FirstOrDefaultAsync();
|
.FirstOrDefaultAsync();
|
||||||
if (operatorMember is null) return StatusCode(403, "You need to be a part of chat to invite member to the chat.");
|
if (operatorMember is null)
|
||||||
|
return StatusCode(403, "You need to be a part of chat to invite member to the chat.");
|
||||||
|
|
||||||
// Handle realm-owned chat rooms
|
// Handle realm-owned chat rooms
|
||||||
if (chatRoom.RealmId is not null)
|
if (chatRoom.RealmId is not null)
|
||||||
@@ -813,6 +813,128 @@ public class ChatRoomController(
|
|||||||
return Ok(targetMember);
|
return Ok(targetMember);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ChatTimeoutRequest
|
||||||
|
{
|
||||||
|
[MaxLength(4096)] public string? Reason { get; set; }
|
||||||
|
public Instant TimeoutUntil { get; set; }
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("{roomId:guid}/members/{memberId:guid}/timeout")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult> TimeoutChatMember(Guid roomId, Guid memberId, [FromBody] ChatTimeoutRequest request)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
|
||||||
|
var now = SystemClock.Instance.GetCurrentInstant();
|
||||||
|
if (now >= request.TimeoutUntil)
|
||||||
|
return BadRequest("Timeout can only until a time in the future.");
|
||||||
|
|
||||||
|
var chatRoom = await db.ChatRooms
|
||||||
|
.Where(r => r.Id == roomId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (chatRoom is null) return NotFound();
|
||||||
|
|
||||||
|
var operatorMember = await db.ChatMembers
|
||||||
|
.FirstOrDefaultAsync(m => m.AccountId == accountId && m.ChatRoomId == chatRoom.Id);
|
||||||
|
if (operatorMember is null) return BadRequest("You have not joined this chat room.");
|
||||||
|
|
||||||
|
// Check if the chat room is owned by a realm
|
||||||
|
if (chatRoom.RealmId is not null)
|
||||||
|
{
|
||||||
|
if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, accountId, [RealmMemberRole.Moderator]))
|
||||||
|
return StatusCode(403, "You need at least be a realm moderator to timeout members.");
|
||||||
|
}
|
||||||
|
else if (chatRoom.Type == ChatRoomType.DirectMessage)
|
||||||
|
return BadRequest("You cannot timeout member in a direct message.");
|
||||||
|
else if (chatRoom.AccountId != accountId)
|
||||||
|
return StatusCode(403, "You need be the owner to timeout member in the chat.");
|
||||||
|
|
||||||
|
// Find the target member
|
||||||
|
var member = await db.ChatMembers
|
||||||
|
.Where(m => m.AccountId == memberId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (member is null) return NotFound();
|
||||||
|
|
||||||
|
member.TimeoutCause = new ChatTimeoutCause
|
||||||
|
{
|
||||||
|
Reason = request.Reason,
|
||||||
|
SenderId = operatorMember.Id,
|
||||||
|
Type = ChatTimeoutCauseType.ByModerator,
|
||||||
|
Since = now
|
||||||
|
};
|
||||||
|
member.TimeoutUntil = request.TimeoutUntil;
|
||||||
|
db.Update(member);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
_ = crs.PurgeRoomMembersCache(roomId);
|
||||||
|
|
||||||
|
_ = als.CreateActionLogAsync(new CreateActionLogRequest
|
||||||
|
{
|
||||||
|
Action = "chatrooms.timeout",
|
||||||
|
Meta =
|
||||||
|
{
|
||||||
|
{ "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) },
|
||||||
|
{ "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) }
|
||||||
|
},
|
||||||
|
AccountId = currentUser.Id,
|
||||||
|
UserAgent = Request.Headers.UserAgent,
|
||||||
|
IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpDelete("{roomId:guid}/members/{memberId:guid}/timeout")]
|
||||||
|
[Authorize]
|
||||||
|
public async Task<ActionResult> RemoveChatMemberTimeout(Guid roomId, Guid memberId)
|
||||||
|
{
|
||||||
|
if (HttpContext.Items["CurrentUser"] is not Account currentUser) return Unauthorized();
|
||||||
|
var accountId = Guid.Parse(currentUser.Id);
|
||||||
|
|
||||||
|
var chatRoom = await db.ChatRooms
|
||||||
|
.Where(r => r.Id == roomId)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (chatRoom is null) return NotFound();
|
||||||
|
|
||||||
|
// Check if the chat room is owned by a realm
|
||||||
|
if (chatRoom.RealmId is not null)
|
||||||
|
{
|
||||||
|
if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, accountId, [RealmMemberRole.Moderator]))
|
||||||
|
return StatusCode(403, "You need at least be a realm moderator to remove members.");
|
||||||
|
}
|
||||||
|
else if (chatRoom.Type == ChatRoomType.DirectMessage && await crs.IsChatMember(chatRoom.Id, accountId))
|
||||||
|
return StatusCode(403, "You need be part of the DM to update the chat.");
|
||||||
|
else if (chatRoom.AccountId != accountId)
|
||||||
|
return StatusCode(403, "You need be the owner to update the chat.");
|
||||||
|
|
||||||
|
// Find the target member
|
||||||
|
var member = await db.ChatMembers
|
||||||
|
.Where(m => m.AccountId == memberId && m.ChatRoomId == roomId && m.JoinedAt != null && m.LeaveAt == null)
|
||||||
|
.FirstOrDefaultAsync();
|
||||||
|
if (member is null) return NotFound();
|
||||||
|
|
||||||
|
member.TimeoutCause = null;
|
||||||
|
member.TimeoutUntil = null;
|
||||||
|
db.Update(member);
|
||||||
|
await db.SaveChangesAsync();
|
||||||
|
_ = crs.PurgeRoomMembersCache(roomId);
|
||||||
|
|
||||||
|
_ = als.CreateActionLogAsync(new CreateActionLogRequest
|
||||||
|
{
|
||||||
|
Action = "chatrooms.timeout.remove",
|
||||||
|
Meta =
|
||||||
|
{
|
||||||
|
{ "chatroom_id", Google.Protobuf.WellKnownTypes.Value.ForString(roomId.ToString()) },
|
||||||
|
{ "account_id", Google.Protobuf.WellKnownTypes.Value.ForString(memberId.ToString()) }
|
||||||
|
},
|
||||||
|
AccountId = currentUser.Id,
|
||||||
|
UserAgent = Request.Headers.UserAgent,
|
||||||
|
IpAddress = Request.HttpContext.Connection.RemoteIpAddress?.ToString()
|
||||||
|
});
|
||||||
|
|
||||||
|
return NoContent();
|
||||||
|
}
|
||||||
|
|
||||||
[HttpDelete("{roomId:guid}/members/{memberId:guid}")]
|
[HttpDelete("{roomId:guid}/members/{memberId:guid}")]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<ActionResult> RemoveChatMember(Guid roomId, Guid memberId)
|
public async Task<ActionResult> RemoveChatMember(Guid roomId, Guid memberId)
|
||||||
|
|||||||
@@ -92,11 +92,10 @@ public class ChatRoomService(
|
|||||||
.ToList();
|
.ToList();
|
||||||
if (directRoomsId.Count == 0) return rooms;
|
if (directRoomsId.Count == 0) return rooms;
|
||||||
|
|
||||||
List<SnChatMember> members = directRoomsId.Count != 0
|
var members = directRoomsId.Count != 0
|
||||||
? await db.ChatMembers
|
? await db.ChatMembers
|
||||||
.Where(m => directRoomsId.Contains(m.ChatRoomId))
|
.Where(m => directRoomsId.Contains(m.ChatRoomId))
|
||||||
.Where(m => m.AccountId != userId)
|
.Where(m => m.AccountId != userId)
|
||||||
// Ignored the joined at condition here to keep showing userinfo when other didn't accept the invite of DM
|
|
||||||
.ToListAsync()
|
.ToListAsync()
|
||||||
: [];
|
: [];
|
||||||
members = await LoadMemberAccounts(members);
|
members = await LoadMemberAccounts(members);
|
||||||
@@ -122,7 +121,6 @@ public class ChatRoomService(
|
|||||||
if (room.Type != ChatRoomType.DirectMessage) return room;
|
if (room.Type != ChatRoomType.DirectMessage) return room;
|
||||||
var members = await db.ChatMembers
|
var members = await db.ChatMembers
|
||||||
.Where(m => m.ChatRoomId == room.Id && m.AccountId != userId)
|
.Where(m => m.ChatRoomId == room.Id && m.AccountId != userId)
|
||||||
.Where(m => m.JoinedAt != null && m.LeaveAt == null)
|
|
||||||
.ToListAsync();
|
.ToListAsync();
|
||||||
|
|
||||||
if (members.Count <= 0) return room;
|
if (members.Count <= 0) return room;
|
||||||
|
|||||||
Reference in New Issue
Block a user