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
main
Ogoun 6 months ago
parent 4e697c91a8
commit c8a5574633

@ -2,7 +2,10 @@
using BukiVedi.App.Responces; using BukiVedi.App.Responces;
using BukiVedi.Shared.Models; using BukiVedi.Shared.Models;
using BukiVedi.Shared.Services; using BukiVedi.Shared.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace BukiVedi.App.Controllers namespace BukiVedi.App.Controllers
{ {
@ -27,10 +30,21 @@ namespace BukiVedi.App.Controllers
return Ok(new AutoCodeResponse { Success = false, ReasonPhrase = "Аккаунт не найден" }); return Ok(new AutoCodeResponse { Success = false, ReasonPhrase = "Аккаунт не найден" });
} }
var token = UserTokenGenerator.GenerateUserToken(acc); 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 return Ok(new AutoCodeResponse
{ {
Success = true, Success = true
Token = token
}); });
} }
} }

@ -1,12 +1,15 @@
using BukiVedi.App.Requests; using BukiVedi.App.Requests;
using BukiVedi.App.Responces; using BukiVedi.App.Responces;
using BukiVedi.App.Services.Mappers; using BukiVedi.App.Services.Mappers;
using BukiVedi.Shared.Entities;
using BukiVedi.Shared.Services; using BukiVedi.Shared.Services;
using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc;
using MongoDB.Driver;
namespace BukiVedi.App.Controllers namespace BukiVedi.App.Controllers
{ {
[Authorize("authorized")]
[ApiController] [ApiController]
public class BooksController public class BooksController
: BaseController : BaseController
@ -18,7 +21,6 @@ namespace BukiVedi.App.Controllers
_library = library; _library = library;
} }
//[Authorize]
[HttpPost("/api/books/search")] [HttpPost("/api/books/search")]
public async Task<ActionResult<IEnumerable<BookResponse>>> Search([FromBody] QueryRequest request) public async Task<ActionResult<IEnumerable<BookResponse>>> Search([FromBody] QueryRequest request)
{ {
@ -26,11 +28,113 @@ namespace BukiVedi.App.Controllers
return Ok(books.Select(b => BookEntityMapper.Map(b))); return Ok(books.Select(b => BookEntityMapper.Map(b)));
} }
[Authorize]
[HttpPost("/api/books/aisearch")] [HttpPost("/api/books/search/author/{id}")]
public async Task<ActionResult<IEnumerable<BookResponse>>> AiSearch([FromBody] QueryRequest request) public async Task<ActionResult<IEnumerable<BookResponse>>> 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<ActionResult<IEnumerable<BookResponse>>> 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<FavoriteBook>.Filter.And
(
Builders<FavoriteBook>.Filter.Eq(f => f.UserId, account_id),
Builders<FavoriteBook>.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<ActionResult<IEnumerable<BookResponse>>> 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<DisgustingBook>.Filter.And
(
Builders<DisgustingBook>.Filter.Eq(f => f.UserId, account_id),
Builders<DisgustingBook>.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<ActionResult<IEnumerable<BookResponse>>> 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<DisgustingAuthor>.Filter.And
(
Builders<DisgustingAuthor>.Filter.Eq(f => f.UserId, account_id),
Builders<DisgustingAuthor>.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<ActionResult<IEnumerable<BookResponse>>> 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<ReadQueueItem>.Filter.And
(
Builders<ReadQueueItem>.Filter.Eq(f => f.UserId, account_id),
Builders<ReadQueueItem>.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<ActionResult<IEnumerable<BookResponse>>> AiSearch([FromRoute] string id)
{
var books = (await _library.SearchBooksByAuthor(id)).ToArray();
return Ok(books.Select(b => BookEntityMapper.Map(b))); return Ok(books.Select(b => BookEntityMapper.Map(b)));
} }

@ -1,10 +1,8 @@
using BukiVedi.Shared; using BukiVedi.Shared;
using BukiVedi.Shared.Models; using BukiVedi.Shared.Models;
using BukiVedi.Shared.Services; using BukiVedi.Shared.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Mvc.Controllers; using Microsoft.AspNetCore.Mvc.Controllers;
using System.Net; using System.Net;
using System.Security.Claims;
using ZeroLevel; using ZeroLevel;
using ZeroLevel.Services.Serialization; using ZeroLevel.Services.Serialization;
@ -21,7 +19,6 @@ namespace BukiVedi.App.Middlewares
public class BukiVediAuthMiddleware public class BukiVediAuthMiddleware
{ {
private const string TOKEN_HEADER = "X-Token";
private readonly IAuthProvider _authProvider; private readonly IAuthProvider _authProvider;
private readonly RequestDelegate _next; private readonly RequestDelegate _next;
@ -34,8 +31,6 @@ namespace BukiVedi.App.Middlewares
public async Task Invoke(HttpContext context) public async Task Invoke(HttpContext context)
{ {
await ReadDataFromContext(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 try
{ {
await _next(context); await _next(context);
@ -74,28 +69,15 @@ namespace BukiVedi.App.Middlewares
private async Task ReadDataFromContext(HttpContext context) private async Task ReadDataFromContext(HttpContext context)
{ {
var op_context = new OperationContext(_authProvider, Timestamp.UtcNow); var op_context = new OperationContext(_authProvider, Timestamp.UtcNow);
string token = context.Request?.Headers[TOKEN_HEADER]!; string token = context.User?.Claims?.FirstOrDefault(c => c.Type.Equals("Token"))?.Value!;
if (string.IsNullOrWhiteSpace(token))
{
token = context.Request?.Cookies[TOKEN_HEADER]!;
}
if (string.IsNullOrWhiteSpace(token) == false) if (string.IsNullOrWhiteSpace(token) == false)
{ {
// TRY AUTHORIZE // TRY AUTHORIZE
var authData = ReadAccountInfoFromToken(token); var authData = ReadAccountInfoFromToken(token);
if (authData != null) 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); var account = await _authProvider.GetAccountByLogin(authData.Login);
await op_context.SetAccount(account); await op_context.SetAccount(account);
if (account != null)
{
context.User.AddIdentity(identity);
}
context.Items["op_context"] = op_context; context.Items["op_context"] = op_context;
return; return;
} }

@ -3,7 +3,10 @@ using BukiVedi.Shared;
using BukiVedi.Shared.Entities; using BukiVedi.Shared.Entities;
using BukiVedi.Shared.Models; using BukiVedi.Shared.Models;
using BukiVedi.Shared.Services; using BukiVedi.Shared.Services;
using Microsoft.AspNetCore.Authentication.Cookies;
using Microsoft.AspNetCore.Builder;
using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Options;
using System.Net; using System.Net;
using System.Text; using System.Text;
using ZeroLevel; using ZeroLevel;
@ -38,9 +41,15 @@ namespace BukiVedi.App
Log.Error(ex, "Fault services initialization"); Log.Error(ex, "Fault services initialization");
return; return;
} }
var app = builder.Build(); var app = builder.Build();
app.UseCookiePolicy(new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.Strict,
});
app.UseAuthentication();
app.UseAuthorization(); app.UseAuthorization();
app.UseBukiVediAuthMiddleware(); app.UseBukiVediAuthMiddleware();
@ -55,8 +64,7 @@ namespace BukiVedi.App
var path = ctx.Context.Request.Path; var path = ctx.Context.Request.Path;
if (path.HasValue && path.Value.Contains("index.html", StringComparison.OrdinalIgnoreCase)) if (path.HasValue && path.Value.Contains("index.html", StringComparison.OrdinalIgnoreCase))
{ {
var context = ctx.Context.Items["op_context"] as OperationContext; if (ctx.Context.User == null)
if (context == null || context.OperationInitiator == null)
{ {
ctx.Context.Response.ContentLength = 0; ctx.Context.Response.ContentLength = 0;
ctx.Context.Response.Body = Stream.Null; ctx.Context.Response.Body = Stream.Null;
@ -86,8 +94,20 @@ namespace BukiVedi.App
} }
services.AddSingleton<IAuthProvider>(authProvider); services.AddSingleton<IAuthProvider>(authProvider);
services.AddSingleton<ILibrary>(new Library()); services.AddSingleton<ILibrary>(new Library());
services.AddAuthentication(); services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie();
services.AddAuthorization();
services.AddAuthorization(options =>
{
options.AddPolicy("authorized", policy =>
{
policy.RequireAuthenticatedUser();
policy.AuthenticationSchemes = new List<string>()
{
CookieAuthenticationDefaults.AuthenticationScheme
};
});
});
services.AddControllers(); services.AddControllers();
} }
} }

@ -31,6 +31,7 @@
} }
#search { #search {
display: block;
margin-top: 20px; margin-top: 20px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
@ -43,9 +44,12 @@
} }
#searchButton { #searchButton {
display: block;
margin-top: 10px;
margin-left: auto; margin-left: auto;
margin-right: auto; margin-right: auto;
max-width: 160px; max-width: 200px;
width: 200px;
min-width: 80px; min-width: 80px;
height: 33px; height: 33px;
} }
@ -106,6 +110,7 @@
margin-left: 30px; margin-left: 30px;
padding-bottom: 5px; padding-bottom: 5px;
color: #06001aff; color: #06001aff;
width: 100%;
} }
.card .unassigned { .card .unassigned {
@ -115,6 +120,46 @@
.card .due { .card .due {
color: #ec7373; color: #ec7373;
} }
.menu {
visibility: hidden;
z-index: 1000;
position: relative;
height: 0;
background-color: #171717;
}
.open-menu {
text-decoration: none;
font-size: 18px;
font-weight: bolder;
}
.menu.opened {
visibility: visible;
color: azure;
width: 180px;
}
.menu-item {
text-decoration: none;
background-color: #272727;
height: 28px;
width: 100%;
text-align: left;
padding-top: 12px;
padding-left: 16px;
cursor: pointer;
}
.menu-item:hover {
background-color: #373737;
}
ul {
list-style-type: none;
}
</style> </style>
</head> </head>
<body> <body>
@ -127,47 +172,134 @@
</div> </div>
<script> <script>
const MenuActions =
{
AddToFavorite: '0',
ToReadingQueue: '1',
BookBlock: '2',
AuthorBlock: '3',
Tag: '4',
Note: '5',
Share: '6'
};
function Tag(id) { }
function Note(id) { }
function Share(id) { }
function eraseCookie(name) { function eraseCookie(name) {
document.cookie = name + '=; Max-Age=-99999999;'; document.cookie = name + '=; Max-Age=-99999999;';
} }
function search(searchText) { function bindMenuButton(menuId, buttonId) {
BukiVedi.Search(searchText, books => { var openMenuBtn = document.getElementById(buttonId);
$("#books").empty(); openMenuBtn.addEventListener('click', function (e) {
for (var idx in books) { e.preventDefault();
let data = books[idx]; var menu = document.getElementById(menuId);
menu.classList.toggle('opened');
const book = $('<div>').addClass('card'); }, false);
//window.addEventListener('click', function (e) { if (menu.classList.contains('opened') && !document.getElementById('menu').contains(e.target) && !document.getElementById('menuBtn').contains(e.target)) { menu.classList.toggle('opened'); } })
// Кнопка скачать }
const download = $('<a>').on('click', () => BukiVedi.Download(data.id));
$('<div>').addClass('card-circle').appendTo(download);
download.appendTo(book);
//автор
let author = '--';
if (data.authors.length > 0) {
author = data.authors[0].name;
}
const author_genre = $('<p>').addClass('author').text(author); function proceedMenuAction(bookId, menuId, actionType) {
$('<string>').text(' · ').appendTo(author_genre); var menu = document.getElementById(menuId);
menu.classList.toggle('opened');
switch (actionType) {
case MenuActions.AddToFavorite:
BukiVedi.AddToFavorite(bookId);
break;
case MenuActions.ToReadingQueue:
BukiVedi.ToReadingQueue(bookId);
break;
case MenuActions.BookBlock:
BukiVedi.BookBlock(bookId);
break;
case MenuActions.AuthorBlock:
BukiVedi.AuthorBlock(bookId);
break;
case MenuActions.Tag:
Tag(bookId);
break;
case MenuActions.Note:
Note(bookId);
break;
case MenuActions.Share:
Share(bookId);
break;
}
}
// Жанр function appendBooksToContent(books) {
let genre = '--'; $("#books").empty();
if (data.genres.length > 0) {
genre = data.genres[0].name;
}
$('<span>').addClass('due').text(genre).appendTo(author_genre);
author_genre.appendTo(book);
// Название for (var idx in books) {
$('<p>').text(data.title).appendTo(book); const data = books[idx];
$('<p>').addClass('desc').text(data.description).appendTo(book);
$('#books').append(book); let id = data.id;
//автор
var authorLinks = [];
if (data.authors.length > 0) {
for (idx in data.authors) {
author = data.authors[idx].name;
author_id = data.authors[idx].id;
authorLinks.push("<a onclick=\"BukiVedi.SearchByAuthor('" + author_id + "', appendBooksToContent)\">" + author + "</a>");
authorLinks.push("<strong>;</strong>");
}
} }
});
// Жанр
let genre = '--';
if (data.genres.length > 0) {
genre = data.genres[0].name;
}
if (genre == null) genre = '--';
let menuId = "menu_" + id;
let btnId = "menuBtn_" + id;
$('#books').append(
$("<div class='card'>"
+ "<a onclick=\"BukiVedi.Download('" + id + "')\">"
+ "<div class='card-circle'></div>"
+ "</a>"
+ "<div style='display: float;'>"
+ "<a id='"
+ btnId
+ "' href='#' class='open-menu'>···</a>"
+ "<nav id='"
+ menuId
+ "' class='menu'><ul>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.AddToFavorite + "')\" class='menu-item'>В избранное</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.ToReadingQueue + "')\"class='menu-item'>В очередь на чтение</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.BookBlock + "')\"class='menu-item'>Игнорировать</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.AuthorBlock + "')\"class='menu-item'>Игнорировать автора</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.Tag + "')\"class='menu-item'>Теги</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.Note + "')\"class='menu-item'>Заметка</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.Share + "')\"class='menu-item'>Поделиться</li>"
+ "</ul></nav>"
+ "</div>"
+ "<p class='author'>"
+ authorLinks.join("")
+ "<string> · </string><span class='due'>"
+ genre
+ "</span>"
+ "</p>"
+ "<p>"
+ data.title
+ "</p>"
+ "<p class='desc'>"
+ data.description
+ "</p>"
+ "</div>")
);
bindMenuButton(menuId, btnId);
}
}
function search(searchText) {
BukiVedi.Search(searchText, books => appendBooksToContent(books));
} }
$(() => { $(() => {

@ -1,4 +1,19 @@
class BukiVedi { class BukiVedi {
static SearchByAuthor(id, success) {
$.ajax({
contentType: "application/json; charset=utf-8",
type: "POST",
url: "../api/books/search/author/" + id,
success: function (data, textStatus, jqXHR) {
success(data);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static Search(query, success) { static Search(query, success) {
const info = { "query": query }; const info = { "query": query };
$.ajax({ $.ajax({
@ -20,6 +35,59 @@
window.location.href = "../api/books/download/" + id; window.location.href = "../api/books/download/" + id;
} }
static AddToFavorite(id, success) {
$.ajax({
type: "POST",
url: "../api/books/" + id + "/favorite",
success: function (data, textStatus, jqXHR) {
if (success)
success();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static ToReadingQueue(id) {
$.ajax({
type: "POST",
url: "../api/books/" + id + "/read",
success: function (data, textStatus, jqXHR) {
if (success)
success();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static BookBlock(id) {
$.ajax({
type: "POST",
url: "../api/books/" + id + "/block",
success: function (data, textStatus, jqXHR) {
if (success)
success();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static AuthorBlock(id) {
$.ajax({
type: "POST",
url: "../api/books/" + id + "/author/block",
success: function (data, textStatus, jqXHR) {
if (success)
success();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static Logout() { static Logout() {
localStorage.removeItem(this._token_name); localStorage.removeItem(this._token_name);
window.location.replace("web/login.html"); window.location.replace("web/login.html");

@ -107,8 +107,6 @@
const pwd = document.getElementById('password').value; const pwd = document.getElementById('password').value;
BukiVediAuth.SignIn(name, pwd, (data) => { BukiVediAuth.SignIn(name, pwd, (data) => {
if (data.success == true) { if (data.success == true) {
localStorage.setItem("token", data.token);
document.cookie = "X-Token=" + data.token;
window.location.replace("index.html"); window.location.replace("index.html");
} }
else { else {

@ -34,17 +34,121 @@
} }
] ]
}, },
{
"ContainingType": "BukiVedi.App.Controllers.BooksController",
"Method": "BlockBookAuthor",
"RelativePath": "api/books/{id}/author/block",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "id",
"Type": "System.String",
"IsRequired": true
}
],
"ReturnTypes": [
{
"Type": "System.Collections.Generic.IEnumerable\u00601[[BukiVedi.App.Responces.BookResponse, BukiVedi.App, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]",
"MediaTypes": [
"text/plain",
"application/json",
"text/json"
],
"StatusCode": 200
}
]
},
{
"ContainingType": "BukiVedi.App.Controllers.BooksController",
"Method": "BlockBook",
"RelativePath": "api/books/{id}/block",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "id",
"Type": "System.String",
"IsRequired": true
}
],
"ReturnTypes": [
{
"Type": "System.Collections.Generic.IEnumerable\u00601[[BukiVedi.App.Responces.BookResponse, BukiVedi.App, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]",
"MediaTypes": [
"text/plain",
"application/json",
"text/json"
],
"StatusCode": 200
}
]
},
{
"ContainingType": "BukiVedi.App.Controllers.BooksController",
"Method": "AddToFavorite",
"RelativePath": "api/books/{id}/favorite",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "id",
"Type": "System.String",
"IsRequired": true
}
],
"ReturnTypes": [
{
"Type": "System.Collections.Generic.IEnumerable\u00601[[BukiVedi.App.Responces.BookResponse, BukiVedi.App, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]",
"MediaTypes": [
"text/plain",
"application/json",
"text/json"
],
"StatusCode": 200
}
]
},
{
"ContainingType": "BukiVedi.App.Controllers.BooksController",
"Method": "AddBookToReadingQueue",
"RelativePath": "api/books/{id}/read",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "id",
"Type": "System.String",
"IsRequired": true
}
],
"ReturnTypes": [
{
"Type": "System.Collections.Generic.IEnumerable\u00601[[BukiVedi.App.Responces.BookResponse, BukiVedi.App, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]",
"MediaTypes": [
"text/plain",
"application/json",
"text/json"
],
"StatusCode": 200
}
]
},
{ {
"ContainingType": "BukiVedi.App.Controllers.BooksController", "ContainingType": "BukiVedi.App.Controllers.BooksController",
"Method": "AiSearch", "Method": "AiSearch",
"RelativePath": "api/books/aisearch", "RelativePath": "api/books/author/{id}",
"HttpMethod": "POST", "HttpMethod": "POST",
"IsController": true, "IsController": true,
"Order": 0, "Order": 0,
"Parameters": [ "Parameters": [
{ {
"Name": "request", "Name": "id",
"Type": "BukiVedi.App.Requests.QueryRequest", "Type": "System.String",
"IsRequired": true "IsRequired": true
} }
], ],
@ -101,5 +205,31 @@
"StatusCode": 200 "StatusCode": 200
} }
] ]
},
{
"ContainingType": "BukiVedi.App.Controllers.BooksController",
"Method": "SearchByAuthor",
"RelativePath": "api/books/search/author/{id}",
"HttpMethod": "POST",
"IsController": true,
"Order": 0,
"Parameters": [
{
"Name": "id",
"Type": "System.String",
"IsRequired": true
}
],
"ReturnTypes": [
{
"Type": "System.Collections.Generic.IEnumerable\u00601[[BukiVedi.App.Responces.BookResponse, BukiVedi.App, Version=1.0.0.0, Culture=neutral, PublicKeyToken=null]]",
"MediaTypes": [
"text/plain",
"application/json",
"text/json"
],
"StatusCode": 200
}
]
} }
] ]

@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("BukiVedi.App")] [assembly: System.Reflection.AssemblyCompanyAttribute("BukiVedi.App")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+7eed3bd890d6fea441a83f8139430004eb71fe5e")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4e697c91a83d95731e2cea4131399178264fd6a2")]
[assembly: System.Reflection.AssemblyProductAttribute("BukiVedi.App")] [assembly: System.Reflection.AssemblyProductAttribute("BukiVedi.App")]
[assembly: System.Reflection.AssemblyTitleAttribute("BukiVedi.App")] [assembly: System.Reflection.AssemblyTitleAttribute("BukiVedi.App")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

@ -1 +1 @@
827c2c1ccbda85cec2cca48419c134275d2524c227339cc52e803bf1942355b5 9d9d30a119e571814faa5f6aa186f82345b545bc19591da9baa14505e2ac9e3d

@ -110,6 +110,7 @@
margin-left: 30px; margin-left: 30px;
padding-bottom: 5px; padding-bottom: 5px;
color: #06001aff; color: #06001aff;
width: 100%;
} }
.card .unassigned { .card .unassigned {
@ -119,6 +120,46 @@
.card .due { .card .due {
color: #ec7373; color: #ec7373;
} }
.menu {
visibility: hidden;
z-index: 1000;
position: relative;
height: 0;
background-color: #171717;
}
.open-menu {
text-decoration: none;
font-size: 18px;
font-weight: bolder;
}
.menu.opened {
visibility: visible;
color: azure;
width: 180px;
}
.menu-item {
text-decoration: none;
background-color: #272727;
height: 28px;
width: 100%;
text-align: left;
padding-top: 12px;
padding-left: 16px;
cursor: pointer;
}
.menu-item:hover {
background-color: #373737;
}
ul {
list-style-type: none;
}
</style> </style>
</head> </head>
<body> <body>
@ -131,47 +172,134 @@
</div> </div>
<script> <script>
const MenuActions =
{
AddToFavorite: '0',
ToReadingQueue: '1',
BookBlock: '2',
AuthorBlock: '3',
Tag: '4',
Note: '5',
Share: '6'
};
function Tag(id) { }
function Note(id) { }
function Share(id) { }
function eraseCookie(name) { function eraseCookie(name) {
document.cookie = name + '=; Max-Age=-99999999;'; document.cookie = name + '=; Max-Age=-99999999;';
} }
function search(searchText) { function bindMenuButton(menuId, buttonId) {
BukiVedi.Search(searchText, books => { var openMenuBtn = document.getElementById(buttonId);
$("#books").empty(); openMenuBtn.addEventListener('click', function (e) {
for (var idx in books) { e.preventDefault();
let data = books[idx]; var menu = document.getElementById(menuId);
menu.classList.toggle('opened');
const book = $('<div>').addClass('card'); }, false);
//window.addEventListener('click', function (e) { if (menu.classList.contains('opened') && !document.getElementById('menu').contains(e.target) && !document.getElementById('menuBtn').contains(e.target)) { menu.classList.toggle('opened'); } })
// Кнопка скачать }
const download = $('<a>').on('click', () => BukiVedi.Download(data.id));
$('<div>').addClass('card-circle').appendTo(download);
download.appendTo(book);
//автор
let author = '--';
if (data.authors.length > 0) {
author = data.authors[0].name;
}
const author_genre = $('<p>').addClass('author').text(author); function proceedMenuAction(bookId, menuId, actionType) {
$('<string>').text(' · ').appendTo(author_genre); var menu = document.getElementById(menuId);
menu.classList.toggle('opened');
switch (actionType) {
case MenuActions.AddToFavorite:
BukiVedi.AddToFavorite(bookId);
break;
case MenuActions.ToReadingQueue:
BukiVedi.ToReadingQueue(bookId);
break;
case MenuActions.BookBlock:
BukiVedi.BookBlock(bookId);
break;
case MenuActions.AuthorBlock:
BukiVedi.AuthorBlock(bookId);
break;
case MenuActions.Tag:
Tag(bookId);
break;
case MenuActions.Note:
Note(bookId);
break;
case MenuActions.Share:
Share(bookId);
break;
}
}
// Жанр function appendBooksToContent(books) {
let genre = '--'; $("#books").empty();
if (data.genres.length > 0) {
genre = data.genres[0].name;
}
$('<span>').addClass('due').text(genre).appendTo(author_genre);
author_genre.appendTo(book);
// Название for (var idx in books) {
$('<p>').text(data.title).appendTo(book); const data = books[idx];
$('<p>').addClass('desc').text(data.description).appendTo(book);
$('#books').append(book); let id = data.id;
//автор
var authorLinks = [];
if (data.authors.length > 0) {
for (idx in data.authors) {
author = data.authors[idx].name;
author_id = data.authors[idx].id;
authorLinks.push("<a onclick=\"BukiVedi.SearchByAuthor('" + author_id + "', appendBooksToContent)\">" + author + "</a>");
authorLinks.push("<strong>;</strong>");
}
} }
});
// Жанр
let genre = '--';
if (data.genres.length > 0) {
genre = data.genres[0].name;
}
if (genre == null) genre = '--';
let menuId = "menu_" + id;
let btnId = "menuBtn_" + id;
$('#books').append(
$("<div class='card'>"
+ "<a onclick=\"BukiVedi.Download('" + id + "')\">"
+ "<div class='card-circle'></div>"
+ "</a>"
+ "<div style='display: float;'>"
+ "<a id='"
+ btnId
+ "' href='#' class='open-menu'>···</a>"
+ "<nav id='"
+ menuId
+ "' class='menu'><ul>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.AddToFavorite + "')\" class='menu-item'>В избранное</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.ToReadingQueue + "')\"class='menu-item'>В очередь на чтение</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.BookBlock + "')\"class='menu-item'>Игнорировать</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.AuthorBlock + "')\"class='menu-item'>Игнорировать автора</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.Tag + "')\"class='menu-item'>Теги</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.Note + "')\"class='menu-item'>Заметка</li>"
+ "<li onclick=\"proceedMenuAction('" + id + "', '" + menuId + "', '" + MenuActions.Share + "')\"class='menu-item'>Поделиться</li>"
+ "</ul></nav>"
+ "</div>"
+ "<p class='author'>"
+ authorLinks.join("")
+ "<string> · </string><span class='due'>"
+ genre
+ "</span>"
+ "</p>"
+ "<p>"
+ data.title
+ "</p>"
+ "<p class='desc'>"
+ data.description
+ "</p>"
+ "</div>")
);
bindMenuButton(menuId, btnId);
}
}
function search(searchText) {
BukiVedi.Search(searchText, books => appendBooksToContent(books));
} }
$(() => { $(() => {

@ -1,4 +1,19 @@
class BukiVedi { class BukiVedi {
static SearchByAuthor(id, success) {
$.ajax({
contentType: "application/json; charset=utf-8",
type: "POST",
url: "../api/books/search/author/" + id,
success: function (data, textStatus, jqXHR) {
success(data);
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static Search(query, success) { static Search(query, success) {
const info = { "query": query }; const info = { "query": query };
$.ajax({ $.ajax({
@ -20,6 +35,59 @@
window.location.href = "../api/books/download/" + id; window.location.href = "../api/books/download/" + id;
} }
static AddToFavorite(id, success) {
$.ajax({
type: "POST",
url: "../api/books/" + id + "/favorite",
success: function (data, textStatus, jqXHR) {
if (success)
success();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static ToReadingQueue(id) {
$.ajax({
type: "POST",
url: "../api/books/" + id + "/read",
success: function (data, textStatus, jqXHR) {
if (success)
success();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static BookBlock(id) {
$.ajax({
type: "POST",
url: "../api/books/" + id + "/block",
success: function (data, textStatus, jqXHR) {
if (success)
success();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static AuthorBlock(id) {
$.ajax({
type: "POST",
url: "../api/books/" + id + "/author/block",
success: function (data, textStatus, jqXHR) {
if (success)
success();
},
error: function (jqXHR, textStatus, errorThrown) {
console.log(jqXHR.statusText);
}
});
}
static Logout() { static Logout() {
localStorage.removeItem(this._token_name); localStorage.removeItem(this._token_name);
window.location.replace("web/login.html"); window.location.replace("web/login.html");

@ -107,8 +107,6 @@
const pwd = document.getElementById('password').value; const pwd = document.getElementById('password').value;
BukiVediAuth.SignIn(name, pwd, (data) => { BukiVediAuth.SignIn(name, pwd, (data) => {
if (data.success == true) { if (data.success == true) {
localStorage.setItem("token", data.token);
document.cookie = "X-Token=" + data.token;
window.location.replace("index.html"); window.location.replace("index.html");
} }
else { else {

@ -21,7 +21,7 @@
/// <summary> /// <summary>
/// Авторы /// Авторы
/// </summary> /// </summary>
public string[] AuthorIds { get; set; } public List<string> AuthorIds { get; set; }
/// <summary> /// <summary>
/// Формат /// Формат
/// </summary> /// </summary>

@ -11,7 +11,7 @@ namespace BukiVedi.Shared.Services
{ {
public interface ILibrary public interface ILibrary
{ {
Task SearchBooksByAuthor(string name); Task<IEnumerable<BookEntity>> SearchBooksByAuthor(string author_id);
Task<IEnumerable<BookEntity>> SearchBooks(string title); Task<IEnumerable<BookEntity>> SearchBooks(string title);
Task DownloadToStream(Stream stream, string id); Task DownloadToStream(Stream stream, string id);
} }
@ -58,31 +58,37 @@ namespace BukiVedi.Shared.Services
} }
} }
public async Task SearchBooksByAuthor(string name) public async Task<IEnumerable<BookEntity>> SearchBooksByAuthor(string author_id)
{ {
if (string.IsNullOrWhiteSpace(name) == false && name.Select(ch => char.IsLetter(ch)).Count() > 5) if (string.IsNullOrWhiteSpace(author_id) == false)
{ {
var authorFilter = Builders<Author>.Filter.Eq<string>(a => a.Name, name); var filter = Builders<Book>.Filter.AnyEq(b => b.AuthorIds, author_id);
var authors = await Tables.Authors.Get(authorFilter); var result = await Tables.Books.Get(filter);
if (result != null && result.Any())
if (authors.Any())
{ {
var authorsIds = authors.Select(a => a.Id).ToArray(); return result.Select(b => new BookEntity
var result = await Tables.Books.Get(Builders<Book>.Filter.ElemMatch(b => b.AuthorIds, a => authorsIds.Any(at => at.Equals(a, StringComparison.OrdinalIgnoreCase)))); {
Authors = GetAuthors(b),
Title = b.Title,
Year = b.Year,
Description = b.Description,
Format = b.Format,
Genre = new Genre(),
Id = b.Id,
});
} }
} }
return Enumerable.Empty<BookEntity>();
} }
public async Task ReadAllBooks(Func<BookEntity, bool> bookHandler) public async Task ReadAllBooks(Func<BookEntity, bool> bookHandler)
{ {
foreach (var b in (await Tables.Books.GetAll())) foreach (var b in (await Tables.Books.GetAll()))
{ {
var authors = new List<Author>(b.AuthorIds?.Length ?? 0); var authors = new List<Author>(b.AuthorIds?.Count ?? 0);
Genre genre; Genre genre;
if (b.AuthorIds?.Length > 0) if (b.AuthorIds?.Count > 0)
{ {
foreach (var aid in b.AuthorIds) foreach (var aid in b.AuthorIds)
{ {
@ -261,7 +267,7 @@ namespace BukiVedi.Shared.Services
{ {
foreach (var book in (await Tables.Books.GetAll())) foreach (var book in (await Tables.Books.GetAll()))
{ {
var authorLine = (book.AuthorIds?.Length > 0) ? string.Join(' ', book.AuthorIds.Select(a => authors[a])) : string.Empty; var authorLine = (book.AuthorIds?.Count > 0) ? string.Join(' ', book.AuthorIds.Select(a => authors[a])) : string.Empty;
var genre = string.IsNullOrWhiteSpace(book.GenreId) ? string.Empty : genres[book.GenreId]; var genre = string.IsNullOrWhiteSpace(book.GenreId) ? string.Empty : genres[book.GenreId];
var indexItem = new BookDocument var indexItem = new BookDocument
{ {
@ -340,7 +346,7 @@ namespace BukiVedi.Shared.Services
{ {
ArchiveIndex = zip_book.ArchiveIndex, ArchiveIndex = zip_book.ArchiveIndex,
ArchivePath = archive_path, ArchivePath = archive_path,
AuthorIds = author_ids.ToArray(), AuthorIds = author_ids.ToList(),
Year = zip_book.Year, Year = zip_book.Year,
Description = zip_book.Description, Description = zip_book.Description,
Title = zip_book.Title, Title = zip_book.Title,

@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("BukiVedi.Shared")] [assembly: System.Reflection.AssemblyCompanyAttribute("BukiVedi.Shared")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0854d0d5e375585dfb4831af0aa4e5089fb0e8ae")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4e697c91a83d95731e2cea4131399178264fd6a2")]
[assembly: System.Reflection.AssemblyProductAttribute("BukiVedi.Shared")] [assembly: System.Reflection.AssemblyProductAttribute("BukiVedi.Shared")]
[assembly: System.Reflection.AssemblyTitleAttribute("BukiVedi.Shared")] [assembly: System.Reflection.AssemblyTitleAttribute("BukiVedi.Shared")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

@ -1 +1 @@
c26bb1f765467d5f9ebfe36a8f5628a1bf2b1c03f059be7613e04f8f77a7a9fe 86c42d4be25574172e2d579dfa02e41a379e51b0c675314acf9d44ccc18e6cee

@ -14,7 +14,7 @@ using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("TitleReader")] [assembly: System.Reflection.AssemblyCompanyAttribute("TitleReader")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+0854d0d5e375585dfb4831af0aa4e5089fb0e8ae")] [assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4e697c91a83d95731e2cea4131399178264fd6a2")]
[assembly: System.Reflection.AssemblyProductAttribute("TitleReader")] [assembly: System.Reflection.AssemblyProductAttribute("TitleReader")]
[assembly: System.Reflection.AssemblyTitleAttribute("TitleReader")] [assembly: System.Reflection.AssemblyTitleAttribute("TitleReader")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]

@ -1 +1 @@
22a66b48f25fdf876eb7692c83b659900d8e9c1b260c873fe5d6180b00a753b8 c5ac3a47b85a3d5934c2eb5b570db310225ebb22491771b55abcb0f32d99cb90

Loading…
Cancel
Save

Powered by TurnKey Linux.