├── .deployment ├── .editorconfig ├── .gitattributes ├── .gitignore ├── .travis.yml ├── .vscode ├── C4.code-snippets ├── launch.json ├── settings.json └── tasks.json ├── CoreWiki.Application ├── Articles │ ├── Managing │ │ ├── ArticleManagingProfile.cs │ │ ├── Commands │ │ │ ├── CreateNewArticleCommand.cs │ │ │ ├── CreateNewArticleCommandHandler.cs │ │ │ ├── CreateSkeletonArticleCommand.cs │ │ │ ├── DeleteArticleCommand.cs │ │ │ ├── DeleteArticleCommandHandler.cs │ │ │ ├── EditArticleCommand.cs │ │ │ └── EditArticleCommandHandler.cs │ │ ├── Dto │ │ │ ├── ArticleManageDto.cs │ │ │ ├── CommentDto.cs │ │ │ └── SlugHistoryDto.cs │ │ ├── Events │ │ │ ├── ArticleCreatedNotification.cs │ │ │ ├── ArticleDeletedNotification.cs │ │ │ ├── ArticleEditedNotification.cs │ │ │ └── DeleteHomePageAttemptNotification.cs │ │ ├── Exceptions │ │ │ ├── ArticleNotFoundException.cs │ │ │ ├── CreateArticleException.cs │ │ │ ├── DeleteArticleException.cs │ │ │ ├── InvalidTopicException.cs │ │ │ └── NoContentChangedException.cs │ │ ├── IArticleManagementService.cs │ │ ├── Impl │ │ │ └── ArticleManagementService.cs │ │ └── Queries │ │ │ ├── GetArticleHandler.cs │ │ │ ├── GetArticleQuery.cs │ │ │ ├── GetArticlesToCreateFromArticleHandler.cs │ │ │ ├── GetArticlesToCreateFromArticleQuery.cs │ │ │ └── GetIsTopicAvailableQuery.cs │ ├── Reading │ │ ├── ArticleReadingProfile.cs │ │ ├── Commands │ │ │ ├── CreateNewCommentCommand.cs │ │ │ ├── CreateNewCommentCommandHandler.cs │ │ │ ├── IncrementViewCountCommand.cs │ │ │ └── IncrementViewCountHandler.cs │ │ ├── Dto │ │ │ ├── ArticleReadingDto.cs │ │ │ ├── CommentDto.cs │ │ │ ├── CreateCommentDto.cs │ │ │ └── SlugHistoryDto.cs │ │ ├── Events │ │ │ └── CommentPostedNotification.cs │ │ ├── Exceptions │ │ │ └── CreateCommentException.cs │ │ ├── IArticleReadingService.cs │ │ ├── Impl │ │ │ └── ArticleReadingService.cs │ │ └── Queries │ │ │ ├── GetArticleByIdQuery.cs │ │ │ ├── GetArticleHandler.cs │ │ │ ├── GetArticleQuery.cs │ │ │ ├── GetArticleWithHistoriesBySlugQuery.cs │ │ │ ├── GetIsTopicAvailableQuery.cs │ │ │ ├── GetLatestArticlesQuery.cs │ │ │ └── GetSlugHistoryQuery.cs │ └── Search │ │ ├── Dto │ │ ├── ArticleSearchDto.cs │ │ └── SearchResultDto.cs │ │ ├── IArticlesSearchEngine.cs │ │ ├── Impl │ │ └── ArticlesDbSearchEngine.cs │ │ ├── Queries │ │ ├── SearchArticlesHandler.cs │ │ └── SearchArticlesQuery.cs │ │ └── SearchArticleProfile.cs ├── Common │ ├── CommandResult.cs │ ├── ConfigurePipeLineLogger.cs │ ├── Constants.cs │ ├── StringHelpers.cs │ └── UrlHelpers.cs ├── CoreWiki.Application.csproj └── MyAssemblyInfo.cs ├── CoreWiki.Core ├── CoreWiki.Core.csproj └── Domain │ ├── Article.cs │ ├── ArticleHistory.cs │ ├── Comment.cs │ ├── SearchResult.cs │ └── SlugHistory.cs ├── CoreWiki.Data.Abstractions ├── CoreWiki.Data.Abstractions.csproj └── Interfaces │ ├── IArticleRepository.cs │ ├── ICommentRepository.cs │ └── ISlugHistoryRepository.cs ├── CoreWiki.Data ├── ApplicationDbContext.cs ├── ArticleNotFoundException.cs ├── CoreWiki.Data.EntityFramework.csproj ├── Migrations │ ├── 20180619143102_Initial.Designer.cs │ ├── 20180619143102_Initial.cs │ ├── 20180624223112_AddSlugHistory.Designer.cs │ ├── 20180624223112_AddSlugHistory.cs │ ├── 20180626164441_ArticleHistory.Designer.cs │ ├── 20180626164441_ArticleHistory.cs │ ├── 20180703155655_Add AuthorName to article.Designer.cs │ ├── 20180703155655_Add AuthorName to article.cs │ └── ApplicationDbContextModelSnapshot.cs ├── Models │ ├── ArticleDAO.cs │ ├── ArticleHistoryDAO.cs │ ├── CommentDAO.cs │ ├── ProtectedArticles.cs │ └── SlugHistoryDAO.cs ├── Repositories │ ├── ArticleRepository.cs │ ├── CommentRepository.cs │ └── SlugHistoryRepository.cs ├── Security │ ├── CoreWikiIdentityContext.cs │ ├── CoreWikiUser.cs │ └── Migrations │ │ ├── 20180530213903_CreateIdentitySchema.Designer.cs │ │ ├── 20180530213903_CreateIdentitySchema.cs │ │ ├── 20180623153425_CanNotify.Designer.cs │ │ ├── 20180623153425_CanNotify.cs │ │ ├── 20180801205508_NormalizedRoleNamesToUpper.Designer.cs │ │ ├── 20180801205508_NormalizedRoleNamesToUpper.cs │ │ ├── 20181109171726_Added displayname to user.Designer.cs │ │ ├── 20181109171726_Added displayname to user.cs │ │ └── CoreWikiIdentityContextModelSnapshot.cs └── StartupExtensions.cs ├── CoreWiki.FirstStart ├── Areas │ └── FirstStart │ │ └── Pages │ │ ├── Index.cshtml │ │ ├── Index.cshtml.cs │ │ ├── Shared │ │ └── _Layout.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml ├── CoreWiki.FirstStart.csproj ├── FirstStartConfiguration.cs ├── StartupExtensions.cs └── UserAppConfig.cs ├── CoreWiki.Notifications.Abstractions ├── Configuration │ └── EmailNotifications.cs ├── CoreWiki.Notifications.Abstractions.csproj └── Notifications │ ├── IEmailMessageFormatter.cs │ ├── IEmailNotifier.cs │ ├── INotificationService.cs │ ├── ITemplateParser.cs │ └── ITemplateProvider.cs ├── CoreWiki.Notifications ├── CoreWiki.Notifications.csproj ├── EmailMessageFormatter.cs ├── EmailNotifier.cs ├── Models │ ├── ConfirmationEmailModel.cs │ ├── EmailMessageBaseModel.cs │ ├── ForgotPasswordEmailModel.cs │ └── NewCommentEmailModel.cs ├── NotificationService.cs ├── StartupExtensions.cs ├── TemplateParser.cs ├── TemplateProvider.cs └── Templates │ ├── ConfirmationEmail.cshtml │ ├── ForgotPasswordEmail.cshtml │ ├── NewCommentEmail.cshtml │ ├── Shared │ └── _Layout.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── CoreWiki.Test ├── Application │ ├── Managing │ │ ├── ArticleManagingProfileTests.cs │ │ └── Commands │ │ │ ├── CreateNewArticleCommandHandlerTests.cs │ │ │ ├── CreateNewCommentCommandHandlerTests.cs │ │ │ ├── DeleteArticleCommandHandlerTests.cs │ │ │ └── EditArticleCommandHandlerTests.cs │ ├── Reading │ │ └── ArticleReadingProfileTests.cs │ └── Search │ │ └── ArticleSearchProfileTests.cs ├── Core │ └── Domain │ │ └── ArticleTests.cs ├── CoreWiki.Test.csproj ├── Helpers │ ├── StringHelperTests.cs │ └── UrlHelperTests.cs ├── Pages │ └── SearchTests.cs ├── SearchArticles │ └── Search.cs └── Website │ ├── CoreWikiWebsiteProfileTests.cs │ └── Pages │ ├── Create │ ├── BaseFixture.cs │ ├── OnGet.cs │ └── OnPost.cs │ ├── Details │ └── OnGet.cs │ └── PageModelExtensions.cs ├── CoreWiki.code-workspace ├── CoreWiki.sln ├── CoreWiki ├── App_Data │ └── README.md ├── Areas │ └── Identity │ │ ├── AnyRoleRequirement.cs │ │ ├── AuthorizationPolicy.cs │ │ ├── IdentityHostingStartup.cs │ │ ├── Pages │ │ ├── Account │ │ │ ├── AccessDenied.cshtml │ │ │ ├── AccessDenied.cshtml.cs │ │ │ ├── ConfirmEmail.cshtml │ │ │ ├── ConfirmEmail.cshtml.cs │ │ │ ├── ExternalLogin.cshtml │ │ │ ├── ExternalLogin.cshtml.cs │ │ │ ├── ForgotPassword.cshtml │ │ │ ├── ForgotPassword.cshtml.cs │ │ │ ├── ForgotPasswordConfirmation.cshtml │ │ │ ├── ForgotPasswordConfirmation.cshtml.cs │ │ │ ├── Lockout.cshtml │ │ │ ├── Lockout.cshtml.cs │ │ │ ├── Login.cshtml │ │ │ ├── Login.cshtml.cs │ │ │ ├── LoginWith2fa.cshtml │ │ │ ├── LoginWith2fa.cshtml.cs │ │ │ ├── LoginWithRecoveryCode.cshtml │ │ │ ├── LoginWithRecoveryCode.cshtml.cs │ │ │ ├── Logout.cshtml │ │ │ ├── Logout.cshtml.cs │ │ │ ├── Manage │ │ │ │ ├── ChangePassword.cshtml │ │ │ │ ├── ChangePassword.cshtml.cs │ │ │ │ ├── DeletePersonalData.cshtml │ │ │ │ ├── DeletePersonalData.cshtml.cs │ │ │ │ ├── Disable2fa.cshtml │ │ │ │ ├── Disable2fa.cshtml.cs │ │ │ │ ├── DownloadPersonalData.cshtml │ │ │ │ ├── DownloadPersonalData.cshtml.cs │ │ │ │ ├── EnableAuthenticator.cshtml │ │ │ │ ├── EnableAuthenticator.cshtml.cs │ │ │ │ ├── ExternalLogins.cshtml │ │ │ │ ├── ExternalLogins.cshtml.cs │ │ │ │ ├── GenerateRecoveryCodes.cshtml │ │ │ │ ├── GenerateRecoveryCodes.cshtml.cs │ │ │ │ ├── Index.cshtml │ │ │ │ ├── Index.cshtml.cs │ │ │ │ ├── ManageNavPages.cs │ │ │ │ ├── MyRoles.cshtml │ │ │ │ ├── MyRoles.cshtml.cs │ │ │ │ ├── PersonalData.cshtml │ │ │ │ ├── PersonalData.cshtml.cs │ │ │ │ ├── ResetAuthenticator.cshtml │ │ │ │ ├── ResetAuthenticator.cshtml.cs │ │ │ │ ├── SetPassword.cshtml │ │ │ │ ├── SetPassword.cshtml.cs │ │ │ │ ├── TwoFactorAuthentication.cshtml │ │ │ │ ├── TwoFactorAuthentication.cshtml.cs │ │ │ │ ├── _Layout.cshtml │ │ │ │ ├── _ManageNav.cshtml │ │ │ │ ├── _StatusMessage.cshtml │ │ │ │ └── _ViewImports.cshtml │ │ │ ├── Register.cshtml │ │ │ ├── Register.cshtml.cs │ │ │ ├── ResetPassword.cshtml │ │ │ ├── ResetPassword.cshtml.cs │ │ │ ├── ResetPasswordConfirmation.cshtml │ │ │ ├── ResetPasswordConfirmation.cshtml.cs │ │ │ └── _ViewImports.cshtml │ │ ├── Error.cshtml │ │ ├── Error.cshtml.cs │ │ ├── UserAdmin │ │ │ ├── Index.cshtml │ │ │ ├── Index.cshtml.cs │ │ │ ├── Roles.cshtml │ │ │ └── Roles.cshtml.cs │ │ ├── _ValidationScriptsPartial.cshtml │ │ ├── _ViewImports.cshtml │ │ └── _ViewStart.cshtml │ │ ├── PolicyConstants.cs │ │ └── Services │ │ └── HaveIBeenPwnedClient.cs ├── Configuration │ ├── Settings │ │ ├── AppSettings.cs │ │ ├── Comments.cs │ │ ├── CommentsEngine.cs │ │ ├── Connectionstrings.cs │ │ ├── CspSettings.cs │ │ └── Disqus.cs │ └── Startup │ │ ├── ConfigureApplicationLayer.cs │ │ ├── ConfigureAutomapperServices.cs │ │ ├── ConfigureDatabase.cs │ │ ├── ConfigureExceptions.cs │ │ ├── ConfigureHttpClients.cs │ │ ├── ConfigureLocalisation.cs │ │ ├── ConfigureMediator.cs │ │ ├── ConfigureRSSFeed.cs │ │ ├── ConfigureRouting.cs │ │ ├── ConfigureScopedServices.cs │ │ ├── ConfigureSecurityAndAuthentication.cs │ │ ├── ConfigureTelemetry.cs │ │ └── CoreWikiWebsiteProfile.cs ├── CoreWiki.csproj ├── Globalization │ ├── Models │ │ ├── Article.resx │ │ └── Comment.resx │ ├── PagerTagHelper.resx │ └── Pages │ │ ├── ArticleNotFound.resx │ │ ├── Components │ │ ├── CreateComments │ │ │ └── CreateComments.resx │ │ └── ListComments │ │ │ └── ListComments.resx │ │ ├── Create.resx │ │ ├── CreateArticleFromLink.resx │ │ ├── Delete.resx │ │ ├── Details.resx │ │ ├── Edit.resx │ │ ├── Privacy.resx │ │ ├── Search.resx │ │ ├── Shared │ │ ├── _CookieConsentPartial.resx │ │ ├── _Layout.resx │ │ ├── _LoginPartial.resx │ │ └── _ThemePartial.resx │ │ └── _ArticleRow.resx ├── Helpers │ ├── AriaHelper.cs │ ├── ArticleNotFoundInitializer.cs │ ├── ArticleNotFoundResult.cs │ └── NewCommentNotificationHandler.cs ├── Pages │ ├── ArticleNotFound.cshtml │ ├── Components │ │ ├── CreateComments │ │ │ ├── CreateComments.cshtml │ │ │ └── CreateComments.cshtml.cs │ │ └── ListComments │ │ │ ├── ListComments.cshtml │ │ │ └── ListComments.cshtml.cs │ ├── Create.cshtml │ ├── Create.cshtml.cs │ ├── Delete.cshtml │ ├── Delete.cshtml.cs │ ├── Details.cshtml │ ├── Details.cshtml.cs │ ├── Edit.cshtml │ ├── Edit.cshtml.cs │ ├── Error.cshtml │ ├── Error.cshtml.cs │ ├── History.cshtml │ ├── History.cshtml.cs │ ├── HttpErrors │ │ └── 404.cshtml │ ├── Privacy.cshtml │ ├── Privacy.cshtml.cs │ ├── Search.cshtml │ ├── Search.cshtml.cs │ ├── Shared │ │ ├── _CommentsPartial.cshtml │ │ ├── _CookieConsentPartial.cshtml │ │ ├── _DiffLine.cshtml │ │ ├── _DiffPane.cshtml │ │ ├── _DisqusComments.cshtml │ │ ├── _Layout.cshtml │ │ ├── _LoginPartial.cshtml │ │ ├── _ThemePartial.cshtml │ │ └── _ValidationScriptsPartial.cshtml │ ├── _ArticleRow.cshtml │ ├── _EditorScript.cshtml │ ├── _EditorStyle.cshtml │ ├── _ViewImports.cshtml │ └── _ViewStart.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── RSSProvider.cs ├── Startup.cs ├── TagHelpers │ ├── AuthorizationTagHelper.cs │ ├── GravatarTagHelper.cs │ ├── MarkdownTagHelper.cs │ └── PagerTagHelper.cs ├── ViewModels │ ├── ArticleCreate.cs │ ├── ArticleCreateFromLink.cs │ ├── ArticleDelete.cs │ ├── ArticleDetails.cs │ ├── ArticleEdit.cs │ ├── ArticleHistory.cs │ ├── ArticleHistoryDetail.cs │ ├── ArticleSummary.cs │ └── Comment.cs ├── appsettings.Development.json ├── appsettings.json ├── bundleconfig.json ├── bundleconfig.json.bindings ├── compilerconfig.json ├── compilerconfig.json.defaults ├── libman.json ├── package-lock.json ├── package.json ├── updateDb.cmd ├── web.config └── wwwroot │ ├── css │ ├── _custom-styles.scss │ ├── _custom-variables.scss │ ├── diff.css │ ├── diff.min.css │ ├── diff.scss │ ├── siteTheme.css │ ├── siteTheme.min.css │ ├── siteTheme.scss │ ├── themeDark.css │ ├── themeDark.min.css │ ├── themeDark.scss │ ├── themeGhost.css │ ├── themeGhost.min.css │ ├── themeGhost.scss │ ├── themeOuch.css │ ├── themeOuch.min.css │ ├── themeOuch.scss │ ├── themeRainbowFritz.css │ ├── themeRainbowFritz.min.css │ └── themeRainbowFritz.scss │ ├── images │ ├── 51.jpg │ ├── banner1.svg │ ├── banner2.svg │ ├── banner3.svg │ ├── banner4.svg │ └── icons │ │ ├── icon-114x114.png │ │ ├── icon-120x120.png │ │ ├── icon-128x128.png │ │ ├── icon-144x144.png │ │ ├── icon-152x152.png │ │ ├── icon-16x16.png │ │ ├── icon-180x180.png │ │ ├── icon-192x192.png │ │ ├── icon-32x32.png │ │ ├── icon-32x32white.png │ │ ├── icon-384x384.png │ │ ├── icon-512x512.png │ │ ├── icon-57x57.png │ │ ├── icon-60x60.png │ │ ├── icon-72x72.png │ │ ├── icon-76x76.png │ │ └── icon-96x96.png │ ├── js │ └── site.js │ └── manifest.json ├── Dockerfile ├── LICENSE.txt ├── README.md ├── appveyor.yml ├── azure-pipelines.yml ├── build.cake ├── build.ps1 ├── build.sh ├── contributing.md ├── databaseUpdate.cmd ├── deployToAzure.cake ├── deployToAzure.cmd ├── docs ├── architecture.pptx ├── c4models │ ├── 1-context.md │ ├── 1-context.puml │ ├── 2-containers.md │ ├── 2-containers.puml │ ├── 3-backend-container.md │ ├── 3-backend-container.puml │ ├── 3-container-mobile.md │ ├── 3-website-container.md │ ├── 3-website-container.puml │ ├── 4-CoreWiki-Classes-APIController.puml │ ├── 4-classes-apicontroller.md │ ├── 4-classes-applicationservice.md │ ├── 4-classes-applicationservice.puml │ ├── 4-classes-pages.md │ ├── 4-classes-pages.puml │ ├── images │ │ ├── Backend-Container-Diagram-For-CoreWiki.png │ │ ├── Classes-for-APIControllers-CoreWiki.png │ │ ├── Classes-for-ApplicationService-Diagram-For-CoreWiki.png │ │ ├── Classes-for-Razor-Pages-For-CoreWiki.png │ │ ├── Container-Diagram-For-CoreWiki.png │ │ ├── Frontend-Container-Diagram-For-CoreWiki.png │ │ └── System-Landscape-diagram-for-CoreWiki.png │ └── readme.md └── objects.pptx ├── global.json ├── nuget.config ├── package-lock.json └── pipelines └── azure-pipelines-cake.yml /.deployment: -------------------------------------------------------------------------------- 1 | [config] 2 | command = deployToAzure.cmd 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = crlf 5 | indent_style = tab 6 | trim_trailing_whitespace = true 7 | insert_final_newline = true 8 | 9 | [*.yml] 10 | indent_style = space 11 | 12 | [*.cs] 13 | csharp_style_var_for_built_in_types = true:error 14 | csharp_style_var_when_type_is_apparent = true:error 15 | csharp_style_var_elsewhere = true:error 16 | csharp_prefer_braces = true:warning 17 | dotnet_naming_rule.private_members_with_underscore.symbols = private_fields 18 | dotnet_naming_rule.private_members_with_underscore.style = prefix_underscore 19 | dotnet_naming_rule.private_members_with_underscore.severity = suggestion 20 | 21 | dotnet_naming_symbols.private_fields.applicable_kinds = field 22 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private 23 | 24 | dotnet_naming_style.prefix_underscore.capitalization = camel_case 25 | dotnet_naming_style.prefix_underscore.required_prefix = _ 26 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=crlf 2 | *.sh text eol=lf -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: csharp 2 | os: 3 | - osx 4 | - linux 5 | 6 | sudo: required 7 | dist: trusty 8 | 9 | osx_image: xcode9.2 10 | 11 | mono: none 12 | dotnet: 2.1.401 13 | 14 | script: 15 | - ./build.sh --Target=Travis -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/CoreWiki/bin/Debug/netcoreapp2.1/CoreWiki.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}/CoreWiki", 16 | "stopAtEntry": false, 17 | "internalConsoleOptions": "openOnSessionStart", 18 | "launchBrowser": { 19 | "enabled": true, 20 | "args": "${auto-detect-url}", 21 | "windows": { 22 | "command": "cmd.exe", 23 | "args": "/C start ${auto-detect-url}" 24 | }, 25 | "osx": { 26 | "command": "open" 27 | }, 28 | "linux": { 29 | "command": "xdg-open" 30 | } 31 | }, 32 | "env": { 33 | "ASPNETCORE_ENVIRONMENT": "Development" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ,] 46 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "plantuml.exportFormat": "png", 3 | "plantuml.exportOutDir": "docs/c4models/images", 4 | "plantuml.exportSubFolder": false, 5 | "plantuml.diagramsRoot": "docs/c4models/", 6 | } 7 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/CoreWiki/CoreWiki.csproj" 11 | ], 12 | "problemMatcher": "$msCompile" 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/ArticleManagingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Application.Articles.Managing.Commands; 3 | using CoreWiki.Application.Articles.Managing.Dto; 4 | using CoreWiki.Core.Domain; 5 | 6 | namespace CoreWiki.Application.Articles.Managing 7 | { 8 | public class ArticleManagingProfile: Profile 9 | { 10 | public ArticleManagingProfile() 11 | { 12 | CreateMap() 13 | .ForMember(d => d.Id, m=> m.Ignore()) 14 | .ForMember(d => d.Version, m => m.UseValue(1)) 15 | .ForMember(d => d.Published, m => m.Ignore()) 16 | .ForMember(d => d.Comments, m => m.Ignore()) 17 | .ForMember(d => d.History, m => m.Ignore()) 18 | .ForMember(d => d.ViewCount, m => m.UseValue(0)) 19 | .ForMember(d => d.Slug, m => m.Ignore()) 20 | ; 21 | 22 | CreateMap() 23 | .ForMember(d => d.Id, m => m.Ignore()) 24 | .ForMember(d => d.Version, m => m.UseValue(1)) 25 | .ForMember(d => d.Published, m => m.Ignore()) 26 | .ForMember(d => d.Comments, m => m.Ignore()) 27 | .ForMember(d => d.History, m => m.Ignore()) 28 | .ForMember(d => d.ViewCount, m => m.UseValue(0)) 29 | .ForMember(d => d.Topic, m => m.MapFrom(s => Article.SlugToTopic(s.Slug))) 30 | .ForMember(d => d.Content, m => m.UseValue("")); 31 | 32 | CreateMap(); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Commands/CreateNewArticleCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreWiki.Application.Common; 3 | using MediatR; 4 | 5 | namespace CoreWiki.Application.Articles.Managing.Commands 6 | { 7 | public class CreateNewArticleCommand : IRequest 8 | { 9 | 10 | public string Topic { get; set; } 11 | 12 | public string Content { get; set; } 13 | 14 | public Guid AuthorId { get; set; } 15 | 16 | public string AuthorName { get; set; } 17 | 18 | } 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Commands/CreateSkeletonArticleCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreWiki.Application.Common; 3 | using MediatR; 4 | 5 | namespace CoreWiki.Application.Articles.Managing.Commands 6 | { 7 | public class CreateSkeletonArticleCommand : IRequest 8 | { 9 | 10 | public Guid AuthorId { get; set; } 11 | 12 | public string AuthorName { get; set; } 13 | 14 | public string Slug { get; set; } 15 | 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Commands/DeleteArticleCommand.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Common; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Commands 5 | { 6 | public class DeleteArticleCommand : IRequest 7 | { 8 | public string Slug { get; } 9 | 10 | public DeleteArticleCommand(string slug) 11 | { 12 | Slug = slug; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Commands/DeleteArticleCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using CoreWiki.Application.Articles.Managing.Exceptions; 5 | using CoreWiki.Application.Common; 6 | using MediatR; 7 | 8 | namespace CoreWiki.Application.Articles.Managing.Commands 9 | { 10 | public class DeleteArticleCommandHandler : IRequestHandler 11 | { 12 | private readonly IArticleManagementService _articleManagementService; 13 | 14 | public DeleteArticleCommandHandler(IArticleManagementService articleManagementService) 15 | { 16 | _articleManagementService = articleManagementService; 17 | } 18 | 19 | public async Task Handle(DeleteArticleCommand request, CancellationToken cancellationToken) 20 | { 21 | try 22 | { 23 | await _articleManagementService.Delete(request.Slug); 24 | return CommandResult.Success(); 25 | } 26 | catch (Exception ex) 27 | { 28 | return CommandResult.Error(new DeleteArticleException("There was an error deleting the article", ex)); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Commands/EditArticleCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreWiki.Application.Common; 3 | using MediatR; 4 | 5 | namespace CoreWiki.Application.Articles.Managing.Commands 6 | { 7 | public class EditArticleCommand : IRequest 8 | { 9 | 10 | public int Id { get; set; } 11 | public string Topic { get; set; } 12 | public string Content { get; set; } 13 | public Guid AuthorId { get; set; } 14 | public string AuthorName { get; set; } 15 | 16 | //public EditArticleCommand() 17 | //{ 18 | 19 | //} 20 | 21 | //public EditArticleCommand(int id, string topic, string content, Guid authorId, string authorName) 22 | //{ 23 | // this.Id = id; 24 | // this.Topic = topic; 25 | // this.Content = content; 26 | // this.AuthorId = authorId; 27 | // this.AuthorName = authorName; 28 | //} 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Commands/EditArticleCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using CoreWiki.Application.Common; 5 | using MediatR; 6 | 7 | namespace CoreWiki.Application.Articles.Managing.Commands 8 | { 9 | public class EditArticleCommandHandler : IRequestHandler 10 | { 11 | private readonly IArticleManagementService _articleManagementService; 12 | 13 | public EditArticleCommandHandler(IArticleManagementService articleManagementService) 14 | { 15 | _articleManagementService = articleManagementService; 16 | } 17 | 18 | public async Task Handle(EditArticleCommand request, CancellationToken cancellationToken) 19 | { 20 | try 21 | { 22 | var theArticle = await _articleManagementService.Update(request.Id, request.Topic, request.Content, request.AuthorId, request.AuthorName); 23 | 24 | return CommandResult.Success(theArticle.Slug); 25 | } 26 | catch (Exception ex) 27 | { 28 | return CommandResult.Error(ex); 29 | } 30 | } 31 | } 32 | 33 | } 34 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Dto/ArticleManageDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaTime; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Dto 5 | { 6 | public class ArticleManageDto 7 | { 8 | public bool IsHomePage { get; set; } 9 | public string Content { get; set; } 10 | public int Id { get; set; } 11 | public string Slug { get; set; } 12 | public string Topic { get; set; } 13 | public int Version { get; set; } 14 | public int ViewCount { get; set; } 15 | public CommentDto[] Comments { get; set; } 16 | 17 | public Instant Published { get; set; } 18 | public Guid AuthorId { get; set; } 19 | public SlugHistoryDto[] History { get; set; } 20 | public string AuthorName { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Dto/CommentDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaTime; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Dto 5 | { 6 | public class CommentDto 7 | { 8 | public int ArticleId { get; set; } 9 | public Instant Submitted { get; set; } 10 | public Guid AuthorId { get; set; } 11 | public string Content { get; set; } 12 | public string DisplayName { get; set; } 13 | public string Email { get; set; } 14 | public int Id { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Dto/SlugHistoryDto.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Reading.Dto; 2 | using NodaTime; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Dto 5 | { 6 | public class SlugHistoryDto 7 | { 8 | public int Id { get; set; } 9 | 10 | public virtual ArticleReadingDto Article { get; set; } 11 | 12 | public string OldSlug { get; set; } 13 | 14 | public Instant Added { get; set; } 15 | public int Version { get; set; } 16 | public string Content { get; set; } 17 | public string AuthorName { get; set; } 18 | public Instant Published { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Events/ArticleCreatedNotification.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Core.Domain; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Events 5 | { 6 | public class ArticleCreatedNotification : INotification 7 | { 8 | public Article Article { get; } 9 | 10 | public ArticleCreatedNotification(Article article) 11 | { 12 | Article = article; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Events/ArticleDeletedNotification.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Core.Domain; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Events 5 | { 6 | public class ArticleDeletedNotification : INotification 7 | { 8 | public Article Article { get; } 9 | 10 | public ArticleDeletedNotification(Article article) 11 | { 12 | Article = article; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Events/ArticleEditedNotification.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Core.Domain; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Events 5 | { 6 | public class ArticleEditedNotification: INotification 7 | { 8 | public Article Editedarticle { get; } 9 | 10 | public ArticleEditedNotification(Article editedarticle) 11 | { 12 | Editedarticle = editedarticle; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Events/DeleteHomePageAttemptNotification.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace CoreWiki.Application.Articles.Managing.Events 4 | { 5 | public class DeleteHomePageAttemptNotification : INotification 6 | { 7 | public DeleteHomePageAttemptNotification() 8 | { 9 | 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Exceptions/ArticleNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Exceptions 5 | { 6 | [Serializable] 7 | public class ArticleNotFoundException : Exception 8 | { 9 | public ArticleNotFoundException() 10 | { 11 | } 12 | 13 | public ArticleNotFoundException(string message) : base(message) 14 | { 15 | } 16 | 17 | public ArticleNotFoundException(string message, Exception innerException) : base(message, innerException) 18 | { 19 | } 20 | 21 | protected ArticleNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) 22 | { 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Exceptions/CreateArticleException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Exceptions 5 | { 6 | [Serializable] 7 | internal class CreateArticleException : Exception 8 | { 9 | public CreateArticleException() 10 | { 11 | } 12 | 13 | public CreateArticleException(string message) : base(message) 14 | { 15 | //Log error 16 | } 17 | 18 | public CreateArticleException(string message, Exception innerException) : base(message, innerException) 19 | { 20 | } 21 | 22 | protected CreateArticleException(SerializationInfo info, StreamingContext context) : base(info, context) 23 | { 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Exceptions/DeleteArticleException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CoreWiki.Application.Articles.Managing.Exceptions 4 | { 5 | [Serializable] 6 | internal class DeleteArticleException : Exception 7 | { 8 | public DeleteArticleException(string message, Exception innerException) : base(message, innerException) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Exceptions/InvalidTopicException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Exceptions 5 | { 6 | [Serializable] 7 | public class InvalidTopicException : Exception 8 | { 9 | 10 | public InvalidTopicException(string message) : base(message) { } 11 | 12 | public InvalidTopicException(string message, Exception innerException) : base(message, innerException) { } 13 | 14 | protected InvalidTopicException(SerializationInfo info, StreamingContext context) : base(info, context) { } 15 | 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Exceptions/NoContentChangedException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Exceptions 5 | { 6 | [Serializable] 7 | public class NoContentChangedException : Exception 8 | { 9 | public NoContentChangedException() 10 | { 11 | } 12 | 13 | public NoContentChangedException(string message) : base(message) 14 | { 15 | } 16 | 17 | public NoContentChangedException(string message, Exception innerException) : base(message, innerException) 18 | { 19 | } 20 | 21 | protected NoContentChangedException(SerializationInfo info, StreamingContext context) : base(info, context) 22 | { 23 | } 24 | } 25 | 26 | 27 | } 28 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/IArticleManagementService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using CoreWiki.Application.Articles.Managing.Dto; 5 | using CoreWiki.Core.Domain; 6 | 7 | namespace CoreWiki.Application.Articles.Managing 8 | { 9 | public interface IArticleManagementService 10 | { 11 | Task
CreateArticleAndHistory(Article article); 12 | Task
Update(int id, string topic, string content, Guid authorId, string authorName); 13 | Task
Delete(string slug); 14 | Task IsTopicAvailable(string articleSlug, int articleId); 15 | Task GetArticleBySlug(string articleSlug); 16 | 17 | Task> GetArticlesToCreate(string slug); 18 | Task<(string,IList)> GetArticlesToCreate(int articleId); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Queries/GetArticleHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CoreWiki.Application.Articles.Managing.Dto; 4 | using CoreWiki.Application.Common; 5 | using CoreWiki.Core.Domain; 6 | using MediatR; 7 | 8 | namespace CoreWiki.Application.Articles.Managing.Queries 9 | { 10 | class GetArticleHandler : IRequestHandler, 11 | IRequestHandler 12 | { 13 | private readonly IArticleManagementService _service; 14 | 15 | public GetArticleHandler(IArticleManagementService service) 16 | { 17 | _service = service; 18 | } 19 | 20 | public Task Handle(GetIsTopicAvailableQuery request, CancellationToken cancellationToken) 21 | { 22 | var article = new Article { Topic = request.Topic }; 23 | return _service.IsTopicAvailable(article.Slug, request.ArticleId); 24 | } 25 | 26 | public Task Handle(GetArticleQuery request, CancellationToken cancellationToken) 27 | { 28 | return _service.GetArticleBySlug(request.Slug); 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Queries/GetArticleQuery.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Managing.Dto; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Managing.Queries 5 | { 6 | public class GetArticleQuery: IRequest 7 | { 8 | public string Slug { get; } 9 | public GetArticleQuery(string slug) => Slug = slug; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Queries/GetArticlesToCreateFromArticleHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using MediatR; 5 | 6 | namespace CoreWiki.Application.Articles.Managing.Queries 7 | { 8 | 9 | public class GetArticlesToCreateFromArticleHandler : IRequestHandler 10 | { 11 | private readonly IArticleManagementService _articleReadingService; 12 | 13 | public GetArticlesToCreateFromArticleHandler(IArticleManagementService articleReadingService) 14 | { 15 | _articleReadingService = articleReadingService; 16 | } 17 | 18 | public async Task<(string,string[])> Handle(GetArticlesToCreateFromArticleQuery request, CancellationToken cancellationToken) 19 | { 20 | var result = await _articleReadingService.GetArticlesToCreate(request.ArticleId); 21 | return (result.Item1, result.Item2.ToArray()); 22 | } 23 | 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Queries/GetArticlesToCreateFromArticleQuery.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace CoreWiki.Application.Articles.Managing.Queries 4 | { 5 | public class GetArticlesToCreateFromArticleQuery : IRequest<(string,string[])> 6 | { 7 | public GetArticlesToCreateFromArticleQuery(int articleId) 8 | { 9 | 10 | this.ArticleId = articleId; 11 | 12 | } 13 | 14 | public int ArticleId { get; } 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Managing/Queries/GetIsTopicAvailableQuery.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace CoreWiki.Application.Articles.Managing.Queries 4 | { 5 | public class GetIsTopicAvailableQuery : IRequest 6 | { 7 | public string Topic { get; set; } 8 | public int ArticleId { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/ArticleReadingProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Application.Articles.Reading.Commands; 3 | using CoreWiki.Application.Articles.Reading.Dto; 4 | using CoreWiki.Core.Domain; 5 | 6 | namespace CoreWiki.Application.Articles.Reading 7 | { 8 | public class ArticleReadingProfile: Profile 9 | { 10 | 11 | public ArticleReadingProfile() 12 | { 13 | CreateMap(); 14 | CreateMap() 15 | .ForMember(d => d.Id, m => m.Ignore()); 16 | CreateMap(); 17 | CreateMap(); 18 | CreateMap() 19 | .ForMember( d => d.Version, m => m.MapFrom(s => s.Article.Version)) 20 | .ForMember(d => d.Content, m => m.MapFrom(s => s.Article.Content)) 21 | .ForMember(d => d.AuthorName, m => m.MapFrom(s => s.Article.AuthorName)) 22 | .ForMember(d => d.Published, m => m.MapFrom(s => s.Article.Published)); 23 | CreateMap(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Commands/CreateNewCommentCommand.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreWiki.Application.Common; 3 | using MediatR; 4 | using NodaTime; 5 | 6 | namespace CoreWiki.Application.Articles.Reading.Commands 7 | { 8 | public class CreateNewCommentCommand : IRequest 9 | { 10 | public int ArticleID { get; set; } 11 | public Instant Submitted { get; set; } 12 | public Guid AuthorId { get; set; } 13 | public string Content { get; set; } 14 | public string DisplayName { get; set; } 15 | public string Email { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Commands/CreateNewCommentCommandHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading; 3 | using System.Threading.Tasks; 4 | using AutoMapper; 5 | using CoreWiki.Application.Articles.Reading.Dto; 6 | using CoreWiki.Application.Articles.Reading.Exceptions; 7 | using CoreWiki.Application.Common; 8 | using MediatR; 9 | 10 | namespace CoreWiki.Application.Articles.Reading.Commands 11 | { 12 | public class CreateNewCommentCommandHandler : IRequestHandler 13 | { 14 | private readonly IArticleReadingService _articleReadingService; 15 | private readonly IMapper _mapper; 16 | 17 | public CreateNewCommentCommandHandler(IArticleReadingService articleReadingService, IMapper mapper) 18 | { 19 | _articleReadingService = articleReadingService; 20 | _mapper = mapper; 21 | } 22 | 23 | public async Task Handle(CreateNewCommentCommand request, CancellationToken cancellationToken) 24 | { 25 | try 26 | { 27 | var comment = _mapper.Map(request); 28 | await _articleReadingService.CreateComment(comment); 29 | 30 | return CommandResult.Success(); 31 | } 32 | catch (Exception ex) 33 | { 34 | return CommandResult.Error(new CreateCommentException("There was an error creating the comment", ex)); 35 | } 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Commands/IncrementViewCountCommand.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace CoreWiki.Application.Articles.Reading.Commands 4 | { 5 | public class IncrementViewCountCommand : IRequest 6 | { 7 | 8 | public IncrementViewCountCommand(string slug) 9 | { 10 | this.Slug = slug; 11 | } 12 | 13 | public string Slug { get; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Commands/IncrementViewCountHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using MediatR; 4 | 5 | namespace CoreWiki.Application.Articles.Reading.Commands 6 | { 7 | public class IncrementViewCountHandler : IRequestHandler 8 | { 9 | private readonly IArticleReadingService _service; 10 | 11 | public IncrementViewCountHandler(IArticleReadingService service) 12 | { 13 | _service = service; 14 | } 15 | 16 | public async Task Handle(IncrementViewCountCommand request, CancellationToken cancellationToken) 17 | { 18 | 19 | await _service.IncrementViewCount(request.Slug); 20 | return Unit.Value; 21 | 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Dto/ArticleReadingDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaTime; 3 | 4 | namespace CoreWiki.Application.Articles.Reading.Dto 5 | { 6 | public class ArticleReadingDto 7 | { 8 | public bool IsHomePage { get; set; } 9 | public string Content { get; set; } 10 | public int Id { get; set; } 11 | public string Slug { get; set; } 12 | public string Topic { get; set; } 13 | public int Version { get; set; } 14 | public int ViewCount { get; set; } 15 | public CommentDto[] Comments { get; set; } 16 | 17 | public Instant Published { get; set; } 18 | public Guid AuthorId { get; set; } 19 | public SlugHistoryDto[] History { get; set; } 20 | public string AuthorName { get; set; } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Dto/CommentDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaTime; 3 | 4 | namespace CoreWiki.Application.Articles.Reading.Dto 5 | { 6 | public class CommentDto 7 | { 8 | public int ArticleId { get; set; } 9 | public Instant Submitted { get; set; } 10 | public Guid AuthorId { get; set; } 11 | public string Content { get; set; } 12 | public string DisplayName { get; set; } 13 | public string Email { get; set; } 14 | public int Id { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Dto/CreateCommentDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaTime; 3 | 4 | namespace CoreWiki.Application.Articles.Reading.Dto 5 | { 6 | 7 | public class CreateCommentDto 8 | { 9 | public int ArticleID { get; set; } 10 | public Instant Submitted { get; set; } 11 | public Guid AuthorId { get; set; } 12 | public string Content { get; set; } 13 | public string DisplayName { get; set; } 14 | public string Email { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Dto/SlugHistoryDto.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | 3 | namespace CoreWiki.Application.Articles.Reading.Dto 4 | { 5 | public class SlugHistoryDto 6 | { 7 | public int Id { get; set; } 8 | 9 | public virtual ArticleReadingDto Article { get; set; } 10 | 11 | public string OldSlug { get; set; } 12 | 13 | public Instant Added { get; set; } 14 | public int Version { get; set; } 15 | public string Content { get; set; } 16 | public string AuthorName { get; set; } 17 | public Instant Published { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Events/CommentPostedNotification.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Core.Domain; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Reading.Events 5 | { 6 | public class CommentPostedNotification : INotification 7 | { 8 | public Article Article { get; } 9 | public Comment Comment { get; } 10 | 11 | public CommentPostedNotification(Article article, Comment comment) 12 | { 13 | Article = article; 14 | Comment = comment; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Exceptions/CreateCommentException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CoreWiki.Application.Articles.Reading.Exceptions 4 | { 5 | [Serializable] 6 | internal class CreateCommentException : Exception 7 | { 8 | public CreateCommentException(string message, Exception innerException) : base(message, innerException) 9 | { 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/IArticleReadingService.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using CoreWiki.Application.Articles.Reading.Dto; 4 | 5 | namespace CoreWiki.Application.Articles.Reading 6 | { 7 | public interface IArticleReadingService 8 | { 9 | Task GetArticleBySlug(string articleSlug); 10 | Task IsTopicAvailable(string articleSlug, int articleId); 11 | Task GetSlugHistoryWithArticle(string slug); 12 | 13 | Task CreateComment(CreateCommentDto commentDto); 14 | 15 | Task GetArticleById(int articleId); 16 | Task GetArticleWithHistoriesBySlug(string articleSlug); 17 | 18 | Task> GetLatestArticles(int numOfArticlesToGet); 19 | Task IncrementViewCount(string slug); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Queries/GetArticleByIdQuery.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Reading.Dto; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Reading.Queries 5 | { 6 | public class GetArticleByIdQuery : IRequest 7 | { 8 | public int Id { get; } 9 | public GetArticleByIdQuery(int id) => Id = id; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Queries/GetArticleQuery.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Reading.Dto; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Reading.Queries 5 | { 6 | public class GetArticleQuery: IRequest 7 | { 8 | public string Slug { get; } 9 | public GetArticleQuery(string slug) => Slug = slug; 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Queries/GetArticleWithHistoriesBySlugQuery.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Reading.Dto; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Reading.Queries 5 | { 6 | public class GetArticleWithHistoriesBySlugQuery: IRequest 7 | { 8 | public string Slug { get; } 9 | 10 | public GetArticleWithHistoriesBySlugQuery(string slug) 11 | { 12 | Slug = slug; 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Queries/GetIsTopicAvailableQuery.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | 3 | namespace CoreWiki.Application.Articles.Reading.Queries 4 | { 5 | public class GetIsTopicAvailableQuery : IRequest 6 | { 7 | public string Slug { get; set; } 8 | public int ArticleId { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Queries/GetLatestArticlesQuery.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using CoreWiki.Application.Articles.Reading.Dto; 3 | using MediatR; 4 | 5 | namespace CoreWiki.Application.Articles.Reading.Queries 6 | { 7 | public class GetLatestArticlesQuery: IRequest> 8 | { 9 | public int NumOfArticlesToGet { get; } 10 | 11 | public GetLatestArticlesQuery(int numOfArticlesToGet) 12 | { 13 | NumOfArticlesToGet = numOfArticlesToGet; 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Reading/Queries/GetSlugHistoryQuery.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Reading.Dto; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Reading.Queries 5 | { 6 | public class GetSlugHistoryQuery : IRequest 7 | { 8 | 9 | public GetSlugHistoryQuery(string historicalSlug) 10 | { 11 | this.HistoricalSlug = historicalSlug; 12 | } 13 | 14 | public string HistoricalSlug { get; } 15 | } 16 | 17 | 18 | } 19 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Search/Dto/ArticleSearchDto.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using NodaTime; 3 | 4 | namespace CoreWiki.Application.Articles.Search.Dto 5 | { 6 | public class ArticleSearchDto 7 | { 8 | public bool IsHomePage { get; set; } 9 | public string Content { get; set; } 10 | public int Id { get; set; } 11 | public string Slug { get; set; } 12 | public string Topic { get; set; } 13 | public int Version { get; set; } 14 | public int ViewCount { get; set; } 15 | 16 | public Instant Published { get; set; } 17 | public Guid AuthorId { get; set; } 18 | public string AuthorName { get; set; } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Search/Dto/SearchResultDto.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | 3 | namespace CoreWiki.Application.Articles.Search.Dto 4 | { 5 | public class SearchResultDto 6 | { 7 | public string Query { get; set; } 8 | 9 | public List Results { get; set; } 10 | 11 | public int TotalResults { get; set; } 12 | 13 | public int ResultsPerPage { get; set; } 14 | 15 | public int CurrentPage { get; set; } 16 | 17 | public int TotalPages { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Search/IArticlesSearchEngine.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using CoreWiki.Application.Articles.Search.Dto; 3 | using CoreWiki.Core.Domain; 4 | 5 | namespace CoreWiki.Application.Articles.Search 6 | { 7 | public interface IArticlesSearchEngine 8 | { 9 | Task> SearchAsync(string query, int pageNumber, int resultsPerPage); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Search/Impl/ArticlesDbSearchEngine.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Application.Articles.Search.Dto; 3 | using CoreWiki.Core.Domain; 4 | using CoreWiki.Data.Abstractions.Interfaces; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace CoreWiki.Application.Articles.Search.Impl 9 | { 10 | public class ArticlesDbSearchEngine : IArticlesSearchEngine 11 | { 12 | 13 | private readonly IArticleRepository _articleRepo; 14 | private readonly IMapper _mapper; 15 | 16 | public ArticlesDbSearchEngine(IArticleRepository articleRepo, IMapper mapper) 17 | { 18 | _articleRepo = articleRepo; 19 | _mapper = mapper; 20 | } 21 | 22 | public async Task> SearchAsync(string query, int pageNumber, int resultsPerPage) 23 | { 24 | var filteredQuery = query.Trim(); 25 | var offset = (pageNumber - 1) * resultsPerPage; 26 | 27 | var (articles, totalFound) = await _articleRepo.GetArticlesForSearchQuery(filteredQuery, offset, resultsPerPage); 28 | 29 | var result = new SearchResult
30 | { 31 | Query = filteredQuery, 32 | Results = articles.ToList(), 33 | CurrentPage = pageNumber, 34 | ResultsPerPage = resultsPerPage, 35 | TotalResults = totalFound 36 | }; 37 | 38 | return _mapper.Map>(result); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Search/Queries/SearchArticlesHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CoreWiki.Application.Articles.Search.Dto; 4 | using MediatR; 5 | 6 | namespace CoreWiki.Application.Articles.Search.Queries 7 | { 8 | class SearchArticlesHandler: IRequestHandler> 9 | { 10 | private readonly IArticlesSearchEngine _articlesSearchEngine; 11 | 12 | public SearchArticlesHandler(IArticlesSearchEngine articlesSearchEngine) 13 | { 14 | _articlesSearchEngine = articlesSearchEngine; 15 | } 16 | public Task> Handle(SearchArticlesQuery request, CancellationToken cancellationToken) 17 | { 18 | return _articlesSearchEngine.SearchAsync(request.Query, request.PageNumber, request.ResultsPerPage); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Search/Queries/SearchArticlesQuery.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Search.Dto; 2 | using MediatR; 3 | 4 | namespace CoreWiki.Application.Articles.Search.Queries 5 | { 6 | public class SearchArticlesQuery: IRequest> 7 | { 8 | public string Query { get; } 9 | public int PageNumber { get; } 10 | public int ResultsPerPage { get; } 11 | 12 | public SearchArticlesQuery(string query, int pageNumber, int resultsPerPage) 13 | { 14 | Query = query; 15 | PageNumber = pageNumber; 16 | ResultsPerPage = resultsPerPage; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CoreWiki.Application/Articles/Search/SearchArticleProfile.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Application.Articles.Search.Dto; 3 | using CoreWiki.Core.Domain; 4 | 5 | namespace CoreWiki.Application.Articles.Search 6 | { 7 | public class SearchArticleProfile: Profile 8 | { 9 | public SearchArticleProfile() 10 | { 11 | CreateMap(); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CoreWiki.Application/Common/CommandResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace CoreWiki.Application.Common 4 | { 5 | public class CommandResult 6 | { 7 | public bool Successful { get; set; } 8 | 9 | public Exception Exception { get; set; } 10 | 11 | public static CommandResult Success() 12 | { 13 | return new CommandResult { Successful = true }; 14 | } 15 | 16 | public static CommandResult Success(dynamic objectId) 17 | { 18 | return new CommandResult { Successful = true, ObjectId=objectId }; 19 | } 20 | 21 | public dynamic ObjectId { get; set; } 22 | 23 | public static CommandResult Error(Exception exception) 24 | { 25 | return new CommandResult {Successful = false, Exception = exception}; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CoreWiki.Application/Common/ConfigurePipeLineLogger.cs: -------------------------------------------------------------------------------- 1 | using MediatR.Pipeline; 2 | using Microsoft.Extensions.Logging; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace CoreWiki.Application.Common 7 | { 8 | public static class ConfigurePipeLineLogger 9 | { 10 | public class RequestLogger : IRequestPreProcessor 11 | { 12 | private readonly ILogger _logger; 13 | 14 | public RequestLogger(ILogger logger) 15 | { 16 | _logger = logger; 17 | } 18 | 19 | public Task Process(TRequest request, CancellationToken cancellationToken) 20 | { 21 | var name = typeof(TRequest).Name; 22 | 23 | _logger.LogInformation("CoreWiki Request Query: {Name} {@Request}", name, request); 24 | 25 | return Task.CompletedTask; 26 | } 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CoreWiki.Application/Common/Constants.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Application.Common 2 | { 3 | public static class Constants 4 | { 5 | public const string HomePageSlug = "home-page"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CoreWiki.Application/Common/UrlHelpers.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace CoreWiki.Application.Common 4 | { 5 | public class UrlHelpers 6 | { 7 | public static string SlugToTopic(string slug) 8 | { 9 | if (string.IsNullOrEmpty(slug)) 10 | { 11 | return ""; 12 | } 13 | 14 | var textInfo = new CultureInfo("en-US", false).TextInfo; 15 | var outValue = textInfo.ToTitleCase(slug); 16 | 17 | return outValue.Replace("-", " "); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CoreWiki.Application/CoreWiki.Application.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | System 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CoreWiki.Application/MyAssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | [assembly: InternalsVisibleTo("CoreWiki.Test")] 6 | -------------------------------------------------------------------------------- /CoreWiki.Core/CoreWiki.Core.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.0 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /CoreWiki.Core/Domain/ArticleHistory.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | using System; 3 | 4 | namespace CoreWiki.Core.Domain 5 | { 6 | public class ArticleHistory 7 | { 8 | public int Id { get; set; } 9 | 10 | public Guid AuthorId { get; set; } 11 | 12 | public string AuthorName { get; set; } 13 | 14 | public int Version { get; set; } 15 | 16 | public string Topic { get; set; } 17 | 18 | public string Slug { get; set; } 19 | 20 | public Instant Published { get; set; } 21 | 22 | public string Content { get; set; } 23 | 24 | public int ArticleId { get; set; } 25 | 26 | public static ArticleHistory FromArticle(Article article) 27 | { 28 | 29 | return new ArticleHistory 30 | { 31 | ArticleId = article.Id, 32 | AuthorId = article.AuthorId, 33 | AuthorName = article.AuthorName, 34 | Content = article.Content, 35 | Published = article.Published, 36 | Slug = article.Slug, 37 | Topic = article.Topic, 38 | Version = article.Version 39 | }; 40 | 41 | } 42 | 43 | } 44 | 45 | 46 | } 47 | -------------------------------------------------------------------------------- /CoreWiki.Core/Domain/Comment.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | using System; 3 | 4 | namespace CoreWiki.Core.Domain 5 | { 6 | public class Comment 7 | { 8 | 9 | public int Id { get; set; } 10 | 11 | public int ArticleId { get; set; } 12 | 13 | public string DisplayName { get; set; } 14 | 15 | public string Email { get; set; } 16 | 17 | public Instant Submitted { get; set; } 18 | 19 | public Guid AuthorId { get; set; } 20 | 21 | public string Content { get; set; } 22 | 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /CoreWiki.Core/Domain/SearchResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace CoreWiki.Core.Domain 5 | { 6 | public class SearchResult 7 | { 8 | public string Query { get; set; } 9 | 10 | public List Results { get; set; } 11 | 12 | public int TotalResults { get; set; } 13 | 14 | public int ResultsPerPage { get; set; } 15 | 16 | public int CurrentPage { get; set; } 17 | 18 | public int TotalPages => (int) Math.Ceiling((decimal)TotalResults / ResultsPerPage); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CoreWiki.Core/Domain/SlugHistory.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | 3 | namespace CoreWiki.Core.Domain 4 | { 5 | public class SlugHistory { 6 | 7 | public int Id { get; set; } 8 | 9 | public virtual Article Article { get; set; } 10 | 11 | public string OldSlug { get; set; } 12 | 13 | public Instant Added { get; set; } 14 | 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /CoreWiki.Data.Abstractions/CoreWiki.Data.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /CoreWiki.Data.Abstractions/Interfaces/IArticleRepository.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Core.Domain; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Threading.Tasks; 5 | 6 | namespace CoreWiki.Data.Abstractions.Interfaces 7 | { 8 | public interface IArticleRepository : IDisposable 9 | { 10 | 11 | Task
GetArticleBySlug(string articleSlug); 12 | 13 | Task
GetArticleWithHistoriesBySlug(string articleSlug); 14 | 15 | Task
GetArticleById(int articleId); 16 | 17 | Task> GetLatestArticles(int numOfArticlesToGet); 18 | 19 | Task
CreateArticleAndHistory(Article article); 20 | 21 | Task<(IEnumerable
articles, int totalFound)> GetArticlesForSearchQuery(string filteredQuery, int offset, int resultsPerPage); 22 | 23 | Task IsTopicAvailable(string articleSlug, int articleId); 24 | 25 | Task Exists(int id); 26 | 27 | Task Update(Article article); 28 | 29 | Task IncrementViewCount(string slug); 30 | 31 | Task
Delete(string slug); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CoreWiki.Data.Abstractions/Interfaces/ICommentRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CoreWiki.Core.Domain; 4 | 5 | namespace CoreWiki.Data.Abstractions.Interfaces 6 | { 7 | public interface ICommentRepository : IDisposable 8 | { 9 | Task CreateComment(Comment comment); 10 | Task DeleteAllCommentsOfArticle(int articleId); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CoreWiki.Data.Abstractions/Interfaces/ISlugHistoryRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | using CoreWiki.Core.Domain; 4 | 5 | namespace CoreWiki.Data.Abstractions.Interfaces 6 | { 7 | public interface ISlugHistoryRepository : IDisposable 8 | { 9 | 10 | Task GetSlugHistoryWithArticle(string slug); 11 | 12 | Task AddToHistory(string oldSlug, Article article); 13 | 14 | Task DeleteAllHistoryOfArticle(int articleId); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CoreWiki.Data/ArticleNotFoundException.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Runtime.Serialization; 3 | 4 | namespace CoreWiki.Data.EntityFramework 5 | { 6 | [Serializable] 7 | public class ArticleNotFoundException : Exception 8 | { 9 | public ArticleNotFoundException() 10 | { 11 | } 12 | 13 | public ArticleNotFoundException(string message) : base(message) 14 | { 15 | } 16 | 17 | public ArticleNotFoundException(string message, Exception innerException) : base(message, innerException) 18 | { 19 | } 20 | 21 | protected ArticleNotFoundException(SerializationInfo info, StreamingContext context) : base(info, context) 22 | { 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CoreWiki.Data/CoreWiki.Data.EntityFramework.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | true 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | $([System.String]::Copy("%(Filename)").Replace(".Designer","")).cs 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /CoreWiki.Data/Models/ProtectedArticles.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Data.EntityFramework.Models 2 | { 3 | 4 | public static class ProtectedArticles { 5 | 6 | public static string[] ToArray() { 7 | 8 | return new [] { 9 | HomePage 10 | }; 11 | 12 | } 13 | 14 | public const string HomePage = "home-page"; 15 | 16 | 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /CoreWiki.Data/Models/SlugHistoryDAO.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | using NodaTime.Extensions; 3 | using System; 4 | using System.ComponentModel; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.ComponentModel.DataAnnotations.Schema; 7 | 8 | namespace CoreWiki.Data.EntityFramework.Models 9 | { 10 | 11 | [Table("SlugHistories")] 12 | public class SlugHistoryDAO 13 | { 14 | [Required, Key] 15 | [DatabaseGenerated(DatabaseGeneratedOption.Identity)] 16 | public int Id { get; set; } 17 | 18 | public virtual ArticleDAO Article { get; set; } 19 | 20 | public string OldSlug { get; set; } 21 | 22 | [NotMapped] 23 | public Instant Added { get; set; } 24 | 25 | [Obsolete("This property only exists for EF serialization purposes")] 26 | [DataType(DataType.DateTime)] 27 | [Column("Added")] 28 | [EditorBrowsable(EditorBrowsableState.Never)] // Make it harder to shoot are selfs in the foot. 29 | public DateTime AddedDateTime 30 | { 31 | get => Added.ToDateTimeUtc(); 32 | set => Added = DateTime.SpecifyKind(value, DateTimeKind.Utc).ToInstant(); 33 | } 34 | 35 | public Core.Domain.SlugHistory ToDomain() { 36 | 37 | return new Core.Domain.SlugHistory 38 | { 39 | 40 | Added = this.Added, 41 | Article = this.Article.ToDomain(), 42 | Id = this.Id, 43 | OldSlug = this.OldSlug 44 | 45 | }; 46 | 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /CoreWiki.Data/Repositories/CommentRepository.cs: -------------------------------------------------------------------------------- 1 | using System.Linq; 2 | using CoreWiki.Data.EntityFramework.Models; 3 | using System.Threading.Tasks; 4 | using CoreWiki.Core.Domain; 5 | using CoreWiki.Data.Abstractions.Interfaces; 6 | 7 | namespace CoreWiki.Data.EntityFramework.Repositories 8 | { 9 | public class CommentRepository : ICommentRepository 10 | { 11 | 12 | public CommentRepository(ApplicationDbContext context) 13 | { 14 | Context = context; 15 | } 16 | 17 | public ApplicationDbContext Context { get; } 18 | 19 | public async Task CreateComment(Comment comment) 20 | { 21 | await Context.Comments.AddAsync(CommentDAO.FromDomain(comment)); 22 | await Context.SaveChangesAsync(); 23 | } 24 | 25 | public async Task DeleteAllCommentsOfArticle(int articleId) 26 | { 27 | Context.Comments.RemoveRange(Context.Comments.Where(c => c.IdArticle == articleId)); 28 | await Context.SaveChangesAsync(); 29 | } 30 | 31 | 32 | public void Dispose() 33 | { 34 | this.Context.Dispose(); 35 | } 36 | 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CoreWiki.Data/Repositories/SlugHistoryRepository.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Data.Abstractions.Interfaces; 2 | using CoreWiki.Data.EntityFramework.Models; 3 | using Microsoft.EntityFrameworkCore; 4 | using System; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace CoreWiki.Data.EntityFramework.Repositories 9 | { 10 | public class SlugHistoryRepository : ISlugHistoryRepository 11 | { 12 | public SlugHistoryRepository(ApplicationDbContext context) 13 | { 14 | Context = context; 15 | } 16 | 17 | public ApplicationDbContext Context { get; } 18 | 19 | public async Task GetSlugHistoryWithArticle(string slug) 20 | { 21 | var res = await Context.SlugHistories.Include(h => h.Article) 22 | .OrderByDescending(h => h.Added) 23 | .FirstOrDefaultAsync(h => h.OldSlug == slug.ToLowerInvariant()) 24 | .ConfigureAwait(false); 25 | return res?.ToDomain(); 26 | } 27 | 28 | public void Dispose() 29 | { 30 | Context.Dispose(); 31 | } 32 | 33 | public Task AddToHistory(string oldSlug, Core.Domain.Article article) 34 | { 35 | var newSlug = new SlugHistoryDAO() 36 | { 37 | OldSlug = oldSlug, 38 | Article = ArticleDAO.FromDomain(article), 39 | AddedDateTime = DateTime.UtcNow 40 | }; 41 | 42 | Context.SlugHistories.Add(newSlug); 43 | return Context.SaveChangesAsync(); 44 | } 45 | 46 | public async Task DeleteAllHistoryOfArticle(int articleId) 47 | { 48 | Context.SlugHistories.RemoveRange(Context.SlugHistories.Where(sh => sh.Article.Id == articleId)); 49 | await Context.SaveChangesAsync(); 50 | } 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /CoreWiki.Data/Security/CoreWikiIdentityContext.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | using Microsoft.AspNetCore.Identity.EntityFrameworkCore; 7 | using Microsoft.EntityFrameworkCore; 8 | 9 | namespace CoreWiki.Data.EntityFramework.Security 10 | { 11 | public class CoreWikiIdentityContext : IdentityDbContext 12 | { 13 | public CoreWikiIdentityContext(DbContextOptions options) 14 | : base(options) 15 | { 16 | } 17 | 18 | protected override void OnModelCreating(ModelBuilder builder) 19 | { 20 | base.OnModelCreating(builder); 21 | 22 | builder.Entity().HasData(new[] 23 | { 24 | new IdentityRole 25 | { 26 | Name = "Authors", 27 | NormalizedName = "Authors".ToUpper() 28 | }, 29 | new IdentityRole 30 | { 31 | Name = "Editors", 32 | NormalizedName = "Editors".ToUpper() 33 | }, 34 | new IdentityRole 35 | { 36 | Name = "Administrators", 37 | NormalizedName = "Administrators".ToUpper() 38 | } 39 | }); 40 | } 41 | 42 | public static void SeedData(CoreWikiIdentityContext context) 43 | { 44 | 45 | context.Database.Migrate(); 46 | 47 | } 48 | 49 | public override void Dispose() 50 | { 51 | base.Dispose(); 52 | } 53 | 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /CoreWiki.Data/Security/CoreWikiUser.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Identity; 6 | 7 | namespace CoreWiki.Data.EntityFramework.Security 8 | { 9 | // Add profile data for application users by adding properties to the CoreWikiUser class 10 | public class CoreWikiUser : IdentityUser 11 | { 12 | 13 | public string DisplayName { get; set; } 14 | 15 | public bool CanNotify { get; set; } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CoreWiki.FirstStart/Areas/FirstStart/Pages/Shared/_Layout.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | @ViewBag.Title 7 | 8 | @* This is a hack to allow Bootstrap to work, more thought required 9 | Do we care about theme support at this stage ? 10 | *@ 11 | 12 | 13 | 14 | 15 | 16 | 17 | @RenderSection("Styles", false) 18 | 19 | 20 | 21 |
22 | @RenderBody() 23 |
24 | @RenderSection("Scripts", required: false) 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /CoreWiki.FirstStart/Areas/FirstStart/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 |  2 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 3 | -------------------------------------------------------------------------------- /CoreWiki.FirstStart/Areas/FirstStart/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /CoreWiki.FirstStart/CoreWiki.FirstStart.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /CoreWiki.FirstStart/FirstStartConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace CoreWiki.FirstStart 4 | { 5 | public class FirstStartConfiguration 6 | { 7 | 8 | //public string WikiName { get; set; } 9 | 10 | [Required] 11 | public string AdminUserName { get; set; } 12 | 13 | [Required] 14 | public string AdminDisplayName { get; set; } 15 | 16 | [Required, EmailAddress] 17 | public string AdminEmail { get; set; } 18 | 19 | [Required] 20 | [DataType(DataType.Password)] 21 | [MinLength(6)] 22 | public string AdminPassword {get;set;} 23 | 24 | [Required] 25 | public string Database {get;set;} 26 | 27 | public string ConnectionString { get; set; } 28 | 29 | } 30 | 31 | 32 | } 33 | -------------------------------------------------------------------------------- /CoreWiki.FirstStart/UserAppConfig.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.FirstStart 2 | { 3 | public class UserAppConfig { 4 | 5 | public string DataProvider { get; set; } 6 | 7 | public string ConnectionString { get; set; } 8 | 9 | } 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /CoreWiki.Notifications.Abstractions/Configuration/EmailNotifications.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Notifications.Abstractions.Configuration 2 | { 3 | 4 | public class EmailNotifications 5 | { 6 | public string SendGridApiKey { get; set; } 7 | public string FromEmailAddress { get; set; } 8 | public string FromName { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CoreWiki.Notifications.Abstractions/CoreWiki.Notifications.Abstractions.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /CoreWiki.Notifications.Abstractions/Notifications/IEmailMessageFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace CoreWiki.Notifications.Abstractions.Notifications 4 | { 5 | public interface IEmailMessageFormatter 6 | { 7 | Task FormatEmailMessage(string templateName, T model) where T : class; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CoreWiki.Notifications.Abstractions/Notifications/IEmailNotifier.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | 3 | namespace CoreWiki.Notifications.Abstractions.Notifications 4 | { 5 | public interface IEmailNotifier 6 | { 7 | Task SendEmailAsync(string recipientEmail, string subject, string body); 8 | Task SendEmailAsync(string recipientEmail, string recipientName, string subject, string body); 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki.Notifications.Abstractions/Notifications/INotificationService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace CoreWiki.Notifications.Abstractions.Notifications 5 | { 6 | public interface INotificationService 7 | { 8 | Task SendConfirmationEmail(string confirmEmail, string userId, string confirmCode); 9 | Task SendForgotPasswordEmail(string accountEmail, string resetToken, Func canNotifyUser); 10 | Task SendNewCommentEmail(string authorEmail, string authorName, string commenterName, string topic, string articleSlug, Func canNotifyUser); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CoreWiki.Notifications.Abstractions/Notifications/ITemplateParser.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc.ViewEngines; 3 | 4 | namespace CoreWiki.Notifications.Abstractions.Notifications 5 | { 6 | public interface ITemplateParser 7 | { 8 | Task Parse(IView view, TModel model) where TModel : class; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CoreWiki.Notifications.Abstractions/Notifications/ITemplateProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.ViewEngines; 2 | 3 | namespace CoreWiki.Notifications.Abstractions.Notifications 4 | { 5 | public interface ITemplateProvider 6 | { 7 | IView GetTemplate(string templateName); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/CoreWiki.Notifications.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/EmailMessageFormatter.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using CoreWiki.Notifications.Abstractions.Notifications; 3 | 4 | namespace CoreWiki.Notifications 5 | { 6 | public class EmailMessageFormatter : IEmailMessageFormatter 7 | { 8 | private readonly ITemplateProvider _templateProvider; 9 | private readonly ITemplateParser _templateParser; 10 | 11 | public EmailMessageFormatter(ITemplateProvider templateProvider, ITemplateParser templateParser) 12 | { 13 | _templateProvider = templateProvider; 14 | _templateParser = templateParser; 15 | } 16 | 17 | public async Task FormatEmailMessage(string templateName, TModel model) where TModel : class 18 | { 19 | var template = _templateProvider.GetTemplate(templateName); 20 | 21 | return await _templateParser.Parse(template, model); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/Models/ConfirmationEmailModel.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Notifications.Models 2 | { 3 | public class ConfirmationEmailModel : EmailMessageBaseModel 4 | { 5 | public string ReturnUrl { get; set; } 6 | public string ConfirmEmail { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/Models/EmailMessageBaseModel.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Notifications.Models 2 | { 3 | public abstract class EmailMessageBaseModel 4 | { 5 | public string BaseUrl { get; set; } 6 | public string Title { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/Models/ForgotPasswordEmailModel.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Notifications.Models 2 | { 3 | public class ForgotPasswordEmailModel : EmailMessageBaseModel 4 | { 5 | public string ReturnUrl { get; set; } 6 | public string AccountEmail { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/Models/NewCommentEmailModel.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Notifications.Models 2 | { 3 | public class NewCommentEmailModel : EmailMessageBaseModel 4 | { 5 | public string AuthorName { get; set; } 6 | public string ArticleTopic { get; set; } 7 | public string ArticleUrl { get; set; } 8 | public string CommenterDisplayName { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/TemplateProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Razor; 2 | using Microsoft.AspNetCore.Mvc.ViewEngines; 3 | using System; 4 | using CoreWiki.Notifications.Abstractions.Notifications; 5 | 6 | namespace CoreWiki.Notifications 7 | { 8 | public class TemplateProvider : ITemplateProvider 9 | { 10 | public const string ConfirmationEmailTemplate = "ConfirmationEmail"; 11 | public const string ForgotPasswordEmailTemplate = "ForgotPasswordEmail"; 12 | public const string NewCommentEmailTemplate = "NewCommentEmail"; 13 | private const string TemplateRoot = "Templates"; 14 | private const string TemplateExtension = ".cshtml"; 15 | private readonly IRazorViewEngine _viewEngine; 16 | 17 | public TemplateProvider(IRazorViewEngine viewEngine) 18 | { 19 | _viewEngine = viewEngine; 20 | } 21 | 22 | public IView GetTemplate(string templateName) 23 | { 24 | var viewPath = $"/{TemplateRoot}/{templateName}{TemplateExtension}"; 25 | var viewEngineResult = _viewEngine.GetView(executingFilePath: null, viewPath: viewPath, isMainPage: true); 26 | if (viewEngineResult.Success) return viewEngineResult.View; 27 | 28 | var searchedLocations = string.Concat(viewEngineResult.SearchedLocations); 29 | 30 | throw new Exception($"Template: '{templateName}' not found. Searched locations: {searchedLocations}"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/Templates/ConfirmationEmail.cshtml: -------------------------------------------------------------------------------- 1 | @using CoreWiki.Notifications.Models 2 | @model CoreWiki.Notifications.Models.ConfirmationEmailModel 3 | 4 | @{ 5 | ViewData["Title"] = @Model.Title; 6 | ViewData["BaseUrl"] = @Model.BaseUrl; 7 | } 8 | 9 | 10 | 11 | 18 | 19 | 20 | 31 | 32 |
12 |

13 | Please confirm your email address @Model.ConfirmEmail by clicking "Confirm". 14 |

15 | 16 |
17 |
21 | 22 | 23 | 28 | 29 |
24 | 25 | Confirm 26 | 27 |
30 |
33 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/Templates/ForgotPasswordEmail.cshtml: -------------------------------------------------------------------------------- 1 | @using CoreWiki.Notifications.Models 2 | @model CoreWiki.Notifications.Models.ForgotPasswordEmailModel 3 | 4 | @{ 5 | ViewData["Title"] = @Model.Title; 6 | ViewData["BaseUrl"] = @Model.BaseUrl; 7 | } 8 | 9 | 10 | 11 | 18 | 19 | 20 | 31 | 32 |
12 |

13 | Reset password for @Model.AccountEmail by clicking "Reset Password". 14 |

15 | 16 |
17 |
21 | 22 | 23 | 28 | 29 |
24 | 25 | Reset Password 26 | 27 |
30 |
33 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/Templates/NewCommentEmail.cshtml: -------------------------------------------------------------------------------- 1 | @using CoreWiki.Notifications.Models 2 | @model CoreWiki.Notifications.Models.NewCommentEmailModel 3 | 4 | @{ 5 | ViewData["Title"] = @Model.Title; 6 | ViewData["BaseUrl"] = @Model.BaseUrl; 7 | } 8 | 9 | 10 | 11 | 18 | 19 | 20 | 31 | 32 |
12 |

13 | Someone left a comment on an article you wrote. 14 |

15 | 16 |
17 |
21 | 22 | 23 | 28 | 29 |
24 | 25 | View Comment 26 | 27 |
30 |
33 | -------------------------------------------------------------------------------- /CoreWiki.Notifications/Templates/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 |  -------------------------------------------------------------------------------- /CoreWiki.Notifications/Templates/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "~/Templates/Shared/_Layout.cshtml"; 3 | } 4 | -------------------------------------------------------------------------------- /CoreWiki.Test/Application/Managing/ArticleManagingProfileTests.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Application.Articles.Managing; 3 | using Xunit; 4 | 5 | namespace CoreWiki.Test.Application.Managing 6 | { 7 | public class ArticleManagingProfileTests 8 | { 9 | private readonly IMapper _mapper; 10 | private readonly MapperConfiguration _mapperConfiguration; 11 | 12 | public ArticleManagingProfileTests() 13 | { 14 | _mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfile()); 15 | _mapper = _mapperConfiguration.CreateMapper(); 16 | } 17 | 18 | [Fact] 19 | public void ConfigTest() 20 | { 21 | _mapperConfiguration.AssertConfigurationIsValid(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CoreWiki.Test/Application/Reading/ArticleReadingProfileTests.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Application.Articles.Reading; 3 | using Xunit; 4 | 5 | namespace CoreWiki.Test.Application.Reading 6 | { 7 | public class ArticleReadingProfileTests 8 | { 9 | private readonly IMapper _mapper; 10 | private readonly MapperConfiguration _mapperConfiguration; 11 | 12 | public ArticleReadingProfileTests() 13 | { 14 | _mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfile()); 15 | _mapper = _mapperConfiguration.CreateMapper(); 16 | } 17 | 18 | [Fact] 19 | public void ConfigTest() 20 | { 21 | _mapperConfiguration.AssertConfigurationIsValid(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CoreWiki.Test/Application/Search/ArticleSearchProfileTests.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Application.Articles.Search; 3 | using Xunit; 4 | 5 | namespace CoreWiki.Test.Application.Search 6 | { 7 | public class ArticleSearchProfileTests 8 | { 9 | private readonly IMapper _mapper; 10 | private readonly MapperConfiguration _mapperConfiguration; 11 | 12 | public ArticleSearchProfileTests() 13 | { 14 | _mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfile()); 15 | _mapper = _mapperConfiguration.CreateMapper(); 16 | } 17 | 18 | [Fact] 19 | public void ConfigTest() 20 | { 21 | _mapperConfiguration.AssertConfigurationIsValid(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CoreWiki.Test/Core/Domain/ArticleTests.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Core.Domain; 2 | using Xunit; 3 | 4 | namespace CoreWiki.Test.Core.Domain 5 | { 6 | public class ArticleTests 7 | { 8 | [Theory] 9 | [InlineData("HomePage", "home-page")] 10 | public void SettingTopic_ShouldGenerateValidSlug(string topic, string expectedSlug) 11 | { 12 | // Arrange 13 | var sut = new Article(); 14 | 15 | // Act 16 | sut.Topic = topic; 17 | 18 | // Assert 19 | Assert.Equal(expectedSlug, sut.Slug); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki.Test/CoreWiki.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp2.1 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /CoreWiki.Test/Helpers/StringHelperTests.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Common; 2 | using Xunit; 3 | 4 | namespace CoreWiki.Test.Helpers 5 | { 6 | public class StringHelperTests 7 | { 8 | [Theory] 9 | [InlineData(null, 0)] 10 | [InlineData("", 0)] 11 | [InlineData(" ", 0)] 12 | [InlineData(" 12 34", 2)] 13 | [InlineData("Test", 1)] 14 | [InlineData(" Test", 1)] 15 | [InlineData(" Test ", 1)] 16 | [InlineData(" Test double space ", 3)] 17 | [InlineData("Test double space", 3)] 18 | [InlineData("Don't count \" spaced quotes \"", 4)] 19 | [InlineData("test content fair dinkum 12345", 5)] 20 | public void WordCountShouldBeAccurate(string sentence, int expected_word_count) 21 | { 22 | var actual_word_count = sentence.WordCount(); 23 | Assert.Equal(expected_word_count, actual_word_count); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /CoreWiki.Test/Helpers/UrlHelperTests.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Common; 2 | using Xunit; 3 | 4 | namespace CoreWiki.Test.Helpers 5 | { 6 | public class UrlHelperTests 7 | { 8 | [Theory] 9 | [InlineData(null, "")] 10 | [InlineData("", "")] 11 | [InlineData("one-two", "One Two")] 12 | [InlineData("home-page", "Home Page")] 13 | [InlineData("onetwo", "Onetwo")] 14 | [InlineData("one-two-three", "One Two Three")] 15 | [InlineData("él-sofá", "Él Sofá")] 16 | public void SlugShouldBeATopic(string slug, string expected_topic) 17 | { 18 | var actual_topic = UrlHelpers.SlugToTopic(slug); 19 | Assert.Equal(expected_topic, actual_topic); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki.Test/Pages/SearchTests.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Search.Dto; 2 | using CoreWiki.Application.Articles.Search.Queries; 3 | using CoreWiki.Configuration.Startup; 4 | using CoreWiki.Pages; 5 | using MediatR; 6 | using Moq; 7 | using System.Collections.Generic; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using Xunit; 11 | 12 | namespace CoreWiki.Test.Pages 13 | { 14 | public class SearchTests 15 | { 16 | [Fact] 17 | public async Task OnGetAsync_WithPageNumberEqualsTo2And12Posts_ShouldReturnCurrentPageEqualsTo2() 18 | { 19 | 20 | var mediator = new Mock(); 21 | 22 | mediator.Setup(o => o.Send(It.IsAny(), default(CancellationToken))).Returns( 23 | Task.FromResult(new SearchResultDto 24 | { 25 | CurrentPage = 2, 26 | Results = new List 27 | { 28 | new ArticleSearchDto { Slug = "test11" }, 29 | new ArticleSearchDto { Slug = "test12" } 30 | } 31 | })); 32 | 33 | // Act 34 | var searchModel = new SearchModel(mediator.Object, ConfigureAutomapperServices.ConfigureAutomapper(null)); 35 | 36 | var result = await searchModel.OnGetAsync(query: "test", pageNumber: 2); 37 | 38 | Assert.Equal(2, searchModel.SearchResult.CurrentPage); 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /CoreWiki.Test/SearchArticles/Search.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Search; 2 | using CoreWiki.Application.Articles.Search.Queries; 3 | using Moq; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Text; 7 | using System.Threading; 8 | using Xunit; 9 | 10 | namespace CoreWiki.Test.SearchArticles 11 | { 12 | 13 | public class SearchEngineShould 14 | { 15 | //private readonly MockRepository _Mockery; 16 | private readonly Mock _MockSearchEngine; 17 | 18 | public SearchEngineShould() 19 | { 20 | 21 | // _Mockery = new MockRepository(MockBehavior.Loose); 22 | _MockSearchEngine = new Mock(); 23 | 24 | } 25 | 26 | [Fact] 27 | public void WhenHandlingQuery_ExecuteSearch() { 28 | 29 | // Arrange 30 | _MockSearchEngine.Setup(m => m.SearchAsync(It.IsAny(), It.IsAny(), It.IsAny())).Verifiable(); 31 | var sut = new SearchArticlesHandler(_MockSearchEngine.Object); 32 | 33 | // Act 34 | sut.Handle(new SearchArticlesQuery("test", 1, 1), CancellationToken.None); 35 | 36 | // Assert 37 | _MockSearchEngine.Verify(m => m.SearchAsync(It.IsAny(), It.IsAny(), It.IsAny()), Times.Exactly(1)); 38 | 39 | } 40 | 41 | } 42 | 43 | 44 | } 45 | -------------------------------------------------------------------------------- /CoreWiki.Test/Website/CoreWikiWebsiteProfileTests.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Configuration.Startup; 3 | using Xunit; 4 | 5 | namespace CoreWiki.Test.Website 6 | { 7 | public class CoreWikiWebsiteProfileTests 8 | { 9 | private readonly IMapper _mapper; 10 | private readonly MapperConfiguration _mapperConfiguration; 11 | 12 | public CoreWikiWebsiteProfileTests() 13 | { 14 | _mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfile()); 15 | _mapper = _mapperConfiguration.CreateMapper(); 16 | } 17 | 18 | [Fact] 19 | public void ConfigTest() 20 | { 21 | _mapperConfiguration.AssertConfigurationIsValid(); 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CoreWiki.Test/Website/Pages/Create/BaseFixture.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using AutoMapper; 3 | using CoreWiki.Configuration.Startup; 4 | using CoreWiki.Pages; 5 | using MediatR; 6 | using Moq; 7 | 8 | namespace CoreWiki.Test.Website.Pages.Create 9 | { 10 | public abstract class BaseFixture 11 | { 12 | 13 | protected const string _newArticleSlug = "new-page"; 14 | protected const string _newArticleTopic = "New Page"; 15 | protected const string username = "John Doe"; 16 | protected Guid userId = Guid.NewGuid(); 17 | protected Mock _mediator; 18 | protected CreateModel _sut; 19 | protected MapperConfiguration _mapperConfiguration; 20 | protected IMapper _mapper; 21 | 22 | protected BaseFixture() 23 | { 24 | _mediator = new Mock(); 25 | _mapperConfiguration = new MapperConfiguration(cfg => cfg.AddProfile()); 26 | _mapper = _mapperConfiguration.CreateMapper(); 27 | } 28 | 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /CoreWiki.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": "." 5 | } 6 | ], 7 | "settings": { 8 | "files.exclude": { 9 | "**/.cr/": true, 10 | "**/.vs/": true, 11 | "**/obj/": true, 12 | "**/bin/": true, 13 | "**/packages/": true, 14 | "**/node_modules/": true, 15 | "**/*.csproj.user": true, 16 | "**/*.js.map": true, 17 | "**/*.js": {"when": "$(basename).ts"}, 18 | "**/TestResults/": true 19 | }, 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /CoreWiki/App_Data/README.md: -------------------------------------------------------------------------------- 1 | # App_Data folder 2 | 3 | This folder contains any file-based databases in use by the CoreWiki application 4 | 5 | No files other than this README should be committed to source control 6 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/AnyRoleRequirement.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | 7 | namespace CoreWiki.Areas.Identity 8 | { 9 | public class AnyRoleRequirement : AuthorizationHandler, IAuthorizationRequirement 10 | { 11 | 12 | private string[] roles; 13 | 14 | public AnyRoleRequirement(string[] roles) 15 | { 16 | this.roles = roles; 17 | } 18 | 19 | protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, AnyRoleRequirement requirement) 20 | { 21 | 22 | foreach (var role in roles) 23 | { 24 | 25 | if (context.User.IsInRole(role)) 26 | { 27 | context.Succeed(requirement); 28 | break; 29 | } 30 | 31 | } 32 | 33 | return Task.CompletedTask; 34 | 35 | } 36 | } 37 | 38 | public static class RequirementExtensions 39 | { 40 | 41 | public static AuthorizationPolicyBuilder RequireAnyRole(this AuthorizationPolicyBuilder policy, params string[] roles) 42 | { 43 | 44 | return RequireAnyRole(policy, (IEnumerable)roles); 45 | 46 | } 47 | 48 | public static AuthorizationPolicyBuilder RequireAnyRole(this AuthorizationPolicyBuilder policy, IEnumerable roles) 49 | { 50 | 51 | policy.AddRequirements(new AnyRoleRequirement(roles.ToArray())); 52 | 53 | return policy; 54 | 55 | } 56 | 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/AccessDenied.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model AccessDeniedModel 3 | @{ 4 | ViewData["Title"] = "Access denied"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

You do not have access to this resource.

10 |
11 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/AccessDenied.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace CoreWiki.Areas.Identity.Pages.Account 8 | { 9 | public class AccessDeniedModel : PageModel 10 | { 11 | public void OnGet() 12 | { 13 | 14 | } 15 | } 16 | } 17 | 18 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ConfirmEmail.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ConfirmEmailModel 3 | @{ 4 | ViewData["Title"] = "Confirm email"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | Thank you for confirming your email. 11 |

12 |
13 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ConfirmEmail.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using CoreWiki.Data.EntityFramework.Security; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | 11 | namespace CoreWiki.Areas.Identity.Pages.Account 12 | { 13 | [AllowAnonymous] 14 | public class ConfirmEmailModel : PageModel 15 | { 16 | private readonly UserManager _userManager; 17 | 18 | public ConfirmEmailModel(UserManager userManager) 19 | { 20 | _userManager = userManager; 21 | } 22 | 23 | public async Task OnGetAsync(string userId, string code) 24 | { 25 | if (userId == null || code == null) 26 | { 27 | return Redirect("/"); 28 | } 29 | 30 | var user = await _userManager.FindByIdAsync(userId); 31 | if (user == null) 32 | { 33 | return NotFound($"Unable to load user with ID '{userId}'."); 34 | } 35 | 36 | var result = await _userManager.ConfirmEmailAsync(user, code); 37 | if (!result.Succeeded) 38 | { 39 | throw new InvalidOperationException($"Error confirming email for user with ID '{userId}':"); 40 | } 41 | 42 | return Page(); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ExternalLogin.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ExternalLoginModel 3 | @{ 4 | ViewData["Title"] = "Register"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Associate your @Model.LoginProvider account.

9 |
10 | 11 |

12 | You've successfully authenticated with @Model.LoginProvider. 13 | Please enter an email address for this site below and click the Register button to finish 14 | logging in. 15 |

16 | 17 |
18 |
19 |
20 |
21 |
22 | 23 | 24 | 25 |
26 | 27 |
28 |
29 |
30 | 31 | @section Scripts { 32 | 33 | } 34 | 35 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ForgotPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ForgotPasswordModel 3 | @{ 4 | ViewData["Title"] = "Forgot your password?"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Enter your email.

9 |
10 |
11 |
12 |
13 |
14 |
15 | 16 | 17 | 18 |
19 | 20 |
21 |
22 |
23 | 24 | @section Scripts { 25 | 26 | } 27 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ForgotPasswordConfirmation 3 | @{ 4 | ViewData["Title"] = "Forgot password confirmation"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | Please check your email to reset your password. 10 |

11 | 12 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ForgotPasswordConfirmation.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Authorization; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | 7 | namespace CoreWiki.Areas.Identity.Pages.Account 8 | { 9 | [AllowAnonymous] 10 | public class ForgotPasswordConfirmation : PageModel 11 | { 12 | public void OnGet() 13 | { 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Lockout.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LockoutModel 3 | @{ 4 | ViewData["Title"] = "Locked out"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

This account has been locked out, please try again later.

10 |
11 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Lockout.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace CoreWiki.Areas.Identity.Pages.Account 9 | { 10 | [AllowAnonymous] 11 | public class LockoutModel : PageModel 12 | { 13 | public void OnGet() 14 | { 15 | 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/LoginWith2fa.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginWith2faModel 3 | @{ 4 | ViewData["Title"] = "Two-factor authentication"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

Your login is protected with an authenticator app. Enter your authenticator code below.

10 |
11 |
12 |
13 | 14 |
15 |
16 | 17 | 18 | 19 |
20 |
21 |
22 | 26 |
27 |
28 |
29 | 30 |
31 |
32 |
33 |
34 |

35 | Don't have access to your authenticator device? You can 36 | log in with a recovery code. 37 |

38 | 39 | @section Scripts { 40 | 41 | } 42 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/LoginWithRecoveryCode.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LoginWithRecoveryCodeModel 3 | @{ 4 | ViewData["Title"] = "Recovery code verification"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |
9 |

10 | You have requested to log in with a recovery code. This login will not be remembered until you provide 11 | an authenticator app code at log in or disable 2FA and log in again. 12 |

13 |
14 |
15 |
16 |
17 |
18 | 19 | 20 | 21 |
22 | 23 |
24 |
25 |
26 | 27 | @section Scripts { 28 | 29 | } 30 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Logout.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model LogoutModel 3 | @{ 4 | ViewData["Title"] = "Log out"; 5 | } 6 | 7 |
8 |

@ViewData["Title"]

9 |

You have successfully logged out of the application.

10 |
-------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Logout.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using CoreWiki.Data.EntityFramework.Security; 7 | using Microsoft.AspNetCore.Identity; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.AspNetCore.Mvc.RazorPages; 10 | using Microsoft.Extensions.Logging; 11 | 12 | namespace CoreWiki.Areas.Identity.Pages.Account 13 | { 14 | [AllowAnonymous] 15 | public class LogoutModel : PageModel 16 | { 17 | private readonly SignInManager _signInManager; 18 | private readonly ILogger _logger; 19 | 20 | public LogoutModel(SignInManager signInManager, ILogger logger) 21 | { 22 | _signInManager = signInManager; 23 | _logger = logger; 24 | } 25 | 26 | public void OnGet() 27 | { 28 | } 29 | 30 | public async Task OnPost(string returnUrl = null) 31 | { 32 | await _signInManager.SignOutAsync(); 33 | 34 | foreach (var cookie in Request.Cookies.Keys) 35 | { 36 | if (cookie.StartsWith(".AspNetCore.Antiforgery.")) 37 | { 38 | Response.Cookies.Delete(cookie); 39 | } 40 | } 41 | 42 | _logger.LogInformation("User logged out."); 43 | if (returnUrl != null) 44 | { 45 | return LocalRedirect(returnUrl); 46 | } 47 | else 48 | { 49 | //return Page(); 50 | return LocalRedirect("/"); 51 | } 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/ChangePassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ChangePasswordModel 3 | @{ 4 | ViewData["Title"] = "Change password"; 5 | } 6 | 7 |

@ViewData["Title"]

8 | 9 |
10 |
11 |
12 |
13 |
14 | 15 | 16 | 17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | @section Scripts { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/DeletePersonalData.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model DeletePersonalDataModel 3 | @{ 4 | ViewData["Title"] = "Delete Personal Data"; 5 | ViewData["ActivePage"] = ManageNavPages.DeletePersonalData; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | 16 | 17 |
18 |
19 |
20 | @if (Model.RequirePassword) 21 | { 22 |
23 | 24 | 25 | 26 |
27 | } 28 | 29 |
30 |
31 | 32 | @section Scripts { 33 | 34 | } 35 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/Disable2fa.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model Disable2faModel 3 | @{ 4 | ViewData["Title"] = "Disable two-factor authentication (2FA)"; 5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; 6 | } 7 | 8 | 9 |

@ViewData["Title"]

10 | 11 | 21 | 22 |
23 |
24 | 25 |
26 |
27 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/DownloadPersonalData.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model DownloadPersonalDataModel 3 | @{ 4 | ViewData["Title"] = "Download Your Data"; 5 | ViewData["ActivePage"] = ManageNavPages.DownloadPersonalData; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 | @section Scripts { 11 | 12 | } -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/GenerateRecoveryCodes.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model GenerateRecoveryCodesModel 3 | @{ 4 | ViewData["Title"] = "Generate two-factor authentication (2FA) recovery codes"; 5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; 6 | } 7 | 8 | 9 |

@ViewData["Title"]

10 | 23 |
24 |
25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/MyRoles.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model MyRolesDataModel 3 | @{ 4 | ViewData["Title"] = "My Roles & Permissions"; 5 | ViewData["ActivePage"] = ManageNavPages.MyRoles; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 |
11 |
12 |
13 |
14 |

15 | @if (Model.Roles.Any()) 16 | { 17 | @: Your account may have 1 or more permissions (AKA Roles). Below are what is assigned to your account: 18 | } 19 | else 20 | { 21 | @: You do not currently have any roles. Please speak to an administratior or raise a support ticket. 22 | } 23 |

24 |
You have @Model.Roles.Count Roles/Permissions
25 |
26 |
    27 | @foreach (var role in Model.Roles) 28 | { 29 |
  • @role
  • 30 | } 31 |
32 |
33 |
34 |
35 | 36 | @section Scripts { 37 | 38 | } 39 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/MyRoles.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using System.Threading.Tasks; 3 | using CoreWiki.Data.EntityFramework.Security; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | using Microsoft.Extensions.Logging; 8 | 9 | namespace CoreWiki.Areas.Identity.Pages.Account.Manage 10 | { 11 | public class MyRolesDataModel : PageModel 12 | { 13 | private readonly UserManager _userManager; 14 | private readonly ILogger _logger; 15 | 16 | public MyRolesDataModel( 17 | UserManager userManager, 18 | RoleManager roleManager, 19 | ILogger logger) 20 | { 21 | _userManager = userManager; 22 | _logger = logger; 23 | } 24 | 25 | public IList Roles { get; set; } 26 | 27 | public async Task OnGet() 28 | { 29 | var user = await _userManager.GetUserAsync(User); 30 | if (user == null) 31 | { 32 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 33 | } 34 | 35 | Roles = await _userManager.GetRolesAsync(user); 36 | 37 | return Page(); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PersonalDataModel 3 | @{ 4 | ViewData["Title"] = "Personal Data"; 5 | ViewData["ActivePage"] = ManageNavPages.PersonalData; 6 | } 7 | 8 |

@ViewData["Title"]

9 | 10 |
11 |
12 |

Your account contains personal data that you have given us. This page allows you to download or delete that data.

13 |

14 | Deleting this data will permanently remove your account, and this cannot be recovered. 15 |

16 |
17 | 18 |
19 |

20 | Delete 21 |

22 |
23 |
24 | 25 | @section Scripts { 26 | 27 | } 28 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/PersonalData.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using CoreWiki.Data.EntityFramework.Security; 3 | using Microsoft.AspNetCore.Identity; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.AspNetCore.Mvc.RazorPages; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace CoreWiki.Areas.Identity.Pages.Account.Manage 9 | { 10 | public class PersonalDataModel : PageModel 11 | { 12 | private readonly UserManager _userManager; 13 | private readonly ILogger _logger; 14 | 15 | public PersonalDataModel( 16 | UserManager userManager, 17 | ILogger logger) 18 | { 19 | _userManager = userManager; 20 | _logger = logger; 21 | } 22 | 23 | public async Task OnGet() 24 | { 25 | var user = await _userManager.GetUserAsync(User); 26 | if (user == null) 27 | { 28 | return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'."); 29 | } 30 | 31 | return Page(); 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/ResetAuthenticator.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetAuthenticatorModel 3 | @{ 4 | ViewData["Title"] = "Reset authenticator key"; 5 | ViewData["ActivePage"] = ManageNavPages.TwoFactorAuthentication; 6 | } 7 | 8 | 9 |

@ViewData["Title"]

10 | 20 |
21 |
22 | 23 |
24 |
25 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/SetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model SetPasswordModel 3 | @{ 4 | ViewData["Title"] = "Set password"; 5 | ViewData["ActivePage"] = ManageNavPages.ChangePassword; 6 | } 7 | 8 |

Set your password

9 | 10 |

11 | You do not have a local username/password for this site. Add a local 12 | account so you can log in without an external login. 13 |

14 |
15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 | 25 | 26 | 27 |
28 | 29 |
30 |
31 |
32 | 33 | @section Scripts { 34 | 35 | } 36 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/_Layout.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "/Pages/Shared/_Layout.cshtml"; 3 | } 4 | 5 |

Manage your account

6 | 7 |
8 |

Change your account settings

9 |
10 |
11 |
12 | 13 |
14 |
15 | @RenderBody() 16 |
17 |
18 |
19 | 20 | @section Scripts { 21 | @RenderSection("Scripts", required: false) 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/_ManageNav.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Http 2 | @inject SignInManager SignInManager 3 | @inject UserManager UserManager 4 | @inject IHttpContextAccessor HttpContext 5 | @{ 6 | var hasExternalLogins = (await SignInManager.GetExternalAuthenticationSchemesAsync()).Any(); 7 | } 8 | 28 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/_StatusMessage.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 | @if (!String.IsNullOrEmpty(Model)) 4 | { 5 | var statusMessageClass = Model.StartsWith("Error") ? "danger" : "success"; 6 | 10 | } 11 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/Manage/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using CoreWiki.Areas.Identity.Pages.Account.Manage 2 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ResetPassword.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetPasswordModel 3 | @{ 4 | ViewData["Title"] = "Reset password"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

Reset your password.

9 |
10 |
11 |
12 |
13 |
14 | 15 |
16 | 17 | 18 | 19 |
20 |
21 | 22 | 23 | 24 |
25 |
26 | 27 | 28 | 29 |
30 | 31 |
32 |
33 |
34 | 35 | @section Scripts { 36 | 37 | } 38 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ResetPasswordConfirmationModel 3 | @{ 4 | ViewData["Title"] = "Reset password confirmation"; 5 | } 6 | 7 |

@ViewData["Title"]

8 |

9 | Your password has been reset. Please click here to log in. 10 |

11 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/ResetPasswordConfirmation.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Authorization; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace CoreWiki.Areas.Identity.Pages.Account 9 | { 10 | [AllowAnonymous] 11 | public class ResetPasswordConfirmationModel : PageModel 12 | { 13 | public void OnGet() 14 | { 15 | 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Account/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using CoreWiki.Areas.Identity.Pages.Account -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore.Mvc; 7 | using Microsoft.AspNetCore.Mvc.RazorPages; 8 | using Microsoft.AspNetCore.Authorization; 9 | using Microsoft.AspNetCore.Identity; 10 | 11 | namespace CoreWiki.Areas.Identity.Pages 12 | { 13 | [AllowAnonymous] 14 | public class ErrorModel : PageModel 15 | { 16 | public string RequestId { get; set; } 17 | 18 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 19 | 20 | [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] 21 | public void OnGet() 22 | { 23 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/UserAdmin/Roles.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model RolesModel 3 | @{ 4 | } 5 | 6 |

Add Role

7 |
8 |
Role name:
9 | 10 |
11 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/UserAdmin/Roles.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Identity; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.AspNetCore.Mvc.RazorPages; 7 | 8 | namespace CoreWiki.Areas.Identity.Pages.UserAdmin 9 | { 10 | public class RolesModel : PageModel 11 | { 12 | [BindProperty] 13 | [Required] 14 | public string RoleName { get; set; } 15 | private readonly RoleManager RoleManager; 16 | 17 | public RolesModel(RoleManager roleManager) 18 | { 19 | RoleManager = roleManager; 20 | } 21 | 22 | 23 | public void OnGet() 24 | { 25 | } 26 | 27 | public async Task OnPostAsync() 28 | { 29 | if (!ModelState.IsValid) 30 | { 31 | return Page(); 32 | } 33 | 34 | var result = await RoleManager.CreateAsync(new IdentityRole(RoleName)); 35 | if (result.Errors.Any()) 36 | { 37 | foreach (var error in result.Errors) 38 | { 39 | ModelState.AddModelError(error.Code, error.Description); 40 | } 41 | return Page(); 42 | } 43 | return RedirectToPage("Index"); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using CoreWiki.Areas.Identity 3 | @using CoreWiki.Data.EntityFramework.Security 4 | @using Microsoft.AspNetCore.Mvc.Localization 5 | 6 | @inject IViewLocalizer Localizer 7 | 8 | @namespace CoreWiki.Areas.Identity.Pages 9 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 10 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 |  2 | @{ 3 | Layout = "/Pages/Shared/_Layout.cshtml"; 4 | } 5 | -------------------------------------------------------------------------------- /CoreWiki/Areas/Identity/PolicyConstants.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Areas.Identity 2 | { 3 | public static class PolicyConstants 4 | { 5 | public const string CanDeleteArticles = nameof(CanDeleteArticles); 6 | public const string CanCreateComments = nameof(CanCreateComments); 7 | public const string CanWriteArticles = nameof(CanWriteArticles); 8 | public const string CanEditArticles = nameof(CanEditArticles); 9 | public const string CanManageRoles = nameof(CanManageRoles); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Settings/AppSettings.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreWiki.Notifications.Abstractions.Configuration; 3 | 4 | namespace CoreWiki.Configuration.Settings 5 | { 6 | public class AppSettings 7 | { 8 | 9 | public Uri Url { get; set; } 10 | public Connectionstrings ConnectionStrings { get; set; } 11 | public Comments Comments { get; set; } 12 | public EmailNotifications EmailNotifications { get; set; } 13 | public CspSettings CspSettings { get; set; } 14 | 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Settings/Comments.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Configuration.Settings 2 | { 3 | public class Comments 4 | { 5 | public CommentsEngine Engine { get; set; } 6 | public Disqus Disqus { get; set; } 7 | public bool IsEngineLocal => Engine == CommentsEngine.Local; 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Settings/CommentsEngine.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Configuration.Settings 2 | { 3 | public enum CommentsEngine 4 | { 5 | Local = 0, 6 | Disqus = 1 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Settings/Connectionstrings.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Configuration.Settings 2 | { 3 | public class Connectionstrings 4 | { 5 | public string CoreWikiIdentityContextConnection { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Settings/CspSettings.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Configuration.Settings 2 | { 3 | public class CspSettings 4 | { 5 | public string[] ImageSources { get; set; } 6 | public string[] StyleSources { get; set; } 7 | public string[] ScriptSources { get; set; } 8 | public string[] FontSources { get; set; } 9 | public string[] FormActions { get; set; } 10 | public string[] FrameAncestors { get; set; } 11 | public string[] ReportUris { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Settings/Disqus.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.Configuration.Settings 2 | { 3 | public class Disqus 4 | { 5 | public string ShortName { get; set; } 6 | 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureApplicationLayer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using CoreWiki.Application.Articles.Managing; 6 | using CoreWiki.Application.Articles.Managing.Impl; 7 | using CoreWiki.Application.Articles.Reading; 8 | using CoreWiki.Application.Articles.Reading.Impl; 9 | using Microsoft.Extensions.DependencyInjection; 10 | 11 | namespace CoreWiki.Configuration.Startup 12 | { 13 | public static class ConfigureApplicationLayer 14 | { 15 | public static IServiceCollection ConfigureApplicationServices(this IServiceCollection services) 16 | { 17 | services.AddTransient(); 18 | services.AddTransient(); 19 | return services; 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureAutomapperServices.cs: -------------------------------------------------------------------------------- 1 | using AutoMapper; 2 | using CoreWiki.Application.Articles.Managing; 3 | using CoreWiki.Application.Articles.Reading; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace CoreWiki.Configuration.Startup 7 | { 8 | public static class ConfigureAutomapperServices 9 | { 10 | 11 | public static IMapper ConfigureAutomapper(this IServiceCollection services) { 12 | 13 | var config = new MapperConfiguration(cfg => { 14 | cfg.AddProfile(); 15 | cfg.AddProfile(); 16 | cfg.AddProfile(); 17 | }); 18 | 19 | config.AssertConfigurationIsValid(); 20 | var mapper = config.CreateMapper(); 21 | 22 | services?.AddSingleton(mapper); 23 | 24 | return mapper; 25 | 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureDatabase.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Data.EntityFramework; 2 | using CoreWiki.Data.EntityFramework.Security; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.Extensions.Configuration; 5 | using Microsoft.Extensions.DependencyInjection; 6 | 7 | namespace CoreWiki.Configuration.Startup 8 | { 9 | public static partial class ConfigurationExtensions 10 | { 11 | public static IServiceCollection ConfigureDatabase(this IServiceCollection services, IConfiguration config) 12 | { 13 | 14 | // Exit now if we don't have a data configuration 15 | if (string.IsNullOrEmpty(config["DataProvider"])) return services; 16 | 17 | services.AddRepositories(config); 18 | return services; 19 | } 20 | 21 | /// 22 | /// Initialize the database with appropriate schema and content 23 | /// 24 | /// 25 | /// 26 | /// 27 | public static IApplicationBuilder InitializeData(this IApplicationBuilder app, IConfiguration config) 28 | { 29 | 30 | // Exit now if we don't have a data configuration 31 | if (string.IsNullOrEmpty(config["DataProvider"])) return app; 32 | 33 | var scope = app.ApplicationServices.CreateScope(); 34 | 35 | var identityContext = scope.SeedData() 36 | .ServiceProvider.GetService(); 37 | CoreWikiIdentityContext.SeedData(identityContext); 38 | 39 | return app; 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureExceptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | 4 | namespace CoreWiki.Configuration.Startup 5 | { 6 | public static partial class ConfigurationExtensions 7 | { 8 | public static IApplicationBuilder ConfigureExceptions(this IApplicationBuilder app, IHostingEnvironment env) 9 | { 10 | if (env.IsDevelopment()) 11 | { 12 | app.UseDeveloperExceptionPage(); 13 | app.UseDatabaseErrorPage(); 14 | } 15 | else 16 | { 17 | app.UseExceptionHandler("/Error"); 18 | } 19 | 20 | return app; 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureHttpClients.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Areas.Identity.Services; 2 | using Microsoft.Extensions.DependencyInjection; 3 | 4 | namespace CoreWiki.Configuration.Startup 5 | { 6 | public static partial class ConfigurationExtensions 7 | { 8 | public static IServiceCollection ConfigureHttpClients(this IServiceCollection services) 9 | { 10 | services.AddHttpClient(); 11 | return services; 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureLocalisation.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Localization; 3 | using Microsoft.Extensions.DependencyInjection; 4 | 5 | namespace CoreWiki.Configuration.Startup 6 | { 7 | public static partial class ConfigurationExtensions 8 | { 9 | public static IServiceCollection ConfigureLocalisation(this IServiceCollection services) 10 | { 11 | services.AddLocalization(options => options.ResourcesPath = "Globalization"); 12 | return services; 13 | } 14 | 15 | public static IApplicationBuilder ConfigureLocalisation(this IApplicationBuilder app) 16 | { 17 | app.UseRequestLocalization(new RequestLocalizationOptions 18 | { 19 | DefaultRequestCulture = new RequestCulture("en-US"), 20 | }); 21 | 22 | return app; 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureMediator.cs: -------------------------------------------------------------------------------- 1 | using MediatR; 2 | using MediatR.Pipeline; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace CoreWiki.Configuration.Startup 10 | { 11 | public static class ConfigureMediator 12 | { 13 | public static IServiceCollection AddMediator(this IServiceCollection services) 14 | { 15 | services.AddMediatR(); 16 | return services; 17 | } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureRSSFeed.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using CoreWiki.Configuration.Settings; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Options; 6 | using Snickler.RSSCore.Extensions; 7 | using Snickler.RSSCore.Models; 8 | 9 | namespace CoreWiki.Configuration.Startup 10 | { 11 | public static partial class ConfigurationExtensions 12 | { 13 | public static IApplicationBuilder ConfigureRSSFeed(this IApplicationBuilder app, IOptionsSnapshot settings) 14 | { 15 | app.UseRSSFeed("/feed", new RSSFeedOptions 16 | { 17 | Title = "CoreWiki RSS Feed", 18 | Copyright = DateTime.UtcNow.Year.ToString(), 19 | Description = "RSS Feed for CoreWiki", 20 | Url = settings.Value.Url 21 | }); 22 | 23 | return app; 24 | } 25 | 26 | public static IServiceCollection ConfigureRSSFeed(this IServiceCollection services) 27 | { 28 | services.AddRSSFeed(); 29 | return services; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureRouting.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Areas.Identity; 2 | using Microsoft.AspNetCore.Builder; 3 | using Microsoft.AspNetCore.Mvc; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace CoreWiki.Configuration.Startup 7 | { 8 | public static partial class ConfigurationExtensions 9 | { 10 | public static IServiceCollection ConfigureRouting(this IServiceCollection services) 11 | { 12 | services.AddRouting(options => options.LowercaseUrls = true); 13 | 14 | services.AddMvc(options => 15 | { 16 | options.Filters.Add(new AutoValidateAntiforgeryTokenAttribute()); 17 | }) 18 | .AddViewLocalization() 19 | .AddDataAnnotationsLocalization() 20 | .AddRazorPagesOptions(options => 21 | { 22 | options.Conventions.AddPageRoute("/Edit", "/{Slug}/Edit"); 23 | options.Conventions.AddPageRoute("/Delete", "{Slug}/Delete"); 24 | options.Conventions.AddPageRoute("/Details", "/"); 25 | // options.Conventions.AddPageRoute("/Details", "/wiki/{Slug?}"); 26 | options.Conventions.AddPageRoute("/Details", @"Index"); 27 | options.Conventions.AddPageRoute("/Create", "{Slug?}/Create"); 28 | options.Conventions.AddPageRoute("/History", "{Slug?}/History"); 29 | options.Conventions.AuthorizeAreaFolder("Identity", "/UserAdmin", PolicyConstants.CanManageRoles); 30 | options.Conventions.AddPageRoute("/Details", "{Slug?}"); 31 | }) 32 | .SetCompatibilityVersion(CompatibilityVersion.Version_2_1); 33 | 34 | return services; 35 | } 36 | 37 | public static IApplicationBuilder ConfigureRouting(this IApplicationBuilder app) 38 | { 39 | app.UseStaticFiles(); 40 | return app; 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureScopedServices.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Application.Articles.Search; 2 | using CoreWiki.Application.Articles.Search.Impl; 3 | using CoreWiki.Notifications; 4 | using Microsoft.AspNetCore.Http; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using NodaTime; 8 | using WebEssentials.AspNetCore.Pwa; 9 | 10 | namespace CoreWiki.Configuration.Startup 11 | { 12 | public static partial class ConfigurationExtensions 13 | { 14 | public static IServiceCollection ConfigureScopedServices(this IServiceCollection services, IConfiguration configuration) 15 | { 16 | services.AddSingleton(SystemClock.Instance); 17 | 18 | services.AddHttpContextAccessor(); 19 | services.AddSingleton(); 20 | 21 | services.AddEmailNotifications(configuration); 22 | services.AddScoped(); 23 | 24 | services.AddProgressiveWebApp(new PwaOptions { 25 | EnableCspNonce = true, 26 | RegisterServiceWorker = false 27 | }); 28 | 29 | return services; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureSecurityAndAuthentication.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using CoreWiki.Data.EntityFramework.Security; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.AspNetCore.Hosting; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.AspNetCore.Identity; 7 | using Microsoft.Extensions.DependencyInjection; 8 | 9 | namespace CoreWiki.Configuration.Startup 10 | { 11 | public static partial class ConfigurationExtensions 12 | { 13 | public static IServiceCollection ConfigureSecurityAndAuthentication(this IServiceCollection services) 14 | { 15 | services.Configure(options => 16 | { 17 | // This lambda determines whether user consent for non-essential cookies is needed for a given request. 18 | options.CheckConsentNeeded = context => true; 19 | options.MinimumSameSitePolicy = SameSiteMode.None; 20 | }); 21 | return services; 22 | } 23 | 24 | public static IApplicationBuilder ConfigureSecurityHeaders(this IApplicationBuilder app, IHostingEnvironment env) 25 | { 26 | if (!env.IsDevelopment()) 27 | { 28 | app.UseHsts(options => options.MaxAge(days: 365).IncludeSubdomains()); 29 | } 30 | app.UseXContentTypeOptions(); 31 | app.UseReferrerPolicy(options => options.NoReferrer()); 32 | app.UseXXssProtection(options => options.EnabledWithBlockMode()); 33 | app.UseXfo(options => options.Deny()); 34 | app.UseHttpsRedirection(); 35 | return app; 36 | } 37 | 38 | public static async Task ConfigureAuthentication(this IApplicationBuilder app, UserManager userManager, RoleManager roleManager) 39 | { 40 | app.UseCookiePolicy(); 41 | app.UseAuthentication(); 42 | return app; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CoreWiki/Configuration/Startup/ConfigureTelemetry.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.Helpers; 2 | using Microsoft.ApplicationInsights.Extensibility; 3 | using Microsoft.AspNetCore.Builder; 4 | using Microsoft.Extensions.DependencyInjection; 5 | 6 | namespace CoreWiki.Configuration.Startup 7 | { 8 | public static partial class ConfigurationExtensions 9 | { 10 | public static IApplicationBuilder ConfigureTelemetry(this IApplicationBuilder app) 11 | { 12 | var initializer = new ArticleNotFoundInitializer(); 13 | 14 | var configuration = app.ApplicationServices.GetService(); 15 | configuration.TelemetryInitializers.Add(initializer); 16 | 17 | return app; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CoreWiki/Helpers/AriaHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.Rendering; 2 | 3 | namespace CoreWiki.Helpers 4 | { 5 | public static class AriaHelper 6 | { 7 | public static void AddAriaSpans(this TagBuilder tagBuilder, string screenReaderText, string ariaHiddenText) 8 | { 9 | tagBuilder.Attributes.Add("aria-label", screenReaderText); 10 | 11 | var visibleTextTag = new TagBuilder("span"); 12 | visibleTextTag.Attributes.Add("aria-hidden", "true"); 13 | visibleTextTag.InnerHtml.Append(ariaHiddenText); 14 | tagBuilder.InnerHtml.AppendHtml(visibleTextTag); 15 | 16 | var screenReaderTag = new TagBuilder("span"); 17 | screenReaderTag.AddCssClass("sr-only"); 18 | screenReaderTag.InnerHtml.Append(screenReaderText); 19 | tagBuilder.InnerHtml.AppendHtml(screenReaderTag); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki/Helpers/ArticleNotFoundInitializer.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights.Channel; 2 | using Microsoft.ApplicationInsights.DataContracts; 3 | using Microsoft.ApplicationInsights.Extensibility; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Net; 8 | using System.Threading.Tasks; 9 | 10 | namespace CoreWiki.Helpers 11 | { 12 | public class ArticleNotFoundInitializer : ITelemetryInitializer 13 | { 14 | public void Initialize(ITelemetry telemetry) 15 | { 16 | var request = telemetry as RequestTelemetry; 17 | 18 | if (request != null && request.ResponseCode.Equals(((int)HttpStatusCode.NotFound).ToString(), StringComparison.OrdinalIgnoreCase)) 19 | { 20 | telemetry.Context.Properties["Missing Document"] = request.Url.Segments.LastOrDefault(); 21 | } 22 | } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /CoreWiki/Helpers/ArticleNotFoundResult.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.ApplicationInsights; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Collections.Generic; 4 | 5 | namespace CoreWiki.Helpers 6 | { 7 | public class ArticleNotFoundResult : ViewResult 8 | { 9 | private static TelemetryClient telemetry = new TelemetryClient(); 10 | public ArticleNotFoundResult(string name = "") 11 | { 12 | 13 | if (!string.IsNullOrEmpty(name)) // do not track if no name was submitted 14 | { 15 | var documentDictionary = new Dictionary 16 | { 17 | {"Document", name } 18 | }; 19 | 20 | telemetry.TrackEvent("Missing Document", documentDictionary); 21 | 22 | } 23 | ViewName = "ArticleNotFound"; 24 | StatusCode = 404; 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CoreWiki/Helpers/NewCommentNotificationHandler.cs: -------------------------------------------------------------------------------- 1 | using System.Threading; 2 | using System.Threading.Tasks; 3 | using CoreWiki.Application.Articles.Reading.Events; 4 | using CoreWiki.Data.EntityFramework.Security; 5 | using CoreWiki.Notifications.Abstractions.Notifications; 6 | using MediatR; 7 | using Microsoft.AspNetCore.Identity; 8 | 9 | namespace CoreWiki.Helpers 10 | { 11 | // TODO: Move this to a more suitable location in the project: CoreWiki.Notifications? 12 | public class NewCommentNotificationHandler : INotificationHandler 13 | { 14 | private readonly UserManager _userManager; 15 | private readonly INotificationService _notificationService; 16 | 17 | public NewCommentNotificationHandler(UserManager userManager, INotificationService notificationService) 18 | { 19 | _userManager = userManager; 20 | _notificationService = notificationService; 21 | } 22 | 23 | public async Task Handle(CommentPostedNotification notification, CancellationToken cancellationToken) 24 | { 25 | var author = await _userManager.FindByIdAsync(notification.Article.AuthorId.ToString()); 26 | if (author != null) 27 | { 28 | await _notificationService.SendNewCommentEmail(author.Email, author.UserName, notification.Comment.DisplayName, notification.Article.Topic, notification.Article.Slug, () => author.CanNotify); 29 | } 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /CoreWiki/Pages/ArticleNotFound.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = Localizer["ArticleNotFound"]; 3 | } 4 |

@Localizer["ArticleNotFound"]

5 | 6 |
    7 |
  • @Localizer["ListItem1"]
  • 8 |
  • @Localizer["ListItem2"]
  • 9 | 10 |
  • @Localizer["ListItem3"]
  • 11 |
12 | 13 | 16 |
17 | @Localizer["GoHome"] 18 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Components/CreateComments/CreateComments.cshtml: -------------------------------------------------------------------------------- 1 | @model ViewModels.Comment 2 |
3 |
4 |
5 |
@Localizer["NewComment"]
6 |
7 | 8 |
9 |
10 |
11 |
12 | 13 | 14 | 15 |
16 |
17 |
18 |
19 | 20 | 21 | 22 |
23 |
24 |
25 |
26 |
27 |
28 | 29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 | 37 |
38 |
39 |
40 |
41 |
42 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Components/CreateComments/CreateComments.cshtml.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.ViewModels; 2 | using Microsoft.AspNetCore.Mvc; 3 | 4 | namespace CoreWiki.Pages.Components.CreateComments 5 | { 6 | [ViewComponent(Name = "CreateComments")] 7 | public class CreateComments : ViewComponent 8 | { 9 | public IViewComponentResult Invoke(Comment comment) 10 | { 11 | return View("CreateComments", comment); 12 | } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Components/ListComments/ListComments.cshtml: -------------------------------------------------------------------------------- 1 | @model ICollection 2 |
3 |

@Localizer["CommentsList"]

4 | 5 | @if (@Model.Count == 0) 6 | { 7 |
8 |
9 |
10 |
@Localizer["NoComments"]
11 |
12 |
13 |
14 | } 15 | else 16 | { 17 | foreach (var item in Model.OrderByDescending(x => x.Submitted)) 18 | { 19 |
20 |
21 |
22 |
23 |
24 | Avatar 25 |
26 |
27 |
28 |
29 | 30 | @item.DisplayName 31 | 32 |
33 | Commented on @item.Submitted 34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 | 42 |
43 |
44 | } 45 | } 46 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Components/ListComments/ListComments.cshtml.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.ViewModels; 2 | using Microsoft.AspNetCore.Mvc; 3 | using System.Collections.Generic; 4 | 5 | namespace CoreWiki.Pages.Components.ListComments 6 | { 7 | [ViewComponent(Name = "ListComments")] 8 | public class ListComments : ViewComponent 9 | { 10 | 11 | 12 | public ListComments() 13 | { 14 | 15 | } 16 | 17 | public IViewComponentResult Invoke(ICollection comments) 18 | { 19 | return View("ListComments", comments); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Delete.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model CoreWiki.Pages.DeleteModel 3 | 4 | @{ 5 | ViewData["Title"] = Localizer["Delete"]; 6 | } 7 | 8 |

@Localizer["Delete"]

9 | 10 |

@Localizer["DeleteQuestion"]

11 |
12 |
13 |
14 |
15 |
16 | @Html.DisplayNameFor(model => model.Article.Topic) 17 |
18 |
19 | @Html.DisplayFor(model => model.Article.Topic) 20 |
21 |
22 | @Html.DisplayNameFor(model => model.Article.Published) 23 |
24 |
25 | @Html.DisplayFor(model => model.Article.Published) 26 |
27 |
28 | @Html.DisplayNameFor(model => model.Article.Content) 29 |
30 |
31 | @Html.DisplayFor(model => model.Article.Content) 32 |
33 |
34 | 35 |
36 | 37 |
38 | 41 |
42 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Error.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model ErrorModel 3 | @{ 4 | ViewData["Title"] = "Error"; 5 | } 6 | 7 |

Error.

8 |

An error occurred while processing your request.

9 | 10 | @if (Model.ShowRequestId) 11 | { 12 |

13 | Request ID: @Model.RequestId 14 |

15 | } 16 | 17 |

Development Mode

18 |

19 | Swapping to Development environment will display more detailed information about the error that occurred. 20 |

21 |

22 | Development environment should not be enabled in deployed applications, as it can result in sensitive information from exceptions being displayed to end users. For local debugging, development environment can be enabled by setting the ASPNETCORE_ENVIRONMENT environment variable to Development, and restarting the application. 23 |

24 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Error.cshtml.cs: -------------------------------------------------------------------------------- 1 | using System.Diagnostics; 2 | using Microsoft.AspNetCore.Mvc.RazorPages; 3 | 4 | namespace CoreWiki.Pages 5 | { 6 | public class ErrorModel : PageModel 7 | { 8 | public string RequestId { get; set; } 9 | 10 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 11 | 12 | public void OnGet() 13 | { 14 | RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier; 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CoreWiki/Pages/HttpErrors/404.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @{ 3 | var title = "Page not found"; 4 | ViewData["Title"] = title; 5 | } 6 |

@title

7 | 8 | Go to Homepage 9 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @page 2 | @model PrivacyModel 3 | @{ 4 | ViewData["Title"] = Localizer["PrivacyPolicy"]; 5 | } 6 |

@ViewData["Title"]

7 | 8 |

@Localizer["PrivacyPolicyText"]

9 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Privacy.cshtml.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc.RazorPages; 2 | 3 | namespace CoreWiki.Pages 4 | { 5 | public class PrivacyModel : PageModel 6 | { 7 | public void OnGet() 8 | { 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Search.cshtml: -------------------------------------------------------------------------------- 1 | @page "{handler?}" 2 | @using System.Collections.Generic 3 | @using Microsoft.AspNetCore.Mvc.Rendering 4 | @using Humanizer; 5 | @model SearchModel 6 | @{ 7 | ViewData["Title"] = Model.RequestedPage == "search" ? Localizer["Search"] : Localizer["LatestChanges"]; 8 | var searchResult = Model.SearchResult; 9 | var urlParams = new Dictionary 10 | { 11 | {"Query", searchResult?.Query ?? ""} 12 | 13 | }; 14 | } 15 |

@ViewData["Title"]

16 | 17 |

18 | @Localizer["NewArticle"] 19 |

20 | 21 | @if (Model.RequestedPage == "search") 22 | { 23 |
24 |
25 |
26 | 27 |
28 | 29 |
30 |
31 |
32 |
33 | } 34 | 35 | @if (searchResult != null) 36 | { 37 |
38 | @("result".ToQuantity(searchResult.TotalResults)) 39 | found for @searchResult.Query 40 |
41 | 42 | if (searchResult.TotalPages > 1) 43 | { 44 | 50 | 51 | } 52 | 53 |
54 | @foreach (var article in searchResult.Results) 55 | { 56 | @await Html.PartialAsync("_ArticleRow", article) 57 | } 58 |
59 | 60 | } 61 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Search.cshtml.cs: -------------------------------------------------------------------------------- 1 | using CoreWiki.ViewModels; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.AspNetCore.Mvc.RazorPages; 4 | using System.Threading.Tasks; 5 | using CoreWiki.Application.Articles.Reading.Queries; 6 | using CoreWiki.Application.Articles.Search.Dto; 7 | using CoreWiki.Application.Articles.Search.Queries; 8 | using MediatR; 9 | using AutoMapper; 10 | 11 | namespace CoreWiki.Pages 12 | { 13 | public class SearchModel : PageModel 14 | { 15 | private readonly IMediator _mediator; 16 | private readonly IMapper _mapper; 17 | public SearchResultDto SearchResult; 18 | private const int ResultsPerPage = 10; 19 | 20 | public SearchModel(IMediator mediator, IMapper mapper) 21 | { 22 | _mediator = mediator; 23 | _mapper = mapper; 24 | } 25 | 26 | public string RequestedPage => Request.Path.Value.ToLowerInvariant().Substring(1); 27 | 28 | public async Task OnGetAsync([FromQuery(Name = "Query")]string query = "", [FromQuery(Name ="PageNumber")]int pageNumber = 1) 29 | { 30 | if (string.IsNullOrEmpty(query)) 31 | { 32 | return Page(); 33 | } 34 | var qry = new SearchArticlesQuery(query, pageNumber, ResultsPerPage); 35 | var result = await _mediator.Send(qry); 36 | 37 | SearchResult = _mapper.Map>(result); 38 | 39 | return Page(); 40 | } 41 | 42 | public async Task OnGetLatestChangesAsync() 43 | { 44 | var qry = new GetLatestArticlesQuery(10); 45 | var results = await _mediator.Send(qry); 46 | 47 | SearchResult = _mapper.Map>(results); 48 | SearchResult.ResultsPerPage = 11; 49 | SearchResult.CurrentPage = 1; 50 | 51 | return Page(); 52 | } 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Shared/_CommentsPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using CoreWiki.Configuration.Settings 2 | @using Microsoft.AspNetCore.Authorization 3 | @model DetailsModel 4 | @inject IOptionsSnapshot settings 5 | @inject IAuthorizationService AuthorizationService 6 | 7 | @if (settings.Value.Comments.IsEngineLocal) 8 | { 9 | 10 | if ((await AuthorizationService.AuthorizeAsync(User, null, PolicyConstants.CanCreateComments)).Succeeded) { 11 | @await Component.InvokeAsync("CreateComments", new Comment { ArticleId = Model.Article.Id }) 12 | } 13 | 14 | @await Component.InvokeAsync("ListComments", @Model.Article.Comments) 15 | 16 | } 17 | else 18 | { 19 | 20 | } 21 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Shared/_CookieConsentPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Http.Features 2 | 3 | @{ 4 | var consentFeature = Context.Features.Get(); 5 | var showBanner = !consentFeature?.CanTrack ?? false; 6 | var cookieString = consentFeature?.CreateConsentCookie(); 7 | } 8 | 9 | @if (showBanner) 10 | { 11 | 17 | 25 | } 26 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Shared/_DiffLine.cshtml: -------------------------------------------------------------------------------- 1 | @model DiffPlex.DiffBuilder.Model.DiffPiece 2 | @using DiffPlex.DiffBuilder.Model 3 | 4 | @if (!string.IsNullOrEmpty(Model.Text)) 5 | { 6 | string spaceValue = "\u00B7"; 7 | string tabValue = "\u00B7\u00B7"; 8 | if (Model.Type == ChangeType.Deleted || Model.Type == ChangeType.Inserted || Model.Type == ChangeType.Unchanged) 9 | { 10 | @Html.Raw(Html.Encode(Model.Text).Replace(" ", spaceValue).Replace("\t", tabValue)) 11 | } 12 | else if (Model.Type == ChangeType.Modified) 13 | { 14 | foreach (var character in Model.SubPieces) 15 | { 16 | if (character.Type == ChangeType.Imaginary) { continue; } 17 | 18 | @character.Text.Replace(" ", spaceValue.ToString()) 19 | } 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Shared/_DiffPane.cshtml: -------------------------------------------------------------------------------- 1 | @model DiffPlex.DiffBuilder.Model.DiffPaneModel 2 | 3 | 4 |
5 | 6 | @foreach (var diffLine in Model.Lines) 7 | { 8 | 9 | 10 | 13 | 18 | 19 | } 20 |
11 | @Html.Raw(diffLine.Position.HasValue ? diffLine.Position.ToString() : " ") 12 | 14 | 15 | @await Html.PartialAsync("_DiffLine", diffLine) 16 | 17 |
21 |
22 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Shared/_DisqusComments.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | @{ 3 | var src = $"https://{Model}.disqus.com/embed.js"; 4 | } 5 |
6 |

Disqus

7 |
8 |
9 | 27 | 28 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Shared/_LoginPartial.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | 3 | @inject SignInManager SignInManager 4 | @inject UserManager UserManager 5 | 6 | @if (SignInManager.IsSignedIn(User)) 7 | { 8 |
  • 9 | Hello @UserManager.GetUserName(User) 10 |
  • 11 |
  • 12 | 15 |
  • 16 | } 17 | else 18 | { 19 | 20 | 21 | } 22 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Shared/_ThemePartial.cshtml: -------------------------------------------------------------------------------- 1 |  8 | 37 | 38 | -------------------------------------------------------------------------------- /CoreWiki/Pages/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 12 | 18 | 19 | -------------------------------------------------------------------------------- /CoreWiki/Pages/_ArticleRow.cshtml: -------------------------------------------------------------------------------- 1 | @using CoreWiki.Application.Common 2 | @using Microsoft.AspNetCore.Authorization 3 | @inject IAuthorizationService AuthorizationService 4 | @model ArticleSummary 5 | 6 | 7 |
    8 |
    9 |
    10 |

    11 | @Model.Topic 12 |

    13 |
    14 |
    15 |
    @Model.Published
    16 |
    @Localizer["ViewCount"]: @Model.ViewCount
    17 | 18 | @Localizer["Edit"] 19 | @if (!Model.IsHomePage) 20 | { 21 | @Localizer["Delete"] 22 | } 23 |
    24 |
    25 | 26 |
    27 | -------------------------------------------------------------------------------- /CoreWiki/Pages/_EditorScript.cshtml: -------------------------------------------------------------------------------- 1 |  2 | 54 | 55 | -------------------------------------------------------------------------------- /CoreWiki/Pages/_EditorStyle.cshtml: -------------------------------------------------------------------------------- 1 |  2 | -------------------------------------------------------------------------------- /CoreWiki/Pages/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Identity 2 | @using CoreWiki.Areas.Identity 3 | @using CoreWiki.Data.EntityFramework.Security 4 | @using CoreWiki 5 | @using CoreWiki.Helpers 6 | @using Microsoft.Extensions.Options; 7 | @using CoreWiki.Configuration.Settings; 8 | @using Microsoft.AspNetCore.Mvc.Localization 9 | @using CoreWiki.ViewModels 10 | @inject IViewLocalizer Localizer 11 | 12 | 13 | @namespace CoreWiki.Pages 14 | 15 | 16 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 17 | 18 | @addTagHelper *, NWebsec.AspNetCore.Mvc.TagHelpers 19 | 20 | @addTagHelper *, CoreWiki 21 | -------------------------------------------------------------------------------- /CoreWiki/Pages/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /CoreWiki/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using Microsoft.AspNetCore; 7 | using Microsoft.AspNetCore.Hosting; 8 | using Microsoft.Extensions.Configuration; 9 | using Microsoft.Extensions.Logging; 10 | 11 | namespace CoreWiki 12 | { 13 | public class Program 14 | { 15 | 16 | private static IWebHost _Host; 17 | private static bool _Restart = true; 18 | 19 | public static void Main(string[] args) 20 | { 21 | 22 | while (_Restart) { 23 | 24 | _Restart = false; 25 | _Host = BuildWebHost(args); 26 | _Host.Run(); 27 | 28 | } 29 | 30 | } 31 | 32 | public static Task Restart() { 33 | 34 | _Restart = true; 35 | return _Host.StopAsync(); 36 | 37 | } 38 | 39 | public static IWebHost BuildWebHost(string[] args) => 40 | WebHost.CreateDefaultBuilder(args) 41 | .UseApplicationInsights() 42 | .UseStartup() 43 | .UseKestrel(options => 44 | { 45 | options.AddServerHeader = false; 46 | }) 47 | .Build(); 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CoreWiki/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:5000/", 7 | "sslPort": 44333 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "CoreWiki": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:8081;http://localhost:8080" 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /CoreWiki/RSSProvider.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore; 2 | using Microsoft.Extensions.Options; 3 | using Snickler.RSSCore.Models; 4 | using Snickler.RSSCore.Providers; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | using CoreWiki.Application.Articles.Reading.Queries; 10 | using CoreWiki.Configuration.Settings; 11 | using MediatR; 12 | 13 | namespace CoreWiki 14 | { 15 | public class RSSProvider : IRSSProvider 16 | { 17 | private readonly IMediator _mediator; 18 | private readonly Uri baseURL; 19 | 20 | public RSSProvider(IMediator mediator, IOptionsSnapshot settings) 21 | { 22 | _mediator = mediator; 23 | baseURL = settings.Value.Url; 24 | } 25 | 26 | public async Task> RetrieveSyndicationItems() 27 | { 28 | var articles = await _mediator.Send(new GetLatestArticlesQuery(10)); 29 | return articles.Select(rssItem => 30 | { 31 | var absoluteURL = new Uri(baseURL, $"/{rssItem.Slug}"); 32 | 33 | var wikiItem = new RSSItem 34 | { 35 | Content = rssItem.Content, 36 | PermaLink = absoluteURL, 37 | LinkUri = absoluteURL, 38 | PublishDate = rssItem.Published.ToDateTimeUtc(), 39 | LastUpdated = rssItem.Published.ToDateTimeUtc(), 40 | Title = rssItem.Topic, 41 | }; 42 | 43 | wikiItem.Authors.Add("Jeff Fritz"); // TODO: Grab from user who saved record... not this guy 44 | return wikiItem; 45 | }).ToList(); 46 | } 47 | 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/ArticleCreate.cs: -------------------------------------------------------------------------------- 1 | using System.ComponentModel.DataAnnotations; 2 | 3 | namespace CoreWiki.ViewModels 4 | { 5 | public class ArticleCreate 6 | { 7 | 8 | [Required] 9 | public string Topic { get; set; } 10 | 11 | public string Content { get; set; } 12 | } 13 | } 14 | 15 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/ArticleCreateFromLink.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.ViewModels 2 | { 3 | public class ArticleCreateFromLink 4 | { 5 | public string Slug { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/ArticleDelete.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | 3 | namespace CoreWiki.ViewModels 4 | { 5 | public class ArticleDelete 6 | { 7 | public string Topic { get; set; } 8 | public string Content { get; set; } 9 | public Instant Published { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/ArticleDetails.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using NodaTime; 4 | 5 | namespace CoreWiki.ViewModels 6 | { 7 | public class ArticleDetails 8 | { 9 | public bool IsHomePage { get; set; } 10 | public int Id { get; set; } 11 | public Guid AuthorId { get; set; } 12 | public string Slug { get; set; } 13 | public string Topic { get; set; } 14 | public string Content { get; set; } 15 | public Instant Published { get; set; } 16 | public int ViewCount { get; set; } 17 | public int Version { get; set; } 18 | 19 | public IReadOnlyCollection Comments { get; set; } 20 | 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/ArticleEdit.cs: -------------------------------------------------------------------------------- 1 | namespace CoreWiki.ViewModels 2 | { 3 | public class ArticleEdit 4 | { 5 | public int Id { get; set; } 6 | public string Topic { get; set; } 7 | 8 | public string Slug { get; set; } 9 | 10 | public string Content { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/ArticleHistory.cs: -------------------------------------------------------------------------------- 1 | using System.Collections.Generic; 2 | using NodaTime; 3 | 4 | namespace CoreWiki.ViewModels 5 | { 6 | public class ArticleHistory 7 | { 8 | public string Topic { get; set; } 9 | public int Version { get; set; } 10 | public string AuthorName { get; set; } 11 | public Instant Published { get; set; } 12 | public IReadOnlyCollection History { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/ArticleHistoryDetail.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | 3 | namespace CoreWiki.ViewModels 4 | { 5 | public class ArticleHistoryDetail 6 | { 7 | public int Version { get; set; } 8 | public string AuthorName { get; set; } 9 | public Instant Published { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/ArticleSummary.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | 3 | namespace CoreWiki.ViewModels 4 | { 5 | public class ArticleSummary 6 | { 7 | public bool IsHomePage { get; set; } 8 | public string Slug { get; set; } 9 | public string Topic { get; set; } 10 | public Instant Published { get; set; } 11 | public int ViewCount { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CoreWiki/ViewModels/Comment.cs: -------------------------------------------------------------------------------- 1 | using NodaTime; 2 | 3 | namespace CoreWiki.ViewModels 4 | { 5 | public class Comment 6 | { 7 | public int ArticleId { get; set; } 8 | public string Email { get; set; } 9 | public string DisplayName { get; set; } 10 | public string Content { get; set; } 11 | public Instant Submitted { get; set; } 12 | 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /CoreWiki/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "IncludeScopes": false, 4 | "LogLevel": { 5 | "Default": "Debug", 6 | "System": "Information", 7 | "Microsoft": "Information" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /CoreWiki/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Url": "https://localhost:64908", 3 | "Logging": { 4 | "IncludeScopes": false, 5 | "LogLevel": { 6 | "Default": "Warning" 7 | } 8 | }, 9 | "DataProvider": "SQLite", 10 | "ConnectionStrings": { 11 | "CoreWikiIdentityContextConnection": "DataSource=./App_Data/wikiIdentity.db", 12 | "CoreWikiData": "DataSource=./App_Data/wikiContent.db" 13 | }, 14 | "Authentication": { 15 | "Microsoft": { 16 | "ApplicationId": "", 17 | "Password": "" 18 | }, 19 | "RequireConfirmedEmail": false 20 | }, 21 | "Comments": { 22 | "Engine": "Local", 23 | "Disqus": { 24 | "ShortName": "" 25 | } 26 | }, 27 | "EmailNotifications": { 28 | "SendGridApiKey": "", 29 | "FromEmailAddress": "noreply@corewiki.jeffreyfritz.com", 30 | "FromName": "No Reply Team" 31 | }, 32 | "CspSettings": { 33 | "ImageSources": [], 34 | "StyleSources": [], 35 | "ScriptSources": [], 36 | "FontSources": [], 37 | "FormActions": [], 38 | "FrameAncestors": [], 39 | "ReportUris": [] 40 | } 41 | } -------------------------------------------------------------------------------- /CoreWiki/bundleconfig.json: -------------------------------------------------------------------------------- 1 | // Configure bundling and minification for the project. 2 | // More info at https://go.microsoft.com/fwlink/?LinkId=808241 3 | [ 4 | { 5 | "outputFileName": "wwwroot/js/site.min.js", 6 | "inputFiles": [ 7 | "wwwroot/js/site.js" 8 | ], 9 | // Optionally specify minification options 10 | "minify": { 11 | "enabled": true, 12 | "renameLocals": true 13 | }, 14 | // Optionally generate .map file 15 | "sourceMap": false 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /CoreWiki/bundleconfig.json.bindings: -------------------------------------------------------------------------------- 1 | produceoutput=true -------------------------------------------------------------------------------- /CoreWiki/compilerconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFile": "wwwroot/lib/popper.js/dist/umd/popper.es5.js", 4 | "inputFile": "wwwroot/lib/popper.js/dist/umd/popper.js" 5 | }, 6 | { 7 | "outputFile": "wwwroot/lib/popper.js/dist/umd/popper-utils.es5.js", 8 | "inputFile": "wwwroot/lib/popper.js/dist/umd/popper-utils.js" 9 | }, 10 | { 11 | "outputFile": "wwwroot/css/siteTheme.css", 12 | "inputFile": "wwwroot/css/siteTheme.scss" 13 | }, 14 | { 15 | "outputFile": "wwwroot/css/themeOuch.css", 16 | "inputFile": "wwwroot/css/themeOuch.scss" 17 | }, 18 | { 19 | "outputFile": "wwwroot/css/themeDark.css", 20 | "inputFile": "wwwroot/css/themeDark.scss" 21 | }, 22 | { 23 | "outputFile": "wwwroot/css/diff.css", 24 | "inputFile": "wwwroot/css/diff.scss" 25 | }, 26 | { 27 | "outputFile": "wwwroot/css/themeRainbowFritz.css", 28 | "inputFile": "wwwroot/css/themeRainbowFritz.scss" 29 | }, 30 | { 31 | "outputFile": "wwwroot/css/themeGhost.css", 32 | "inputFile": "wwwroot/css/themeGhost.scss" 33 | } 34 | ] -------------------------------------------------------------------------------- /CoreWiki/compilerconfig.json.defaults: -------------------------------------------------------------------------------- 1 | { 2 | "compilers": { 3 | "less": { 4 | "autoPrefix": "", 5 | "cssComb": "none", 6 | "ieCompat": true, 7 | "strictMath": false, 8 | "strictUnits": false, 9 | "relativeUrls": true, 10 | "rootPath": "", 11 | "sourceMapRoot": "", 12 | "sourceMapBasePath": "", 13 | "sourceMap": false 14 | }, 15 | "sass": { 16 | "includePath": "", 17 | "indentType": "space", 18 | "indentWidth": 2, 19 | "outputStyle": "nested", 20 | "Precision": 5, 21 | "relativeUrls": true, 22 | "sourceMapRoot": "", 23 | "sourceMap": false 24 | }, 25 | "stylus": { 26 | "sourceMap": false 27 | }, 28 | "babel": { 29 | "sourceMap": false 30 | }, 31 | "coffeescript": { 32 | "bare": false, 33 | "runtimeMode": "node", 34 | "sourceMap": false 35 | } 36 | }, 37 | "minifiers": { 38 | "css": { 39 | "enabled": true, 40 | "termSemicolons": true, 41 | "gzip": false 42 | }, 43 | "javascript": { 44 | "enabled": true, 45 | "termSemicolons": true, 46 | "gzip": false 47 | } 48 | } 49 | } -------------------------------------------------------------------------------- /CoreWiki/package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "corewiki", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@fortawesome/fontawesome-free": { 8 | "version": "5.3.1", 9 | "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.3.1.tgz", 10 | "integrity": "sha512-jt6yi7iZVtkY9Jc6zFo+G2vqL4M81pb3IA3WmnnDt9ci7Asz+mPg4gbZL8pjx0nGFBsG0Bmd7BjU9IQkebqxFA==" 11 | }, 12 | "bootstrap": { 13 | "version": "4.1.3", 14 | "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.1.3.tgz", 15 | "integrity": "sha512-rDFIzgXcof0jDyjNosjv4Sno77X4KuPeFxG2XZZv1/Kc8DRVGVADdoQyyOVDwPqL36DDmtCQbrpMCqvpPLJQ0w==" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /CoreWiki/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "corewiki", 3 | "version": "1.0.0", 4 | "description": "", 5 | "dependencies": { 6 | "bootstrap": "^4.1.3", 7 | "@fortawesome/fontawesome-free": "^5.2.0" 8 | }, 9 | "devDependencies": {}, 10 | "scripts": { 11 | "test": "echo \"Error: no test specified\" && exit 1" 12 | }, 13 | "author": "", 14 | "license": "MIT" 15 | } 16 | -------------------------------------------------------------------------------- /CoreWiki/updateDb.cmd: -------------------------------------------------------------------------------- 1 | dotnet ef database update --context applicationdbcontext --project "../CoreWiki.Data" -------------------------------------------------------------------------------- /CoreWiki/web.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /CoreWiki/wwwroot/css/_custom-styles.scss: -------------------------------------------------------------------------------- 1 | // This file is included in each of the Themes 2 | 3 | // Add additional styles here. 4 | // For example, overwrite certain styles or add new components. 5 | // Tip: You can use bootstrap's mixins here! 6 | 7 | 8 | // Contents normally written in site.css 9 | body { 10 | padding-top: 50px; 11 | padding-bottom: 20px; 12 | } 13 | 14 | /* Wrapping element */ 15 | /* Set some basic padding to keep content from hitting the edges */ 16 | .body-content { 17 | padding-top: 2rem; 18 | padding-right: 15px; 19 | padding-left: 15px; 20 | } 21 | 22 | .pageNumber { 23 | margin: 0px 4px; 24 | } 25 | 26 | button.accept-policy { 27 | font-size: 1rem; 28 | line-height: inherit; 29 | } 30 | -------------------------------------------------------------------------------- /CoreWiki/wwwroot/css/_custom-variables.scss: -------------------------------------------------------------------------------- 1 | // Overwrite Bootstrap's variables here 2 | // You can find them in node_modules/bootstrap/scss/_variables.scss 3 | // Copy the variables you need into this file, don't modify files under node_modules/ 4 | 5 | // Some example variables that you can uncomment: 6 | 7 | // Enabling shadows and gradients 8 | //$enable-shadows: true; 9 | //$enable-gradients: true; 10 | 11 | // Changing the body background and text 12 | //$body-bg: #b6ff00; 13 | //$body-color: #151417; 14 | 15 | // Changing the border radius of buttons 16 | //$border-radius: 15px; 17 | 18 | // Changing the theme colors 19 | //$primary: #860471; 20 | //$secondary: #436296; 21 | //$success: #2bc550; 22 | //$info: #495dff; 23 | //$warning: #ef8143; 24 | //$danger: #ff293a; 25 | //$light: #dfe6ee; 26 | $dark: #061935; 27 | 28 | 29 | // Adding (!) an additional theme color (ex. classes btn-cool, bg-cool) 30 | //$theme-colors: ( 31 | // "cool": #4d3fa3 32 | //); 33 | -------------------------------------------------------------------------------- /CoreWiki/wwwroot/css/diff.min.css: -------------------------------------------------------------------------------- 1 | #diffBar{width:3%;height:100%;float:left;position:relative;background:#ddd;}.diffBarLineLeft,.diffBarLineRight{width:50%;float:left;height:0;cursor:pointer;}.inView{background-image:url("../Content/InView.png");background-repeat:repeat;}#activeBar{position:absolute;top:0;background-color:#69f;opacity:.5;filter:alpha(opacity="50");}#diffBox{margin-left:auto;margin-right:auto;border:solid 2px #000;}#leftPane,#rightPane{float:left;width:50%;}.diffHeader{font-weight:bold;padding:2px 0 2px 10px;background-color:#fff;text-align:center;}.diffPane{margin-right:0;padding:0;overflow:auto;font-family:Courier New;font-size:1em;}.diffTable{width:100%;height:100%;}.line{padding-left:.2em;white-space:nowrap;width:100%;}.piece{font-size:12px;}.lineNumber{padding:0 .3em;background-color:#fff;text-align:right;}.InsertedLine{background-color:#ff0;}.ModifiedLine{background-color:#dcdcff;font-size:0;}.DeletedLine{background-color:#ffc864;}.UnchangedLine{background-color:#fff;}.ImaginaryLine{background-color:#c8c8c8;}.InsertedCharacter{background-color:#ffff96;}.DeletedCharacter{background-color:#c86464;}.clear{clear:both;} -------------------------------------------------------------------------------- /CoreWiki/wwwroot/css/siteTheme.scss: -------------------------------------------------------------------------------- 1 | /*! Site Bootstrap 4 Theme 2 | * 3 | * Built on top of Bootstrap 4 (https://getbootstrap.com) 4 | * Built with the ideas from the Bootstrap Theme Kit by HackerThemes (https://hackerthemes.com) 5 | */ 6 | @import 'custom-variables'; 7 | @import '../../node_modules/bootstrap/scss/bootstrap'; 8 | @import 'custom-styles'; 9 | 10 | -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/51.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/51.jpg -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-114x114.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-114x114.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-120x120.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-120x120.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-128x128.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-144x144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-144x144.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-152x152.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-152x152.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-16x16.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-180x180.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-180x180.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-192x192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-192x192.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-32x32.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-32x32white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-32x32white.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-384x384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-384x384.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-512x512.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-57x57.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-57x57.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-60x60.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-60x60.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-72x72.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-72x72.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-76x76.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-76x76.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/images/icons/icon-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/CoreWiki/wwwroot/images/icons/icon-96x96.png -------------------------------------------------------------------------------- /CoreWiki/wwwroot/js/site.js: -------------------------------------------------------------------------------- 1 | // Write your Javascript code. 2 | (function () { 3 | 4 | var reformatTimeStamps = function () { 5 | 6 | var timeStamps = document.querySelectorAll(".timeStampValue"); 7 | for (var ts of timeStamps) { 8 | 9 | var thisTimeStamp = ts.getAttribute("data-value"); 10 | var date = new Date(thisTimeStamp); 11 | moment.locale(window.navigator.userLanguage || window.navigator.language); 12 | ts.textContent = moment(date).format('LLL'); 13 | 14 | } 15 | 16 | } 17 | 18 | reformatTimeStamps(); 19 | 20 | })(); 21 | 22 | //Prevent duplicate submit 23 | 24 | $("form").submit(function (e) { 25 | if ($(this).valid()) { 26 | if ($(this).attr('attempted') === 'true') { 27 | e.preventDefault(); 28 | } 29 | else { 30 | $(this).attr('attempted', 'true'); 31 | } 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /CoreWiki/wwwroot/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Core Wiki", 3 | "short_name": "CoreWiki", 4 | "description": "An ASP.NET Core Wiki", 5 | "theme_color": "#212529", 6 | "background_color": "#007bff", 7 | "display": "minimal-ui", 8 | "Scope": "/", 9 | "start_url": "/", 10 | "icons": [ 11 | { 12 | "src": "images/icons/icon-72x72.png", 13 | "sizes": "72x72", 14 | "type": "image/png" 15 | }, 16 | { 17 | "src": "images/icons/icon-96x96.png", 18 | "sizes": "96x96", 19 | "type": "image/png" 20 | }, 21 | { 22 | "src": "images/icons/icon-128x128.png", 23 | "sizes": "128x128", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "images/icons/icon-144x144.png", 28 | "sizes": "144x144", 29 | "type": "image/png" 30 | }, 31 | { 32 | "src": "images/icons/icon-152x152.png", 33 | "sizes": "152x152", 34 | "type": "image/png" 35 | }, 36 | { 37 | "src": "images/icons/icon-192x192.png", 38 | "sizes": "192x192", 39 | "type": "image/png" 40 | }, 41 | { 42 | "src": "images/icons/icon-384x384.png", 43 | "sizes": "384x384", 44 | "type": "image/png" 45 | }, 46 | { 47 | "src": "images/icons/icon-512x512.png", 48 | "sizes": "512x512", 49 | "type": "image/png" 50 | } 51 | ] 52 | } 53 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM cakebuild/cake:v0.30.0-2.1-sdk AS builder 2 | 3 | RUN apt-get update -qq \ 4 | && curl -sL https://deb.nodesource.com/setup_9.x | bash - \ 5 | && apt-get install -y nodejs 6 | 7 | ADD . /src 8 | 9 | RUN Cake /src/build.cake --Target=Publish 10 | 11 | FROM microsoft/dotnet:2.1.3-aspnetcore-runtime 12 | 13 | WORKDIR app 14 | 15 | COPY --from=builder /src/output . 16 | 17 | CMD ["dotnet","CoreWiki.dll"] 18 | 19 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Jeffrey T. Fritz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /appveyor.yml: -------------------------------------------------------------------------------- 1 | # Operating system (build VM template) 2 | os: Visual Studio 2017 3 | 4 | # Build script 5 | build_script: 6 | - ps: .\build.ps1 --target="AppVeyor" --verbosity=Verbose 7 | 8 | # Tests 9 | test: off 10 | 11 | init: 12 | - git config --global core.autocrlf true -------------------------------------------------------------------------------- /azure-pipelines.yml: -------------------------------------------------------------------------------- 1 | # ASP.NET Core 2 | # Build and test ASP.NET Core projects targeting .NET Core. 3 | # Add steps that run tests, create a NuGet package, deploy, and more: 4 | # https://docs.microsoft.com/azure/devops/pipelines/languages/dotnet-core 5 | 6 | pool: 7 | vmImage: 'Ubuntu 16.04' 8 | 9 | variables: 10 | buildConfiguration: 'Release' 11 | 12 | steps: 13 | - script: dotnet build --configuration $(buildConfiguration) 14 | displayName: 'dotnet build $(buildConfiguration)' 15 | -------------------------------------------------------------------------------- /databaseUpdate.cmd: -------------------------------------------------------------------------------- 1 | cd Corewiki 2 | dotnet ef database update -c ApplicationDbContext -p ..\Corewiki.Data -------------------------------------------------------------------------------- /deployToAzure.cake: -------------------------------------------------------------------------------- 1 | #addin nuget:?package=Cake.Kudu&version=0.8.0 2 | 3 | if (!Kudu.IsRunningOnKudu) 4 | { 5 | throw new Exception("Not building on Kudu."); 6 | } 7 | 8 | var deploymentPath = Kudu.Deployment.Target; 9 | if (!DirectoryExists(deploymentPath)) 10 | { 11 | throw new DirectoryNotFoundException( 12 | string.Format( 13 | "Deployment target directory not found {0}", 14 | deploymentPath 15 | ) 16 | ); 17 | } 18 | 19 | 20 | Task("Kudu-Sync") 21 | .IsDependentOn("Publish") 22 | .Does( () => { 23 | Information("Deploying web from {0} to {1}", publishPath, deploymentPath); 24 | Kudu.Sync(publishPath); 25 | }); 26 | 27 | #load "build.cake" -------------------------------------------------------------------------------- /deployToAzure.cmd: -------------------------------------------------------------------------------- 1 | IF NOT EXIST "Tools" (md "Tools") 2 | IF NOT EXIST "Tools\Addins" (md "Tools\Addins") 3 | nuget install Cake -Version 0.30.0 -ExcludeVersion -OutputDirectory "Tools" 4 | .\Tools\Cake\Cake.exe deployToAzure.cake --target="Kudu-Sync" -verbosity=Verbose 5 | -------------------------------------------------------------------------------- /docs/architecture.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/architecture.pptx -------------------------------------------------------------------------------- /docs/c4models/1-context.md: -------------------------------------------------------------------------------- 1 | # Core Wiki Context 2 | 3 | The CoreWiki offers articles that other visitors can comment on. 4 | ![alt=Context](images/System-Landscape-diagram-for-CoreWiki.png) 5 | 6 | Articles are managed by authenticated users only. The authentication is provided by ASP.NET Identity, which can also be configured to provide external auth providers like [Twitter](http://www.twitter.com) and [Facebook](http://www.facebook.com). 7 | 8 | Comments can be displayed in a couple of fashions. 9 | 10 | - Internal Comments provided by the system 11 | - External comments through [Disqus](http://www.disqus.com) 12 | 13 | It also sends out notifications to users by usage of SendGrid, an external email service. 14 | 15 | The CoreWiki System is described more in detail in [CoreWiki Containers](2-containers.md) 16 | -------------------------------------------------------------------------------- /docs/c4models/1-context.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | !includeurl https://raw.githubusercontent.com/RicardoNiepel/C4-PlantUML/release/1-0/C4_Context.puml 3 | 4 | 'LAYOUT_TOP_DOWN 5 | 'LAYOUT_AS_SKETCH 6 | 'LAYOUT_WITH_LEGEND 7 | 8 | title System-Landscape-diagram-for-CoreWiki 9 | 10 | Person(reader, "Reader", "The user that visits the Corewiki website") 11 | Person(author, "Author", "The authenticated user that manages articles and comments on the Corewiki website") 12 | System_Ext(disqus, "Discus", "Commenting service") 13 | System_Ext(sendgrid, "SendGrid", "Sends email") 14 | System_Ext(external_auth_provider, "External Auth Provider", "Provide authentication (Twitter, Facebook,...)") 15 | 16 | System_Boundary(SB1, "Corewiki"){ 17 | System(CoreWikiSystem, "Corewiki UI", "provides a wikiblog to users") 18 | } 19 | 20 | Rel(reader, CoreWikiSystem, "read articles and comments on them") 21 | Rel(author, CoreWikiSystem, "manage articles and internal comments") 22 | Rel(CoreWikiSystem, disqus, "Display external comments") 23 | Rel(CoreWikiSystem, sendgrid, "send email to users") 24 | Rel(CoreWikiSystem, external_auth_provider, "authenticate users") 25 | Rel(reader, disqus, "Comments on articles through Disqus") 26 | @enduml 27 | -------------------------------------------------------------------------------- /docs/c4models/2-containers.md: -------------------------------------------------------------------------------- 1 | # Core Wiki Containers 2 | 3 | Diving a bit deeper, the CoreWiki systemm consist of a couple of components. 4 | 5 | - The ASP.NET Web front end, providing Razor Pages described in [CoreWiki Frontend](3-website-container.md) 6 | - (A Corewiki Mobile App (not on the roadmap just yet) 7 | - The CoreWiki Backend, which uses the databases: and described in [CoreWiki Backend](3-backend-container.md) 8 | - An articles Database (to store articles and internal comments) 9 | - Identity Database (to store user identities) 10 | 11 | ![alt=Corewiki Containers](images/Container-Diagram-For-CoreWiki.png) 12 | -------------------------------------------------------------------------------- /docs/c4models/3-container-mobile.md: -------------------------------------------------------------------------------- 1 | # Core Wiki Mobile App 2 | 3 | > not yet on the roadmap 4 | -------------------------------------------------------------------------------- /docs/c4models/3-website-container.md: -------------------------------------------------------------------------------- 1 | # CoreWiki Web frontend 2 | 3 | The Web frontend consists of a set of components. 4 | 5 | - Reading Pages: An area where articles can be displayed. This also provides the option for adding comments to a specific article, listing the latest changes and search for existing articles. 6 | - Managing Pages: An area where authenticated users can manage articles. 7 | - Identity Pages: An area providing the visitor a way to manage his/hers account and authenticate against either the internal or external auth provider. 8 | 9 | ![alt=Website Component](images/Frontend-Container-Diagram-For-CoreWiki.png) 10 | 11 | All areas use the Backend service to do their work. There is to be no business logic in any of these areas. All of them are about just displaying content and offer the user a way to interact with it. 12 | 13 | The backend can be called through the API provided (if hosted in the same app, then we can call the API controller directly). 14 | 15 | The reading page possible also use the [Disqus](http://www.disqus.com) system to display the external comments. 16 | 17 | A more general description of how pages are modeled can be found at [RazorPages](4-classes-pages.md) 18 | -------------------------------------------------------------------------------- /docs/c4models/4-classes-apicontroller.md: -------------------------------------------------------------------------------- 1 | # CoreWiki Api Controllers 2 | 3 | ![alt=API Controllers](images/Classes-for-APIControllers-CoreWiki.png) 4 | -------------------------------------------------------------------------------- /docs/c4models/4-classes-applicationservice.md: -------------------------------------------------------------------------------- 1 | # CoreWiki Application Services 2 | 3 | ![alt=Application Services](images/Classes-for-ApplicationService-Diagram-For-CoreWiki.png) 4 | -------------------------------------------------------------------------------- /docs/c4models/4-classes-pages.md: -------------------------------------------------------------------------------- 1 | # CoreWiki Razor Pages 2 | 3 | ![alt=Razor Pages](images/Classes-for-Razor-Pages-For-CoreWiki.png) 4 | -------------------------------------------------------------------------------- /docs/c4models/4-classes-pages.puml: -------------------------------------------------------------------------------- 1 | @startuml 2 | !includeurl https://raw.githubusercontent.com/RicardoNiepel/C4-PlantUML/release/1-0/C4_Component.puml 3 | 4 | LAYOUT_TOP_DOWN 5 | 'LAYOUT_AS_SKETCH 6 | 'LAYOUT_WITH_LEGEND 7 | 8 | title Classes-for-Razor-Pages-For-CoreWiki 9 | 10 | Person(reader, "Reader", "The user that visits the Corewiki website") 11 | Container(CoreWikiBackend, "CoreWiki Backend", "ASP.NET Core", "Provides articles and commenting functionality.") 12 | 13 | Container_Boundary(C3, "Corewiki Frontend"){ 14 | Component(Pages, "*.Page", "CSHTML", "Displays the ViewModel as HTML and calls PageModel for sending change requests") 15 | Component(PageModel, "*.PageModel", "ASP.NET Core", "manages the ViewModel through calling the API") 16 | Component(ViewModel, "*.ViewModel", "ASP.NET Core", "Contain the data displayed on the details page") 17 | } 18 | 19 | Rel(Pages, PageModel, "Request to update", "Razor Pages") 20 | Rel(Pages, ViewModel, "Read from", "Razor Pages") 21 | Rel_R(PageModel, ViewModel, "Updates", ".NET Core") 22 | 23 | Rel(reader, Pages, "Read articles and comments", "HTTPS") 24 | Rel(PageModel, CoreWikiBackend, "Query articles and comments and send commands", "HTTPS/APIControllers") 25 | 26 | 27 | @enduml 28 | -------------------------------------------------------------------------------- /docs/c4models/images/Backend-Container-Diagram-For-CoreWiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/c4models/images/Backend-Container-Diagram-For-CoreWiki.png -------------------------------------------------------------------------------- /docs/c4models/images/Classes-for-APIControllers-CoreWiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/c4models/images/Classes-for-APIControllers-CoreWiki.png -------------------------------------------------------------------------------- /docs/c4models/images/Classes-for-ApplicationService-Diagram-For-CoreWiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/c4models/images/Classes-for-ApplicationService-Diagram-For-CoreWiki.png -------------------------------------------------------------------------------- /docs/c4models/images/Classes-for-Razor-Pages-For-CoreWiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/c4models/images/Classes-for-Razor-Pages-For-CoreWiki.png -------------------------------------------------------------------------------- /docs/c4models/images/Container-Diagram-For-CoreWiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/c4models/images/Container-Diagram-For-CoreWiki.png -------------------------------------------------------------------------------- /docs/c4models/images/Frontend-Container-Diagram-For-CoreWiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/c4models/images/Frontend-Container-Diagram-For-CoreWiki.png -------------------------------------------------------------------------------- /docs/c4models/images/System-Landscape-diagram-for-CoreWiki.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/c4models/images/System-Landscape-diagram-for-CoreWiki.png -------------------------------------------------------------------------------- /docs/c4models/readme.md: -------------------------------------------------------------------------------- 1 | # CoreWiki Architecture 2 | 3 | C4 Models describing the architecture. 4 | 5 | - [Context](1-context.md) 6 | - [CoreWiki System](2-containers.md) 7 | - [Website](3-website-container.md) 8 | - [Pages](4-classes-pages.md) 9 | - [Backend](3-backend-container.md) 10 | - [API Controller](4-classes-apicontroller.md) 11 | - [API Application Service](4-classes-applicationservice.md) 12 | - [MobileApp](3-container-mobile.md) 13 | -------------------------------------------------------------------------------- /docs/objects.pptx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csharpfritz/CoreWiki/db5c0bd8de1c80fab536823bac1c9752c9bf3146/docs/objects.pptx -------------------------------------------------------------------------------- /global.json: -------------------------------------------------------------------------------- 1 | { 2 | "sdk": { 3 | "version": "2.1.403" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /nuget.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "requires": true, 3 | "lockfileVersion": 1, 4 | "dependencies": { 5 | "moment": { 6 | "version": "2.22.0", 7 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.22.0.tgz", 8 | "integrity": "sha512-1muXCh8jb1N/gHRbn9VDUBr0GYb8A/aVcHlII9QSB68a50spqEVLIGN6KVmCOnSvJrUhC0edGgKU5ofnGXdYdg==" 9 | }, 10 | "momentjs": { 11 | "version": "2.0.0", 12 | "resolved": "https://registry.npmjs.org/momentjs/-/momentjs-2.0.0.tgz", 13 | "integrity": "sha1-c9+QS0+kGPbjxgXoMc727VUY69Q=" 14 | } 15 | } 16 | } 17 | --------------------------------------------------------------------------------