Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Searchable blog posts #235

Open
wants to merge 25 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
3ab3f1e
Blogs: the "Id" property of the "BlogInfo" is now a GUID to ensure th…
abjerner May 3, 2018
13d8bc0
Blogs: The "BlogRssItem" class should include "Guid" and "Description…
abjerner May 3, 2018
dcfe22e
Blogs: Added POCO for a database RSS item
abjerner May 3, 2018
b5e62ff
Blogs: Added logic for retrieving blog posts from the database
abjerner May 3, 2018
e1eec34
Blogs: added custom Examine indexer for blog posts
abjerner May 3, 2018
7818ecf
Blogs: integrated blog posts in the global search
abjerner May 3, 2018
609de9c
Blogs: Using full name for the class name as "Search" now refers to t…
abjerner May 3, 2018
cabc36f
Blogs: Moved private static helper methods to the "BlogRssUtils" clas…
abjerner May 10, 2018
35313f8
Blogs: renamed "BlogRssUtils" to "BlogUtils"
abjerner May 10, 2018
fc32672
Blogs: Introduced new "BlogPostsCache" class
abjerner May 10, 2018
1f6e3c5
Blogs: changed the constructor for "BlogItemSearchResult" a bit
abjerner May 10, 2018
c52473f
Blogs: added method to the cache for getting posts from a specific blog
abjerner May 10, 2018
437edcd
Blogs: we should reference to local logo URL
abjerner May 10, 2018
edf1a18
Blogs: more work on the update logic
abjerner May 10, 2018
f5f55e5
Blogs: might as well change the name a bit as the service now *is* fo…
abjerner May 10, 2018
b2adf08
Blogs: The view should now use the cache instead of the service
abjerner May 10, 2018
5761f93
Blogs: code style cleanup
abjerner May 10, 2018
f2170e3
Blogs: much better :D
abjerner May 10, 2018
1f0d2a6
Blogs: #H5IS
abjerner May 10, 2018
55cf419
Blogs: get the value of the <guid> element to be used as a unique ide…
abjerner May 10, 2018
7106be5
Blogs: I can haz description?
abjerner May 10, 2018
5a7c419
Merge branch 'blog-posts-search' of https://github.com/abjerner/OurUm…
nul800sebastiaan May 21, 2018
154c0c8
Adds migrations to create database table to blog post items
nul800sebastiaan May 21, 2018
a3947e4
Don't show raw HTML coming from external blogs
nul800sebastiaan May 21, 2018
8255d08
Merge remote-tracking branch 'origin/master' into abjerner-blog-posts…
nul800sebastiaan May 21, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion OurUmbraco.Site/OurUmbraco.Site.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -4396,7 +4396,8 @@
<Content Include="Views\Partials\Grid\Bootstrap2.cshtml" />
<Content Include="Views\Partials\Grid\Bootstrap2-Fluid.cshtml" />
<Content Include="Views\MacroPartials\Members\ResetPassword.cshtml" />
<Content Include="Views\Partials\Members\ResetPassword.cshtml" />
<Content Include="Views\Partials\Members\ResetPassword.cshtml" />
<Content Include="Views\Partials\Search\BlogItem.cshtml" />
<None Include="web.Debug.config">
<DependentUpon>web.config</DependentUpon>
</None>
Expand Down
8 changes: 6 additions & 2 deletions OurUmbraco.Site/Views/Partials/Community/Blogs.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@

@inherits OurUmbraco.Our.Models.OurUmbracoTemplatePage
@{
var service = new BlogPostsService();

var items = service.GetCachedBlogPosts(60, 4).ToArray();
var cache = new BlogPostsCache();

var blogs = cache.GetBlogs();
ViewBag.Blogs = blogs;

var items = cache.GetCachedBlogPosts(60, 4).ToArray();

if (items.Length == 0)
{
Expand Down
32 changes: 32 additions & 0 deletions OurUmbraco.Site/Views/Partials/Search/BlogItem.cshtml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
@using OurUmbraco.Community.BlogPosts
@using Skybrud.Essentials.Strings
@inherits UmbracoViewPage<OurUmbraco.Search.BlogItemSearchResult>

@{

if (Model.Blog == null)
{
return;
}

}

<li>
<a href="@Model.Item.Link" target="_blank" rel="noopener">
<div class="type-icon">
<i class="icon-Rss"></i>
</div>
<div class="type-context">
<div class="type-name">
@Model.Item.Title
<span style="color: #445255; font-size: 14px;">(via @Model.Blog.Title)</span>
</div>
<span class="type-datetime">Published: @Model.Item.PublishedDate.ToString("MMMM dd, yyyy")</span>
@if (Model.Item.HasDescription)
{
var description = StringUtils.StripHtml(Model.Item.Description);
<div class="type-description">@Umbraco.Truncate(description, 300)</div>
}
</div>
</a>
</li>
30 changes: 28 additions & 2 deletions OurUmbraco.Site/Views/Partials/SearchResults.cshtml
Original file line number Diff line number Diff line change
@@ -1,6 +1,15 @@
@using OurUmbraco.Our
@using OurUmbraco.Community.BlogPosts
@using OurUmbraco.Our
@using OurUmbraco.Search
@using Skybrud.Essentials.Json
@model OurUmbraco.Our.Models.SearchResultContentModel

@{

BlogPostsService blogs = null;

}

<div class="plain">
<!-- search OVERVIEW START -->
<section class="search-results">
Expand Down Expand Up @@ -69,7 +78,24 @@
}

<ul class="search-all-results docs-search-listing">
@foreach(var result in Model.Results.SearchResults) {
@foreach(var result in Model.Results.SearchResults) {

string nodeTypeAlias = result["nodeTypeAlias"];

if (nodeTypeAlias == "blogItem")
{

string rawData;
if (result.Fields.TryGetValue("data", out rawData))
{
BlogInfo blog = (blogs = (blogs ?? new BlogPostsService())).GetBlogs().FirstOrDefault(x => x.Id.ToString() == result["blogId"]);
@Html.Partial("~/Views/Partials/Search/BlogItem.cshtml", new BlogItemSearchResult(result, blog))
continue;
}

}


<li class="@result.SolvedClass()">
<a href="@result.FullUrl()">
<div class="type-icon">
Expand Down
16 changes: 16 additions & 0 deletions OurUmbraco.Site/config/ExamineIndex.config
Original file line number Diff line number Diff line change
Expand Up @@ -111,5 +111,21 @@ More information and documentation can be found on CodePlex: http://umbracoexami
<add Name="memberId" />
</IndexUserFields>
</IndexSet>

<!-- Documentation (Custom) -->
<IndexSet SetName="BlogItemsIndexSet" IndexPath="~/App_Data/TEMP/ExamineIndexes/BlogItems/">
<IndexUserFields>
<add Name="body"/>
<add Name="nodeName"/>
<add Name="createDate" EnableSorting="true" Type="DateTime"/>
<add Name="updateDate" EnableSorting="true" Type="DateTime"/>
<add Name="description"/>
<add Name="blogId"/>
<add Name="url"/>
<add Name="nodeTypeAlias" />
<add Name="data"/>
</IndexUserFields>

</IndexSet>

</ExamineLuceneIndexSets>
13 changes: 12 additions & 1 deletion OurUmbraco.Site/config/ExamineSettings.config
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,13 @@ More information and documentation can be found on CodePlex: http://umbracoexami
analyzer="Lucene.Net.Analysis.Standard.StandardAnalyzer, Lucene.Net"
indexTypes="PullRequest"/>

<add name="BlogItemsIndexer" type="Examine.LuceneEngine.Providers.SimpleDataIndexer, Examine"
autoOptimize="false"
indexSet="BlogItemsIndexSet"
dataService="OurUmbraco.Our.Examine.BlogItemsIndexDataService, OurUmbraco"
analyzer="Lucene.Net.Analysis.Standard.StandardAnalyzer, Lucene.Net"
indexTypes="blogItems"/>

</providers>

</ExamineIndexProviders>
Expand All @@ -71,11 +78,15 @@ More information and documentation can be found on CodePlex: http://umbracoexami

<add name="MultiIndexSearcher"
type="Examine.LuceneEngine.Providers.MultiIndexSearcher, Examine"
indexSets="ForumIndexSet,projectIndexSet,documentationIndexSet"
indexSets="ForumIndexSet,projectIndexSet,documentationIndexSet,BlogItemsIndexSet"
enableLeadingWildcards="true" />

<add name="PullRequestSearcher" type="Examine.LuceneEngine.Providers.LuceneSearcher, Examine" />

<add name="BlogItemsSearcher"
type="Examine.LuceneEngine.Providers.LuceneSearcher, Examine"
indexSet="BlogItemsIndexSet" analyzer="Lucene.Net.Analysis.Standard.StandardAnalyzer, Lucene.Net" />

</providers>
</ExamineSearchProviders>

Expand Down
51 changes: 51 additions & 0 deletions OurUmbraco/Community/BlogPosts/BlogDatabaseItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;
using Newtonsoft.Json;
using Skybrud.Essentials.Json;
using Umbraco.Core.Persistence;
using Umbraco.Core.Persistence.DatabaseAnnotations;

namespace OurUmbraco.Community.BlogPosts
{

[TableName(TableName)]
[PrimaryKey(PrimaryKey, autoIncrement = true)]
[ExplicitColumns]
public class BlogDatabaseItem
{

public const string TableName = "CommunityBlogItems";

public const string PrimaryKey = "Id";

private BlogRssItem _data;

[Column(PrimaryKey)]
[PrimaryKeyColumn(AutoIncrement = true)]
public int Id { get; set; }

[Column("UniqueId")]
public string UniqueId { get; set; }

[Column("BlogId")]
public string BlogId { get; set; }

[Column("PubDate")]
public DateTime PublishedDate { get; set; }

[Column("Title")]
public string Title { get; set; }

[Column("Data")]
[SpecialDbType(SpecialDbTypes.NTEXT)]
public string DataRaw { get; set; }

[Ignore]
public BlogRssItem Data
{
get { return _data ?? JsonUtils.ParseJsonObject<BlogRssItem>(DataRaw); }
set { _data = value; DataRaw = JsonConvert.SerializeObject(_data, Formatting.None); }
}

}

}
4 changes: 2 additions & 2 deletions OurUmbraco/Community/BlogPosts/BlogInfo.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ namespace OurUmbraco.Community.BlogPosts {
/// </summary>
public class BlogInfo {

public string Id { get; private set; }
public Guid Id { get; private set; }

public string Title { get; private set; }

Expand All @@ -35,7 +35,7 @@ public bool HasLogoUrl {
public Encoding Encoding { get; set; }

private BlogInfo(JObject obj) {
Id = obj.GetString("id");
Id = obj.GetString("id", Guid.Parse);
Title = obj.GetString("title");
Url = obj.GetString("url");
RssUrl = obj.GetString("rss");
Expand Down
144 changes: 144 additions & 0 deletions OurUmbraco/Community/BlogPosts/BlogPostsCache.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Examine;
using OurUmbraco.Search;
using Skybrud.Essentials.Json;
using Skybrud.Essentials.Json.Extensions;
using Umbraco.Core.IO;
using Umbraco.Core.Logging;

namespace OurUmbraco.Community.BlogPosts
{

public class BlogPostsCache
{

public const string SearcherName = "BlogItemsSearcher";

public static readonly string BlogsJsonFile = IOHelper.MapPath("~/config/CommunityBlogs.json");

public static readonly string PostsJsonFile = IOHelper.MapPath("~/App_Data/TEMP/CommunityBlogPosts.json");

/// <summary>
/// Gets an array with information about the blogs stored in the <c>~/config/CommunityBlogs.json</c> configuration file.
/// </summary>
/// <returns>Array of <see cref="BlogInfo"/>.</returns>
public BlogInfo[] GetBlogs()
{

// Check whether the config file exists
var configPath = BlogsJsonFile;
if (File.Exists(BlogsJsonFile) == false)
{
LogHelper.Warn<BlogPostsService>("Config file was not found: " + configPath);
return new BlogInfo[0];
}

// Attempt to load information about each blog
try
{
var root = JsonUtils.LoadJsonObject(configPath);
return root.GetArrayItems("blogs", BlogInfo.Parse);
}
catch (Exception ex)
{
LogHelper.Error<BlogPostsService>("Unable to parse config file", ex);
return new BlogInfo[0];
}
}

public BlogCachedRssItem[] GetCachedBlogPosts(int take, int numberOfPostsPerBlog)
{

// Return an empty array as the file doesn't exist
if (File.Exists(PostsJsonFile) == false) return new BlogCachedRssItem[0];

var blogs = GetBlogs().ToDictionary(x => x.Id.ToString());

try
{
var blogPosts = new List<BlogCachedRssItem>();

foreach (var item in JsonUtils.LoadJsonArray(PostsJsonFile).Select(token => token.ToObject<BlogRssItem>()))
{
if (blogs.TryGetValue(item.Channel.Id, out var blog) == false)
continue;

blog.LogoUrl = $"/media/blogs/{blog.Id}{BlogUtils.GetFileExtension(blog.LogoUrl)}";
blogPosts.Add(new BlogCachedRssItem(blog, item));
}

var filteredBlogPosts = new List<BlogCachedRssItem>();
foreach (var item in blogPosts)
if (filteredBlogPosts.Count(b => b.Blog.Id == item.Blog.Id) < numberOfPostsPerBlog)
filteredBlogPosts.Add(item);

return filteredBlogPosts.Take(take).OrderByDescending(x => x.PublishedDate).ToArray();
}
catch (Exception ex)
{
LogHelper.Error<BlogPostsService>("Unable to load blog posts from JSON file", ex);
return new BlogCachedRssItem[0];
}

}



public BlogItemSearchResult[] GetBlogPosts(BlogInfo blog)
{
if (blog == null) throw new ArgumentNullException(nameof(blog));
int total;
return GetBlogPosts(blog, Int32.MaxValue, out total);
}

public BlogItemSearchResult[] GetBlogPosts(BlogInfo blog, int max)
{
if (blog == null) throw new ArgumentNullException(nameof(blog));
int total;
return GetBlogPosts(blog, max, out total);
}

public BlogItemSearchResult[] GetBlogPosts(BlogInfo blog, int max, out int total)
{

if (blog == null) throw new ArgumentNullException(nameof(blog));

total = -1;

try
{

// Get a reference to the searcher
var searcher = ExamineManager.Instance.SearchProviderCollection[SearcherName];

// Create a new search criteria and set our query
var criteria = searcher.CreateSearchCriteria();
criteria = criteria.RawQuery($"blogId:{blog.Id}");

// Make the search in Examine
var results = searcher.Search(criteria);
total = results.TotalItemCount;

return results.OrderByDescending(GetCreateDate).Take(max).Select(x => new BlogItemSearchResult(x, blog)).ToArray();

}
catch (Exception ex)
{
LogHelper.Error<BlogPostsCache>("Unable to load blog posts for blog with ID " + blog.Id, ex);
return new BlogItemSearchResult[0];
}

}

private string GetCreateDate(SearchResult result)
{
string value;
return result.Fields.TryGetValue("createDate", out value) ? value : String.Empty;
}

}

}
Loading