refactoring the login
This commit is contained in:
parent
2f645b7f0f
commit
9289207085
18 changed files with 209 additions and 368 deletions
3
.vscode/launch.json
vendored
3
.vscode/launch.json
vendored
|
|
@ -28,7 +28,8 @@
|
||||||
"justMyCode": false,
|
"justMyCode": false,
|
||||||
"serverReadyAction": {
|
"serverReadyAction": {
|
||||||
"action": "openExternally",
|
"action": "openExternally",
|
||||||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)"
|
"pattern": "\\bNow listening on:\\s+(https?://\\S+)",
|
||||||
|
"killOnServerStop": true
|
||||||
},
|
},
|
||||||
"env": {
|
"env": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development"
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
|
|
||||||
1
.vscode/settings.json
vendored
1
.vscode/settings.json
vendored
|
|
@ -3,6 +3,7 @@
|
||||||
|
|
||||||
"cSpell.words": [
|
"cSpell.words": [
|
||||||
"appsettings",
|
"appsettings",
|
||||||
|
"asciidoctor",
|
||||||
"Cratie",
|
"Cratie",
|
||||||
"Newtonsoft",
|
"Newtonsoft",
|
||||||
"Npgsql",
|
"Npgsql",
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ namespace Yavsc
|
||||||
public const string UserNameRegExp = @"^[a-zA-Z][a-zA-Z0-9._-]*$";
|
public const string UserNameRegExp = @"^[a-zA-Z][a-zA-Z0-9._-]*$";
|
||||||
public const string UserFileNamePatternRegExp = @"^([a-zA-Z0-9._-]*/)*[a-zA-Z0-9._-]+$";
|
public const string UserFileNamePatternRegExp = @"^([a-zA-Z0-9._-]*/)*[a-zA-Z0-9._-]+$";
|
||||||
|
|
||||||
public const string LoginPath = "~/signin";
|
public const string SigninPath = "~/signin";
|
||||||
public const string LogoutPath = "~/signout";
|
public const string LogoutPath = "~/signout";
|
||||||
|
|
||||||
public const string AccessDeniedPath = "~/Account/AccessDenied";
|
public const string AccessDeniedPath = "~/Account/AccessDenied";
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ using IdentityServer8.Events;
|
||||||
using IdentityServer8.Extensions;
|
using IdentityServer8.Extensions;
|
||||||
using IdentityModel;
|
using IdentityModel;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
|
using Microsoft.CodeAnalysis.CSharp.Syntax;
|
||||||
|
|
||||||
namespace Yavsc.Controllers
|
namespace Yavsc.Controllers
|
||||||
{
|
{
|
||||||
|
|
@ -82,31 +83,134 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public async Task<IActionResult> SignIn(SignInModel model, [FromForm] string button)
|
||||||
|
{
|
||||||
|
if (Request.Method == "POST") // "hGbkk9B94NAae#aG"
|
||||||
|
|
||||||
|
{
|
||||||
|
if (model.Provider == null || model.Provider == "LOCAL")
|
||||||
|
{
|
||||||
|
if (ModelState.IsValid)
|
||||||
|
{
|
||||||
|
var user = await _userManager.FindByNameAsync(model.UserName);
|
||||||
|
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
|
||||||
|
if (user != null)
|
||||||
|
{
|
||||||
|
|
||||||
|
|
||||||
|
var signin = await _signInManager.CheckPasswordSignInAsync(user, model.Password, true);
|
||||||
|
|
||||||
|
// validate username/password against in-memory store
|
||||||
|
if (signin.Succeeded)
|
||||||
|
{
|
||||||
|
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId));
|
||||||
|
|
||||||
|
// only set explicit expiration here if user chooses "remember me".
|
||||||
|
// otherwise we rely upon expiration configured in cookie middleware.
|
||||||
|
await HttpContext.SignInAsync(user, _roleManager, model.RememberMe, _dbContext);
|
||||||
|
var authResult = await HttpContext.AuthenticateAsync();
|
||||||
|
if (!authResult.Succeeded)
|
||||||
|
{
|
||||||
|
return this.Unauthorized();
|
||||||
|
}
|
||||||
|
String bearer = await HttpContext.GetTokenAsync("Bearer", "Bearer");
|
||||||
|
HttpContext.Response.Cookies.Append("Bearer", bearer);
|
||||||
|
|
||||||
|
if (context != null)
|
||||||
|
{
|
||||||
|
if (context.IsNativeClient())
|
||||||
|
{
|
||||||
|
// The client is native, so this change in how to
|
||||||
|
// return the response is for better UX for the end user.
|
||||||
|
return this.LoadingPage("Redirect", model.ReturnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
|
||||||
|
return Redirect(model.ReturnUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
// request for a local page
|
||||||
|
if (Url.IsLocalUrl(model.ReturnUrl))
|
||||||
|
{
|
||||||
|
return Redirect(model.ReturnUrl);
|
||||||
|
}
|
||||||
|
else if (string.IsNullOrEmpty(model.ReturnUrl))
|
||||||
|
{
|
||||||
|
return Redirect("~/");
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// user might have clicked on a malicious link - should be logged
|
||||||
|
throw new Exception("invalid return URL");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await _events.RaiseAsync(new UserLoginFailureEvent(model.UserName, "invalid credentials", clientId: context?.Client.ClientId));
|
||||||
|
ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
|
||||||
|
// Note: the "provider" parameter corresponds to the external
|
||||||
|
// authentication provider choosen by the user agent.
|
||||||
|
if (string.IsNullOrEmpty(model.Provider))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("Provider not specified");
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instruct the middleware corresponding to the requested external identity
|
||||||
|
// provider to redirect the user agent to its own authorization endpoint.
|
||||||
|
// Note: the authenticationScheme parameter must match the value configured in Startup.cs
|
||||||
|
|
||||||
|
// Note: the "returnUrl" parameter corresponds to the endpoint the user agent
|
||||||
|
// will be redirected to after a successful authentication and not
|
||||||
|
// the redirect_uri of the requesting client application.
|
||||||
|
if (string.IsNullOrEmpty(model.ReturnUrl))
|
||||||
|
{
|
||||||
|
_logger.LogWarning("ReturnUrl not specified");
|
||||||
|
return BadRequest();
|
||||||
|
}
|
||||||
|
// Note: this still is not the redirect uri given to the third party provider, at building the challenge.
|
||||||
|
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { model.ReturnUrl }, protocol: "https", host: Config.Authority);
|
||||||
|
var properties = _signInManager.ConfigureExternalAuthenticationProperties(model.Provider, redirectUrl);
|
||||||
|
// var properties = new AuthenticationProperties{RedirectUri=ReturnUrl};
|
||||||
|
return new ChallengeResult(model.Provider, properties);
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return View(model);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Entry point into the login workflow
|
/// Entry point into the login workflow
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpGet]
|
[HttpGet(Constants.SigninPath)]
|
||||||
public async Task<IActionResult> Login(string returnUrl)
|
public async Task<IActionResult> Signin(SignInModel model)
|
||||||
{
|
{
|
||||||
// build a model so we know what to show on the login page
|
// build a model so we know what to show on the login page
|
||||||
var vm = await BuildLoginViewModelAsync(returnUrl);
|
var vm = await BuildLoginViewModelAsync(model);
|
||||||
|
|
||||||
if (vm.IsExternalLoginOnly)
|
if (vm.IsExternalLoginOnly)
|
||||||
{
|
{
|
||||||
// we only have one option for logging in and it's an external provider
|
// we only have one option for logging in and it's an external provider
|
||||||
return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, returnUrl });
|
return RedirectToAction("Challenge", "External", new { scheme = vm.ExternalLoginScheme, model.ReturnUrl });
|
||||||
}
|
}
|
||||||
|
ModelState.Clear();
|
||||||
return View("Signin", vm);
|
return View("Signin", vm);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Handle postback from username/password login
|
/// Handle postback from username/password login
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HttpPost(Constants.LoginPath)]
|
///
|
||||||
|
[HttpPost(Constants.SigninPath)]
|
||||||
[ValidateAntiForgeryToken]
|
[ValidateAntiForgeryToken]
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public async Task<IActionResult> Login([FromForm] SignInModel model, [FromForm] string button)
|
|
||||||
|
public async Task<IActionResult> Signin([FromForm] SignInModel model, [FromForm] string button)
|
||||||
{
|
{
|
||||||
|
|
||||||
// check if we are in the context of an authorization request
|
// check if we are in the context of an authorization request
|
||||||
|
|
@ -273,28 +377,25 @@ namespace Yavsc.Controllers
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
/* helper APIs for the AccountController */
|
/* helper APIs for the AccountController */
|
||||||
/*****************************************/
|
/*****************************************/
|
||||||
private async Task<SignInModel> BuildLoginViewModelAsync(string returnUrl)
|
private async Task<SignInModel> BuildLoginViewModelAsync(SignInModel model)
|
||||||
{
|
{
|
||||||
var context = await _interaction.GetAuthorizationContextAsync(returnUrl);
|
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
|
||||||
if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
|
if (context?.IdP != null && await _schemeProvider.GetSchemeAsync(context.IdP) != null)
|
||||||
{
|
{
|
||||||
var local = context.IdP == IdentityServer8.IdentityServerConstants.LocalIdentityProvider;
|
var local = context.IdP == IdentityServer8.IdentityServerConstants.LocalIdentityProvider;
|
||||||
|
|
||||||
// this is meant to short circuit the UI and only trigger the one external IdP
|
// this is meant to short circuit the UI and only trigger the one external IdP
|
||||||
var vm = new SignInModel
|
|
||||||
{
|
model.EnableLocalLogin = local;
|
||||||
EnableLocalLogin = local,
|
model.UserName = context?.LoginHint;
|
||||||
ReturnUrl = returnUrl,
|
model.IsExternalLoginOnly = false;
|
||||||
UserName = context?.LoginHint,
|
|
||||||
IsExternalLoginOnly = false
|
|
||||||
};
|
|
||||||
|
|
||||||
if (!local)
|
if (!local)
|
||||||
{
|
{
|
||||||
vm.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } };
|
model.ExternalProviders = new[] { new ExternalProvider { AuthenticationScheme = context.IdP } };
|
||||||
}
|
}
|
||||||
|
|
||||||
return vm;
|
return model;
|
||||||
}
|
}
|
||||||
|
|
||||||
var schemes = await _schemeProvider.GetAllSchemesAsync();
|
var schemes = await _schemeProvider.GetAllSchemesAsync();
|
||||||
|
|
@ -322,22 +423,12 @@ namespace Yavsc.Controllers
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return new SignInModel
|
model.RememberMe = AccountOptions.AllowRememberLogin;
|
||||||
{
|
model.EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin;
|
||||||
RememberMe = AccountOptions.AllowRememberLogin,
|
model.UserName = context?.LoginHint;
|
||||||
EnableLocalLogin = allowLocal && AccountOptions.AllowLocalLogin,
|
model.ExternalProviders = providers.ToArray();
|
||||||
ReturnUrl = returnUrl,
|
|
||||||
UserName = context?.LoginHint,
|
|
||||||
ExternalProviders = providers.ToArray()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<SignInModel> BuildLoginViewModelAsync(SignInModel model)
|
return model;
|
||||||
{
|
|
||||||
var vm = await BuildLoginViewModelAsync(model.ReturnUrl);
|
|
||||||
vm.UserName = model.UserName;
|
|
||||||
vm.RememberMe = model.RememberMe;
|
|
||||||
return vm;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
|
private async Task<LogoutViewModel> BuildLogoutViewModelAsync(string logoutId)
|
||||||
|
|
@ -432,28 +523,6 @@ namespace Yavsc.Controllers
|
||||||
return System.Guid.NewGuid().ToString();
|
return System.Guid.NewGuid().ToString();
|
||||||
}
|
}
|
||||||
|
|
||||||
[AllowAnonymous]
|
|
||||||
[HttpGet(Constants.LoginPath)]
|
|
||||||
public ActionResult SignIn(string returnUrl = null)
|
|
||||||
{
|
|
||||||
// Note: the "returnUrl" parameter corresponds to the endpoint the user agent
|
|
||||||
// will be redirected to after a successful authentication and not
|
|
||||||
// the redirect_uri of the requesting client application against the third
|
|
||||||
// party identity provider.
|
|
||||||
return View(new SignInModel
|
|
||||||
{
|
|
||||||
ReturnUrl = returnUrl ?? "/",
|
|
||||||
ExternalProviders = [],
|
|
||||||
EnableLocalLogin = true
|
|
||||||
});
|
|
||||||
/*
|
|
||||||
Note: When using an external login provider, redirect the query :
|
|
||||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(OpenIdConnectDefaults.AuthenticationScheme, returnUrl);
|
|
||||||
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme, properties);
|
|
||||||
*/
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
[AllowAnonymous]
|
[AllowAnonymous]
|
||||||
public ActionResult AccessDenied(string requestUrl = null)
|
public ActionResult AccessDenied(string requestUrl = null)
|
||||||
{
|
{
|
||||||
|
|
@ -466,106 +535,7 @@ namespace Yavsc.Controllers
|
||||||
return View("AccessDenied", requestUrl);
|
return View("AccessDenied", requestUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<IActionResult> SignIn(SignInModel model)
|
|
||||||
{
|
|
||||||
if (Request.Method == "POST") // "hGbkk9B94NAae#aG"
|
|
||||||
|
|
||||||
{
|
|
||||||
if (model.Provider == null || model.Provider == "LOCAL")
|
|
||||||
{
|
|
||||||
if (ModelState.IsValid)
|
|
||||||
{
|
|
||||||
var user = await _userManager.FindByNameAsync(model.UserName);
|
|
||||||
var context = await _interaction.GetAuthorizationContextAsync(model.ReturnUrl);
|
|
||||||
if (user != null)
|
|
||||||
{
|
|
||||||
|
|
||||||
|
|
||||||
var signin = await _signInManager.CheckPasswordSignInAsync(user, model.Password, true);
|
|
||||||
|
|
||||||
// validate username/password against in-memory store
|
|
||||||
if (signin.Succeeded)
|
|
||||||
{
|
|
||||||
await _events.RaiseAsync(new UserLoginSuccessEvent(user.UserName, user.Id, user.UserName, clientId: context?.Client.ClientId));
|
|
||||||
|
|
||||||
// only set explicit expiration here if user chooses "remember me".
|
|
||||||
// otherwise we rely upon expiration configured in cookie middleware.
|
|
||||||
await HttpContext.SignInAsync(user, _roleManager, model.RememberMe, _dbContext);
|
|
||||||
var authResult = await HttpContext.AuthenticateAsync();
|
|
||||||
if (!authResult.Succeeded)
|
|
||||||
{
|
|
||||||
return this.Unauthorized();
|
|
||||||
}
|
|
||||||
String bearer = await HttpContext.GetTokenAsync("Bearer", "Bearer");
|
|
||||||
HttpContext.Response.Cookies.Append("Bearer", bearer);
|
|
||||||
|
|
||||||
if (context != null)
|
|
||||||
{
|
|
||||||
if (context.IsNativeClient())
|
|
||||||
{
|
|
||||||
// The client is native, so this change in how to
|
|
||||||
// return the response is for better UX for the end user.
|
|
||||||
return this.LoadingPage("Redirect", model.ReturnUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// we can trust model.ReturnUrl since GetAuthorizationContextAsync returned non-null
|
|
||||||
return Redirect(model.ReturnUrl);
|
|
||||||
}
|
|
||||||
|
|
||||||
// request for a local page
|
|
||||||
if (Url.IsLocalUrl(model.ReturnUrl))
|
|
||||||
{
|
|
||||||
return Redirect(model.ReturnUrl);
|
|
||||||
}
|
|
||||||
else if (string.IsNullOrEmpty(model.ReturnUrl))
|
|
||||||
{
|
|
||||||
return Redirect("~/");
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// user might have clicked on a malicious link - should be logged
|
|
||||||
throw new Exception("invalid return URL");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await _events.RaiseAsync(new UserLoginFailureEvent(model.UserName, "invalid credentials", clientId: context?.Client.ClientId));
|
|
||||||
ModelState.AddModelError(string.Empty, AccountOptions.InvalidCredentialsErrorMessage);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
|
|
||||||
// Note: the "provider" parameter corresponds to the external
|
|
||||||
// authentication provider choosen by the user agent.
|
|
||||||
if (string.IsNullOrEmpty(model.Provider))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("Provider not specified");
|
|
||||||
return BadRequest();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instruct the middleware corresponding to the requested external identity
|
|
||||||
// provider to redirect the user agent to its own authorization endpoint.
|
|
||||||
// Note: the authenticationScheme parameter must match the value configured in Startup.cs
|
|
||||||
|
|
||||||
// Note: the "returnUrl" parameter corresponds to the endpoint the user agent
|
|
||||||
// will be redirected to after a successful authentication and not
|
|
||||||
// the redirect_uri of the requesting client application.
|
|
||||||
if (string.IsNullOrEmpty(model.ReturnUrl))
|
|
||||||
{
|
|
||||||
_logger.LogWarning("ReturnUrl not specified");
|
|
||||||
return BadRequest();
|
|
||||||
}
|
|
||||||
// Note: this still is not the redirect uri given to the third party provider, at building the challenge.
|
|
||||||
var redirectUrl = Url.Action("ExternalLoginCallback", "Account", new { model.ReturnUrl }, protocol: "https", host: Config.Authority);
|
|
||||||
var properties = _signInManager.ConfigureExternalAuthenticationProperties(model.Provider, redirectUrl);
|
|
||||||
// var properties = new AuthenticationProperties{RedirectUri=ReturnUrl};
|
|
||||||
return new ChallengeResult(model.Provider, properties);
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return View(model);
|
|
||||||
}
|
|
||||||
|
|
||||||
//
|
//
|
||||||
// GET: /Account/Register
|
// GET: /Account/Register
|
||||||
|
|
|
||||||
|
|
@ -2,13 +2,9 @@
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Yavsc.Models;
|
using Yavsc.Models;
|
||||||
using Yavsc.ViewModels.Auth;
|
|
||||||
using Microsoft.AspNetCore.Mvc.Rendering;
|
using Microsoft.AspNetCore.Mvc.Rendering;
|
||||||
using Yavsc.Models.Blog;
|
using Yavsc.Models.Blog;
|
||||||
using Yavsc.Helpers;
|
|
||||||
using Microsoft.Extensions.Options;
|
using Microsoft.Extensions.Options;
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Yavsc.ViewModels.Blog;
|
|
||||||
using Yavsc.Server.Exceptions;
|
using Yavsc.Server.Exceptions;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
|
|
||||||
|
|
@ -97,22 +93,23 @@ namespace Yavsc.Controllers
|
||||||
[Authorize()]
|
[Authorize()]
|
||||||
public IActionResult Create(string title)
|
public IActionResult Create(string title)
|
||||||
{
|
{
|
||||||
var result = new BlogPostCreateViewModel
|
var result = new BlogPostEditViewModel
|
||||||
|
(new BlogPost
|
||||||
{
|
{
|
||||||
Title = title
|
Title = title
|
||||||
};
|
}, true);
|
||||||
SetLangItems();
|
SetLangItems();
|
||||||
return View(result);
|
return View(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
// POST: Blog/Create
|
// POST: Blog/Create
|
||||||
[HttpPost, Authorize, ValidateAntiForgeryToken]
|
[HttpPost, Authorize, ValidateAntiForgeryToken]
|
||||||
public IActionResult Create(BlogPostEditViewModel blogInput)
|
public IActionResult Create(BlogPost blogInput)
|
||||||
{
|
{
|
||||||
if (ModelState.IsValid)
|
if (ModelState.IsValid)
|
||||||
{
|
{
|
||||||
BlogPost post = blogSpotService.Create(User.GetUserId(),
|
BlogPost post = blogSpotService.Create(User.GetUserId(),
|
||||||
BlogPostEditViewModel.FromViewModel(blogInput));
|
blogInput);
|
||||||
return RedirectToAction("Index");
|
return RedirectToAction("Index");
|
||||||
}
|
}
|
||||||
return View("Edit", blogInput);
|
return View("Edit", blogInput);
|
||||||
|
|
@ -133,6 +130,8 @@ namespace Yavsc.Controllers
|
||||||
{
|
{
|
||||||
return NotFound();
|
return NotFound();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
SetLangItems();
|
SetLangItems();
|
||||||
return View(blog);
|
return View(blog);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,3 @@
|
||||||
|
|
||||||
|
|
||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Security.Claims;
|
using System.Security.Claims;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
|
@ -11,14 +9,12 @@ using Yavsc.Models.Blog;
|
||||||
using Yavsc.Server.Exceptions;
|
using Yavsc.Server.Exceptions;
|
||||||
using Yavsc.Server.Helpers;
|
using Yavsc.Server.Helpers;
|
||||||
using Yavsc.ViewModels.Auth;
|
using Yavsc.ViewModels.Auth;
|
||||||
using Yavsc.ViewModels.Blog;
|
|
||||||
|
|
||||||
public class BlogSpotService
|
public class BlogSpotService
|
||||||
{
|
{
|
||||||
private readonly ApplicationDbContext _context;
|
private readonly ApplicationDbContext _context;
|
||||||
private readonly IAuthorizationService _authorizationService;
|
private readonly IAuthorizationService _authorizationService;
|
||||||
|
|
||||||
|
|
||||||
public BlogSpotService(ApplicationDbContext context,
|
public BlogSpotService(ApplicationDbContext context,
|
||||||
IAuthorizationService authorizationService)
|
IAuthorizationService authorizationService)
|
||||||
{
|
{
|
||||||
|
|
@ -26,15 +22,8 @@ public class BlogSpotService
|
||||||
_context = context;
|
_context = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BlogPost Create(string userId, BlogPostEditViewModel blogInput)
|
public BlogPost Create(string userId, BlogPost post)
|
||||||
{
|
{
|
||||||
BlogPost post = new BlogPost
|
|
||||||
{
|
|
||||||
Title = blogInput.Title,
|
|
||||||
Content = blogInput.Content,
|
|
||||||
Photo = blogInput.Photo,
|
|
||||||
AuthorId = userId
|
|
||||||
};
|
|
||||||
_context.BlogSpot.Add(post);
|
_context.BlogSpot.Add(post);
|
||||||
_context.SaveChanges(userId);
|
_context.SaveChanges(userId);
|
||||||
return post;
|
return post;
|
||||||
|
|
@ -42,17 +31,18 @@ public class BlogSpotService
|
||||||
public async Task<BlogPostEditViewModel> GetPostForEdition(ClaimsPrincipal user, long blogPostId)
|
public async Task<BlogPostEditViewModel> GetPostForEdition(ClaimsPrincipal user, long blogPostId)
|
||||||
{
|
{
|
||||||
var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId);
|
var blog = await _context.BlogSpot.Include(x => x.Author).Include(x => x.ACL).SingleAsync(m => m.Id == blogPostId);
|
||||||
var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission());
|
var auth = await _authorizationService.AuthorizeAsync(user, blog, new EditPermission());
|
||||||
if (!auth.Succeeded)
|
if (!auth.Succeeded)
|
||||||
{
|
{
|
||||||
throw new AuthorizationFailureException(auth);
|
throw new AuthorizationFailureException(auth);
|
||||||
}
|
}
|
||||||
return BlogPostEditViewModel.From(blog);
|
var pub = await _context.blogSpotPublications.AnyAsync(x => x.BlogpostId == blog.Id);
|
||||||
|
|
||||||
|
return new BlogPostEditViewModel(blog, pub);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<BlogPost> Details(ClaimsPrincipal user, long blogPostId)
|
public async Task<BlogPost> Details(ClaimsPrincipal user, long blogPostId)
|
||||||
{
|
{
|
||||||
|
|
||||||
BlogPost blog = await _context.BlogSpot
|
BlogPost blog = await _context.BlogSpot
|
||||||
.Include(p => p.Author)
|
.Include(p => p.Author)
|
||||||
.Include(p => p.Tags)
|
.Include(p => p.Tags)
|
||||||
|
|
@ -91,7 +81,7 @@ public class BlogSpotService
|
||||||
// saves the change
|
// saves the change
|
||||||
_context.Update(blog);
|
_context.Update(blog);
|
||||||
var publication = await _context.blogSpotPublications.SingleOrDefaultAsync
|
var publication = await _context.blogSpotPublications.SingleOrDefaultAsync
|
||||||
(p=>p.BlogpostId==blogEdit.Id);
|
(p => p.BlogpostId == blogEdit.Id);
|
||||||
if (publication != null)
|
if (publication != null)
|
||||||
{
|
{
|
||||||
if (!blogEdit.Publish)
|
if (!blogEdit.Publish)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<div class="login-page">
|
<div class="login-page">
|
||||||
<div class="lead">
|
<div class="lead">
|
||||||
<h1>Login</h1>
|
<h1>Signin</h1>
|
||||||
<p>Choose how to login</p>
|
<p>Choose how to login</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form asp-route="Login">
|
<form asp-route="Signin">
|
||||||
<input type="hidden" asp-for="ReturnUrl" />
|
<input type="hidden" asp-for="ReturnUrl" />
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
@ -84,4 +84,4 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
|
|
||||||
<div class="login-page">
|
<div class="login-page">
|
||||||
<div class="lead">
|
<div class="lead">
|
||||||
<h1>Login</h1>
|
<h1>Signin</h1>
|
||||||
<p>Choose how to login</p>
|
<p>Choose how to login</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
@ -19,7 +19,7 @@
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<form asp-action="Login" method="POST" asp-controller="Account">
|
<form asp-action="Signin" method="POST" asp-controller="Account">
|
||||||
<input type="hidden" asp-for="ReturnUrl" />
|
<input type="hidden" asp-for="ReturnUrl" />
|
||||||
|
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
@model BlogPostCreateViewModel
|
@model BlogPostEditViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Blog post edition";
|
ViewData["Title"] = "Blog post edition";
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
@model BlogPostEditViewModel
|
@model BlogPostEditViewModel
|
||||||
|
|
||||||
@{
|
@{
|
||||||
ViewData["Title"] = "Blog post edition";
|
ViewData["Title"] = "Blog post edition";
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +54,6 @@
|
||||||
<h2 title="Titre du post" class="blogtitle" id="titleview" >@Model.Title</h2>
|
<h2 title="Titre du post" class="blogtitle" id="titleview" >@Model.Title</h2>
|
||||||
|
|
||||||
|
|
||||||
<div title="Contenu du post" id="contentview"><asciidoc>@Model.Content</asciidoc></div>
|
|
||||||
|
|
||||||
<hr>
|
<hr>
|
||||||
<form asp-action="Edit">
|
<form asp-action="Edit">
|
||||||
|
|
@ -84,7 +82,8 @@
|
||||||
<div class="form-group mdcoding">
|
<div class="form-group mdcoding">
|
||||||
<label asp-for="Content" class="col-md-2 control-label" ></label>
|
<label asp-for="Content" class="col-md-2 control-label" ></label>
|
||||||
<div class="col-md-10">
|
<div class="col-md-10">
|
||||||
<textarea asp-for="Content" class="form-control" id="Content" data-from="contentview">
|
<textarea asp-for="Content" class="form-control"
|
||||||
|
id="Content" data-from="contentview">
|
||||||
</textarea>
|
</textarea>
|
||||||
<span asp-validation-for="Content" class="text-danger" >
|
<span asp-validation-for="Content" class="text-danger" >
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -112,6 +111,14 @@
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
@await Component.InvokeAsync("Directory","")
|
@await Component.InvokeAsync("Directory","")
|
||||||
|
|
||||||
|
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<div id="prev">
|
||||||
|
<asciidoc>@Model.Content</asciidoc>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div >
|
<div >
|
||||||
@{ await Html.RenderPartialAsync("_PostFilesPartial"); }
|
@{ await Html.RenderPartialAsync("_PostFilesPartial"); }
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,58 +0,0 @@
|
||||||
|
|
||||||
|
|
||||||
@using Yavsc.ViewModels.Account
|
|
||||||
@model SignInModel
|
|
||||||
@{
|
|
||||||
ViewData["Title"] = "Log in";
|
|
||||||
}
|
|
||||||
|
|
||||||
<div class="jumbotron">
|
|
||||||
<h1>@ViewData["Title"]</h1>
|
|
||||||
<hr/>
|
|
||||||
|
|
||||||
<h2 class="lead text-left">Use a local account to log in</h2>
|
|
||||||
<form asp-action="SignIn" class="form-horizontal" role="form">
|
|
||||||
<div asp-validation-summary="All" class="text-danger"></div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="EMail" class="col-md-2 control-label">Username</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input asp-for="UserName" class="form-control" autocomplete="email" aria-required="true" placeholder="UserName" />
|
|
||||||
<span asp-validation-for="UserName" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="Password" class="col-md-2 control-label">Password</label>
|
|
||||||
<div class="col-md-10">
|
|
||||||
<input asp-for="Password" class="form-control" autocomplete="current-password" aria-required="true" placeholder="password" />
|
|
||||||
<span asp-validation-for="Password" class="text-danger"></span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-md-offset-2 col-md-10">
|
|
||||||
<div class="checkbox">
|
|
||||||
<input asp-for="RememberMe" />
|
|
||||||
<label for="RememberMe" class="control-label">Remember me</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<div class="col-md-offset-2 col-md-10">
|
|
||||||
<button type="submit" class="btn btn-light btn-success" name="submit.Signin">Login</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<p>
|
|
||||||
<a asp-action="Register" asp-controller="Account">Register as a new user?</a>
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
<a asp-action="ForgotPassword" asp-controller="Account">Forgot your password</a>
|
|
||||||
</p>
|
|
||||||
<input type="hidden" name="Provider" value="LOCAL" />
|
|
||||||
<input type="hidden" name="ReturnUrl" value="@Model.ReturnUrl" />
|
|
||||||
|
|
||||||
@Html.AntiForgeryToken()
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<hr/>
|
|
||||||
<h2 class="lead text-left">Use another service to log in:</h2>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
@ -52,7 +52,7 @@ else
|
||||||
<a class="nav-link" asp-controller="Account" asp-action="Register" >Register</a>
|
<a class="nav-link" asp-controller="Account" asp-action="Register" >Register</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="dropdown-item">
|
<li class="dropdown-item">
|
||||||
<a class="nav-link" asp-controller="Account" asp-action="Signin" asp-route-ReturnUrl="~/" >Signin</a>
|
<a class="nav-link" asp-controller="Account" asp-action="Signin" asp-route-ReturnUrl="~/" asp-route-AllowRememberLogin="true" >Signin</a>
|
||||||
</li>
|
</li>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,7 +14,6 @@
|
||||||
@using Yavsc.Abstract.Models.Messaging;
|
@using Yavsc.Abstract.Models.Messaging;
|
||||||
@using Yavsc.Billing;
|
@using Yavsc.Billing;
|
||||||
@using Yavsc.Server.Models.Calendar;
|
@using Yavsc.Server.Models.Calendar;
|
||||||
@using Yavsc.ViewModels.Blog;
|
|
||||||
@using Yavsc.ViewModels.Haircut;
|
@using Yavsc.ViewModels.Haircut;
|
||||||
@using Yavsc.ViewModels.Administration;
|
@using Yavsc.ViewModels.Administration;
|
||||||
@using Yavsc.ViewModels.Account;
|
@using Yavsc.ViewModels.Account;
|
||||||
|
|
|
||||||
|
|
@ -1,63 +1,85 @@
|
||||||
using System;
|
|
||||||
using System.Collections.Generic;
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using System.Linq;
|
|
||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
using Yavsc;
|
|
||||||
using Yavsc.Abstract.Identity;
|
using Yavsc.Abstract.Identity;
|
||||||
using Yavsc.Abstract.Identity.Security;
|
using Yavsc.Abstract.Identity.Security;
|
||||||
using Yavsc.Attributes.Validation;
|
|
||||||
using Yavsc.Interfaces;
|
using Yavsc.Interfaces;
|
||||||
using Yavsc.Models.Access;
|
using Yavsc.Models.Access;
|
||||||
using Yavsc.Models.Relationship;
|
using Yavsc.Models.Relationship;
|
||||||
using Yavsc.ViewModels.Blog;
|
|
||||||
|
|
||||||
namespace Yavsc.Models.Blog
|
namespace Yavsc.Models.Blog
|
||||||
{
|
{
|
||||||
public class BlogPost : BlogPostBase,
|
public class BlogPostEditViewModel : BlogPost
|
||||||
IBlogPost, ICircleAuthorized, ITaggable<long>
|
{
|
||||||
|
public BlogPostEditViewModel(BlogPost post, bool publish)
|
||||||
|
{
|
||||||
|
Id = post.Id;
|
||||||
|
AuthorId = post.AuthorId;
|
||||||
|
ACL = post.ACL;
|
||||||
|
Content = post.Content;
|
||||||
|
Title = post.Title;
|
||||||
|
Publish = publish;
|
||||||
|
}
|
||||||
|
public bool Publish { get; set; }
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public class BlogPost :
|
||||||
|
IBlogPost, ICircleAuthorized, ITaggable<long>
|
||||||
{
|
{
|
||||||
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
[Key(), DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||||
[Display(Name="Identifiant du post")]
|
[Display(Name = "Identifiant du post")]
|
||||||
public long Id { get; set; }
|
public long Id { get; set; }
|
||||||
|
|
||||||
[Display(Name="Identifiant de l'auteur")]
|
[StringLength(1024)]
|
||||||
|
public string? Photo { get; set; }
|
||||||
|
|
||||||
|
[StringLength(1024)]
|
||||||
|
[Required]
|
||||||
|
public string Title { get; set; }
|
||||||
|
|
||||||
|
[StringLength(56224)]
|
||||||
|
public string? Content { get; set; }
|
||||||
|
|
||||||
|
[InverseProperty("Target")]
|
||||||
|
[Display(Name = "Liste de contrôle d'accès")]
|
||||||
|
public virtual List<CircleAuthorizationToBlogPost>? ACL { get; set; }
|
||||||
|
|
||||||
|
[Display(Name = "Identifiant de l'auteur")]
|
||||||
[ForeignKey("Author")]
|
[ForeignKey("Author")]
|
||||||
public string? AuthorId { get; set; }
|
public string? AuthorId { get; set; }
|
||||||
|
|
||||||
[Display(Name="Auteur")]
|
[Display(Name = "Auteur")]
|
||||||
public virtual ApplicationUser? Author { set; get; }
|
public virtual ApplicationUser? Author { set; get; }
|
||||||
|
|
||||||
|
|
||||||
[Display(Name="Date de création")]
|
[Display(Name = "Date de création")]
|
||||||
public DateTime DateCreated
|
public DateTime DateCreated
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Display(Name="Créateur")]
|
[Display(Name = "Créateur")]
|
||||||
public string? UserCreated
|
public string? UserCreated
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Display(Name="Dernière modification")]
|
[Display(Name = "Dernière modification")]
|
||||||
public DateTime DateModified
|
public DateTime DateModified
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
[Display(Name="Utilisateur ayant modifé le dernier")]
|
[Display(Name = "Utilisateur ayant modifé le dernier")]
|
||||||
public string? UserModified
|
public string? UserModified
|
||||||
{
|
{
|
||||||
get; set;
|
get; set;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool AuthorizeCircle(long circleId)
|
public bool AuthorizeCircle(long circleId)
|
||||||
{
|
{
|
||||||
return ACL?.Any( i=>i.CircleId == circleId) ?? true;
|
return ACL?.Any(i => i.CircleId == circleId) ?? true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ICircleAuthorization[] GetACL()
|
public ICircleAuthorization[] GetACL()
|
||||||
|
|
@ -67,26 +89,26 @@ namespace Yavsc.Models.Blog
|
||||||
|
|
||||||
public void Tag(Tag tag)
|
public void Tag(Tag tag)
|
||||||
{
|
{
|
||||||
var existent = Tags.SingleOrDefault(t => t.PostId == Id && t.TagId == tag.Id);
|
var existent = Tags.SingleOrDefault(t => t.PostId == Id && t.TagId == tag.Id);
|
||||||
if (existent==null) Tags.Add(new BlogTag { PostId = Id, Tag = tag } );
|
if (existent == null) Tags.Add(new BlogTag { PostId = Id, Tag = tag });
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeTag(Tag tag)
|
public void DeTag(Tag tag)
|
||||||
{
|
{
|
||||||
var existent = Tags.SingleOrDefault(t => (( t.TagId == tag.Id) && t.PostId == Id));
|
var existent = Tags.SingleOrDefault(t => ((t.TagId == tag.Id) && t.PostId == Id));
|
||||||
if (existent!=null) Tags.Remove(existent);
|
if (existent != null) Tags.Remove(existent);
|
||||||
}
|
}
|
||||||
|
|
||||||
public string[] GetTags()
|
public string[] GetTags()
|
||||||
{
|
{
|
||||||
return Tags.Select(t=>t.Tag.Name).ToArray();
|
return Tags.Select(t => t.Tag.Name).ToArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
[InverseProperty("Post")]
|
[InverseProperty("Post")]
|
||||||
public virtual List<BlogTag> Tags { get; set; }
|
public virtual List<BlogTag> Tags { get; set; }
|
||||||
|
|
||||||
[InverseProperty("Post")]
|
[InverseProperty("Post")]
|
||||||
public virtual List<Comment> Comments { get; set; }
|
public virtual List<Comment> Comments { get; set; }
|
||||||
|
|
||||||
IApplicationUser IBlogPost.Author { get => this.Author; }
|
IApplicationUser IBlogPost.Author { get => this.Author; }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
using Yavsc.Models.Blog;
|
|
||||||
|
|
||||||
public class BlogPostEdition
|
|
||||||
{
|
|
||||||
public string Content { get; internal set; }
|
|
||||||
public string Title { get; internal set; }
|
|
||||||
public string Photo { get; internal set; }
|
|
||||||
|
|
||||||
internal static BlogPostEdition From(BlogPost blog)
|
|
||||||
{
|
|
||||||
throw new NotImplementedException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -40,7 +40,6 @@ namespace Yavsc.ViewModels.Account
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public string? Provider { get; set; }
|
public string? Provider { get; set; }
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// This value does NOT indicate the OAuth client method recieving the code,
|
/// This value does NOT indicate the OAuth client method recieving the code,
|
||||||
/// but the one called once authorized.
|
/// but the one called once authorized.
|
||||||
|
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
|
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
|
||||||
using Yavsc.Models.Access;
|
|
||||||
|
|
||||||
namespace Yavsc.ViewModels.Blog
|
|
||||||
{
|
|
||||||
public class BlogPostBase
|
|
||||||
{
|
|
||||||
[StringLength(1024)]
|
|
||||||
public string? Photo { get; set; }
|
|
||||||
|
|
||||||
[StringLength(1024)]
|
|
||||||
[Required]
|
|
||||||
public string Title { get; set; }
|
|
||||||
|
|
||||||
[StringLength(56224)]
|
|
||||||
public string? Content { get; set; }
|
|
||||||
|
|
||||||
[InverseProperty("Target")]
|
|
||||||
[Display(Name = "Liste de contrôle d'accès")]
|
|
||||||
public virtual List<CircleAuthorizationToBlogPost>? ACL { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,50 +0,0 @@
|
||||||
using System.ComponentModel.DataAnnotations;
|
|
||||||
using Yavsc.Models.Blog;
|
|
||||||
|
|
||||||
namespace Yavsc.ViewModels.Blog;
|
|
||||||
|
|
||||||
|
|
||||||
public class BlogPostCreateViewModel : BlogPostBase
|
|
||||||
{
|
|
||||||
public bool Publish { get; set; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public class BlogPostEditViewModel : BlogPostCreateViewModel
|
|
||||||
{
|
|
||||||
|
|
||||||
[Required]
|
|
||||||
|
|
||||||
public required long Id { get; set; }
|
|
||||||
|
|
||||||
|
|
||||||
public BlogPostEditViewModel()
|
|
||||||
{
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static BlogPostEditViewModel From(BlogPost blogInput)
|
|
||||||
{
|
|
||||||
return new BlogPostEditViewModel
|
|
||||||
{
|
|
||||||
Id = blogInput.Id,
|
|
||||||
Title = blogInput.Title,
|
|
||||||
Publish = false,
|
|
||||||
Photo = blogInput.Photo,
|
|
||||||
Content = blogInput.Content,
|
|
||||||
ACL = blogInput.ACL
|
|
||||||
};
|
|
||||||
}
|
|
||||||
public static BlogPostEditViewModel FromViewModel(BlogPostEditViewModel blogInput)
|
|
||||||
{
|
|
||||||
return new BlogPostEditViewModel
|
|
||||||
{
|
|
||||||
Id = blogInput.Id,
|
|
||||||
Title = blogInput.Title,
|
|
||||||
Publish = false,
|
|
||||||
Photo = blogInput.Photo,
|
|
||||||
Content = blogInput.Content,
|
|
||||||
ACL = blogInput.ACL
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue