🐛 Bug fixes 💄 Optimizations

This commit is contained in:
LittleSheep 2025-05-18 20:05:15 +08:00
parent 5b9b28d77a
commit cf9084b8c0
15 changed files with 7314 additions and 60 deletions

View File

@ -32,6 +32,7 @@ public class ActionLogType
public const string RealmJoin = "realms.join";
public const string RealmLeave = "realms.leave";
public const string RealmKick = "realms.kick";
public const string RealmAdjustRole = "realms.role.edit";
public const string ChatroomCreate = "chatrooms.create";
public const string ChatroomUpdate = "chatrooms.update";
public const string ChatroomDelete = "chatrooms.delete";
@ -39,6 +40,7 @@ public class ActionLogType
public const string ChatroomJoin = "chatrooms.join";
public const string ChatroomLeave = "chatrooms.leave";
public const string ChatroomKick = "chatrooms.kick";
public const string ChatroomAdjustRole = "chatrooms.role.edit";
}
public class ActionLog : ModelBase

View File

@ -115,7 +115,7 @@ public class AppDatabase(
"chat.realtime.create",
"accounts.statuses.create",
"accounts.statuses.update",
"stickers.pack.create",
"stickers.packs.create",
"stickers.create"
}.Select(permission =>
PermissionService.NewPermissionNode("group:default", "global", permission, true))

View File

@ -18,6 +18,7 @@ public class ChatRoom : ModelBase
[MaxLength(1024)] public string? Name { get; set; }
[MaxLength(4096)] public string? Description { get; set; }
public ChatRoomType Type { get; set; }
public bool IsCommunity { get; set; }
public bool IsPublic { get; set; }
[MaxLength(32)] public string? PictureId { get; set; }

View File

@ -127,6 +127,8 @@ public class ChatRoomController(
[MaxLength(32)] public string? PictureId { get; set; }
[MaxLength(32)] public string? BackgroundId { get; set; }
public Guid? RealmId { get; set; }
public bool? IsCommunity { get; set; }
public bool? IsPublic { get; set; }
}
[HttpPost]
@ -141,6 +143,8 @@ public class ChatRoomController(
{
Name = request.Name,
Description = request.Description ?? string.Empty,
IsCommunity = request.IsCommunity ?? false,
IsPublic = request.IsPublic ?? false,
Type = ChatRoomType.Group,
Members = new List<ChatMember>
{
@ -243,6 +247,10 @@ public class ChatRoomController(
chatRoom.Name = request.Name;
if (request.Description is not null)
chatRoom.Description = request.Description;
if (request.IsCommunity is not null)
chatRoom.IsCommunity = request.IsCommunity.Value;
if (request.IsPublic is not null)
chatRoom.IsPublic = request.IsPublic.Value;
db.ChatRooms.Update(chatRoom);
await db.SaveChangesAsync();
@ -507,29 +515,34 @@ public class ChatRoomController(
}
else
{
// Check if the current user has permission to change roles
var currentMember = await db.ChatMembers
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId)
.FirstOrDefaultAsync();
if (currentMember is null || currentMember.Role < ChatMemberRole.Moderator)
return StatusCode(403, "You need at least be a moderator to change member roles.");
// Find the target member
var targetMember = await db.ChatMembers
.Where(m => m.AccountId == memberId && m.ChatRoomId == roomId)
.FirstOrDefaultAsync();
if (targetMember is null) return NotFound();
// Check if the current user has sufficient permissions
if (currentMember.Role <= targetMember.Role)
return StatusCode(403, "You cannot modify the role of members with equal or higher roles.");
if (currentMember.Role <= newRole)
return StatusCode(403, "You cannot assign a role equal to or higher than your own.");
// Check if the current user has permission to change roles
if (
!await crs.IsMemberWithRole(
chatRoom.Id,
currentUser.Id,
ChatMemberRole.Moderator,
targetMember.Role,
newRole
)
)
return StatusCode(403, "You don't have enough permission to edit the roles of members.");
targetMember.Role = newRole;
db.ChatMembers.Update(targetMember);
await db.SaveChangesAsync();
als.CreateActionLogFromRequest(
ActionLogType.RealmAdjustRole,
new Dictionary<string, object>
{ { "chatroom_id", roomId }, { "account_id", memberId }, { "new_role", newRole } },
Request
);
return Ok(targetMember);
}
@ -550,20 +563,12 @@ public class ChatRoomController(
// Check if the chat room is owned by a realm
if (chatRoom.RealmId is not null)
{
var realmMember = await db.RealmMembers
.Where(m => m.AccountId == currentUser.Id)
.Where(m => m.RealmId == chatRoom.RealmId)
.FirstOrDefaultAsync();
if (realmMember is null || realmMember.Role < RealmMemberRole.Moderator)
if (!await rs.IsMemberWithRole(chatRoom.RealmId.Value, currentUser.Id, RealmMemberRole.Moderator))
return StatusCode(403, "You need at least be a realm moderator to remove members.");
}
else
{
// Check if the current user has permission to remove members
var currentMember = await db.ChatMembers
.Where(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId)
.FirstOrDefaultAsync();
if (currentMember is null || currentMember.Role < ChatMemberRole.Moderator)
if (!await crs.IsMemberWithRole(chatRoom.Id, currentUser.Id, ChatMemberRole.Moderator))
return StatusCode(403, "You need at least be a moderator to remove members.");
// Find the target member
@ -572,8 +577,8 @@ public class ChatRoomController(
.FirstOrDefaultAsync();
if (targetMember is null) return NotFound();
// Check if current user has sufficient permissions
if (currentMember.Role <= targetMember.Role)
// Check if the current user has sufficient permissions
if (!await crs.IsMemberWithRole(chatRoom.Id, memberId, targetMember.Role))
return StatusCode(403, "You cannot remove members with equal or higher roles.");
db.ChatMembers.Remove(targetMember);
@ -602,8 +607,8 @@ public class ChatRoomController(
.Where(r => r.Id == roomId)
.FirstOrDefaultAsync();
if (chatRoom is null) return NotFound();
if (!chatRoom.IsPublic)
return StatusCode(403, "This chat room is private. You need an invitation to join.");
if (!chatRoom.IsCommunity)
return StatusCode(403, "This chat room isn't a community. You need an invitation to join.");
var existingMember = await db.ChatMembers
.FirstOrDefaultAsync(m => m.AccountId == currentUser.Id && m.ChatRoomId == roomId);

View File

@ -72,10 +72,14 @@ public class ChatRoomService(AppDatabase db, IMemoryCache cache)
return room;
}
public async Task<bool> IsMemberWithRole(Guid roomId, Guid accountId, ChatMemberRole requiredRole)
public async Task<bool> IsMemberWithRole(Guid roomId, Guid accountId, params ChatMemberRole[] requiredRoles)
{
if (requiredRoles.Length == 0)
return false;
var maxRequiredRole = requiredRoles.Max();
var member = await db.ChatMembers
.FirstOrDefaultAsync(m => m.ChatRoomId == roomId && m.AccountId == accountId);
return member?.Role >= requiredRole;
return member?.Role >= maxRequiredRole;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,244 @@
using System.Collections.Generic;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class OptimizeFileStorage : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "image_id",
table: "stickers",
type: "character varying(32)",
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(128)");
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "realms",
type: "character varying(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "realms",
type: "character varying(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "publishers",
type: "character varying(32)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "publishers",
type: "character varying(32)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "id",
table: "files",
type: "character varying(32)",
maxLength: 32,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldMaxLength: 128);
migrationBuilder.AddColumn<List<CloudFileSensitiveMark>>(
name: "sensitive_marks",
table: "files",
type: "jsonb",
nullable: true);
migrationBuilder.AddColumn<string>(
name: "storage_id",
table: "files",
type: "character varying(32)",
maxLength: 32,
nullable: true);
migrationBuilder.AddColumn<string>(
name: "storage_url",
table: "files",
type: "character varying(4096)",
maxLength: 4096,
nullable: true);
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "chat_rooms",
type: "character varying(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "chat_rooms",
type: "character varying(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "account_profiles",
type: "character varying(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "account_profiles",
type: "character varying(32)",
maxLength: 32,
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(128)",
oldNullable: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "sensitive_marks",
table: "files");
migrationBuilder.DropColumn(
name: "storage_id",
table: "files");
migrationBuilder.DropColumn(
name: "storage_url",
table: "files");
migrationBuilder.AlterColumn<string>(
name: "image_id",
table: "stickers",
type: "character varying(128)",
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(32)");
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "realms",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldMaxLength: 32,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "realms",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldMaxLength: 32,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "publishers",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "publishers",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "id",
table: "files",
type: "character varying(128)",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldMaxLength: 32);
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "chat_rooms",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldMaxLength: 32,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "chat_rooms",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldMaxLength: 32,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "picture_id",
table: "account_profiles",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldMaxLength: 32,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "background_id",
table: "account_profiles",
type: "character varying(128)",
nullable: true,
oldClrType: typeof(string),
oldType: "character varying(32)",
oldMaxLength: 32,
oldNullable: true);
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,37 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace DysonNetwork.Sphere.Migrations
{
/// <inheritdoc />
public partial class DontKnowHowToNameThing : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateIndex(
name: "ix_stickers_slug",
table: "stickers",
column: "slug");
migrationBuilder.CreateIndex(
name: "ix_sticker_packs_prefix",
table: "sticker_packs",
column: "prefix",
unique: true);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "ix_stickers_slug",
table: "stickers");
migrationBuilder.DropIndex(
name: "ix_sticker_packs_prefix",
table: "sticker_packs");
}
}
}

View File

@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Text.Json;
using DysonNetwork.Sphere;
using DysonNetwork.Sphere.Account;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
@ -11,7 +12,6 @@ using NetTopologySuite.Geometries;
using NodaTime;
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
using NpgsqlTypes;
using Point = NetTopologySuite.Geometries.Point;
#nullable disable
@ -537,7 +537,8 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnName("account_id");
b.Property<string>("BackgroundId")
.HasColumnType("character varying(128)")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("background_id");
b.Property<string>("Bio")
@ -573,7 +574,8 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnName("middle_name");
b.Property<string>("PictureId")
.HasColumnType("character varying(128)")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("picture_id");
b.Property<Instant>("UpdatedAt")
@ -957,7 +959,8 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnName("id");
b.Property<string>("BackgroundId")
.HasColumnType("character varying(128)")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("background_id");
b.Property<Instant>("CreatedAt")
@ -983,7 +986,8 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnName("name");
b.Property<string>("PictureId")
.HasColumnType("character varying(128)")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("picture_id");
b.Property<Guid?>("RealmId")
@ -1766,7 +1770,7 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnName("account_id");
b.Property<string>("BackgroundId")
.HasColumnType("character varying(128)")
.HasColumnType("character varying(32)")
.HasColumnName("background_id");
b.Property<string>("Bio")
@ -1795,7 +1799,7 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnName("nick");
b.Property<string>("PictureId")
.HasColumnType("character varying(128)")
.HasColumnType("character varying(32)")
.HasColumnName("picture_id");
b.Property<Guid?>("RealmId")
@ -1972,7 +1976,8 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnName("account_id");
b.Property<string>("BackgroundId")
.HasColumnType("character varying(128)")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("background_id");
b.Property<Instant>("CreatedAt")
@ -2004,7 +2009,8 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnName("name");
b.Property<string>("PictureId")
.HasColumnType("character varying(128)")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("picture_id");
b.Property<string>("Slug")
@ -2101,7 +2107,8 @@ namespace DysonNetwork.Sphere.Migrations
b.Property<string>("ImageId")
.IsRequired()
.HasColumnType("character varying(128)")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("image_id");
b.Property<Guid>("PackId")
@ -2127,6 +2134,9 @@ namespace DysonNetwork.Sphere.Migrations
b.HasIndex("PackId")
.HasDatabaseName("ix_stickers_pack_id");
b.HasIndex("Slug")
.HasDatabaseName("ix_stickers_slug");
b.ToTable("stickers", (string)null);
});
@ -2174,6 +2184,10 @@ namespace DysonNetwork.Sphere.Migrations
b.HasKey("Id")
.HasName("pk_sticker_packs");
b.HasIndex("Prefix")
.IsUnique()
.HasDatabaseName("ix_sticker_packs_prefix");
b.HasIndex("PublisherId")
.HasDatabaseName("ix_sticker_packs_publisher_id");
@ -2183,8 +2197,8 @@ namespace DysonNetwork.Sphere.Migrations
modelBuilder.Entity("DysonNetwork.Sphere.Storage.CloudFile", b =>
{
b.Property<string>("Id")
.HasMaxLength(128)
.HasColumnType("character varying(128)")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("id");
b.Property<Guid>("AccountId")
@ -2240,10 +2254,24 @@ namespace DysonNetwork.Sphere.Migrations
.HasColumnType("uuid")
.HasColumnName("post_id");
b.Property<List<CloudFileSensitiveMark>>("SensitiveMarks")
.HasColumnType("jsonb")
.HasColumnName("sensitive_marks");
b.Property<long>("Size")
.HasColumnType("bigint")
.HasColumnName("size");
b.Property<string>("StorageId")
.HasMaxLength(32)
.HasColumnType("character varying(32)")
.HasColumnName("storage_id");
b.Property<string>("StorageUrl")
.HasMaxLength(4096)
.HasColumnType("character varying(4096)")
.HasColumnName("storage_url");
b.Property<Instant>("UpdatedAt")
.HasColumnType("timestamp with time zone")
.HasColumnName("updated_at");

View File

@ -177,8 +177,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
}
var query = db.RealmMembers
.Where(m => m.RealmId == realm.Id)
.Where(m => m.JoinedAt != null);
.Where(m => m.RealmId == realm.Id);
var total = await query.CountAsync();
Response.Headers["X-Total"] = total.ToString();
@ -372,6 +371,118 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
return Ok(realm);
}
[HttpPost("{slug}/members/me")]
[Authorize]
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)
return BadRequest("You are already a member of this realm.");
var member = new RealmMember
{
AccountId = currentUser.Id,
RealmId = realm.Id,
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)
{
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();
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
.Where(m => m.AccountId == memberId && m.RealmId == realm.Id)
.FirstOrDefaultAsync();
if (memberToRemove is null) return NotFound();
if (memberToRemove.Role >= currentMember.Role)
return StatusCode(403, "You cannot remove members with equal or higher roles.");
db.RealmMembers.Remove(memberToRemove);
await db.SaveChangesAsync();
als.CreateActionLogFromRequest(
ActionLogType.ChatroomKick,
new Dictionary<string, object> { { "realm_id", realm.Id }, { "account_id", memberId } },
Request
);
return NoContent();
}
[HttpPatch("{slug}/members/{memberId:guid}/role")]
[Authorize]
public async Task<ActionResult<RealmMember>> UpdateMemberRole(string slug, Guid memberId,
[FromBody] RealmMemberRole newRole)
{
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();
var member = await db.RealmMembers
.Where(m => m.AccountId == memberId && m.RealmId == realm.Id)
.Include(m => m.Account)
.FirstOrDefaultAsync();
if (member is null) return NotFound();
if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Moderator, member.Role, newRole))
return StatusCode(403, "You do not have permission to update member roles in this realm.");
member.Role = newRole;
db.RealmMembers.Update(member);
await db.SaveChangesAsync();
als.CreateActionLogFromRequest(
ActionLogType.RealmAdjustRole,
new Dictionary<string, object>
{ { "realm_id", realm.Id }, { "account_id", memberId }, { "new_role", newRole } },
Request
);
return Ok(member);
}
[HttpDelete("{slug}")]
[Authorize]
public async Task<ActionResult> Delete(string slug)
@ -385,10 +496,7 @@ public class RealmController(AppDatabase db, RealmService rs, FileService fs, Ac
.FirstOrDefaultAsync();
if (realm is null) return NotFound();
var member = await db.RealmMembers
.Where(m => m.AccountId == currentUser.Id && m.RealmId == realm.Id && m.JoinedAt != null)
.FirstOrDefaultAsync();
if (member is null || member.Role < RealmMemberRole.Owner)
if (!await rs.IsMemberWithRole(realm.Id, currentUser.Id, RealmMemberRole.Owner))
return StatusCode(403, "Only the owner can delete this realm.");
db.Realms.Remove(realm);

View File

@ -11,10 +11,14 @@ public class RealmService(AppDatabase db, NotificationService nty)
$"You just got invited to join {member.Realm.Name}");
}
public async Task<bool> IsMemberWithRole(Guid realmId, Guid accountId, RealmMemberRole requiredRole)
public async Task<bool> IsMemberWithRole(Guid realmId, Guid accountId, params RealmMemberRole[] requiredRoles)
{
if (requiredRoles.Length == 0)
return false;
var maxRequiredRole = requiredRoles.Max();
var member = await db.RealmMembers
.FirstOrDefaultAsync(m => m.RealmId == realmId && m.AccountId == accountId);
return member?.Role >= requiredRole;
return member?.Role >= maxRequiredRole;
}
}

View File

@ -1,20 +1,23 @@
using System.ComponentModel.DataAnnotations;
using DysonNetwork.Sphere.Storage;
using Microsoft.EntityFrameworkCore;
namespace DysonNetwork.Sphere.Sticker;
[Index(nameof(Slug))] // The slug index shouldn't be unique, the sticker slug can be repeated across packs.
public class Sticker : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();
[MaxLength(128)] public string Slug { get; set; } = null!;
public string ImageId { get; set; } = null!;
[MaxLength(32)] public string ImageId { get; set; } = null!;
public CloudFile Image { get; set; } = null!;
public Guid PackId { get; set; }
public StickerPack Pack { get; set; } = null!;
}
[Index(nameof(Prefix), IsUnique = true)]
public class StickerPack : ModelBase
{
public Guid Id { get; set; } = Guid.NewGuid();

View File

@ -34,7 +34,7 @@ public class StickerService(AppDatabase db, FileService fs, IMemoryCache cache)
await db.SaveChangesAsync();
// Invalidate cache for this sticker
InvalidateStickerCache(sticker);
PurgeStickerCache(sticker);
return sticker;
}
@ -45,7 +45,7 @@ public class StickerService(AppDatabase db, FileService fs, IMemoryCache cache)
await fs.MarkUsageAsync(sticker.Image, -1);
// Invalidate cache for this sticker
InvalidateStickerCache(sticker);
PurgeStickerCache(sticker);
}
public async Task DeleteStickerPackAsync(StickerPack pack)
{
@ -65,7 +65,7 @@ public class StickerService(AppDatabase db, FileService fs, IMemoryCache cache)
// Invalidate cache for all stickers in this pack
foreach (var sticker in stickers)
{
InvalidateStickerCache(sticker);
PurgeStickerCache(sticker);
}
}
@ -91,14 +91,12 @@ public class StickerService(AppDatabase db, FileService fs, IMemoryCache cache)
// Store in cache if found
if (sticker != null)
{
cache.Set(cacheKey, sticker, CacheDuration);
}
return sticker;
}
private void InvalidateStickerCache(Sticker sticker)
private void PurgeStickerCache(Sticker sticker)
{
// Remove both possible cache entries
cache.Remove($"StickerLookup_{sticker.Id}");

View File

@ -28,7 +28,7 @@ public class CloudFile : ModelBase
[MaxLength(4096)] public string? Description { get; set; }
[Column(TypeName = "jsonb")] public Dictionary<string, object>? FileMeta { get; set; } = null!;
[Column(TypeName = "jsonb")] public Dictionary<string, object>? UserMeta { get; set; } = null!;
[Column(TypeName = "jsonb")] public List<CloudFileSensitiveMark> SensitiveMarks { get; set; } = new();
[Column(TypeName = "jsonb")] public List<CloudFileSensitiveMark>? SensitiveMarks { get; set; } = [];
[MaxLength(256)] public string? MimeType { get; set; }
[MaxLength(256)] public string? Hash { get; set; }
public long Size { get; set; }