Add auto-registration mechanism #21
1
.gitignore
vendored
1
.gitignore
vendored
@ -247,3 +247,4 @@ npm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
.env
|
.env
|
||||||
|
Logs/*
|
||||||
@ -2,6 +2,7 @@
|
|||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using System.Net;
|
using System.Net;
|
||||||
|
using YABA.API.Settings;
|
||||||
using YABA.API.ViewModels;
|
using YABA.API.ViewModels;
|
||||||
using YABA.Service.Interfaces;
|
using YABA.Service.Interfaces;
|
||||||
|
|
||||||
@ -24,7 +25,7 @@ namespace YABA.API.Controllers
|
|||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Obsolete]
|
[DevOnly]
|
||||||
[Route("GetWebsiteMetaData")]
|
[Route("GetWebsiteMetaData")]
|
||||||
[ProducesResponseType(typeof(GetWebsiteMetaDataResponse), (int)HttpStatusCode.OK)]
|
[ProducesResponseType(typeof(GetWebsiteMetaDataResponse), (int)HttpStatusCode.OK)]
|
||||||
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||||
|
|||||||
42
YABA.API/Controllers/TestController.cs
Normal file
42
YABA.API/Controllers/TestController.cs
Normal file
@ -0,0 +1,42 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using System.Net;
|
||||||
|
using YABA.API.Settings;
|
||||||
|
|
||||||
|
namespace YABA.API.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[ApiVersion("1")]
|
||||||
|
[DevOnly]
|
||||||
|
[Route("api/v{version:apiVersion}/[controller]")]
|
||||||
|
public class TestController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<TestController> _logger;
|
||||||
|
|
||||||
|
public TestController(ILogger<TestController> logger)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("TestLog")]
|
||||||
|
[ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.NotFound)]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||||
|
public IActionResult TestLog()
|
||||||
|
{
|
||||||
|
var testObject = new { id = 1, name = "Test Message" };
|
||||||
|
_logger.LogDebug("Testing debug: {@TestObject}", testObject);
|
||||||
|
return Ok(testObject);
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("TestLogError")]
|
||||||
|
[ProducesResponseType(typeof(string), (int)HttpStatusCode.OK)]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.NotFound)]
|
||||||
|
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
|
||||||
|
public IActionResult TestLogError()
|
||||||
|
{
|
||||||
|
var testObject = new { id = 1, name = "Test Message" };
|
||||||
|
throw new Exception();
|
||||||
|
return Ok(testObject);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -24,6 +24,13 @@ namespace YABA.API.Middlewares
|
|||||||
if (!string.IsNullOrEmpty(userAuthProviderId))
|
if (!string.IsNullOrEmpty(userAuthProviderId))
|
||||||
{
|
{
|
||||||
var userId = userService.GetUserId(userAuthProviderId);
|
var userId = userService.GetUserId(userAuthProviderId);
|
||||||
|
|
||||||
|
if(userId <= 0)
|
||||||
|
{
|
||||||
|
var registedUser = await userService.RegisterUser(userAuthProviderId);
|
||||||
|
userId = registedUser.Id;
|
||||||
|
}
|
||||||
|
|
||||||
httpContext.User.Identities.FirstOrDefault().AddClaim(new Claim(ClaimsLookup.UserId.GetClaimName(), userId.ToString()));
|
httpContext.User.Identities.FirstOrDefault().AddClaim(new Claim(ClaimsLookup.UserId.GetClaimName(), userId.ToString()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,6 +12,7 @@ using YABA.API.Settings.Swashbuckle;
|
|||||||
using YABA.Data.Configuration;
|
using YABA.Data.Configuration;
|
||||||
using YABA.Data.Context;
|
using YABA.Data.Context;
|
||||||
using YABA.Service.Configuration;
|
using YABA.Service.Configuration;
|
||||||
|
using Serilog;
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
var configuration = builder.Configuration;
|
var configuration = builder.Configuration;
|
||||||
@ -77,6 +78,10 @@ builder.Services.AddSwaggerGen(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Add Serilog
|
||||||
|
Log.Logger = new LoggerConfiguration().ReadFrom.Configuration(configuration).CreateLogger();
|
||||||
|
builder.Host.UseSerilog();
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Run database migrations
|
// Run database migrations
|
||||||
|
|||||||
34
YABA.API/Settings/DevOnlyAttribute.cs
Normal file
34
YABA.API/Settings/DevOnlyAttribute.cs
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.AspNetCore.Mvc.Filters;
|
||||||
|
|
||||||
|
namespace YABA.API.Settings
|
||||||
|
{
|
||||||
|
// Source: https://stackoverflow.com/questions/56495475/asp-net-core-its-possible-to-configure-an-action-in-controller-only-in-developm
|
||||||
|
public class DevOnlyAttribute : Attribute, IFilterFactory
|
||||||
|
{
|
||||||
|
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
|
||||||
|
{
|
||||||
|
return new DevOnlyAttributeImpl(serviceProvider.GetRequiredService<IWebHostEnvironment>());
|
||||||
|
}
|
||||||
|
|
||||||
|
public bool IsReusable => true;
|
||||||
|
|
||||||
|
private class DevOnlyAttributeImpl : Attribute, IAuthorizationFilter
|
||||||
|
{
|
||||||
|
public DevOnlyAttributeImpl(IWebHostEnvironment hostingEnv)
|
||||||
|
{
|
||||||
|
HostingEnv = hostingEnv;
|
||||||
|
}
|
||||||
|
|
||||||
|
private IWebHostEnvironment HostingEnv { get; }
|
||||||
|
|
||||||
|
public void OnAuthorization(AuthorizationFilterContext context)
|
||||||
|
{
|
||||||
|
if (!HostingEnv.IsDevelopment())
|
||||||
|
{
|
||||||
|
context.Result = new NotFoundResult();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -18,6 +18,10 @@
|
|||||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.1" />
|
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.15.1" />
|
||||||
|
<PackageReference Include="Serilog" Version="2.12.0" />
|
||||||
|
<PackageReference Include="Serilog.AspNetCore" Version="6.1.0" />
|
||||||
|
<PackageReference Include="Serilog.Settings.Configuration" Version="3.4.0" />
|
||||||
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.2.3" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="6.5.0" />
|
||||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
|
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="6.5.0" />
|
||||||
|
|||||||
@ -6,6 +6,18 @@
|
|||||||
"Microsoft.Hosting.Lifetime": "Information"
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"Serilog": {
|
||||||
|
"MinimumLevel": "Information",
|
||||||
|
"WriteTo": [
|
||||||
|
{
|
||||||
|
"Name": "File",
|
||||||
|
"Args": {
|
||||||
|
"path": "Logs/log-.txt",
|
||||||
|
"rollingInterval": "Day"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
"WebClient": {
|
"WebClient": {
|
||||||
"Url": "https://localhost:3000"
|
"Url": "https://localhost:3000"
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,8 @@
|
|||||||
using AutoMapper;
|
using AutoMapper;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Npgsql;
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
using YABA.Common.DTOs;
|
using YABA.Common.DTOs;
|
||||||
@ -13,12 +17,14 @@ namespace YABA.Service
|
|||||||
private readonly YABAReadOnlyContext _roContext;
|
private readonly YABAReadOnlyContext _roContext;
|
||||||
private readonly YABAReadWriteContext _context;
|
private readonly YABAReadWriteContext _context;
|
||||||
private readonly IMapper _mapper;
|
private readonly IMapper _mapper;
|
||||||
|
private readonly ILogger<UserService> _logger;
|
||||||
|
|
||||||
public UserService (YABAReadOnlyContext roContext, YABAReadWriteContext context, IMapper mapper)
|
public UserService (YABAReadOnlyContext roContext, YABAReadWriteContext context, IMapper mapper, ILogger<UserService> logger)
|
||||||
{
|
{
|
||||||
_roContext = roContext;
|
_roContext = roContext;
|
||||||
_context = context;
|
_context = context;
|
||||||
_mapper = mapper;
|
_mapper = mapper;
|
||||||
|
_logger = logger;
|
||||||
}
|
}
|
||||||
|
|
||||||
public bool IsUserRegistered(string authProviderId)
|
public bool IsUserRegistered(string authProviderId)
|
||||||
@ -28,19 +34,40 @@ namespace YABA.Service
|
|||||||
|
|
||||||
public async Task<UserDTO> RegisterUser(string authProviderId)
|
public async Task<UserDTO> RegisterUser(string authProviderId)
|
||||||
{
|
{
|
||||||
if(IsUserRegistered(authProviderId))
|
try
|
||||||
|
{
|
||||||
|
if (!IsUserRegistered(authProviderId))
|
||||||
{
|
{
|
||||||
var user = _roContext.Users.FirstOrDefault(x => x.Auth0Id == authProviderId);
|
|
||||||
return _mapper.Map<UserDTO>(user);
|
|
||||||
}
|
|
||||||
|
|
||||||
var userToRegister = new User
|
var userToRegister = new User
|
||||||
{
|
{
|
||||||
Auth0Id = authProviderId
|
Auth0Id = authProviderId
|
||||||
};
|
};
|
||||||
|
|
||||||
var registedUser = _context.Users.Add(userToRegister);
|
var registedUser = _context.Users.Add(userToRegister);
|
||||||
return await _context.SaveChangesAsync() > 0 ? _mapper.Map<UserDTO>(registedUser.Entity) : null;
|
await _context.SaveChangesAsync();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
if(ex.InnerException is PostgresException &&
|
||||||
|
((PostgresException)ex.InnerException).Code == "23505")
|
||||||
|
{
|
||||||
|
var postgresException = (PostgresException)ex.InnerException;
|
||||||
|
_logger.LogWarning("Swallowing constraint violation: {@ConstraintName} for {@AuthProviderId}", postgresException.ConstraintName, authProviderId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
throw ex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return await Get(authProviderId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<UserDTO> Get(string authProviderId)
|
||||||
|
{
|
||||||
|
var user = await _roContext.Users.FirstOrDefaultAsync(x => x.Auth0Id == authProviderId);
|
||||||
|
return _mapper.Map<UserDTO>(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int GetUserId(string authProviderId)
|
public int GetUserId(string authProviderId)
|
||||||
|
|||||||
@ -276,32 +276,28 @@ export function BookmarksListView(props) {
|
|||||||
<hr className="mt-1" />
|
<hr className="mt-1" />
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Row>
|
|
||||||
<Col xs="9" className="mb-3">
|
|
||||||
<div className="d-flex justify-content-start align-items-center">
|
|
||||||
{
|
{
|
||||||
getDisplayedBookmarksCount() <= 0 &&
|
(bookmarksState.length > 0 || getSelectedBookmarksCount() > 0) && <Row>
|
||||||
<span className="fs-4">No bookmarks to display</span>
|
<Col xs="2" className="mb-3">
|
||||||
}
|
|
||||||
|
|
||||||
{
|
{
|
||||||
getDisplayedBookmarksCount() > 0 &&
|
bookmarksState.length > 0 &&
|
||||||
<Button className="me-2" variant="primary" onClick={() => dispatchBookmarksState({type: getAreAllBookmarksSelected() ? "UNSELECT_ALL" : "SELECT_ALL"})}>{getAreAllBookmarksSelected() ? "Unselect All" : "Select All" }</Button>
|
<Button variant="primary" onClick={() => dispatchBookmarksState({type: getAreAllBookmarksSelected() ? "UNSELECT_ALL" : "SELECT_ALL"})}>{getAreAllBookmarksSelected() ? "Unselect All" : "Select All" }</Button>
|
||||||
}
|
}
|
||||||
|
</Col>
|
||||||
|
<Col xs="7" className="mb-3">
|
||||||
{
|
{
|
||||||
getSelectedBookmarksCount() > 0 &&
|
getSelectedBookmarksCount() > 0 &&
|
||||||
<DropdownButton variant="secondary" title={`${getSelectedBookmarksCount()} selected`}>
|
<div className="d-flex justify-content-end align-items-center">
|
||||||
<Dropdown.Item onClick={() => handleHideBookmarks(getSelectedBookmarks().map(x => x.id))}>
|
<span className="fs-5 me-2"> {getSelectedBookmarksCount()} selected</span>
|
||||||
{props.showHidden ? "Unhide" : "Hide"}
|
<Button variant="primary" className="me-2" onClick={() => handleHideBookmarks(getSelectedBookmarks().map(x => x.id))}>{props.showHidden ? "Unhide" : "Hide"}</Button>
|
||||||
</Dropdown.Item>
|
<Button
|
||||||
<Dropdown.Item onClick={handleDeleteMultipleBookmarks}>
|
variant="danger"
|
||||||
<span className="text-danger">Delete</span>
|
onClick={handleDeleteMultipleBookmarks}
|
||||||
</Dropdown.Item>
|
>
|
||||||
</DropdownButton>
|
Delete
|
||||||
}
|
</Button>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
|
}
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="3" className="mb-3">
|
<Col xs="3" className="mb-3">
|
||||||
{
|
{
|
||||||
@ -313,8 +309,11 @@ export function BookmarksListView(props) {
|
|||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
|
}
|
||||||
|
|
||||||
<Row>
|
<Row>
|
||||||
<Col xs="9">
|
<Col xs="9">
|
||||||
|
{ getFilteredBookmarks().length <= 0 && <div className="fs-3">No Bookmarks found</div> }
|
||||||
{
|
{
|
||||||
getFilteredBookmarks().map((bookmark) => {
|
getFilteredBookmarks().map((bookmark) => {
|
||||||
return <Bookmark
|
return <Bookmark
|
||||||
@ -329,6 +328,7 @@ export function BookmarksListView(props) {
|
|||||||
}
|
}
|
||||||
</Col>
|
</Col>
|
||||||
<Col xs="3">
|
<Col xs="3">
|
||||||
|
{ getTagGroups(getNotSelectedTags()).length <= 0 && <div className="fs-3">No Tags gound</div> }
|
||||||
{
|
{
|
||||||
getTagGroups(getNotSelectedTags()).map((group) => {
|
getTagGroups(getNotSelectedTags()).map((group) => {
|
||||||
return <div key={group.name} className="mb-3">
|
return <div key={group.name} className="mb-3">
|
||||||
|
|||||||
Reference in New Issue
Block a user