* improved default browse page

* fixed bug where you couldn't add more categories if none already existed
* added ability to delete quotes
* added call handler to delete the search index when a quote is deleted
This commit is contained in:
tmont 2011-03-04 08:20:20 +00:00
parent 2ac8d0155b
commit a41335c5ba
11 changed files with 115 additions and 13 deletions

View File

@ -0,0 +1,31 @@
using Microsoft.Practices.Unity;
using Microsoft.Practices.Unity.InterceptionExtension;
using Portoa.Search;
using VideoGameQuotes.Api;
namespace VideoGameQuotes.Web.Configuration {
/// <summary>
/// Call handler that deletes the search index whenever a quote is deleted
/// </summary>
public class DeleteSearchIndexCallHandler : ICallHandler {
private readonly IUnityContainer container;
public DeleteSearchIndexCallHandler(IUnityContainer container) {
this.container = container;
}
public IMethodReturn Invoke(IMethodInvocation input, GetNextHandlerDelegate getNext) {
var quoteId = (int)input.Arguments[0];
var returnValue = getNext()(input, getNext);
if (returnValue.Exception != null) {
return returnValue;
}
container.Resolve<ISearchIndexBuilder<Quote, int>>().DeleteIndex(new Quote { Id = quoteId });
return returnValue;
}
public int Order { get; set; }
}
}

View File

@ -43,6 +43,12 @@ namespace VideoGameQuotes.Web.Configuration {
.AddPolicy("UpdateSearchIndexPolicy") .AddPolicy("UpdateSearchIndexPolicy")
.AddCallHandler<UpdateSearchIndexCallHandler>() .AddCallHandler<UpdateSearchIndexCallHandler>()
.AddMatchingRule(new QuoteUpdatedMatchingRule()); .AddMatchingRule(new QuoteUpdatedMatchingRule());
Container
.Configure<Interception>()
.AddPolicy("DeleteSearchIndexPolicy")
.AddCallHandler<DeleteSearchIndexCallHandler>()
.AddMatchingRule(new QuoteDeletedMatchingRule());
} }
#region lucene-related factories #region lucene-related factories
@ -60,6 +66,7 @@ namespace VideoGameQuotes.Web.Configuration {
} }
#endregion #endregion
#region matching rules
private class QuoteUpdatedMatchingRule : IMatchingRule { private class QuoteUpdatedMatchingRule : IMatchingRule {
private static readonly MethodBase saveMethod = typeof(IRepository<Quote, int>).GetMethod("Save", new[] { typeof(Quote) }); private static readonly MethodBase saveMethod = typeof(IRepository<Quote, int>).GetMethod("Save", new[] { typeof(Quote) });
@ -67,5 +74,14 @@ namespace VideoGameQuotes.Web.Configuration {
return member == saveMethod; return member == saveMethod;
} }
} }
private class QuoteDeletedMatchingRule : IMatchingRule {
private static readonly MethodBase deleteMethod = typeof(IRepository<Quote, int>).GetMethod("Delete", new[] { typeof(int) });
public bool Matches(MethodBase member) {
return member == deleteMethod;
}
}
#endregion
} }
} }

View File

@ -31,13 +31,23 @@ namespace VideoGameQuotes.Web.Controllers {
return this.SerializeToJson(data); return this.SerializeToJson(data);
} }
[HttpPost, VerifyUser(Group = UserGroup.Admin)]
public ActionResult Delete(int id) {
try {
quoteService.Delete(id);
return Json(this.CreateJsonResponse());
} catch (Exception e) {
return Json(this.CreateJsonErrorResponse(e));
}
}
public ActionResult Browse(BrowseModel model, int page = 1) { public ActionResult Browse(BrowseModel model, int page = 1) {
if (page < 1) { if (page < 1) {
return new StatusOverrideResult(View("BadPaging")) { StatusCode = HttpStatusCode.BadRequest }; return new StatusOverrideResult(View("BadPaging")) { StatusCode = HttpStatusCode.BadRequest };
} }
if (model.IsEmpty) { if (model.IsEmpty) {
return View("DefaultBrowse"); return View("DefaultBrowse", quoteService.GetDefaultBrowseModel());
} }
model.CurrentUser = currentUserProvider.CurrentUser; model.CurrentUser = currentUserProvider.CurrentUser;

View File

@ -75,7 +75,8 @@ namespace VideoGameQuotes.Web {
//these routes don't work with constraints in mono...? //these routes don't work with constraints in mono...?
routes.MapSmartRoute("quote-edit", "quote/edit/{id}", new { controller = "Quote", action = "Edit" }); routes.MapSmartRoute("quote-edit", "quote/edit/{id}", new { controller = "Quote", action = "Edit" });
routes.MapSmartRoute("individual-quote-with-text", "quote/{id}/{*text}", new { controller = "Quote", action = "Quote" }); routes.MapSmartRoute("quote-delete", "quote/delete", new { controller = "Quote", action = "Delete" });
routes.MapSmartRoute("single-quote", "quote/{id}/{*text}", new { controller = "Quote", action = "Quote" });
routes.MapSmartRoute("dismiss-flag", "dismiss-flag", new { controller = "Quote", action = "DismissFlag" }); routes.MapSmartRoute("dismiss-flag", "dismiss-flag", new { controller = "Quote", action = "DismissFlag" });
routes.MapSmartRoute("default", "", new { controller = "Home", action = "Index" }); routes.MapSmartRoute("default", "", new { controller = "Home", action = "Index" });

View File

@ -0,0 +1,9 @@
namespace VideoGameQuotes.Web.Models {
public class DefaultBrowseModel {
public int TotalNumberOfQuotes { get; set; }
public int TotalNumberOfGames { get; set; }
public int TotalNumberOfSystems { get; set; }
public int TotalNumberOfPublishers { get; set; }
public int TotalNumberOfCategories { get; set; }
}
}

View File

@ -25,6 +25,8 @@ namespace VideoGameQuotes.Web.Services {
Vote GetVoteOrCreateNew(Quote quote, User voter); Vote GetVoteOrCreateNew(Quote quote, User voter);
IEnumerable<Quote> GetBrowsableQuotes(BrowseModel model, int start, int end, out int totalCount); IEnumerable<Quote> GetBrowsableQuotes(BrowseModel model, int start, int end, out int totalCount);
Quote GetQuoteForDayOfYear(int day); Quote GetQuoteForDayOfYear(int day);
DefaultBrowseModel GetDefaultBrowseModel();
void Delete(int id);
} }
public class QuoteService : IQuoteService { public class QuoteService : IQuoteService {
@ -53,6 +55,22 @@ namespace VideoGameQuotes.Web.Services {
this.publisherRepository = publisherRepository; this.publisherRepository = publisherRepository;
} }
[UnitOfWork]
public DefaultBrowseModel GetDefaultBrowseModel() {
return new DefaultBrowseModel {
TotalNumberOfCategories = categoryRepository.Records.Count(),
TotalNumberOfSystems = systemRepository.Records.Count(),
TotalNumberOfPublishers = publisherRepository.Records.Count(),
TotalNumberOfGames = gameRepository.Records.Count(),
TotalNumberOfQuotes = quoteRepository.Records.Count()
};
}
[UnitOfWork]
public void Delete(int id) {
quoteRepository.Delete(id);
}
[UnitOfWork] [UnitOfWork]
public Game GetGame(int id) { public Game GetGame(int id) {
return gameRepository.FindById(id); return gameRepository.FindById(id);

View File

@ -105,6 +105,7 @@
</Reference> </Reference>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="Configuration\DeleteSearchIndexCallHandler.cs" />
<Compile Include="Configuration\EnableLogging.cs" /> <Compile Include="Configuration\EnableLogging.cs" />
<Compile Include="Configuration\EnableSearchWithLucene.cs" /> <Compile Include="Configuration\EnableSearchWithLucene.cs" />
<Compile Include="Configuration\UpdateSearchIndexCallHandler.cs" /> <Compile Include="Configuration\UpdateSearchIndexCallHandler.cs" />
@ -115,6 +116,7 @@
<Compile Include="Controllers\HomeController.cs" /> <Compile Include="Controllers\HomeController.cs" />
<Compile Include="Controllers\PublisherController.cs" /> <Compile Include="Controllers\PublisherController.cs" />
<Compile Include="Controllers\SystemController.cs" /> <Compile Include="Controllers\SystemController.cs" />
<Compile Include="Models\DefaultBrowseModel.cs" />
<Compile Include="Models\EditCategoryModel.cs" /> <Compile Include="Models\EditCategoryModel.cs" />
<Compile Include="Models\EditGameModel.cs" /> <Compile Include="Models\EditGameModel.cs" />
<Compile Include="Models\EditPublisherModel.cs" /> <Compile Include="Models\EditPublisherModel.cs" />
@ -210,6 +212,7 @@
<Content Include="Views\Home\About.aspx" /> <Content Include="Views\Home\About.aspx" />
<Content Include="Views\Home\Contact.aspx" /> <Content Include="Views\Home\Contact.aspx" />
<Content Include="Views\Home\ContactSuccess.aspx" /> <Content Include="Views\Home\ContactSuccess.aspx" />
<Content Include="Views\Quote\DeleteSuccess.aspx" />
<Content Include="Views\Shared\CaptchaJavaScript.ascx" /> <Content Include="Views\Shared\CaptchaJavaScript.ascx" />
<Content Include="Views\Shared\PagingMenu.ascx" /> <Content Include="Views\Shared\PagingMenu.ascx" />
<Content Include="Views\Quote\QuoteFormJavaScript.ascx" /> <Content Include="Views\Quote\QuoteFormJavaScript.ascx" />
@ -236,7 +239,9 @@
<Content Include="Views\Shared\Unknown.aspx" /> <Content Include="Views\Shared\Unknown.aspx" />
<Content Include="Views\User\Edit.aspx" /> <Content Include="Views\User\Edit.aspx" />
<Content Include="Views\User\InvalidUsername.aspx" /> <Content Include="Views\User\InvalidUsername.aspx" />
<Content Include="Web.config" /> <Content Include="Web.config">
<SubType>Designer</SubType>
</Content>
<Content Include="Web.Debug.config"> <Content Include="Web.Debug.config">
<DependentUpon>Web.config</DependentUpon> <DependentUpon>Web.config</DependentUpon>
</Content> </Content>

View File

@ -1,14 +1,16 @@
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %> <%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage<VideoGameQuotes.Web.Models.DefaultBrowseModel>" MasterPageFile="~/Views/Shared/Site.Master" %>
<asp:Content runat="server" ID="Title" ContentPlaceHolderID="TitleContent">Browse</asp:Content> <asp:Content runat="server" ID="Title" ContentPlaceHolderID="TitleContent">Browse</asp:Content>
<asp:Content runat="server" ID="Main" ContentPlaceHolderID="MainContent"> <asp:Content runat="server" ID="Main" ContentPlaceHolderID="MainContent">
<h2>Browse</h2> <p>
There are currently <strong><%= Model.TotalNumberOfQuotes %></strong> quotes. Choose wisely.
</p>
<div id="browse-default-menu"> <div id="browse-default-menu">
<ul> <ul>
<li><a href="#" id="browse-game">Games</a></li> <li><a href="#" id="browse-game">Games</a> <small>(<%= Model.TotalNumberOfGames %>)</small></li>
<li><a href="#" id="browse-system">Systems</a></li> <li><a href="#" id="browse-system">Systems</a> <small>(<%= Model.TotalNumberOfSystems %>)</small></li>
<li><a href="#" id="browse-category">Categories</a></li> <li><a href="#" id="browse-category">Categories</a> <small>(<%= Model.TotalNumberOfCategories %>)</small></li>
<li><a href="#" id="browse-publisher">Publishers</a></li> <li><a href="#" id="browse-publisher">Publishers</a> <small>(<%= Model.TotalNumberOfPublishers %>)</small></li>
</ul> </ul>
</div> </div>

View File

@ -0,0 +1,9 @@
<%@ Page Title="" Language="C#" Inherits="System.Web.Mvc.ViewPage" MasterPageFile="~/Views/Shared/Site.Master" %>
<asp:Content runat="server" ID="Title" ContentPlaceHolderID="TitleContent">Quote Successfully Deleted</asp:Content>
<asp:Content runat="server" ID="Main" ContentPlaceHolderID="MainContent">
<h2>Wild Success!</h2>
<p>
The quote and all its votes and flags were deleted.
</p>
</asp:Content>

View File

@ -16,8 +16,8 @@
</p> </p>
<p> <p>
Anyway, try <%= Html.ActionLink("searching", "Search", "Quote") %> for the quote Anyway, try searching for the quote you were hoping to find using the little search box up
you were hoping to find. Maybe that will make you less of a stain on humanity. there in the top right corner. Maybe that will make you less of a stain on humanity.
</p> </p>
<p> <p>

View File

@ -28,11 +28,12 @@
</p> </p>
<p class="quote-links"> <p class="quote-links">
<a class="quote-flag-icon" href="#" title="flag this quote as inaccurate, fake, spam, duplicate, etc."></a> <a class="quote-flag-icon quote-flag-link" href="#" title="flag this quote as inaccurate, fake, spam, duplicate, etc."></a>
<a class="quote-permalink" href="<%= Url.Action("Quote", "Quote", new { id = Model.Quote.Id, text = Model.Quote.GetUrlFriendlyText() }) %>" title="permanent link to this quote"></a> <a class="quote-permalink" href="<%= Url.Action("Quote", "Quote", new { id = Model.Quote.Id, text = Model.Quote.GetUrlFriendlyText() }) %>" title="permanent link to this quote"></a>
<% if (Model.User != null && Model.User.Group >= UserGroup.Admin) { %> <% if (Model.User != null && Model.User.Group >= UserGroup.Admin) { %>
<a class="edit-icon" href="<%= Url.Action("Edit", "Quote", new { id = Model.Quote.Id }) %>" title="edit this quote"></a> <a class="edit-icon" href="<%= Url.Action("Edit", "Quote", new { id = Model.Quote.Id }) %>" title="edit this quote"></a>
<small>(<%= Model.Quote.FlagCount %>)</small> <a class="delete-icon delete-quote-link" href="#" title="delete this quote and all its flags and votes"></a>
<strong>[<%= Model.Quote.FlagCount %>]</strong>
<% } %> <% } %>
</p> </p>
</div> </div>