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 %>
-
-
-
-
-
-
report
- <% if (Model.User != null && Model.User.Group >= UserGroup.Admin) { %>
- | <%= Html.ActionLink("flags", "flags", "quote", new { id = Model.Quote.Id }, null) %>
- | <%= Html.ActionLink("edit", "edit", "quote", new { id = Model.Quote.Id }, null) %>
- <% } %>
-
-
- - 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) %>
+ <% } %>
+
+
+
+ report |
+ <%= Html.ActionLink("permalink", "quote", "quote", new { id = Model.Quote.Id, text = Model.Quote.GetUrlFriendlyText() }, new { title = "permanent link to this quote" })%>
+ <% if (Model.User != null && Model.User.Group >= UserGroup.Admin) { %>
+ | <%= Html.ActionLink("edit", "edit", "quote", new { id = Model.Quote.Id }, null) %>
+ (<%= Model.Quote.Flags.Count() %> flags)
+ <% } %>
+
+
+
+
+
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⁈
-
-
-
-
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;
}