diff --git a/Src/VideoGameQuotes.Web/Controllers/CategoryController.cs b/Src/VideoGameQuotes.Web/Controllers/CategoryController.cs new file mode 100644 index 0000000..a1ffefd --- /dev/null +++ b/Src/VideoGameQuotes.Web/Controllers/CategoryController.cs @@ -0,0 +1,78 @@ +using System.Web.Mvc; +using Portoa.Persistence; +using Portoa.Web.Controllers; +using VideoGameQuotes.Api; +using VideoGameQuotes.Web.Models; +using VideoGameQuotes.Web.Security; +using VideoGameQuotes.Web.Services; + +namespace VideoGameQuotes.Web.Controllers { + public class CategoryController : Controller { + private readonly ICategoryService categoryService; + + public CategoryController(ICategoryService categoryService) { + this.categoryService = categoryService; + } + + [HttpPost, VerifyUser(Group = UserGroup.Admin)] + public JsonResult Delete(int id) { + if (id < 1) { + return Json(this.CreateJsonResponse("Invalid ID")); + } + + try { + var category = categoryService.FindById(id); + var numQuotes = categoryService.GetQuotesForCategory(category); + if (numQuotes > 0) { + return Json(this.CreateJsonErrorResponse( + string.Format("There are {0} quotes that are still using this category", numQuotes) + )); + } + } catch (EntityNotFoundException) { + return Json(this.CreateJsonErrorResponse("No category exists for ID " + id)); + } + + categoryService.Delete(id); + return Json(this.CreateJsonResponse()); + } + + [HttpPost, VerifyUser(Group = UserGroup.Admin)] + public JsonResult Edit(EditCategoryModel model) { + if (model.CategoryId < 1) { + ModelState.AddModelError("CategoryId", "Invalid category ID"); + } + + if (!ModelState.IsValid) { + return Json(this.CreateJsonErrorResponse("Some errors occurred")); + } + + try { + var category = categoryService.FindById(model.CategoryId); + if (categoryService.FindByName(model.CategoryName) != null) { + return Json(this.CreateJsonErrorResponse("A category already exists for that name")); + } + + category.Name = model.CategoryName; + + category = categoryService.Save(category); + return Json(this.CreateJsonResponse(data: category.ToDto())); + } catch (EntityNotFoundException) { + return Json(this.CreateJsonErrorResponse("No category exists for ID " + model.CategoryId)); + } + } + + [HttpPost, VerifyUser] + public JsonResult Create(EditCategoryModel model) { + if (!ModelState.IsValid) { + return Json(this.CreateJsonErrorResponse("Some errors occurred.")); + } + + if (categoryService.FindByName(model.CategoryName) != null) { + return Json(this.CreateJsonErrorResponse("A category already exists for that name")); + } + + var category = categoryService.Save(new Category { Name = model.CategoryName }); + return Json(this.CreateJsonResponse(data: category.ToDto())); + } + } +} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs b/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs index 62819bd..7f7691c 100644 --- a/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs +++ b/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs @@ -233,17 +233,6 @@ namespace VideoGameQuotes.Web.Controllers { } } - [HttpPost, VerifyUser] - public JsonResult CreateCategory(Category category) { - try { - category = quoteService.SaveCategory(category); - var data = new Dictionary { { "categoryId", category.Id.ToString() }, { "categoryName", category.Name } }; - return Json(this.CreateJsonResponse(null, data)); - } catch (Exception e) { - return Json(this.CreateJsonErrorResponse(e)); - } - } - public ActionResult Search(string searchQuery) { var model = new SearchModel { User = currentUserProvider.CurrentUser, diff --git a/Src/VideoGameQuotes.Web/Global.asax.cs b/Src/VideoGameQuotes.Web/Global.asax.cs index 2e3a3ab..f5441b6 100644 --- a/Src/VideoGameQuotes.Web/Global.asax.cs +++ b/Src/VideoGameQuotes.Web/Global.asax.cs @@ -43,6 +43,7 @@ namespace VideoGameQuotes.Web { .RegisterType() .RegisterType() .RegisterType() + .RegisterType() .RegisterType() .RegisterType() .RegisterType() @@ -71,7 +72,7 @@ namespace VideoGameQuotes.Web { //bullshit route so that RenderAction works routes.MapRoute("mainmenu", "home/mainmenu", new { controller = "Home", action = "MainMenu" }); - routes.MapRoute("crud-default", "{controller}/{action}", null, new { controller = "system|publisher|game", action = "create|edit|delete" }); + routes.MapRoute("crud-default", "{controller}/{action}", null, new { controller = "system|publisher|game|category", action = "create|edit|delete" }); routes.MapRoute("users-paged", "admin/users/{start}-{end}", new { controller = "Admin", action = "Users" }, new { start = @"\d+", end = @"\d+" }); routes.MapRoute("admin", "admin/{action}", new { controller = "Admin", action = "Index" }, new { action = "users|create|flags|password" }); @@ -88,7 +89,6 @@ namespace VideoGameQuotes.Web { routes.MapRoute("quote", "{action}", new { controller = "Quote" }, new { action = "submit|recent|random|vote|report" }); routes.MapRoute("dismiss-flag", "dismiss-flag", new { controller = "Quote", action = "DismissFlag" }); routes.MapRoute("individual-quote", "quote/{id}/{*text}", new { controller = "Quote", action = "Quote" }, new { id = @"\d+" }); - routes.MapRoute("create-category", "category/create", new { controller = "Quote", action = "CreateCategory" }); routes.MapRoute("default", "{controller}", new { controller = "home", action = "index" }); } } diff --git a/Src/VideoGameQuotes.Web/Models/EditCategoryModel.cs b/Src/VideoGameQuotes.Web/Models/EditCategoryModel.cs new file mode 100644 index 0000000..13bb2a2 --- /dev/null +++ b/Src/VideoGameQuotes.Web/Models/EditCategoryModel.cs @@ -0,0 +1,9 @@ +using System.ComponentModel.DataAnnotations; + +namespace VideoGameQuotes.Web.Models { + public class EditCategoryModel { + public int CategoryId { get; set; } + [Required(ErrorMessage = "The category must have a name, queerball")] + public string CategoryName { get; set; } + } +} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Security/VerifyUserAttribute.cs b/Src/VideoGameQuotes.Web/Security/VerifyUserAttribute.cs index 96f93c1..eddf53f 100644 --- a/Src/VideoGameQuotes.Web/Security/VerifyUserAttribute.cs +++ b/Src/VideoGameQuotes.Web/Security/VerifyUserAttribute.cs @@ -22,6 +22,7 @@ namespace VideoGameQuotes.Web.Security { if (!allowedToExecuteAction) { filterContext.Result = new ErrorViewResult { Message = "You are not a verified user (are you hiding your IP address?)", + ModelCreator = exception => new ErrorModel { Exception = exception, User = UserProvider.CurrentUser }, StatusCode = HttpStatusCode.Forbidden, ViewName = "Forbidden" }; diff --git a/Src/VideoGameQuotes.Web/Services/CategoryService.cs b/Src/VideoGameQuotes.Web/Services/CategoryService.cs new file mode 100644 index 0000000..3a1772f --- /dev/null +++ b/Src/VideoGameQuotes.Web/Services/CategoryService.cs @@ -0,0 +1,53 @@ +using System.Linq; +using JetBrains.Annotations; +using Portoa.Persistence; +using VideoGameQuotes.Api; + +namespace VideoGameQuotes.Web.Services { + public interface ICategoryService { + [CanBeNull] + Category FindByName(string name); + Category Save(Category category); + Category FindById(int id); + void Delete(int id); + int GetQuotesForCategory(Category category); + } + + public class CategoryService : ICategoryService { + private readonly IRepository categoryRepository; + private readonly IRepository quoteRepository; + + public CategoryService(IRepository categoryRepository, IRepository quoteRepository) { + this.categoryRepository = categoryRepository; + this.quoteRepository = quoteRepository; + } + + [UnitOfWork] + public Category FindByName(string name) { + return categoryRepository.Records.FirstOrDefault(category => category.Name == name); + } + + [UnitOfWork] + public Category Save(Category category) { + return categoryRepository.Save(category); + } + + [UnitOfWork] + public Category FindById(int id) { + return categoryRepository.FindById(id); + } + + [UnitOfWork] + public void Delete(int id) { + categoryRepository.Delete(id); + } + + [UnitOfWork] + public int GetQuotesForCategory(Category category) { + return quoteRepository + .Records + .Where(quote => quote.Categories.Contains(category)) + .Count(); + } + } +} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Services/GameService.cs b/Src/VideoGameQuotes.Web/Services/GameService.cs index 544a195..65b5fc4 100644 --- a/Src/VideoGameQuotes.Web/Services/GameService.cs +++ b/Src/VideoGameQuotes.Web/Services/GameService.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using Portoa.Persistence; diff --git a/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj b/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj index b1ec353..a77f5f4 100644 --- a/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj +++ b/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj @@ -86,12 +86,15 @@ + + + @@ -140,7 +143,9 @@ + + diff --git a/Src/VideoGameQuotes.Web/Views/Shared/ExceptionView.ascx b/Src/VideoGameQuotes.Web/Views/Shared/ExceptionView.ascx index 8012248..339a809 100644 --- a/Src/VideoGameQuotes.Web/Views/Shared/ExceptionView.ascx +++ b/Src/VideoGameQuotes.Web/Views/Shared/ExceptionView.ascx @@ -1,12 +1,15 @@ -<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> +<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl>" %> +<%@ Import Namespace="VideoGameQuotes.Api" %> <% - if (true) { - if (Model.Exception != null) { %> -

Exception Details

<% - Html.RenderPartial("RecursiveExceptionView", Model.Exception); - } else { %> -

No exception was thrown.

- <% } - } + if (Model.User == null || Model.User.Group < UserGroup.Admin) { + return; + } + + if (Model.Exception != null) { %> +

Exception Details

<% + Html.RenderPartial("RecursiveExceptionView", Model.Exception); + } else { %> +

No exception was thrown.

+ <% } %> \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Views/Shared/Forbidden.aspx b/Src/VideoGameQuotes.Web/Views/Shared/Forbidden.aspx index 187d250..337d89d 100644 --- a/Src/VideoGameQuotes.Web/Views/Shared/Forbidden.aspx +++ b/Src/VideoGameQuotes.Web/Views/Shared/Forbidden.aspx @@ -1,4 +1,4 @@ -<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> +<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage>" MasterPageFile="~/Views/Shared/Site.Master" %> Forbidden diff --git a/Src/VideoGameQuotes.Web/Views/Shared/NotFound.aspx b/Src/VideoGameQuotes.Web/Views/Shared/NotFound.aspx index 878bc39..3e78e85 100644 --- a/Src/VideoGameQuotes.Web/Views/Shared/NotFound.aspx +++ b/Src/VideoGameQuotes.Web/Views/Shared/NotFound.aspx @@ -1,4 +1,4 @@ -<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> +<%@ Page Language="C#" Inherits="System.Web.Mvc.ViewPage>" MasterPageFile="~/Views/Shared/Site.Master" %> 404 diff --git a/Src/VideoGameQuotes.Web/Views/Shared/RecursiveExceptionView.ascx b/Src/VideoGameQuotes.Web/Views/Shared/RecursiveExceptionView.ascx index ab565d5..b7c173a 100644 --- a/Src/VideoGameQuotes.Web/Views/Shared/RecursiveExceptionView.ascx +++ b/Src/VideoGameQuotes.Web/Views/Shared/RecursiveExceptionView.ascx @@ -1,9 +1,9 @@ <%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> <%@ Import Namespace="Portoa.Util" %> -

<%= Model.GetType().GetFriendlyName(false) %>: <%= Html.Encode(Model.Message) %>

+

<%= Model.GetType().GetFriendlyName(false) %>: <%: Model.Message %>

-
<%= Model.StackTrace %>
+
<%: Model.StackTrace %>

diff --git a/Src/VideoGameQuotes.Web/media/images/accept.png b/Src/VideoGameQuotes.Web/media/images/accept.png new file mode 100644 index 0000000..89c8129 Binary files /dev/null and b/Src/VideoGameQuotes.Web/media/images/accept.png differ diff --git a/Src/VideoGameQuotes.Web/media/images/cancel.png b/Src/VideoGameQuotes.Web/media/images/cancel.png new file mode 100644 index 0000000..c149c2b Binary files /dev/null and b/Src/VideoGameQuotes.Web/media/images/cancel.png differ