14 Commits

Author SHA1 Message Date
tparnell
a3838b0f4d picture elem 2019-02-03 20:25:08 -05:00
tparnell
38f82061e9 do not block anything 2019-02-03 20:22:21 -05:00
tparnell
57a8bba66a add csp back 2019-02-03 19:18:11 -05:00
tparnell
43d6e33638 rm csp 2019-02-03 19:11:28 -05:00
tparnell
d875ca6fea headers 🎉 2019-02-03 18:53:16 -05:00
Tommy Parnell
d846a538a0 headerz 2019-02-03 17:33:01 -05:00
Tommy Parnell
00b711aef4 increase hsts header 2019-02-03 13:14:20 -05:00
Tommy Parnell
dbb6ae208b output cache all the things 2019-02-03 13:01:21 -05:00
Tommy Parnell
de62e6275d inline styles 2019-02-03 11:53:52 -05:00
tparnell
d873be97d8 bettah config 2019-02-02 14:17:20 -05:00
tparnell
f3080faae0 start to move things to config 2019-02-02 13:49:46 -05:00
tparnell
6ed0ef4205 only render gtm in production 2019-02-02 13:30:30 -05:00
tparnell
ab9250b968 Revert "fix menu animation"
This reverts commit c24684fa8b.
2019-02-02 12:38:01 -05:00
tparnell
c24684fa8b fix menu animation 2019-02-02 12:28:55 -05:00
15 changed files with 193 additions and 43 deletions

17
.vscode/launch.json vendored
View File

@@ -1,18 +1,17 @@
{ {
// Use IntelliSense to find out which attributes exist for C# debugging // Use IntelliSense to learn about possible attributes.
// Use hover for the description of the existing attributes // Hover to view descriptions of existing attributes.
// For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0", "version": "0.2.0",
"configurations": [ "configurations": [
{ {
"name": ".NET Core Launch (web)", "name": ".NET Core Launch (web)",
"type": "coreclr", "type": "coreclr",
"request": "launch", "request": "launch",
"preLaunchTask": "build", "preLaunchTask": "build",
// If you have changed target frameworks, make sure to update the program path. "program": "${workspaceFolder}/src/TerribleDev.Blog.Web/bin/Debug/netcoreapp2.2/TerribleDev.Blog.Web.dll",
"program": "${workspaceFolder}/TerribleDev.Blog.Web/bin/Debug/netcoreapp2.2/TerribleDev.Blog.Web.dll",
"args": [], "args": [],
"cwd": "${workspaceFolder}/TerribleDev.Blog.Web", "cwd": "${workspaceFolder}/src/TerribleDev.Blog.Web",
"stopAtEntry": false, "stopAtEntry": false,
"internalConsoleOptions": "openOnSessionStart", "internalConsoleOptions": "openOnSessionStart",
"launchBrowser": { "launchBrowser": {
@@ -42,5 +41,5 @@
"request": "attach", "request": "attach",
"processId": "${command:pickProcess}" "processId": "${command:pickProcess}"
} }
,] ]
} }

2
.vscode/tasks.json vendored
View File

@@ -7,7 +7,7 @@
"type": "process", "type": "process",
"args": [ "args": [
"build", "build",
"${workspaceFolder}/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj" "${workspaceFolder}/src/TerribleDev.Blog.Web/TerribleDev.Blog.Web.csproj"
], ],
"problemMatcher": "$msCompile" "problemMatcher": "$msCompile"
} }

7
docker-compose.yml Normal file
View File

@@ -0,0 +1,7 @@
version: '3'
services:
webapp:
build: ./src/TerribleDev.Blog.Web
ports:
- "80:80"
- "443:443"

View File

@@ -47,6 +47,7 @@ namespace TerribleDev.Blog.Web.Controllers
[Route("/")] [Route("/")]
[Route("/index.html")] [Route("/index.html")]
[Route("/page/{pageNumber?}" )] [Route("/page/{pageNumber?}" )]
[OutputCache(Duration = 31536000, VaryByParam = "pageNumber")]
public IActionResult Index(int pageNumber = 1) public IActionResult Index(int pageNumber = 1)
{ {
if(!postsByPage.TryGetValue(pageNumber, out var result)) if(!postsByPage.TryGetValue(pageNumber, out var result))

View File

@@ -15,12 +15,18 @@ namespace TerribleDev.Blog.Web.Controllers
{ {
public class SeoController : Controller public class SeoController : Controller
{ {
private readonly BlogConfiguration configuration;
public SeoController(BlogConfiguration configuration)
{
this.configuration = configuration;
}
public static DateTimeOffset publishDate = DateTimeOffset.UtcNow; // keep publish date in memory so we just return when the server was kicked public static DateTimeOffset publishDate = DateTimeOffset.UtcNow; // keep publish date in memory so we just return when the server was kicked
public static IEnumerable<SyndicationItem> postsToSyndication = HomeController.postsAsList.Select(a => a.ToSyndicationItem()).ToList(); public static IEnumerable<SyndicationItem> postsToSyndication = HomeController.postsAsList.Select(a => a.ToSyndicationItem()).ToList();
[Route("/rss")] [Route("/rss")]
[Route("/rss.xml")] [Route("/rss.xml")]
[ResponseCache(Duration = 7200)] [ResponseCache(Duration = 7200)]
[OutputCache(Duration = 86400)] [OutputCache(Duration = 31536000)]
public async Task Rss() public async Task Rss()
{ {
Response.StatusCode = 200; Response.StatusCode = 200;
@@ -28,8 +34,8 @@ namespace TerribleDev.Blog.Web.Controllers
using (XmlWriter xmlWriter = XmlWriter.Create(this.Response.Body, new XmlWriterSettings() { Async = true, Indent = false, Encoding = Encoding.UTF8 })) using (XmlWriter xmlWriter = XmlWriter.Create(this.Response.Body, new XmlWriterSettings() { Async = true, Indent = false, Encoding = Encoding.UTF8 }))
{ {
var writer = new RssFeedWriter(xmlWriter); var writer = new RssFeedWriter(xmlWriter);
await writer.WriteTitle("The Ramblings of TerribleDev"); await writer.WriteTitle(configuration.Title);
await writer.WriteValue("link", "https://blog.terribledev.io"); await writer.WriteValue("link", configuration.Link);
await writer.WriteDescription("My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space."); await writer.WriteDescription("My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space.");
foreach (var item in postsToSyndication) foreach (var item in postsToSyndication)
@@ -43,12 +49,12 @@ namespace TerribleDev.Blog.Web.Controllers
} }
[Route("/sitemap.xml")] [Route("/sitemap.xml")]
[ResponseCache(Duration = 7200)] [ResponseCache(Duration = 7200)]
[OutputCache(Duration = 86400)] [OutputCache(Duration = 31536000)]
public void SiteMap() public void SiteMap()
{ {
Response.StatusCode = 200; Response.StatusCode = 200;
Response.ContentType = "text/xml"; Response.ContentType = "text/xml";
var sitewideLinks = new List<SiteMapItem>(HomeController.tagToPost.Keys.Select(a=> new SiteMapItem() { LastModified = DateTime.UtcNow, Location = $"https://blog.terribledev.io/tag/{a}/"})) var sitewideLinks = new List<SiteMapItem>(HomeController.tagToPost.Keys.Select(a => new SiteMapItem() { LastModified = DateTime.UtcNow, Location = $"https://blog.terribledev.io/tag/{a}/" }))
{ {
new SiteMapItem() { LastModified = DateTime.UtcNow, Location="https://blog.terribledev.io/all-tags/" } new SiteMapItem() { LastModified = DateTime.UtcNow, Location="https://blog.terribledev.io/all-tags/" }
}; };

View File

@@ -50,7 +50,7 @@ namespace TerribleDev.Blog.Web.MarkExtension
return false; return false;
} }
this.images.Add(linkInline.Url); this.images.Add(linkInline.Url);
return false; return true;
} }
} }
} }

View File

@@ -0,0 +1,18 @@
using Markdig.Renderers.Html;
using Markdig.Syntax.Inlines;
namespace TerribleDev.Blog.Web.MarkExtension
{
public class PictureInlineRenderer : HtmlObjectRenderer<LinkInline>
{
protected override void Write(HtmlRenderer renderer, LinkInline link)
{
if(!link.IsImage)
{
base.Write(renderer, link);
}
}
}
}

View File

@@ -0,0 +1,8 @@
namespace TerribleDev.Blog.Web.Models
{
public class BlogConfiguration
{
public string Title { get; set; }
public string Link { get; set; }
}
}

View File

@@ -15,20 +15,33 @@ using Microsoft.Extensions.FileProviders;
using Microsoft.Net.Http.Headers; using Microsoft.Net.Http.Headers;
using HardHat.Middlewares; using HardHat.Middlewares;
using HardHat; using HardHat;
using TerribleDev.Blog.Web.Models;
namespace TerribleDev.Blog.Web namespace TerribleDev.Blog.Web
{ {
public class Startup public class Startup
{ {
public Startup(IConfiguration configuration) public Startup(IConfiguration configuration, IHostingEnvironment env)
{ {
Configuration = configuration; Configuration = configuration;
Env = env;
} }
public IConfiguration Configuration { get; } public IConfiguration Configuration { get; }
public IHostingEnvironment Env { get; }
// This method gets called by the runtime. Use this method to add services to the container. // This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services) public void ConfigureServices(IServiceCollection services)
{ {
Func<BlogConfiguration> getBlog = () => Configuration.GetSection("Blog").Get<BlogConfiguration>();
if (Env.IsDevelopment())
{
services.AddTransient(a => getBlog());
}
else
{
services.AddSingleton(getBlog());
}
services.AddResponseCompression(a => services.AddResponseCompression(a =>
{ {
a.EnableForHttps = true; a.EnableForHttps = true;
@@ -57,31 +70,47 @@ namespace TerribleDev.Blog.Web
app.UseHttpsRedirection(); app.UseHttpsRedirection();
app.UseResponseCompression(); app.UseResponseCompression();
var cacheTime = env.IsDevelopment() ? 0 : 31536000; var cacheTime = env.IsDevelopment() ? 0 : 31536000;
app.UseStaticFiles(new StaticFileOptions app.UseStaticFiles(new StaticFileOptions
{ {
OnPrepareResponse = ctx => OnPrepareResponse = ctx =>
{ {
ctx.Context.Response.Headers[HeaderNames.CacheControl] = ctx.Context.Response.Headers[HeaderNames.CacheControl] =
"public,max-age=" + cacheTime; "public,max-age=" + cacheTime;
} }
}); });
app.UseStaticFiles(new StaticFileOptions app.UseStaticFiles(new StaticFileOptions
{ {
FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "img")), FileProvider = new PhysicalFileProvider(Path.Combine(Directory.GetCurrentDirectory(), "wwwroot", "img")),
OnPrepareResponse = ctx => OnPrepareResponse = ctx =>
{ {
ctx.Context.Response.Headers[HeaderNames.CacheControl] = ctx.Context.Response.Headers[HeaderNames.CacheControl] =
"public,max-age=" + cacheTime; "public,max-age=" + cacheTime;
} }
}); });
app.UseRewriter(new Microsoft.AspNetCore.Rewrite.RewriteOptions().AddRedirect("(.*[^/|.xml|.html])$", "$1/", 301)); app.UseRewriter(new Microsoft.AspNetCore.Rewrite.RewriteOptions().AddRedirect("(.*[^/|.xml|.html])$", "$1/", 301));
app.UseIENoOpen(); app.UseIENoOpen();
app.UseNoMimeSniff(); app.UseNoMimeSniff();
app.UseCrossSiteScriptingFilters(); app.UseCrossSiteScriptingFilters();
app.UseFrameGuard(new FrameGuardOptions(FrameGuardOptions.FrameGuard.SAMEORIGIN)); app.UseFrameGuard(new FrameGuardOptions(FrameGuardOptions.FrameGuard.SAMEORIGIN));
app.UseHsts(TimeSpan.FromDays(30), false, preload: true); app.UseHsts(TimeSpan.FromDays(365), false, preload: true);
app.UseContentSecurityPolicy(
new ContentSecurityPolicy()
{
// DefaultSrc = new HashSet<string>() {
// CSPConstants.Self, "https://www.google-analytics.com", "https://www.googletagmanager.com", "https://stats.g.doubleclick.net"
// },
// ScriptSrc = new HashSet<string>()
// {
// CSPConstants.Self, CSPConstants.UnsafeInline, "https://www.google-analytics.com", "https://www.googletagmanager.com", "https://stats.g.doubleclick.net"
// },
// StyleSrc = new HashSet<string>()
// {
// CSPConstants.Self, CSPConstants.UnsafeInline
// },
UpgradeInsecureRequests = true
});
app.UseOutputCaching(); app.UseOutputCaching();
app.UseMvc(); app.UseMvc();
} }

View File

@@ -0,0 +1,72 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Razor.TagHelpers;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Extensions.FileProviders;
using System.IO;
using System.Threading.Tasks;
namespace TerribleDev.Blog.Web.Taghelpers
{
[HtmlTargetElement("inline-style")]
public class InlineStyleTagHelper : TagHelper
{
[HtmlAttributeName("href")]
public string Href { get; set; }
private IHostingEnvironment HostingEnvironment { get; }
private IMemoryCache Cache { get; }
public InlineStyleTagHelper(IHostingEnvironment hostingEnvironment, IMemoryCache cache)
{
HostingEnvironment = hostingEnvironment;
Cache = cache;
}
public override async Task ProcessAsync(TagHelperContext context, TagHelperOutput output)
{
var path = Href;
// Get the value from the cache, or compute the value and add it to the cache
var fileContent = await Cache.GetOrCreateAsync("InlineStyleTagHelper-" + path, async entry =>
{
var fileProvider = HostingEnvironment.WebRootFileProvider;
if(HostingEnvironment.IsDevelopment())
{
var changeToken = fileProvider.Watch(path);
entry.AddExpirationToken(changeToken);
}
entry.SetPriority(CacheItemPriority.NeverRemove);
var file = fileProvider.GetFileInfo(path);
if (file == null || !file.Exists)
return null;
return await ReadFileContent(file);
});
if (fileContent == null)
{
output.SuppressOutput();
return;
}
output.TagName = "style";
output.Attributes.RemoveAll("href");
output.Content.AppendHtml(fileContent);
}
private static async Task<string> ReadFileContent(IFileInfo file)
{
using (var stream = file.CreateReadStream())
using (var textReader = new StreamReader(stream))
{
return await textReader.ReadToEndAsync();
}
}
}
}

View File

@@ -26,7 +26,7 @@
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" /> <PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.0.2105168" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.0" /> <PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="2.2.0" />
<PackageReference Include="YamlDotNet" Version="5.3.0" /> <PackageReference Include="YamlDotNet" Version="5.3.0" />
<PackageReference Include="HardHat" Version="2.0.0" /> <PackageReference Include="HardHat" Version="2.1.1" />
<PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" /> <PackageReference Include="Microsoft.SyndicationFeed.ReaderWriter" Version="1.0.2" />
<PackageReference Include="WebEssentials.AspNetCore.OutputCaching" Version="1.0.16" /> <PackageReference Include="WebEssentials.AspNetCore.OutputCaching" Version="1.0.16" />
</ItemGroup> </ItemGroup>

View File

@@ -1,4 +1,5 @@
@model IPost @inject BlogConfiguration config
@model IPost
@{ @{
ViewData["Title"] = "Post"; ViewData["Title"] = "Post";
ViewData["HideNav"] = true; ViewData["HideNav"] = true;
@@ -13,7 +14,7 @@
<meta property="og:type" content="blog"> <meta property="og:type" content="blog">
<meta property="og:title" content="@Model.Title"> <meta property="og:title" content="@Model.Title">
<meta property="og:url" content="https://blog.terribledev.io/@Model.Url/"> <meta property="og:url" content="https://blog.terribledev.io/@Model.Url/">
<meta property="og:site_name" content="The Ramblings of TerribleDev"> <meta property="og:site_name" content="@config.Title">
<meta property="og:description" content="@Model.SummaryPlainShort"> <meta property="og:description" content="@Model.SummaryPlainShort">
<meta property="og:updated_time" content="@Model.PublishDate.ToString("O")"> <meta property="og:updated_time" content="@Model.PublishDate.ToString("O")">
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">

View File

@@ -1,11 +1,12 @@
<meta name="description" content="My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space." /> @inject BlogConfiguration config
<meta name="description" content="My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space." />
<meta property="og:type" content="blog"> <meta property="og:type" content="blog">
<meta property="og:title" content="The Ramblings of TerribleDev"> <meta property="og:title" content="@config.Title">
<meta property="og:url" content="https://blog.terribledev.io/"> <meta property="og:url" content="https://blog.terribledev.io/">
<meta property="og:site_name" content="The Ramblings of TerribleDev"> <meta property="og:site_name" content="@config.Title">
<meta property="og:description" content="My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space."> <meta property="og:description" content="My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space.">
<meta name="twitter:card" content="summary"> <meta name="twitter:card" content="summary">
<meta name="twitter:title" content="The Ramblings of TerribleDev"> <meta name="twitter:title" content="@config.Title">
<meta name="twitter:description" content="My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space."> <meta name="twitter:description" content="My name is Tommy Parnell. I usually go by TerribleDev on the internets. These are just some of my writings and rants about the software space.">
<meta name="twitter:creator" content="@@TerribleDev"> <meta name="twitter:creator" content="@@TerribleDev">
<meta property="og:image" content="https://www.gravatar.com/avatar/333e3cea32cd17ff2007d131df336061?s=640" /> <meta property="og:image" content="https://www.gravatar.com/avatar/333e3cea32cd17ff2007d131df336061?s=640" />

View File

@@ -1,20 +1,24 @@
<!DOCTYPE html> @inject BlogConfiguration config
<!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<partial name="Gtm" /> <environment names="Production">
<partial name="Gtm" />
</environment>
<meta name="author" content="Tommy &quot;TerribleDev&quot; Parnell" /> <meta name="author" content="Tommy &quot;TerribleDev&quot; Parnell" />
<meta name="theme-color" content="#4A4A4A" /> <meta name="theme-color" content="#4A4A4A" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="alternate" type="application/atom+xml" async title="RSS" href="/rss.xml"> <link rel="alternate" type="application/atom+xml" async title="RSS" href="/rss.xml">
<link rel="manifest" href="~/manifest.json" async asp-append-version="true"> <link rel="manifest" href="~/manifest.json" async asp-append-version="true">
<link asp-append-version="true" rel="icon" async href="~/favicon.ico" /> <link asp-append-version="true" rel="icon" async href="~/favicon.ico" />
<title>@ViewData["Title"] - The Ramblings of TerribleDev</title> <title>@ViewData["Title"] - @config.Title</title>
<environment names="Development"> <environment names="Development">
<link asp-append-version="true" rel="stylesheet" href="~/css/site.css" /> <inline-style href="css/site.css"></inline-style>
</environment> </environment>
<environment names="Production"> <environment names="Production">
<link asp-append-version="true" rel="stylesheet" href="~/css/site.min.css" /> <inline-style href="css/site.min.css"></inline-style>
</environment> </environment>
@RenderSection("Head", false) @RenderSection("Head", false)
</head> </head>
@@ -22,7 +26,7 @@
<partial name="Nav" /> <partial name="Nav" />
<header class="header"> <header class="header">
<svg aria-label="Open Menu" id="menuBtn" role="button" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M4 10h24c1.104 0 2-.896 2-2s-.896-2-2-2H4c-1.104 0-2 .896-2 2s.896 2 2 2zm24 4H4c-1.104 0-2 .896-2 2s.896 2 2 2h24c1.104 0 2-.896 2-2s-.896-2-2-2zm0 8H4c-1.104 0-2 .896-2 2s.896 2 2 2h24c1.104 0 2-.896 2-2s-.896-2-2-2z" /></svg> <svg aria-label="Open Menu" id="menuBtn" role="button" xmlns="http://www.w3.org/2000/svg" width="32" height="32"><path d="M4 10h24c1.104 0 2-.896 2-2s-.896-2-2-2H4c-1.104 0-2 .896-2 2s.896 2 2 2zm24 4H4c-1.104 0-2 .896-2 2s.896 2 2 2h24c1.104 0 2-.896 2-2s-.896-2-2-2zm0 8H4c-1.104 0-2 .896-2 2s.896 2 2 2h24c1.104 0 2-.896 2-2s-.896-2-2-2z" /></svg>
<div class="headerCallout"><a href="/" class="link-unstyled ">The Ramblings of TerribleDev</a></div> <div class="headerCallout"><a href="/" class="link-unstyled ">@config.Title</a></div>
</header> </header>
@{ @{
var bodyBump = ViewData["HideNav"] == null ? "bodyWithNav": ""; var bodyBump = ViewData["HideNav"] == null ? "bodyWithNav": "";

View File

@@ -9,5 +9,9 @@
} }
} }
}, },
"AllowedHosts": "*" "AllowedHosts": "*",
"Blog": {
"title": "The Ramblings of TerribleDev",
"link": "https://blog.terribledev.io"
}
} }