Merge pull request 'Basic Moderation System' (#1) from moderation into master

Reviewed-on: https://gitea.milenia.local.alanmoon.net/Moonbase/sodoff/pulls/1
This commit is contained in:
Alan Moon 2025-03-02 16:05:53 -08:00
commit ee7edd5145
7 changed files with 214 additions and 0 deletions

View File

@ -0,0 +1,91 @@
using System.Linq.Expressions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using sodoff.Model;
using sodoff.Schema;
using sodoff.Services;
namespace sodoff.Controllers.Internal
{
[ApiController]
public class ModerationController : Controller
{
public readonly DBContext ctx;
public readonly ModerationService moderationService;
public ModerationController(DBContext ctx, ModerationService moderationService)
{
this.ctx = ctx;
this.moderationService = moderationService;
}
[HttpPost]
[Route("Moderation/AddBanToVikingByGuid")]
public IActionResult AddBanToVikingByGuid([FromForm] Guid token, [FromForm] Guid userId, [FromForm] int banType, [FromForm] int days = 0)
{
var validationResult = ValidateSession(token);
if(validationResult)
{
// get the viking
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
// get execution timestamp
DateTime timestamp = DateTime.UtcNow;
DateTime expiration = new DateTime();
if (days == 0) expiration = new DateTime(9999, 99, 99); // a days value of 0 should mean indefinite ban (might change later)
else expiration = timestamp.AddDays(days);
if (viking != null) return Ok(moderationService.AddBanToViking(viking, (UserBanType)banType, expiration));
else return NotFound();
}
return Unauthorized("You Do Not Have Sufficient Permissions To Moderate Users");
}
[HttpPost]
[Route("Moderation/RemoveBansFromVikingByGuidAndType")]
public IActionResult RemoveBanFromVikingByGuid([FromForm] Guid token, [FromForm] Guid userId, [FromForm] int banType)
{
var validationResult = ValidateSession(token);
if (validationResult)
{
// get the viking
Viking? viking = ctx.Vikings.FirstOrDefault(e => e.Uid == userId);
// remove all bans of type
if (viking != null) return Ok(moderationService.RemoveBansFromVikingByType(viking, (UserBanType)banType));
else return NotFound();
}
return Unauthorized("You Do Not Have Sufficient Permissions To Moderate Users");
}
[HttpPost]
[Route("Moderation/CheckForVikingBan")]
public IActionResult CheckForVikingBan([FromForm] Guid token)
{
// get viking session
var session = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token);
if (session != null && session.Viking != null) return Ok(moderationService.IsVikingBanned(session.Viking));
else return Ok(UserBanType.NotBanned); // invalid session, for now just return not banned
}
private bool ValidateSession(Guid token)
{
// get active session
var session = ctx.Sessions.FirstOrDefault(e => e.ApiToken == token);
if (session != null)
{
// most endpoints here should only be activated by a 'Moderator' or above
Role? vikingRole = session.Viking?.MMORoles.FirstOrDefault()?.Role;
if (vikingRole != null && (vikingRole == Role.Moderator || vikingRole == Role.Admin)) return true;
else return false;
} else return false;
}
}
}

View File

@ -30,6 +30,7 @@ public class DBContext : DbContext {
public DbSet<RatingRank> RatingRanks { get; set; } = null!; public DbSet<RatingRank> RatingRanks { get; set; } = null!;
public DbSet<UserMissionData> UserMissionData { get; set; } = null!; public DbSet<UserMissionData> UserMissionData { get; set; } = null!;
public DbSet<UserBadgeCompleteData> UserBadgesCompleted { get; set; } = null!; public DbSet<UserBadgeCompleteData> UserBadgesCompleted { get; set; } = null!;
public DbSet<UserBan> Bans { get; set; } = null!;
private readonly IOptions<ApiServerConfig> config; private readonly IOptions<ApiServerConfig> config;
@ -156,6 +157,9 @@ public class DBContext : DbContext {
builder.Entity<Viking>().HasMany(v => v.UserBadgesCompleted) builder.Entity<Viking>().HasMany(v => v.UserBadgesCompleted)
.WithOne(r => r.Viking); .WithOne(r => r.Viking);
builder.Entity<Viking>().HasMany(v => v.UserBans)
.WithOne(r => r.Viking);
// Dragons // Dragons
builder.Entity<Dragon>().HasOne(d => d.Viking) builder.Entity<Dragon>().HasOne(d => d.Viking)
.WithMany(e => e.Dragons) .WithMany(e => e.Dragons)
@ -301,5 +305,10 @@ public class DBContext : DbContext {
builder.Entity<UserBadgeCompleteData>().HasOne(r => r.Viking) builder.Entity<UserBadgeCompleteData>().HasOne(r => r.Viking)
.WithMany(v => v.UserBadgesCompleted) .WithMany(v => v.UserBadgesCompleted)
.HasForeignKey(r => r.VikingId); .HasForeignKey(r => r.VikingId);
// Bans
builder.Entity<UserBan>().HasOne(r => r.Viking)
.WithMany(e => e.UserBans)
.HasForeignKey(e => e.VikingId);
} }
} }

19
src/Model/UserBan.cs Normal file
View File

@ -0,0 +1,19 @@
using System;
using System.ComponentModel.DataAnnotations;
using sodoff.Schema;
namespace sodoff.Model;
public class UserBan
{
[Key]
public int Id { get; set; }
public int VikingId { get; set; }
public UserBanType UserBanType { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime? ExpiresOn { get; set; }
public virtual Viking? Viking { get; set; }
}

View File

@ -44,6 +44,7 @@ public class Viking {
public virtual Dragon? SelectedDragon { get; set; } public virtual Dragon? SelectedDragon { get; set; }
public virtual ICollection<UserMissionData> UserMissions { get; set; } = null!; public virtual ICollection<UserMissionData> UserMissions { get; set; } = null!;
public virtual ICollection<UserBadgeCompleteData> UserBadgesCompleted { get; set; } = null!; public virtual ICollection<UserBadgeCompleteData> UserBadgesCompleted { get; set; } = null!;
public virtual ICollection<UserBan> UserBans { get; set; } = null!;
public DateTime? CreationDate { get; set; } public DateTime? CreationDate { get; set; }
public DateTime? BirthDate { get; set; } public DateTime? BirthDate { get; set; }

View File

@ -40,6 +40,7 @@ builder.Services.AddScoped<AchievementService>();
builder.Services.AddScoped<GameDataService>(); builder.Services.AddScoped<GameDataService>();
builder.Services.AddScoped<ProfileService>(); builder.Services.AddScoped<ProfileService>();
builder.Services.AddScoped<NeighborhoodService>(); builder.Services.AddScoped<NeighborhoodService>();
builder.Services.AddScoped<ModerationService>();
bool assetServer = builder.Configuration.GetSection("AssetServer").GetValue<bool>("Enabled"); bool assetServer = builder.Configuration.GetSection("AssetServer").GetValue<bool>("Enabled");
string assetIP = builder.Configuration.GetSection("AssetServer").GetValue<string>("ListenIP"); string assetIP = builder.Configuration.GetSection("AssetServer").GetValue<string>("ListenIP");

10
src/Schema/UserBanType.cs Normal file
View File

@ -0,0 +1,10 @@
namespace sodoff.Schema;
public enum UserBanType
{
NotBanned = 0,
IndefiniteOpenChatBan = 1,
TemporaryOpenChatBan = 2,
IndefiniteAccountBan = 3,
TemporaryAccountBan = 4
}

View File

@ -0,0 +1,83 @@
using System;
using sodoff.Model;
using sodoff.Schema;
namespace sodoff.Services;
public class ModerationService
{
public readonly DBContext ctx;
public ModerationService(DBContext ctx)
{
this.ctx = ctx;
}
public string AddBanToViking(Viking viking, UserBanType userBanType, DateTime expirationDate = new DateTime())
{
// get UTC time stamp of function execution
DateTime timestamp = DateTime.UtcNow;
// construct user ban
UserBan userBan = new UserBan
{
UserBanType = userBanType,
ExpiresOn = expirationDate,
CreatedAt = timestamp
};
// add to viking userban list
viking.UserBans.Add(userBan);
ctx.SaveChanges();
// return success message
return "Success";
}
public bool RemoveBanById(int id)
{
// find ban
UserBan? ban = ctx.Bans.FirstOrDefault(e => e.Id == id);
// remove it
if (ban != null) { ctx.Bans.Remove(ban); ctx.SaveChanges(); return true; }
else return false;
}
public bool RemoveBansFromVikingByType(Viking viking, UserBanType userBanType)
{
// get all bans of type
List<UserBan> userBans = viking.UserBans.Where(e => e.UserBanType == userBanType).ToList();
if (userBans.Count == 0) return false;
// delete all
foreach(var ban in userBans) { viking.UserBans.Remove(ban); }
ctx.SaveChanges();
return true;
}
public UserBanType IsVikingBanned(Viking viking)
{
// get UTC time stamp of function execution
DateTime timestamp = DateTime.UtcNow;
// sort viking ban list by latest first
List<UserBan> bans = viking.UserBans.OrderByDescending(e => e.CreatedAt).ToList();
if (bans.Count == 0) return UserBanType.NotBanned; // no bans in list means viking is not banned
if (bans.First().UserBanType == UserBanType.IndefiniteAccountBan) return UserBanType.IndefiniteAccountBan;
else if (bans.First().UserBanType == UserBanType.IndefiniteOpenChatBan) return UserBanType.IndefiniteOpenChatBan;
if (DateTime.Compare(bans.First().ExpiresOn ?? new DateTime(9999, 99, 99), timestamp) > 0) return bans.First().UserBanType;
else
{
// ban should be removed
RemoveBanById(bans.First().Id);
return UserBanType.NotBanned;
}
}
}