Notify level API on chat

🗃️ Enrich the settings of chat members
🐛 Fix role settings on both chat & realm
This commit is contained in:
LittleSheep 2025-06-09 23:32:37 +08:00
parent 0c48694493
commit 9e17be38d8
9 changed files with 3540 additions and 46 deletions

View File

@ -41,11 +41,11 @@ public class ChatRoom : ModelBase, IIdentifiedResource
public string ResourceIdentifier => $"chatroom/{Id}";
}
public enum ChatMemberRole
public abstract class ChatMemberRole
{
Owner = 100,
Moderator = 50,
Member = 0
public const int Owner = 100;
public const int Moderator = 50;
public const int Member = 0;
}
public enum ChatMemberNotify
@ -55,6 +55,18 @@ public enum ChatMemberNotify
None
}
public enum ChatTimeoutCauseType
{
ByModerator = 0,
BySlowMode = 1,
}
public class ChatTimeoutCause
{
public ChatTimeoutCauseType Type { get; set; }
public Guid? SenderId { get; set; }
}
public class ChatMember : ModelBase
{
public Guid Id { get; set; }
@ -65,12 +77,27 @@ public class ChatMember : ModelBase
[MaxLength(1024)] public string? Nick { get; set; }
public ChatMemberRole Role { get; set; } = ChatMemberRole.Member;
public int Role { get; set; } = ChatMemberRole.Member;
public ChatMemberNotify Notify { get; set; } = ChatMemberNotify.All;
public Instant? LastReadAt { get; set; }
public Instant? JoinedAt { get; set; }
public Instant? LeaveAt { get; set; }
public bool IsBot { get; set; } = false;
/// <summary>
/// The break time is the user doesn't receive any message from this member for a while.
/// Expect mentioned him or her.
/// </summary>
public Instant? BreakUntil { get; set; }
/// <summary>
/// The timeout is the user can't send any message.
/// Set by the moderator of the chat room.
/// </summary>
public Instant? TimeoutUntil { get; set; }
/// <summary>
/// The timeout cause is the reason why the user is timeout.
/// </summary>
[Column(TypeName = "jsonb")] public ChatTimeoutCause? TimeoutCause { get; set; }
}
public class ChatMemberTransmissionObject : ModelBase
@ -82,12 +109,16 @@ public class ChatMemberTransmissionObject : ModelBase
[MaxLength(1024)] public string? Nick { get; set; }
public ChatMemberRole Role { get; set; } = ChatMemberRole.Member;
public int 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 Instant? BreakUntil { get; set; }
public Instant? TimeoutUntil { get; set; }
public ChatTimeoutCause? TimeoutCause { get; set; }
public static ChatMemberTransmissionObject FromEntity(ChatMember member)
{
return new ChatMemberTransmissionObject
@ -102,6 +133,9 @@ public class ChatMemberTransmissionObject : ModelBase
JoinedAt = member.JoinedAt,
LeaveAt = member.LeaveAt,
IsBot = member.IsBot,
BreakUntil = member.BreakUntil,
TimeoutUntil = member.TimeoutUntil,
TimeoutCause = member.TimeoutCause,
CreatedAt = member.CreatedAt,
UpdatedAt = member.UpdatedAt,
DeletedAt = member.DeletedAt

View File

@ -254,7 +254,8 @@ public class ChatRoomController(
if (picture is null) return BadRequest("Invalid picture id, unable to find the file on cloud.");
// Remove old references for pictures
var oldPictureRefs = await fileRefService.GetResourceReferencesAsync(chatRoomResourceId, "chat.room.picture");
var oldPictureRefs =
await fileRefService.GetResourceReferencesAsync(chatRoomResourceId, "chat.room.picture");
foreach (var oldRef in oldPictureRefs)
{
await fileRefService.DeleteReferenceAsync(oldRef.Id);
@ -276,7 +277,8 @@ public class ChatRoomController(
if (background is null) return BadRequest("Invalid background id, unable to find the file on cloud.");
// Remove old references for backgrounds
var oldBackgroundRefs = await fileRefService.GetResourceReferencesAsync(chatRoomResourceId, "chat.room.background");
var oldBackgroundRefs =
await fileRefService.GetResourceReferencesAsync(chatRoomResourceId, "chat.room.background");
foreach (var oldRef in oldBackgroundRefs)
{
await fileRefService.DeleteReferenceAsync(oldRef.Id);
@ -404,7 +406,7 @@ public class ChatRoomController(
public class ChatMemberRequest
{
[Required] public Guid RelatedUserId { get; set; }
[Required] public ChatMemberRole Role { get; set; }
[Required] public int Role { get; set; }
}
[HttpPost("invites/{roomId:guid}")]
@ -551,10 +553,47 @@ public class ChatRoomController(
return NoContent();
}
public class ChatMemberNotifyRequest
{
public ChatMemberNotify? NotifyLevel { get; set; }
public Instant? BreakUntil { get; set; }
}
[HttpPatch("{roomId:guid}/members/me/notify")]
[Authorize]
public async Task<ActionResult<ChatMember>> UpdateChatMemberNotify(
Guid roomId,
Guid memberId,
[FromBody] ChatMemberNotifyRequest request
)
{
if (HttpContext.Items["CurrentUser"] is not Account.Account currentUser) return Unauthorized();
var chatRoom = await db.ChatRooms
.Where(r => r.Id == roomId)
.FirstOrDefaultAsync();
if (chatRoom is null) return NotFound();
var targetMember = await db.ChatMembers
.Where(m => m.AccountId == memberId && m.ChatRoomId == roomId)
.FirstOrDefaultAsync();
if (targetMember is null) return BadRequest("You have not joined this chat room.");
if (request.NotifyLevel is not null)
targetMember.Notify = request.NotifyLevel.Value;
if (request.BreakUntil is not null)
targetMember.BreakUntil = request.BreakUntil.Value;
db.ChatMembers.Update(targetMember);
await db.SaveChangesAsync();
await crs.PurgeRoomMembersCache(roomId);
return Ok(targetMember);
}
[HttpPatch("{roomId:guid}/members/{memberId:guid}/role")]
[Authorize]
public async Task<ActionResult<ChatMember>> UpdateChatMemberRole(Guid roomId, Guid memberId,
[FromBody] ChatMemberRole newRole)
public async Task<ActionResult<ChatMember>> UpdateChatMemberRole(Guid roomId, Guid memberId, [FromBody] int 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();
@ -597,6 +636,8 @@ public class ChatRoomController(
db.ChatMembers.Update(targetMember);
await db.SaveChangesAsync();
await crs.PurgeRoomMembersCache(roomId);
als.CreateActionLogFromRequest(
ActionLogType.RealmAdjustRole,
new Dictionary<string, object>

View File

@ -121,7 +121,7 @@ public class ChatRoomService(AppDatabase db, ICacheService cache)
return room;
}
public async Task<bool> IsMemberWithRole(Guid roomId, Guid accountId, params ChatMemberRole[] requiredRoles)
public async Task<bool> IsMemberWithRole(Guid roomId, Guid accountId, params int[] requiredRoles)
{
if (requiredRoles.Length == 0)
return false;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,50 @@
using DysonNetwork.Sphere.Chat;
using Microsoft.EntityFrameworkCore.Migrations;
using NodaTime;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class EnrichChatMembers : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<Instant>(
name: "break_until",
table: "chat_members",
type: "timestamp with time zone",
nullable: true);
migrationBuilder.AddColumn<ChatTimeoutCause>(
name: "timeout_cause",
table: "chat_members",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<Instant>(
name: "timeout_until",
table: "chat_members",
type: "timestamp with time zone",
nullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "break_until",
table: "chat_members");
migrationBuilder.DropColumn(
name: "timeout_cause",
table: "chat_members");
migrationBuilder.DropColumn(
name: "timeout_until",
table: "chat_members");
}
}
}

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Sphere;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Chat;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
@ -885,6 +886,10 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("uuid")
.HasColumnName("account_id");
b.Property<Instant?>("BreakUntil")
.HasColumnType("timestamp with time zone")
.HasColumnName("break_until");
b.Property<Guid>("ChatRoomId")
.HasColumnType("uuid")
.HasColumnName("chat_room_id");
@ -926,6 +931,14 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("integer")
.HasColumnName("role");
b.Property<ChatTimeoutCause>("TimeoutCause")
.HasColumnType("jsonb")
.HasColumnName("timeout_cause");
b.Property<Instant?>("TimeoutUntil")
.HasColumnType("timestamp with time zone")
.HasColumnName("timeout_until");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");

View File

@ -36,11 +36,11 @@ public class Realm : ModelBase, IIdentifiedResource
public string ResourceIdentifier => $"realm/{Id}";
}
public enum RealmMemberRole
public abstract class RealmMemberRole
{
Owner = 100,
Moderator = 50,
Normal = 0
public const int Owner = 100;
public const int Moderator = 50;
public const int Normal = 0;
}
public class RealmMember : ModelBase
@ -50,7 +50,7 @@ public class RealmMember : ModelBase
public Guid AccountId { get; set; }
public Account.Account Account { get; set; } = null!;
public RealmMemberRole Role { get; set; } = RealmMemberRole.Normal;
public int Role { get; set; } = RealmMemberRole.Normal;
public Instant? JoinedAt { get; set; }
public Instant? LeaveAt { get; set; }
}

View File

@ -67,7 +67,7 @@ public class RealmController(
public class RealmMemberRequest
{
[Required] public Guid RelatedUserId { get; set; }
[Required] public RealmMemberRole Role { get; set; }
[Required] public int Role { get; set; }
}
[HttpPost("invites/{slug}")]
@ -516,8 +516,7 @@ public class RealmController(
[HttpPatch("{slug}/members/{memberId:guid}/role")]
[Authorize]
public async Task<ActionResult<RealmMember>> UpdateMemberRole(string slug, Guid memberId,
[FromBody] RealmMemberRole newRole)
public async Task<ActionResult<RealmMember>> UpdateMemberRole(string slug, Guid memberId, [FromBody] int 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();

View File

@ -19,7 +19,7 @@ public class RealmService(AppDatabase db, NotificationService nty, IStringLocali
);
}
public async Task<bool> IsMemberWithRole(Guid realmId, Guid accountId, params RealmMemberRole[] requiredRoles)
public async Task<bool> IsMemberWithRole(Guid realmId, Guid accountId, params int[] requiredRoles)
{
if (requiredRoles.Length == 0)
return false;