PhoneToolMX/PhoneToolMX/Controllers/BaseController.cs
2023-10-21 13:46:56 -07:00

243 lines
8.3 KiB
C#

using AsterNET.Manager;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Mvc;
using PhoneToolMX.Data;
using PhoneToolMX.Helpers;
using PhoneToolMX.Models;
using PhoneToolMX.Models.ViewModels;
using PhoneToolMX.Services;
using System.Security.Authentication;
using System.Security.Claims;
namespace PhoneToolMX.Controllers
{
[Authorize]
[Route("[controller]")]
public abstract class BaseController<T, TViewModel> : Controller
where T: OwnedBase, IModel
where TViewModel : IViewModel, new()
{
protected private readonly PTMXContext _context;
private readonly UserManager<User> _userManager;
protected BaseController(UserManager<User> mgr, PTMXContext ctx)
{
_context = ctx;
_userManager = mgr;
}
protected private virtual Task PreForm(TViewModel vm)
{
throw new NotImplementedException();
}
#region Helper methods
/// <summary>
/// Gets the "friendly" name for an owned model object.
/// </summary>
/// <param name="vm">The model object to inspect</param>
/// <returns>The friendly name, or its ID if none was defined</returns>
private static string GetFriendlyName(IViewModel vm)
{
if (vm is not TViewModel model) return vm.Id.ToString();
return typeof(TViewModel).GetProperties().Where(pi =>
{
var headers = pi.GetCustomAttributes(typeof(HeaderAttribute), false);
return headers.Length != 0 && ((HeaderAttribute)headers[0]).Primary;
}).Select(pi => pi.GetValue(model)?.ToString()).FirstOrDefault() ?? model.Id.ToString();
}
private static string GetFriendlyName(IOwnedModel model)
{
var vm = new TViewModel().FromEntity(model);
return GetFriendlyName(vm);
}
protected private async Task<User> CurrentUser()
{
var claim = HttpContext.User.Claims.FirstOrDefault(c => c.Type.Equals(ClaimTypes.NameIdentifier));
if (claim == null) {
throw new InvalidCredentialException("fuck this noise, nameid claim missing");
}
var user = await _userManager.FindByIdAsync(claim.Value);
return user;
}
private async Task<IActionResult> FormView(TViewModel vm)
{
await PreForm(vm);
return View("_Form", vm);
}
private void SetMessage(FormMessage msg)
{
TempData["Message"] = msg.ToString();
}
private void SetMessage(FormMessageType type, string msg)
{
SetMessage(new FormMessage
{
Type = type,
Message = msg,
});
}
private async Task<IActionResult> ValidationError(TViewModel vm)
{
var plural = ModelState.ErrorCount > 1 ? "s" : string.Empty;
SetMessage(
FormMessageType.Error,
$"Error{plural} occurred in validation. Make the changes required and click Submit to try again."
);
return await FormView(vm);
}
#endregion
#region Create
[HttpGet("New")]
public async Task<IActionResult> New()
{
ViewData["Action"] = "add";
ViewData["Title"] = $"Add new {typeof(T).Name.ToLower()}";
return await FormView(default);
}
[HttpPost("New")]
public async Task<IActionResult> NewPost(TViewModel vm)
{
ViewData["Title"] = $"New {typeof(T).Name}";
if (!ModelState.IsValid) return await ValidationError(vm);
if (vm.ToEntity(_context) is not T model) throw new InvalidOperationException($"{typeof(TViewModel).FullName}.ToEntity() somehow produced something other than a {typeof(T).FullName}");
var entity = await _context.AddOwnable(await CurrentUser(), model);
await _context.SaveChangesAsync();
SetMessage(
FormMessageType.Success,
$"{typeof(T).Name} {GetFriendlyName(entity.Entity)} was created."
);
return RedirectToAction("Edit", new { id = entity.Entity.Id });
}
#endregion
#region Read
[HttpGet]
public async Task<IActionResult> Index()
{
ViewData["Title"] = $"My {typeof(T).Name}s";
return View("Index", _context
.GetOwned<T>(await CurrentUser())
.Select(m => (TViewModel)new TViewModel().FromEntity(m))
.ToList());
}
#endregion
#region Update
[HttpGet("Edit")]
public async Task<IActionResult> Edit(int id)
{
ViewData["Action"] = "update";
var model = _context.GetOwned<T>(await CurrentUser()).FirstOrDefault(o => o.Id == id);
if (model == null) return NotFound();
ViewData["Title"] = $"Editing {typeof(T).Name.ToLower()} {GetFriendlyName(new TViewModel().FromEntity(model))}";
return await FormView((TViewModel)new TViewModel().FromEntity(model));
}
[HttpPost("Edit")]
public async Task<IActionResult> EditPost(TViewModel vm)
{
if (vm?.Id == null) {
return BadRequest();
}
if (!ModelState.IsValid) return await ValidationError(vm);
// merge VM's changes with DbModel
var currentModel = _context.GetEntityById<T>(vm.Id);
if (vm.ToEntity(_context, currentModel) is not T entity) throw new InvalidOperationException($"{typeof(TViewModel).FullName}.ToEntity() somehow produced something other than a {typeof(T).FullName}");
currentModel.Commit(entity);
// and commit back
_context.Set<T>().Update(currentModel);
await _context.SaveChangesAsync();
// Try to notify the relevant endpoint to update its config
var amiStatus = "";
if (HttpContext.RequestServices.GetService<IAsteriskManager>() is {} ami) {
try { await ami.SendNotifyAsync(vm.NotifyOnChange());
amiStatus = "Your phone's configuration should be updated shortly.";
}
catch (BadResponseException e) { amiStatus = $"You'll need to manually update your phone settings (AMI Response: {e.Message})."; }
catch (ManagerException e) { amiStatus = $"You'll need to manually update your phone settings (AMI Client Error: {e.Message})."; }
catch (Exception e) {
amiStatus = $"You'll need to manually update your phone settings (Unknown Error: {e.Message}).";
}
}
SetMessage(
FormMessageType.Success,
$"{typeof(T).Name} {GetFriendlyName(currentModel)} was updated.{" " + amiStatus}"
);
return RedirectToAction("Edit", new { id = entity.Id });
}
#endregion
#region Delete
[HttpGet("Delete")]
public async Task<IActionResult> Delete(int id)
{
var model = _context.GetEntityById<T>(id);
if (model?.IsOwnedBy(await CurrentUser()) != true) {
return NotFound();
}
ViewData["Title"] = $"Delete {GetFriendlyName(model)}";
TempData["ModelDelete"] = model.Id;
return View("DeleteConfirm", model);
}
[HttpPost("Delete")]
public async Task<IActionResult> PostDelete(int id)
{
if ((int?)TempData["ModelDelete"] != id) {
return BadRequest();
}
var model = _context.GetEntityById<T>(id);
if (model?.IsOwnedBy(await CurrentUser()) != true) {
return NotFound();
}
_context.Set<T>().Remove(model);
await _context.SaveChangesAsync();
SetMessage(
FormMessageType.Success,
$"{typeof(T).Name} {GetFriendlyName(new TViewModel().FromEntity(model))} was successfully deleted."
);
return RedirectToAction("Index");
}
#endregion
}
}