Skip to content

Commit

Permalink
Merge V2 (stage -> master) (#27)
Browse files Browse the repository at this point in the history
* add for #7 - new configuration for dev only

* add for #22 - remove properties

* add for #9 - email client

* add for #8 - cleanup csproj files

* for #8 and #9 (#24)

* add for #9 - email client

* add for #8 - cleanup csproj files

* add for #4 - support SASS

* add for #1 - subscribe

* add for #12

* add for #15 - post metadata

* add for #19

* add for #17

* ::SERVER:: :bot: update posts from server, @2019/10/2 21:58:15

* add for #25 - remove octokit

* add for #10

* add for #20

* add for #5 - redesign UI

* add for #10 - refactor html headers

* add for #10 - add sitemap

* add for #21 - validate github hook

* add for #10 - refactor

* add for #10 - optimize scripts and styles

* add for #10 - optimize for github hook

* add for #10 - post hosted service executed at midnignt

* add for #22 - cleanup gitignot

* add for #10 - optimize

* add for #10

fix feed issues
introduce ga-lite.js
add logging

* add for #10 optimize

* add for #10 - optimze

* add for #10 fix issues

* Finalize V2

For #11 - add README and LICENSE
For #10 - A lot of refactors and changes
For #13 - Simply log to system

* fix merge issues
  • Loading branch information
JerryBian committed Oct 13, 2019
1 parent 0e30d8d commit 1f80bff
Show file tree
Hide file tree
Showing 1,998 changed files with 214,648 additions and 12,690 deletions.
626 changes: 1 addition & 625 deletions .gitignore

Large diffs are not rendered by default.

File renamed without changes.
25 changes: 25 additions & 0 deletions README.md
@@ -0,0 +1,25 @@
Source of building my personal websites.

There are three branches for different purposes:

- master: This is the source for production environment, running at https://blog.laobian.me
- stage: This is the source for staging environment. While new development is kicked off, we can reach it at https://stage.blog.laobian.me. Having said that, it was disabled by default.
- dev: This is the latest source code for development.

### Build

Clone repository to locally. You need tools like Microsoft Visual Studio 2019+, and npm, they will be used in normal development.

Before building the source, make sure you have .NET Core 3.0 installed.

```cs
dotnet --info
```

Now just start the msbuild, either in the CLI or visual studio.

For the assets configuration, you can just refer to the code...

### License

MIT.
37 changes: 0 additions & 37 deletions src/blog/BlogServiceRegister.cs

This file was deleted.

35 changes: 10 additions & 25 deletions src/blog/Controllers/AboutController.cs
@@ -1,41 +1,26 @@
using System;
using System.IO;
using System.Threading.Tasks;
using Laobian.Share.BlogEngine;
using Laobian.Share.Config;
using Laobian.Share.Infrastructure.Cache;
using Markdig;
using Laobian.Share.BlogEngine;
using Laobian.Share.BlogEngine.Model;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Options;

namespace Laobian.Blog.Controllers
{
public class AboutController : Controller
{
private readonly AppConfig _appConfig;
private readonly IMemoryCacheClient _cacheClient;
private readonly IBlogService _blogService;

public AboutController(IMemoryCacheClient cacheClient, IOptions<AppConfig> appConfig)
public AboutController(IBlogService blogService)
{
_appConfig = appConfig.Value;
_cacheClient = cacheClient;
_blogService = blogService;
}

public async Task<IActionResult> Index()
[ResponseCache(CacheProfileName = "Cache1Day")]
public IActionResult Index()
{
var html = await _cacheClient.GetOrAddAsync(BlogConstant.EnglishAboutMemCacheKey, async () =>
{
var localPath = Path.Combine(_appConfig.AssetRepoLocalDir, BlogConstant.EnAboutGitHub);
if (!System.IO.File.Exists(localPath))
{
return string.Empty;
}
var md = await System.IO.File.ReadAllTextAsync(localPath);
return Markdown.ToHtml(md);
}, TimeSpan.FromDays(1));
var html = _blogService.GetAboutHtml(RequestLang.English);

ViewData["Title"] = "关于";
ViewData["Canonical"] = "/about/";
ViewData["Description"] = "关于作者以及这个博客的一切";
return View(model: html);
}
}
Expand Down
43 changes: 34 additions & 9 deletions src/blog/Controllers/ArchiveController.cs
Expand Up @@ -17,57 +17,82 @@ public ArchiveController(IBlogService blogService)
}

[Route("/category")]
[ResponseCache(CacheProfileName = "Cache10Sec")]
public IActionResult Category()
{
var model = new List<ArchiveViewModel>();
var posts = _blogService.GetPosts().Where(_ => _.IsPublic).ToList();
var posts = _blogService.GetPublishedPosts();
var cats = _blogService.GetCategories();
foreach (var blogCategory in cats)
{
var catModel = new ArchiveViewModel(blogCategory.Name, blogCategory.Link);
catModel.Posts.AddRange(posts.Where(p=>p.CategoryNames.Contains(blogCategory.Name, StringComparer.OrdinalIgnoreCase)));
catModel.Posts.AddRange(posts.Where(p => p.CategoryNames.Contains(blogCategory.Name, StringComparer.OrdinalIgnoreCase)));

model.Add(catModel);
}

var remainingPosts = posts.Except(model.SelectMany(_ => _.Posts).Distinct()).ToList();
if (remainingPosts.Any())
{
var catModel = new ArchiveViewModel(BlogConstant.DefaultCategoryName, BlogConstant.DefaultCategoryLink);
catModel.Posts.AddRange(remainingPosts);
model.Add(catModel);
}

ViewData["Title"] = "分类";
ViewData["Canonical"] = "/category/";
ViewData["Description"] = "所有文章以分类的形式展现";
return View("Index", model);
}

[Route("/tag")]
[ResponseCache(CacheProfileName = "Cache10Sec")]
public IActionResult Tag()
{
var model = new List<ArchiveViewModel>();
var posts = _blogService.GetPosts().Where(_ => _.IsPublic).ToList();
var posts = _blogService.GetPublishedPosts();
var tags = _blogService.GetTags();
foreach (var tag in tags)
{
var catModel = new ArchiveViewModel(tag.Name, tag.Link);
catModel.Posts.AddRange(posts.Where(p => p.TagNames.Contains(tag.Name, StringComparer.OrdinalIgnoreCase)));
var tagModel = new ArchiveViewModel(tag.Name, tag.Link);
tagModel.Posts.AddRange(posts.Where(p => p.TagNames.Contains(tag.Name, StringComparer.OrdinalIgnoreCase)));

model.Add(catModel);
model.Add(tagModel);
}

var remainingPosts = posts.Except(model.SelectMany(_ => _.Posts).Distinct()).ToList();
if (remainingPosts.Any())
{
var tagModel = new ArchiveViewModel(BlogConstant.DefaultTagName, BlogConstant.DefaultTagLink);
tagModel.Posts.AddRange(remainingPosts);
model.Add(tagModel);
}

ViewData["Title"] = "标签";
ViewData["Canonical"] = "/tag/";
ViewData["Description"] = "所有文章以标签归类的形式展现";
return View("Index", model);
}

[Route("/archive")]
[ResponseCache(CacheProfileName = "Cache10Sec")]
public IActionResult Date()
{
var model = new List<ArchiveViewModel>();
var posts = _blogService.GetPosts().Where(_ => _.IsPublic).ToList();
var posts = _blogService.GetPublishedPosts();
var dates = posts.Select(_ => _.CreationTimeUtc.Year).Distinct();
foreach (var date in dates)
{
var catModel = new ArchiveViewModel($"{date}", date.ToString());
catModel.Posts.AddRange(posts.Where(p => p.CreationTimeUtc.Year == date));
catModel.Posts.AddRange(posts.Where(p => p.CreationTimeUtc.Year == date).OrderByDescending(p => p.CreationTimeUtc));

model.Add(catModel);
}

ViewData["Title"] = "存档";
return View("Index", model);
ViewData["Canonical"] = "/archive/";
ViewData["Description"] = "所有文章以发表日期归类的形式展现";
return View("Index", model.OrderByDescending(m => m.Name));
}
}
}
110 changes: 78 additions & 32 deletions src/blog/Controllers/GitHubController.cs
@@ -1,77 +1,123 @@
using System;
using System.IO;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using System.Threading.Tasks;
using Laobian.Share.BlogEngine;
using Laobian.Share.Infrastructure.GitHub;
using Laobian.Share.Config;
using Laobian.Share.Helper;
using Laobian.Share.Infrastructure.Git;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;

namespace Laobian.Blog.Controllers
{
[ApiController]
[Route("github")]
public class GitHubController : ControllerBase
{
private readonly AppConfig _appConfig;
private readonly IBlogService _blogService;
private readonly ILogger<GitHubController> _logger;

public GitHubController(IBlogService blogService)
public GitHubController(IBlogService blogService, IOptions<AppConfig> appConfig, ILogger<GitHubController> logger)
{
_logger = logger;
_blogService = blogService;
_appConfig = appConfig.Value;
}

// http://michaco.net/blog/HowToValidateGitHubWebhooksInCSharpWithASPNETCoreMVC
[HttpPost]
[Route("hook")]
public async Task<IActionResult> Hook(GitHubPayload payload)
public async Task<IActionResult> Hook()
{
if (!Request.Headers.ContainsKey("X-GitHub-Event"))
if (!Request.Headers.ContainsKey("X-GitHub-Event") ||
!Request.Headers.ContainsKey("X-Hub-Signature") ||
!Request.Headers.ContainsKey("X-GitHub-Delivery"))
{
return BadRequest();
_logger.LogWarning("Headers are not completed.");
return BadRequest("Invalid Request.");
}
var gitHubEvent = Request.Headers["X-GitHub-Event"];
if (!string.Equals("push", gitHubEvent, StringComparison.OrdinalIgnoreCase))

if (!StringEqualsHelper.IgnoreCase("push", Request.Headers["X-GitHub-Event"]))
{
return BadRequest();
_logger.LogWarning("Invalid github event {Event}", Request.Headers["X-GitHub-Event"]);
return BadRequest("Only support push event.");
}

if (!Request.Headers.ContainsKey("X-Hub-Signature"))
var signature = Request.Headers["X-Hub-Signature"].ToString();
if (!signature.StartsWith("sha1=", StringComparison.OrdinalIgnoreCase))
{
return BadRequest();
_logger.LogWarning("Invalid github signature {Signature}", signature);
return BadRequest("Invalid signature.");
}

using (var sha1 = SHA1.Create())
using (var reader = new StreamReader(Request.Body))
{
var hash = sha1.ComputeHash(Encoding.UTF8.GetBytes("abc"));
var a = Convert.ToBase64String(hash);
var d = string.Concat(hash.Select(b => b.ToString("x2")));
var body = await reader.ReadToEndAsync();
signature = signature.Substring("sha1=".Length);
var secret = Encoding.UTF8.GetBytes(_appConfig.AssetGitHubHookSecret);
var bodyBytes = Encoding.UTF8.GetBytes(body);

}
//if(!string.Equals())
using (var hmacSha1 = new HMACSHA1(secret))
{
var hash = hmacSha1.ComputeHash(bodyBytes);
var builder = new StringBuilder(hash.Length * 2);
foreach (var b in hash)
{
builder.AppendFormat("{0:x2}", b);
}

if (payload.Commits.Any(_ => GitHubMessageProvider.IsServerCommit(_.Message)))
{
return Ok("Server update, no need to update local.");
}
var hashStr = builder.ToString();

await _blogService.UpdateLocalAssetsAsync();
if (!hashStr.Equals(signature))
{
_logger.LogWarning("Invalid github signature {Signature}, {HashString}", signature, hashStr);
return BadRequest("Invalid signature.");
}
}

var modifiedPosts = payload.Commits.SelectMany(c => c.Modified).ToList();
if (modifiedPosts.Any())
{
var posts = _blogService.GetPosts();
foreach (var blogPost in posts)
var payload = SerializeHelper.FromJson<GitHubPayload>(body);
if (payload.Commits.Any(c =>
StringEqualsHelper.IgnoreCase(_appConfig.AssetGitCommitEmail, c.Author.Email) &&
StringEqualsHelper.IgnoreCase(_appConfig.AssetGitCommitUser, c.Author.User)))
{
var modifiedPost = modifiedPosts.FirstOrDefault(p =>
string.Equals(p, blogPost.GitHubPath, StringComparison.OrdinalIgnoreCase));
if (modifiedPost != null)
_logger.LogInformation("Got request from server, no need to refresh.");
return Ok("No need to refresh.");
}

var modifiedPosts = payload.Commits.SelectMany(c => c.Modified).Distinct().ToList();
await _blogService.UpdateMemoryAssetsAsync();
_logger.LogInformation("Local assets refreshed.");

bool requireCommit = false;

if (modifiedPosts.Any())
{
var posts = _blogService.GetPosts();
foreach (var blogPost in posts)
{
blogPost.LastUpdateTimeUtc = DateTime.UtcNow;
var modifiedPost = modifiedPosts.FirstOrDefault(p =>
string.Equals(p, blogPost.GitHubPath, StringComparison.OrdinalIgnoreCase));
if (modifiedPost != null)
{
requireCommit = true;
blogPost.LastUpdateTimeUtc = DateTime.UtcNow;
}
}
}
}

return Ok("Local updated.");
if (requireCommit || payload.Commits.SelectMany(c => c.Added).Any())
{
await _blogService.UpdateCloudAssetsAsync();
_logger.LogInformation("Cloud assets updated.");
}

return Ok("Local updated.");
}
}
}
}

0 comments on commit 1f80bff

Please sign in to comment.