diff --git a/src/Controllers/Internal/ModerationController.cs b/src/Controllers/Internal/ModerationController.cs new file mode 100644 index 0000000..9b35e3c --- /dev/null +++ b/src/Controllers/Internal/ModerationController.cs @@ -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; + } + } +} diff --git a/src/Model/DBContext.cs b/src/Model/DBContext.cs index 1832763..bc1a0c9 100644 --- a/src/Model/DBContext.cs +++ b/src/Model/DBContext.cs @@ -30,6 +30,7 @@ public class DBContext : DbContext { public DbSet RatingRanks { get; set; } = null!; public DbSet UserMissionData { get; set; } = null!; public DbSet UserBadgesCompleted { get; set; } = null!; + public DbSet Bans { get; set; } = null!; private readonly IOptions config; @@ -156,6 +157,9 @@ public class DBContext : DbContext { builder.Entity().HasMany(v => v.UserBadgesCompleted) .WithOne(r => r.Viking); + builder.Entity().HasMany(v => v.UserBans) + .WithOne(r => r.Viking); + // Dragons builder.Entity().HasOne(d => d.Viking) .WithMany(e => e.Dragons) @@ -301,5 +305,10 @@ public class DBContext : DbContext { builder.Entity().HasOne(r => r.Viking) .WithMany(v => v.UserBadgesCompleted) .HasForeignKey(r => r.VikingId); + + // Bans + builder.Entity().HasOne(r => r.Viking) + .WithMany(e => e.UserBans) + .HasForeignKey(e => e.VikingId); } } diff --git a/src/Model/UserBan.cs b/src/Model/UserBan.cs new file mode 100644 index 0000000..c7bcb0d --- /dev/null +++ b/src/Model/UserBan.cs @@ -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; } +} diff --git a/src/Model/Viking.cs b/src/Model/Viking.cs index a787107..0578525 100644 --- a/src/Model/Viking.cs +++ b/src/Model/Viking.cs @@ -44,6 +44,7 @@ public class Viking { public virtual Dragon? SelectedDragon { get; set; } public virtual ICollection UserMissions { get; set; } = null!; public virtual ICollection UserBadgesCompleted { get; set; } = null!; + public virtual ICollection UserBans { get; set; } = null!; public DateTime? CreationDate { get; set; } public DateTime? BirthDate { get; set; } diff --git a/src/Program.cs b/src/Program.cs index 91cbbaa..1c1cbe9 100644 --- a/src/Program.cs +++ b/src/Program.cs @@ -40,6 +40,7 @@ builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); +builder.Services.AddScoped(); bool assetServer = builder.Configuration.GetSection("AssetServer").GetValue("Enabled"); string assetIP = builder.Configuration.GetSection("AssetServer").GetValue("ListenIP"); diff --git a/src/Schema/UserBanType.cs b/src/Schema/UserBanType.cs new file mode 100644 index 0000000..29c99a3 --- /dev/null +++ b/src/Schema/UserBanType.cs @@ -0,0 +1,10 @@ +namespace sodoff.Schema; + +public enum UserBanType +{ + NotBanned = 0, + IndefiniteOpenChatBan = 1, + TemporaryOpenChatBan = 2, + IndefiniteAccountBan = 3, + TemporaryAccountBan = 4 +} diff --git a/src/Services/ModerationService.cs b/src/Services/ModerationService.cs new file mode 100644 index 0000000..35686b9 --- /dev/null +++ b/src/Services/ModerationService.cs @@ -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 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 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; + } + } +}