Chat and realm members with status

This commit is contained in:
LittleSheep 2025-06-27 23:24:27 +08:00
parent 8432436fcf
commit 661b612537
3 changed files with 157 additions and 29 deletions

View File

@ -65,7 +65,7 @@ public class AccountEventService(
}; };
} }
return new Status return new Status
{ {
Attitude = StatusAttitude.Neutral, Attitude = StatusAttitude.Neutral,
IsOnline = false, IsOnline = false,
@ -75,6 +75,70 @@ public class AccountEventService(
}; };
} }
public async Task<Dictionary<Guid, Status>> GetStatuses(List<Guid> userIds)
{
var results = new Dictionary<Guid, Status>();
var cacheMissUserIds = new List<Guid>();
foreach (var userId in userIds)
{
var cacheKey = $"{StatusCacheKey}{userId}";
var cachedStatus = await cache.GetAsync<Status>(cacheKey);
if (cachedStatus != null)
{
cachedStatus.IsOnline = !cachedStatus.IsInvisible && ws.GetAccountIsConnected(userId);
results[userId] = cachedStatus;
}
else
{
cacheMissUserIds.Add(userId);
}
}
if (cacheMissUserIds.Any())
{
var now = SystemClock.Instance.GetCurrentInstant();
var statusesFromDb = await db.AccountStatuses
.Where(e => cacheMissUserIds.Contains(e.AccountId))
.Where(e => e.ClearedAt == null || e.ClearedAt > now)
.GroupBy(e => e.AccountId)
.Select(g => g.OrderByDescending(e => e.CreatedAt).First())
.ToListAsync();
var foundUserIds = new HashSet<Guid>();
foreach (var status in statusesFromDb)
{
var isOnline = ws.GetAccountIsConnected(status.AccountId);
status.IsOnline = !status.IsInvisible && isOnline;
results[status.AccountId] = status;
var cacheKey = $"{StatusCacheKey}{status.AccountId}";
await cache.SetAsync(cacheKey, status, TimeSpan.FromMinutes(5));
foundUserIds.Add(status.AccountId);
}
var usersWithoutStatus = cacheMissUserIds.Except(foundUserIds).ToList();
if (usersWithoutStatus.Any())
{
foreach (var userId in usersWithoutStatus)
{
var isOnline = ws.GetAccountIsConnected(userId);
var defaultStatus = new Status
{
Attitude = StatusAttitude.Neutral,
IsOnline = isOnline,
IsCustomized = false,
Label = isOnline ? "Online" : "Offline",
AccountId = userId,
};
results[userId] = defaultStatus;
}
}
}
return results;
}
public async Task<Status> CreateStatus(Account user, Status status) public async Task<Status> CreateStatus(Account user, Status status)
{ {
var now = SystemClock.Instance.GetCurrentInstant(); var now = SystemClock.Instance.GetCurrentInstant();

View File

@ -22,7 +22,8 @@ public class ChatRoomController(
ActionLogService als, ActionLogService als,
NotificationService nty, NotificationService nty,
RelationshipService rels, RelationshipService rels,
IStringLocalizer<NotificationResource> localizer IStringLocalizer<NotificationResource> localizer,
AccountEventService aes
) : ControllerBase ) : ControllerBase
{ {
[HttpGet("{id:guid}")] [HttpGet("{id:guid}")]
@ -149,7 +150,7 @@ public class ChatRoomController(
public class ChatRoomRequest public class ChatRoomRequest
{ {
[Required][MaxLength(1024)] public string? Name { get; set; } [Required] [MaxLength(1024)] public string? Name { get; set; }
[MaxLength(4096)] public string? Description { get; set; } [MaxLength(4096)] public string? Description { get; set; }
[MaxLength(32)] public string? PictureId { get; set; } [MaxLength(32)] public string? PictureId { get; set; }
[MaxLength(32)] public string? BackgroundId { get; set; } [MaxLength(32)] public string? BackgroundId { get; set; }
@ -384,7 +385,7 @@ public class ChatRoomController(
[HttpGet("{roomId:guid}/members")] [HttpGet("{roomId:guid}/members")]
public async Task<ActionResult<List<ChatMember>>> ListMembers(Guid roomId, [FromQuery] int take = 20, public async Task<ActionResult<List<ChatMember>>> ListMembers(Guid roomId, [FromQuery] int take = 20,
[FromQuery] int skip = 0) [FromQuery] int skip = 0, [FromQuery] bool withStatus = false, [FromQuery] string? status = null)
{ {
var currentUser = HttpContext.Items["CurrentUser"] as Account.Account; var currentUser = HttpContext.Items["CurrentUser"] as Account.Account;
@ -400,24 +401,54 @@ public class ChatRoomController(
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.");
} }
var query = db.ChatMembers IQueryable<ChatMember> query = db.ChatMembers
.Where(m => m.ChatRoomId == roomId) .Where(m => m.ChatRoomId == roomId)
.Where(m => m.LeaveAt == null) // Add this condition to exclude left members .Where(m => m.LeaveAt == null) // Add this condition to exclude left members
.Include(m => m.Account) .Include(m => m.Account)
.Include(m => m.Account.Profile); .Include(m => m.Account.Profile);
var total = await query.CountAsync(); if (withStatus)
Response.Headers.Append("X-Total", total.ToString()); {
var members = await query
.OrderBy(m => m.JoinedAt)
.ToListAsync();
var members = await query var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList());
.OrderBy(m => m.JoinedAt)
.Skip(skip)
.Take(take)
.ToListAsync();
return Ok(members); if (!string.IsNullOrEmpty(status))
{
members = members.Where(m =>
memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null &&
s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList();
}
members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline)
.ToList();
var total = members.Count;
Response.Headers.Append("X-Total", total.ToString());
var result = members.Skip(skip).Take(take).ToList();
return Ok(result);
}
else
{
var total = await query.CountAsync();
Response.Headers.Append("X-Total", total.ToString());
var members = await query
.OrderBy(m => m.JoinedAt)
.Skip(skip)
.Take(take)
.ToListAsync();
return Ok(members);
}
} }
public class ChatMemberRequest public class ChatMemberRequest
{ {
[Required] public Guid RelatedUserId { get; set; } [Required] public Guid RelatedUserId { get; set; }

View File

@ -15,7 +15,8 @@ public class RealmController(
RealmService rs, RealmService rs,
FileReferenceService fileRefService, FileReferenceService fileRefService,
RelationshipService rels, RelationshipService rels,
ActionLogService als ActionLogService als,
AccountEventService aes
) : Controller ) : Controller
{ {
[HttpGet("{slug}")] [HttpGet("{slug}")]
@ -179,7 +180,9 @@ public class RealmController(
public async Task<ActionResult<List<RealmMember>>> ListMembers( public async Task<ActionResult<List<RealmMember>>> ListMembers(
string slug, string slug,
[FromQuery] int offset = 0, [FromQuery] int offset = 0,
[FromQuery] int take = 20 [FromQuery] int take = 20,
[FromQuery] bool withStatus = false,
[FromQuery] string? status = null
) )
{ {
var realm = await db.Realms var realm = await db.Realms
@ -194,24 +197,54 @@ public class RealmController(
return StatusCode(403, "You must be a member to view this realm's members."); return StatusCode(403, "You must be a member to view this realm's members.");
} }
var query = db.RealmMembers IQueryable<RealmMember> query = db.RealmMembers
.Where(m => m.RealmId == realm.Id) .Where(m => m.RealmId == realm.Id)
.Where(m => m.LeaveAt == null); .Where(m => m.LeaveAt == null)
var total = await query.CountAsync();
Response.Headers["X-Total"] = total.ToString();
var members = await query
.OrderBy(m => m.CreatedAt)
.Skip(offset)
.Take(take)
.Include(m => m.Account) .Include(m => m.Account)
.Include(m => m.Account.Profile) .Include(m => m.Account.Profile);
.ToListAsync();
return Ok(members); if (withStatus)
{
var members = await query
.OrderBy(m => m.CreatedAt)
.ToListAsync();
var memberStatuses = await aes.GetStatuses(members.Select(m => m.AccountId).ToList());
if (!string.IsNullOrEmpty(status))
{
members = members.Where(m =>
memberStatuses.TryGetValue(m.AccountId, out var s) && s.Label != null &&
s.Label.Equals(status, StringComparison.OrdinalIgnoreCase)).ToList();
}
members = members.OrderByDescending(m => memberStatuses.TryGetValue(m.AccountId, out var s) && s.IsOnline)
.ToList();
var total = members.Count;
Response.Headers["X-Total"] = total.ToString();
var result = members.Skip(offset).Take(take).ToList();
return Ok(result);
}
else
{
var total = await query.CountAsync();
Response.Headers["X-Total"] = total.ToString();
var members = await query
.OrderBy(m => m.CreatedAt)
.Skip(offset)
.Take(take)
.ToListAsync();
return Ok(members);
}
} }
[HttpGet("{slug}/members/me")] [HttpGet("{slug}/members/me")]
[Authorize] [Authorize]
public async Task<ActionResult<RealmMember>> GetCurrentIdentity(string slug) public async Task<ActionResult<RealmMember>> GetCurrentIdentity(string slug)