├── .gitattributes ├── .gitignore ├── README.md └── src ├── .dockerignore ├── .editorconfig ├── WilderBlog.Data ├── BaseRepository.cs ├── BlogResult.cs ├── BlogStory.cs ├── Episode.cs ├── EpisodeLink.cs ├── IWilderRepository.cs ├── MemoryRepository.cs ├── Migrations │ ├── 20170122085411_InitialDb.Designer.cs │ ├── 20170122085411_InitialDb.cs │ ├── 20201109173732_AddingAbstracts.Designer.cs │ ├── 20201109173732_AddingAbstracts.cs │ └── WilderContextModelSnapshot.cs ├── Program.cs ├── Properties │ └── launchSettings.json ├── Publication.cs ├── WilderBlog.Data.csproj ├── WilderContext.cs ├── WilderInitializer.cs ├── WilderRepository.cs ├── WilderUser.cs └── config.json ├── WilderBlog.Tests ├── BlogStoryExtensionTests.cs ├── Properties │ └── launchSettings.json ├── RepositoryTests.cs └── WilderBlog.Tests.csproj ├── WilderBlog.sln └── WilderBlog ├── Config └── AppSettings.cs ├── Controllers ├── Api │ ├── ActiveUsersController.cs │ ├── EpisodeController.cs │ └── MessageController.cs └── Web │ ├── AdminController.cs │ ├── PodcastController.cs │ ├── RootController.cs │ ├── SearchController.cs │ ├── TagController.cs │ └── VideosController.cs ├── Data ├── calendar.json ├── courses.json ├── episodeList.json ├── publications.json └── videos.json ├── Dockerfile ├── EmailTemplates ├── ContactTemplate.txt ├── WilderMindsContact.txt ├── exceptionMessage.txt └── logmessage.txt ├── Filters └── WilderExceptionFilter.cs ├── Helpers ├── DataExtensions.cs └── HealthCheckExtensions.cs ├── Logger ├── EmailLogger.cs ├── EmailLoggerExtensions.cs └── EmailLoggerProvider.cs ├── MetaWeblog └── WilderWeblogProvider.cs ├── Models ├── AdDateRange.cs ├── ContactModel.cs ├── SiteVerfiyResult.cs └── SiteVerifyModel.cs ├── Program.cs ├── Properties └── launchSettings.json ├── Services ├── ActiveUsersMiddleware.cs ├── AdService.cs ├── DataProviders │ ├── CalendarProvider.cs │ ├── CoursesProvider.cs │ ├── DataProvider.cs │ ├── PodcastEpisodesProvider.cs │ ├── PublicationsProvider.cs │ └── VideosProvider.cs ├── EmailExceptionMiddleware.cs ├── FakeAzureImageService.cs ├── GoogleCaptchaService.cs ├── IMailService.cs ├── LoggingMailService.cs ├── MailService.cs ├── SpamState.cs ├── UrlRewriter.cs └── WilderContextFactory.cs ├── Startup.cs ├── Views ├── Podcast │ ├── Episode.cshtml │ ├── Index.cshtml │ ├── _Episode.cshtml │ └── _Headshot.cshtml ├── Root │ ├── About.cshtml │ ├── Calendar.cshtml │ ├── Contact.cshtml │ ├── Error.cshtml │ ├── Exception.cshtml │ ├── Index.cshtml │ ├── NotFound.cshtml │ ├── PsStats.cshtml │ └── Story.cshtml ├── Search │ └── Index.cshtml ├── Shared │ ├── _Info.cshtml │ ├── _InlineAd.cshtml │ ├── _Layout.cshtml │ ├── _Menu.cshtml │ ├── _Pager.cshtml │ ├── _Sidebar.cshtml │ └── _StoryBrief.cshtml ├── Tag │ └── Index.cshtml ├── Videos │ ├── Index.cshtml │ ├── Video.cshtml │ └── _VideoFrame.cshtml ├── _ViewImports.cshtml └── _ViewStart.cshtml ├── WilderBlog.csproj ├── bundleconfig.json ├── client ├── .browserslistrc ├── .gitignore ├── .npmrc ├── babel.config.js ├── package-lock.json ├── package.json ├── public │ ├── favicon.ico │ └── index.html ├── src │ ├── App.vue │ ├── assets │ │ └── logo.png │ ├── components │ │ ├── HelloWorld.vue │ │ └── ModelError.vue │ ├── contact │ │ ├── ContactMail.ts │ │ ├── contact.vue │ │ ├── http.ts │ │ ├── index.ts │ │ └── subjects.ts │ ├── helpers │ │ ├── isProduction.ts │ │ └── recaptcha.ts │ ├── logger │ │ └── index.ts │ ├── lookups │ │ └── index.ts │ ├── main.ts │ ├── shims-vue.d.ts │ ├── store │ │ └── index.ts │ └── validators │ │ └── index.ts ├── tsconfig.json └── vue.config.js ├── comments.xml ├── compilerconfig.json ├── compilerconfig.json.defaults ├── config.json ├── libman.json ├── package-lock.json ├── package.json ├── runtimeconfig.template.json ├── tailwind.config.js ├── web.config └── wwwroot ├── 3rdparty ├── css │ ├── audioplayer.css │ └── sh │ │ ├── shCore.css │ │ ├── shCoreDefault.css │ │ ├── shCoreDjango.css │ │ ├── shCoreEclipse.css │ │ ├── shCoreEmacs.css │ │ ├── shCoreFadeToGrey.css │ │ ├── shCoreMDUltra.css │ │ ├── shCoreMidnight.css │ │ ├── shCoreRDark.css │ │ ├── shThemeDefault.css │ │ ├── shThemeDjango.css │ │ ├── shThemeEclipse.css │ │ ├── shThemeEmacs.css │ │ ├── shThemeFadeToGrey.css │ │ ├── shThemeMDUltra.css │ │ ├── shThemeMidnight.css │ │ └── shThemeRDark.css └── js │ ├── sh │ ├── shAutoloader.js │ ├── shBrushAS3.js │ ├── shBrushAppleScript.js │ ├── shBrushBash.js │ ├── shBrushCSharp.js │ ├── shBrushColdFusion.js │ ├── shBrushCpp.js │ ├── shBrushCss.js │ ├── shBrushDelphi.js │ ├── shBrushDiff.js │ ├── shBrushErlang.js │ ├── shBrushGroovy.js │ ├── shBrushJScript.js │ ├── shBrushJava.js │ ├── shBrushJavaFX.js │ ├── shBrushPerl.js │ ├── shBrushPhp.js │ ├── shBrushPlain.js │ ├── shBrushPowerShell.js │ ├── shBrushPython.js │ ├── shBrushRuby.js │ ├── shBrushSass.js │ ├── shBrushScala.js │ ├── shBrushSql.js │ ├── shBrushVb.js │ ├── shBrushXml.js │ ├── shCore.js │ └── shLegacy.js │ └── syntaxhighlight.js ├── app ├── contact.html ├── favicon.ico └── js │ ├── chunk-vendors.js │ ├── chunk-vendors.js.map │ ├── contact.js │ └── contact.js.map ├── css ├── fonts.css ├── fonts.min.css ├── wilderblog.css └── wilderblog.src.css ├── favicon.ico ├── fonts ├── EOT │ ├── Atkinson-Hyperlegible-Bold-102.eot │ ├── Atkinson-Hyperlegible-BoldItalic-102.eot │ ├── Atkinson-Hyperlegible-Italic-102.eot │ └── Atkinson-Hyperlegible-Regular-102.eot ├── SVG │ ├── Atkinson-Hyperlegible-Bold-102.svg │ ├── Atkinson-Hyperlegible-BoldItalic-102.svg │ ├── Atkinson-Hyperlegible-Italic-102.svg │ └── Atkinson-Hyperlegible-Regular-102.svg ├── TTF │ ├── Atkinson-Hyperlegible-Bold-102.ttf │ ├── Atkinson-Hyperlegible-BoldItalic-102.ttf │ ├── Atkinson-Hyperlegible-Italic-102.ttf │ └── Atkinson-Hyperlegible-Regular-102.ttf ├── WOFF │ ├── Atkinson-Hyperlegible-Bold-102.woff │ ├── Atkinson-Hyperlegible-BoldItalic-102.woff │ ├── Atkinson-Hyperlegible-Italic-102.woff │ └── Atkinson-Hyperlegible-Regular-102.woff └── WOFF2 │ ├── Atkinson-Hyperlegible-Bold-102a.woff2 │ ├── Atkinson-Hyperlegible-BoldItalic-102a.woff2 │ ├── Atkinson-Hyperlegible-Italic-102a.woff2 │ └── Atkinson-Hyperlegible-Regular-102a.woff2 ├── img ├── AppThumbs │ ├── goonews.png │ ├── howtowatch.png │ ├── moonphaser.png │ └── stayglucose.png ├── MVP_Horizontal_BlueOnly.png ├── ads │ ├── bootstrap4-250x250.jpg │ ├── bootstrap4-468x60.jpg │ ├── design-apis-250x250.jpg │ ├── design-apis-468x60.jpg │ ├── from-scratch-250x250.jpg │ ├── from-scratch-468x60.jpg │ ├── grpc-250x250.jpg │ ├── grpc-468x60.jpg │ ├── signal-r-250x250.jpg │ ├── signal-r-468x60.jpg │ ├── vue-core-250x250.jpg │ └── vue-core-468x60.jpg ├── bs-ad.jpg ├── buymeacoffee.png ├── headers │ ├── about.jpg │ ├── cats.jpg │ ├── class.jpg │ ├── code.jpg │ ├── podcast.jpg │ ├── search.jpg │ ├── shawn_talk.jpg │ └── video.jpg ├── headshots │ ├── shawn-2015-200.jpg │ ├── shawn-2015-800.jpg │ ├── shawn-2015-square-200.jpg │ ├── shawn-2015-square-800.jpg │ ├── shawn-2015-square.jpg │ ├── shawn-2015.jpg │ ├── shawn-head-2016-4x3-big.jpg │ ├── shawn-head-2016-800x600.jpg │ ├── shawn-head-2016-square-200.jpg │ ├── shawn-head-2016-square-800.jpg │ ├── shawn-head-2016-square-big.jpg │ ├── shawn-sidebar.jpg │ ├── shawndown.jpg │ ├── shawnplaying.jpg │ ├── shawnplaying_small.jpg │ ├── shawnpool.jpg │ ├── shawnpool.png │ ├── shawnreally.jpg │ ├── shawnreally.png │ ├── shawnsmirk.jpg │ ├── shawnsmirk.png │ ├── stwhead.png │ ├── stwhead_100x130.jpg │ ├── stwhead_1024(300dpi).jpg │ ├── stwhead_1024(300dpi).png │ ├── stwhead_130x160.jpg │ ├── stwhead_130x160.png │ ├── stwhead_144.png │ ├── stwhead_144_square.jpg │ ├── stwhead_144_square.png │ ├── stwhead_220x245.jpg │ ├── stwhead_320.png │ ├── stwhead_640.jpg │ ├── stwhead_640.png │ ├── stwhead_72_square.png │ ├── stwhead_78_100.jpg │ ├── stwheadcigar.png │ ├── stwspeaking.jpg │ ├── turningheadshot.gif │ └── tx_head.png ├── hwpod │ ├── Brian-Friesen.jpg │ ├── IBM360Selectric.jpg │ ├── Merrick-christensen.jpg │ ├── OhioScientific.jpg │ ├── Rui-Carvalho.jpg │ ├── Stacey-Mulcahy.jpg │ ├── TI99.jpg │ ├── Tim-Huckaby.jpg │ ├── aaron-skonnard.jpg │ ├── amstrad6128.jpg │ ├── apple2.jpg │ ├── apple2c.jpg │ ├── apple2e.jpg │ ├── apple2gs.jpg │ ├── apple2plus.jpg │ ├── apple3.jpg │ ├── atari400.jpg │ ├── atari800.jpg │ ├── atley-hunter.jpg │ ├── barry-dorrans.jpg │ ├── bbcmicro-b.jpg │ ├── beth-massi.jpg │ ├── bill-vaughn.jpg │ ├── bill-wagner.jpg │ ├── billy-hollis.jpg │ ├── brad-wilson.jpg │ ├── brian-noyes.jpg │ ├── bruce-thomas.jpg │ ├── burke-holland.jpg │ ├── c64.jpg │ ├── carl-franklin.jpg │ ├── charles-petzold.jpg │ ├── chris-anderson.jpg │ ├── chris-sells.jpg │ ├── chris-woodruff.jpg │ ├── clemens-vasters.jpg │ ├── coleco-adam.jpg │ ├── commodore-pet.jpg │ ├── compaq-3.jpg │ ├── compaq-portable.jpg │ ├── corey-house.jpg │ ├── cpc464.jpg │ ├── cyber-mainframe.jpg │ ├── dan-wahlin.jpg │ ├── dave-ward.jpg │ ├── david-giard.jpg │ ├── deborah-kurata.jpg │ ├── digital-vax.jpg │ ├── dylan-beattie.jpg │ ├── erik-mork.jpg │ ├── frans-bouma.jpg │ ├── fritz-onion.jpg │ ├── g-andrew-duthie.jpg │ ├── gateway.jpg │ ├── generic-cpm.jpg │ ├── glenn-block.jpg │ ├── hadi-hariri.jpg │ ├── hp-2100.jpg │ ├── hp-35.jpg │ ├── hp-41.jpg │ ├── ian-griffiths.jpg │ ├── ibm-3227.jpg │ ├── ibm-cardpunch.jpg │ ├── ibm-pc.jpg │ ├── ibm-ps2.jpg │ ├── jeff-atwood.jpg │ ├── jeff-handley.jpg │ ├── jennifer-marsman.jpg │ ├── jeremy-likness.jpg │ ├── jesse-liberty.jpg │ ├── jim-wilson.jpg │ ├── jim-wooley.jpg │ ├── joe-eames.jpg │ ├── john-papa.jpg │ ├── john-petersen.jpg │ ├── john-robbins.jpg │ ├── jon-flanders.jpg │ ├── jon-galloway.jpg │ ├── jose-miguel-torres.jpg │ ├── joseph-guadagno.jpg │ ├── julie-lerman.jpg │ ├── julie-yack.jpg │ ├── k-scott-allen.jpg │ ├── kate-gregory.jpg │ ├── kathleen-dollard.jpg │ ├── kaypro.jpg │ ├── keith-elder.jpg │ ├── kent-alstad.jpg │ ├── kevin-grossnicklaus.jpg │ ├── lars-klint.jpg │ ├── laurent-bugnion.jpg │ ├── lino-tadros.jpg │ ├── logo.jpg │ ├── logo_125.jpg │ ├── logo_200.jpg │ ├── lynn-langit.jpg │ ├── mac-128k.jpg │ ├── mac-lc-ii.jpg │ ├── mads-torgersen.jpg │ ├── mainframe.jpg │ ├── mario-cardinal.jpg │ ├── martine-dowden.jpg │ ├── mary-jo-foley.jpg │ ├── matt-pietrek.jpg │ ├── matt-podwysocki.jpg │ ├── michele-bustamante.jpg │ ├── miguel-castro.jpg │ ├── miguel-de-icaza.jpg │ ├── mikayla-hutchinson.jpg │ ├── mike-woodring.jpg │ ├── monrobot.jpg │ ├── nick-landry.jpg │ ├── pc-clone.jpg │ ├── pcclone.jpg │ ├── pdp-10.jpg │ ├── pdp-8e.jpg │ ├── peter-ritchie.jpg │ ├── phil-haack.jpg │ ├── powerbookduo-dock.jpg │ ├── quique-martinez.jpg │ ├── rachel-appel.jpg │ ├── richard-campbell.jpg │ ├── richard-hundhausen.jpg │ ├── rick-strahl.jpg │ ├── rob-hale.jpg │ ├── rob-windsor.jpg │ ├── rocky-lhotka.jpg │ ├── rod-paddock.jpg │ ├── rowan-miller.jpg │ ├── sara-ford.jpg │ ├── scott-guthrie.jpg │ ├── scott-hanselman.jpg │ ├── scott-hunter.jpg │ ├── scott-meyers.jpg │ ├── scott-stanfield.jpg │ ├── selectric.jpg │ ├── shawn-wildermuth.jpg │ ├── spectra-7046.jpg │ ├── stephen-forte.jpg │ ├── steve-evans.jpg │ ├── steve-smith.jpg │ ├── stuart-celarier.jpg │ ├── sym-1.jpg │ ├── tandy-1000hx.jpg │ ├── ted-neward.jpg │ ├── teletype.jpg │ ├── thompson-to7.jpg │ ├── tim-heuer.jpg │ ├── timexsinclair1000.jpg │ ├── todd-miranda.jpg │ ├── toshiba-msx-hx10.jpg │ ├── troy-hunt.jpg │ ├── trs80-m1.jpg │ ├── trs80.jpg │ ├── trs80coco.jpg │ ├── vic20.jpg │ ├── vt100.jpg │ ├── walt-ritscher.jpg │ ├── ward-bell.jpg │ ├── win95.jpg │ ├── z-183.jpg │ ├── zx-spectrum-128.jpg │ ├── zx-spectrum.jpg │ └── zx81.jpg ├── logo_150_bk_trans.png ├── logo_300x94_bk_trans.png ├── ps_logo.png ├── ps_logo_sm.png ├── pslogo-new-inverse.png ├── pslogo-new.png ├── shawn-head-sm.gif ├── shawn-head-sm.jpg ├── shawn-head.gif ├── shawn-tight-head.gif ├── turning-2016.gif └── vue-ad.jpg ├── js ├── about.js ├── recaptcha.js ├── site.es5.js ├── site.es5.min.js └── site.js ├── lib ├── cookieconsent │ └── build │ │ ├── cookieconsent.min.css │ │ └── cookieconsent.min.js ├── fortawesome │ └── fontawesome-free │ │ ├── js │ │ ├── all.js │ │ ├── all.min.js │ │ ├── brands.js │ │ ├── brands.min.js │ │ ├── conflict-detection.js │ │ ├── conflict-detection.min.js │ │ ├── fontawesome.js │ │ ├── fontawesome.min.js │ │ ├── regular.js │ │ ├── regular.min.js │ │ ├── solid.js │ │ ├── solid.min.js │ │ ├── v4-shims.js │ │ └── v4-shims.min.js │ │ └── sprites │ │ ├── brands.svg │ │ ├── regular.svg │ │ └── solid.svg ├── lodash │ ├── lodash.js │ └── lodash.min.js └── respond.js │ └── dest │ ├── respond.matchmedia.addListener.min.js │ ├── respond.matchmedia.addListener.src.js │ ├── respond.min.js │ └── respond.src.js ├── web.config └── wlwmanifest.xml /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | 7 | # Standard to msysgit 8 | *.doc diff=astextplain 9 | *.DOC diff=astextplain 10 | *.docx diff=astextplain 11 | *.DOCX diff=astextplain 12 | *.dot diff=astextplain 13 | *.DOT diff=astextplain 14 | *.pdf diff=astextplain 15 | *.PDF diff=astextplain 16 | *.rtf diff=astextplain 17 | *.RTF diff=astextplain 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # WilderBlog 2 | 3 | ### **Archived Project** 4 | 5 | I am no longer using this for my blog, but feel free to use it as the basis of yours. I've moved to a static site generator (@11ty). You can see that repo here: 6 | 7 | > https://github.com/shawnwildermuth/WilderBlogGenerator 8 | 9 | Project Status: Archived 10 | 11 | I re-wrote my blog using a new stack of web technologies including: 12 | 13 | - .NET 5 14 | - ASP.NET Core 5 15 | - Entity Framework Core 5 16 | - Vue 17 | - Bootstrap 4 18 | - Azure Websites (Docker) 19 | - Azure Blob Storage 20 | 21 | The project wasn't build to be a blog engine or a easy to re-use for your own blog. It doesn't mean you can't use it for that. Feel free to fork the project, but that wasn't a design goal. 22 | 23 | The current project is built with many pre-release technologies so that the code-base will change a lot as these technologies get closer to release. 24 | 25 | See my blog entry (and the live site) for more information on how and why I built it: 26 | 27 | - http://wildermuth.com/2016/04/14/Welcome-to-the-New-Wildermuth-com 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /src/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /src/.editorconfig: -------------------------------------------------------------------------------- 1 | [*.cs] 2 | 3 | # IDE0011: Add braces 4 | csharp_prefer_braces = when_multiline:warning 5 | -------------------------------------------------------------------------------- /src/WilderBlog.Data/BaseRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WilderBlog.Data 4 | { 5 | public class BaseRepository 6 | { 7 | protected int CalculatePages(int totalCount, int pageSize) 8 | { 9 | return ((int)(totalCount / pageSize)) + ((totalCount % pageSize) > 0 ? 1 : 0); 10 | } 11 | } 12 | } -------------------------------------------------------------------------------- /src/WilderBlog.Data/BlogResult.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace WilderBlog.Data 4 | { 5 | public class BlogResult 6 | { 7 | public IEnumerable Stories = new List(); 8 | public int TotalResults; 9 | public int TotalPages; 10 | public int CurrentPage; 11 | } 12 | } -------------------------------------------------------------------------------- /src/WilderBlog.Data/BlogStory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace WilderBlog.Data 5 | { 6 | public class BlogStory 7 | { 8 | public int Id { get; set; } 9 | public string Body { get; set; } = ""; 10 | public string Categories { get; set; } = ""; 11 | public DateTime DatePublished { get; set; } 12 | public bool IsPublished { get; set; } 13 | public string Slug { get; set; } = ""; 14 | public string Title { get; set; } = ""; 15 | public string? UniqueId { get; set; } = ""; 16 | public string? FeatureImageUrl { get; set; } = ""; 17 | public string? Abstract { get; set; } = ""; 18 | } 19 | } -------------------------------------------------------------------------------- /src/WilderBlog.Data/Episode.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace WilderBlog.Data 4 | { 5 | public enum EpisodeStatus 6 | { 7 | Planned, 8 | Live, 9 | Staged 10 | } 11 | 12 | public class Episode 13 | { 14 | public string[] Blurb { get; set; } = new string[0]; 15 | public int EpisodeNumber { get; set; } 16 | public string GuestName { get; set; } = ""; 17 | public string[] GuestBio { get; set; } = new string[0]; 18 | public string GuestHeadshot { get; set; } = ""; 19 | public string GuestFirstMachine { get; set; } = ""; 20 | public string GuestFirstMachineName { get; set; } = ""; 21 | public string GuestFirstMachineLink { get; set; } = ""; 22 | public EpisodeLink[] GuestLinks { get; set; } = new EpisodeLink[0]; 23 | public string PodcastName { get; set; } = ""; 24 | public string AudioLink { get; set; } = ""; 25 | public DateTime PublishedDate { get; set; } 26 | public EpisodeStatus Status { get; set; } 27 | public TimeSpan Length { get; set; } 28 | public string YouTubeLink { get; set; } = ""; 29 | 30 | public string Slug() 31 | { 32 | return string.Concat(EpisodeNumber, "_", GuestName.Replace(" ", "_")); 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /src/WilderBlog.Data/EpisodeLink.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace WilderBlog.Data 7 | { 8 | public class EpisodeLink 9 | { 10 | public string Name { get; set; } = ""; 11 | public string Link { get; set; } = ""; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /src/WilderBlog.Data/IWilderRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | 5 | namespace WilderBlog.Data 6 | { 7 | public interface IWilderRepository 8 | { 9 | // Story 10 | Task GetStories(int pageSize = 10, int page = 1); 11 | Task GetStoriesByTerm(string term, int pageSize, int page); 12 | Task GetStoriesByTag(string tag, int pageSize, int page); 13 | 14 | Task GetStory(int id); 15 | Task GetStory(string slug); 16 | 17 | Task> GetCategories(); 18 | 19 | void AddStory(BlogStory story); 20 | Task DeleteStory(string postid); 21 | Task SaveAllAsync(); 22 | 23 | } 24 | } -------------------------------------------------------------------------------- /src/WilderBlog.Data/Migrations/20201109173732_AddingAbstracts.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace WilderBlog.Data.Migrations 4 | { 5 | public partial class AddingAbstracts : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.AddColumn( 10 | name: "Abstract", 11 | table: "BlogStory", 12 | nullable: true); 13 | 14 | migrationBuilder.AddColumn( 15 | name: "FeatureImageUrl", 16 | table: "BlogStory", 17 | nullable: true); 18 | } 19 | 20 | protected override void Down(MigrationBuilder migrationBuilder) 21 | { 22 | migrationBuilder.DropColumn( 23 | name: "Abstract", 24 | table: "BlogStory"); 25 | 26 | migrationBuilder.DropColumn( 27 | name: "FeatureImageUrl", 28 | table: "BlogStory"); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/WilderBlog.Data/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WilderBlog.Data 8 | { 9 | public class Program 10 | { 11 | public static void Main(string[] args) 12 | { 13 | 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/WilderBlog.Data/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:51682/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WilderBlog.Data": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:51683/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/WilderBlog.Data/Publication.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | namespace WilderBlog.Data 3 | { 4 | public class Publications 5 | { 6 | public int Id { get; set; } 7 | public string Comments { get; set; } = ""; 8 | public DateTime DatePublished { get; set; } 9 | public bool IsBook { get; set; } 10 | public string Link { get; set; } = ""; 11 | public string PublicationName { get; set; } = ""; 12 | public string Publisher { get; set; } = ""; 13 | public string Title { get; set; } = ""; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/WilderBlog.Data/WilderBlog.Data.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | WilderBlog.Data Class Library 5 | Shawn Wildermuth 6 | netstandard2.1 7 | true 8 | WilderBlog.Data 9 | WilderBlog.Data 10 | enable 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /src/WilderBlog.Data/WilderContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 3 | using Microsoft.EntityFrameworkCore; 4 | using Microsoft.EntityFrameworkCore.Metadata.Builders; 5 | using Microsoft.Extensions.Configuration; 6 | 7 | namespace WilderBlog.Data 8 | { 9 | public class WilderContext : IdentityDbContext 10 | { 11 | public WilderContext(DbContextOptions options, IConfiguration config) : base(options) 12 | { 13 | _config = config; 14 | } 15 | 16 | private IConfiguration _config; 17 | 18 | public DbSet Stories => Set(); 19 | 20 | protected override void OnModelCreating(ModelBuilder builder) 21 | { 22 | // Create Defaults 23 | base.OnModelCreating(builder); 24 | 25 | MapEntity(builder.Entity()); 26 | } 27 | 28 | private void MapEntity(EntityTypeBuilder bldr) 29 | { 30 | // Override the name of the table because of a RC2 change 31 | bldr.ToTable("BlogStory"); 32 | } 33 | 34 | protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) 35 | { 36 | optionsBuilder.UseSqlServer(_config["WilderDb:ConnectionString"]); 37 | base.OnConfiguring(optionsBuilder); 38 | } 39 | 40 | } 41 | } -------------------------------------------------------------------------------- /src/WilderBlog.Data/WilderUser.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Identity; 2 | 3 | namespace WilderBlog.Data 4 | { 5 | public class WilderUser : IdentityUser 6 | { 7 | } 8 | } -------------------------------------------------------------------------------- /src/WilderBlog.Data/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "WilderDb": { 3 | "OldConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=OldWilderDb;Trusted_Connection=True;MultipleActiveResultsets=true;", 4 | "ConnectionString": "Server=(localdb)\\MSSQLLocalDB;Database=WilderBlogDb;Trusted_Connection=True;MultipleActiveResultsets=true;" 5 | } 6 | } -------------------------------------------------------------------------------- /src/WilderBlog.Tests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:55537/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WilderBlog.Tests": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:55538/" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /src/WilderBlog.Tests/RepositoryTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace WilderBlog.Tests 5 | { 6 | public class RepositoryTests 7 | { 8 | [Fact] 9 | public void TestConstruction() 10 | { 11 | // TODO 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/WilderBlog.Tests/WilderBlog.Tests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | all 14 | runtime; build; native; contentfiles; analyzers; buildtransitive 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /src/WilderBlog/Controllers/Api/ActiveUsersController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.Extensions.Caching.Memory; 8 | using WilderBlog.Services; 9 | 10 | namespace WilderBlog.Controllers.Api 11 | { 12 | public class ActiveUsersController : Controller 13 | { 14 | private IMemoryCache _cache; 15 | 16 | public ActiveUsersController(IMemoryCache cache) 17 | { 18 | _cache = cache; 19 | } 20 | 21 | [HttpGet("/api/active/users")] 22 | public IActionResult Get() 23 | { 24 | try 25 | { 26 | var users = ActiveUsersMiddleware.GetActiveUserCount(_cache); 27 | return Ok(new { ActiveUsers = users, Message = $"{users} active on the site" }); 28 | } 29 | catch (Exception ex) 30 | { 31 | return Ok(new { ActiveUsers = 0, Message = $"Exception Thrown during process: {ex}" }); 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/WilderBlog/Controllers/Api/EpisodeController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Logging; 6 | using WilderBlog.Services.DataProviders; 7 | 8 | namespace WilderBlog.Controllers.Api 9 | { 10 | [Route("/api/episodes")] 11 | public class EpisodeController : Controller 12 | { 13 | private PodcastEpisodesProvider _provider; 14 | const int PAGE_SIZE = 10; 15 | private ILogger _logger; 16 | 17 | public EpisodeController(PodcastEpisodesProvider podcastProvider, ILogger logger) 18 | { 19 | _provider = podcastProvider; 20 | _logger = logger; 21 | } 22 | 23 | IEnumerable LiveEpisodes => _provider.Get() 24 | .Where(e => e.Status == PodcastEpisodeStatus.Live && e.PublishedDate <= DateTime.UtcNow) 25 | .OrderBy(e => e.EpisodeNumber); 26 | 27 | [HttpGet("{page:int}")] 28 | public IActionResult Get(int page = 1, int pageSize = PAGE_SIZE) 29 | { 30 | try 31 | { 32 | var data = LiveEpisodes; 33 | 34 | var results = new 35 | { 36 | Success = true, 37 | ResultCount = data.Count(), 38 | Page = page, 39 | Results = data.Skip(pageSize * (page - 1)).Take(pageSize) 40 | }; 41 | 42 | return Ok(results); 43 | } 44 | catch (Exception ex) 45 | { 46 | _logger.LogError("Failed to get episodes from the API", ex); 47 | return BadRequest(); 48 | } 49 | 50 | } 51 | 52 | [HttpGet("{number:int}")] 53 | public ActionResult Get(int number) 54 | { 55 | try 56 | { 57 | var data = LiveEpisodes 58 | .First(e => e.EpisodeNumber == number); 59 | 60 | return data; 61 | } 62 | catch (Exception ex) 63 | { 64 | _logger.LogError("Failed to get episode from the API", ex); 65 | 66 | return BadRequest(); 67 | } 68 | } 69 | 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/WilderBlog/Controllers/Api/MessageController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Cors; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using System; 5 | using System.Threading.Tasks; 6 | using WilderBlog.Models; 7 | using WilderBlog.Services; 8 | 9 | namespace WilderBlog.Controllers.Api 10 | { 11 | [Route("api/[controller]")] 12 | [ApiController] 13 | [EnableCors(Startup.CorsPolicyName)] 14 | public class MessageController : ControllerBase 15 | { 16 | private readonly IMailService _mailService; 17 | private readonly ILogger _logger; 18 | 19 | public MessageController(IMailService mailService, ILogger logger) 20 | { 21 | _mailService = mailService; 22 | _logger = logger; 23 | } 24 | 25 | public async Task Post([FromBody] ContactModel model) 26 | { 27 | try 28 | { 29 | if (await _mailService.SendMailAsync("WilderMindsContact.txt", model.Name, model.Email, model.Subject, model.Msg, model.Phone)) 30 | { 31 | return Ok(new { Success = true, Message = "Message Sent" }); 32 | } 33 | 34 | } 35 | catch (Exception ex) 36 | { 37 | _logger.LogError("Failed to send email from contact page", ex); 38 | return BadRequest(new { Reason = "Error Occurred" }); 39 | } 40 | 41 | return BadRequest(new { Reason = "Failed to send email..." }); 42 | 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/WilderBlog/Controllers/Web/AdminController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Identity; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.AspNetCore.Mvc.ModelBinding; 5 | using WilderBlog.Data; 6 | 7 | namespace WilderBlog.Controllers 8 | { 9 | [Route("[controller]")] 10 | public class AdminController : Controller 11 | { 12 | [Route("changepwd")] 13 | public async Task ChangePwd([FromServices] UserManager userManager, 14 | string username, 15 | string oldPwd, 16 | string newPwd) 17 | { 18 | var user = await userManager.FindByEmailAsync(username); 19 | if (user == null) return BadRequest(new { success = false }); 20 | var result = await userManager.ChangePasswordAsync(user, oldPwd, newPwd); 21 | if (result.Succeeded) return Ok(new { success = true }); 22 | else return BadRequest(new { success = false, errors = result.Errors }); 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/WilderBlog/Controllers/Web/PodcastController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using WilderBlog.Data; 7 | using WilderBlog.Services.DataProviders; 8 | 9 | namespace WilderBlog.Controllers 10 | { 11 | [Route("hwpod")] 12 | public class PodcastController : Controller 13 | { 14 | private PodcastEpisodesProvider _podcastProvider; 15 | 16 | public PodcastController(PodcastEpisodesProvider podcastProvider) 17 | { 18 | _podcastProvider = podcastProvider; 19 | } 20 | 21 | [HttpGet("")] 22 | public IActionResult Index() 23 | { 24 | var episodes = _podcastProvider.Get(); 25 | var latest = _podcastProvider.Get().Where(e => e.Status == PodcastEpisodeStatus.Live && 26 | e.PublishedDate.AddHours(14) <= DateTime.UtcNow) 27 | .OrderByDescending(e => e.EpisodeNumber) 28 | .First(); 29 | 30 | return View(Tuple.Create>(latest, episodes)); 31 | } 32 | 33 | [HttpGet("{id:int}/{tag}")] 34 | public IActionResult Episode(int id, string tag) 35 | { 36 | var episode = _podcastProvider.Get() 37 | .Where(e => e.Status == PodcastEpisodeStatus.Live && 38 | e.EpisodeNumber == id) 39 | .FirstOrDefault(); 40 | 41 | if (episode == null) return RedirectToAction("Index"); 42 | 43 | return View(episode); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/WilderBlog/Controllers/Web/SearchController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | using WilderBlog.Data; 8 | 9 | namespace WilderBlog.Controllers 10 | { 11 | [Route("[controller]")] 12 | public class SearchController : Controller 13 | { 14 | private IWilderRepository _repo; 15 | private readonly ILogger _logger; 16 | 17 | public SearchController(IWilderRepository repo, ILogger logger) 18 | { 19 | _repo = repo; 20 | _logger = logger; 21 | } 22 | 23 | [HttpGet("")] 24 | public IActionResult Index() 25 | { 26 | ViewBag.Term = ""; 27 | return View(new BlogResult()); 28 | } 29 | 30 | [HttpGet("{term}/{page:int?}")] 31 | public async Task Pager(string term, int page = 1) 32 | { 33 | ViewBag.Term = term; 34 | var results = new BlogResult(); 35 | try 36 | { 37 | results = await _repo.GetStoriesByTerm(term, 15, page); 38 | } 39 | catch (Exception ex) 40 | { 41 | _logger.LogError($"Failed to get search results: {term} - {ex}"); 42 | } 43 | 44 | return View("Index", results); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/WilderBlog/Controllers/Web/TagController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Threading.Tasks; 5 | using WilderBlog.Data; 6 | 7 | namespace WilderBlog.Controllers 8 | { 9 | [Route("[controller]")] 10 | public class TagController : Controller 11 | { 12 | private IWilderRepository _repo; 13 | private readonly ILogger _logger; 14 | readonly int _pageSize = 25; 15 | 16 | public TagController(IWilderRepository repo, ILogger logger) 17 | { 18 | _repo = repo; 19 | _logger = logger; 20 | } 21 | 22 | [HttpGet("{tag}")] 23 | public Task Index(string tag) 24 | { 25 | return Pager(tag, 1); 26 | } 27 | 28 | [HttpGet("{tag}/{page}")] 29 | public async Task Pager(string tag, int page) 30 | { 31 | BlogResult result = new(); 32 | 33 | try 34 | { 35 | result = await _repo.GetStoriesByTag(tag, _pageSize, page); 36 | } 37 | catch (Exception ex) 38 | { 39 | _logger.LogError($"Failed to load Tags: {tag} - {ex}"); 40 | 41 | } 42 | 43 | return View("Index", result); 44 | } 45 | } 46 | } -------------------------------------------------------------------------------- /src/WilderBlog/Controllers/Web/VideosController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using WilderBlog.Services.DataProviders; 8 | 9 | namespace WilderBlog.Controllers 10 | { 11 | [Route("[controller]")] 12 | public class VideosController : Controller 13 | { 14 | private VideosProvider _videos; 15 | 16 | public VideosController(VideosProvider videos) 17 | { 18 | _videos = videos; 19 | } 20 | 21 | [HttpGet("")] 22 | public IActionResult Index() 23 | { 24 | return View(_videos.Get()); 25 | } 26 | 27 | [HttpGet("{id:int}")] 28 | public IActionResult Video(int id) 29 | { 30 | var result = _videos.Get().Where(v => v.Id == id).FirstOrDefault(); 31 | if (result == null) return RedirectToAction("Index"); 32 | return View(result); 33 | } 34 | 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/WilderBlog/Data/calendar.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/shawnwildermuth/WilderBlog/2665aca9eaaa24b937d193df72f4a8146acf47f5/src/WilderBlog/Data/calendar.json -------------------------------------------------------------------------------- /src/WilderBlog/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM mcr.microsoft.com/dotnet/aspnet:5.0-focal AS base 2 | WORKDIR /app 3 | EXPOSE 80 4 | 5 | FROM wilderminds/net5-npm:15.0 AS build 6 | WORKDIR /src 7 | COPY ["WilderBlog/WilderBlog.csproj", "WilderBlog/"] 8 | COPY ["WilderBlog.Data/WilderBlog.Data.csproj", "WilderBlog.Data/"] 9 | RUN dotnet restore "WilderBlog/WilderBlog.csproj" 10 | COPY . . 11 | WORKDIR "/src/WilderBlog" 12 | RUN npm ci 13 | RUN npm run purge 14 | 15 | RUN dotnet build "WilderBlog.csproj" -c Release -o /app/build 16 | 17 | FROM build AS publish 18 | RUN dotnet publish "WilderBlog.csproj" -c Release -o /app/publish 19 | 20 | FROM base AS final 21 | WORKDIR /app 22 | COPY --from=publish /app/publish . 23 | ENTRYPOINT ["dotnet", "WilderBlog.dll"] -------------------------------------------------------------------------------- /src/WilderBlog/EmailTemplates/ContactTemplate.txt: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |
5 | From: {0} 6 |
7 |
8 | Name: {1} 9 |
10 |
11 | Subject: {2} 12 |
13 |
14 | Message: 15 |
16 |
17 | {3} 18 |
19 | 20 | -------------------------------------------------------------------------------- /src/WilderBlog/EmailTemplates/WilderMindsContact.txt: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 |

From Wilder Minds Website!

5 |
6 | Name: {1} 7 |
8 |
9 | From: {0} 10 |
11 |
12 | Phone: {4} 13 |
14 |
15 | Subject: {2} 16 |
17 |
18 | Message: 19 |
20 |
21 | {3} 22 |
23 | 24 | -------------------------------------------------------------------------------- /src/WilderBlog/EmailTemplates/exceptionMessage.txt: -------------------------------------------------------------------------------- 1 | Exception Occurred on WilderBlog 2 | 3 | {2} 4 | 5 | Exception Info: 6 | {3} -------------------------------------------------------------------------------- /src/WilderBlog/EmailTemplates/logmessage.txt: -------------------------------------------------------------------------------- 1 | Subject: {2} 2 | 3 | Message: {3} 4 | -------------------------------------------------------------------------------- /src/WilderBlog/Filters/WilderExceptionFilter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.Mvc.Filters; 8 | using Microsoft.Extensions.Hosting; 9 | 10 | namespace WilderBlog.Filters 11 | { 12 | public class WilderExceptionFilter : ExceptionFilterAttribute 13 | { 14 | private readonly IHostEnvironment _hostingEnvironment; 15 | 16 | public WilderExceptionFilter(IHostEnvironment hostingEnvironment) 17 | { 18 | _hostingEnvironment = hostingEnvironment; 19 | } 20 | 21 | public override void OnException(ExceptionContext context) 22 | { 23 | if (_hostingEnvironment.IsDevelopment()) 24 | { 25 | return; 26 | } 27 | 28 | 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/WilderBlog/Helpers/DataExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Text.RegularExpressions; 5 | using WilderBlog.Data; 6 | 7 | namespace WilderBlog.Helpers 8 | { 9 | public static class DataExtensions 10 | { 11 | 12 | public static string GetSummary(this BlogStory me) 13 | { 14 | var MAXPARAGRAPHS = 3; 15 | var regex = new Regex("(]*>.*?

)", RegexOptions.IgnoreCase | RegexOptions.Singleline); 16 | var result = regex.Matches(me.Body); 17 | StringBuilder bldr = new StringBuilder(); 18 | var x = 0; 19 | foreach (Match m in result) 20 | { 21 | x++; 22 | bldr.Append(m.Value); 23 | if (x == MAXPARAGRAPHS) break; 24 | } 25 | return bldr.ToString(); 26 | 27 | } 28 | 29 | public static string GetStoryUrl(this BlogStory story) 30 | { 31 | return string.Format("{0:0000}/{1:00}/{2:00}/{3}", story.DatePublished.Year, story.DatePublished.Month, story.DatePublished.Day, GetUrlSafeTitle(story)); 32 | } 33 | 34 | //public static Uri GetStoryFullUri(this BlogStory story, HttpRequest request) 35 | //{ 36 | // return new Uri(new Uri(request.GetDisplayUrl()), story.GetStoryUrl()); 37 | //} 38 | 39 | public static string GetUrlSafeTitle(this BlogStory story) 40 | { 41 | // Characters to replace with underscore 42 | char[] replacements = @" ""'?*.,+&:;\/#".ToCharArray(); 43 | 44 | string[] splits = story.Title.Split(replacements, StringSplitOptions.RemoveEmptyEntries); 45 | StringBuilder bldr = new StringBuilder(); 46 | foreach (string s in splits) 47 | { 48 | bldr.Append(s); 49 | bldr.Append("-"); 50 | } 51 | return bldr.ToString(0, bldr.Length - 1); 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/WilderBlog/Helpers/HealthCheckExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Diagnostics.HealthChecks; 9 | using WilderBlog.Data; 10 | 11 | namespace WilderBlog.Helpers 12 | { 13 | public static class HealthCheckExtensions 14 | { 15 | public static IServiceCollection ConfigureHealthChecks(this IServiceCollection coll, IConfiguration config) 16 | { 17 | var connectionString = config["WilderDb:ConnectionString"]; 18 | var instrumentationKey = config["ApplicationInsights:InstrumentationKey"]; 19 | 20 | coll.AddHealthChecks() 21 | .AddSqlServer(connectionString, name: "DbConnection") 22 | .AddSqlServer(connectionString, 23 | "SELECT COUNT(*) FROM BlogStory", 24 | "BlogDb") 25 | .AddDbContextCheck() 26 | .AddApplicationInsightsPublisher(instrumentationKey: instrumentationKey); ; 27 | 28 | return coll; 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/WilderBlog/Logger/EmailLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Microsoft.AspNetCore.Http; 3 | using Microsoft.AspNetCore.Http.Extensions; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Logging; 6 | using WilderBlog.Services; 7 | 8 | namespace WilderBlog.Logger 9 | { 10 | public class EmailLogger : ILogger 11 | { 12 | private string _categoryName; 13 | private Func _filter; 14 | private IMailService _mailService; 15 | private readonly IHttpContextAccessor _contextAccessor; 16 | 17 | public EmailLogger(string categoryName, Func filter, IMailService mailService, IHttpContextAccessor contextAccessor) 18 | { 19 | _categoryName = categoryName; 20 | _filter = filter; 21 | _mailService = mailService; 22 | _contextAccessor = contextAccessor; 23 | } 24 | 25 | public IDisposable BeginScope(TState state) 26 | { 27 | // Not necessary 28 | return null!; 29 | } 30 | 31 | public bool IsEnabled(LogLevel logLevel) 32 | { 33 | return (_filter == null || _filter(_categoryName, logLevel)); 34 | } 35 | 36 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, Func formatter) 37 | { 38 | if (!IsEnabled(logLevel)) 39 | { 40 | return; 41 | } 42 | 43 | if (formatter == null) 44 | { 45 | throw new ArgumentNullException(nameof(formatter)); 46 | } 47 | 48 | var message = formatter(state, exception); 49 | 50 | if (string.IsNullOrEmpty(message)) 51 | { 52 | return; 53 | } 54 | 55 | message = $@"
56 |

Error on WilderBlog

57 |

Level: {logLevel}

58 |

{message}

"; 59 | 60 | if (exception != null) 61 | { 62 | message += $"

{exception}

"; 63 | } 64 | 65 | var url = UriHelper.GetEncodedPathAndQuery(_contextAccessor.HttpContext?.Request); 66 | message += $"

Request: {url}

"; 67 | 68 | 69 | _mailService.SendMailAsync("logmessage.txt", "Shawn Wildermuth", "shawn@wildermuth.com", "[WilderBlog Log Message]", message).Wait(); 70 | 71 | } 72 | } 73 | } -------------------------------------------------------------------------------- /src/WilderBlog/Logger/EmailLoggerExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.Extensions.Logging; 8 | using WilderBlog.Services; 9 | 10 | namespace WilderBlog.Logger 11 | { 12 | public static class EmailLoggerExtensions 13 | { 14 | public static ILoggerFactory AddEmail(this ILoggerFactory factory, 15 | IMailService mailService, 16 | IHttpContextAccessor contextAccessor, 17 | Func? filter = null) 18 | { 19 | #pragma warning disable CS8604 // Possible null reference argument. 20 | factory.AddProvider(new EmailLoggerProvider(filter, mailService, contextAccessor)); 21 | #pragma warning restore CS8604 // Possible null reference argument. 22 | return factory; 23 | } 24 | 25 | public static ILoggerFactory AddEmail(this ILoggerFactory factory, 26 | IMailService mailService, 27 | IHttpContextAccessor contextAccessor, 28 | LogLevel minLevel) 29 | { 30 | return AddEmail( 31 | factory, 32 | mailService, 33 | contextAccessor, 34 | (_, logLevel) => logLevel >= minLevel); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/WilderBlog/Logger/EmailLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Http; 7 | using Microsoft.Extensions.Logging; 8 | using WilderBlog.Services; 9 | 10 | namespace WilderBlog.Logger 11 | { 12 | public class EmailLoggerProvider : ILoggerProvider 13 | { 14 | private readonly Func _filter; 15 | private readonly IMailService _mailService; 16 | private readonly IHttpContextAccessor _contextAccessor; 17 | 18 | public EmailLoggerProvider(Func filter, IMailService mailService, IHttpContextAccessor contextAccessor) 19 | { 20 | _mailService = mailService; 21 | _contextAccessor = contextAccessor; 22 | _filter = filter; 23 | } 24 | 25 | public ILogger CreateLogger(string categoryName) 26 | { 27 | return new EmailLogger(categoryName, _filter, _mailService, _contextAccessor); 28 | } 29 | 30 | public void Dispose() 31 | { 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/WilderBlog/Models/AdDateRange.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WilderBlog.Models 8 | { 9 | public class AdDateRange 10 | { 11 | public AdDateRange(string startDate, string endDate, params string[] ads) 12 | { 13 | if (!DateTime.TryParse(startDate, out Start) || !DateTime.TryParse(endDate, out End) || ads == null || ads.Length == 0) 14 | { 15 | throw new InvalidOperationException("Invalid Ads"); 16 | } 17 | Ads = ads; 18 | } 19 | 20 | public readonly DateTime Start; 21 | public readonly DateTime End; 22 | public readonly string[] Ads; 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/WilderBlog/Models/ContactModel.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace WilderBlog.Models 4 | { 5 | public class ContactModel 6 | { 7 | [Required] 8 | [EmailAddress] 9 | public string Email { get; set; } = ""; 10 | [MaxLength(4096)] 11 | [MinLength(5)] 12 | public string Msg { get; set; } = ""; 13 | [Required] 14 | public string Name { get; set; } = ""; 15 | [Required] 16 | public string Subject { get; set; } = ""; 17 | public string Recaptcha { get; set; } = ""; 18 | public string Phone { get; set; } = ""; 19 | } 20 | } -------------------------------------------------------------------------------- /src/WilderBlog/Models/SiteVerfiyResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WilderBlog.Models 8 | { 9 | public class SiteVerifyResult 10 | { 11 | public bool Success { get; set; } 12 | public string Challenge_Ts { get; set; } = ""; 13 | public string Hostname { get; set; } = ""; 14 | public List ErrorCodes { get; set; } = new(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/WilderBlog/Models/SiteVerifyModel.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace WilderBlog.Models 8 | { 9 | public class SiteVerifyModel 10 | { 11 | public string Secret { get; set; } = ""; 12 | public string Response { get; set; } = ""; 13 | public string RemoteIp { get; set; } = ""; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/WilderBlog/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.IO; 3 | using System.Linq; 4 | using System.Reflection; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.DependencyInjection; 10 | using Microsoft.Extensions.Logging; 11 | using Microsoft.Extensions.Options; 12 | using WilderBlog.Data; 13 | 14 | namespace WilderBlog 15 | { 16 | public class Program 17 | { 18 | public static void Main(string[] args) 19 | { 20 | var host = WebHost.CreateDefaultBuilder(args) 21 | .ConfigureAppConfiguration(ConfigureConfiguration) 22 | .UseStartup() 23 | .Build(); 24 | 25 | if (args.Contains("/seed")) 26 | { 27 | Seed(host).Wait(); 28 | } 29 | 30 | host.Run(); 31 | } 32 | 33 | private static async Task Seed(IWebHost host) 34 | { 35 | var scopeFactory = host.Services.GetService(); 36 | using (var scope = scopeFactory?.CreateScope()) 37 | { 38 | var initializer = scope?.ServiceProvider.GetService(); 39 | await initializer!.SeedAsync(); 40 | } 41 | } 42 | 43 | 44 | private static void ConfigureConfiguration(WebHostBuilderContext ctx, IConfigurationBuilder builder) 45 | { 46 | // Reset to remove the old configuration sources to give us complete control 47 | builder.Sources.Clear(); 48 | 49 | builder.SetBasePath(ctx.HostingEnvironment.ContentRootPath) 50 | .AddJsonFile("config.json", false, true) 51 | .AddUserSecrets(Assembly.GetEntryAssembly()) 52 | .AddEnvironmentVariables(); 53 | 54 | } 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/WilderBlog/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:8099/", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "WilderBlog": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "http://localhost:54451/" 25 | }, 26 | "Docker": { 27 | "commandName": "Docker", 28 | "launchBrowser": true, 29 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}", 30 | "httpPort": 10000 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /src/WilderBlog/Services/DataProviders/CalendarProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace WilderBlog.Services.DataProviders 8 | { 9 | public class CalendarProvider : DataProvider 10 | { 11 | public CalendarProvider(IHostEnvironment env) 12 | : base(env, "calendar.json") 13 | { 14 | } 15 | 16 | public override IEnumerable Get() 17 | { 18 | return base.Get().OrderBy(a => a.EventDate).ToList(); 19 | } 20 | } 21 | 22 | public class CalendarEntry 23 | { 24 | public string EventName { get; set; } = ""; 25 | public DateTime EventDate { get; set; } 26 | public int Length { get; set; } 27 | public string Link { get; set; } = ""; 28 | public string Location { get; set; } = ""; 29 | public string Note { get; set; } = ""; 30 | public string Logo { get; set; } = ""; 31 | public bool ReverseLogo { get; set; } = false; 32 | 33 | public string FormattedDate 34 | { 35 | get 36 | { 37 | if (Length > 1) 38 | { 39 | var endDate = EventDate.AddDays(Length - 1); 40 | if (endDate.Month == EventDate.Month) 41 | { 42 | return string.Format("{0} {1}-{2}, {3}", EventDate.ToString("MMM"), EventDate.Day, endDate.Day, EventDate.Year); 43 | } 44 | else 45 | { 46 | if (endDate.Year == EventDate.Year) 47 | { 48 | return string.Format("{0} {2}-{1} {3}, {4}", EventDate.ToString("MMM"), endDate.ToString("MMM"), EventDate.Day, endDate.Day, EventDate.Year); 49 | } 50 | else 51 | { 52 | return string.Format("{0}-{1}", EventDate.ToString("MMM d, YYYY"), endDate.ToString("MMM d, YYYY")); 53 | } 54 | } 55 | } 56 | else 57 | { 58 | return EventDate.ToString("MMM d, yyyy"); 59 | } 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/WilderBlog/Services/DataProviders/CoursesProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Hosting; 3 | 4 | namespace WilderBlog.Services.DataProviders 5 | { 6 | public class CoursesProvider : DataProvider 7 | { 8 | 9 | public CoursesProvider(IHostEnvironment env) 10 | : base(env, "courses.json") 11 | { 12 | } 13 | } 14 | 15 | public enum CourseType 16 | { 17 | Invalid = 0, 18 | Pluralsight = 1, 19 | WilderMinds = 2 20 | } 21 | 22 | public class Course 23 | { 24 | public string Id { get; set; } = ""; 25 | public string Name { get; set; } = ""; 26 | public string Hint { get; set; } = ""; 27 | public CourseType CourseType { get; set; } = CourseType.Pluralsight; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/WilderBlog/Services/DataProviders/DataProvider.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.IO; 3 | using Microsoft.AspNetCore.Hosting; 4 | using Microsoft.Extensions.Hosting; 5 | using Newtonsoft.Json; 6 | 7 | namespace WilderBlog.Services.DataProviders 8 | { 9 | public abstract class DataProvider 10 | { 11 | protected string _path; 12 | 13 | public DataProvider(IHostEnvironment env, string path) 14 | { 15 | _path = Path.Combine(env.ContentRootPath, $@"Data{Path.DirectorySeparatorChar}{path}"); 16 | } 17 | 18 | public virtual IEnumerable Get() 19 | { 20 | var json = File.ReadAllText(_path); 21 | return JsonConvert.DeserializeObject>(json); 22 | } 23 | 24 | } 25 | } -------------------------------------------------------------------------------- /src/WilderBlog/Services/DataProviders/PodcastEpisodesProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace WilderBlog.Services.DataProviders 8 | { 9 | public class PodcastEpisodesProvider : DataProvider 10 | { 11 | public PodcastEpisodesProvider(IHostEnvironment env) 12 | : base(env, "episodeList.json") 13 | { 14 | } 15 | 16 | public override IEnumerable Get() 17 | { 18 | return base.Get().OrderByDescending(a => a.PublishedDate).ToList(); 19 | } 20 | } 21 | 22 | public class PodcastEpisode 23 | { 24 | public string[] Blurb { get; set; } = new string[0]; 25 | public int EpisodeNumber { get; set; } 26 | public string[] GuestBio { get; set; } = new string[0]; 27 | public string GuestName { get; set; } = ""; 28 | public string GuestHeadshot { get; set; } = ""; 29 | public string GuestFirstMachineImage { get; set; } = ""; 30 | public string GuestFirstMachineLink { get; set; } = ""; 31 | public string GuestFirstMachineName { get; set; } = ""; 32 | public ICollection GuestLinks { get; set; } = new List(); 33 | public string PodcastName { get; set; } = ""; 34 | public string AudioLink { get; set; } = ""; 35 | public DateTime PublishedDate { get; set; } 36 | public string YouTubeLink { get; set; } = ""; 37 | public PodcastEpisodeStatus Status { get; set; } 38 | } 39 | 40 | public enum PodcastEpisodeStatus 41 | { 42 | Planned, 43 | Live, 44 | Staged 45 | } 46 | 47 | public class GuestLink 48 | { 49 | public string Name { get; set; } = ""; 50 | public string Link { get; set; } = ""; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/WilderBlog/Services/DataProviders/PublicationsProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace WilderBlog.Services.DataProviders 8 | { 9 | public class PublicationsProvider : DataProvider 10 | { 11 | public PublicationsProvider(IHostEnvironment env) 12 | : base(env, "publications.json") 13 | { 14 | } 15 | 16 | public override IEnumerable Get() 17 | { 18 | return base.Get().OrderByDescending(p => p.DatePublished).ToList(); 19 | } 20 | } 21 | 22 | public class Publication 23 | { 24 | public int Id { get; set; } 25 | public string PublicationName { get; set; } = ""; 26 | public string Publisher { get; set; } = ""; 27 | public DateTime DatePublished { get; set; } 28 | public string Comments { get; set; } = ""; 29 | public bool IsBook { get; set; } 30 | public string Title { get; set; } = ""; 31 | public string Link { get; set; } = ""; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/WilderBlog/Services/DataProviders/VideosProvider.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.Extensions.Hosting; 6 | 7 | namespace WilderBlog.Services.DataProviders 8 | { 9 | public class VideosProvider : DataProvider