From c8a557463342d499355ed27e425463fa89b7173b Mon Sep 17 00:00:00 2001 From: Ogoun Date: Fri, 5 Apr 2024 04:45:46 +0300 Subject: [PATCH] UPDATES 1. Search by author 2. List of all authors in web 3. Favorite 4. Write block authors and books in db 5. Write to reading queue 6. Book context menu --- .../Controllers/AuthController.cs | 18 +- .../Controllers/BooksController.cs | 114 +++++++++- .../Middlewares/AuthExtractMiddleware.cs | 20 +- src/BukiVedi.App/Program.cs | 30 ++- .../bin/Debug/net8.0/BukiVedi.App.pdb | Bin 28504 -> 30180 bytes .../bin/Debug/net8.0/BukiVedi.Shared.pdb | Bin 33948 -> 33852 bytes .../bin/Debug/net8.0/web/index.html | 198 +++++++++++++++--- .../bin/Debug/net8.0/web/js/bukivedi.js | 68 ++++++ .../bin/Debug/net8.0/web/login.html | 2 - .../obj/Debug/net8.0/ApiEndpoints.json | 136 +++++++++++- .../Debug/net8.0/BukiVedi.App.AssemblyInfo.cs | 2 +- .../BukiVedi.App.AssemblyInfoInputs.cache | 2 +- ...ukiVedi.App.csproj.AssemblyReference.cache | Bin 13417 -> 13417 bytes .../obj/Debug/net8.0/BukiVedi.App.pdb | Bin 28504 -> 30180 bytes src/BukiVedi.App/web/index.html | 192 ++++++++++++++--- src/BukiVedi.App/web/js/bukivedi.js | 68 ++++++ src/BukiVedi.App/web/login.html | 2 - src/BukiVedi.Shared/Entities/Book.cs | 2 +- src/BukiVedi.Shared/Services/Library.cs | 36 ++-- .../bin/Debug/net8.0/BukiVedi.Shared.pdb | Bin 33948 -> 33852 bytes .../net8.0/BukiVedi.Shared.AssemblyInfo.cs | 2 +- .../BukiVedi.Shared.AssemblyInfoInputs.cache | 2 +- ...Vedi.Shared.csproj.AssemblyReference.cache | Bin 11772 -> 12220 bytes .../obj/Debug/net8.0/BukiVedi.Shared.pdb | Bin 33948 -> 33852 bytes .../Debug/net8.0/TitleReader.AssemblyInfo.cs | 2 +- .../TitleReader.AssemblyInfoInputs.cache | 2 +- ...TitleReader.csproj.AssemblyReference.cache | Bin 7927 -> 7869 bytes 27 files changed, 773 insertions(+), 125 deletions(-) diff --git a/src/BukiVedi.App/Controllers/AuthController.cs b/src/BukiVedi.App/Controllers/AuthController.cs index 67e7a74..404e591 100644 --- a/src/BukiVedi.App/Controllers/AuthController.cs +++ b/src/BukiVedi.App/Controllers/AuthController.cs @@ -2,7 +2,10 @@ using BukiVedi.App.Responces; using BukiVedi.Shared.Models; using BukiVedi.Shared.Services; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Mvc; +using System.Security.Claims; namespace BukiVedi.App.Controllers { @@ -27,10 +30,21 @@ namespace BukiVedi.App.Controllers return Ok(new AutoCodeResponse { Success = false, ReasonPhrase = "Аккаунт не найден" }); } var token = UserTokenGenerator.GenerateUserToken(acc); + + var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, + ClaimTypes.Name, ClaimTypes.Role); + identity.AddClaim(new Claim("Subject", acc.Login)); + identity.AddClaim(new Claim("Token", token)); + await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, + new ClaimsPrincipal(identity), + new AuthenticationProperties + { + IsPersistent = true + }); + return Ok(new AutoCodeResponse { - Success = true, - Token = token + Success = true }); } } diff --git a/src/BukiVedi.App/Controllers/BooksController.cs b/src/BukiVedi.App/Controllers/BooksController.cs index a0f0d4d..2334cd2 100644 --- a/src/BukiVedi.App/Controllers/BooksController.cs +++ b/src/BukiVedi.App/Controllers/BooksController.cs @@ -1,12 +1,15 @@ using BukiVedi.App.Requests; using BukiVedi.App.Responces; using BukiVedi.App.Services.Mappers; +using BukiVedi.Shared.Entities; using BukiVedi.Shared.Services; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using MongoDB.Driver; namespace BukiVedi.App.Controllers { + [Authorize("authorized")] [ApiController] public class BooksController : BaseController @@ -18,7 +21,6 @@ namespace BukiVedi.App.Controllers _library = library; } - //[Authorize] [HttpPost("/api/books/search")] public async Task>> Search([FromBody] QueryRequest request) { @@ -26,11 +28,113 @@ namespace BukiVedi.App.Controllers return Ok(books.Select(b => BookEntityMapper.Map(b))); } - [Authorize] - [HttpPost("/api/books/aisearch")] - public async Task>> AiSearch([FromBody] QueryRequest request) + + [HttpPost("/api/books/search/author/{id}")] + public async Task>> SearchByAuthor([FromRoute] string id) { - var books = (await _library.SearchBooks(request.Query)).ToArray(); + var books = (await _library.SearchBooksByAuthor(id)).ToArray(); + return Ok(books.Select(b => BookEntityMapper.Map(b))); + } + + + [HttpPost("/api/books/{id}/favorite")] + public async Task>> AddToFavorite([FromRoute] string id) + { + if (await Tables.Books.ExistById(id)) + { + var account_id = OperationContext.OperationInitiator.Id; + if (!string.IsNullOrEmpty(account_id)) + { + var exists_fiter = Builders.Filter.And + ( + Builders.Filter.Eq(f => f.UserId, account_id), + Builders.Filter.Eq(f => f.BookId, id) + ); + if (await Tables.FavoriteBooks.Exists(exists_fiter) == false) + { + await Tables.FavoriteBooks.Write(new FavoriteBook { BookId = id, UserId = account_id }); + } + } + } + return Ok(); + } + + [HttpPost("/api/books/{id}/block")] + public async Task>> BlockBook([FromRoute] string id) + { + if (await Tables.Books.ExistById(id)) + { + var account_id = OperationContext.OperationInitiator.Id; + if (!string.IsNullOrEmpty(account_id)) + { + var exists_fiter = Builders.Filter.And + ( + Builders.Filter.Eq(f => f.UserId, account_id), + Builders.Filter.Eq(f => f.BookId, id) + ); + if (await Tables.DisgustingBooks.Exists(exists_fiter) == false) + { + await Tables.DisgustingBooks.Write(new DisgustingBook { BookId = id, UserId = account_id }); + } + } + } + return Ok(); + } + + [HttpPost("/api/books/{id}/author/block")] + public async Task>> BlockBookAuthor([FromRoute] string id) + { + var book = await Tables.Books.GetById(id); + if (book != null) + { + var account_id = OperationContext.OperationInitiator.Id; + var authors = book.AuthorIds; + if (!string.IsNullOrEmpty(account_id) && authors?.Count > 0) + { + foreach (var author in authors) + { + var exists_fiter = Builders.Filter.And + ( + Builders.Filter.Eq(f => f.UserId, account_id), + Builders.Filter.Eq(f => f.AuthorId, author) + ); + if (await Tables.DisgustingAuthors.Exists(exists_fiter) == false) + { + await Tables.DisgustingAuthors.Write(new DisgustingAuthor { AuthorId = author, UserId = account_id }); + } + } + } + } + return Ok(); + } + + [HttpPost("/api/books/{id}/read")] + public async Task>> AddBookToReadingQueue([FromRoute] string id) + { + if (await Tables.Books.ExistById(id)) + { + var account_id = OperationContext.OperationInitiator.Id; + if (!string.IsNullOrEmpty(account_id)) + { + var exists_fiter = Builders.Filter.And + ( + Builders.Filter.Eq(f => f.UserId, account_id), + Builders.Filter.Eq(f => f.BookId, id) + ); + if (await Tables.ReadQueue.Exists(exists_fiter) == false) + { + await Tables.ReadQueue.Write(new ReadQueueItem { BookId = id, UserId = account_id, Timestamp = Timestamp.UtcNow }); + } + } + } + return Ok(); + } + + + [HttpPost("/api/books/author/{id}")] + public async Task>> AiSearch([FromRoute] string id) + { + var books = (await _library.SearchBooksByAuthor(id)).ToArray(); return Ok(books.Select(b => BookEntityMapper.Map(b))); } diff --git a/src/BukiVedi.App/Middlewares/AuthExtractMiddleware.cs b/src/BukiVedi.App/Middlewares/AuthExtractMiddleware.cs index 83160ab..bdff4f8 100644 --- a/src/BukiVedi.App/Middlewares/AuthExtractMiddleware.cs +++ b/src/BukiVedi.App/Middlewares/AuthExtractMiddleware.cs @@ -1,10 +1,8 @@ using BukiVedi.Shared; using BukiVedi.Shared.Models; using BukiVedi.Shared.Services; -using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Mvc.Controllers; using System.Net; -using System.Security.Claims; using ZeroLevel; using ZeroLevel.Services.Serialization; @@ -21,7 +19,6 @@ namespace BukiVedi.App.Middlewares public class BukiVediAuthMiddleware { - private const string TOKEN_HEADER = "X-Token"; private readonly IAuthProvider _authProvider; private readonly RequestDelegate _next; @@ -34,8 +31,6 @@ namespace BukiVedi.App.Middlewares public async Task Invoke(HttpContext context) { await ReadDataFromContext(context); - context.Response.Headers.Add("server_time", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss zzz")); - context.Response.Headers.Add("server_time_ts", Timestamp.UtcNow.ToString()); try { await _next(context); @@ -74,28 +69,15 @@ namespace BukiVedi.App.Middlewares private async Task ReadDataFromContext(HttpContext context) { var op_context = new OperationContext(_authProvider, Timestamp.UtcNow); - string token = context.Request?.Headers[TOKEN_HEADER]!; - if (string.IsNullOrWhiteSpace(token)) - { - token = context.Request?.Cookies[TOKEN_HEADER]!; - } + string token = context.User?.Claims?.FirstOrDefault(c => c.Type.Equals("Token"))?.Value!; if (string.IsNullOrWhiteSpace(token) == false) { // TRY AUTHORIZE var authData = ReadAccountInfoFromToken(token); if (authData != null) { - var identity = new ClaimsIdentity(CookieAuthenticationDefaults.AuthenticationScheme, - ClaimTypes.Name, ClaimTypes.Role); - identity.AddClaim(new Claim("Subject", authData.Login)); var account = await _authProvider.GetAccountByLogin(authData.Login); - await op_context.SetAccount(account); - - if (account != null) - { - context.User.AddIdentity(identity); - } context.Items["op_context"] = op_context; return; } diff --git a/src/BukiVedi.App/Program.cs b/src/BukiVedi.App/Program.cs index 4900493..557ac95 100644 --- a/src/BukiVedi.App/Program.cs +++ b/src/BukiVedi.App/Program.cs @@ -3,7 +3,10 @@ using BukiVedi.Shared; using BukiVedi.Shared.Entities; using BukiVedi.Shared.Models; using BukiVedi.Shared.Services; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Builder; using Microsoft.Extensions.FileProviders; +using Microsoft.Extensions.Options; using System.Net; using System.Text; using ZeroLevel; @@ -38,9 +41,15 @@ namespace BukiVedi.App Log.Error(ex, "Fault services initialization"); return; } - var app = builder.Build(); + app.UseCookiePolicy(new CookiePolicyOptions + { + MinimumSameSitePolicy = SameSiteMode.Strict, + }); + + app.UseAuthentication(); + app.UseAuthorization(); app.UseBukiVediAuthMiddleware(); @@ -55,8 +64,7 @@ namespace BukiVedi.App var path = ctx.Context.Request.Path; if (path.HasValue && path.Value.Contains("index.html", StringComparison.OrdinalIgnoreCase)) { - var context = ctx.Context.Items["op_context"] as OperationContext; - if (context == null || context.OperationInitiator == null) + if (ctx.Context.User == null) { ctx.Context.Response.ContentLength = 0; ctx.Context.Response.Body = Stream.Null; @@ -86,8 +94,20 @@ namespace BukiVedi.App } services.AddSingleton(authProvider); services.AddSingleton(new Library()); - services.AddAuthentication(); - services.AddAuthorization(); + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(); + + services.AddAuthorization(options => + { + options.AddPolicy("authorized", policy => + { + policy.RequireAuthenticatedUser(); + policy.AuthenticationSchemes = new List() + { + CookieAuthenticationDefaults.AuthenticationScheme + }; + }); + }); + services.AddControllers(); } } diff --git a/src/BukiVedi.App/bin/Debug/net8.0/BukiVedi.App.pdb b/src/BukiVedi.App/bin/Debug/net8.0/BukiVedi.App.pdb index 5a48303fcb1ec6a3505cd319067b1bccf3fd78af..d9070a027ef4bb0cd3f2ca50ac303be38a803f94 100644 GIT binary patch delta 5810 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 VZiw*Uzq@@^0(eZH|B2a5^k2dfgem|4 diff --git a/src/BukiVedi.App/bin/Debug/net8.0/BukiVedi.Shared.pdb b/src/BukiVedi.App/bin/Debug/net8.0/BukiVedi.Shared.pdb index 0d5880ca3c3141b40369f12430069870f27973b1..4520f0595775897b451e203ea99e4ddbe10b0343 100644 GIT binary patch delta 9238 zcmZ`;34BcF+JDYSGFu`GA|#PjED;gXEFv)>D=L;GM108zskK3ZTFT5M_S)j8s_HeG zs}!Y-rAaN*Qp=~PrB_w;PU-cv&vu4vW z)#0V8HSs1xN1})YM17)&21)RT�VOF}{lMTiS`p4Q)iha9RQXJ@&H;ax>8O=?pU( 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 KN91t}ll}|UjVtc} delta 9356 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 @@ -127,47 +172,134 @@