using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Authorization; using PlotLine.Services; using PlotLine.ViewModels; namespace PlotLine.Controllers; [Authorize] public sealed class CharactersController(ICharacterService characters) : Controller { public async Task Index(int projectId) { var model = await characters.GetCharactersAsync(projectId); return model is null ? NotFound() : View(model); } public async Task Create(int projectId) { var model = await characters.GetCreateCharacterAsync(projectId); return model is null ? NotFound() : View("Edit", model); } public async Task Edit(int id) { var model = await characters.GetEditCharacterAsync(id); return model is null ? NotFound() : View(model); } public async Task Details(int id) { var model = await characters.GetCharacterDetailAsync(id); return model is null ? NotFound() : View(model); } public async Task CropAvatar(int imageId) { var model = await characters.GetAvatarCropAsync(imageId); return model is null ? NotFound() : View(model); } [HttpPost] [ValidateAntiForgeryToken] public async Task Save(CharacterEditViewModel model) { if (!ModelState.IsValid) { model.Project ??= (await characters.GetCreateCharacterAsync(model.ProjectID))?.Project; return View("Edit", model); } int characterId; try { characterId = await characters.SaveCharacterAsync(model); } catch (SubscriptionLimitException ex) when (model.CharacterID == 0) { ModelState.AddModelError(string.Empty, ex.Message); var createModel = await characters.GetCreateCharacterAsync(model.ProjectID); if (createModel is null) { return NotFound(); } createModel.CharacterName = model.CharacterName; createModel.ShortName = model.ShortName; createModel.Sex = model.Sex; createModel.BirthDate = model.BirthDate; createModel.AgeAtSeriesStart = model.AgeAtSeriesStart; createModel.Height = model.Height; createModel.EyeColour = model.EyeColour; createModel.CharacterImportance = model.CharacterImportance; createModel.ShowInQuickAddBar = model.ShowInQuickAddBar; createModel.DefaultDescription = model.DefaultDescription; return View("Edit", createModel); } catch (EntityImageUploadException ex) { TempData["ImageError"] = ex.Message; return RedirectToAction(nameof(Edit), new { id = ex.EntityId }); } catch (InvalidOperationException ex) { ModelState.AddModelError(string.Empty, ex.Message); var editModel = model.CharacterID == 0 ? await characters.GetCreateCharacterAsync(model.ProjectID) : await characters.GetEditCharacterAsync(model.CharacterID); if (editModel is null) { return NotFound(); } editModel.CharacterName = model.CharacterName; editModel.ShortName = model.ShortName; editModel.Sex = model.Sex; editModel.BirthDate = model.BirthDate; editModel.AgeAtSeriesStart = model.AgeAtSeriesStart; editModel.Height = model.Height; editModel.EyeColour = model.EyeColour; editModel.CharacterImportance = model.CharacterImportance; editModel.ShowInQuickAddBar = model.ShowInQuickAddBar; editModel.DefaultDescription = model.DefaultDescription; return View("Edit", editModel); } return RedirectToAction(nameof(Details), new { id = characterId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task UploadImage(CharacterImageUploadViewModel model) { var result = await characters.UploadCharacterImageAsync(model); TempData[result.Succeeded ? "ImageMessage" : "ImageError"] = result.Succeeded ? "Image added to gallery." : result.ErrorMessage ?? "Character image could not be uploaded."; return RedirectToAction(nameof(Details), new { id = model.CharacterID }); } [HttpPost] [ValidateAntiForgeryToken] public async Task SaveAvatar(CharacterAvatarCropViewModel model) { var result = await characters.SaveCharacterAvatarAsync(model); TempData[result.Succeeded ? "ImageMessage" : "ImageError"] = result.Succeeded ? "Avatar updated." : result.ErrorMessage ?? "Avatar could not be saved."; return result.Succeeded ? RedirectToAction(nameof(Details), new { id = model.CharacterID }) : RedirectToAction(nameof(CropAvatar), new { imageId = model.CharacterImageID }); } [HttpPost] [ValidateAntiForgeryToken] public async Task RemoveAvatar(int id) { await characters.RemoveCharacterAvatarAsync(id); TempData["ImageMessage"] = "Avatar removed."; return RedirectToAction(nameof(Details), new { id }); } [HttpPost] [ValidateAntiForgeryToken] public async Task DeleteImage(int imageId, int characterId) { await characters.DeleteCharacterImageAsync(imageId); TempData["ImageMessage"] = "Image removed from gallery."; return RedirectToAction(nameof(Details), new { id = characterId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task Archive(int id, int projectId) { await characters.ArchiveCharacterAsync(id); TempData["ArchiveMessage"] = "Character archived. You can restore it from Archived Items."; return RedirectToAction(nameof(Index), new { projectId }); } [HttpPost] [ValidateAntiForgeryToken] public async Task SaveInitialRelationship(InitialRelationshipEditViewModel model) { await characters.SaveInitialRelationshipAsync(model); TempData["RelationshipMessage"] = model.CharacterRelationshipID == 0 ? "Initial relationship added." : "Initial relationship updated."; return RedirectToAction(nameof(Details), new { id = model.ReturnCharacterID ?? model.CharacterID }); } [HttpPost] [ValidateAntiForgeryToken] public async Task ArchiveRelationship(int id, int characterId) { await characters.ArchiveRelationshipAsync(id); TempData["ArchiveMessage"] = "Relationship archived. You can restore it from Archived Items."; return RedirectToAction(nameof(Details), new { id = characterId }); } }