Skip to content

Commit

Permalink
feat(net): support evento v1 api (#68)
Browse files Browse the repository at this point in the history
* feat: v1 api data structure

* feat(net): evento v1 api

* fix(ui): logic error

* fix: history page does not show feedback

* build: add `EVENTO_API_V1` macro

* fix: auto login

* fix: guess image ext

* fix(net): modify error handle if return boolean

* fix: encoded url

* fix: time format convert

* chore: clean up code

---------

Co-authored-by: Mairon <[email protected]>
  • Loading branch information
Serein207 and Mairon1206 authored Sep 30, 2024
1 parent 533ac02 commit 2325031
Show file tree
Hide file tree
Showing 22 changed files with 838 additions and 186 deletions.
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ target_compile_definitions(${PROJECT_NAME}
$<$<CONFIG:Release>:EVENTO_RELEASE>
${PLATFORM}
LOCALE_DIR="${SOURCE_LOCALE_DIR}"
EVENTO_API_V1
)

target_link_libraries(${PROJECT_NAME}
Expand Down
116 changes: 81 additions & 35 deletions src/Controller/Core/AccountManager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
#include <Infrastructure/Network/ResponseStruct.h>
#include <Infrastructure/Utils/Config.h>
#include <Infrastructure/Utils/Result.h>
#include <chrono>
#include <keychain/keychain.h>
#include <optional>
#include <sast_link.h>
Expand Down Expand Up @@ -60,10 +61,15 @@ void AccountManager::performLogin() {
auto data = result.unwrap();

self.userInfo = data.userInfo;

self.setNetworkAccessToken(data.accessToken);
#ifdef EVENTO_API_V1
self.setKeychainAccessToken(data.accessToken);
self.expiredTime = std::chrono::system_clock::now() + std::chrono::days(30);
#else
self.setKeychainRefreshToken(data.refreshToken);
self.scheduleRenewAccessToken();
self.expiredTime = std::chrono::system_clock::now() + std::chrono::days(7);
#endif
self.setNetworkAccessToken(data.accessToken);
self.setLoginState(true);
});
}
Expand All @@ -83,17 +89,15 @@ void AccountManager::performRefreshToken() {
}
co_return Ok(std::monostate{});
}(),
[weak_this = weak_from_this(), &self](Result<std::monostate> result) {
if (auto alive = weak_this.lock()) {
if (result.isErr()) {
self.setLoginState(false);
self.bridge.getMessageManager().showMessage("登录过期,请重新登录",
MessageType::Info);
return;
}
self.performGetUserInfo();
spdlog::info("refresh token success");
[&self = *this](Result<std::monostate> result) {
if (result.isErr()) {
self.setLoginState(false);
self.bridge.getMessageManager().showMessage("登录过期,请重新登录",
MessageType::Info);
return;
}
self.performGetUserInfo();
spdlog::info("refresh token success");
});
}

Expand All @@ -108,16 +112,14 @@ void AccountManager::performGetUserInfo() {
}
co_return result.unwrap();
}(),
[weak_this = weak_from_this(), &self](Result<UserInfoEntity> result) {
if (auto alive = weak_this.lock()) {
if (result.isErr()) {
self.setLoginState(false);
return;
}
self.userInfo = result.unwrap();
spdlog::info("get user info success");
self.setLoginState(true);
[&self = *this](Result<UserInfoEntity> result) {
if (result.isErr()) {
self.setLoginState(false);
return;
}
self.userInfo = result.unwrap();
spdlog::info("get user info success");
self.setLoginState(true);
});
}

Expand Down Expand Up @@ -146,11 +148,24 @@ void AccountManager::tryLoginDirectly() {
if (isLogin()) {
return;
}
if (std::chrono::system_clock::now() + 15min < expiredTime) {
return;
}

#ifdef EVENTO_API_V1
if (auto token = getKeychainAccessToken()) {
spdlog::info("Token is found. Login directly!");
setNetworkAccessToken(*token);
performGetUserInfo();
}
#else
spdlog::info("Try login directly, expired time: {}", expiredTime.time_since_epoch().count());

// If the token is not expired after 15min, we don't need to login again
if (std::chrono::system_clock::now() + 15min < expiredTime) {
if (getKeychainRefreshToken()) {
performRefreshToken();
}
#endif
}

UserInfoEntity AccountManager::getUserInfo() {
Expand Down Expand Up @@ -180,17 +195,20 @@ void AccountManager::setKeychainRefreshToken(const std::string& refreshToken) co
keychain::Error err;
keychain::setPassword(package, service, userInfo.id, refreshToken, err);

if (err.code != 0) {
if (err.type != keychain::ErrorType::NoError) {
spdlog::error("Failed to save refresh token: {}", err.message);
return;
}
spdlog::info("Save refresh token success");
}

std::optional<std::string> AccountManager::getKeychainRefreshToken() const {
keychain::Error err;
auto refreshToken = keychain::getPassword(package, service, userInfo.id, err);

if (err.code != 0) {
spdlog::error("Failed to save refresh token: {}", err.message);
if (err.type != keychain::ErrorType::NoError) {
spdlog::error("Failed to get refresh token: {}", err.message);
return std::nullopt;
}

if (refreshToken.empty()) {
Expand All @@ -203,22 +221,50 @@ std::optional<std::string> AccountManager::getKeychainRefreshToken() const {
void AccountManager::scheduleRenewAccessToken() {
auto& self = *this;
renewAccessTokenTimer.expires_after(55min);
renewAccessTokenTimer.async_wait(
[weak_this = weak_from_this(), &self](const boost::system::error_code& ec) {
if (ec) {
spdlog::error("Failed to renew access token: {}", ec.message());
return;
}
if (auto alive = weak_this.lock()) {
self.performRefreshToken();
}
});
renewAccessTokenTimer.async_wait([&self = *this](const boost::system::error_code& ec) {
if (ec) {
spdlog::error("Failed to renew access token: {}", ec.message());
return;
}
self.performRefreshToken();
});
}

void AccountManager::setNetworkAccessToken(std::string accessToken) {
evento::networkClient()->tokenBytes = accessToken;
}

#ifdef EVENTO_API_V1
std::optional<std::string> AccountManager::getKeychainAccessToken() const {
keychain::Error err;
auto accessToken = keychain::getPassword(package, service, userInfo.id, err);

if (err.type != keychain::ErrorType::NoError) {
debug(), (int) err.type;
spdlog::error("Failed to get access token: {}", err.message);
return std::nullopt;
}

if (accessToken.empty()) {
return std::nullopt;
}

return accessToken;
}

void AccountManager::setKeychainAccessToken(const std::string& accessToken) const {
keychain::Error err;
keychain::setPassword(package, service, userInfo.id, accessToken, err);

if (err.type != keychain::ErrorType::NoError) {
debug(), (int) err.type;
spdlog::error("Failed to save access token: {}", err.message);
return;
}
spdlog::info("Save access token success");
}
#endif

void AccountManager::setLoginState(bool newState) {
auto& self = *this;
if (loginState != newState) {
Expand Down
12 changes: 11 additions & 1 deletion src/Controller/Core/AccountManager.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,12 @@ class AccountManager : private GlobalAgent<AccountManagerBridge>,
net::steady_timer renewAccessTokenTimer;

static const inline std::string package = "org.sast.evento";
static const inline std::string service = "refresh-token";
static const inline std::string service =
#ifdef EVENTO_API_V1
"access-token";
#else
"refresh-token";
#endif

public:
AccountManager(slint::ComponentHandle<UiEntryName> uiEntry, UiBridge& bridge);
Expand All @@ -45,6 +50,11 @@ class AccountManager : private GlobalAgent<AccountManagerBridge>,

static void setNetworkAccessToken(std::string accessToken);

#ifdef EVENTO_API_V1
[[nodiscard]] std::optional<std::string> getKeychainAccessToken() const;
void setKeychainAccessToken(const std::string& accessToken) const;
#endif

void setLoginState(bool newState);
void onStateChanged();

Expand Down
70 changes: 35 additions & 35 deletions src/Controller/View/HistoryPage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
#include <Controller/View/HistoryPage.h>
#include <Infrastructure/Network/NetworkClient.h>
#include <Infrastructure/Network/ResponseStruct.h>
#include <ranges>
#include <slint.h>
#include <spdlog/spdlog.h>

Expand Down Expand Up @@ -38,43 +37,44 @@ void HistoryPage::loadHistoryEvents(int page, int size) {
auto& self = *this;
self->set_state(PageState::Loading);

executor()->asyncExecute(
networkClient()->getHistoryEventList(page, size),
[&self = *this, this](Result<EventQueryRes> result) {
if (result.isErr()) {
self->set_state(PageState::Error);
self.bridge.getMessageManager().showMessage(result.unwrapErr().what(),
MessageType::Error);
return;
}
executor()->asyncExecute(loadHistoryEventsTask(page, size),
[&self = *this, this](Result<std::vector<EventFeedbackStruct>> result) {
if (result.isErr()) {
return;
}

auto res = result.unwrap();
self->set_total(res.total);
self->set_events(convert::from(res.elements));

feedbacks.clear();
auto feedbackSize = res.elements.size();
auto list = result.unwrap();
self->set_total(static_cast<int>(list.size()));
self->set_models(
std::make_shared<slint::VectorModel<EventFeedbackStruct>>(
list));
self->set_state(PageState::Normal);
});
}

auto trans = [](const auto& e) { return e.id; };
for (const auto& eventId : res.elements | std::views::transform(trans)) {
executor()->asyncExecute(
networkClient()->getUserFeedback(eventId, 0min),
[&self = *this, feedbackSize](Result<std::optional<FeedbackEntity>> result) {
if (result.isErr()) {
spdlog::warn("feedback load failed: {}", result.unwrapErr().what());
self.feedbacks.emplace_back(FeedbackStruct{});
} else {
self.feedbacks.emplace_back(convert::from(result.unwrap()));
}
Task<Result<std::vector<EventFeedbackStruct>>> HistoryPage::loadHistoryEventsTask(int page,
int size) {
auto& self = *this;
auto historyEventsRes = co_await networkClient()->getHistoryEventList(page, size);
if (historyEventsRes.isErr()) {
slint::invoke_from_event_loop([&self = *this] { self->set_state(PageState::Error); });
co_return historyEventsRes.unwrapErr();
}

if (self.feedbacks.size() == feedbackSize) {
self->set_feedbacks(std::make_shared<slint::VectorModel<FeedbackStruct>>(
self.feedbacks));
self->set_state(PageState::Normal);
}
});
}
});
auto historyEvents = historyEventsRes.unwrap();
std::vector<EventFeedbackStruct> res;
for (auto const& event : historyEvents.elements) {
auto feedbackRes = co_await networkClient()->getUserFeedback(event.id);
if (feedbackRes.isErr()) {
spdlog::warn("feedback load failed: {}", feedbackRes.unwrapErr().what());
self.bridge.getMessageManager().showMessage(feedbackRes.unwrapErr().what(),
MessageType::Error);
res.emplace_back(convert::from(event), FeedbackStruct{});
} else {
res.emplace_back(convert::from(event), convert::from(feedbackRes.unwrap()));
}
}
co_return res;
}

void HistoryPage::feedbackEvent(int eventId, int rating, std::string content) {
Expand Down
5 changes: 4 additions & 1 deletion src/Controller/View/HistoryPage.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <Controller/Core/BasicView.h>
#include <Controller/Core/GlobalAgent.hh>
#include <Controller/Core/UiBase.h>
#include <Infrastructure/Utils/Result.h>
#include <vector>

EVENTO_UI_START
Expand All @@ -18,9 +19,11 @@ class HistoryPage : public BasicView, private GlobalAgent<HistoryPageBridge> {

void loadHistoryEvents(int page, int size);

Task<Result<std::vector<EventFeedbackStruct>>> loadHistoryEventsTask(int page, int size);

void feedbackEvent(int eventId, int rating, std::string content);

std::vector<FeedbackStruct> feedbacks;
std::vector<EventFeedbackStruct> eventFeedbacks;
};

EVENTO_UI_END
1 change: 1 addition & 0 deletions src/Controller/View/MyEventPage.cc
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@ void MyEventPage::refreshUiModel(Result<EventQueryRes> result) {
self->set_not_started_model(convert::from(models[(int) EventState::SigningUp]));
self->set_active_model(convert::from(models[(int) EventState::Active]));
self->set_completed_model(convert::from(models[(int) EventState::Completed]));
self->set_state(PageState::Normal);
}

EVENTO_UI_END
2 changes: 1 addition & 1 deletion src/Infrastructure/Network/Api/Evento.hh
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ struct Evento {
std::format("{}{}{}",
url.path(),
url.has_query() ? "?" : "",
url.query()),
url.encoded_query().data()),
11};

req.set(http::field::host, url.host());
Expand Down
Loading

0 comments on commit 2325031

Please sign in to comment.