From c4b93c46fac7c4a0d99ad1bbba78d14e822af115 Mon Sep 17 00:00:00 2001 From: tmont Date: Mon, 21 Feb 2011 23:59:06 +0000 Subject: [PATCH] user stuff, restyled SingleQuote to not be so hideous --- .../ICurrentUserProvider.cs | 15 --- .../Persistence/UserService.cs | 32 +++++++ Src/VideoGameQuotes.Api/User.cs | 6 ++ .../VideoGameQuotes.Api.csproj | 1 - .../Controllers/AdminController.cs | 27 +++++- .../Controllers/HomeController.cs | 5 +- .../Controllers/QuoteController.cs | 31 ++++--- .../Controllers/UserController.cs | 91 +++++++++++++++++++ Src/VideoGameQuotes.Web/Global.asax.cs | 18 +++- .../Models/InvalidUsernameModel.cs | 5 + .../Models/PagedModelWithUser.cs | 8 ++ .../Models/PagedQuoteCollectionModel.cs | 14 --- Src/VideoGameQuotes.Web/Models/QuoteModel.cs | 6 ++ Src/VideoGameQuotes.Web/Models/UserModel.cs | 28 ++++++ .../Security/SessionBasedUserProvider.cs | 3 +- ...serAttribute.cs => VerifyUserAttribute.cs} | 6 +- .../Services/AdministrationService.cs | 14 ++- .../VideoGameQuotes.Web.csproj | 13 ++- .../Views/Admin/SingleUser.ascx | 26 ++++++ .../Views/Admin/Users.aspx | 19 ++++ Src/VideoGameQuotes.Web/Views/Quote/Best.aspx | 6 +- .../Views/Quote/Quote.aspx | 3 +- .../Views/{Quote => Shared}/BadPaging.aspx | 0 .../Views/Shared/SingleQuote.ascx | 48 +++++----- .../Views/Shared/Unknown.aspx | 6 +- Src/VideoGameQuotes.Web/Views/User/Edit.aspx | 31 +++++++ .../Views/User/InvalidUsername.aspx | 11 +++ Src/VideoGameQuotes.Web/media/css/quote.css | 54 +++++++++-- 28 files changed, 425 insertions(+), 102 deletions(-) delete mode 100644 Src/VideoGameQuotes.Api/ICurrentUserProvider.cs create mode 100644 Src/VideoGameQuotes.Web/Controllers/UserController.cs create mode 100644 Src/VideoGameQuotes.Web/Models/InvalidUsernameModel.cs create mode 100644 Src/VideoGameQuotes.Web/Models/PagedModelWithUser.cs delete mode 100644 Src/VideoGameQuotes.Web/Models/PagedQuoteCollectionModel.cs create mode 100644 Src/VideoGameQuotes.Web/Models/UserModel.cs rename Src/VideoGameQuotes.Web/Security/{IsValidUserAttribute.cs => VerifyUserAttribute.cs} (79%) create mode 100644 Src/VideoGameQuotes.Web/Views/Admin/SingleUser.ascx create mode 100644 Src/VideoGameQuotes.Web/Views/Admin/Users.aspx rename Src/VideoGameQuotes.Web/Views/{Quote => Shared}/BadPaging.aspx (100%) create mode 100644 Src/VideoGameQuotes.Web/Views/User/Edit.aspx create mode 100644 Src/VideoGameQuotes.Web/Views/User/InvalidUsername.aspx diff --git a/Src/VideoGameQuotes.Api/ICurrentUserProvider.cs b/Src/VideoGameQuotes.Api/ICurrentUserProvider.cs deleted file mode 100644 index 1cd2b25..0000000 --- a/Src/VideoGameQuotes.Api/ICurrentUserProvider.cs +++ /dev/null @@ -1,15 +0,0 @@ -using JetBrains.Annotations; - -namespace VideoGameQuotes.Api { - /// - /// Provides an interface for identifying the current user - /// - public interface ICurrentUserProvider { - /// - /// Returns the user currently performing actions on the site, or null - /// if the user cannot be identified - /// - [CanBeNull] - User CurrentUser { get; } - } -} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Api/Persistence/UserService.cs b/Src/VideoGameQuotes.Api/Persistence/UserService.cs index 30324ee..abb1dd3 100644 --- a/Src/VideoGameQuotes.Api/Persistence/UserService.cs +++ b/Src/VideoGameQuotes.Api/Persistence/UserService.cs @@ -1,12 +1,22 @@ using System; +using System.Linq; +using JetBrains.Annotations; using Portoa.Persistence; namespace VideoGameQuotes.Api.Persistence { public interface IUserService { User Save(User user); + [CanBeNull] User FindByUsername(string name); + [CanBeNull] User FindByIpAddress(string ipAddress); + void Delete(int id); + void Ban(User user); + [CanBeNull] + User FindByUsernameOrIp(string usernameOrIp); + [NotNull] + User FindById(int id); } public class UserService : IUserService { @@ -30,5 +40,27 @@ namespace VideoGameQuotes.Api.Persistence { public User FindByIpAddress(string ipAddress) { return repository.FindByIpAddress(ipAddress); } + + [UnitOfWork] + public void Delete(int id) { + repository.Delete(id); + } + + [UnitOfWork] + public void Ban(User user) { + throw new NotImplementedException(); + } + + [UnitOfWork] + public User FindByUsernameOrIp(string usernameOrIp) { + return repository + .Records + .FirstOrDefault(user => user.Username == usernameOrIp || user.IpAddress == usernameOrIp); + } + + [UnitOfWork] + public User FindById(int id) { + return repository.FindById(id); + } } } \ No newline at end of file diff --git a/Src/VideoGameQuotes.Api/User.cs b/Src/VideoGameQuotes.Api/User.cs index 2eb310c..dc1858b 100644 --- a/Src/VideoGameQuotes.Api/User.cs +++ b/Src/VideoGameQuotes.Api/User.cs @@ -24,4 +24,10 @@ namespace VideoGameQuotes.Api { return passwordProtector.VerifyPassword(potentialPassword); } } + + public static class UserExtensions { + public static string GetUsernameOrIp(this User user) { + return user.Username ?? user.IpAddress; + } + } } \ No newline at end of file diff --git a/Src/VideoGameQuotes.Api/VideoGameQuotes.Api.csproj b/Src/VideoGameQuotes.Api/VideoGameQuotes.Api.csproj index baf8322..fb9a549 100644 --- a/Src/VideoGameQuotes.Api/VideoGameQuotes.Api.csproj +++ b/Src/VideoGameQuotes.Api/VideoGameQuotes.Api.csproj @@ -69,7 +69,6 @@ - diff --git a/Src/VideoGameQuotes.Web/Controllers/AdminController.cs b/Src/VideoGameQuotes.Web/Controllers/AdminController.cs index 034a491..c294401 100644 --- a/Src/VideoGameQuotes.Web/Controllers/AdminController.cs +++ b/Src/VideoGameQuotes.Web/Controllers/AdminController.cs @@ -1,23 +1,42 @@ -using System.Web.Mvc; +using System.Linq; +using System.Net; +using System.Web.Mvc; using Portoa.Persistence; using Portoa.Web; using Portoa.Web.ErrorHandling; +using Portoa.Web.Results; using VideoGameQuotes.Api; using VideoGameQuotes.Web.Models; using VideoGameQuotes.Web.Security; using VideoGameQuotes.Web.Services; namespace VideoGameQuotes.Web.Controllers { - [IsValidUser(Group = UserGroup.Admin)] + [VerifyUser(Group = UserGroup.Admin)] public class AdminController : Controller { - private readonly ICurrentUserProvider userProvider; + private readonly ICurrentUserProvider userProvider; private readonly IAdministrationService adminService; - public AdminController(ICurrentUserProvider userProvider, IAdministrationService adminService) { + public AdminController(ICurrentUserProvider userProvider, IAdministrationService adminService) { this.userProvider = userProvider; this.adminService = adminService; } + public ActionResult Users(int start = 1, int end = 20) { + if (start < 1 || start > end || end < 1) { + return new StatusOverrideResult(View("BadPaging")) { StatusCode = HttpStatusCode.BadRequest }; + } + + var model = new PagedModelWithUser { + Start = start, + End = end, + Records = adminService.GetPagedUsers(start, end), + TotalCount = adminService.GetAllUsers().Count(), + CurrentUser = userProvider.CurrentUser + }; + + return View(model); + } + [HttpGet] public ActionResult Create() { var model = new CreateAdminModel(); diff --git a/Src/VideoGameQuotes.Web/Controllers/HomeController.cs b/Src/VideoGameQuotes.Web/Controllers/HomeController.cs index 6f05a69..6dad0be 100644 --- a/Src/VideoGameQuotes.Web/Controllers/HomeController.cs +++ b/Src/VideoGameQuotes.Web/Controllers/HomeController.cs @@ -4,6 +4,7 @@ using System.Net.Mail; using System.Security.Cryptography; using System.Text; using System.Web.Mvc; +using Portoa.Web; using Portoa.Web.Controllers; using Portoa.Web.Security; using VideoGameQuotes.Api; @@ -12,7 +13,7 @@ using VideoGameQuotes.Web.Models; namespace VideoGameQuotes.Web.Controllers { public class HomeController : Controller { private readonly IAuthenticationService authenticationService; - private readonly ICurrentUserProvider userProvider; + private readonly ICurrentUserProvider userProvider; private static readonly string[] answers = new[] { "I AM ERROR.", @@ -26,7 +27,7 @@ namespace VideoGameQuotes.Web.Controllers { "ryu huyabasa" }; - public HomeController(IAuthenticationService authenticationService, ICurrentUserProvider userProvider) { + public HomeController(IAuthenticationService authenticationService, ICurrentUserProvider userProvider) { this.authenticationService = authenticationService; this.userProvider = userProvider; } diff --git a/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs b/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs index 13b091d..39754a6 100644 --- a/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs +++ b/Src/VideoGameQuotes.Web/Controllers/QuoteController.cs @@ -6,6 +6,7 @@ using System.Web.Mvc; using JetBrains.Annotations; using Portoa.Persistence; using Portoa.Validation.DataAnnotations; +using Portoa.Web; using Portoa.Web.Controllers; using Portoa.Web.Results; using VideoGameQuotes.Api; @@ -17,10 +18,10 @@ using VideoGameQuotes.Web.Services; namespace VideoGameQuotes.Web.Controllers { public class QuoteController : Controller { private readonly IQuoteService quoteService; - private readonly ICurrentUserProvider currentUserProvider; + private readonly ICurrentUserProvider currentUserProvider; private readonly IQuoteSearcher quoteSearcher; - public QuoteController(IQuoteService quoteService, ICurrentUserProvider currentUserProvider, IQuoteSearcher quoteSearcher) { + public QuoteController(IQuoteService quoteService, ICurrentUserProvider currentUserProvider, IQuoteSearcher quoteSearcher) { this.quoteService = quoteService; this.currentUserProvider = currentUserProvider; this.quoteSearcher = quoteSearcher; @@ -37,7 +38,7 @@ namespace VideoGameQuotes.Web.Controllers { }); } - [HttpPost, IsValidUser] + [HttpPost, VerifyUser] public JsonResult Report(ReportModel model) { if (!ModelState.IsValid) { return Json(this.CreateJsonErrorResponse("Invalid request")); @@ -48,12 +49,12 @@ namespace VideoGameQuotes.Web.Controllers { quote.AddFlag(model.Comment, model.FlagType, currentUserProvider.CurrentUser); quoteService.SaveQuote(quote); return Json(this.CreateJsonResponse()); - } catch (Exception e) { + } catch { return Json(this.CreateJsonErrorResponse("Unable to create your report")); } } - [HttpPost, IsValidUser] + [HttpPost, VerifyUser] public JsonResult Vote(VoteModel model) { if (!ModelState.IsValid) { return Json(this.CreateJsonErrorResponse("Invalid request")); @@ -89,9 +90,9 @@ namespace VideoGameQuotes.Web.Controllers { return new StatusOverrideResult(View("BadPaging")) { StatusCode = HttpStatusCode.BadRequest }; } - return View(new PagedQuoteCollectionModel { - Quotes = quoteService.GetBestQuotes(start, end), - User = currentUserProvider.CurrentUser, + return View(new PagedModelWithUser { + Records = quoteService.GetBestQuotes(start, end), + CurrentUser = currentUserProvider.CurrentUser, Start = start, End = end }); @@ -106,7 +107,7 @@ namespace VideoGameQuotes.Web.Controllers { return RedirectToAction("Quote", new { id = quote.Id, text = quote.GetUrlFriendlyText() }); } - [HttpPost, IsValidUser(Group = UserGroup.Admin)] + [HttpPost, VerifyUser(Group = UserGroup.Admin)] public ActionResult DismissFlag([GreaterThanZero]int quoteId, [GreaterThanZero]int flagId) { if (!ModelState.IsValid) { return Json(this.CreateJsonErrorResponse("quote/flag is invalid")); @@ -127,7 +128,7 @@ namespace VideoGameQuotes.Web.Controllers { } } - [HttpGet, IsValidUser(Group = UserGroup.Admin)] + [HttpGet, VerifyUser(Group = UserGroup.Admin)] public ActionResult Edit(int id) { try { var model = new EditQuoteModel(quoteService.GetQuote(id)); @@ -138,19 +139,19 @@ namespace VideoGameQuotes.Web.Controllers { } } - [HttpPost, IsValidUser(Group = UserGroup.Admin)] + [HttpPost, VerifyUser(Group = UserGroup.Admin)] public ActionResult Edit(EditQuoteModel model) { return CreateOrEditQuote(model, "Edit"); } - [IsValidUser] + [VerifyUser] public ActionResult Submit() { var model = new EditQuoteModel(); ResetEditQuoteModel(model); return View(model); } - [HttpPost, IsValidUser] + [HttpPost, VerifyUser] public ActionResult Submit(EditQuoteModel model) { return CreateOrEditQuote(model, "Submit"); } @@ -276,7 +277,7 @@ namespace VideoGameQuotes.Web.Controllers { } } - [IsValidUser(Group = UserGroup.Admin)] + [VerifyUser(Group = UserGroup.Admin)] public ActionResult Flags(int id) { try { var model = new QuoteModel { @@ -290,7 +291,7 @@ namespace VideoGameQuotes.Web.Controllers { } } - [HttpPost, IsValidUser] + [HttpPost, VerifyUser] public JsonResult CreateCategory(Category category) { try { category = quoteService.SaveCategory(category); diff --git a/Src/VideoGameQuotes.Web/Controllers/UserController.cs b/Src/VideoGameQuotes.Web/Controllers/UserController.cs new file mode 100644 index 0000000..25b3fca --- /dev/null +++ b/Src/VideoGameQuotes.Web/Controllers/UserController.cs @@ -0,0 +1,91 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Web.Mvc; +using Portoa.Persistence; +using Portoa.Validation.DataAnnotations; +using Portoa.Web.Controllers; +using Portoa.Web.Results; +using VideoGameQuotes.Api; +using VideoGameQuotes.Api.Persistence; +using VideoGameQuotes.Web.Models; +using VideoGameQuotes.Web.Security; + +namespace VideoGameQuotes.Web.Controllers { + + public class UserController : Controller { + private readonly IUserService userService; + + public UserController(IUserService userService) { + this.userService = userService; + } + + [HttpPost, VerifyUser(Group = UserGroup.Admin)] + public ActionResult Delete([GreaterThanZero]int id) { + if (!ModelState.IsValid) { + return Json(this.CreateJsonErrorResponse("Invalid user id")); + } + + try { + userService.Delete(id); + return Json(this.CreateJsonResponse()); + } catch (Exception e) { + return Json(this.CreateJsonErrorResponse(e)); + } + } + + [HttpPost, VerifyUser(Group = UserGroup.Admin)] + public ActionResult Ban([GreaterThanZero]int id) { + if (!ModelState.IsValid) { + return Json(this.CreateJsonErrorResponse("Invalid user id")); + } + + try { + userService.Delete(id); + return Json(this.CreateJsonResponse()); + } catch (Exception e) { + return Json(this.CreateJsonErrorResponse(e)); + } + } + + [HttpGet, VerifyUser(Group = UserGroup.Admin)] + public ActionResult Edit([Required]string usernameOrIp) { + if (!ModelState.IsValid) { + return GetUsernameNotFoundResult(usernameOrIp); + } + + var user = userService.FindByUsernameOrIp(usernameOrIp); + if (user == null) { + return GetUsernameNotFoundResult(usernameOrIp); + } + + return View(new EditUserModel { Id = user.Id, Username = user.Username, IpAddress = user.IpAddress, Group = user.Group }); + } + + [HttpPost, VerifyUser(Group = UserGroup.Admin)] + public ActionResult Edit(EditUserModel model) { + if (!ModelState.IsValid) { + return View(model); + } + + try { + var user = userService.FindById(model.Id); + user.Username = model.Username; + user.IpAddress = model.IpAddress; + user.Group = model.Group; + user = userService.Save(user); + return RedirectToAction("Edit", new { usernameOrIp = user.Username ?? user.IpAddress }); + } catch (EntityNotFoundException) { + ModelState.AddModelError("Id", "Invalid user ID"); + return View(model); + } + } + + private ActionResult GetUsernameNotFoundResult(string usernameOrIp) { + return new StatusOverrideResult(View("InvalidUsername", new InvalidUsernameModel { UsernameOrIp = usernameOrIp })) { + StatusCode = HttpStatusCode.NotFound + }; + } + + } +} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Global.asax.cs b/Src/VideoGameQuotes.Web/Global.asax.cs index ed18a04..d66b094 100644 --- a/Src/VideoGameQuotes.Web/Global.asax.cs +++ b/Src/VideoGameQuotes.Web/Global.asax.cs @@ -1,10 +1,14 @@ -using System.Collections.Specialized; +using System; +using System.Collections.Specialized; using System.Configuration; +using System.Web; using System.Web.Mvc; using System.Web.Routing; using Microsoft.Practices.Unity; using Portoa.Logging; using Portoa.Web; +using Portoa.Web.Controllers; +using Portoa.Web.ErrorHandling; using Portoa.Web.Models; using Portoa.Web.Security; using Portoa.Web.Unity; @@ -19,7 +23,7 @@ using VideoGameQuotes.Web.Security; using VideoGameQuotes.Web.Services; namespace VideoGameQuotes.Web { - public class MvcApplication : MvcApplicationBase { + public class MvcApplication : MvcApplicationBase { protected override void ConfigureModelBinders(ModelBinderDictionary binders) { binders @@ -37,8 +41,8 @@ namespace VideoGameQuotes.Web { Container .AddNewExtension() - .RegisterType() - .RegisterType(new InjectionProperty(attr => attr.UserProvider)) + .RegisterType, SessionBasedUserProvider>() + .RegisterType(new InjectionProperty(attr => attr.UserProvider)) .RegisterType() .RegisterType() .RegisterType() @@ -68,7 +72,11 @@ namespace VideoGameQuotes.Web { //bullshit route so that RenderAction works routes.MapRoute("mainmenu", "home/mainmenu", new { controller = "Home", action = "MainMenu" }); - routes.MapRoute("admin", "admin/{action}", new { controller = "Admin", action = "Index" }); + 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" }); + + routes.MapRoute("user-edit", "user/edit/{usernameOrIp}", new { controller = "User", action = "Edit", usernameOrIp = @"\w+" }); + routes.MapRoute("user-default", "user/{action}/{id}", new { controller = "User", action = "delete|ban", id = UrlParameter.Optional }); routes.MapRoute("api", "api/{action}/{id}/{*criteria}", new { controller = "Api" }, new { action = "game|system|category|publisher|quote", id = @"\d+|all" }); routes.MapRoute("home", "{action}", new { controller = "Home", action = "Index" }, new { action = "about|contact|login|logout" }); diff --git a/Src/VideoGameQuotes.Web/Models/InvalidUsernameModel.cs b/Src/VideoGameQuotes.Web/Models/InvalidUsernameModel.cs new file mode 100644 index 0000000..df50d9f --- /dev/null +++ b/Src/VideoGameQuotes.Web/Models/InvalidUsernameModel.cs @@ -0,0 +1,5 @@ +namespace VideoGameQuotes.Web.Models { + public class InvalidUsernameModel { + public string UsernameOrIp { get; set; } + } +} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Models/PagedModelWithUser.cs b/Src/VideoGameQuotes.Web/Models/PagedModelWithUser.cs new file mode 100644 index 0000000..a5f0a42 --- /dev/null +++ b/Src/VideoGameQuotes.Web/Models/PagedModelWithUser.cs @@ -0,0 +1,8 @@ +using Portoa.Web.Models; +using VideoGameQuotes.Api; + +namespace VideoGameQuotes.Web.Models { + public class PagedModelWithUser : PagedModel { + public User CurrentUser { get; set; } + } +} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Models/PagedQuoteCollectionModel.cs b/Src/VideoGameQuotes.Web/Models/PagedQuoteCollectionModel.cs deleted file mode 100644 index 3d72612..0000000 --- a/Src/VideoGameQuotes.Web/Models/PagedQuoteCollectionModel.cs +++ /dev/null @@ -1,14 +0,0 @@ -using System; - -namespace VideoGameQuotes.Web.Models { - public class PagedQuoteCollectionModel : QuoteCollectionModel { - public int Start { get; set; } - public int End { get; set; } - public int PageSize { get { return End - Start + 1; } } - public int NextStart { get { return End + 1; } } - public int NextEnd { get { return End + PageSize; } } - public int PreviousStart { get { return Math.Max(0, Start - PageSize); } } - public int PreviousEnd { get { return Math.Max(PageSize - 1, End - PageSize); } } - public bool HasPrevious { get { return Start > 0; } } - } -} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Models/QuoteModel.cs b/Src/VideoGameQuotes.Web/Models/QuoteModel.cs index 78ec05e..8779f55 100644 --- a/Src/VideoGameQuotes.Web/Models/QuoteModel.cs +++ b/Src/VideoGameQuotes.Web/Models/QuoteModel.cs @@ -2,6 +2,12 @@ using VideoGameQuotes.Api; namespace VideoGameQuotes.Web.Models { + + public class PagedUserModel { + public int Start { get; set; } + public int End { get; set; } + } + public class QuoteModel { [NotNull] public Quote Quote { get; set; } diff --git a/Src/VideoGameQuotes.Web/Models/UserModel.cs b/Src/VideoGameQuotes.Web/Models/UserModel.cs new file mode 100644 index 0000000..87df560 --- /dev/null +++ b/Src/VideoGameQuotes.Web/Models/UserModel.cs @@ -0,0 +1,28 @@ +using System; +using System.Linq; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Web.Mvc; +using VideoGameQuotes.Api; + +namespace VideoGameQuotes.Web.Models { + public class UserModel { + public User CurrentUser { get; set; } + public User User { get; set; } + } + + public class EditUserModel { + [Required] + public int Id { get; set; } + public string Username { get; set; } + [Required] + public UserGroup Group { get; set; } + public string IpAddress { get; set; } + + public IEnumerable GetGroupList(UserGroup selectedGroup) { + return Enum.GetValues(typeof(UserGroup)) + .Cast() + .Select(group => new SelectListItem { Text = group.ToString(), Value = group.ToString()/*, Selected = group == selectedGroup*/ }); + } + } +} \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Security/SessionBasedUserProvider.cs b/Src/VideoGameQuotes.Web/Security/SessionBasedUserProvider.cs index 258082e..b3fa233 100644 --- a/Src/VideoGameQuotes.Web/Security/SessionBasedUserProvider.cs +++ b/Src/VideoGameQuotes.Web/Security/SessionBasedUserProvider.cs @@ -1,10 +1,11 @@ using System.Web; +using Portoa.Web; using Portoa.Web.Session; using VideoGameQuotes.Api; using VideoGameQuotes.Api.Persistence; namespace VideoGameQuotes.Web.Security { - public class SessionBasedUserProvider : ICurrentUserProvider { + public class SessionBasedUserProvider : ICurrentUserProvider { private readonly IUserService userService; private readonly ISessionStore sessionStore; private readonly HttpContextBase httpContext; diff --git a/Src/VideoGameQuotes.Web/Security/IsValidUserAttribute.cs b/Src/VideoGameQuotes.Web/Security/VerifyUserAttribute.cs similarity index 79% rename from Src/VideoGameQuotes.Web/Security/IsValidUserAttribute.cs rename to Src/VideoGameQuotes.Web/Security/VerifyUserAttribute.cs index 143f60b..96f93c1 100644 --- a/Src/VideoGameQuotes.Web/Security/IsValidUserAttribute.cs +++ b/Src/VideoGameQuotes.Web/Security/VerifyUserAttribute.cs @@ -6,12 +6,12 @@ using VideoGameQuotes.Api; namespace VideoGameQuotes.Web.Security { [NeedsBuildUp] - public class IsValidUserAttribute : ActionFilterAttribute { - public IsValidUserAttribute() { + public class VerifyUserAttribute : ActionFilterAttribute { + public VerifyUserAttribute() { Group = UserGroup.User; } - public ICurrentUserProvider UserProvider { get; set; } + public ICurrentUserProvider UserProvider { get; set; } public UserGroup Group { get; set; } public override void OnActionExecuting(ActionExecutingContext filterContext) { diff --git a/Src/VideoGameQuotes.Web/Services/AdministrationService.cs b/Src/VideoGameQuotes.Web/Services/AdministrationService.cs index 3ee025d..430b015 100644 --- a/Src/VideoGameQuotes.Web/Services/AdministrationService.cs +++ b/Src/VideoGameQuotes.Web/Services/AdministrationService.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Linq; using Portoa.Persistence; using VideoGameQuotes.Api; @@ -9,6 +10,7 @@ namespace VideoGameQuotes.Web.Services { IEnumerable GetFlaggedQuotes(); User GetUser(int id); IEnumerable GetAllUsers(); + IEnumerable GetPagedUsers(int start, int end); } public class AdministrationService : IAdministrationService { @@ -42,5 +44,15 @@ namespace VideoGameQuotes.Web.Services { public IEnumerable GetAllUsers() { return userRepository.Records; } + + [UnitOfWork] + public IEnumerable GetPagedUsers(int start, int end) { + return userRepository + .Records + .OrderBy(user => user.Username) + .ThenBy(user => user.IpAddress) + .Skip(start - 1) + .Take(end - start + 1); + } } } \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj b/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj index bb0f650..43ab00f 100644 --- a/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj +++ b/Src/VideoGameQuotes.Web/VideoGameQuotes.Web.csproj @@ -87,24 +87,27 @@ + + - + + - + @@ -139,10 +142,12 @@ + + - + @@ -163,6 +168,8 @@ + + Web.config diff --git a/Src/VideoGameQuotes.Web/Views/Admin/SingleUser.ascx b/Src/VideoGameQuotes.Web/Views/Admin/SingleUser.ascx new file mode 100644 index 0000000..736de40 --- /dev/null +++ b/Src/VideoGameQuotes.Web/Views/Admin/SingleUser.ascx @@ -0,0 +1,26 @@ +<%@ Control Language="C#" Inherits="System.Web.Mvc.ViewUserControl" %> +<%@ Import Namespace="VideoGameQuotes.Api" %> + +
+

+ <%: Model.User.Username ?? "" %> + [<%: Model.User.IpAddress %>] +

+ +
+
Created
+
<%: Model.User.Created %>
+
Group
+
<%: Model.User.Group %>
+
+ + <% if (Model.CurrentUser.Group == UserGroup.Admin) { %> +
+ <%= Html.ActionLink("edit", "edit", "User", new { usernameOrIp = Model.User.Username ?? Model.User.IpAddress }, null) %> | + <%= Html.ActionLink("delete", "delete", "User", new { id = Model.User.Id }, null)%> | + <%= Html.ActionLink("ban", "ban", "User", new { id = Model.User.Id }, null)%> +
+ <% } %> + +
+
\ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Views/Admin/Users.aspx b/Src/VideoGameQuotes.Web/Views/Admin/Users.aspx new file mode 100644 index 0000000..4f6351d --- /dev/null +++ b/Src/VideoGameQuotes.Web/Views/Admin/Users.aspx @@ -0,0 +1,19 @@ +<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage>" MasterPageFile="~/Views/Shared/Site.Master" %> +<%@ Import Namespace="VideoGameQuotes.Web.Models" %> +Manage Users + +

Users

+ +

+ <% if (Model.HasPrevious) { %> + <%= Html.ActionLink("previous", "users", new { start = Model.PreviousStart, end = Model.PreviousEnd }) %> + <% } %> + <%= Html.ActionLink("next", "users", new { start = Model.NextStart, end = Model.NextEnd }) %> +

+ + <% + foreach (var user in Model.Records) { + Html.RenderPartial("SingleUser", new UserModel { CurrentUser = Model.CurrentUser, User = user }); + } + %> +
\ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Views/Quote/Best.aspx b/Src/VideoGameQuotes.Web/Views/Quote/Best.aspx index 371ae1d..4343a19 100644 --- a/Src/VideoGameQuotes.Web/Views/Quote/Best.aspx +++ b/Src/VideoGameQuotes.Web/Views/Quote/Best.aspx @@ -1,4 +1,4 @@ -<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> +<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage>" MasterPageFile="~/Views/Shared/Site.Master" %> <%@ Import Namespace="VideoGameQuotes.Web.Models" %> Best: <%= Model.Start %> – <%= Model.End %> @@ -11,8 +11,8 @@

<% - foreach (var quote in Model.Quotes) { - Html.RenderPartial("SingleQuote", new QuoteModel { Quote = quote, User = Model.User }); + foreach (var quote in Model.Records) { + Html.RenderPartial("SingleQuote", new QuoteModel { Quote = quote, User = Model.CurrentUser }); } %>
diff --git a/Src/VideoGameQuotes.Web/Views/Quote/Quote.aspx b/Src/VideoGameQuotes.Web/Views/Quote/Quote.aspx index 2364b43..76c0a52 100644 --- a/Src/VideoGameQuotes.Web/Views/Quote/Quote.aspx +++ b/Src/VideoGameQuotes.Web/Views/Quote/Quote.aspx @@ -1,5 +1,6 @@ <%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> -<%: Model.Quote.Game.Name %> +<%@ Import Namespace="VideoGameQuotes.Api" %> +<%: Model.Quote.GetAbbreviatedText() %> <% Html.RenderPartial("SingleQuote", Model); %> \ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Views/Quote/BadPaging.aspx b/Src/VideoGameQuotes.Web/Views/Shared/BadPaging.aspx similarity index 100% rename from Src/VideoGameQuotes.Web/Views/Quote/BadPaging.aspx rename to Src/VideoGameQuotes.Web/Views/Shared/BadPaging.aspx diff --git a/Src/VideoGameQuotes.Web/Views/Shared/SingleQuote.ascx b/Src/VideoGameQuotes.Web/Views/Shared/SingleQuote.ascx index aaabc7f..ba9f760 100644 --- a/Src/VideoGameQuotes.Web/Views/Shared/SingleQuote.ascx +++ b/Src/VideoGameQuotes.Web/Views/Shared/SingleQuote.ascx @@ -19,31 +19,35 @@ -

+

<%: Model.Quote.Text %> -

-
- -
-
-
-
-
Game
-
<%= Html.ActionLink(Model.Quote.Game.Name, "browse", "Quote", new { qualifiers = "game/" + Model.Quote.Game.Id }, null) %>
-
Added
-
<%: Model.Quote.GetHumanReadableTimeSinceCreated() %>
-
Categories
-
- <%= Model.Quote.Categories.Implode(category => Html.ActionLink(category.Name, "browse", "Quote", new { qualifiers = "category/" + category.Id }, null).ToString(), ", ")%> -
-
+
+

+ added <%: Model.Quote.GetHumanReadableTimeSinceCreated() %> + <% if (Model.User != null && Model.User.Group >= UserGroup.Admin) { %> + by <%= Html.ActionLink(Model.Quote.Creator.GetUsernameOrIp(), "edit", "user", new { usernameOrIp = Model.Quote.Creator.GetUsernameOrIp()}, null) %> + <% } %> +

+ + +
+ +
+
diff --git a/Src/VideoGameQuotes.Web/Views/Shared/Unknown.aspx b/Src/VideoGameQuotes.Web/Views/Shared/Unknown.aspx index 83c6a64..412e780 100644 --- a/Src/VideoGameQuotes.Web/Views/Shared/Unknown.aspx +++ b/Src/VideoGameQuotes.Web/Views/Shared/Unknown.aspx @@ -1,13 +1,9 @@ -<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> +<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage>" MasterPageFile="~/Views/Shared/Site.Master" %> Unknown Error

WTF⁈

-
- BLARGH!! -
-

WTF⁈ How did this even happen⁈

diff --git a/Src/VideoGameQuotes.Web/Views/User/Edit.aspx b/Src/VideoGameQuotes.Web/Views/User/Edit.aspx new file mode 100644 index 0000000..60b3d35 --- /dev/null +++ b/Src/VideoGameQuotes.Web/Views/User/Edit.aspx @@ -0,0 +1,31 @@ +<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> +<%@ Import Namespace="Portoa.Web.Util" %> +Edit User + +

Edit User

+ + <% using (Html.BeginForm()) { %> + <%= Html.HiddenFor(user => user.Id) %> + +

+ <%= Html.LabelFor(user => user.Username) %> +
+ <%= Html.TextBoxFor(user => user.Username) %> +

+ +

+ <%= Html.LabelFor(user => user.IpAddress) %> +
+ <%= Html.TextBoxFor(user => user.IpAddress) %> +

+ +

+ <%= Html.LabelFor(user => user.Group) %> +
+ <%= Html.DropDownListFor(user => user.Group, Model.GetGroupList(Model.Group))%> +

+ + <%= Html.Submit("Save") %> + <% } %> + +
\ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/Views/User/InvalidUsername.aspx b/Src/VideoGameQuotes.Web/Views/User/InvalidUsername.aspx new file mode 100644 index 0000000..11a448b --- /dev/null +++ b/Src/VideoGameQuotes.Web/Views/User/InvalidUsername.aspx @@ -0,0 +1,11 @@ +<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> +User Not Found + +

+ No user exists with username or IP address "<%: Model.UsernameOrIp %>". +

+ +

+ <%= Html.ActionLink("list of users", "Users", "Admin") %> +

+
\ No newline at end of file diff --git a/Src/VideoGameQuotes.Web/media/css/quote.css b/Src/VideoGameQuotes.Web/media/css/quote.css index df6c8ce..bc6e3b9 100644 --- a/Src/VideoGameQuotes.Web/media/css/quote.css +++ b/Src/VideoGameQuotes.Web/media/css/quote.css @@ -1,6 +1,8 @@ -.quote-container { - width: 500px; - margin: auto; +/* box shadow stuff from http://nicolasgallagher.com/css-drop-shadows-without-images/demo/ */ + +.quote-container { + width: 600px; + margin: 0 auto 50px auto; position: relative; } @@ -8,8 +10,6 @@ float: left; font-family: Georgia, serif; padding: 5px; - color: #FFFFFF; - background-color: #6699FF; } .quote-score { cursor: help; @@ -17,19 +17,59 @@ .quote-score, .vote-container { text-align: center; } +.vote-container { + height: 20px; +} +.quote-data { + margin-bottom: 10px; +} .vote-for, .vote-against { cursor: pointer; } .vote-for:hover, .vote-against:hover { - color: #FFFF99; + color: #46C46E; } -.quote-container .quote-data .quote-text { +.quote-text { float: left; font-weight: bold; font-size: 20px; } +.quote-score-container { + width: 40px; + font-size: 20px; +} +.quote-text { + box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset; + -moz-box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset; + -webkit-box-shadow: 0 15px 10px -10px rgba(0, 0, 0, 0.5), 0 1px 4px rgba(0, 0, 0, 0.3), 0 0 40px rgba(0, 0, 0, 0.1) inset; + background-color: #FFFFFF; + padding: 1.5em 1em; + width: 500px; + position: relative; +} +.quote-game-link { +} +.quote-categories a { + border: 1px solid #999999 !important; + background-color: #BBBBBB; + padding: 5px 8px; + display: block; + color: #FFFFFF !important; +} +.quote-categories a:hover { + background-color: #999999; + border: 1px solid #BBBBBB !important; +} +.quote-categories li { + float: left; + margin-right: 2px; +} + +.quote-details-created { + float: left; +} .quote-links { float: right; }