From b0cb791bc2acb14a697bc1304049afdd00fc7b71 Mon Sep 17 00:00:00 2001 From: Carl Tibule Date: Thu, 2 Mar 2023 23:45:57 -0600 Subject: [PATCH] Add auto-registration mechanism - Add User registration mechanism when adding auth provider id to claims, if no User Id corresponds to the auth provider Id - Swallow unique constraint violation for unique auth provider id on user table, in case of duplicate requests - Add Serilog logging - Add no bookmarks and no tags message when none is found on Bookmark List page --- .gitignore | 3 +- YABA.API/Controllers/MiscController.cs | 3 +- YABA.API/Controllers/TestController.cs | 42 ++++++++++++ .../Middlewares/AddCustomClaimsMiddleware.cs | 7 ++ YABA.API/Program.cs | 5 ++ YABA.API/Settings/DevOnlyAttribute.cs | 34 ++++++++++ YABA.API/YABA.API.csproj | 4 ++ YABA.API/appsettings.json | 12 ++++ YABA.Service/UserService.cs | 47 +++++++++++--- yaba-web/src/views/bookmarksListView.jsx | 64 +++++++++---------- 10 files changed, 177 insertions(+), 44 deletions(-) create mode 100644 YABA.API/Controllers/TestController.cs create mode 100644 YABA.API/Settings/DevOnlyAttribute.cs diff --git a/.gitignore b/.gitignore index cd8907d..e73ac58 100644 --- a/.gitignore +++ b/.gitignore @@ -246,4 +246,5 @@ logs/ npm-debug.log* yarn-debug.log* yarn-error.log* -.env \ No newline at end of file +.env +Logs/* \ No newline at end of file diff --git a/YABA.API/Controllers/MiscController.cs b/YABA.API/Controllers/MiscController.cs index e497a6f..09cbf6d 100644 --- a/YABA.API/Controllers/MiscController.cs +++ b/YABA.API/Controllers/MiscController.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using System.Net; +using YABA.API.Settings; using YABA.API.ViewModels; using YABA.Service.Interfaces; @@ -24,7 +25,7 @@ namespace YABA.API.Controllers } [HttpGet] - [Obsolete] + [DevOnly] [Route("GetWebsiteMetaData")] [ProducesResponseType(typeof(GetWebsiteMetaDataResponse), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] diff --git a/YABA.API/Controllers/TestController.cs b/YABA.API/Controllers/TestController.cs new file mode 100644 index 0000000..78ade0d --- /dev/null +++ b/YABA.API/Controllers/TestController.cs @@ -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 _logger; + + public TestController(ILogger 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); + } + } +} diff --git a/YABA.API/Middlewares/AddCustomClaimsMiddleware.cs b/YABA.API/Middlewares/AddCustomClaimsMiddleware.cs index db3893f..14ef26e 100644 --- a/YABA.API/Middlewares/AddCustomClaimsMiddleware.cs +++ b/YABA.API/Middlewares/AddCustomClaimsMiddleware.cs @@ -24,6 +24,13 @@ namespace YABA.API.Middlewares if (!string.IsNullOrEmpty(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())); } } diff --git a/YABA.API/Program.cs b/YABA.API/Program.cs index b9ae8be..bcfce83 100644 --- a/YABA.API/Program.cs +++ b/YABA.API/Program.cs @@ -12,6 +12,7 @@ using YABA.API.Settings.Swashbuckle; using YABA.Data.Configuration; using YABA.Data.Context; using YABA.Service.Configuration; +using Serilog; var builder = WebApplication.CreateBuilder(args); 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(); // Run database migrations diff --git a/YABA.API/Settings/DevOnlyAttribute.cs b/YABA.API/Settings/DevOnlyAttribute.cs new file mode 100644 index 0000000..c3886d0 --- /dev/null +++ b/YABA.API/Settings/DevOnlyAttribute.cs @@ -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()); + } + + 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(); + } + } + } + } +} diff --git a/YABA.API/YABA.API.csproj b/YABA.API/YABA.API.csproj index ed1d9b0..7cb397c 100644 --- a/YABA.API/YABA.API.csproj +++ b/YABA.API/YABA.API.csproj @@ -18,6 +18,10 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + + + diff --git a/YABA.API/appsettings.json b/YABA.API/appsettings.json index 117c848..97d4479 100644 --- a/YABA.API/appsettings.json +++ b/YABA.API/appsettings.json @@ -6,6 +6,18 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "Serilog": { + "MinimumLevel": "Information", + "WriteTo": [ + { + "Name": "File", + "Args": { + "path": "Logs/log-.txt", + "rollingInterval": "Day" + } + } + ] + }, "WebClient": { "Url": "https://localhost:3000" } diff --git a/YABA.Service/UserService.cs b/YABA.Service/UserService.cs index 534cd52..f76f7e9 100644 --- a/YABA.Service/UserService.cs +++ b/YABA.Service/UserService.cs @@ -1,4 +1,8 @@ using AutoMapper; +using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Logging; +using Npgsql; +using System; using System.Linq; using System.Threading.Tasks; using YABA.Common.DTOs; @@ -13,12 +17,14 @@ namespace YABA.Service private readonly YABAReadOnlyContext _roContext; private readonly YABAReadWriteContext _context; private readonly IMapper _mapper; + private readonly ILogger _logger; - public UserService (YABAReadOnlyContext roContext, YABAReadWriteContext context, IMapper mapper) + public UserService (YABAReadOnlyContext roContext, YABAReadWriteContext context, IMapper mapper, ILogger logger) { _roContext = roContext; _context = context; _mapper = mapper; + _logger = logger; } public bool IsUserRegistered(string authProviderId) @@ -28,19 +34,40 @@ namespace YABA.Service public async Task RegisterUser(string authProviderId) { - if(IsUserRegistered(authProviderId)) + try { - var user = _roContext.Users.FirstOrDefault(x => x.Auth0Id == authProviderId); - return _mapper.Map(user); + if (!IsUserRegistered(authProviderId)) + { + var userToRegister = new User + { + Auth0Id = authProviderId + }; + + var registedUser = _context.Users.Add(userToRegister); + 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; + } } - var userToRegister = new User - { - Auth0Id = authProviderId - }; + return await Get(authProviderId); + } - var registedUser = _context.Users.Add(userToRegister); - return await _context.SaveChangesAsync() > 0 ? _mapper.Map(registedUser.Entity) : null; + public async Task Get(string authProviderId) + { + var user = await _roContext.Users.FirstOrDefaultAsync(x => x.Auth0Id == authProviderId); + return _mapper.Map(user); } public int GetUserId(string authProviderId) diff --git a/yaba-web/src/views/bookmarksListView.jsx b/yaba-web/src/views/bookmarksListView.jsx index 18afe94..2d08a20 100644 --- a/yaba-web/src/views/bookmarksListView.jsx +++ b/yaba-web/src/views/bookmarksListView.jsx @@ -276,45 +276,44 @@ export function BookmarksListView(props) {
- - -
+ { + (bookmarksState.length > 0 || getSelectedBookmarksCount() > 0) && + { - getDisplayedBookmarksCount() <= 0 && - No bookmarks to display + bookmarksState.length > 0 && + } - - { - getDisplayedBookmarksCount() > 0 && - - } - + + { getSelectedBookmarksCount() > 0 && - - handleHideBookmarks(getSelectedBookmarks().map(x => x.id))}> - {props.showHidden ? "Unhide" : "Hide"} - - - Delete - - +
+ {getSelectedBookmarksCount()} selected + + +
} - -
- - - { - getSelectedTags().length > 0 && ( - getSelectedTags().map((tag) => { - return - }) - ) - } - -
+ + + { + getSelectedTags().length > 0 && ( + getSelectedTags().map((tag) => { + return + }) + ) + } + + + } + + { getFilteredBookmarks().length <= 0 &&
No Bookmarks found
} { getFilteredBookmarks().map((bookmark) => { return + { getTagGroups(getNotSelectedTags()).length <= 0 &&
No Tags gound
} { getTagGroups(getNotSelectedTags()).map((group) => { return
-- 2.49.0