mirror of
https://github.com/zebrajr/ladybird.git
synced 2025-12-06 12:20:00 +01:00
LibDatabase+LibWebView: Extract our SQLite wrapper to its own library
It currently lives in LibWebView as it was only used for cookies and local storage, both of which are managed in the UI process. Let's move it to its own library now to allow other processes to use it, without having to depend on LibWebView (and therefore LibWeb).
This commit is contained in:
parent
e433dee543
commit
187d02c45d
|
|
@ -1,5 +1,6 @@
|
||||||
add_subdirectory(LibCompress)
|
add_subdirectory(LibCompress)
|
||||||
add_subdirectory(LibCrypto)
|
add_subdirectory(LibCrypto)
|
||||||
|
add_subdirectory(LibDatabase)
|
||||||
add_subdirectory(LibDiff)
|
add_subdirectory(LibDiff)
|
||||||
add_subdirectory(LibDNS)
|
add_subdirectory(LibDNS)
|
||||||
add_subdirectory(LibGC)
|
add_subdirectory(LibGC)
|
||||||
|
|
|
||||||
8
Libraries/LibDatabase/CMakeLists.txt
Normal file
8
Libraries/LibDatabase/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
set(SOURCES
|
||||||
|
Database.cpp
|
||||||
|
)
|
||||||
|
|
||||||
|
find_package(SQLite3 REQUIRED)
|
||||||
|
|
||||||
|
ladybird_lib(LibDatabase database EXPLICIT_SYMBOL_EXPORT)
|
||||||
|
target_link_libraries(LibDatabase PRIVATE LibCore SQLite::SQLite3)
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
* Copyright (c) 2022-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
*/
|
*/
|
||||||
|
|
@ -8,12 +8,11 @@
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <AK/Time.h>
|
#include <AK/Time.h>
|
||||||
#include <LibCore/Directory.h>
|
#include <LibCore/Directory.h>
|
||||||
#include <LibCore/StandardPaths.h>
|
#include <LibDatabase/Database.h>
|
||||||
#include <LibWebView/Database.h>
|
|
||||||
|
|
||||||
#include <sqlite3.h>
|
#include <sqlite3.h>
|
||||||
|
|
||||||
namespace WebView {
|
namespace Database {
|
||||||
|
|
||||||
static constexpr StringView sql_error(int error_code)
|
static constexpr StringView sql_error(int error_code)
|
||||||
{
|
{
|
||||||
|
|
@ -39,13 +38,10 @@ static constexpr StringView sql_error(int error_code)
|
||||||
} \
|
} \
|
||||||
})
|
})
|
||||||
|
|
||||||
ErrorOr<NonnullRefPtr<Database>> Database::create()
|
ErrorOr<NonnullRefPtr<Database>> Database::create(ByteString const& directory, StringView name)
|
||||||
{
|
{
|
||||||
// FIXME: Move this to a generic "Ladybird data directory" helper.
|
TRY(Core::Directory::create(directory, Core::Directory::CreateDirectories::Yes));
|
||||||
auto database_path = ByteString::formatted("{}/Ladybird", Core::StandardPaths::user_data_directory());
|
auto database_file = ByteString::formatted("{}/{}.db", directory, name);
|
||||||
TRY(Core::Directory::create(database_path, Core::Directory::CreateDirectories::Yes));
|
|
||||||
|
|
||||||
auto database_file = ByteString::formatted("{}/Ladybird.db", database_path);
|
|
||||||
|
|
||||||
sqlite3* m_database { nullptr };
|
sqlite3* m_database { nullptr };
|
||||||
SQL_TRY(sqlite3_open(database_file.characters(), &m_database));
|
SQL_TRY(sqlite3_open(database_file.characters(), &m_database));
|
||||||
|
|
@ -67,7 +63,7 @@ Database::~Database()
|
||||||
sqlite3_close(m_database);
|
sqlite3_close(m_database);
|
||||||
}
|
}
|
||||||
|
|
||||||
ErrorOr<Database::StatementID> Database::prepare_statement(StringView statement)
|
ErrorOr<StatementID> Database::prepare_statement(StringView statement)
|
||||||
{
|
{
|
||||||
sqlite3_stmt* prepared_statement { nullptr };
|
sqlite3_stmt* prepared_statement { nullptr };
|
||||||
SQL_TRY(sqlite3_prepare_v2(m_database, statement.characters_without_null_termination(), static_cast<int>(statement.length()), &prepared_statement, nullptr));
|
SQL_TRY(sqlite3_prepare_v2(m_database, statement.characters_without_null_termination(), static_cast<int>(statement.length()), &prepared_statement, nullptr));
|
||||||
|
|
@ -119,10 +115,10 @@ void Database::apply_placeholder(StatementID statement_id, int index, ValueType
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
template void Database::apply_placeholder(StatementID, int, String const&);
|
template DATABASE_API void Database::apply_placeholder(StatementID, int, String const&);
|
||||||
template void Database::apply_placeholder(StatementID, int, UnixDateTime const&);
|
template DATABASE_API void Database::apply_placeholder(StatementID, int, UnixDateTime const&);
|
||||||
template void Database::apply_placeholder(StatementID, int, int const&);
|
template DATABASE_API void Database::apply_placeholder(StatementID, int, int const&);
|
||||||
template void Database::apply_placeholder(StatementID, int, bool const&);
|
template DATABASE_API void Database::apply_placeholder(StatementID, int, bool const&);
|
||||||
|
|
||||||
template<typename ValueType>
|
template<typename ValueType>
|
||||||
ValueType Database::result_column(StatementID statement_id, int column)
|
ValueType Database::result_column(StatementID statement_id, int column)
|
||||||
|
|
@ -144,9 +140,9 @@ ValueType Database::result_column(StatementID statement_id, int column)
|
||||||
VERIFY_NOT_REACHED();
|
VERIFY_NOT_REACHED();
|
||||||
}
|
}
|
||||||
|
|
||||||
template String Database::result_column(StatementID, int);
|
template DATABASE_API String Database::result_column(StatementID, int);
|
||||||
template UnixDateTime Database::result_column(StatementID, int);
|
template DATABASE_API UnixDateTime Database::result_column(StatementID, int);
|
||||||
template int Database::result_column(StatementID, int);
|
template DATABASE_API int Database::result_column(StatementID, int);
|
||||||
template bool Database::result_column(StatementID, int);
|
template DATABASE_API bool Database::result_column(StatementID, int);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (c) 2022-2024, Tim Flynn <trflynn89@serenityos.org>
|
* Copyright (c) 2022-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
* Copyright (c) 2023, Jelle Raaijmakers <jelle@ladybird.org>
|
* Copyright (c) 2023, Jelle Raaijmakers <jelle@ladybird.org>
|
||||||
*
|
*
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
|
@ -13,19 +13,18 @@
|
||||||
#include <AK/RefCounted.h>
|
#include <AK/RefCounted.h>
|
||||||
#include <AK/StringView.h>
|
#include <AK/StringView.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
#include <LibWebView/Forward.h>
|
#include <LibDatabase/Forward.h>
|
||||||
|
|
||||||
struct sqlite3;
|
struct sqlite3;
|
||||||
struct sqlite3_stmt;
|
struct sqlite3_stmt;
|
||||||
|
|
||||||
namespace WebView {
|
namespace Database {
|
||||||
|
|
||||||
class WEBVIEW_API Database : public RefCounted<Database> {
|
class DATABASE_API Database : public RefCounted<Database> {
|
||||||
public:
|
public:
|
||||||
static ErrorOr<NonnullRefPtr<Database>> create();
|
static ErrorOr<NonnullRefPtr<Database>> create(ByteString const& directory, StringView name);
|
||||||
~Database();
|
~Database();
|
||||||
|
|
||||||
using StatementID = size_t;
|
|
||||||
using OnResult = Function<void(StatementID)>;
|
using OnResult = Function<void(StatementID)>;
|
||||||
|
|
||||||
ErrorOr<StatementID> prepare_statement(StringView statement);
|
ErrorOr<StatementID> prepare_statement(StringView statement);
|
||||||
18
Libraries/LibDatabase/Forward.h
Normal file
18
Libraries/LibDatabase/Forward.h
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Types.h>
|
||||||
|
#include <LibDatabase/Export.h>
|
||||||
|
|
||||||
|
namespace Database {
|
||||||
|
|
||||||
|
class Database;
|
||||||
|
|
||||||
|
using StatementID = size_t;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -10,6 +10,7 @@
|
||||||
#include <LibCore/StandardPaths.h>
|
#include <LibCore/StandardPaths.h>
|
||||||
#include <LibCore/System.h>
|
#include <LibCore/System.h>
|
||||||
#include <LibCore/TimeZoneWatcher.h>
|
#include <LibCore/TimeZoneWatcher.h>
|
||||||
|
#include <LibDatabase/Database.h>
|
||||||
#include <LibDevTools/DevToolsServer.h>
|
#include <LibDevTools/DevToolsServer.h>
|
||||||
#include <LibFileSystem/FileSystem.h>
|
#include <LibFileSystem/FileSystem.h>
|
||||||
#include <LibImageDecoderClient/Client.h>
|
#include <LibImageDecoderClient/Client.h>
|
||||||
|
|
@ -17,7 +18,6 @@
|
||||||
#include <LibWeb/Loader/UserAgent.h>
|
#include <LibWeb/Loader/UserAgent.h>
|
||||||
#include <LibWebView/Application.h>
|
#include <LibWebView/Application.h>
|
||||||
#include <LibWebView/CookieJar.h>
|
#include <LibWebView/CookieJar.h>
|
||||||
#include <LibWebView/Database.h>
|
|
||||||
#include <LibWebView/HeadlessWebView.h>
|
#include <LibWebView/HeadlessWebView.h>
|
||||||
#include <LibWebView/HelperProcess.h>
|
#include <LibWebView/HelperProcess.h>
|
||||||
#include <LibWebView/Menu.h>
|
#include <LibWebView/Menu.h>
|
||||||
|
|
@ -347,9 +347,12 @@ ErrorOr<void> Application::launch_services()
|
||||||
};
|
};
|
||||||
|
|
||||||
if (m_browser_options.disable_sql_database == DisableSQLDatabase::No) {
|
if (m_browser_options.disable_sql_database == DisableSQLDatabase::No) {
|
||||||
m_database = Database::create().release_value_but_fixme_should_propagate_errors();
|
// FIXME: Move this to a generic "Ladybird data directory" helper.
|
||||||
m_cookie_jar = CookieJar::create(*m_database).release_value_but_fixme_should_propagate_errors();
|
auto database_path = ByteString::formatted("{}/Ladybird", Core::StandardPaths::user_data_directory());
|
||||||
m_storage_jar = StorageJar::create(*m_database).release_value_but_fixme_should_propagate_errors();
|
|
||||||
|
m_database = TRY(Database::Database::create(database_path, "Ladybird"sv));
|
||||||
|
m_cookie_jar = TRY(CookieJar::create(*m_database));
|
||||||
|
m_storage_jar = TRY(StorageJar::create(*m_database));
|
||||||
} else {
|
} else {
|
||||||
m_cookie_jar = CookieJar::create();
|
m_cookie_jar = CookieJar::create();
|
||||||
m_storage_jar = StorageJar::create();
|
m_storage_jar = StorageJar::create();
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
#include <AK/Swift.h>
|
#include <AK/Swift.h>
|
||||||
#include <LibCore/EventLoop.h>
|
#include <LibCore/EventLoop.h>
|
||||||
#include <LibCore/Forward.h>
|
#include <LibCore/Forward.h>
|
||||||
|
#include <LibDatabase/Forward.h>
|
||||||
#include <LibDevTools/DevToolsDelegate.h>
|
#include <LibDevTools/DevToolsDelegate.h>
|
||||||
#include <LibDevTools/Forward.h>
|
#include <LibDevTools/Forward.h>
|
||||||
#include <LibImageDecoderClient/Client.h>
|
#include <LibImageDecoderClient/Client.h>
|
||||||
|
|
@ -181,7 +182,7 @@ private:
|
||||||
RefPtr<WebContentClient> m_spare_web_content_process;
|
RefPtr<WebContentClient> m_spare_web_content_process;
|
||||||
bool m_has_queued_task_to_launch_spare_web_content_process { false };
|
bool m_has_queued_task_to_launch_spare_web_content_process { false };
|
||||||
|
|
||||||
RefPtr<Database> m_database;
|
RefPtr<Database::Database> m_database;
|
||||||
OwnPtr<CookieJar> m_cookie_jar;
|
OwnPtr<CookieJar> m_cookie_jar;
|
||||||
OwnPtr<StorageJar> m_storage_jar;
|
OwnPtr<StorageJar> m_storage_jar;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,6 @@ set(SOURCES
|
||||||
BrowserProcess.cpp
|
BrowserProcess.cpp
|
||||||
ConsoleOutput.cpp
|
ConsoleOutput.cpp
|
||||||
CookieJar.cpp
|
CookieJar.cpp
|
||||||
Database.cpp
|
|
||||||
DOMNodeProperties.cpp
|
DOMNodeProperties.cpp
|
||||||
HeadlessWebView.cpp
|
HeadlessWebView.cpp
|
||||||
HelperProcess.cpp
|
HelperProcess.cpp
|
||||||
|
|
@ -70,16 +69,13 @@ set(GENERATED_SOURCES
|
||||||
)
|
)
|
||||||
|
|
||||||
ladybird_lib(LibWebView webview EXPLICIT_SYMBOL_EXPORT)
|
ladybird_lib(LibWebView webview EXPLICIT_SYMBOL_EXPORT)
|
||||||
target_link_libraries(LibWebView PRIVATE LibCore LibDevTools LibFileSystem LibGfx LibImageDecoderClient LibIPC LibRequests LibJS LibWeb LibUnicode LibURL LibSyntax LibTextCodec)
|
target_link_libraries(LibWebView PRIVATE LibCore LibDatabase LibDevTools LibFileSystem LibGfx LibImageDecoderClient LibIPC LibRequests LibJS LibWeb LibUnicode LibURL LibSyntax LibTextCodec)
|
||||||
|
|
||||||
if (APPLE)
|
if (APPLE)
|
||||||
target_link_libraries(LibWebView PRIVATE LibThreading)
|
target_link_libraries(LibWebView PRIVATE LibThreading)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
# Third-party
|
# Third-party
|
||||||
find_package(SQLite3 REQUIRED)
|
|
||||||
target_link_libraries(LibWebView PRIVATE SQLite::SQLite3)
|
|
||||||
|
|
||||||
if (HAS_FONTCONFIG)
|
if (HAS_FONTCONFIG)
|
||||||
target_link_libraries(LibWebView PRIVATE Fontconfig::Fontconfig)
|
target_link_libraries(LibWebView PRIVATE Fontconfig::Fontconfig)
|
||||||
endif()
|
endif()
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <AK/StringBuilder.h>
|
#include <AK/StringBuilder.h>
|
||||||
#include <AK/Time.h>
|
#include <AK/Time.h>
|
||||||
#include <AK/Vector.h>
|
#include <AK/Vector.h>
|
||||||
|
#include <LibDatabase/Database.h>
|
||||||
#include <LibURL/URL.h>
|
#include <LibURL/URL.h>
|
||||||
#include <LibWeb/Cookie/ParsedCookie.h>
|
#include <LibWeb/Cookie/ParsedCookie.h>
|
||||||
#include <LibWebView/CookieJar.h>
|
#include <LibWebView/CookieJar.h>
|
||||||
|
|
@ -20,7 +21,7 @@ namespace WebView {
|
||||||
|
|
||||||
static constexpr auto DATABASE_SYNCHRONIZATION_TIMER = AK::Duration::from_seconds(30);
|
static constexpr auto DATABASE_SYNCHRONIZATION_TIMER = AK::Duration::from_seconds(30);
|
||||||
|
|
||||||
ErrorOr<NonnullOwnPtr<CookieJar>> CookieJar::create(Database& database)
|
ErrorOr<NonnullOwnPtr<CookieJar>> CookieJar::create(Database::Database& database)
|
||||||
{
|
{
|
||||||
Statements statements {};
|
Statements statements {};
|
||||||
|
|
||||||
|
|
@ -665,7 +666,7 @@ void CookieJar::PersistedStorage::insert_cookie(Web::Cookie::Cookie const& cooki
|
||||||
cookie.persistent);
|
cookie.persistent);
|
||||||
}
|
}
|
||||||
|
|
||||||
static Web::Cookie::Cookie parse_cookie(Database& database, Database::StatementID statement_id)
|
static Web::Cookie::Cookie parse_cookie(Database::Database& database, Database::StatementID statement_id)
|
||||||
{
|
{
|
||||||
int column = 0;
|
int column = 0;
|
||||||
auto convert_text = [&](auto& field) { field = database.result_column<String>(statement_id, column++); };
|
auto convert_text = [&](auto& field) { field = database.result_column<String>(statement_id, column++); };
|
||||||
|
|
|
||||||
|
|
@ -13,10 +13,10 @@
|
||||||
#include <AK/StringView.h>
|
#include <AK/StringView.h>
|
||||||
#include <AK/Traits.h>
|
#include <AK/Traits.h>
|
||||||
#include <LibCore/Timer.h>
|
#include <LibCore/Timer.h>
|
||||||
|
#include <LibDatabase/Forward.h>
|
||||||
#include <LibURL/Forward.h>
|
#include <LibURL/Forward.h>
|
||||||
#include <LibWeb/Cookie/Cookie.h>
|
#include <LibWeb/Cookie/Cookie.h>
|
||||||
#include <LibWeb/Forward.h>
|
#include <LibWeb/Forward.h>
|
||||||
#include <LibWebView/Database.h>
|
|
||||||
#include <LibWebView/Forward.h>
|
#include <LibWebView/Forward.h>
|
||||||
|
|
||||||
namespace WebView {
|
namespace WebView {
|
||||||
|
|
@ -76,13 +76,13 @@ class WEBVIEW_API CookieJar {
|
||||||
void insert_cookie(Web::Cookie::Cookie const& cookie);
|
void insert_cookie(Web::Cookie::Cookie const& cookie);
|
||||||
TransientStorage::Cookies select_all_cookies();
|
TransientStorage::Cookies select_all_cookies();
|
||||||
|
|
||||||
Database& database;
|
Database::Database& database;
|
||||||
Statements statements;
|
Statements statements;
|
||||||
RefPtr<Core::Timer> synchronization_timer {};
|
RefPtr<Core::Timer> synchronization_timer {};
|
||||||
};
|
};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static ErrorOr<NonnullOwnPtr<CookieJar>> create(Database&);
|
static ErrorOr<NonnullOwnPtr<CookieJar>> create(Database::Database&);
|
||||||
static NonnullOwnPtr<CookieJar> create();
|
static NonnullOwnPtr<CookieJar> create();
|
||||||
|
|
||||||
~CookieJar();
|
~CookieJar();
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ class Action;
|
||||||
class Application;
|
class Application;
|
||||||
class Autocomplete;
|
class Autocomplete;
|
||||||
class CookieJar;
|
class CookieJar;
|
||||||
class Database;
|
|
||||||
class Menu;
|
class Menu;
|
||||||
class OutOfProcessWebView;
|
class OutOfProcessWebView;
|
||||||
class ProcessManager;
|
class ProcessManager;
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@
|
||||||
|
|
||||||
#include <AK/NonnullOwnPtr.h>
|
#include <AK/NonnullOwnPtr.h>
|
||||||
#include <AK/StdLibExtras.h>
|
#include <AK/StdLibExtras.h>
|
||||||
|
#include <LibDatabase/Database.h>
|
||||||
#include <LibWebView/StorageJar.h>
|
#include <LibWebView/StorageJar.h>
|
||||||
|
|
||||||
namespace WebView {
|
namespace WebView {
|
||||||
|
|
@ -13,7 +14,7 @@ namespace WebView {
|
||||||
// Quota size is specified in https://storage.spec.whatwg.org/#registered-storage-endpoints
|
// Quota size is specified in https://storage.spec.whatwg.org/#registered-storage-endpoints
|
||||||
static constexpr size_t LOCAL_STORAGE_QUOTA = 5 * MiB;
|
static constexpr size_t LOCAL_STORAGE_QUOTA = 5 * MiB;
|
||||||
|
|
||||||
ErrorOr<NonnullOwnPtr<StorageJar>> StorageJar::create(Database& database)
|
ErrorOr<NonnullOwnPtr<StorageJar>> StorageJar::create(Database::Database& database)
|
||||||
{
|
{
|
||||||
Statements statements {};
|
Statements statements {};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -9,8 +9,8 @@
|
||||||
#include <AK/HashMap.h>
|
#include <AK/HashMap.h>
|
||||||
#include <AK/String.h>
|
#include <AK/String.h>
|
||||||
#include <AK/Traits.h>
|
#include <AK/Traits.h>
|
||||||
|
#include <LibDatabase/Forward.h>
|
||||||
#include <LibWeb/StorageAPI/StorageEndpoint.h>
|
#include <LibWeb/StorageAPI/StorageEndpoint.h>
|
||||||
#include <LibWebView/Database.h>
|
|
||||||
#include <LibWebView/Forward.h>
|
#include <LibWebView/Forward.h>
|
||||||
#include <LibWebView/StorageOperationError.h>
|
#include <LibWebView/StorageOperationError.h>
|
||||||
|
|
||||||
|
|
@ -31,7 +31,7 @@ class WEBVIEW_API StorageJar {
|
||||||
AK_MAKE_NONMOVABLE(StorageJar);
|
AK_MAKE_NONMOVABLE(StorageJar);
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static ErrorOr<NonnullOwnPtr<StorageJar>> create(Database&);
|
static ErrorOr<NonnullOwnPtr<StorageJar>> create(Database::Database&);
|
||||||
static NonnullOwnPtr<StorageJar> create();
|
static NonnullOwnPtr<StorageJar> create();
|
||||||
|
|
||||||
~StorageJar();
|
~StorageJar();
|
||||||
|
|
@ -71,7 +71,7 @@ private:
|
||||||
void clear(StorageEndpointType storage_endpoint, String const& storage_key);
|
void clear(StorageEndpointType storage_endpoint, String const& storage_key);
|
||||||
Vector<String> get_keys(StorageEndpointType storage_endpoint, String const& storage_key);
|
Vector<String> get_keys(StorageEndpointType storage_endpoint, String const& storage_key);
|
||||||
|
|
||||||
Database& database;
|
Database::Database& database;
|
||||||
Statements statements;
|
Statements statements;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue
Block a user