Browse Source

Merge branch 'master' of https://github.com/volosoft/abp

pull/441/head
Armağan Ünlü 7 years ago
parent
commit
c49ff165ac
  1. 2
      README.md
  2. 30
      docs/docs-nav.json
  3. 10
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml
  4. 12
      framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/UserMenu/Default.cshtml
  5. 4
      framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AutoMapperExpressionExtensions.cs
  6. 25
      framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModule.cs
  7. 23
      framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs
  8. 3
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json
  9. 3
      framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json
  10. 9
      framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs
  11. 4
      modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs
  12. 2
      modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json
  13. 2
      modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobsDomainModule.cs
  14. 625
      modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180912113852_Added_BlogUsers.Designer.cs
  15. 35
      modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180912113852_Added_BlogUsers.cs
  16. 650
      modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180918060116_Added_SocialLinks_To_Blog.Designer.cs
  17. 68
      modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180918060116_Added_SocialLinks_To_Blog.cs
  18. 645
      modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180918061015_SocialLinks_Made_Nullable.Designer.cs
  19. 98
      modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180918061015_SocialLinks_Made_Nullable.cs
  20. 59
      modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs
  21. 10933
      modules/blogging/app/Volo.BloggingTestApp/Logs/logs.txt
  22. 10
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo.Blogging.Application.Contracts.csproj
  23. 14
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs
  24. 7
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissionDefinitionProvider.cs
  25. 3
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs
  26. 14
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/BlogDto.cs
  27. 24
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/Dtos/BlogDto.cs
  28. 21
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/Dtos/CreateBlogDto.cs
  29. 23
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/Dtos/UpdateBlogDto.cs
  30. 9
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/IBlogAppService.cs
  31. 5
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Comments/Dtos/CommentWithDetailsDto.cs
  32. 4
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Comments/Dtos/CommentWithRepliesDto.cs
  33. 4
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Comments/ICommentAppService.cs
  34. 14
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/ApplicationContracts/en.json
  35. 14
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/ApplicationContracts/tr.json
  36. 24
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/BlogUserDto.cs
  37. 2
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/GetPostInput.cs
  38. 2
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/IPostAppService.cs
  39. 20
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/PostDto.cs
  40. 2
      modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/PostWithDetailsDto.cs
  41. 6
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs
  42. 18
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationModule.cs
  43. 53
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs
  44. 79
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAppService.cs
  45. 61
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAuthorizationHandler.cs
  46. 13
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/CommonOperations.cs
  47. 114
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs
  48. 61
      modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAuthorizationHandler.cs
  49. 2
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Blogs/BlogConsts.cs
  50. 0
      modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs
  51. 1
      modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj
  52. 21
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/Blog.cs
  53. 5
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs
  54. 2
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Comments/ICommentRepository.cs
  55. 7
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/IPostTagRepository.cs
  56. 4
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Tagging/ITagRepository.cs
  57. 43
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUser.cs
  58. 22
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs
  59. 9
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/IBlogUserLookupService.cs
  60. 14
      modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/IBlogUserRepository.cs
  61. 1
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo.Blogging.EntityFrameworkCore.csproj
  62. 17
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs
  63. 6
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Comments/EfCoreCommentRepository.cs
  64. 3
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContext.cs
  65. 15
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs
  66. 3
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/IBloggingDbContext.cs
  67. 17
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Posts/EfCorePostTagRepository.cs
  68. 15
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Tagging/EfCoreTagRepository.cs
  69. 27
      modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Users/EfCoreQaUserRepository.cs
  70. 1
      modules/blogging/src/Volo.Blogging.HttpApi.Client/Volo.Blogging.HttpApi.Client.csproj
  71. 10
      modules/blogging/src/Volo.Blogging.HttpApi.Client/Volo/Blogging/BloggingHttpApiClientModule.cs
  72. 1
      modules/blogging/src/Volo.Blogging.HttpApi/Volo.Blogging.HttpApi.csproj
  73. 5
      modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BloggingHttpApiModule.cs
  74. 77
      modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogsController.cs
  75. 7
      modules/blogging/src/Volo.Blogging.Web/AbpBloggingWebAutoMapperProfile.cs
  76. 26
      modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/PostsController.cs
  77. 210
      modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/TagHelpers/GravatarTagHelper.cs
  78. 14
      modules/blogging/src/Volo.Blogging.Web/BloggingMenuContributor.cs
  79. 8
      modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs
  80. 27
      modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json
  81. 27
      modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json
  82. 33
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Create.cshtml
  83. 65
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Create.cshtml.cs
  84. 34
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Edit.cshtml
  85. 85
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Edit.cshtml.cs
  86. 45
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Index.cshtml
  87. 13
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Index.cshtml.cs
  88. 14
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/create.js
  89. 14
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/edit.js
  90. 84
      modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js
  91. 66
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs
  92. 2
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Index.cshtml
  93. 4
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Index.cshtml.cs
  94. 270
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml
  95. 42
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml.cs
  96. 2
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Edit.cshtml.cs
  97. 305
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Index.cshtml
  98. 10
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Index.cshtml.cs
  99. 3
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/New.cshtml.cs
  100. 38
      modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/detail.js

2
README.md

@ -1,6 +1,6 @@
# ABP # ABP
[![Build Status](https://travis-ci.org/volosoft/abp.svg?branch=master)](https://travis-ci.org/volosoft/abp) [![Build Status](http://vjenkins.dynu.net:5480/job/abp/badge/icon)](http://vjenkins.dynu.net:5480/blue/organizations/jenkins/abp/activity)
This project is the next generation of the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) web application framework. This project is the next generation of the [ASP.NET Boilerplate](https://aspnetboilerplate.com/) web application framework.

30
docs/docs-nav.json

@ -0,0 +1,30 @@
{
"items": [{
"text": "Getting Started",
"items": [{
"text": "From Startup Templates",
"items": [{
"text": "ASP.NET Core MVC",
"path": "Getting-Started-AspNetCore-MVC-Template.md"
}]
},{
"text": "From Empty Projects",
"items": [{
"text": "With ASP.NET Core Web Application",
"path": "Getting-Started-AspNetCore-Application.md"
},{
"text": "With Console Application",
"path": "Getting-Started-Console-Application.md"
}]
}]
},{
"text": "Tutorials",
"items": [{
"text": "Application Development",
"items": [{
"text": "With ASP.NET Core MVC",
"path": "Tutorials/AspNetCore-Mvc/Part-I.md"
}]
}]
}]
}

10
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Menu/Default.cshtml

@ -2,6 +2,10 @@
@model ApplicationMenu @model ApplicationMenu
@foreach (var menuItem in Model.Items) @foreach (var menuItem in Model.Items)
{ {
var elementId = string.IsNullOrEmpty(menuItem.ElementId) ? string.Empty : $"id=\"{menuItem.ElementId}\"";
var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass;
var disabled = menuItem.IsDisabled ? "disabled" : string.Empty;
if (menuItem.IsLeaf) if (menuItem.IsLeaf)
{ {
if (menuItem.Url == null) if (menuItem.Url == null)
@ -9,7 +13,7 @@
continue; continue;
} }
<li class="nav-item @(menuItem.IsDisabled ? "disabled" : "")"> <li class="nav-item @cssClass @disabled" @elementId>
<a class="nav-link" href="@(menuItem.Url ?? "#")"> <a class="nav-link" href="@(menuItem.Url ?? "#")">
@if (menuItem.Icon != null) @if (menuItem.Icon != null)
{ {
@ -29,7 +33,9 @@
<div class="dropdown-menu" aria-labelledby="Menu_@(menuItem.Name)"> <div class="dropdown-menu" aria-labelledby="Menu_@(menuItem.Name)">
@foreach (var childMenuItem in menuItem.Items) @foreach (var childMenuItem in menuItem.Items)
{ {
<a class="dropdown-item" href="@(childMenuItem.Url ?? "#")">@childMenuItem.DisplayName</a> <a class="dropdown-item @cssClass @disabled" href="@(childMenuItem.Url ?? "#")" @Html.Raw(elementId)>
@childMenuItem.DisplayName
</a>
} }
</div> </div>
</li> </li>

12
framework/src/Volo.Abp.AspNetCore.Mvc.UI.Theme.Basic/Themes/Basic/Components/Toolbar/UserMenu/Default.cshtml

@ -11,16 +11,20 @@
<a class="btn btn-link dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false"> <a class="btn btn-link dropdown-toggle" href="#" role="button" id="dropdownMenuLink" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
@CurrentUser.UserName @CurrentUser.UserName
</a> </a>
@if (Model.Items.Any()) @if (Model.Items.Any())
{ {
<div class="dropdown-menu" aria-labelledby="dropdownMenuLink"> <div class="dropdown-menu" aria-labelledby="dropdownMenuLink">
@foreach (var menuItem in Model.Items) @foreach (var menuItem in Model.Items)
{ {
<a class="dropdown-item" href="@menuItem.Url">@menuItem.DisplayName</a> var elementId = string.IsNullOrEmpty(menuItem.ElementId) ? string.Empty : $"id=\"{menuItem.ElementId}\"";
} var cssClass = string.IsNullOrEmpty(menuItem.CssClass) ? string.Empty : menuItem.CssClass;
var disabled = menuItem.IsDisabled ? "disabled" : string.Empty;
@*<a class="dropdown-item" href="/Account/Logout">@L["Logout"]</a>*@ <a class="dropdown-item @cssClass @disabled" href="@(menuItem.Url ?? "#")" @Html.Raw(elementId)>
@menuItem.DisplayName
</a>
}
</div> </div>
} }
</div> </div>

4
framework/src/Volo.Abp.AutoMapper/Volo/Abp/AutoMapper/AutoMapperExpressionExtensions.cs

@ -6,9 +6,9 @@ namespace Volo.Abp.AutoMapper
{ {
public static class AutoMapperExpressionExtensions public static class AutoMapperExpressionExtensions
{ {
public static void Ignore<TDestination, TMember, TResult>(this IMappingExpression<TDestination, TMember> mappingExpression, Expression<Func<TMember, TResult>> destinationMember) public static IMappingExpression<TDestination, TMember> Ignore<TDestination, TMember, TResult>(this IMappingExpression<TDestination, TMember> mappingExpression, Expression<Func<TMember, TResult>> destinationMember)
{ {
mappingExpression.ForMember(destinationMember, opts => opts.Ignore()); return mappingExpression.ForMember(destinationMember, opts => opts.Ignore());
} }
} }
} }

25
framework/src/Volo.Abp.Core/Volo/Abp/Modularity/AbpModule.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Reflection; using System.Reflection;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
namespace Volo.Abp.Modularity namespace Volo.Abp.Modularity
@ -91,6 +92,30 @@ namespace Volo.Abp.Modularity
ServiceConfigurationContext.Services.Configure(configureOptions); ServiceConfigurationContext.Services.Configure(configureOptions);
} }
protected void Configure<TOptions>(string name, Action<TOptions> configureOptions)
where TOptions : class
{
ServiceConfigurationContext.Services.Configure(name, configureOptions);
}
protected void Configure<TOptions>(IConfiguration configuration)
where TOptions : class
{
ServiceConfigurationContext.Services.Configure<TOptions>(configuration);
}
protected void Configure<TOptions>(IConfiguration configuration, Action<BinderOptions> configureBinder)
where TOptions : class
{
ServiceConfigurationContext.Services.Configure<TOptions>(configuration, configureBinder);
}
protected void Configure<TOptions>(string name, IConfiguration configuration)
where TOptions : class
{
ServiceConfigurationContext.Services.Configure<TOptions>(name, configuration);
}
protected void PreConfigure<TOptions>(Action<TOptions> configureOptions) protected void PreConfigure<TOptions>(Action<TOptions> configureOptions)
where TOptions : class where TOptions : class
{ {

23
framework/src/Volo.Abp.UI.Navigation/Volo/Abp/Ui/Navigation/ApplicationMenuItem.cs

@ -37,7 +37,7 @@ namespace Volo.Abp.UI.Navigation
/// Default value: 1000. /// Default value: 1000.
/// </summary> /// </summary>
public int Order { get; set; } public int Order { get; set; }
/// <summary> /// <summary>
/// The URL to navigate when this menu item is selected. /// The URL to navigate when this menu item is selected.
/// </summary> /// </summary>
@ -65,7 +65,7 @@ namespace Volo.Abp.UI.Navigation
/// Can be used to disable this menu item. /// Can be used to disable this menu item.
/// </summary> /// </summary>
public bool IsDisabled { get; set; } public bool IsDisabled { get; set; }
/// <inheritdoc cref="IHasMenuItems.Items"/> /// <inheritdoc cref="IHasMenuItems.Items"/>
[NotNull] [NotNull]
public IList<ApplicationMenuItem> Items { get; } public IList<ApplicationMenuItem> Items { get; }
@ -75,14 +75,27 @@ namespace Volo.Abp.UI.Navigation
/// </summary> /// </summary>
public object CustomData { get; set; } public object CustomData { get; set; }
/// <summary>
/// Can be used to render the element with a specific Id for DOM selections.
/// </summary>
public string ElementId { get; set; }
/// <summary>
/// Can be used to render the element with extra CSS classes.
/// </summary>
public string CssClass { get; set; }
public ApplicationMenuItem( public ApplicationMenuItem(
[NotNull] string name, [NotNull] string name,
[NotNull] string displayName, [NotNull] string displayName,
string url = null, string url = null,
string icon = null, string icon = null,
int order = DefaultOrder, int order = DefaultOrder,
object customData = null, object customData = null,
string target = null) string target = null,
string elementId = null,
string cssClass = null)
{ {
Check.NotNullOrWhiteSpace(name, nameof(name)); Check.NotNullOrWhiteSpace(name, nameof(name));
Check.NotNullOrWhiteSpace(displayName, nameof(displayName)); Check.NotNullOrWhiteSpace(displayName, nameof(displayName));
@ -94,6 +107,8 @@ namespace Volo.Abp.UI.Navigation
Order = order; Order = order;
CustomData = customData; CustomData = customData;
Target = target; Target = target;
ElementId = elementId;
CssClass = cssClass;
Items = new List<ApplicationMenuItem>(); Items = new List<ApplicationMenuItem>();
} }

3
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/en.json

@ -33,6 +33,7 @@
"PagerNext": "Next", "PagerNext": "Next",
"PagerPrevious": "Previous", "PagerPrevious": "Previous",
"PagerInfo": "Showing {0} to {1} of {2} entries.", "PagerInfo": "Showing {0} to {1} of {2} entries.",
"DatatableActionDropdownDefaultText": "Actions" "DatatableActionDropdownDefaultText": "Actions",
"ChangePassword": "Change password"
} }
} }

3
framework/src/Volo.Abp.UI/Localization/Resources/AbpUi/tr.json

@ -33,6 +33,7 @@
"PagerNext": "Sonraki", "PagerNext": "Sonraki",
"PagerPrevious": "Önceki", "PagerPrevious": "Önceki",
"PagerInfo": "{2} kayıttan {0} ile {1} arası gösteriliyor.", "PagerInfo": "{2} kayıttan {0} ile {1} arası gösteriliyor.",
"DatatableActionDropdownDefaultText": "İşlemler" "DatatableActionDropdownDefaultText": "İşlemler",
"ChangePassword": "Şifre değiştir"
} }
} }

9
framework/src/Volo.Abp.VirtualFileSystem/Microsoft/Extensions/FileProviders/AbpFileInfoExtensions.cs

@ -1,6 +1,6 @@
using System.IO; using JetBrains.Annotations;
using System.IO;
using System.Text; using System.Text;
using JetBrains.Annotations;
using Volo.Abp; using Volo.Abp;
namespace Microsoft.Extensions.FileProviders namespace Microsoft.Extensions.FileProviders
@ -24,7 +24,10 @@ namespace Microsoft.Extensions.FileProviders
using (var stream = fileInfo.CreateReadStream()) using (var stream = fileInfo.CreateReadStream())
{ {
return encoding.GetString(stream.GetAllBytes()); using (var streamReader = new StreamReader(stream, encoding, true))
{
return streamReader.ReadToEnd();
}
} }
} }
} }

4
modules/account/src/Volo.Abp.Account.Web/AbpAccountUserMenuContributor.cs

@ -10,7 +10,7 @@ namespace Volo.Abp.Account.Web
{ {
public AbpAccountUserMenuContributor() public AbpAccountUserMenuContributor()
{ {
} }
public Task ConfigureMenuAsync(MenuConfigurationContext context) public Task ConfigureMenuAsync(MenuConfigurationContext context)
@ -22,6 +22,8 @@ namespace Volo.Abp.Account.Web
var l = context.ServiceProvider.GetRequiredService<IStringLocalizer<AbpUiResource>>(); var l = context.ServiceProvider.GetRequiredService<IStringLocalizer<AbpUiResource>>();
context.Menu.AddItem(new ApplicationMenuItem("Account.ChangePassword", l["ChangePassword"], icon: "fa fa-key", url: "#", elementId: "abp-account-change-password"));
context.Menu.AddItem(new ApplicationMenuItem("Account.Logout", l["Logout"], url: "/Account/Logout", icon: "fa fa-power-off", order: int.MaxValue - 1000)); context.Menu.AddItem(new ApplicationMenuItem("Account.Logout", l["Logout"], url: "/Account/Logout", icon: "fa fa-power-off", order: int.MaxValue - 1000));
return Task.CompletedTask; return Task.CompletedTask;

2
modules/account/src/Volo.Abp.Account.Web/Localization/Resources/AbpAccount/Web/tr.json

@ -9,7 +9,7 @@
"UseAnotherServiceToLogin": "Başka bir servisle giriş yap", "UseAnotherServiceToLogin": "Başka bir servisle giriş yap",
"UserLockedOutMessage": "Kullanıcı hesabı hatalı giriş denemeleri nedeniyle kilitlenmiştir. Lütfen bir süre bekleyip tekrar deneyin.", "UserLockedOutMessage": "Kullanıcı hesabı hatalı giriş denemeleri nedeniyle kilitlenmiştir. Lütfen bir süre bekleyip tekrar deneyin.",
"InvalidUserNameOrPassword": "Kullanıcı adı ya da şifre geçersiz!", "InvalidUserNameOrPassword": "Kullanıcı adı ya da şifre geçersiz!",
"LoginIsNotAllowed": "You are not allowed to login! E-posta adresinizi ya da telefon numaranızı doğrulamanız gerekiyor.", "LoginIsNotAllowed": "Giriş yapamazsınız! E-posta adresinizi ya da telefon numaranızı doğrulamanız gerekiyor.",
"SelfRegistrationDisabledMessage": "Bu uygulama için kullanıcıların kendi kendilerine kaydolmaları engellenmiştir. Yeni bir kullanıcı kaydetmek için lütfen uygulama yöneticisi ile iletişime geçin." "SelfRegistrationDisabledMessage": "Bu uygulama için kullanıcıların kendi kendilerine kaydolmaları engellenmiştir. Yeni bir kullanıcı kaydetmek için lütfen uygulama yöneticisi ile iletişime geçin."
} }
} }

2
modules/background-jobs/src/Volo.Abp.BackgroundJobs.Domain/Volo/Abp/BackgroundJobs/BackgroundJobsDomainModule.cs

@ -9,7 +9,7 @@ namespace Volo.Abp.BackgroundJobs
typeof(AbpBackgroundJobsModule), typeof(AbpBackgroundJobsModule),
typeof(AbpAutoMapperModule) typeof(AbpAutoMapperModule)
)] )]
public class BackgroundJobsDomainModule : AbpModule public class BackgroundJobsDomainModule : AbpModule //TODO: Rename to AbpBackgroundJobsDomainModule
{ {
public override void ConfigureServices(ServiceConfigurationContext context) public override void ConfigureServices(ServiceConfigurationContext context)
{ {

625
modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180912113852_Added_BlogUsers.Designer.cs

@ -0,0 +1,625 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.BloggingTestApp.EntityFrameworkCore;
namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
{
[DbContext(typeof(BloggingTestAppDbContext))]
[Migration("20180912113852_Added_BlogUsers")]
partial class Added_BlogUsers
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(256);
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("NormalizedName");
b.ToTable("AbpRoles");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("ClaimValue")
.HasMaxLength(1024);
b.Property<Guid>("RoleId");
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AbpRoleClaims");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount")
.ValueGeneratedOnAdd()
.HasColumnName("AccessFailedCount")
.HasDefaultValue(0);
b.Property<string>("ConcurrencyStamp")
.IsRequired()
.HasColumnName("ConcurrencyStamp")
.HasMaxLength(256);
b.Property<string>("Email")
.HasColumnName("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("EmailConfirmed")
.HasDefaultValue(false);
b.Property<string>("ExtraProperties")
.HasColumnName("ExtraProperties");
b.Property<bool>("LockoutEnabled")
.ValueGeneratedOnAdd()
.HasColumnName("LockoutEnabled")
.HasDefaultValue(false);
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasColumnName("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.IsRequired()
.HasColumnName("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnName("PasswordHash")
.HasMaxLength(256);
b.Property<string>("PhoneNumber")
.HasColumnName("PhoneNumber")
.HasMaxLength(16);
b.Property<bool>("PhoneNumberConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("PhoneNumberConfirmed")
.HasDefaultValue(false);
b.Property<string>("SecurityStamp")
.IsRequired()
.HasColumnName("SecurityStamp")
.HasMaxLength(256);
b.Property<Guid?>("TenantId")
.HasColumnName("TenantId");
b.Property<bool>("TwoFactorEnabled")
.ValueGeneratedOnAdd()
.HasColumnName("TwoFactorEnabled")
.HasDefaultValue(false);
b.Property<string>("UserName")
.IsRequired()
.HasColumnName("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("Email");
b.HasIndex("NormalizedEmail");
b.HasIndex("NormalizedUserName");
b.HasIndex("UserName");
b.ToTable("AbpUsers");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("ClaimValue")
.HasMaxLength(1024);
b.Property<Guid?>("TenantId");
b.Property<Guid>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AbpUserClaims");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b =>
{
b.Property<Guid>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(64);
b.Property<string>("ProviderDisplayName")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.IsRequired()
.HasMaxLength(196);
b.Property<Guid?>("TenantId");
b.HasKey("UserId", "LoginProvider");
b.HasIndex("LoginProvider", "ProviderKey");
b.ToTable("AbpUserLogins");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b =>
{
b.Property<Guid>("UserId");
b.Property<Guid>("RoleId");
b.Property<Guid?>("TenantId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId", "UserId");
b.ToTable("AbpUserRoles");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b =>
{
b.Property<Guid>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name");
b.Property<Guid?>("TenantId");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AbpUserTokens");
});
modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.IsRequired()
.HasMaxLength(64);
b.Property<string>("ProviderName")
.IsRequired()
.HasMaxLength(64);
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("Name", "ProviderName", "ProviderKey");
b.ToTable("AbpPermissionGrants");
});
modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(64);
b.Property<string>("ProviderName")
.HasMaxLength(64);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2048);
b.HasKey("Id");
b.HasIndex("Name", "ProviderName", "ProviderKey");
b.ToTable("AbpSettings");
});
modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnName("Description")
.HasMaxLength(1024);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasColumnName("Name")
.HasMaxLength(256);
b.Property<string>("ShortName")
.IsRequired()
.HasColumnName("ShortName")
.HasMaxLength(32);
b.HasKey("Id");
b.ToTable("BlgBlogs");
});
modelBuilder.Entity("Volo.Blogging.Comments.Comment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<Guid>("PostId")
.HasColumnName("PostId");
b.Property<Guid?>("RepliedCommentId")
.HasColumnName("RepliedCommentId");
b.Property<string>("Text")
.IsRequired()
.HasColumnName("Text")
.HasMaxLength(1024);
b.HasKey("Id");
b.HasIndex("PostId");
b.HasIndex("RepliedCommentId");
b.ToTable("BlgComments");
});
modelBuilder.Entity("Volo.Blogging.Posts.Post", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<Guid>("BlogId")
.HasColumnName("BlogId");
b.Property<string>("Content")
.HasColumnName("Content")
.HasMaxLength(1048576);
b.Property<string>("CoverImage")
.IsRequired()
.HasColumnName("CoverImage");
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<int>("ReadCount");
b.Property<string>("Title")
.IsRequired()
.HasColumnName("Title")
.HasMaxLength(512);
b.Property<string>("Url")
.IsRequired()
.HasColumnName("Url")
.HasMaxLength(64);
b.HasKey("Id");
b.HasIndex("BlogId");
b.ToTable("BlgPosts");
});
modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b =>
{
b.Property<Guid>("PostId")
.HasColumnName("PostId");
b.Property<Guid>("TagId")
.HasColumnName("TagId");
b.Property<DateTime>("CreationTime");
b.Property<Guid?>("CreatorId");
b.HasKey("PostId", "TagId");
b.HasIndex("TagId");
b.ToTable("BlgPostTags");
});
modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnName("Description")
.HasMaxLength(512);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasColumnName("Name")
.HasMaxLength(64);
b.Property<int>("UsageCount")
.HasColumnName("UsageCount");
b.HasKey("Id");
b.ToTable("BlgTags");
});
modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Email")
.HasColumnName("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("EmailConfirmed")
.HasDefaultValue(false);
b.Property<string>("ExtraProperties")
.HasColumnName("ExtraProperties");
b.Property<string>("PhoneNumber")
.HasColumnName("PhoneNumber")
.HasMaxLength(16);
b.Property<bool>("PhoneNumberConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("PhoneNumberConfirmed")
.HasDefaultValue(false);
b.Property<Guid?>("TenantId")
.HasColumnName("TenantId");
b.Property<string>("UserName")
.IsRequired()
.HasColumnName("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.ToTable("BlgUsers");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityRole")
.WithMany("Claims")
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Claims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Roles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Tokens")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Blogging.Comments.Comment", b =>
{
b.HasOne("Volo.Blogging.Posts.Post")
.WithMany()
.HasForeignKey("PostId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Blogging.Comments.Comment")
.WithMany()
.HasForeignKey("RepliedCommentId");
});
modelBuilder.Entity("Volo.Blogging.Posts.Post", b =>
{
b.HasOne("Volo.Blogging.Blogs.Blog")
.WithMany()
.HasForeignKey("BlogId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b =>
{
b.HasOne("Volo.Blogging.Posts.Post")
.WithMany("Tags")
.HasForeignKey("PostId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Blogging.Tagging.Tag")
.WithMany()
.HasForeignKey("TagId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

35
modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180912113852_Added_BlogUsers.cs

@ -0,0 +1,35 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
{
public partial class Added_BlogUsers : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "BlgUsers",
columns: table => new
{
Id = table.Column<Guid>(nullable: false),
TenantId = table.Column<Guid>(nullable: true),
UserName = table.Column<string>(maxLength: 256, nullable: false),
Email = table.Column<string>(maxLength: 256, nullable: true),
EmailConfirmed = table.Column<bool>(nullable: false, defaultValue: false),
PhoneNumber = table.Column<string>(maxLength: 16, nullable: true),
PhoneNumberConfirmed = table.Column<bool>(nullable: false, defaultValue: false),
ExtraProperties = table.Column<string>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_BlgUsers", x => x.Id);
});
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "BlgUsers");
}
}
}

650
modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180918060116_Added_SocialLinks_To_Blog.Designer.cs

@ -0,0 +1,650 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.BloggingTestApp.EntityFrameworkCore;
namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
{
[DbContext(typeof(BloggingTestAppDbContext))]
[Migration("20180918060116_Added_SocialLinks_To_Blog")]
partial class Added_SocialLinks_To_Blog
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(256);
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("NormalizedName");
b.ToTable("AbpRoles");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("ClaimValue")
.HasMaxLength(1024);
b.Property<Guid>("RoleId");
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AbpRoleClaims");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount")
.ValueGeneratedOnAdd()
.HasColumnName("AccessFailedCount")
.HasDefaultValue(0);
b.Property<string>("ConcurrencyStamp")
.IsRequired()
.HasColumnName("ConcurrencyStamp")
.HasMaxLength(256);
b.Property<string>("Email")
.HasColumnName("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("EmailConfirmed")
.HasDefaultValue(false);
b.Property<string>("ExtraProperties")
.HasColumnName("ExtraProperties");
b.Property<bool>("LockoutEnabled")
.ValueGeneratedOnAdd()
.HasColumnName("LockoutEnabled")
.HasDefaultValue(false);
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasColumnName("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.IsRequired()
.HasColumnName("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnName("PasswordHash")
.HasMaxLength(256);
b.Property<string>("PhoneNumber")
.HasColumnName("PhoneNumber")
.HasMaxLength(16);
b.Property<bool>("PhoneNumberConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("PhoneNumberConfirmed")
.HasDefaultValue(false);
b.Property<string>("SecurityStamp")
.IsRequired()
.HasColumnName("SecurityStamp")
.HasMaxLength(256);
b.Property<Guid?>("TenantId")
.HasColumnName("TenantId");
b.Property<bool>("TwoFactorEnabled")
.ValueGeneratedOnAdd()
.HasColumnName("TwoFactorEnabled")
.HasDefaultValue(false);
b.Property<string>("UserName")
.IsRequired()
.HasColumnName("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("Email");
b.HasIndex("NormalizedEmail");
b.HasIndex("NormalizedUserName");
b.HasIndex("UserName");
b.ToTable("AbpUsers");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("ClaimValue")
.HasMaxLength(1024);
b.Property<Guid?>("TenantId");
b.Property<Guid>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AbpUserClaims");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b =>
{
b.Property<Guid>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(64);
b.Property<string>("ProviderDisplayName")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.IsRequired()
.HasMaxLength(196);
b.Property<Guid?>("TenantId");
b.HasKey("UserId", "LoginProvider");
b.HasIndex("LoginProvider", "ProviderKey");
b.ToTable("AbpUserLogins");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b =>
{
b.Property<Guid>("UserId");
b.Property<Guid>("RoleId");
b.Property<Guid?>("TenantId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId", "UserId");
b.ToTable("AbpUserRoles");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b =>
{
b.Property<Guid>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name");
b.Property<Guid?>("TenantId");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AbpUserTokens");
});
modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.IsRequired()
.HasMaxLength(64);
b.Property<string>("ProviderName")
.IsRequired()
.HasMaxLength(64);
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("Name", "ProviderName", "ProviderKey");
b.ToTable("AbpPermissionGrants");
});
modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(64);
b.Property<string>("ProviderName")
.HasMaxLength(64);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2048);
b.HasKey("Id");
b.HasIndex("Name", "ProviderName", "ProviderKey");
b.ToTable("AbpSettings");
});
modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnName("Description")
.HasMaxLength(1024);
b.Property<string>("Facebook")
.IsRequired()
.HasColumnName("Facebook")
.HasMaxLength(128);
b.Property<string>("Github")
.IsRequired()
.HasColumnName("Github")
.HasMaxLength(128);
b.Property<string>("Instagram")
.IsRequired()
.HasColumnName("Instagram")
.HasMaxLength(128);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasColumnName("Name")
.HasMaxLength(256);
b.Property<string>("ShortName")
.IsRequired()
.HasColumnName("ShortName")
.HasMaxLength(32);
b.Property<string>("StackOverflow")
.IsRequired()
.HasColumnName("StackOverflow")
.HasMaxLength(128);
b.Property<string>("Twitter")
.IsRequired()
.HasColumnName("Twitter")
.HasMaxLength(128);
b.HasKey("Id");
b.ToTable("BlgBlogs");
});
modelBuilder.Entity("Volo.Blogging.Comments.Comment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<Guid>("PostId")
.HasColumnName("PostId");
b.Property<Guid?>("RepliedCommentId")
.HasColumnName("RepliedCommentId");
b.Property<string>("Text")
.IsRequired()
.HasColumnName("Text")
.HasMaxLength(1024);
b.HasKey("Id");
b.HasIndex("PostId");
b.HasIndex("RepliedCommentId");
b.ToTable("BlgComments");
});
modelBuilder.Entity("Volo.Blogging.Posts.Post", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<Guid>("BlogId")
.HasColumnName("BlogId");
b.Property<string>("Content")
.HasColumnName("Content")
.HasMaxLength(1048576);
b.Property<string>("CoverImage")
.IsRequired()
.HasColumnName("CoverImage");
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<int>("ReadCount");
b.Property<string>("Title")
.IsRequired()
.HasColumnName("Title")
.HasMaxLength(512);
b.Property<string>("Url")
.IsRequired()
.HasColumnName("Url")
.HasMaxLength(64);
b.HasKey("Id");
b.HasIndex("BlogId");
b.ToTable("BlgPosts");
});
modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b =>
{
b.Property<Guid>("PostId")
.HasColumnName("PostId");
b.Property<Guid>("TagId")
.HasColumnName("TagId");
b.Property<DateTime>("CreationTime");
b.Property<Guid?>("CreatorId");
b.HasKey("PostId", "TagId");
b.HasIndex("TagId");
b.ToTable("BlgPostTags");
});
modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnName("Description")
.HasMaxLength(512);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasColumnName("Name")
.HasMaxLength(64);
b.Property<int>("UsageCount")
.HasColumnName("UsageCount");
b.HasKey("Id");
b.ToTable("BlgTags");
});
modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Email")
.HasColumnName("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("EmailConfirmed")
.HasDefaultValue(false);
b.Property<string>("ExtraProperties")
.HasColumnName("ExtraProperties");
b.Property<string>("PhoneNumber")
.HasColumnName("PhoneNumber")
.HasMaxLength(16);
b.Property<bool>("PhoneNumberConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("PhoneNumberConfirmed")
.HasDefaultValue(false);
b.Property<Guid?>("TenantId")
.HasColumnName("TenantId");
b.Property<string>("UserName")
.IsRequired()
.HasColumnName("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.ToTable("BlgUsers");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityRole")
.WithMany("Claims")
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Claims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Roles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Tokens")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Blogging.Comments.Comment", b =>
{
b.HasOne("Volo.Blogging.Posts.Post")
.WithMany()
.HasForeignKey("PostId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Blogging.Comments.Comment")
.WithMany()
.HasForeignKey("RepliedCommentId");
});
modelBuilder.Entity("Volo.Blogging.Posts.Post", b =>
{
b.HasOne("Volo.Blogging.Blogs.Blog")
.WithMany()
.HasForeignKey("BlogId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b =>
{
b.HasOne("Volo.Blogging.Posts.Post")
.WithMany("Tags")
.HasForeignKey("PostId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Blogging.Tagging.Tag")
.WithMany()
.HasForeignKey("TagId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

68
modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180918060116_Added_SocialLinks_To_Blog.cs

@ -0,0 +1,68 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
{
public partial class Added_SocialLinks_To_Blog : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "Facebook",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "Github",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "Instagram",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "StackOverflow",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "Twitter",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
defaultValue: "");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Facebook",
table: "BlgBlogs");
migrationBuilder.DropColumn(
name: "Github",
table: "BlgBlogs");
migrationBuilder.DropColumn(
name: "Instagram",
table: "BlgBlogs");
migrationBuilder.DropColumn(
name: "StackOverflow",
table: "BlgBlogs");
migrationBuilder.DropColumn(
name: "Twitter",
table: "BlgBlogs");
}
}
}

645
modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180918061015_SocialLinks_Made_Nullable.Designer.cs

@ -0,0 +1,645 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Volo.BloggingTestApp.EntityFrameworkCore;
namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
{
[DbContext(typeof(BloggingTestAppDbContext))]
[Migration("20180918061015_SocialLinks_Made_Nullable")]
partial class SocialLinks_Made_Nullable
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.1-rtm-30846")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Volo.Abp.Identity.IdentityRole", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConcurrencyStamp");
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("NormalizedName")
.IsRequired()
.HasMaxLength(256);
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("NormalizedName");
b.ToTable("AbpRoles");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("ClaimValue")
.HasMaxLength(1024);
b.Property<Guid>("RoleId");
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("RoleId");
b.ToTable("AbpRoleClaims");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<int>("AccessFailedCount")
.ValueGeneratedOnAdd()
.HasColumnName("AccessFailedCount")
.HasDefaultValue(0);
b.Property<string>("ConcurrencyStamp")
.IsRequired()
.HasColumnName("ConcurrencyStamp")
.HasMaxLength(256);
b.Property<string>("Email")
.HasColumnName("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("EmailConfirmed")
.HasDefaultValue(false);
b.Property<string>("ExtraProperties")
.HasColumnName("ExtraProperties");
b.Property<bool>("LockoutEnabled")
.ValueGeneratedOnAdd()
.HasColumnName("LockoutEnabled")
.HasDefaultValue(false);
b.Property<DateTimeOffset?>("LockoutEnd");
b.Property<string>("NormalizedEmail")
.HasColumnName("NormalizedEmail")
.HasMaxLength(256);
b.Property<string>("NormalizedUserName")
.IsRequired()
.HasColumnName("NormalizedUserName")
.HasMaxLength(256);
b.Property<string>("PasswordHash")
.HasColumnName("PasswordHash")
.HasMaxLength(256);
b.Property<string>("PhoneNumber")
.HasColumnName("PhoneNumber")
.HasMaxLength(16);
b.Property<bool>("PhoneNumberConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("PhoneNumberConfirmed")
.HasDefaultValue(false);
b.Property<string>("SecurityStamp")
.IsRequired()
.HasColumnName("SecurityStamp")
.HasMaxLength(256);
b.Property<Guid?>("TenantId")
.HasColumnName("TenantId");
b.Property<bool>("TwoFactorEnabled")
.ValueGeneratedOnAdd()
.HasColumnName("TwoFactorEnabled")
.HasDefaultValue(false);
b.Property<string>("UserName")
.IsRequired()
.HasColumnName("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.HasIndex("Email");
b.HasIndex("NormalizedEmail");
b.HasIndex("NormalizedUserName");
b.HasIndex("UserName");
b.ToTable("AbpUsers");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ClaimType")
.IsRequired()
.HasMaxLength(256);
b.Property<string>("ClaimValue")
.HasMaxLength(1024);
b.Property<Guid?>("TenantId");
b.Property<Guid>("UserId");
b.HasKey("Id");
b.HasIndex("UserId");
b.ToTable("AbpUserClaims");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b =>
{
b.Property<Guid>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(64);
b.Property<string>("ProviderDisplayName")
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.IsRequired()
.HasMaxLength(196);
b.Property<Guid?>("TenantId");
b.HasKey("UserId", "LoginProvider");
b.HasIndex("LoginProvider", "ProviderKey");
b.ToTable("AbpUserLogins");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b =>
{
b.Property<Guid>("UserId");
b.Property<Guid>("RoleId");
b.Property<Guid?>("TenantId");
b.HasKey("UserId", "RoleId");
b.HasIndex("RoleId", "UserId");
b.ToTable("AbpUserRoles");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b =>
{
b.Property<Guid>("UserId");
b.Property<string>("LoginProvider")
.HasMaxLength(128);
b.Property<string>("Name");
b.Property<Guid?>("TenantId");
b.Property<string>("Value");
b.HasKey("UserId", "LoginProvider", "Name");
b.ToTable("AbpUserTokens");
});
modelBuilder.Entity("Volo.Abp.PermissionManagement.PermissionGrant", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.IsRequired()
.HasMaxLength(64);
b.Property<string>("ProviderName")
.IsRequired()
.HasMaxLength(64);
b.Property<Guid?>("TenantId");
b.HasKey("Id");
b.HasIndex("Name", "ProviderName", "ProviderKey");
b.ToTable("AbpPermissionGrants");
});
modelBuilder.Entity("Volo.Abp.SettingManagement.Setting", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(128);
b.Property<string>("ProviderKey")
.HasMaxLength(64);
b.Property<string>("ProviderName")
.HasMaxLength(64);
b.Property<string>("Value")
.IsRequired()
.HasMaxLength(2048);
b.HasKey("Id");
b.HasIndex("Name", "ProviderName", "ProviderKey");
b.ToTable("AbpSettings");
});
modelBuilder.Entity("Volo.Blogging.Blogs.Blog", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnName("Description")
.HasMaxLength(1024);
b.Property<string>("Facebook")
.HasColumnName("Facebook")
.HasMaxLength(128);
b.Property<string>("Github")
.HasColumnName("Github")
.HasMaxLength(128);
b.Property<string>("Instagram")
.HasColumnName("Instagram")
.HasMaxLength(128);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasColumnName("Name")
.HasMaxLength(256);
b.Property<string>("ShortName")
.IsRequired()
.HasColumnName("ShortName")
.HasMaxLength(32);
b.Property<string>("StackOverflow")
.HasColumnName("StackOverflow")
.HasMaxLength(128);
b.Property<string>("Twitter")
.HasColumnName("Twitter")
.HasMaxLength(128);
b.HasKey("Id");
b.ToTable("BlgBlogs");
});
modelBuilder.Entity("Volo.Blogging.Comments.Comment", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<Guid>("PostId")
.HasColumnName("PostId");
b.Property<Guid?>("RepliedCommentId")
.HasColumnName("RepliedCommentId");
b.Property<string>("Text")
.IsRequired()
.HasColumnName("Text")
.HasMaxLength(1024);
b.HasKey("Id");
b.HasIndex("PostId");
b.HasIndex("RepliedCommentId");
b.ToTable("BlgComments");
});
modelBuilder.Entity("Volo.Blogging.Posts.Post", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<Guid>("BlogId")
.HasColumnName("BlogId");
b.Property<string>("Content")
.HasColumnName("Content")
.HasMaxLength(1048576);
b.Property<string>("CoverImage")
.IsRequired()
.HasColumnName("CoverImage");
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<int>("ReadCount");
b.Property<string>("Title")
.IsRequired()
.HasColumnName("Title")
.HasMaxLength(512);
b.Property<string>("Url")
.IsRequired()
.HasColumnName("Url")
.HasMaxLength(64);
b.HasKey("Id");
b.HasIndex("BlogId");
b.ToTable("BlgPosts");
});
modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b =>
{
b.Property<Guid>("PostId")
.HasColumnName("PostId");
b.Property<Guid>("TagId")
.HasColumnName("TagId");
b.Property<DateTime>("CreationTime");
b.Property<Guid?>("CreatorId");
b.HasKey("PostId", "TagId");
b.HasIndex("TagId");
b.ToTable("BlgPostTags");
});
modelBuilder.Entity("Volo.Blogging.Tagging.Tag", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<DateTime>("CreationTime")
.HasColumnName("CreationTime");
b.Property<Guid?>("CreatorId")
.HasColumnName("CreatorId");
b.Property<Guid?>("DeleterId")
.HasColumnName("DeleterId");
b.Property<DateTime?>("DeletionTime")
.HasColumnName("DeletionTime");
b.Property<string>("Description")
.HasColumnName("Description")
.HasMaxLength(512);
b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd()
.HasColumnName("IsDeleted")
.HasDefaultValue(false);
b.Property<DateTime?>("LastModificationTime")
.HasColumnName("LastModificationTime");
b.Property<Guid?>("LastModifierId")
.HasColumnName("LastModifierId");
b.Property<string>("Name")
.IsRequired()
.HasColumnName("Name")
.HasMaxLength(64);
b.Property<int>("UsageCount")
.HasColumnName("UsageCount");
b.HasKey("Id");
b.ToTable("BlgTags");
});
modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Email")
.HasColumnName("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("EmailConfirmed")
.HasDefaultValue(false);
b.Property<string>("ExtraProperties")
.HasColumnName("ExtraProperties");
b.Property<string>("PhoneNumber")
.HasColumnName("PhoneNumber")
.HasMaxLength(16);
b.Property<bool>("PhoneNumberConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("PhoneNumberConfirmed")
.HasDefaultValue(false);
b.Property<Guid?>("TenantId")
.HasColumnName("TenantId");
b.Property<string>("UserName")
.IsRequired()
.HasColumnName("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.ToTable("BlgUsers");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityRole")
.WithMany("Claims")
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserClaim", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Claims")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserLogin", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Logins")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserRole", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityRole")
.WithMany()
.HasForeignKey("RoleId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Roles")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityUserToken", b =>
{
b.HasOne("Volo.Abp.Identity.IdentityUser")
.WithMany("Tokens")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Blogging.Comments.Comment", b =>
{
b.HasOne("Volo.Blogging.Posts.Post")
.WithMany()
.HasForeignKey("PostId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Blogging.Comments.Comment")
.WithMany()
.HasForeignKey("RepliedCommentId");
});
modelBuilder.Entity("Volo.Blogging.Posts.Post", b =>
{
b.HasOne("Volo.Blogging.Blogs.Blog")
.WithMany()
.HasForeignKey("BlogId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Volo.Blogging.Posts.PostTag", b =>
{
b.HasOne("Volo.Blogging.Posts.Post")
.WithMany("Tags")
.HasForeignKey("PostId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Volo.Blogging.Tagging.Tag")
.WithMany()
.HasForeignKey("TagId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}

98
modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/20180918061015_SocialLinks_Made_Nullable.cs

@ -0,0 +1,98 @@
using Microsoft.EntityFrameworkCore.Migrations;
namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
{
public partial class SocialLinks_Made_Nullable : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Twitter",
table: "BlgBlogs",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "StackOverflow",
table: "BlgBlogs",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "Instagram",
table: "BlgBlogs",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "Github",
table: "BlgBlogs",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 128);
migrationBuilder.AlterColumn<string>(
name: "Facebook",
table: "BlgBlogs",
maxLength: 128,
nullable: true,
oldClrType: typeof(string),
oldMaxLength: 128);
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.AlterColumn<string>(
name: "Twitter",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldMaxLength: 128,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "StackOverflow",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldMaxLength: 128,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Instagram",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldMaxLength: 128,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Github",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldMaxLength: 128,
oldNullable: true);
migrationBuilder.AlterColumn<string>(
name: "Facebook",
table: "BlgBlogs",
maxLength: 128,
nullable: false,
oldClrType: typeof(string),
oldMaxLength: 128,
oldNullable: true);
}
}
}

59
modules/blogging/app/Volo.BloggingTestApp.EntityFrameworkCore/Migrations/BloggingTestAppDbContextModelSnapshot.cs

@ -305,6 +305,18 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
.HasColumnName("Description") .HasColumnName("Description")
.HasMaxLength(1024); .HasMaxLength(1024);
b.Property<string>("Facebook")
.HasColumnName("Facebook")
.HasMaxLength(128);
b.Property<string>("Github")
.HasColumnName("Github")
.HasMaxLength(128);
b.Property<string>("Instagram")
.HasColumnName("Instagram")
.HasMaxLength(128);
b.Property<bool>("IsDeleted") b.Property<bool>("IsDeleted")
.ValueGeneratedOnAdd() .ValueGeneratedOnAdd()
.HasColumnName("IsDeleted") .HasColumnName("IsDeleted")
@ -326,6 +338,14 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
.HasColumnName("ShortName") .HasColumnName("ShortName")
.HasMaxLength(32); .HasMaxLength(32);
b.Property<string>("StackOverflow")
.HasColumnName("StackOverflow")
.HasMaxLength(128);
b.Property<string>("Twitter")
.HasColumnName("Twitter")
.HasMaxLength(128);
b.HasKey("Id"); b.HasKey("Id");
b.ToTable("BlgBlogs"); b.ToTable("BlgBlogs");
@ -501,6 +521,45 @@ namespace Volo.BloggingTestApp.EntityFrameworkCore.Migrations
b.ToTable("BlgTags"); b.ToTable("BlgTags");
}); });
modelBuilder.Entity("Volo.Blogging.Users.BlogUser", b =>
{
b.Property<Guid>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("Email")
.HasColumnName("Email")
.HasMaxLength(256);
b.Property<bool>("EmailConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("EmailConfirmed")
.HasDefaultValue(false);
b.Property<string>("ExtraProperties")
.HasColumnName("ExtraProperties");
b.Property<string>("PhoneNumber")
.HasColumnName("PhoneNumber")
.HasMaxLength(16);
b.Property<bool>("PhoneNumberConfirmed")
.ValueGeneratedOnAdd()
.HasColumnName("PhoneNumberConfirmed")
.HasDefaultValue(false);
b.Property<Guid?>("TenantId")
.HasColumnName("TenantId");
b.Property<string>("UserName")
.IsRequired()
.HasColumnName("UserName")
.HasMaxLength(256);
b.HasKey("Id");
b.ToTable("BlgUsers");
});
modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b => modelBuilder.Entity("Volo.Abp.Identity.IdentityRoleClaim", b =>
{ {
b.HasOne("Volo.Abp.Identity.IdentityRole") b.HasOne("Volo.Abp.Identity.IdentityRole")

10933
modules/blogging/app/Volo.BloggingTestApp/Logs/logs.txt

File diff suppressed because it is too large

10
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo.Blogging.Application.Contracts.csproj

@ -9,6 +9,16 @@
<RootNamespace /> <RootNamespace />
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<None Remove="Volo\Blogging\Localization\ApplicationContracts\en.json" />
<None Remove="Volo\Blogging\Localization\ApplicationContracts\tr.json" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Volo\Blogging\Localization\ApplicationContracts\en.json" />
<EmbeddedResource Include="Volo\Blogging\Localization\ApplicationContracts\tr.json" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Blogging.Domain.Shared\Volo.Blogging.Domain.Shared.csproj" /> <ProjectReference Include="..\Volo.Blogging.Domain.Shared\Volo.Blogging.Domain.Shared.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Application\Volo.Abp.Ddd.Application.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Application\Volo.Abp.Ddd.Application.csproj" />

14
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingApplicationContractsModule.cs

@ -1,6 +1,9 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Authorization.Permissions; using Volo.Abp.Authorization.Permissions;
using Volo.Abp.Localization;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Abp.VirtualFileSystem;
using Volo.Blogging.Localization;
namespace Volo.Blogging namespace Volo.Blogging
{ {
@ -13,6 +16,17 @@ namespace Volo.Blogging
{ {
options.DefinitionProviders.Add<BloggingPermissionDefinitionProvider>(); options.DefinitionProviders.Add<BloggingPermissionDefinitionProvider>();
}); });
context.Services.Configure<VirtualFileSystemOptions>(options =>
{
options.FileSets.AddEmbedded<BloggingApplicationContractsModule>();
});
Configure<AbpLocalizationOptions>(options =>
{
options.Resources
.Get<BloggingResource>()
.AddVirtualJson("/Localization/Resources/Blogging/ApplicationContracts");
});
} }
} }
} }

7
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissionDefinitionProvider.cs

@ -10,17 +10,18 @@ namespace Volo.Blogging
{ {
var bloggingGroup = context.AddGroup(BloggingPermissions.GroupName, L("Permission:Blogging")); var bloggingGroup = context.AddGroup(BloggingPermissions.GroupName, L("Permission:Blogging"));
var blogs = bloggingGroup.AddPermission(BloggingPermissions.Blogs.Default, L("Permission:BlogManagement")); var blogs = bloggingGroup.AddPermission(BloggingPermissions.Blogs.Default, L("Permission:Blogs"));
blogs.AddChild(BloggingPermissions.Blogs.Management, L("Permission:Management"));
blogs.AddChild(BloggingPermissions.Blogs.Update, L("Permission:Edit")); blogs.AddChild(BloggingPermissions.Blogs.Update, L("Permission:Edit"));
blogs.AddChild(BloggingPermissions.Blogs.Delete, L("Permission:Delete")); blogs.AddChild(BloggingPermissions.Blogs.Delete, L("Permission:Delete"));
blogs.AddChild(BloggingPermissions.Blogs.Create, L("Permission:Create")); blogs.AddChild(BloggingPermissions.Blogs.Create, L("Permission:Create"));
var posts = bloggingGroup.AddPermission(BloggingPermissions.Posts.Default, L("Permission:PostManagement")); var posts = bloggingGroup.AddPermission(BloggingPermissions.Posts.Default, L("Permission:Posts"));
posts.AddChild(BloggingPermissions.Posts.Update, L("Permission:Edit")); posts.AddChild(BloggingPermissions.Posts.Update, L("Permission:Edit"));
posts.AddChild(BloggingPermissions.Posts.Delete, L("Permission:Delete")); posts.AddChild(BloggingPermissions.Posts.Delete, L("Permission:Delete"));
posts.AddChild(BloggingPermissions.Posts.Create, L("Permission:Create")); posts.AddChild(BloggingPermissions.Posts.Create, L("Permission:Create"));
var tags = bloggingGroup.AddPermission(BloggingPermissions.Tags.Default, L("Permission:Tag")); var tags = bloggingGroup.AddPermission(BloggingPermissions.Tags.Default, L("Permission:Tags"));
tags.AddChild(BloggingPermissions.Tags.Update, L("Permission:Edit")); tags.AddChild(BloggingPermissions.Tags.Update, L("Permission:Edit"));
tags.AddChild(BloggingPermissions.Tags.Delete, L("Permission:Delete")); tags.AddChild(BloggingPermissions.Tags.Delete, L("Permission:Delete"));
tags.AddChild(BloggingPermissions.Tags.Create, L("Permission:Create")); tags.AddChild(BloggingPermissions.Tags.Create, L("Permission:Create"));

3
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/BloggingPermissions.cs

@ -7,9 +7,11 @@
public static class Blogs public static class Blogs
{ {
public const string Default = GroupName + ".Blog"; public const string Default = GroupName + ".Blog";
public const string Management = Default + ".Management";
public const string Delete = Default + ".Delete"; public const string Delete = Default + ".Delete";
public const string Update = Default + ".Update"; public const string Update = Default + ".Update";
public const string Create = Default + ".Create"; public const string Create = Default + ".Create";
} }
public static class Posts public static class Posts
@ -42,6 +44,7 @@
{ {
GroupName, GroupName,
Blogs.Default, Blogs.Default,
Blogs.Management,
Blogs.Delete, Blogs.Delete,
Blogs.Update, Blogs.Update,
Blogs.Create, Blogs.Create,

14
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/BlogDto.cs

@ -1,14 +0,0 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Blogging.Blogs
{
public class BlogDto : EntityDto<Guid>
{
public string Name { get; set; }
public string ShortName { get; set; }
public string Description { get; set; }
}
}

24
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/Dtos/BlogDto.cs

@ -0,0 +1,24 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Blogging.Blogs.Dtos
{
public class BlogDto : FullAuditedEntityDto<Guid>
{
public string Name { get; set; }
public string ShortName { get; set; }
public string Description { get; set; }
public string Facebook { get; set; }
public string Twitter { get; set; }
public string Instagram { get; set; }
public string Github { get; set; }
public string StackOverflow { get; set; }
}
}

21
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/Dtos/CreateBlogDto.cs

@ -0,0 +1,21 @@
namespace Volo.Blogging.Blogs.Dtos
{
public class CreateBlogDto
{
public string Name { get; set; }
public string ShortName { get; set; }
public string Description { get; set; }
public string Facebook { get; set; }
public string Twitter { get; set; }
public string Instagram { get; set; }
public string Github { get; set; }
public string StackOverflow { get; set; }
}
}

23
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/Dtos/UpdateBlogDto.cs

@ -0,0 +1,23 @@
using System;
namespace Volo.Blogging.Blogs.Dtos
{
public class UpdateBlogDto
{
public string Name { get; set; }
public string ShortName { get; set; }
public string Description { get; set; }
public string Facebook { get; set; }
public string Twitter { get; set; }
public string Instagram { get; set; }
public string Github { get; set; }
public string StackOverflow { get; set; }
}
}

9
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Blogs/IBlogAppService.cs

@ -2,15 +2,24 @@
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Blogging.Blogs.Dtos;
namespace Volo.Blogging.Blogs namespace Volo.Blogging.Blogs
{ {
public interface IBlogAppService : IApplicationService public interface IBlogAppService : IApplicationService
{ {
Task<PagedResultDto<BlogDto>> GetListPagedAsync(PagedAndSortedResultRequestDto input);
Task<ListResultDto<BlogDto>> GetListAsync(); Task<ListResultDto<BlogDto>> GetListAsync();
Task<BlogDto> GetByShortNameAsync(string shortName); Task<BlogDto> GetByShortNameAsync(string shortName);
Task<BlogDto> GetAsync(Guid id); Task<BlogDto> GetAsync(Guid id);
Task<BlogDto> Create(CreateBlogDto input);
Task<BlogDto> Update(Guid id, UpdateBlogDto input);
Task Delete(Guid id);
} }
} }

5
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Comments/Dtos/CommentDto.cs → modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Comments/Dtos/CommentWithDetailsDto.cs

@ -1,12 +1,15 @@
using System; using System;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Blogging.Posts;
namespace Volo.Blogging.Comments.Dtos namespace Volo.Blogging.Comments.Dtos
{ {
public class CommentDto : FullAuditedEntityDto<Guid> public class CommentWithDetailsDto : FullAuditedEntityDto<Guid>
{ {
public Guid? RepliedCommentId { get; set; } public Guid? RepliedCommentId { get; set; }
public string Text { get; set; } public string Text { get; set; }
public BlogUserDto Writer { get; set; }
} }
} }

4
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Comments/Dtos/CommentWithRepliesDto.cs

@ -4,8 +4,8 @@ namespace Volo.Blogging.Comments.Dtos
{ {
public class CommentWithRepliesDto public class CommentWithRepliesDto
{ {
public CommentDto Comment { get; set; } public CommentWithDetailsDto Comment { get; set; }
public List<CommentDto> Replies { get; set; } = new List<CommentDto>(); public List<CommentWithDetailsDto> Replies { get; set; } = new List<CommentWithDetailsDto>();
} }
} }

4
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Comments/ICommentAppService.cs

@ -10,9 +10,9 @@ namespace Volo.Blogging.Comments
{ {
Task<List<CommentWithRepliesDto>> GetHierarchicalListOfPostAsync(GetCommentListOfPostAsync input); Task<List<CommentWithRepliesDto>> GetHierarchicalListOfPostAsync(GetCommentListOfPostAsync input);
Task<CommentDto> CreateAsync(CreateCommentDto input); Task<CommentWithDetailsDto> CreateAsync(CreateCommentDto input);
Task<CommentDto> UpdateAsync(Guid id, UpdateCommentDto input); Task<CommentWithDetailsDto> UpdateAsync(Guid id, UpdateCommentDto input);
Task DeleteAsync(Guid id); Task DeleteAsync(Guid id);
} }

14
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/ApplicationContracts/en.json

@ -0,0 +1,14 @@
{
"culture": "en",
"texts": {
"Permission:Blogging": "Blog",
"Permission:Blogs": "Blogs",
"Permission:Posts": "Posts",
"Permission:Tags": "Tags",
"Permission:Comments": "Comments",
"Permission:Management": "Management",
"Permission:Edit": "Edit",
"Permission:Create": "Create",
"Permission:Delete": "Delete"
}
}

14
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Localization/ApplicationContracts/tr.json

@ -0,0 +1,14 @@
{
"culture": "tr",
"texts": {
"Permission:Blogging": "Blog",
"Permission:Blogs": "Bloglar",
"Permission:Posts": "Yazılar",
"Permission:Tags": "Etiketler",
"Permission:Comments": "Yorumlar",
"Permission:Management": "Yönetme",
"Permission:Edit": "Düzenle",
"Permission:Create": "Ekle",
"Permission:Delete": "Sil"
}
}

24
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/BlogUserDto.cs

@ -0,0 +1,24 @@
using System;
using System.Collections.Generic;
using System.Text;
using Volo.Abp.Application.Dtos;
namespace Volo.Blogging.Posts
{
public class BlogUserDto : EntityDto<Guid>
{
public Guid? TenantId { get; set; }
public string UserName { get; set; }
public string Email { get; set; }
public bool EmailConfirmed { get; set; }
public string PhoneNumber { get; set; }
public bool PhoneNumberConfirmed { get; set; }
public Dictionary<string, object> ExtraProperties { get; set; }
}
}

2
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/GetPostInput.cs

@ -1,4 +1,4 @@
using System;  using System;
namespace Volo.Blogging.Posts namespace Volo.Blogging.Posts
{ {

2
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/IPostAppService.cs

@ -13,6 +13,8 @@ namespace Volo.Blogging.Posts
Task<PostWithDetailsDto> GetAsync(Guid id); Task<PostWithDetailsDto> GetAsync(Guid id);
Task DeleteAsync(Guid id);
Task<PostWithDetailsDto> CreateAsync(CreatePostDto input); Task<PostWithDetailsDto> CreateAsync(CreatePostDto input);
Task<PostWithDetailsDto> UpdateAsync(Guid id, UpdatePostDto input); Task<PostWithDetailsDto> UpdateAsync(Guid id, UpdatePostDto input);

20
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/PostDto.cs

@ -1,20 +0,0 @@
using System;
using Volo.Abp.Application.Dtos;
namespace Volo.Blogging.Posts
{
public class PostDto : FullAuditedEntityDto<Guid>
{
public Guid BlogId { get; protected set; }
public string Title { get; protected set; }
public string CoverImage { get; protected set; }
public string Url { get; set; }
public int ReadCount { get; set; }
public string Content { get; set; }
}
}

2
modules/blogging/src/Volo.Blogging.Application.Contracts/Volo/Blogging/Posts/PostWithDetailsDto.cs

@ -21,6 +21,8 @@ namespace Volo.Blogging.Posts
public int CommentCount { get; set; } public int CommentCount { get; set; }
public BlogUserDto Writer { get; set; }
public List<TagDto> Tags { get; set; } public List<TagDto> Tags { get; set; }
} }
} }

6
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationAutoMapperProfile.cs

@ -1,6 +1,7 @@
using AutoMapper; using AutoMapper;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Blogging.Blogs; using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Comments; using Volo.Blogging.Comments;
using Volo.Blogging.Comments.Dtos; using Volo.Blogging.Comments.Dtos;
using Volo.Blogging.Posts; using Volo.Blogging.Posts;
@ -14,9 +15,8 @@ namespace Volo.Blogging
public BloggingApplicationAutoMapperProfile() public BloggingApplicationAutoMapperProfile()
{ {
CreateMap<Blog, BlogDto>(); CreateMap<Blog, BlogDto>();
CreateMap<Post, PostDto>(); CreateMap<Post, PostWithDetailsDto>().Ignore(x=>x.Writer).Ignore(x=>x.CommentCount);
CreateMap<Post, PostWithDetailsDto>().Ignore(x=>x.CommentCount); CreateMap<Comment, CommentWithDetailsDto>().Ignore(x => x.Writer);
CreateMap<Comment, CommentDto>();
CreateMap<Tag, TagDto>(); CreateMap<Tag, TagDto>();
} }
} }

18
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/BloggingApplicationModule.cs

@ -1,7 +1,10 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Abp.Caching; using Volo.Abp.Caching;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
using Volo.Blogging.Comments;
using Volo.Blogging.Posts;
namespace Volo.Blogging namespace Volo.Blogging
{ {
@ -18,6 +21,19 @@ namespace Volo.Blogging
{ {
options.AddProfile<BloggingApplicationAutoMapperProfile>(validate: true); options.AddProfile<BloggingApplicationAutoMapperProfile>(validate: true);
}); });
context.Services.Configure<AuthorizationOptions>(options =>
{
//TODO: Rename UpdatePolicy/DeletePolicy since it's candidate to conflicts with other modules!
options.AddPolicy("BloggingUpdatePolicy", policy => policy.Requirements.Add(CommonOperations.Update));
options.AddPolicy("BloggingDeletePolicy", policy => policy.Requirements.Add(CommonOperations.Delete));
});
context.Services.AddSingleton<IAuthorizationHandler, CommentAuthorizationHandler>();
context.Services.AddSingleton<IAuthorizationHandler, PostAuthorizationHandler>();
} }
} }
} }

53
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Blogs/BlogAppService.cs

@ -1,9 +1,11 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Dtos; using Volo.Abp.Application.Dtos;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Domain.Entities; using Volo.Abp.Domain.Entities;
using Volo.Blogging.Blogs.Dtos;
namespace Volo.Blogging.Blogs namespace Volo.Blogging.Blogs
{ {
@ -15,6 +17,18 @@ namespace Volo.Blogging.Blogs
{ {
_blogRepository = blogRepository; _blogRepository = blogRepository;
} }
public async Task<PagedResultDto<BlogDto>> GetListPagedAsync(PagedAndSortedResultRequestDto input)
{
var blogs = await _blogRepository.GetListAsync(input.Sorting, input.MaxResultCount, input.SkipCount );
var totalCount = await _blogRepository.GetTotalBlogCount();
var dtos = ObjectMapper.Map<List<Blog>, List<BlogDto>>(blogs);
return new PagedResultDto<BlogDto>(totalCount, dtos);
}
public async Task<ListResultDto<BlogDto>> GetListAsync() public async Task<ListResultDto<BlogDto>> GetListAsync()
{ {
var blogs = await _blogRepository.GetListAsync(); var blogs = await _blogRepository.GetListAsync();
@ -42,5 +56,44 @@ namespace Volo.Blogging.Blogs
return ObjectMapper.Map<Blog, BlogDto>(blog); return ObjectMapper.Map<Blog, BlogDto>(blog);
} }
[Authorize(BloggingPermissions.Blogs.Create)]
public async Task<BlogDto> Create(CreateBlogDto input)
{
var newBlog = await _blogRepository.InsertAsync(new Blog(GuidGenerator.Create(), input.Name, input.ShortName)
{
Description = input.Description,
Facebook = input.Facebook,
Twitter = input.Twitter,
Instagram = input.Instagram,
Github = input.Github,
StackOverflow = input.StackOverflow
});
return ObjectMapper.Map<Blog, BlogDto>(newBlog);
}
[Authorize(BloggingPermissions.Blogs.Update)]
public async Task<BlogDto> Update(Guid id, UpdateBlogDto input)
{
var blog = await _blogRepository.GetAsync(id);
blog.SetName(input.Name);
blog.SetShortName(input.ShortName);
blog.Description = input.Description;
blog.Facebook = input.Facebook;
blog.Twitter = input.Twitter;
blog.Instagram = input.Instagram;
blog.Github = input.Github;
blog.StackOverflow = input.StackOverflow;
return ObjectMapper.Map<Blog, BlogDto>(blog);
}
[Authorize(BloggingPermissions.Blogs.Delete)]
public async Task Delete(Guid id)
{
await _blogRepository.DeleteAsync(id);
}
} }
} }

79
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAppService.cs

@ -5,25 +5,53 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Volo.Abp.Application.Services; using Volo.Abp.Application.Services;
using Volo.Abp.Guids; using Volo.Abp.Guids;
using Volo.Abp.Users;
using Volo.Blogging.Comments.Dtos; using Volo.Blogging.Comments.Dtos;
using Volo.Blogging.Posts;
using Volo.Blogging.Users;
namespace Volo.Blogging.Comments namespace Volo.Blogging.Comments
{ {
// [Authorize(BloggingPermissions.Comments.Default)]
public class CommentAppService : ApplicationService, ICommentAppService public class CommentAppService : ApplicationService, ICommentAppService
{ {
protected IBlogUserLookupService UserLookupService;
private readonly ICommentRepository _commentRepository; private readonly ICommentRepository _commentRepository;
private readonly IGuidGenerator _guidGenerator; private readonly IGuidGenerator _guidGenerator;
public CommentAppService(ICommentRepository commentRepository, IGuidGenerator guidGenerator) public CommentAppService(ICommentRepository commentRepository, IGuidGenerator guidGenerator, IBlogUserLookupService userLookupService)
{ {
_commentRepository = commentRepository; _commentRepository = commentRepository;
_guidGenerator = guidGenerator; _guidGenerator = guidGenerator;
UserLookupService = userLookupService;
} }
public async Task<List<CommentWithRepliesDto>> GetHierarchicalListOfPostAsync(GetCommentListOfPostAsync input) public async Task<List<CommentWithRepliesDto>> GetHierarchicalListOfPostAsync(GetCommentListOfPostAsync input)
{ {
var comments = await GetListOfPostAsync(input); var comments = await GetListOfPostAsync(input);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
foreach (var commentDto in comments)
{
if (commentDto.CreatorId.HasValue)
{
var creatorUser = await UserLookupService.FindByIdAsync(commentDto.CreatorId.Value);
if (creatorUser != null && !userDictionary.ContainsKey(creatorUser.Id))
{
userDictionary.Add(creatorUser.Id, ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser));
}
}
}
foreach (var commentDto in comments)
{
if (commentDto.CreatorId.HasValue && userDictionary.ContainsKey((Guid)commentDto.CreatorId))
{
commentDto.Writer = userDictionary[(Guid)commentDto.CreatorId];
}
}
var hierarchicalComments = new List<CommentWithRepliesDto>(); var hierarchicalComments = new List<CommentWithRepliesDto>();
foreach (var commentDto in comments) foreach (var commentDto in comments)
@ -45,72 +73,43 @@ namespace Volo.Blogging.Comments
return hierarchicalComments; return hierarchicalComments;
} }
private async Task<List<CommentDto>> GetListOfPostAsync(GetCommentListOfPostAsync input) private async Task<List<CommentWithDetailsDto>> GetListOfPostAsync(GetCommentListOfPostAsync input)
{ {
var comments = await _commentRepository.GetListOfPostAsync(input.PostId); var comments = await _commentRepository.GetListOfPostAsync(input.PostId);
return new List<CommentDto>( return new List<CommentWithDetailsDto>(
ObjectMapper.Map<List<Comment>, List<CommentDto>>(comments)); ObjectMapper.Map<List<Comment>, List<CommentWithDetailsDto>>(comments));
} }
[Authorize(BloggingPermissions.Comments.Create)] [Authorize(BloggingPermissions.Comments.Create)]
public async Task<CommentDto> CreateAsync(CreateCommentDto input) public async Task<CommentWithDetailsDto> CreateAsync(CreateCommentDto input)
{ {
var comment = new Comment(_guidGenerator.Create(), input.PostId, input.RepliedCommentId, input.Text); var comment = new Comment(_guidGenerator.Create(), input.PostId, input.RepliedCommentId, input.Text);
comment = await _commentRepository.InsertAsync(comment); comment = await _commentRepository.InsertAsync(comment);
return ObjectMapper.Map<Comment, CommentDto>(comment); return ObjectMapper.Map<Comment, CommentWithDetailsDto>(comment);
} }
public async Task<CommentDto> UpdateAsync(Guid id, UpdateCommentDto input) public async Task<CommentWithDetailsDto> UpdateAsync(Guid id, UpdateCommentDto input)
{ {
var comment = await _commentRepository.GetAsync(id); var comment = await _commentRepository.GetAsync(id);
if (CurrentUser.Id != comment.CreatorId) await AuthorizationService.CheckAsync(comment, CommonOperations.Update);
{
return await UpdateAsAdminAsync(id, comment, input);
}
return await UpdateCommentAsync(id, comment, input);
}
[Authorize(BloggingPermissions.Comments.Update)]
private async Task<CommentDto> UpdateAsAdminAsync(Guid id, Comment comment, UpdateCommentDto input)
{
return await UpdateCommentAsync(id, comment, input);
}
private async Task<CommentDto> UpdateCommentAsync(Guid id, Comment comment, UpdateCommentDto input)
{
comment.SetText(input.Text); comment.SetText(input.Text);
comment = await _commentRepository.UpdateAsync(comment); comment = await _commentRepository.UpdateAsync(comment);
return ObjectMapper.Map<Comment, CommentDto>(comment); return ObjectMapper.Map<Comment, CommentWithDetailsDto>(comment);
} }
public async Task DeleteAsync(Guid id) public async Task DeleteAsync(Guid id)
{ {
var comment = await _commentRepository.GetAsync(id); var comment = await _commentRepository.GetAsync(id);
if (CurrentUser.Id != comment.CreatorId) await AuthorizationService.CheckAsync(comment, CommonOperations.Delete);
{
await DeleteAsAdminAsync(id);
return;
}
await DeleteCommentAsync(id);
}
[Authorize(BloggingPermissions.Comments.Delete)]
private async Task DeleteAsAdminAsync(Guid id)
{
await DeleteCommentAsync(id);
}
private async Task DeleteCommentAsync(Guid id)
{
await _commentRepository.DeleteAsync(id); await _commentRepository.DeleteAsync(id);
var replies = await _commentRepository.GetRepliesOfComment(id); var replies = await _commentRepository.GetRepliesOfComment(id);

61
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Comments/CommentAuthorizationHandler.cs

@ -0,0 +1,61 @@
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Volo.Abp.Authorization.Permissions;
namespace Volo.Blogging.Comments
{
public class CommentAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Comment>
{
private readonly IPermissionChecker _permissionChecker;
public CommentAuthorizationHandler(IPermissionChecker permissionChecker)
{
_permissionChecker = permissionChecker;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Comment resource)
{
if (requirement.Name == CommonOperations.Delete.Name && await HasDeletePermission(context, resource))
{
context.Succeed(requirement);
return;
}
if (requirement.Name == CommonOperations.Update.Name && await HasUpdatePermission(context, resource))
{
context.Succeed(requirement);
return;
}
}
private async Task<bool> HasDeletePermission(AuthorizationHandlerContext context, Comment resource)
{
if (await _permissionChecker.IsGrantedAsync(context.User, BloggingPermissions.Comments.Delete))
{
return true;
}
return false;
}
private async Task<bool> HasUpdatePermission(AuthorizationHandlerContext context, Comment resource)
{
if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
{
return true;
}
if (await _permissionChecker.IsGrantedAsync(context.User, BloggingPermissions.Comments.Update))
{
return true;
}
return false;
}
}
}

13
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/CommonOperations.cs

@ -0,0 +1,13 @@
using System;
using System.Collections.Generic;
using System.Text;
using Microsoft.AspNetCore.Authorization.Infrastructure;
namespace Volo.Blogging
{
public static class CommonOperations
{
public static OperationAuthorizationRequirement Update = new OperationAuthorizationRequirement { Name = nameof(Update) };
public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = nameof(Delete) };
}
}

114
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAppService.cs

@ -9,6 +9,7 @@ using Volo.Abp.Users;
using Volo.Blogging.Comments; using Volo.Blogging.Comments;
using Volo.Blogging.Tagging; using Volo.Blogging.Tagging;
using Volo.Blogging.Tagging.Dtos; using Volo.Blogging.Tagging.Dtos;
using Volo.Blogging.Users;
namespace Volo.Blogging.Posts namespace Volo.Blogging.Posts
{ {
@ -19,13 +20,16 @@ namespace Volo.Blogging.Posts
//[Authorize(BloggingPermissions.Posts.Default)] //[Authorize(BloggingPermissions.Posts.Default)]
public class PostAppService : ApplicationService, IPostAppService public class PostAppService : ApplicationService, IPostAppService
{ {
protected IBlogUserLookupService UserLookupService { get; }
private readonly IPostRepository _postRepository; private readonly IPostRepository _postRepository;
private readonly ITagRepository _tagRepository; private readonly ITagRepository _tagRepository;
private readonly IPostTagRepository _postTagRepository; private readonly IPostTagRepository _postTagRepository;
private readonly ICommentRepository _commentRepository; private readonly ICommentRepository _commentRepository;
public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, IPostTagRepository postTagRepository, ICommentRepository commentRepository) public PostAppService(IPostRepository postRepository, ITagRepository tagRepository, IPostTagRepository postTagRepository, ICommentRepository commentRepository, IBlogUserLookupService userLookupService)
{ {
UserLookupService = userLookupService;
_postRepository = postRepository; _postRepository = postRepository;
_tagRepository = tagRepository; _tagRepository = tagRepository;
_postTagRepository = postTagRepository; _postTagRepository = postTagRepository;
@ -35,26 +39,61 @@ namespace Volo.Blogging.Posts
public async Task<ListResultDto<PostWithDetailsDto>> GetListByBlogIdAndTagName(Guid id, string tagName) public async Task<ListResultDto<PostWithDetailsDto>> GetListByBlogIdAndTagName(Guid id, string tagName)
{ {
var posts = _postRepository.GetPostsByBlogId(id); var posts = _postRepository.GetPostsByBlogId(id);
var tag = tagName.IsNullOrWhiteSpace() ? null : await _tagRepository.FindByNameAsync(tagName);
var userDictionary = new Dictionary<Guid, BlogUserDto>();
var postDtos = new List<PostWithDetailsDto>(ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts));
var postDtos = new List<PostWithDetailsDto>( if (tag != null)
ObjectMapper.Map<List<Post>, List<PostWithDetailsDto>>(posts)); {
postDtos = await FilterPostsByTag(postDtos, tag);
}
foreach (var postDto in postDtos) foreach (var postDto in postDtos)
{ {
postDto.Tags = await GetTagsOfPost(postDto);
postDto.CommentCount = await _commentRepository.GetCommentCountOfPostAsync(postDto.Id); postDto.CommentCount = await _commentRepository.GetCommentCountOfPostAsync(postDto.Id);
} }
if (!tagName.IsNullOrWhiteSpace()) foreach (var postDto in postDtos)
{ {
var tag = await _tagRepository.GetByNameAsync(tagName); if (postDto.CreatorId.HasValue)
postDtos = postDtos.Where(p => p.Tags.Any(t => t.Id == tag.Id)).ToList(); {
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
if (creatorUser != null && !userDictionary.ContainsKey(creatorUser.Id))
{
userDictionary.Add(creatorUser.Id, ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser));
}
}
}
foreach (var postDto in postDtos)
{
if (postDto.CreatorId.HasValue && userDictionary.ContainsKey((Guid)postDto.CreatorId))
{
postDto.Writer = userDictionary[(Guid)postDto.CreatorId];
}
} }
return new ListResultDto<PostWithDetailsDto>(postDtos); return new ListResultDto<PostWithDetailsDto>(postDtos);
} }
private async Task<List<PostWithDetailsDto>> FilterPostsByTag(List<PostWithDetailsDto> allPostDtos, Tag tag)
{
var filteredPostDtos = new List<PostWithDetailsDto>();
foreach (var postDto in allPostDtos)
{
if (await _postTagRepository.FindByTagIdAndPostIdAsync(postDto.Id, tag.Id) == null)
{
continue;
}
filteredPostDtos.Add(postDto);
}
return filteredPostDtos;
}
public async Task<PostWithDetailsDto> GetForReadingAsync(GetPostInput input) public async Task<PostWithDetailsDto> GetForReadingAsync(GetPostInput input)
{ {
var post = await _postRepository.GetPostByUrl(input.BlogId, input.Url); var post = await _postRepository.GetPostByUrl(input.BlogId, input.Url);
@ -63,7 +102,14 @@ namespace Volo.Blogging.Posts
var postDto = ObjectMapper.Map<Post, PostWithDetailsDto>(post); var postDto = ObjectMapper.Map<Post, PostWithDetailsDto>(post);
postDto.Tags = await GetTagsOfPost(postDto); postDto.Tags = await GetTagsOfPost(postDto.Id);
if (postDto.CreatorId.HasValue)
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
postDto.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
return postDto; return postDto;
} }
@ -74,16 +120,41 @@ namespace Volo.Blogging.Posts
var postDto = ObjectMapper.Map<Post, PostWithDetailsDto>(post); var postDto = ObjectMapper.Map<Post, PostWithDetailsDto>(post);
postDto.Tags = await GetTagsOfPost(postDto); postDto.Tags = await GetTagsOfPost(postDto.Id);
if (postDto.CreatorId.HasValue)
{
var creatorUser = await UserLookupService.FindByIdAsync(postDto.CreatorId.Value);
postDto.Writer = ObjectMapper.Map<BlogUser, BlogUserDto>(creatorUser);
}
return postDto; return postDto;
} }
public async Task DeleteAsync(Guid id)
{
var post = await _postRepository.GetAsync(id);
await AuthorizationService.CheckAsync(post, CommonOperations.Delete);
var tags = await GetTagsOfPost(id);
_tagRepository.DecreaseUsageCountOfTags(tags.Select(t => t.Id).ToList());
_postTagRepository.DeleteOfPost(id);
_commentRepository.DeleteOfPost(id);
await _postRepository.DeleteAsync(id);
}
[Authorize(BloggingPermissions.Posts.Update)] [Authorize(BloggingPermissions.Posts.Update)]
public async Task<PostWithDetailsDto> UpdateAsync(Guid id, UpdatePostDto input) public async Task<PostWithDetailsDto> UpdateAsync(Guid id, UpdatePostDto input)
{ {
var post = await _postRepository.GetAsync(id); var post = await _postRepository.GetAsync(id);
input.Url = await RenameUrlIfItAlreadyExistAsync(input.BlogId, input.Url, post);
await AuthorizationService.CheckAsync(post, CommonOperations.Update);
post.SetTitle(input.Title); post.SetTitle(input.Title);
post.SetUrl(input.Url); post.SetUrl(input.Url);
post.Content = input.Content; post.Content = input.Content;
@ -100,6 +171,8 @@ namespace Volo.Blogging.Posts
[Authorize(BloggingPermissions.Posts.Create)] [Authorize(BloggingPermissions.Posts.Create)]
public async Task<PostWithDetailsDto> CreateAsync(CreatePostDto input) public async Task<PostWithDetailsDto> CreateAsync(CreatePostDto input)
{ {
input.Url = await RenameUrlIfItAlreadyExistAsync(input.BlogId, input.Url);
var post = new Post( var post = new Post(
id: GuidGenerator.Create(), id: GuidGenerator.Create(),
blogId: input.BlogId, blogId: input.BlogId,
@ -107,7 +180,8 @@ namespace Volo.Blogging.Posts
title: input.Title, title: input.Title,
coverImage: input.CoverImage, coverImage: input.CoverImage,
url: input.Url url: input.Url
) {Content = input.Content}; )
{ Content = input.Content };
await _postRepository.InsertAsync(post); await _postRepository.InsertAsync(post);
@ -117,6 +191,18 @@ namespace Volo.Blogging.Posts
return ObjectMapper.Map<Post, PostWithDetailsDto>(post); return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
} }
private async Task<string> RenameUrlIfItAlreadyExistAsync(Guid blogId, string url, Post existingPost = null)
{
var postList = await _postRepository.GetListAsync();
if (postList.Where(p => p.Url == url).WhereIf(existingPost != null, p => existingPost.Id != p.Id).Any())
{
return url + "-" + Guid.NewGuid().ToString().Substring(0, 5);
}
return url;
}
private async Task SaveTags(List<String> newTags, Post post) private async Task SaveTags(List<String> newTags, Post post)
{ {
@ -171,9 +257,9 @@ namespace Volo.Blogging.Posts
} }
} }
private async Task<List<TagDto>> GetTagsOfPost(PostWithDetailsDto postDto) private async Task<List<TagDto>> GetTagsOfPost(Guid id)
{ {
var tagIds = (await _postTagRepository.GetListAsync()).Where(pt => pt.PostId == postDto.Id); var tagIds = (await _postTagRepository.GetListAsync()).Where(pt => pt.PostId == id);
var tags = await _tagRepository.GetListAsync(tagIds.Select(t => t.TagId)); var tags = await _tagRepository.GetListAsync(tagIds.Select(t => t.TagId));
@ -186,7 +272,7 @@ namespace Volo.Blogging.Posts
{ {
return new List<string>(); return new List<string>();
} }
return new List<string>(tags.Split(",").Select(t=>t.Trim())); return new List<string>(tags.Split(",").Select(t => t.Trim()));
} }
} }
} }

61
modules/blogging/src/Volo.Blogging.Application/Volo/Blogging/Posts/PostAuthorizationHandler.cs

@ -0,0 +1,61 @@
using System.Security.Principal;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization.Infrastructure;
using Volo.Abp.Authorization.Permissions;
namespace Volo.Blogging.Posts
{
public class PostAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Post>
{
private readonly IPermissionChecker _permissionChecker;
public PostAuthorizationHandler(IPermissionChecker permissionChecker)
{
_permissionChecker = permissionChecker;
}
protected override async Task HandleRequirementAsync(
AuthorizationHandlerContext context,
OperationAuthorizationRequirement requirement,
Post resource)
{
if (requirement.Name == CommonOperations.Delete.Name && await HasDeletePermission(context, resource))
{
context.Succeed(requirement);
return;
}
if (requirement.Name == CommonOperations.Update.Name && await HasUpdatePermission(context, resource))
{
context.Succeed(requirement);
return;
}
}
private async Task<bool> HasDeletePermission(AuthorizationHandlerContext context, Post resource)
{
if (await _permissionChecker.IsGrantedAsync(context.User, BloggingPermissions.Comments.Delete))
{
return true;
}
return false;
}
private async Task<bool> HasUpdatePermission(AuthorizationHandlerContext context, Post resource)
{
if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
{
return true;
}
if (await _permissionChecker.IsGrantedAsync(context.User, BloggingPermissions.Comments.Update))
{
return true;
}
return false;
}
}
}

2
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Blogs/BlogConsts.cs

@ -7,5 +7,7 @@
public const int MaxShortNameLength = 32; public const int MaxShortNameLength = 32;
public const int MaxDescriptionLength = 1024; public const int MaxDescriptionLength = 1024;
public const int MaxSocialLinkLength = 128;
} }
} }

0
modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/BlogConsts.cs → modules/blogging/src/Volo.Blogging.Domain.Shared/Volo/Blogging/Posts/PostConsts.cs

1
modules/blogging/src/Volo.Blogging.Domain/Volo.Blogging.Domain.csproj

@ -11,6 +11,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Blogging.Domain.Shared\Volo.Blogging.Domain.Shared.csproj" /> <ProjectReference Include="..\Volo.Blogging.Domain.Shared\Volo.Blogging.Domain.Shared.csproj" />
<ProjectReference Include="..\..\..\users\src\Volo.Abp.Users.Domain\Volo.Abp.Users.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.Ddd.Domain\Volo.Abp.Ddd.Domain.csproj" />
</ItemGroup> </ItemGroup>

21
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/Blog.cs

@ -16,6 +16,21 @@ namespace Volo.Blogging.Blogs
[CanBeNull] [CanBeNull]
public virtual string Description { get; set; } public virtual string Description { get; set; }
[CanBeNull]
public virtual string Facebook { get; set; }
[CanBeNull]
public virtual string Twitter { get; set; }
[CanBeNull]
public virtual string Instagram { get; set; }
[CanBeNull]
public virtual string Github { get; set; }
[CanBeNull]
public virtual string StackOverflow { get; set; }
protected Blog() protected Blog()
{ {
@ -33,5 +48,11 @@ namespace Volo.Blogging.Blogs
Name = Check.NotNullOrWhiteSpace(name, nameof(name)); Name = Check.NotNullOrWhiteSpace(name, nameof(name));
return this; return this;
} }
public virtual Blog SetShortName(string shortName)
{
ShortName = Check.NotNullOrWhiteSpace(shortName, nameof(shortName));
return this;
}
} }
} }

5
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Blogs/IBlogRepository.cs

@ -1,4 +1,5 @@
using System; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories; using Volo.Abp.Domain.Repositories;
@ -7,5 +8,9 @@ namespace Volo.Blogging.Blogs
public interface IBlogRepository : IBasicRepository<Blog, Guid> public interface IBlogRepository : IBasicRepository<Blog, Guid>
{ {
Task<Blog> FindByShortNameAsync(string shortName); Task<Blog> FindByShortNameAsync(string shortName);
Task<List<Blog>> GetListAsync(string sorting, int maxResultCount, int skipCount);
Task<int> GetTotalBlogCount();
} }
} }

2
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Comments/ICommentRepository.cs

@ -14,5 +14,7 @@ namespace Volo.Blogging.Comments
Task<int> GetCommentCountOfPostAsync(Guid postId); Task<int> GetCommentCountOfPostAsync(Guid postId);
Task<List<Comment>> GetRepliesOfComment(Guid id); Task<List<Comment>> GetRepliesOfComment(Guid id);
void DeleteOfPost(Guid id);
} }
} }

7
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Posts/IPostTagRepository.cs

@ -1,8 +1,13 @@
using Volo.Abp.Domain.Repositories; using System;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
namespace Volo.Blogging.Posts namespace Volo.Blogging.Posts
{ {
public interface IPostTagRepository : IBasicRepository<PostTag> public interface IPostTagRepository : IBasicRepository<PostTag>
{ {
void DeleteOfPost(Guid id);
Task<PostTag> FindByTagIdAndPostIdAsync(Guid postId, Guid tagId);
} }
} }

4
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Tagging/ITagRepository.cs

@ -11,6 +11,10 @@ namespace Volo.Blogging.Tagging
Task<Tag> GetByNameAsync(string name); Task<Tag> GetByNameAsync(string name);
Task<Tag> FindByNameAsync(string name);
Task<List<Tag>> GetListAsync(IEnumerable<Guid> ids); Task<List<Tag>> GetListAsync(IEnumerable<Guid> ids);
void DecreaseUsageCountOfTags(List<Guid> id);
} }
} }

43
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUser.cs

@ -0,0 +1,43 @@
using System;
using System.Collections.Generic;
using Volo.Abp.Data;
using Volo.Abp.Domain.Entities;
using Volo.Abp.Users;
namespace Volo.Blogging.Users
{
public class BlogUser : AggregateRoot<Guid>, IUser, IHasExtraProperties
{
public virtual Guid? TenantId { get; protected set; }
public virtual string UserName { get; protected set; }
public virtual string Email { get; protected set; }
public virtual bool EmailConfirmed { get; protected set; }
public virtual string PhoneNumber { get; protected set; }
public virtual bool PhoneNumberConfirmed { get; protected set; }
public virtual Dictionary<string, object> ExtraProperties { get; protected set; }
protected BlogUser()
{
ExtraProperties = new Dictionary<string, object>();
}
public BlogUser(IUserData user)
{
Id = user.Id;
Email = user.Email;
EmailConfirmed = user.EmailConfirmed;
PhoneNumber = user.PhoneNumber;
PhoneNumberConfirmed = user.PhoneNumberConfirmed;
UserName = user.UserName;
TenantId = user.TenantId;
ExtraProperties = new Dictionary<string, object>();
}
}
}

22
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/BlogUserLookupService.cs

@ -0,0 +1,22 @@
using Volo.Abp.Uow;
using Volo.Abp.Users;
namespace Volo.Blogging.Users
{
public class BlogUserLookupService : UserLookupService<BlogUser, IBlogUserRepository>, IBlogUserLookupService
{
public BlogUserLookupService(
IBlogUserRepository userRepository,
IUnitOfWorkManager unitOfWorkManager)
: base(
userRepository,
unitOfWorkManager)
{
}
protected override BlogUser CreateUser(IUserData externalUser)
{
return new BlogUser(externalUser);
}
}
}

9
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/IBlogUserLookupService.cs

@ -0,0 +1,9 @@
using Volo.Abp.Users;
namespace Volo.Blogging.Users
{
public interface IBlogUserLookupService : IUserLookupService<BlogUser>
{
}
}

14
modules/blogging/src/Volo.Blogging.Domain/Volo/Blogging/Users/IBlogUserRepository.cs

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Volo.Abp.Domain.Repositories;
using Volo.Abp.Users;
namespace Volo.Blogging.Users
{
public interface IBlogUserRepository : IBasicRepository<BlogUser, Guid>, IUserRepository<BlogUser>
{
Task<List<BlogUser>> GetUsersAsync(int maxCount, string filter, CancellationToken cancellationToken = default);
}
}

1
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo.Blogging.EntityFrameworkCore.csproj

@ -12,6 +12,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Blogging.Domain\Volo.Blogging.Domain.csproj" /> <ProjectReference Include="..\Volo.Blogging.Domain\Volo.Blogging.Domain.csproj" />
<ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.EntityFrameworkCore\Volo.Abp.EntityFrameworkCore.csproj" /> <ProjectReference Include="..\..\..\..\framework\src\Volo.Abp.EntityFrameworkCore\Volo.Abp.EntityFrameworkCore.csproj" />
<ProjectReference Include="..\..\..\users\src\Volo.Abp.Users.EntityFrameworkCore\Volo.Abp.Users.EntityFrameworkCore.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

17
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Blogs/EfCoreBlogRepository.cs

@ -1,4 +1,7 @@
using System; using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Dynamic.Core;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
@ -19,5 +22,19 @@ namespace Volo.Blogging.Blogs
{ {
return await DbSet.FirstOrDefaultAsync(p => p.ShortName == shortName); return await DbSet.FirstOrDefaultAsync(p => p.ShortName == shortName);
} }
public async Task<List<Blog>> GetListAsync(string sorting, int maxResultCount, int skipCount)
{
var auditLogs = await DbSet.OrderBy(sorting ?? "creationTime desc")
.PageBy(skipCount, maxResultCount)
.ToListAsync();
return auditLogs;
}
public async Task<int> GetTotalBlogCount()
{
return await DbSet.CountAsync();
}
} }
} }

6
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Comments/EfCoreCommentRepository.cs

@ -35,5 +35,11 @@ namespace Volo.Blogging.Comments
return await DbSet return await DbSet
.Where(a => a.RepliedCommentId == id).ToListAsync(); .Where(a => a.RepliedCommentId == id).ToListAsync();
} }
public void DeleteOfPost(Guid id)
{
var recordsToDelete = DbSet.Where(pt => pt.PostId == id);
DbSet.RemoveRange(recordsToDelete);
}
} }
} }

3
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContext.cs

@ -5,6 +5,7 @@ using Volo.Blogging.Blogs;
using Volo.Blogging.Comments; using Volo.Blogging.Comments;
using Volo.Blogging.Posts; using Volo.Blogging.Posts;
using Volo.Blogging.Tagging; using Volo.Blogging.Tagging;
using Volo.Blogging.Users;
namespace Volo.Blogging.EntityFrameworkCore namespace Volo.Blogging.EntityFrameworkCore
{ {
@ -14,6 +15,8 @@ namespace Volo.Blogging.EntityFrameworkCore
public static string TablePrefix { get; set; } = BloggingConsts.DefaultDbTablePrefix; public static string TablePrefix { get; set; } = BloggingConsts.DefaultDbTablePrefix;
public static string Schema { get; set; } = BloggingConsts.DefaultDbSchema; public static string Schema { get; set; } = BloggingConsts.DefaultDbSchema;
public DbSet<BlogUser> Users { get; set; }
public DbSet<Blog> Blogs { get; set; } public DbSet<Blog> Blogs { get; set; }
public DbSet<Post> Posts { get; set; } public DbSet<Post> Posts { get; set; }

15
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/BloggingDbContextModelBuilderExtensions.cs

@ -3,10 +3,12 @@ using JetBrains.Annotations;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Volo.Abp; using Volo.Abp;
using Volo.Abp.EntityFrameworkCore.Modeling; using Volo.Abp.EntityFrameworkCore.Modeling;
using Volo.Abp.Users.EntityFrameworkCore;
using Volo.Blogging.Blogs; using Volo.Blogging.Blogs;
using Volo.Blogging.Comments; using Volo.Blogging.Comments;
using Volo.Blogging.Posts; using Volo.Blogging.Posts;
using Volo.Blogging.Tagging; using Volo.Blogging.Tagging;
using Volo.Blogging.Users;
namespace Volo.Blogging.EntityFrameworkCore namespace Volo.Blogging.EntityFrameworkCore
{ {
@ -21,6 +23,14 @@ namespace Volo.Blogging.EntityFrameworkCore
var options = new BloggingModelBuilderConfigurationOptions(); var options = new BloggingModelBuilderConfigurationOptions();
optionsAction?.Invoke(options); optionsAction?.Invoke(options);
builder.Entity<BlogUser>(b =>
{
b.ToTable(options.TablePrefix + "Users", options.Schema);
b.ConfigureAbpUser(options);
b.ConfigureExtraProperties();
});
builder.Entity<Blog>(b => builder.Entity<Blog>(b =>
{ {
b.ToTable(options.TablePrefix + "Blogs", options.Schema); b.ToTable(options.TablePrefix + "Blogs", options.Schema);
@ -28,6 +38,11 @@ namespace Volo.Blogging.EntityFrameworkCore
b.ConfigureFullAudited(); b.ConfigureFullAudited();
b.Property(x => x.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength).HasColumnName(nameof(Blog.Name)); b.Property(x => x.Name).IsRequired().HasMaxLength(BlogConsts.MaxNameLength).HasColumnName(nameof(Blog.Name));
b.Property(x => x.Facebook).HasMaxLength(BlogConsts.MaxSocialLinkLength).HasColumnName(nameof(Blog.Facebook));
b.Property(x => x.Twitter).HasMaxLength(BlogConsts.MaxSocialLinkLength).HasColumnName(nameof(Blog.Twitter));
b.Property(x => x.Instagram).HasMaxLength(BlogConsts.MaxSocialLinkLength).HasColumnName(nameof(Blog.Instagram));
b.Property(x => x.Github).HasMaxLength(BlogConsts.MaxSocialLinkLength).HasColumnName(nameof(Blog.Github));
b.Property(x => x.StackOverflow).HasMaxLength(BlogConsts.MaxSocialLinkLength).HasColumnName(nameof(Blog.StackOverflow));
b.Property(x => x.ShortName).IsRequired().HasMaxLength(BlogConsts.MaxShortNameLength).HasColumnName(nameof(Blog.ShortName)); b.Property(x => x.ShortName).IsRequired().HasMaxLength(BlogConsts.MaxShortNameLength).HasColumnName(nameof(Blog.ShortName));
b.Property(x => x.Description).IsRequired(false).HasMaxLength(BlogConsts.MaxDescriptionLength).HasColumnName(nameof(Blog.Description)); b.Property(x => x.Description).IsRequired(false).HasMaxLength(BlogConsts.MaxDescriptionLength).HasColumnName(nameof(Blog.Description));
}); });

3
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/EntityFrameworkCore/IBloggingDbContext.cs

@ -5,12 +5,15 @@ using Volo.Blogging.Blogs;
using Volo.Blogging.Comments; using Volo.Blogging.Comments;
using Volo.Blogging.Posts; using Volo.Blogging.Posts;
using Volo.Blogging.Tagging; using Volo.Blogging.Tagging;
using Volo.Blogging.Users;
namespace Volo.Blogging.EntityFrameworkCore namespace Volo.Blogging.EntityFrameworkCore
{ {
[ConnectionStringName("Blogging")] [ConnectionStringName("Blogging")]
public interface IBloggingDbContext : IEfCoreDbContext public interface IBloggingDbContext : IEfCoreDbContext
{ {
DbSet<BlogUser> Users { get; }
DbSet<Blog> Blogs { get; set; } DbSet<Blog> Blogs { get; set; }
DbSet<Post> Posts { get; set; } DbSet<Post> Posts { get; set; }

17
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Posts/EfCorePostTagRepository.cs

@ -1,4 +1,8 @@
using Volo.Abp.Domain.Repositories.EntityFrameworkCore; using System;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.Domain.Repositories.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore; using Volo.Abp.EntityFrameworkCore;
using Volo.Blogging.EntityFrameworkCore; using Volo.Blogging.EntityFrameworkCore;
@ -9,5 +13,16 @@ namespace Volo.Blogging.Posts
public EfCorePostTagRepository(IDbContextProvider<IBloggingDbContext> dbContextProvider) : base(dbContextProvider) public EfCorePostTagRepository(IDbContextProvider<IBloggingDbContext> dbContextProvider) : base(dbContextProvider)
{ {
} }
public void DeleteOfPost(Guid id)
{
var recordsToDelete = DbSet.Where(pt=>pt.PostId == id);
DbSet.RemoveRange(recordsToDelete);
}
public async Task<PostTag> FindByTagIdAndPostIdAsync(Guid postId, Guid tagId)
{
return await DbSet.FirstOrDefaultAsync(pt=> pt.PostId == postId && pt.TagId == tagId);
}
} }
} }

15
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Tagging/EfCoreTagRepository.cs

@ -22,6 +22,11 @@ namespace Volo.Blogging.Tagging
} }
public async Task<Tag> GetByNameAsync(string name) public async Task<Tag> GetByNameAsync(string name)
{
return await DbSet.FirstAsync(t=>t.Name == name);
}
public async Task<Tag> FindByNameAsync(string name)
{ {
return await DbSet.FirstOrDefaultAsync(t=>t.Name == name); return await DbSet.FirstOrDefaultAsync(t=>t.Name == name);
} }
@ -30,5 +35,15 @@ namespace Volo.Blogging.Tagging
{ {
return await DbSet.Where(c => ids.Contains(c.Id)).ToListAsync(); return await DbSet.Where(c => ids.Contains(c.Id)).ToListAsync();
} }
public void DecreaseUsageCountOfTags(List<Guid> ids)
{
var tags = DbSet.Where(t => ids.Any(id => id == t.Id));
foreach (var tag in tags)
{
tag.DecreaseUsageCount();
}
}
} }
} }

27
modules/blogging/src/Volo.Blogging.EntityFrameworkCore/Volo/Blogging/Users/EfCoreQaUserRepository.cs

@ -0,0 +1,27 @@
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Volo.Abp.EntityFrameworkCore;
using Volo.Abp.Users.EntityFrameworkCore;
using Volo.Blogging.EntityFrameworkCore;
namespace Volo.Blogging.Users
{
public class EfCoreBlogUserRepository : EfCoreUserRepositoryBase<IBloggingDbContext, BlogUser>, IBlogUserRepository
{
public EfCoreBlogUserRepository(IDbContextProvider<IBloggingDbContext> dbContextProvider)
: base(dbContextProvider)
{
}
public async Task<List<BlogUser>> GetUsersAsync(int maxCount, string filter, CancellationToken cancellationToken = default)
{
return await DbSet
.WhereIf( !string.IsNullOrWhiteSpace( filter), x=>x.UserName.Contains(filter))
.Take(maxCount).ToListAsync(cancellationToken);
}
}
}

1
modules/blogging/src/Volo.Blogging.HttpApi.Client/Volo.Blogging.HttpApi.Client.csproj

@ -11,6 +11,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Blogging.Application.Contracts\Volo.Blogging.Application.Contracts.csproj" /> <ProjectReference Include="..\Volo.Blogging.Application.Contracts\Volo.Blogging.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\..\abp\framework\src\Volo.Abp.Http.Client\Volo.Abp.Http.Client.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

10
modules/blogging/src/Volo.Blogging.HttpApi.Client/Volo/Blogging/BloggingHttpApiClientModule.cs

@ -1,12 +1,20 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.Http.Client;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Blogging namespace Volo.Blogging
{ {
[DependsOn( [DependsOn(
typeof(BloggingApplicationContractsModule))] typeof(BloggingApplicationContractsModule),
typeof(AbpHttpClientModule))]
public class BloggingHttpApiClientModule : AbpModule public class BloggingHttpApiClientModule : AbpModule
{ {
public const string RemoteServiceName = "Blogging";
public override void ConfigureServices(ServiceConfigurationContext context)
{
context.Services.AddHttpClientProxies(typeof(BloggingApplicationContractsModule).Assembly, RemoteServiceName);
}
} }
} }

1
modules/blogging/src/Volo.Blogging.HttpApi/Volo.Blogging.HttpApi.csproj

@ -11,6 +11,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\Volo.Blogging.Application.Contracts\Volo.Blogging.Application.Contracts.csproj" /> <ProjectReference Include="..\Volo.Blogging.Application.Contracts\Volo.Blogging.Application.Contracts.csproj" />
<ProjectReference Include="..\..\..\..\..\abp\framework\src\Volo.Abp.AspNetCore.Mvc\Volo.Abp.AspNetCore.Mvc.csproj" />
</ItemGroup> </ItemGroup>
</Project> </Project>

5
modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BloggingHttpApiModule.cs

@ -1,10 +1,11 @@
using Microsoft.Extensions.DependencyInjection; using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Modularity; using Volo.Abp.Modularity;
namespace Volo.Blogging namespace Volo.Blogging
{ {
[DependsOn( [DependsOn(
typeof(BloggingApplicationContractsModule))] typeof(BloggingApplicationContractsModule),
typeof(AbpAspNetCoreMvcModule))]
public class BloggingHttpApiModule : AbpModule public class BloggingHttpApiModule : AbpModule
{ {

77
modules/blogging/src/Volo.Blogging.HttpApi/Volo/Blogging/BlogsController.cs

@ -0,0 +1,77 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp;
using Volo.Abp.Application.Dtos;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Abp.Auditing;
using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
namespace Volo.Blogging
{
[RemoteService]
[Area("blogging")]
[Controller]
[ControllerName("Blogs")]
[Route("api/blogging/blogs")]
[DisableAuditing]
public class BlogsController : AbpController, IBlogAppService
{
private readonly IBlogAppService _blogAppService;
public BlogsController(IBlogAppService blogAppService)
{
_blogAppService = blogAppService;
}
[HttpGet]
[Route("")]
public async Task<PagedResultDto<BlogDto>> GetListPagedAsync(PagedAndSortedResultRequestDto input)
{
return await _blogAppService.GetListPagedAsync(input);
}
[HttpGet]
[Route("/all")]
public async Task<ListResultDto<BlogDto>> GetListAsync()
{
return await _blogAppService.GetListAsync();
}
[HttpGet]
[Route("{shortName}")]
public async Task<BlogDto> GetByShortNameAsync(string shortName)
{
return await _blogAppService.GetByShortNameAsync(shortName);
}
[HttpGet]
[Route("{id}")]
public async Task<BlogDto> GetAsync(Guid id)
{
return await _blogAppService.GetAsync(id);
}
[HttpPost]
public async Task<BlogDto> Create(CreateBlogDto input)
{
return await _blogAppService.Create(input);
}
[HttpPut]
[Route("{id}")]
public async Task<BlogDto> Update(Guid id, UpdateBlogDto input)
{
throw new NotImplementedException();
}
[HttpDelete]
public async Task Delete(Guid id)
{
await _blogAppService.Delete(id);
}
}
}

7
modules/blogging/src/Volo.Blogging.Web/AbpBloggingWebAutoMapperProfile.cs

@ -1,7 +1,12 @@
using AutoMapper; using AutoMapper;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Pages.Admin.Blogs;
using Volo.Blogging.Pages.Blog.Posts; using Volo.Blogging.Pages.Blog.Posts;
using Volo.Blogging.Posts; using Volo.Blogging.Posts;
using EditModel = Volo.Blogging.Pages.Admin.Blogs.EditModel;
using IndexModel = Volo.Blogging.Pages.Blog.IndexModel;
namespace Volo.Blogging namespace Volo.Blogging
{ {
@ -11,6 +16,8 @@ namespace Volo.Blogging
{ {
CreateMap<PostWithDetailsDto, EditPostViewModel>().Ignore(x=>x.Tags); CreateMap<PostWithDetailsDto, EditPostViewModel>().Ignore(x=>x.Tags);
CreateMap<NewModel.CreatePostViewModel, CreatePostDto>(); CreateMap<NewModel.CreatePostViewModel, CreatePostDto>();
CreateMap<CreateModel.BlogCreateModalView, CreateBlogDto>();
CreateMap<BlogDto, EditModel.BlogEditViewModel>();
} }
} }
} }

26
modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Controllers/PostsController.cs

@ -0,0 +1,26 @@
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc;
using Volo.Blogging.Posts;
namespace Volo.Blogging.Areas.Blog.Controllers
{
[Area("Blog")]
[Route("Blog/[controller]/[action]")]
public class PostsController : AbpController
{
private readonly IPostAppService _postAppService;
public PostsController(IPostAppService postAppService)
{
_postAppService = postAppService;
}
[HttpPost]
public async Task Delete(Guid id)
{
await _postAppService.DeleteAsync(id);
}
}
}

210
modules/blogging/src/Volo.Blogging.Web/Areas/Blog/Helpers/TagHelpers/GravatarTagHelper.cs

@ -0,0 +1,210 @@
using System;
using System.ComponentModel;
using System.Linq;
using System.Security.Cryptography;
using System.Text;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Razor.TagHelpers;
namespace Volo.Blogging.Areas.Blog.Helpers.TagHelpers
{
/// <inheritdoc />
/// <summary>
/// Returns a Globally Recognised Avatar https://en.gravatar.com
/// </summary>
[HtmlTargetElement("img", Attributes = "gravatar-email")]
public class GravatarTagHelper : TagHelper
{
private readonly IHttpContextAccessor _contextAccessor;
public GravatarTagHelper(IHttpContextAccessor contextAccessor)
{
_contextAccessor = contextAccessor;
}
/// <summary>
/// Email Address for the Gravatar
/// </summary>
[HtmlAttributeName("gravatar-email")]
public string Email { get; set; }
/// <summary>
/// Gravatar content rating (note that Gravatars are self-rated)
/// </summary>
[HtmlAttributeName("gravatar-rating")]
public GravatarRating Rating { get; set; } = GravatarRating.GeneralAudiences;
/// <summary>
/// Size in pixels (default: 80)
/// </summary>
[HtmlAttributeName("gravatar-size")]
public int Size { get; set; } = 80;
/// <summary>
/// URL to a custom default image (e.g: 'Url.Content("~/images/no-grvatar.png")' )
/// </summary>
[HtmlAttributeName("default-image-url")]
public string DefaultImageUrl { get; set; } = "";
/// <summary>
/// Prefer the default image over the users own Gravatar
/// </summary>
[HtmlAttributeName("force-default-image")]
public bool ForceDefaultImage { get; set; }
/// <summary>
/// Default image if user hasn't created a Gravatar
/// </summary>
[HtmlAttributeName("default-image")]
public GravatarDefaultImage DefaultImage { get; set; } = GravatarDefaultImage.Default;
/// <summary>
/// Always do secure (https) requests
/// </summary>
[HtmlAttributeName("force-secure-request")]
public bool ForceSecureRequest { get; set; } = true;
public override void Process(TagHelperContext context, TagHelperOutput output)
{
var emailAddress = Email == null ? string.Empty : Email.Trim().ToLower();
var url = string.Format("{0}://{1}.gravatar.com/avatar/{2}?s={3}{4}{5}{6}",
GetUrlScheme(),
GetUrlPrefix(),
GetMd5Hash(emailAddress),
Size,
GetDefaultImageParameter(),
GetForceDefaultImageParameter(),
GetRatingParameter()
);
output.Attributes.SetAttribute("src", url);
}
private string GetUrlScheme()
{
return ForceSecureRequest || _contextAccessor.HttpContext.Request.IsHttps
? "https" : "http";
}
private string GetUrlPrefix()
{
return ForceSecureRequest || _contextAccessor.HttpContext.Request.IsHttps ? "secure" : "www";
}
private string GetDefaultImageParameter()
{
return "&d=" + (!string.IsNullOrEmpty(DefaultImageUrl)
? System.Net.WebUtility.UrlEncode(DefaultImageUrl)
: GetEnumDescription(DefaultImage));
}
private string GetForceDefaultImageParameter()
{
return ForceDefaultImage ? "&f=y" : "";
}
private string GetRatingParameter()
{
return "&r=" + GetEnumDescription(Rating);
}
/// <summary>
/// Generates an MD5 hash of the given string
/// </summary>
/// <remarks>Source: http://msdn.microsoft.com/en-us/library/system.security.cryptography.md5.aspx </remarks>
private static string GetMd5Hash(string input)
{
// Convert the input string to a byte array and compute the hash.
var data = MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(input));
// Create a new Stringbuilder to collect the bytes
// and create a string.
var sBuilder = new StringBuilder();
// Loop through each byte of the hashed data
// and format each one as a hexadecimal string.
foreach (var t in data)
{
sBuilder.Append(t.ToString("x2"));
}
// Return the hexadecimal string.
return sBuilder.ToString();
}
/// <summary>
/// Returns the value of a Description for a given Enum value
/// </summary>
/// <remarks>Source: http://blogs.msdn.com/b/abhinaba/archive/2005/10/21/483337.aspx </remarks>
/// <param name="en"></param>
/// <returns></returns>
private static string GetEnumDescription(Enum en)
{
var type = en.GetType();
var memInfo = type.GetMember(en.ToString());
if (memInfo == null || memInfo.Length <= 0)
{
return en.ToString();
}
var attrs = memInfo[0].GetCustomAttributes(typeof(DescriptionAttribute), false);
if (attrs != null && attrs.Any())
{
return ((DescriptionAttribute)attrs.First()).Description;
}
return en.ToString();
}
public enum GravatarDefaultImage
{
/// <summary>Default Gravatar logo</summary>
[Description("")]
Default,
/// <summary>404 - do not load any image if none is associated with the email hash, instead return an HTTP 404 (File Not Found) response</summary>
[Description("404")]
Http404,
/// <summary>Mystery-Man - a simple, cartoon-style silhouetted outline of a person (does not vary by email hash)</summary>
[Description("mm")]
MysteryMan,
/// <summary>Identicon - a geometric pattern based on an email hash</summary>
[Description("identicon")]
Identicon,
/// <summary>MonsterId - a generated 'monster' with different colors, faces, etc</summary>
[Description("monsterid")]
MonsterId,
/// <summary>Wavatar - generated faces with differing features and backgrounds</summary>
[Description("wavatar")]
Wavatar,
/// <summary>Retro - awesome generated, 8-bit arcade-style pixelated faces</summary>
[Description("retro")]
Retro
}
/// <summary>
/// Gravatar allows users to self-rate their images so that they can indicate if an image is appropriate for a certain audience. By default, only 'G' rated images are displayed unless you indicate that you would like to see higher ratings
/// </summary>
public enum GravatarRating
{
/// <summary>Suitable for display on all websites with any audience type</summary>
[Description("g")]
GeneralAudiences,
/// <summary>May contain rude gestures, provocatively dressed individuals, the lesser swear words, or mild violence</summary>
[Description("pg")]
ParentalGuidance,
/// <summary>May contain such things as harsh profanity, intense violence, nudity, or hard drug use</summary>
[Description("r")]
Restricted,
/// <summary>May contain hardcore sexual imagery or extremely disturbing violence</summary>
[Description("x")]
OnlyMature
}
}
}

14
modules/blogging/src/Volo.Blogging.Web/BloggingMenuContributor.cs

@ -27,7 +27,21 @@ namespace Volo.Blogging
var rootMenuItem = new ApplicationMenuItem("Blogs", l["Menu:Blogs"], "/Blog"); var rootMenuItem = new ApplicationMenuItem("Blogs", l["Menu:Blogs"], "/Blog");
context.Menu.AddItem(rootMenuItem); context.Menu.AddItem(rootMenuItem);
if (await authorizationService.IsGrantedAsync(BloggingPermissions.Blogs.Management))
{
var managementRootMenuItem = new ApplicationMenuItem("BlogManagement", l["Menu:BlogManagement"]);
if (await authorizationService.IsGrantedAsync(BloggingPermissions.Blogs.Management))
{
managementRootMenuItem.AddItem(new ApplicationMenuItem("BlogsAdmin", l["Menu:Blogs"], "/Admin/Blogs"));
}
context.Menu.AddItem(managementRootMenuItem);
}
} }
} }
} }
} }

8
modules/blogging/src/Volo.Blogging.Web/BloggingWebModule.cs

@ -2,6 +2,7 @@
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Volo.Abp.AspNetCore.Mvc.Localization; using Volo.Abp.AspNetCore.Mvc.Localization;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap; using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap;
using Volo.Abp.AspNetCore.Mvc.UI.Bundling;
using Volo.Abp.AutoMapper; using Volo.Abp.AutoMapper;
using Volo.Abp.Localization; using Volo.Abp.Localization;
using Volo.Abp.Localization.Resources.AbpValidation; using Volo.Abp.Localization.Resources.AbpValidation;
@ -15,8 +16,9 @@ namespace Volo.Blogging
{ {
[DependsOn( [DependsOn(
typeof(BloggingHttpApiModule), typeof(BloggingHttpApiModule),
typeof(AbpAutoMapperModule), typeof(AbpAspNetCoreMvcUiBootstrapModule),
typeof(AbpAspNetCoreMvcUiBootstrapModule) typeof(AbpAspNetCoreMvcUiBundlingModule),
typeof(AbpAutoMapperModule)
)] )]
public class BloggingWebModule : AbpModule public class BloggingWebModule : AbpModule
{ {
@ -37,7 +39,7 @@ namespace Volo.Blogging
Configure<VirtualFileSystemOptions>(options => Configure<VirtualFileSystemOptions>(options =>
{ {
options.FileSets.AddEmbedded<BloggingWebModule>("Volo.Blogging"); options.FileSets.AddEmbedded<BloggingWebModule>("Volo.Blogging.Web");
}); });
Configure<AbpLocalizationOptions>(options => Configure<AbpLocalizationOptions>(options =>

27
modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/en.json

@ -2,28 +2,37 @@
"culture": "en", "culture": "en",
"texts": { "texts": {
"Menu:Blogs": "Blog", "Menu:Blogs": "Blog",
"Menu:BlogManagement": "Blog Management",
"Title": "Title", "Title": "Title",
"Delete": "Delete",
"Reply": "Reply",
"ContinueReading": "Continue Reading", "ContinueReading": "Continue Reading",
"DaysAgo": "days ago", "DaysAgo": "{0} days ago",
"YearsAgo": "years ago", "YearsAgo": "{0} years ago",
"MonthsAgo": "months ago", "MonthsAgo": "{0} months ago",
"WeeksAgo": "weeks ago", "WeeksAgo": "{0} weeks ago",
"MinutesAgo": "minutes ago", "MinutesAgo": "{0} minutes ago",
"SecondsAgo": "seconds ago", "SecondsAgo": "{0} seconds ago",
"HoursAgo": "hours ago", "HoursAgo": "{0} hours ago",
"Now": "now", "Now": "now",
"Content": "Content", "Content": "Content",
"SeeAll": "See All", "SeeAll": "See All",
"PopularTags": "Popular Tags", "PopularTags": "Popular Tags",
"Read": "Read", "Read": "{0} Read",
"LastPosts": "Last Posts", "LastPosts": "Last Posts",
"LeaveComment": "Leave Comment", "LeaveComment": "Leave Comment",
"TagsInThisArticle": "Tags in this article", "TagsInThisArticle": "Tags in this article",
"Posts": "Posts", "Posts": "Posts",
"Edit": "Edit", "Edit": "Edit",
"Comment": "Comment", "CommentDeletionWarningMessage": "Comment will be deleted.",
"PostDeletionWarningMessage": "Post will be deleted.",
"BlogDeletionWarningMessage": "Blog will be deleted.",
"AreYouSure": "Are you sure?",
"Comment": "{0} Comment",
"ShareOnTwitter": "Share On Twitter",
"CoverImage": "Cover Image", "CoverImage": "Cover Image",
"CreateANewPost": "Create A New Post", "CreateANewPost": "Create A New Post",
"CreateANewBlog": "Create A New Blog",
"WhatIsNew": "What is new?" "WhatIsNew": "What is new?"
} }
} }

27
modules/blogging/src/Volo.Blogging.Web/Localization/Resources/Blogging/Web/tr.json

@ -3,27 +3,36 @@
"texts": { "texts": {
"Menu:Blogs": "Blog", "Menu:Blogs": "Blog",
"Title": "Başlık", "Title": "Başlık",
"Delete": "Sil",
"Reply": "Yanıtla",
"Menu:BlogManagement": "Blog Yönetimi",
"ContinueReading": "Devamı...", "ContinueReading": "Devamı...",
"DaysAgo": "Gün Önce", "DaysAgo": "{0} Gün Önce",
"YearsAgo": "yıl önce", "YearsAgo": "{0} yıl önce",
"MonthsAgo": "ay önce", "MonthsAgo": "{0} ay önce",
"WeeksAgo": "hafta önce", "WeeksAgo": "{0} hafta önce",
"MinutesAgo": "dakika önce", "MinutesAgo": "{0} dakika önce",
"SecondsAgo": "saniye önce", "SecondsAgo": "{0} saniye önce",
"HoursAgo": "saat önce", "HoursAgo": "{0} saat önce",
"Now": "şimdi", "Now": "şimdi",
"Content": "İçerik", "Content": "İçerik",
"SeeAll": "Hepsini Gör", "SeeAll": "Hepsini Gör",
"PopularTags": "Popüler Etiketler", "PopularTags": "Popüler Etiketler",
"Read": "Okunma", "ReadWithCount": "{0} Okunma",
"ShareOnTwitter": "Twitter'da paylaş",
"LastPosts": "Son Yazılar", "LastPosts": "Son Yazılar",
"LeaveComment": "Yorum Bırak", "LeaveComment": "Yorum Bırak",
"TagsInThisArticle": "Makalenin Etiketleri", "TagsInThisArticle": "Makalenin Etiketleri",
"Posts": "Yazılar", "Posts": "Yazılar",
"Edit": "Düzenle", "Edit": "Düzenle",
"Comment": "Yorum", "CommentDeletionWarningMessage": "Yorum silinecek.",
"PostDeletionWarningMessage": "Yazı silinecek.",
"BlogDeletionWarningMessage": "Blog silinecek.",
"AreYouSure": "Emin misiniz?",
"CommentWithCount": "{0} Yorum",
"CoverImage": "Kapak resmi", "CoverImage": "Kapak resmi",
"CreateANewPost": "Yeni Yazı oluştur", "CreateANewPost": "Yeni Yazı oluştur",
"CreateANewBlog": "Yeni Blog Ekle",
"WhatIsNew": "Yeniler" "WhatIsNew": "Yeniler"
} }
} }

33
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Create.cshtml

@ -0,0 +1,33 @@
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Blogging.Pages.Blog
@inherits BloggingPage
@model Volo.Blogging.Pages.Admin.Blogs.CreateModel
@{
Layout = null;
}
<form asp-page="/Admin/Blogs/Create">
<abp-modal size="@(AbpModalSize.Large)">
<abp-modal-header title="@L["Create"].Value"></abp-modal-header>
<abp-modal-body>
<abp-tabs>
<abp-tab title="@L["Info"].Value">
<abp-input asp-for="Blog.Name"/>
<abp-input asp-for="Blog.ShortName"/>
<abp-input asp-for="Blog.Description"/>
</abp-tab>
<abp-tab title="@L["Social"].Value">
<abp-input asp-for="Blog.Facebook" />
<abp-input asp-for="Blog.Twitter" />
<abp-input asp-for="Blog.Instagram" />
<abp-input asp-for="Blog.Github" />
<abp-input asp-for="Blog.StackOverflow" />
</abp-tab>
</abp-tabs>
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)">
</abp-modal-footer>
</abp-modal>
</form>

65
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Create.cshtml.cs

@ -0,0 +1,65 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Form;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
namespace Volo.Blogging.Pages.Admin.Blogs
{
public class CreateModel : AbpPageModel
{
private readonly IBlogAppService _blogAppService;
[BindProperty]
public BlogCreateModalView Blog { get; set; } = new BlogCreateModalView();
public CreateModel(IBlogAppService blogAppService)
{
_blogAppService = blogAppService;
}
public void OnGet()
{
}
public async void OnPostAsync()
{
var language = ObjectMapper.Map<BlogCreateModalView, CreateBlogDto>(Blog);
await _blogAppService.Create(language);
}
public class BlogCreateModalView
{
[Required]
[StringLength(BlogConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[StringLength(BlogConsts.MaxShortNameLength)]
public string ShortName { get; set; }
[StringLength(BlogConsts.MaxDescriptionLength)]
public string Description { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string Facebook { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string Twitter { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string Instagram { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string Github { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string StackOverflow { get; set; }
}
}
}

34
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Edit.cshtml

@ -0,0 +1,34 @@
@page
@using Volo.Abp.AspNetCore.Mvc.UI.Bootstrap.TagHelpers.Modal
@using Volo.Blogging.Pages.Blog
@inherits BloggingPage
@model Volo.Blogging.Pages.Admin.Blogs.EditModel
@{
Layout = null;
}
<form asp-page="/Admin/Blogs/Edit">
<abp-modal size="@(AbpModalSize.Large)">
<abp-modal-header title="@L["Edit"].Value"></abp-modal-header>
<abp-modal-body>
<abp-tabs>
<abp-tab title="@L["Info"].Value">
<abp-input asp-for="Blog.Id" />
<abp-input asp-for="Blog.Name" />
<abp-input asp-for="Blog.ShortName" />
<abp-input asp-for="Blog.Description" />
</abp-tab>
<abp-tab title="@L["Social"].Value">
<abp-input asp-for="Blog.Facebook" />
<abp-input asp-for="Blog.Twitter" />
<abp-input asp-for="Blog.Instagram" />
<abp-input asp-for="Blog.Github" />
<abp-input asp-for="Blog.StackOverflow" />
</abp-tab>
</abp-tabs>
</abp-modal-body>
<abp-modal-footer buttons="@(AbpModalButtons.Cancel|AbpModalButtons.Save)">
</abp-modal-footer>
</abp-modal>
</form>

85
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Edit.cshtml.cs

@ -0,0 +1,85 @@
using System;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Posts;
namespace Volo.Blogging.Pages.Admin.Blogs
{
public class EditModel : AbpPageModel
{
private readonly IBlogAppService _blogAppService;
[BindProperty(SupportsGet = true)]
public Guid BlogId { get; set; }
[BindProperty]
public BlogEditViewModel Blog { get; set; } = new BlogEditViewModel();
public EditModel(IBlogAppService blogAppService)
{
_blogAppService = blogAppService;
}
public async Task OnGet()
{
var blog = await _blogAppService.GetAsync(BlogId);
Blog = ObjectMapper.Map<BlogDto, BlogEditViewModel>(blog);
}
public async Task OnPost()
{
await _blogAppService.Update(Blog.Id, new UpdateBlogDto()
{
Name = Blog.Name,
ShortName = Blog.ShortName,
Description = Blog.Description,
Facebook = Blog.Facebook,
Twitter = Blog.Twitter,
Instagram = Blog.Instagram,
Github = Blog.Github,
StackOverflow = Blog.StackOverflow
});
}
public class BlogEditViewModel
{
[HiddenInput]
[Required]
public Guid Id { get; set; }
[Required]
[StringLength(BlogConsts.MaxNameLength)]
public string Name { get; set; }
[Required]
[StringLength(BlogConsts.MaxShortNameLength)]
public string ShortName { get; set; }
[StringLength(BlogConsts.MaxDescriptionLength)]
public string Description { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string Facebook { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string Twitter { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string Instagram { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string Github { get; set; }
[StringLength(BlogConsts.MaxSocialLinkLength)]
public string StackOverflow { get; set; }
}
}
}

45
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Index.cshtml

@ -0,0 +1,45 @@
@page
@using Microsoft.AspNetCore.Authorization
@using Volo.Blogging
@using Volo.Blogging.Pages.Blog
@inherits BloggingPage
@model Volo.Blogging.Pages.Admin.Blogs.IndexModel
@inject IAuthorizationService Authorization
@{
ViewBag.PageTitle = "Blogs";
}
@section scripts {
<abp-script src="/Pages/Admin/Blogs/index.js" />
<abp-script src="/Pages/Admin/Blogs/create.js" />
<abp-script src="/Pages/Admin/Blogs/edit.js" />
}
<abp-card>
<abp-card-header>
<abp-row>
<abp-column size-md="_6">
<h2>@L["Blogs"]</h2>
</abp-column>
<abp-column size-md="_6" class="text-right">
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Blogs.Create))
{
<abp-button icon="plus" text="@L["CreateANewBlog"].Value" button-type="Primary" id="CreateNewBlogButtonId"></abp-button>
}
</abp-column>
</abp-row>
</abp-card-header>
<abp-card-body>
<abp-table striped-rows="true" id="BlogsTable" class="nowrap">
<thead>
<tr>
<th>@L["Actions"]</th>
<th>@L["Name"]</th>
<th>@L["ShortName"]</th>
<th>@L["CreationTime"]</th>
<th>@L["Description"]</th>
</tr>
</thead>
</abp-table>
</abp-card-body>
</abp-card>

13
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/Index.cshtml.cs

@ -0,0 +1,13 @@
using System.Threading.Tasks;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
namespace Volo.Blogging.Pages.Admin.Blogs
{
public class IndexModel : AbpPageModel
{
public async Task OnGetAsync()
{
}
}
}

14
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/create.js

@ -0,0 +1,14 @@
var abp = abp || {};
$(function () {
abp.modals.blogCreate = function () {
var initModal = function (publicApi, args) {
var $form = publicApi.getForm();
};
return {
initModal: initModal
}
};
});

14
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/edit.js

@ -0,0 +1,14 @@
var abp = abp || {};
$(function () {
abp.modals.blogEdit = function () {
var initModal = function (publicApi, args) {
var $form = publicApi.getForm();
};
return {
initModal: initModal
}
};
});

84
modules/blogging/src/Volo.Blogging.Web/Pages/Admin/Blogs/index.js

@ -0,0 +1,84 @@
$(function () {
var l = abp.localization.getResource('Blogging');
var _createModal = new abp.ModalManager(abp.appPath + 'Admin/Blogs/Create');
var _editModal = new abp.ModalManager(abp.appPath + 'Admin/Blogs/Edit');
var _dataTable = $('#BlogsTable').DataTable(abp.libs.datatables.normalizeConfiguration({
processing: true,
serverSide: true,
paging: true,
searching: false,
autoWidth: false,
scrollCollapse: true,
order: [[3, "desc"]],
ajax: abp.libs.datatables.createAjax(volo.blogging.blogs.getListPaged),
columnDefs: [
{
rowAction: {
items:
[
{
text: l('Edit'),
visible: function () {
return true; //TODO: Check permission
},
action: function (data) {
_editModal.open({
blogId: data.record.id
});
}
},
{
text: l('Delete'),
visible: function () {
return true; //TODO: Check permission
},
confirmMessage: function (data) { return l('BlogDeletionWarningMessage') },
action: function (data) {
volo.blogging.blogs
.delete(data.record.id)
.then(function () {
_dataTable.ajax.reload();
});
}
}
]
}
},
{
target: 1,
data: "name"
},
{
target: 2,
data: "shortName"
},
{
target: 3,
data: "creationTime",
render: function (date) {
return date;
}
},
{
target: 4,
data: "description"
}
]
}));
$("#CreateNewBlogButtonId").click(function () {
_createModal.open();
});
_createModal.onClose(function () {
_dataTable.ajax.reload();
});
_editModal.onResult(function () {
_dataTable.ajax.reload();
});
});

66
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/BloggingPage.cs

@ -1,4 +1,7 @@
using System; using System;
using System.Text;
using CommonMark;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.Localization; using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.AspNetCore.Mvc.Razor.Internal; using Microsoft.AspNetCore.Mvc.Razor.Internal;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
@ -13,6 +16,8 @@ namespace Volo.Blogging.Pages.Blog
public const string DefaultTitle = "Blog"; public const string DefaultTitle = "Blog";
public const int MaxShortContentLength = 128;
public string GetTitle(string title = null) public string GetTitle(string title = null)
{ {
if (string.IsNullOrWhiteSpace(title)) if (string.IsNullOrWhiteSpace(title))
@ -23,7 +28,50 @@ namespace Volo.Blogging.Pages.Blog
return title; return title;
} }
public string ConvertDatetimeToTimeAgo(DateTime dt) public string GetShortContent(string content)
{
var openingTag = "<p>";
var closingTag = "</p>";
var html = RenderMarkdownToString(content);
var splittedHtml = html.Split(closingTag);
if (splittedHtml.Length < 1)
{
return "";
}
var firstHtmlPart = splittedHtml[0];
var paragraphStartIndex = firstHtmlPart.IndexOf(openingTag, StringComparison.Ordinal) + openingTag.Length;
if (firstHtmlPart.Length - paragraphStartIndex <= MaxShortContentLength)
{
return firstHtmlPart.Substring(paragraphStartIndex);
}
return firstHtmlPart.Substring(paragraphStartIndex, MaxShortContentLength) + "...";
}
public IHtmlContent RenderMarkdownToHtml(string content)
{
byte[] bytes = Encoding.Default.GetBytes(content);
var utf8Content = Encoding.UTF8.GetString(bytes);
var html = CommonMarkConverter.Convert(utf8Content);
return new HtmlString(html);
}
public string RenderMarkdownToString(string content)
{
byte[] bytes = Encoding.Default.GetBytes(content);
var utf8Content = Encoding.UTF8.GetString(bytes);
return CommonMarkConverter.Convert(utf8Content);
}
public LocalizedHtmlString ConvertDatetimeToTimeAgo(DateTime dt)
{ {
var timeDiff = DateTime.Now - dt; var timeDiff = DateTime.Now - dt;
@ -31,37 +79,37 @@ namespace Volo.Blogging.Pages.Blog
if (diffInDays >= 365) if (diffInDays >= 365)
{ {
return diffInDays / 365 + L["YearsAgo"].Value; return L["YearsAgo", diffInDays / 365];
} }
if (diffInDays >= 30) if (diffInDays >= 30)
{ {
return diffInDays / 30 + L["MonthsAgo"].Value; return L["MonthsAgo", diffInDays / 30];
} }
if (diffInDays >= 7) if (diffInDays >= 7)
{ {
return diffInDays / 7 + L["WeeksAgo"].Value; return L["WeeksAgo", diffInDays / 7];
} }
if (diffInDays >= 1) if (diffInDays >= 1)
{ {
return diffInDays + L["DaysAgo"].Value; return L["DaysAgo", diffInDays];
} }
var diffInSeconds = (int) timeDiff.TotalSeconds; var diffInSeconds = (int) timeDiff.TotalSeconds;
if (diffInSeconds >= 3600) if (diffInSeconds >= 3600)
{ {
return diffInSeconds / 3600 + L["HoursAgo"].Value; return L["HoursAgo", diffInSeconds / 3600];
} }
if (diffInSeconds >= 60) if (diffInSeconds >= 60)
{ {
return diffInSeconds / 60 + L["MinutesAgo"].Value; return L["MinutesAgo", diffInSeconds];
} }
if (diffInSeconds >= 1) if (diffInSeconds >= 1)
{ {
return diffInSeconds + L["SecondsAgo"].Value; return L["SecondsAgo", diffInSeconds];
} }
return L["Now"].Value; return L["Now"];
} }
} }
} }

2
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Index.cshtml

@ -12,6 +12,6 @@
<ul> <ul>
@foreach (var blog in Model.Blogs) @foreach (var blog in Model.Blogs)
{ {
<li><a asp-page="./Posts/Index" asp-route-blogShortName="@blog.ShortName">@blog.Name</a></li> <li><a asp-page="./Posts/Index" asp-route-blogShortName="@blog.ShortName">@blog.Name</a></li>
} }
</ul> </ul>

4
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Index.cshtml.cs

@ -1,8 +1,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Blogging.Blogs; using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
namespace Volo.Blogging.Pages.Blog namespace Volo.Blogging.Pages.Blog
{ {
@ -24,7 +26,7 @@ namespace Volo.Blogging.Pages.Blog
if (result.Items.Count == 1) if (result.Items.Count == 1)
{ {
var blog = result.Items[0]; var blog = result.Items[0];
return RedirectToPage("./Posts/Index", new { blogShortName = blog.ShortName}); return RedirectToPage("./Posts/Index", new { blogShortName = blog.ShortName });
} }
Blogs = result.Items; Blogs = result.Items;

270
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml

@ -1,8 +1,10 @@
@page @page
@inherits Volo.Blogging.Pages.Blog.BloggingPage @inherits Volo.Blogging.Pages.Blog.BloggingPage
@using Microsoft.AspNetCore.Authorization @using Microsoft.AspNetCore.Authorization
@using Microsoft.AspNetCore.Http.Extensions
@using Volo.Blogging @using Volo.Blogging
@using Volo.Blogging.Pages.Blog.Posts @using Volo.Blogging.Pages.Blog.Posts
@using Volo.Blogging.Areas.Blog.Helpers.TagHelpers
@inject IAuthorizationService Authorization @inject IAuthorizationService Authorization
@model DetailModel @model DetailModel
@{ @{
@ -20,7 +22,79 @@
</abp-style-bundle> </abp-style-bundle>
} }
<div class="vs-blog"> <div class="vs-blog">
<div class="row mb-5"> <div class="vs-blog-header">
<div class="vs-blog-title">
<div class="row">
<div class="col">
<h1 class="my-0 display-inline-block">
@Model.Blog.ShortName
<small class="text-muted">
@L["Blog"]
</small>
</h1>
</div>
<div class="article-owner">
<div class="article-infos">
<div class="user-card">
<a>
<strong> @(Model.Post.Writer == null ? "" : Model.Post.Writer.UserName)</strong>, @ConvertDatetimeToTimeAgo(Model.Post.CreationTime)
</a>
<span class="seperator">|</span>
<a>
<i class="fa fa-eye"></i> @L["ReadWithCount", @Model.Post.ReadCount]
</a>
<span class="seperator">|</span>
<a>
<i class="fa fa-comment"></i> @L["CommentWithCount", @Model.CommentCount]
</a>
</div>
</div>
</div>
<div class="col-sm-4 text-right">
<br />
<a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName">
<i class="fa fa fa-angle-left"></i> @L["Blog"]
</a>
@if (!string.IsNullOrWhiteSpace(Model.Blog.Facebook))
{
<span class="vs-seperator">|</span>
<a href="https://Facebook.com/@Model.Blog.Facebook">
<i class="fa fa-facebook"></i>
</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.Twitter))
{
<span class="vs-seperator">|</span>
<a href="https://twitter.com/@Model.Blog.Twitter">
<i class="fa fa-twitter"></i>
</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.Instagram))
{
<span class="vs-seperator">|</span>
<a href="https://Instagram.com/@Model.Blog.Instagram">
<i class="fa fa-instagram"></i>
</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.Github))
{
<span class="vs-seperator">|</span>
<a href="https://Github.com/@Model.Blog.Github">
<i class="fa fa-github"></i>
</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.StackOverflow))
{
<span class="vs-seperator">|</span>
<a href="https://StackOverflow.com/@Model.Blog.StackOverflow">
<i class="fa fa-stack-overflow"></i>
</a>
}
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12 mx-auto"> <div class="col-md-12 mx-auto">
<section class="hero-section"> <section class="hero-section">
<div class="hero-articles"> <div class="hero-articles">
@ -34,28 +108,26 @@
} }
</p> </p>
<h1> <h1>
<a href="#">@Model.Post.Title</a> <a asp-page="./Detail" asp-route-postUrl="@Model.Post.Url" asp-route-blogShortName="@Model.BlogShortName">@Model.Post.Title</a>
</h1> </h1>
</div> </div>
<div class="article-owner"> <div class="article-owner">
<div class="article-infos"> <div class="article-infos">
<div class="user-card"> <div class="user-card">
<a href="#"> <a>
<img src="https://placeimg.com/60/60/people" class="article-avatar"> <img gravatar-email="@Model.Post.Writer.Email" default-image="Identicon" class="article-avatar" />
</a> </a>
<a href="#"> <a>
<strong> Halil İbrahim Kalkan</strong>, @ConvertDatetimeToTimeAgo(Model.Post.CreationTime) <strong> @(Model.Post.Writer == null ? "" : Model.Post.Writer.UserName)</strong>, @ConvertDatetimeToTimeAgo(Model.Post.CreationTime)
</a> </a>
<span class="seperator">|</span> <span class="vs-seperator">|</span>
<a href="#"> <a>
<i class="fa fa-eye"></i> @Model.Post.ReadCount @L["Read"] <i class="fa fa-eye"></i> @L["ReadWithCount", @Model.Post.ReadCount]
</a> </a>
<span class="seperator">|</span> <span class="vs-seperator">|</span>
<a href="#"> <a>
<i class="fa fa-comment"></i> @Model.CommentCount @L["Comment"] <i class="fa fa-comment"></i> @L["CommentWithCount", @Model.CommentCount]
</a> </a>
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Posts.Update)) @if (await Authorization.IsGrantedAsync(BloggingPermissions.Posts.Update))
{ {
<span class="seperator">|</span> <span class="seperator">|</span>
@ -63,17 +135,16 @@
<i class="fa fa-pencil"></i> @L["Edit"] <i class="fa fa-pencil"></i> @L["Edit"]
</a> </a>
} }
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Posts.Delete) || (CurrentUser.Id == Model.Post.CreatorId))
{
<span class="seperator">|</span>
<a href="#" id="DeletePostLink" data-postid="@Model.Post.Id" data-blogShortName="@Model.BlogShortName">
<i class="fa fa-trash"></i> @L["Delete"]
</a>
}
</div> </div>
</div> </div>
</div> </div>
<div class="user-link-icons">
<a class="add-bookmark" href="#">
<i class="fa fa-bookmark"></i>
</a>
<a class="share-article" href="#">
<i class="fa fa-share-alt"></i>
</a>
</div>
</div> </div>
</section> </section>
</div> </div>
@ -82,7 +153,10 @@
<div class="row"> <div class="row">
<div class="col-md-8 mx-auto"> <div class="col-md-8 mx-auto">
<section class="post-content"> <section class="post-content">
<p>@Html.Raw(Model.FormattedContent)</p> <p>
@Html.Raw(RenderMarkdownToHtml(Model.Post.Content))
</p>
</section> </section>
</div> </div>
@ -97,41 +171,56 @@
<a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@tag.Name" class="tag">@tag.Name</a> <a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@tag.Name" class="tag">@tag.Name</a>
} }
</div> </div>
<div class="comment-form mt-4">
<div class="clearfix bg-light p-4"> <a class="btn-rounded float-right btn btn-primary"
<h3 class="mt-0">@L["LeaveComment"]</h3> href="@Model.GetTwitterShareUrl(Model.Post.Title, HttpContext.Request.GetEncodedUrl(), "@" + (Model.Blog.Twitter??""))" target="_blank">
<div> <i class="fa fa-twitter"></i>
<form method="post"> @L["ShareOnTwitter"]
<input name="postId" value="@Model.Post.Id" hidden /> </a>
<input name="repliedCommentId" id="repliedCommentId" hidden />
<div class="form-group"> @if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Create))
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea> {
</div> <div class="comment-form mt-4">
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" /> <div class="vs-blog-title mb-0">
</form> <h3>@L["LeaveComment"]</h3>
</div>
<div class="clearfix bg-light p-4">
<div>
<form method="post">
<input name="postId" value="@Model.Post.Id" hidden />
<input name="repliedCommentId" id="repliedCommentId" hidden />
<div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea>
</div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" />
</form>
</div>
</div> </div>
</div> </div>
</div> }
<div class="comment-area">
<div class="comment-area">
@foreach (var commentWithRepliesDto in Model.CommentsWithReplies) @foreach (var commentWithRepliesDto in Model.CommentsWithReplies)
{ {
<div class="media"> <div class="media">
<img class="d-flex mr-3 rounded-circle comment-avatar" src="https://placeimg.com/120/120/people" alt=""> <img gravatar-email="@commentWithRepliesDto.Comment.Writer.Email" default-image="Identicon" class="d-flex mr-3 rounded-circle comment-avatar" />
<div class="media-body"> <div class="media-body">
<h5 class="comment-owner"> <h5 class="comment-owner">
Armağan Ünlü @(commentWithRepliesDto.Comment.Writer == null ? "" : commentWithRepliesDto.Comment.Writer.UserName)
<span class="float-right">@ConvertDatetimeToTimeAgo(commentWithRepliesDto.Comment.CreationTime)</span> <span class="float-right">@ConvertDatetimeToTimeAgo(commentWithRepliesDto.Comment.CreationTime)</span>
</h5> </h5>
<p id="@commentWithRepliesDto.Comment.Id"> <p id="@commentWithRepliesDto.Comment.Id">
@commentWithRepliesDto.Comment.Text @commentWithRepliesDto.Comment.Text
</p> </p>
<div class="comment-buttons"> <div class="comment-buttons font-75 bg-light">
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Create))
{
<a href="#" class="tag replyLink" data-relpyid="@commentWithRepliesDto.Comment.Id">
<i class="fa fa-reply" aria-hidden="true"></i> @L["Reply"]
</a>
}
<a href="#" class="tag replyLink" data-relpyid="@commentWithRepliesDto.Comment.Id">
<i class="fa fa-reply" aria-hidden="true"></i> @L["Reply"]
</a>
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Delete)) @if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Delete))
{ {
<span class="seperator">|</span> <span class="seperator">|</span>
@ -149,34 +238,39 @@
} }
</div> </div>
<div class="comment-form mt-4 replyForm"> @if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Create))
<div class="clearfix bg-light p-4"> {
<h3 class="mt-0">@L["ReplyTo"] Armağan Ünlü</h3> <div class="comment-form mt-4 replyForm">
<div> <div class="clearfix bg-light p-4">
<form method="post"> <h3 class="mt-0">
<input name="postId" value="@Model.Post.Id" hidden /> @L["ReplyTo"]
<input name="repliedCommentId" value="@commentWithRepliesDto.Comment.Id" hidden /> @(commentWithRepliesDto.Comment.Writer == null ? "" : commentWithRepliesDto.Comment.Writer.UserName)
</h3>
<div>
<form method="post">
<input name="postId" value="@Model.Post.Id" hidden />
<input name="repliedCommentId" value="@commentWithRepliesDto.Comment.Id" hidden />
<div class="form-group"> <div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea> <textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea>
</div> </div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Comment"].Value" /> <abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Comment"].Value" />
</form> </form>
</div>
</div> </div>
</div> </div>
</div> }
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Update) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId)) @if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Update) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId))
{ {
<div class="comment-form mt-4 editForm"> <div class="comment-form mt-4 editForm">
<div class="clearfix bg-light p-4"> <div class="clearfix bg-light p-4">
<div> <div>
<form class="editFormClass"> <form class="editFormClass">
<input name="commentId" value="@commentWithRepliesDto.Comment.Id" hidden/> <input name="commentId" value="@commentWithRepliesDto.Comment.Id" hidden />
<div class="form-group"> <div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4">@commentWithRepliesDto.Comment.Text</textarea> <textarea class="form-control no-border" name="text" id="textBoxId" rows="4">@commentWithRepliesDto.Comment.Text</textarea>
</div> </div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value"/> <abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" />
</form> </form>
</div> </div>
</div> </div>
@ -186,24 +280,27 @@
@foreach (var reply in commentWithRepliesDto.Replies) @foreach (var reply in commentWithRepliesDto.Replies)
{ {
<div class="media"> <div class="media">
<img class="d-flex mr-3 rounded-circle answer-avatar" src="https://placeimg.com/120/120/people?t=1535457179534" alt=""> <img gravatar-email="@reply.Writer.Email" default-image="Identicon" class="d-flex mr-3 rounded-circle comment-avatar" />
<div class="media-body"> <div class="media-body">
<h5 class="comment-owner"> <h5 class="comment-owner">
Zlatan Ibrahimovic @(reply.Writer == null ? "" : reply.Writer.UserName)
<span class="float-right">@ConvertDatetimeToTimeAgo(reply.CreationTime)</span> <span class="float-right">@ConvertDatetimeToTimeAgo(reply.CreationTime)</span>
</h5> </h5>
<p id="@reply.Id"> <p id="@reply.Id">
@reply.Text @reply.Text
</p> </p>
<div class="comment-buttons"> <div class="comment-buttons font-75 bg-light">
<a href="#" class="tag" data-relpyid="@commentWithRepliesDto.Comment.Id">
<i class="fa fa-reply" aria-hidden="true"></i> @L["Reply"]
</a>
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Create))
{
<a href="#" class="tag replyLink" data-relpyid="@commentWithRepliesDto.Comment.Id">
<i class="fa fa-reply" aria-hidden="true"></i> @L["Reply"]
</a>
}
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Delete) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId)) @if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Delete) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId))
{ {
<span class="seperator">|</span> <span class="seperator">|</span>
<a href="#" class="tag" data-deleteid="@reply.Id"> <a href="#" class="tag deleteLink" data-deleteid="@reply.Id">
<i class="fa fa-trash" aria-hidden="true"></i> @L["Delete"] <i class="fa fa-trash" aria-hidden="true"></i> @L["Delete"]
</a> </a>
} }
@ -211,39 +308,44 @@
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Update) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId)) @if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Update) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId))
{ {
<span class="seperator">|</span> <span class="seperator">|</span>
<a href="#" class="tag" data-updateid="@reply.Id"> <a href="#" class="tag updateLink" data-updateid="@reply.Id">
<i class="fa fa-pencil" aria-hidden="true"></i> @L["Edit"] <i class="fa fa-pencil" aria-hidden="true"></i> @L["Edit"]
</a> </a>
} }
</div> </div>
<div class="comment-form mt-4 replyForm"> @if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Create))
<div class="clearfix bg-light p-4"> {
<h3 class="mt-0">@L["ReplyTo"] Armağan Ünlü</h3> <div class="comment-form mt-4 replyForm">
<div> <div class="clearfix bg-light p-4">
<form method="post"> <h3 class="mt-0">
<input name="postId" value="@Model.Post.Id" hidden /> @L["ReplyTo"]
<input name="repliedCommentId" value="@commentWithRepliesDto.Comment.Id" hidden /> @(commentWithRepliesDto.Comment.Writer == null ? "" : commentWithRepliesDto.Comment.Writer.UserName)
<div class="form-group"> </h3>
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea> <div>
</div> <form method="post">
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" /> <input name="postId" value="@Model.Post.Id" hidden />
</form> <input name="repliedCommentId" value="@commentWithRepliesDto.Comment.Id" hidden />
<div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4"></textarea>
</div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" />
</form>
</div>
</div> </div>
</div> </div>
</div> }
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Update) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId)) @if (await Authorization.IsGrantedAsync(BloggingPermissions.Comments.Update) || (CurrentUser.Id == commentWithRepliesDto.Comment.CreatorId))
{ {
<div class="comment-form mt-4 editForm"> <div class="comment-form mt-4 editForm">
<div class="clearfix bg-light p-4"> <div class="clearfix bg-light p-4">
<div> <div>
<form class="editFormClass"> <form class="editFormClass">
<input name="commentId" value="@reply.Id" hidden/> <input name="commentId" value="@reply.Id" hidden />
<div class="form-group"> <div class="form-group">
<textarea class="form-control no-border" name="text" id="textBoxId" rows="4">@reply.Text</textarea> <textarea class="form-control no-border" name="text" id="textBoxId" rows="4">@reply.Text</textarea>
</div> </div>
<abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value"/> <abp-button button-type="Primary" class="btn-rounded float-right" type="submit" text="@L["Submit"].Value" />
</form> </form>
</div> </div>
</div> </div>

42
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Detail.cshtml.cs

@ -1,20 +1,21 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
using CommonMark; using System.Web;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Blogging.Blogs; using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Comments; using Volo.Blogging.Comments;
using Volo.Blogging.Comments.Dtos; using Volo.Blogging.Comments.Dtos;
using Volo.Blogging.Posts; using Volo.Blogging.Posts;
namespace Volo.Blogging.Pages.Blog.Posts namespace Volo.Blogging.Pages.Blog.Posts
{ {
public class DetailModel : PageModel public class DetailModel : AbpPageModel
{ {
private const int TwitterLinkLength = 23;
private readonly IPostAppService _postAppService; private readonly IPostAppService _postAppService;
private readonly IBlogAppService _blogAppService; private readonly IBlogAppService _blogAppService;
private readonly ICommentAppService _commentAppService; private readonly ICommentAppService _commentAppService;
@ -27,13 +28,11 @@ namespace Volo.Blogging.Pages.Blog.Posts
[BindProperty] [BindProperty]
public PostDetailsViewModel NewComment { get; set; } public PostDetailsViewModel NewComment { get; set; }
public int CommentCount { get; set; } public int CommentCount { get; set; }
public PostWithDetailsDto Post { get; set; } public PostWithDetailsDto Post { get; set; }
public IHtmlContent FormattedContent { get; set; }
public IReadOnlyList<CommentWithRepliesDto> CommentsWithReplies { get; set; } public IReadOnlyList<CommentWithRepliesDto> CommentsWithReplies { get; set; }
public BlogDto Blog { get; set; } public BlogDto Blog { get; set; }
@ -45,12 +44,12 @@ namespace Volo.Blogging.Pages.Blog.Posts
_commentAppService = commentAppService; _commentAppService = commentAppService;
} }
public async void OnGetAsync() public async Task OnGetAsync()
{ {
await GetData(); await GetData();
} }
public async void OnPostAsync() public async Task OnPostAsync()
{ {
await _commentAppService.CreateAsync(new CreateCommentDto() await _commentAppService.CreateAsync(new CreateCommentDto()
{ {
@ -62,18 +61,10 @@ namespace Volo.Blogging.Pages.Blog.Posts
await GetData(); await GetData();
} }
public async void OnDeleteAsync(Guid commentId)
{
await _commentAppService.DeleteAsync(commentId);
await GetData();
}
private async Task GetData() private async Task GetData()
{ {
Blog = await _blogAppService.GetByShortNameAsync(BlogShortName); Blog = await _blogAppService.GetByShortNameAsync(BlogShortName);
Post = await _postAppService.GetForReadingAsync(new GetPostInput { BlogId = Blog.Id, Url = PostUrl }); Post = await _postAppService.GetForReadingAsync(new GetPostInput { BlogId = Blog.Id, Url = PostUrl });
FormattedContent = RenderMarkdown(Post.Content);
CommentsWithReplies = await _commentAppService.GetHierarchicalListOfPostAsync(new GetCommentListOfPostAsync() { PostId = Post.Id }); CommentsWithReplies = await _commentAppService.GetHierarchicalListOfPostAsync(new GetCommentListOfPostAsync() { PostId = Post.Id });
CountComments(); CountComments();
} }
@ -87,14 +78,19 @@ namespace Volo.Blogging.Pages.Blog.Posts
} }
} }
public IHtmlContent RenderMarkdown(string content) public string GetTwitterShareUrl(string title, string url, string linkedAccounts)
{ {
byte[] bytes = Encoding.Default.GetBytes(content); var readAtString = " | Read More At ";
var utf8Content = Encoding.UTF8.GetString(bytes); var otherCharsLength = (readAtString + linkedAccounts).Length + 1;
var maxTitleLength = 280 - TwitterLinkLength - otherCharsLength;
title = title.Length < maxTitleLength ? title : title.Substring(0, maxTitleLength - 3) + "...";
var text = title +
readAtString +
url +
" " + linkedAccounts;
var html = CommonMarkConverter.Convert(utf8Content); return (new UriBuilder("https://twitter.com/intent/tweet") { Query = "text=" + HttpUtility.UrlEncode(text) }).ToString();
return new HtmlString(html);
} }
public class PostDetailsViewModel public class PostDetailsViewModel

2
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Edit.cshtml.cs

@ -29,7 +29,7 @@ namespace Volo.Blogging.Pages.Blog.Posts
_blogAppService = blogAppService; _blogAppService = blogAppService;
} }
public async void OnGet() public async Task OnGet()
{ {
var postDto = await _postAppService.GetAsync(new Guid(PostId)); var postDto = await _postAppService.GetAsync(new Guid(PostId));
Post = ObjectMapper.Map<PostWithDetailsDto, EditPostViewModel>(postDto); Post = ObjectMapper.Map<PostWithDetailsDto, EditPostViewModel>(postDto);

305
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Index.cshtml

@ -24,72 +24,119 @@
} }
<div class="vs-blog"> <div class="vs-blog">
<div class="vs-blog-header">
<div class="container hero-container pb-5 bg-white"> <div class="vs-blog-title">
<div class="row">
<div class="col">
<h1 class="my-0 display-inline-block">
@Model.Blog.Name
<small class="text-muted">
@L["BLOG"]
</small>
</h1>
</div>
<div class="col-sm-4 text-right">
<br />
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Posts.Create))
{
<a asp-page="./New" asp-route-blogShortName="@Model.BlogShortName">@L["CreateANewPost"]</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.Facebook))
{
<span class="vs-seperator">|</span>
<a href="https://Facebook.com/@Model.Blog.Facebook">
<i class="fa fa-facebook"></i>
</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.Twitter))
{
<span class="vs-seperator">|</span>
<a href="https://twitter.com/@Model.Blog.Twitter">
<i class="fa fa-twitter"></i>
</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.Instagram))
{
<span class="vs-seperator">|</span>
<a href="https://Instagram.com/@Model.Blog.Instagram">
<i class="fa fa-instagram"></i>
</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.Github))
{
<span class="vs-seperator">|</span>
<a href="https://Github.com/@Model.Blog.Github">
<i class="fa fa-github"></i>
</a>
}
@if (!string.IsNullOrWhiteSpace(Model.Blog.StackOverflow))
{
<span class="vs-seperator">|</span>
<a href="https://StackOverflow.com/@Model.Blog.StackOverflow">
<i class="fa fa-stack-overflow"></i>
</a>
}
</div>
</div>
</div>
</div>
<div class="pb-3">
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8">
<section class="hero-section"> <section class="hero-section">
<div class="owl-carousel owl-theme"> @for (var index = 0; index < Model.Posts.Count && index < 1; index++)
{
var post = Model.Posts[index];
<div class="hero-articles">
<div class="img-container">
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">
<img src="@post.CoverImage" class="hero-article-img">
</a>
</div>
<div class="hero-content">
<p class="tags">
@foreach (var tag in post.Tags)
{
<a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@tag.Name" class="tag">@tag.Name</a>
}
</p>
<h2>
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a>
</h2>
<p class="article-sum">
@(GetShortContent(post.Content))
</p>
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName" class="btn btn-primary btn-rounded">@L["ContinueReading"]</a>
@for (var index = 0; index < Model.Posts.Count && index < 3; index++) </div>
{ <div class="article-owner">
var post = Model.Posts[index]; <div class="article-infos">
<div class="item"> <div class="user-card">
<div class="hero-articles"> <a>
<div class="img-container"> <img gravatar-email="@post.Writer.Email" default-image="Identicon" class="article-avatar" />
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName"> </a>
<img src="@post.CoverImage" class="hero-article-img"> <a>
<strong>@post.Writer.UserName</strong>, @ConvertDatetimeToTimeAgo(post.CreationTime)
</a>
<span class="vs-seperator">|</span>
<a>
<i class="fa fa-eye"></i> @L["ReadWithCount", post.ReadCount]
</a>
<span class="vs-seperator">|</span>
<a>
<i class="fa fa-comment"></i> @L["CommentWithCount", @post.CommentCount]
</a> </a>
</div>
<div class="hero-content">
<p class="tags">
@foreach (var tag in post.Tags)
{
<a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@tag.Name" class="tag">@tag.Name</a>
}
</p>
<h2>
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a>
</h2>
<p class="article-sum">
@(post.Content == null ? "" : (post.Content.Length > 150 ? post.Content.Substring(0, 150) + "..." : post.Content))
</p>
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName" class="btn btn-primary btn-rounded">@L["ContinueReading"]</a>
</div>
<div class="article-owner">
<div class="article-infos">
<div class="user-card">
<a>
<img src="https://placeimg.com/120/120/people" class="article-avatar">
</a>
<a>
<strong>Armağan Ünlü</strong>, @ConvertDatetimeToTimeAgo(post.CreationTime)
</a>
<span class="seperator">|</span>
<a>
<i class="fa fa-eye"></i> @post.ReadCount @L["Read"]
</a>
<span class="seperator">|</span>
<a>
<i class="fa fa-comment"></i> @post.CommentCount @L["Comment"]
</a>
</div>
</div>
</div> </div>
</div> </div>
</div> </div>
} </div>
</div>
}
</section> </section>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<h2>@L["WhatIsNew"]</h2>
<div class="list-group"> <div class="list-group">
@for (var index = 0; index < Model.Posts.Count && index < 3; index++) @for (var index = 0; index < Model.Posts.Count && index < 3; index++)
{ {
var post = Model.Posts[index]; var post = Model.Posts[index];
@ -104,14 +151,14 @@
</div> </div>
</div> </div>
<div class="col-8 pl-0"> <div class="col-8 pl-0">
<h3> <h3 class="font-125">
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a> <a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a>
</h3> </h3>
<div class="article-owner"> <div class="article-owner">
<div class="article-infos"> <div class="article-infos">
<div class="user-card"> <div class="user-card">
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName"> <a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">
<strong>Armağan Ünlü</strong>, @ConvertDatetimeToTimeAgo(post.CreationTime) <strong>@post.Writer.UserName</strong>, @ConvertDatetimeToTimeAgo(post.CreationTime)
</a> </a>
</div> </div>
</div> </div>
@ -122,116 +169,88 @@
</div> </div>
} }
</div> </div>
@if (await Authorization.IsGrantedAsync(BloggingPermissions.Posts.Create))
{
<a asp-page="./New" asp-route-blogShortName="@Model.BlogShortName">@L["CreateANewPost"]</a>
}
</div> </div>
</div> </div>
</div> </div>
<div class="container last-post-section pb-5"> <div class="pb-5">
<div class="row"> <div class="row">
<div class="col-md-8"> <div class="col-md-8 box-articles">
<div class="row"> <div class="vs-blog-title">
<div class="col"> <h2>@L["LastPosts"]</h2>
<div class="titline">
<h2>@L["LastPosts"]</h2>
</div>
</div>
</div> </div>
<div class="list-group">
@for (var index = 3; index < Model.Posts.Count; index++)
{
var post = Model.Posts[index];
var oddPost = index % 2 == 1;
@for (var index = 3; index < Model.Posts.Count; index++) <div class="list-group-item">
{ <div class="row @(oddPost?"align-middle":"")">
var post = Model.Posts[index]; <div class="col-md-4 order-md-@(oddPost?"last":"first")">
var oddPost = index % 2 == 1; <div class="img-container">
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">
<section class="box-articles"> <img src="@post.CoverImage" class="box-article-img">
<div class="row @(oddPost?"align-middle":"")"> </a>
<div class="col-md-4 order-md-@(oddPost?"last":"first")"> </div>
<div class="img-container">
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">
<img src="@post.CoverImage" class="box-article-img">
</a>
</div> </div>
</div> <div class="col order-md-@(oddPost?"first":"last")">
<div class="col-md-8 order-md-@(oddPost?"first":"last")"> <p class="tags">
<p class="tags"> @foreach (var tag in post.Tags)
@foreach (var tag in post.Tags) {
{ <a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@tag.Name" class="tag">@tag.Name</a>
<a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@tag.Name" class="tag">@tag.Name</a> }
} </p>
</p> <h3>
<h3> <a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a>
<a asp-page="./Detail" asp-route-postUrl="@post.Url" asp-route-blogShortName="@Model.BlogShortName">@post.Title</a> </h3>
</h3> <p>
<p> @(GetShortContent(post.Content))
@(post.Content == null ? "" : (post.Content.Length > 250 ? post.Content.Substring(0, 150) : post.Content))... </p>
</p> <div class="article-owner">
<div class="article-owner"> <div class="article-infos">
<div class="article-infos"> <div class="user-card">
<div class="user-card"> <a>
<a href="#"> <img gravatar-email="@post.Writer.Email" default-image="Identicon" class="article-avatar" />
<img src="https://placeimg.com/120/120/pople" class="article-avatar"> </a>
</a> <a>
<a> <strong>@post.Writer.UserName</strong>, @ConvertDatetimeToTimeAgo(post.CreationTime)
<strong>Armağan Ünlü</strong>, @ConvertDatetimeToTimeAgo(post.CreationTime) </a>
</a> <span class="vs-seperator">|</span>
<span class="seperator">|</span> <a>
<a> <i class="fa fa-eye"></i> @post.ReadCount
<i class="fa fa-eye"></i> @post.ReadCount </a>
</a> <span class="vs-seperator">|</span>
<span class="seperator">|</span> <a>
<a> <i class="fa fa-comment"></i> @post.CommentCount
<i class="fa fa-comment"></i> @post.CommentCount </a>
</a> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</section> }
} </div>
</div> </div>
<div class="col-md-4"> <div class="col-md-4">
<div class="pb-5 sidebox" id="sidebar"> <div class="vs-blog-title">
<h2>@L["PopularTags"]</h2>
<div class="titline"> </div>
<h2>@L["PopularTags"]</h2> <div class="list-group small-list popular-tags">
</div> @foreach (var popularTag in Model.PopularTags)
<div class="sideboxes"> {
<div class="popular-tags"> <div class="list-group-item">
@foreach (var popularTag in Model.PopularTags) <a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@popularTag.Name">@popularTag.Name <span>(@popularTag.UsageCount @L["Posts"])</span></a>
{
<a asp-page="/Blog/Posts/Index" asp-route-blogShortName="@Model.BlogShortName" asp-route-tagName="@popularTag.Name">@popularTag.Name <span>(@popularTag.UsageCount @L["Posts"])</span></a>
}
</div> </div>
</div> }
<hr>
<footer>
<a href="#">About</a>
<span class="seperator">|</span>
<a href="#">Privacy Policy</a>
<span class="seperator">|</span>
<a href="#">Terms</a>
<p>
Copyright Volosoft - Blog
</p>
</footer>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
</div>
</div>

10
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/Index.cshtml.cs

@ -2,14 +2,16 @@ using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages; using Microsoft.AspNetCore.Mvc.RazorPages;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Blogging.Blogs; using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Posts; using Volo.Blogging.Posts;
using Volo.Blogging.Tagging; using Volo.Blogging.Tagging;
using Volo.Blogging.Tagging.Dtos; using Volo.Blogging.Tagging.Dtos;
namespace Volo.Blogging.Pages.Blog.Posts namespace Volo.Blogging.Pages.Blog.Posts
{ {
public class IndexModel : PageModel public class IndexModel : AbpPageModel
{ {
private readonly IPostAppService _postAppService; private readonly IPostAppService _postAppService;
private readonly IBlogAppService _blogAppService; private readonly IBlogAppService _blogAppService;
@ -21,6 +23,8 @@ namespace Volo.Blogging.Pages.Blog.Posts
[BindProperty(SupportsGet = true)] [BindProperty(SupportsGet = true)]
public string TagName { get; set; } public string TagName { get; set; }
public BlogDto Blog { get; set; }
public IReadOnlyList<PostWithDetailsDto> Posts { get; set; } public IReadOnlyList<PostWithDetailsDto> Posts { get; set; }
public IReadOnlyList<TagDto> PopularTags { get; set; } public IReadOnlyList<TagDto> PopularTags { get; set; }
@ -34,8 +38,8 @@ namespace Volo.Blogging.Pages.Blog.Posts
public async Task OnGetAsync() public async Task OnGetAsync()
{ {
var blog = await _blogAppService.GetByShortNameAsync(BlogShortName); Blog = await _blogAppService.GetByShortNameAsync(BlogShortName);
Posts = (await _postAppService.GetListByBlogIdAndTagName(blog.Id, TagName)).Items; Posts = (await _postAppService.GetListByBlogIdAndTagName(Blog.Id, TagName)).Items;
PopularTags = (await _tagAppService.GetPopularTags(new GetPopularTagsInput {ResultCount = 10})); PopularTags = (await _tagAppService.GetPopularTags(new GetPopularTagsInput {ResultCount = 10}));
} }
} }

3
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/New.cshtml.cs

@ -4,6 +4,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.UI.RazorPages; using Volo.Abp.AspNetCore.Mvc.UI.RazorPages;
using Volo.Blogging.Blogs; using Volo.Blogging.Blogs;
using Volo.Blogging.Blogs.Dtos;
using Volo.Blogging.Posts; using Volo.Blogging.Posts;
namespace Volo.Blogging.Pages.Blog.Posts namespace Volo.Blogging.Pages.Blog.Posts
@ -27,7 +28,7 @@ namespace Volo.Blogging.Pages.Blog.Posts
_blogAppService = blogAppService; _blogAppService = blogAppService;
} }
public async void OnGetAsync() public async Task OnGetAsync()
{ {
Blog = await _blogAppService.GetByShortNameAsync(BlogShortName); Blog = await _blogAppService.GetByShortNameAsync(BlogShortName);
Post = new CreatePostViewModel Post = new CreatePostViewModel

38
modules/blogging/src/Volo.Blogging.Web/Pages/Blog/Posts/detail.js

@ -1,5 +1,7 @@
(function ($) { (function ($) {
var l = abp.localization.getResource('Blogging');
$('div .replyForm').hide(); $('div .replyForm').hide();
$('div .editForm').hide(); $('div .editForm').hide();
@ -25,6 +27,7 @@
$('.replyLink').click(function (event) { $('.replyLink').click(function (event) {
event.preventDefault(); event.preventDefault();
$('div .editForm').hide();
var linkElement = $(this); var linkElement = $(this);
var replyCommentId = linkElement.attr('data-relpyid'); var replyCommentId = linkElement.attr('data-relpyid');
@ -48,8 +51,8 @@
if (deleteCommentId != '' && deleteCommentId !== undefined) { if (deleteCommentId != '' && deleteCommentId !== undefined) {
abp.message.confirm( abp.message.confirm(
'Comment will be deleted.', // TODO: localize l('CommentDeletionWarningMessage'), // TODO: localize
'Are you sure?', l('Are you sure?'),
function(isConfirmed) { function(isConfirmed) {
if (isConfirmed) { if (isConfirmed) {
$.ajax({ $.ajax({
@ -66,8 +69,39 @@
} }
}); });
$('#DeletePostLink').click(function (event) {
event.preventDefault();
var linkElement = $(this);
var deleteCommentId = linkElement.attr('data-postid');
var blogShortName = linkElement.attr('data-blogShortName');
if (deleteCommentId != '' && deleteCommentId !== undefined) {
abp.message.confirm(
l('PostDeletionWarningMessage'), // TODO: localize
l('AreYouSure'),
function(isConfirmed) {
if (isConfirmed) {
$.ajax({
type: "POST",
url: "/Blog/Posts/Delete",
data: { id: deleteCommentId },
success: function () {
window.location.replace('/Blog/' + blogShortName);
}
});
}
}
);
}
});
$('#DeletePostRouteLink').click(function (event) {
console.log("goooo");
});
$('.updateLink').click(function (event) { $('.updateLink').click(function (event) {
event.preventDefault(); event.preventDefault();
$('div .replyForm').hide();
var linkElement = $(this); var linkElement = $(this);
var updateCommentId = $(this).attr('data-updateid'); var updateCommentId = $(this).attr('data-updateid');

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save