Files
Radicade/Server/Admin/RoomBanStore.cs
T
2026-10-16 23:33:13 -07:00

202 lines
5.7 KiB
C#

using System.Text.Json;
using Microsoft.EntityFrameworkCore;
using RadicadeServer.API.Notifications;
using RadicadeServer.DB;
using RadicadeServer.RecNet.Enums.Messages;
using RadicadeServer.RecNet.Messages;
using RadicadeServer.RecNet.Rooms;
namespace RadicadeServer.Admin;
public static class RoomBanStore
{
private static readonly JsonSerializerOptions JsonOptions = new() { WriteIndented = true };
private static readonly object FileLock = new();
private const long CoachAccountId = 1;
private static string ClosedMessage(string roomName, string reason) =>
$"We have closed the room '{roomName}' for: {reason}. We encourage creators to review our Creator Code of Conduct to help make Radicade a fun and safe place for everyone.";
private static string RestoredMessage(string roomName) =>
$"We have restored the room '{roomName}' after a thorough review of the previous accusations. Thank you for continuing to follow the Creator Code of Conduct, and making Radicade a better place.";
public static string FilePath => Path.Join(DBConfig.PersistentLogs, "banned-rooms.json");
public static List<BannedRoom> Load()
{
lock (FileLock) return LoadLocked();
}
public static BannedRoom? Find(long roomId) => Load().FirstOrDefault(x => x.RoomId == roomId);
public static bool IsBanned(long roomId) => Find(roomId) != null;
public static async Task<BannedRoom> BanAsync(RecDBContext db, RoomData room, string reason, string bannedBy)
{
var entry = new BannedRoom
{
RoomId = room.RoomId,
Name = room.Name,
Reason = reason,
BannedBy = bannedBy,
BannedAt = DateTime.UtcNow,
PublishedAt = room.PublishedAt,
Room = room,
SubRooms = await db.SubRooms.AsNoTracking().Where(x => x.RoomId == room.RoomId).ToListAsync(),
Roles = await db.RoomRoles.AsNoTracking().Where(x => x.RoomId == room.RoomId).ToListAsync(),
Stats = await db.RoomStats.AsNoTracking().FirstOrDefaultAsync(x => x.RoomId == room.RoomId)
};
Upsert(entry);
db.Rooms.Remove(room);
await db.SaveChangesAsync();
var why = string.IsNullOrWhiteSpace(reason) ? "a breach of the Creator Code of Conduct" : reason;
await TellOwnerAsync(room.CreatorAccountId, ClosedMessage(room.Name, why));
return entry;
}
public static async Task<bool> RestoreAsync(RecDBContext db, long roomId)
{
var entry = Find(roomId);
if (entry == null) return false;
await InsertRoomWithItsIdAsync(db, entry.Room, entry.PublishedAt);
Remove(roomId);
await TellOwnerAsync(entry.Room.CreatorAccountId, RestoredMessage(entry.Name));
return true;
}
private static async Task InsertRoomWithItsIdAsync(RecDBContext db, RoomData room, DateTime publishedAt)
{
var entity = db.Model.FindEntityType(typeof(RoomData))
?? throw new InvalidOperationException("RoomData is not part of the model.");
var table = entity.GetTableName() ?? nameof(RoomData);
var qualified = entity.GetSchema() is { Length: > 0 } schema ? $"[{schema}].[{table}]" : $"[{table}]";
var columns = new List<string>();
var cells = new List<string>();
var parameters = new List<object>();
foreach (var property in entity.GetProperties())
{
if (property.IsShadowProperty() || property.PropertyInfo == null) continue;
var value = property.Name == nameof(RoomData.PublishedAt)
? publishedAt
: property.PropertyInfo.GetValue(room);
columns.Add($"[{property.GetColumnName()}]");
var sqlValue = ToSqlValue(value);
if (sqlValue == null)
{
cells.Add("NULL");
}
else
{
cells.Add("{" + parameters.Count + "}");
parameters.Add(sqlValue);
}
}
var sql =
$"SET IDENTITY_INSERT {qualified} ON;\n" +
$"INSERT INTO {qualified} ({string.Join(", ", columns)}) VALUES ({string.Join(", ", cells)});\n" +
$"SET IDENTITY_INSERT {qualified} OFF;";
await db.Database.ExecuteSqlRawAsync(sql, parameters);
}
private static async Task TellOwnerAsync(long ownerAccountId, string message)
{
if (ownerAccountId <= 0) return;
try
{
await Notify.SendMessage(ownerAccountId, new Message
{
Type = MessageType.CoachMessage,
Data = message,
FromPlayerId = CoachAccountId
});
}
catch (Exception)
{
}
}
private static object? ToSqlValue(object? value) => value switch
{
null => null,
Enum enumValue => Convert.ChangeType(enumValue, Enum.GetUnderlyingType(enumValue.GetType())),
_ => value
};
private static void Upsert(BannedRoom entry)
{
lock (FileLock)
{
var rooms = LoadLocked();
rooms.RemoveAll(x => x.RoomId == entry.RoomId);
rooms.Add(entry);
SaveLocked(rooms);
}
}
private static void Remove(long roomId)
{
lock (FileLock)
{
var rooms = LoadLocked();
if (rooms.RemoveAll(x => x.RoomId == roomId) == 0) return;
SaveLocked(rooms);
}
}
private static List<BannedRoom> LoadLocked()
{
if (!File.Exists(FilePath)) return new List<BannedRoom>();
try
{
var json = File.ReadAllText(FilePath);
var rooms = JsonSerializer.Deserialize<List<BannedRoom>>(json, JsonOptions) ?? new List<BannedRoom>();
foreach (var room in rooms) room.Room.PublishedAt = room.PublishedAt;
return rooms;
}
catch (Exception)
{
return new List<BannedRoom>();
}
}
private static void SaveLocked(List<BannedRoom> rooms)
{
Directory.CreateDirectory(DBConfig.PersistentLogs);
File.WriteAllText(FilePath, JsonSerializer.Serialize(rooms, JsonOptions));
}
}
public class BannedRoom
{
public long RoomId { get; set; }
public string Name { get; set; } = "";
public string Reason { get; set; } = "";
public string BannedBy { get; set; } = "";
public DateTime BannedAt { get; set; }
public DateTime PublishedAt { get; set; }
public RoomData Room { get; set; } = new();
public List<SubRoom> SubRooms { get; set; } = new();
public List<RoomRole> Roles { get; set; } = new();
public RoomStats? Stats { get; set; }
}