From cab277f3a9adc2c6f1752420765be486593a44b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Thu, 6 Apr 2023 19:02:32 +0200 Subject: [PATCH 1/7] Remove header comments --- YACReaderLibrary/server/requestmapper.cpp | 5 ----- YACReaderLibrary/server/requestmapper.h | 5 ----- 2 files changed, 10 deletions(-) diff --git a/YACReaderLibrary/server/requestmapper.cpp b/YACReaderLibrary/server/requestmapper.cpp index 4051b6f2..306cc963 100644 --- a/YACReaderLibrary/server/requestmapper.cpp +++ b/YACReaderLibrary/server/requestmapper.cpp @@ -1,8 +1,3 @@ -/** - @file - @author Stefan Frings -*/ - #include "requestmapper.h" #include "static.h" #include "staticfilecontroller.h" diff --git a/YACReaderLibrary/server/requestmapper.h b/YACReaderLibrary/server/requestmapper.h index 4ec435fa..3f089ba1 100644 --- a/YACReaderLibrary/server/requestmapper.h +++ b/YACReaderLibrary/server/requestmapper.h @@ -1,8 +1,3 @@ -/** - @file - @author Stefan Frings -*/ - #ifndef REQUESTMAPPER_H #define REQUESTMAPPER_H From 044bab0c4cc136bb05f57f71c9f4dcc44d797c53 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Thu, 6 Apr 2023 19:02:43 +0200 Subject: [PATCH 2/7] Remove unused import --- YACReaderLibrary/server/requestmapper.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/YACReaderLibrary/server/requestmapper.cpp b/YACReaderLibrary/server/requestmapper.cpp index 306cc963..12707bd3 100644 --- a/YACReaderLibrary/server/requestmapper.cpp +++ b/YACReaderLibrary/server/requestmapper.cpp @@ -21,7 +21,6 @@ #include "controllers/v2/folderinfocontroller_v2.h" #include "controllers/v2/pagecontroller_v2.h" #include "controllers/v2/updatecomiccontroller_v2.h" -#include "controllers/v2/errorcontroller_v2.h" #include "controllers/v2/comicdownloadinfocontroller_v2.h" #include "controllers/v2/synccontroller_v2.h" #include "controllers/v2/foldercontentcontroller_v2.h" From ceb34a1409455d90e0a67479193819322fec05bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Fri, 7 Apr 2023 10:49:09 +0200 Subject: [PATCH 3/7] Extract sql queries creation for the search engine so we can reuse them --- YACReaderLibrary/YACReaderLibrary.pro | 2 + .../db/comic_query_result_processor.cpp | 24 ++--------- .../db/folder_query_result_processor.cpp | 40 ++++------------- .../db/folder_query_result_processor.h | 2 +- YACReaderLibrary/db/query_parser.h | 3 ++ YACReaderLibrary/db/search_query.cpp | 43 +++++++++++++++++++ YACReaderLibrary/db/search_query.h | 10 +++++ YACReaderLibrary/library_window.cpp | 2 +- 8 files changed, 71 insertions(+), 55 deletions(-) create mode 100644 YACReaderLibrary/db/search_query.cpp create mode 100644 YACReaderLibrary/db/search_query.h diff --git a/YACReaderLibrary/YACReaderLibrary.pro b/YACReaderLibrary/YACReaderLibrary.pro index 63c2bdc8..70e558f8 100644 --- a/YACReaderLibrary/YACReaderLibrary.pro +++ b/YACReaderLibrary/YACReaderLibrary.pro @@ -83,6 +83,7 @@ HEADERS += comic_flow.h \ db/comic_query_result_processor.h \ db/folder_query_result_processor.h \ db/query_lexer.h \ + db/search_query.h \ folder_content_view.h \ initial_comic_info_extractor.h \ library_comic_opener.h \ @@ -166,6 +167,7 @@ SOURCES += comic_flow.cpp \ db/comic_query_result_processor.cpp \ db/folder_query_result_processor.cpp \ db/query_lexer.cpp \ + db/search_query.cpp \ folder_content_view.cpp \ initial_comic_info_extractor.cpp \ library_comic_opener.cpp \ diff --git a/YACReaderLibrary/db/comic_query_result_processor.cpp b/YACReaderLibrary/db/comic_query_result_processor.cpp index 2e075004..e1cf5e52 100644 --- a/YACReaderLibrary/db/comic_query_result_processor.cpp +++ b/YACReaderLibrary/db/comic_query_result_processor.cpp @@ -4,10 +4,7 @@ #include "comic_model.h" #include "data_base_management.h" #include "qnaturalsorting.h" -#include "db_helper.h" -#include "query_parser.h" - -#include "QsLog.h" +#include "search_query.h" YACReader::ComicQueryResultProcessor::ComicQueryResultProcessor() : querySearchQueue(1) @@ -22,25 +19,10 @@ void YACReader::ComicQueryResultProcessor::createModelData(const QString &filter QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(databasePath); - QSqlQuery selectQuery(db); - - std::string queryString("SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened " - "FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) LEFT JOIN folder f ON (f.id == c.parentId) WHERE "); - try { - QueryParser parser; - auto result = parser.parse(filter.toStdString()); - result.buildSqlString(queryString); + auto query = comicsSearchQuery(db, filter); - queryString += " LIMIT :limit"; - - selectQuery.prepare(queryString.c_str()); - selectQuery.bindValue(":limit", 500); // TODO, load this value from settings - result.bindValues(selectQuery); - - selectQuery.exec(); - - auto data = modelData(selectQuery); + auto data = modelData(query); emit newData(data, databasePath); } catch (const std::exception &e) { diff --git a/YACReaderLibrary/db/folder_query_result_processor.cpp b/YACReaderLibrary/db/folder_query_result_processor.cpp index 7670e9d3..41774eb2 100644 --- a/YACReaderLibrary/db/folder_query_result_processor.cpp +++ b/YACReaderLibrary/db/folder_query_result_processor.cpp @@ -1,13 +1,9 @@ #include "folder_query_result_processor.h" #include "folder_item.h" -#include "qnaturalsorting.h" -#include "yacreader_global_gui.h" -#include "query_parser.h" #include "folder_model.h" #include "data_base_management.h" - -#include "QsLog.h" +#include "search_query.h" #include #include @@ -20,7 +16,7 @@ YACReader::FolderQueryResultProcessor::FolderQueryResultProcessor(FolderModel *m { } -void YACReader::FolderQueryResultProcessor::createModelData(const QString &filter, bool includeComics) +void YACReader::FolderQueryResultProcessor::createModelData(const QString &filter) { querySearchQueue.cancelPending(); @@ -28,33 +24,13 @@ void YACReader::FolderQueryResultProcessor::createModelData(const QString &filte QString connectionName = ""; { QSqlDatabase db = DataBaseManagement::loadDatabase(model->getDatabase()); + try { + auto query = foldersSearchQuery(db, filter); - QSqlQuery selectQuery(db); // TODO check - if (!includeComics) { - selectQuery.prepare("select * from folder where id <> 1 and upper(name) like upper(:filter) order by parentId,name "); - selectQuery.bindValue(":filter", "%%" + filter + "%%"); - } else { - std::string queryString("SELECT DISTINCT f.id, f.parentId, f.name, f.path, f.finished, f.completed " - "FROM folder f LEFT JOIN comic c ON (f.id = c.parentId) " - "INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) WHERE "); - - try { - QueryParser parser; - auto result = parser.parse(filter.toStdString()); - result.buildSqlString(queryString); - - queryString += " AND f.id <> 1 ORDER BY f.parentId,f.name"; - - selectQuery.prepare(queryString.c_str()); - result.bindValues(selectQuery); - - selectQuery.exec(); - - setupFilteredModelData(selectQuery); - } catch (const std::exception &e) { - // Do nothing, uncomplete search string will end here and it is part of how the QueryParser works - // I don't like the idea of using exceptions for this though - } + setupFilteredModelData(query); + } catch (const std::exception &e) { + // Do nothing, uncomplete search string will end here and it is part of how the QueryParser works + // I don't like the idea of using exceptions for this though } connectionName = db.connectionName(); diff --git a/YACReaderLibrary/db/folder_query_result_processor.h b/YACReaderLibrary/db/folder_query_result_processor.h index c375100d..92d5aa34 100644 --- a/YACReaderLibrary/db/folder_query_result_processor.h +++ b/YACReaderLibrary/db/folder_query_result_processor.h @@ -19,7 +19,7 @@ public: FolderQueryResultProcessor(FolderModel *model); public slots: - void createModelData(const QString &filter, bool includeComics); + void createModelData(const QString &filter); signals: void newData(QMap *filteredItems, FolderItem *root); diff --git a/YACReaderLibrary/db/query_parser.h b/YACReaderLibrary/db/query_parser.h index 4b8f0c4e..69a5a315 100644 --- a/YACReaderLibrary/db/query_parser.h +++ b/YACReaderLibrary/db/query_parser.h @@ -9,6 +9,9 @@ #include #include +#define SEARCH_FOLDERS_QUERY "SELECT DISTINCT f.id, f.parentId, f.name, f.path, f.finished, f.completed, f.numChildren, f.firstChildHash FROM folder f LEFT JOIN comic c ON (f.id = c.parentId) INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) WHERE " +#define SEARCH_COMICS_QUERY "SELECT ci.number,ci.title,c.fileName,ci.numPages,c.id,c.parentId,c.path,ci.hash,ci.read,ci.isBis,ci.currentPage,ci.rating,ci.hasBeenOpened,ci.coverSizeRatio,ci.lastTimeOpened,ci.manga FROM comic c INNER JOIN comic_info ci ON (c.comicInfoId = ci.id) LEFT JOIN folder f ON (f.id == c.parentId) WHERE " + /** * This class is used to generate an SQL query string from a search expression, * with a syntax very similar to that used by the Google search engine. diff --git a/YACReaderLibrary/db/search_query.cpp b/YACReaderLibrary/db/search_query.cpp new file mode 100644 index 00000000..bd1d6cd3 --- /dev/null +++ b/YACReaderLibrary/db/search_query.cpp @@ -0,0 +1,43 @@ + +#include "search_query.h" +#include "query_parser.h" + +#include +#include + +QSqlQuery foldersSearchQuery(QSqlDatabase &db, const QString &filter) +{ + QueryParser parser; + auto result = parser.parse(filter.toStdString()); + + std::string queryString(SEARCH_FOLDERS_QUERY); + result.buildSqlString(queryString); + queryString += " AND f.id <> 1 ORDER BY f.parentId,f.name"; + + QSqlQuery selectQuery(db); + selectQuery.prepare(queryString.c_str()); + result.bindValues(selectQuery); + + selectQuery.exec(); + + return selectQuery; +} + +QSqlQuery comicsSearchQuery(QSqlDatabase &db, const QString &filter) +{ + QueryParser parser; + auto result = parser.parse(filter.toStdString()); + + std::string queryString(SEARCH_COMICS_QUERY); + result.buildSqlString(queryString); + queryString += " LIMIT :limit"; + + QSqlQuery selectQuery(db); + selectQuery.prepare(queryString.c_str()); + selectQuery.bindValue(":limit", 500); // TODO, load this value from settings + result.bindValues(selectQuery); + + selectQuery.exec(); + + return selectQuery; +} diff --git a/YACReaderLibrary/db/search_query.h b/YACReaderLibrary/db/search_query.h new file mode 100644 index 00000000..208100a5 --- /dev/null +++ b/YACReaderLibrary/db/search_query.h @@ -0,0 +1,10 @@ + +#ifndef SEARCHQUERY_H +#define SEARCHQUERY_H + +#include + +QSqlQuery foldersSearchQuery(QSqlDatabase &db, const QString &filter); +QSqlQuery comicsSearchQuery(QSqlDatabase &db, const QString &filter); + +#endif // SEARCHQUERY_H diff --git a/YACReaderLibrary/library_window.cpp b/YACReaderLibrary/library_window.cpp index 0ac891e6..608cd88e 100644 --- a/YACReaderLibrary/library_window.cpp +++ b/YACReaderLibrary/library_window.cpp @@ -2415,7 +2415,7 @@ void LibraryWindow::toNormal() void LibraryWindow::setSearchFilter(QString filter) { if (!filter.isEmpty()) { - folderQueryResultProcessor->createModelData(filter, true); + folderQueryResultProcessor->createModelData(filter); comicQueryResultProcessor.createModelData(filter, foldersModel->getDatabase()); } else if (status == LibraryWindow::Searching) { // if no searching, then ignore this clearSearchFilter(); From 5c0b5c7430c405c71db9eff990fbfdd6fbfcd4cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Fri, 7 Apr 2023 10:51:05 +0200 Subject: [PATCH 4/7] Add method for getting the db container path from a library id --- YACReaderLibrary/yacreader_libraries.cpp | 5 +++++ YACReaderLibrary/yacreader_libraries.h | 1 + 2 files changed, 6 insertions(+) diff --git a/YACReaderLibrary/yacreader_libraries.cpp b/YACReaderLibrary/yacreader_libraries.cpp index ad83731c..0861df21 100644 --- a/YACReaderLibrary/yacreader_libraries.cpp +++ b/YACReaderLibrary/yacreader_libraries.cpp @@ -29,6 +29,11 @@ QString YACReaderLibraries::getPath(int id) return ""; } +QString YACReaderLibraries::getDBPath(int id) +{ + return getPath(id) + "/.yacreaderlibrary"; +} + QString YACReaderLibraries::getName(int id) { foreach (QString name, libraries.keys()) diff --git a/YACReaderLibrary/yacreader_libraries.h b/YACReaderLibrary/yacreader_libraries.h index 12e096e6..a536c80b 100644 --- a/YACReaderLibrary/yacreader_libraries.h +++ b/YACReaderLibrary/yacreader_libraries.h @@ -13,6 +13,7 @@ public: QList getNames(); QString getPath(const QString &name); QString getPath(int id); + QString getDBPath(int id); QString getName(int id); bool isEmpty(); bool contains(const QString &name); From 4198b5ca3a3a5eff4eebcc4a32049da81e2fa552 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Fri, 7 Apr 2023 10:51:34 +0200 Subject: [PATCH 5/7] Implement a server controller to provide a search API --- .../controllers/v2/searchcontroller_v2.cpp | 103 ++++++++++++++++++ .../controllers/v2/searchcontroller_v2.h | 26 +++++ YACReaderLibrary/server/requestmapper.cpp | 4 + YACReaderLibrary/server/server.pri | 2 + 4 files changed, 135 insertions(+) create mode 100644 YACReaderLibrary/server/controllers/v2/searchcontroller_v2.cpp create mode 100644 YACReaderLibrary/server/controllers/v2/searchcontroller_v2.h diff --git a/YACReaderLibrary/server/controllers/v2/searchcontroller_v2.cpp b/YACReaderLibrary/server/controllers/v2/searchcontroller_v2.cpp new file mode 100644 index 00000000..3a7351e6 --- /dev/null +++ b/YACReaderLibrary/server/controllers/v2/searchcontroller_v2.cpp @@ -0,0 +1,103 @@ + +#include "searchcontroller_v2.h" + +#include "data_base_management.h" +#include "db_helper.h" +#include "yacreader_libraries.h" +#include "search_query.h" + +#include +#include +#include + +SearchController::SearchController() { } + +void SearchController::service(stefanfrings::HttpRequest &request, stefanfrings::HttpResponse &response) +{ + response.setHeader("Content-Type", "application/json"); + + QString path = QUrl::fromPercentEncoding(request.getPath()).toUtf8(); + QStringList pathElements = path.split('/'); + int libraryId = pathElements.at(3).toInt(); + + auto body = request.getBody(); + QJsonDocument json = QJsonDocument::fromJson(body); + auto query = json["query"].toString(); + + response.setStatus(200, "OK"); + serviceSearch(libraryId, query, response); +} + +void SearchController::serviceSearch(int libraryId, const QString &query, stefanfrings::HttpResponse &response) +{ + QJsonArray results; + + // TODO replace + "/yacreaderlibrary" concatenations with getDBPath + QString libraryDBPath = DBHelper::getLibraries().getDBPath(libraryId); + QString connectionName = ""; + { + QSqlDatabase db = DataBaseManagement::loadDatabase(libraryDBPath); + + // folders + try { + auto sqlQuery = foldersSearchQuery(db, query); + getFolders(libraryId, sqlQuery, results); + } catch (const std::exception &e) { + } + + // comics + try { + auto sqlQuery = foldersSearchQuery(db, query); + getComics(libraryId, sqlQuery, results); + } catch (const std::exception &e) { + } + + connectionName = db.connectionName(); + } + QSqlDatabase::removeDatabase(connectionName); + + QJsonDocument output(results); + + response.write(output.toJson(QJsonDocument::Compact)); +} + +void SearchController::getFolders(int libraryId, QSqlQuery &sqlQuery, QJsonArray &items) +{ + while (sqlQuery.next()) { + QJsonObject folder; + + folder["type"] = "folder"; + folder["id"] = sqlQuery.value("id").toString(); + folder["library_id"] = QString::number(libraryId); + folder["folder_name"] = sqlQuery.value("name").toString(); + folder["num_children"] = sqlQuery.value("numChildren").toInt(); + folder["first_comic_hash"] = sqlQuery.value("firstChildHash").toInt(); + + items.append(folder); + } +} + +void SearchController::getComics(int libraryId, QSqlQuery &sqlQuery, QJsonArray &items) +{ + while (sqlQuery.next()) { + QJsonObject json; + + json["type"] = "comic"; + json["id"] = sqlQuery.value("id").toString(); + json["library_id"] = QString::number(libraryId); + json["file_name"] = sqlQuery.value("fileName").toString(); + auto hash = sqlQuery.value("hash").toString(); + json["file_size"] = hash.right(hash.length() - 40).toLongLong(); + json["hash"] = hash; + json["current_page"] = sqlQuery.value("currentPage").toInt(); + json["num_pages"] = sqlQuery.value("numPages").toInt(); + json["read"] = sqlQuery.value("read").toBool(); + json["cover_size_ratio"] = sqlQuery.value("coverSizeRatio").toFloat(); + json["title"] = sqlQuery.value("title").toString(); + json["number"] = sqlQuery.value("number").toInt(); + json["last_time_opened"] = sqlQuery.value("lastTimeOpened").toLongLong(); + json["manga"] = sqlQuery.value("manga").toBool(); + + items.append(json); + } +} diff --git a/YACReaderLibrary/server/controllers/v2/searchcontroller_v2.h b/YACReaderLibrary/server/controllers/v2/searchcontroller_v2.h new file mode 100644 index 00000000..52c1e778 --- /dev/null +++ b/YACReaderLibrary/server/controllers/v2/searchcontroller_v2.h @@ -0,0 +1,26 @@ + +#ifndef SEARCHCONTROLLER_H +#define SEARCHCONTROLLER_H + +#include "httprequest.h" +#include "httpresponse.h" +#include "httprequesthandler.h" + +#include + +class SearchController : public stefanfrings::HttpRequestHandler +{ + Q_OBJECT + Q_DISABLE_COPY(SearchController) +public: + SearchController(); + + void service(stefanfrings::HttpRequest &request, stefanfrings::HttpResponse &response) override; + +private: + void serviceSearch(int libraryId, const QString &query, stefanfrings::HttpResponse &response); + void getFolders(int libraryId, QSqlQuery &sqlQuery, QJsonArray &items); + void getComics(int libraryId, QSqlQuery &sqlQuery, QJsonArray &items); +}; + +#endif // SEARCHCONTROLLER_H diff --git a/YACReaderLibrary/server/requestmapper.cpp b/YACReaderLibrary/server/requestmapper.cpp index 12707bd3..63ff366b 100644 --- a/YACReaderLibrary/server/requestmapper.cpp +++ b/YACReaderLibrary/server/requestmapper.cpp @@ -34,6 +34,7 @@ #include "controllers/v2/readinglistinfocontroller_v2.h" #include "controllers/v2/comicfullinfocontroller_v2.h" #include "controllers/v2/comiccontrollerinreadinglist_v2.h" +#include "controllers/v2/searchcontroller_v2.h" #include "controllers/webui/statuspagecontroller.h" @@ -257,6 +258,7 @@ void RequestMapper::serviceV2(HttpRequest &request, HttpResponse &response) QRegExp readingLists("/v2/library/.+/reading_lists/?"); QRegExp readingListContent("/v2/library/.+/reading_list/[0-9]+/content/?"); QRegExp readingListInfo("/v2/library/.+/reading_list/[0-9]+/info/?"); + QRegExp search("/v2/library/.+/search/?"); QRegExp sync("/v2/sync"); @@ -318,6 +320,8 @@ void RequestMapper::serviceV2(HttpRequest &request, HttpResponse &response) ReadingListInfoControllerV2().service(request, response); } else if (tagInfo.exactMatch(path)) { TagInfoControllerV2().service(request, response); + } else if (search.exactMatch(path)) { + SearchController().service(request, response); } } else { // response.writeText(library.cap(1)); diff --git a/YACReaderLibrary/server/server.pri b/YACReaderLibrary/server/server.pri index 345ea1cc..ea5eb66f 100644 --- a/YACReaderLibrary/server/server.pri +++ b/YACReaderLibrary/server/server.pri @@ -9,6 +9,7 @@ DEPENDPATH += $$PWD/controllers/v2 HEADERS += \ + $$PWD/controllers/v2/searchcontroller_v2.h \ $$PWD/static.h \ $$PWD/requestmapper.h \ $$PWD/yacreader_http_server.h \ @@ -53,6 +54,7 @@ HEADERS += \ SOURCES += \ + $$PWD/controllers/v2/searchcontroller_v2.cpp \ $$PWD/static.cpp \ $$PWD/requestmapper.cpp \ $$PWD/yacreader_http_server.cpp \ From 6d94efac14fef5c6679072a8bf720d670d60341c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Fri, 7 Apr 2023 11:01:24 +0200 Subject: [PATCH 6/7] Fix yacreaderlibraryserver compiltaion --- YACReaderLibraryServer/YACReaderLibraryServer.pro | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/YACReaderLibraryServer/YACReaderLibraryServer.pro b/YACReaderLibraryServer/YACReaderLibraryServer.pro index b84c2057..bebe2be7 100644 --- a/YACReaderLibraryServer/YACReaderLibraryServer.pro +++ b/YACReaderLibraryServer/YACReaderLibraryServer.pro @@ -66,7 +66,10 @@ HEADERS += ../YACReaderLibrary/library_creator.h \ ../YACReaderLibrary/yacreader_libraries.h \ ../YACReaderLibrary/comic_files_manager.h \ console_ui_library_creator.h \ - libraries_updater.h + libraries_updater.h \ + ../YACReaderLibrary/db/query_lexer.h \ + ../YACReaderLibrary/db/query_parser.h \ + ../YACReaderLibrary/db/search_query.h SOURCES += ../YACReaderLibrary/library_creator.cpp \ @@ -91,7 +94,10 @@ SOURCES += ../YACReaderLibrary/library_creator.cpp \ ../YACReaderLibrary/comic_files_manager.cpp \ console_ui_library_creator.cpp \ main.cpp \ - libraries_updater.cpp + libraries_updater.cpp \ + ../YACReaderLibrary/db/query_lexer.cpp \ + ../YACReaderLibrary/db/query_parser.cpp \ + ../YACReaderLibrary/db/search_query.cpp \ include(../YACReaderLibrary/server/server.pri) From 37ad8dc08261b9352cb668a34c93b2120ef81ed3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luis=20=C3=81ngel=20San=20Mart=C3=ADn?= Date: Fri, 7 Apr 2023 12:35:34 +0200 Subject: [PATCH 7/7] Update CHANGELOG --- CHANGELOG.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2a27d28e..c6f8055a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ Version counting is based on semantic versioning (Major.Feature.Patch) * Fix scroll in grid views when using Qt6 builds. * Fix deleting metadata from comics also deleted the number of pages info. +### Server +* New search API that exposes the search engine. + ## 9.11 ### YACReader