From e9686b6ab684785d5f9acbc98942beae94817562 Mon Sep 17 00:00:00 2001 From: MetroWind Date: Sun, 21 Sep 2025 21:34:34 -0700 Subject: Implement dir handler. Unit test WIP. --- src/app.cpp | 150 ++++++++++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 115 insertions(+), 35 deletions(-) (limited to 'src/app.cpp') diff --git a/src/app.cpp b/src/app.cpp index 7cfcf0d..472c266 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -75,7 +75,7 @@ void setTokenCookies(const mw::Tokens& tokens, App::Response& res) expire_sec = expire.count(); } res.set_header("Set-Cookie", std::format( - "shrt-access-token={}; Max-Age={}", + "webdir-access-token={}; Max-Age={}", mw::urlEncode(tokens.access_token), expire_sec)); // Add refresh token to cookie, with one month expiration. if(tokens.refresh_token.has_value()) @@ -89,7 +89,7 @@ void setTokenCookies(const mw::Tokens& tokens, App::Response& res) } res.set_header("Set-Cookie", std::format( - "shrt-refresh-token={}; Max-Age={}", + "webdir-refresh-token={}; Max-Age={}", mw::urlEncode(*tokens.refresh_token), expire_sec)); } } @@ -111,6 +111,21 @@ mw::HTTPServer::ListenAddress listenAddrFromConfig(const Configuration& config) return sock; } +nlohmann::json jsonFromItem(const LinkItem& item) +{ + return { + {"id", item.id}, + {"owner_id", item.owner_id}, + {"parent_id", item.parent_id}, + {"name", item.name}, + {"url", item.url}, + {"description", item.description}, + {"visibility", LinkItem::visibilityToStr(item.visibility)}, + {"time", mw::timeToSeconds(item.time)}, + {"time_str", mw::timeToStr(item.time)}, + }; +} + } // namespace App::App(const Configuration& conf, @@ -151,18 +166,6 @@ std::string App::urlFor(const std::string& name, const std::string& arg) const { return mw::URL(base_url).appendPath("_/statics").appendPath(arg).str(); } - if(name == "index") - { - return base_url.str(); - } - if(name == "shortcut") - { - return mw::URL(base_url).appendPath(arg).str(); - } - if(name == "links") - { - return mw::URL(base_url).appendPath("_/links").str(); - } if(name == "login") { return mw::URL(base_url).appendPath("_/login").str(); @@ -171,22 +174,13 @@ std::string App::urlFor(const std::string& name, const std::string& arg) const { return mw::URL(base_url).appendPath("_/openid-redirect").str(); } - if(name == "new-link") - { - return mw::URL(base_url).appendPath("_/new-link").str(); - } - if(name == "create-link") - { - return mw::URL(base_url).appendPath("_/create-link").str(); - } - if(name == "delete-link-dialog") + if(name == "index") { - return mw::URL(base_url).appendPath("_/delete-link").appendPath(arg) - .str(); + return base_url.str(); } - if(name == "delete-link") + if(name == "dir") // /dir/ { - return mw::URL(base_url).appendPath("_/delete-link").str(); + return mw::URL(base_url).appendPath("dir").appendPath(arg).str(); } return ""; @@ -194,7 +188,86 @@ std::string App::urlFor(const std::string& name, const std::string& arg) const void App::handleIndex(Response& res) const { - res.set_redirect(urlFor("links"), 301); + res.set_redirect(urlFor("dir", "mw"), 301); +} + +void App::handleDir(const Request& req, Response& res) +{ + auto session = prepareSession(req, res, true); + if(!req.has_param("owner_or_id")) + { + res.status = 400; + res.set_content("Need parameter", "text/plain"); + return; + } + std::string owner_or_id = req.get_param_value("owner_or_id"); + std::string owner; + // If owner_or_id is an integer, it’s the ID of an item. Otherwise + // it’s a username. + auto item_id_maybe = mw::strToNumber(owner_or_id); + std::vector items; + if(item_id_maybe.has_value()) + { + ASSIGN_OR_RESPOND_ERROR(std::optional item, + data->itemByID(*item_id_maybe), res); + if(!item.has_value()) + { + res.status = 400; + res.set_content("Item not found", "text/plain"); + return; + } + ASSIGN_OR_RESPOND_ERROR(std::optional user, + data->userByID(item->owner_id), res); + if(!user.has_value()) + { + res.status = 500; + res.set_content("Owner of item not found", "text/plain"); + return; + } + owner = user->name; + ASSIGN_OR_RESPOND_ERROR(items, data->itemsByParent(*item_id_maybe), + res); + } + else + { + ASSIGN_OR_RESPOND_ERROR(std::optional user, + data->userByName(owner_or_id), res); + if(!user.has_value()) + { + res.status = 404; + res.set_content(std::string("Unknown user: ") + owner_or_id, + "text/plain"); + return; + } + owner = owner_or_id; + ASSIGN_OR_RESPOND_ERROR(items, data->itemsTopLevelByUser(user->id), + res); + } + + nlohmann::json items_data = nlohmann::json::array(); + for(const LinkItem& item: items) + { + if(item.visibility == LinkItem::PRIVATE) + { + if(session->status == SessionValidation::INVALID) + { + continue; + } + if(session->user.name != owner) + { + continue; + } + } + items_data.push_back(jsonFromItem(item)); + } + nlohmann::json data = { + {"session_user", session->user.name}, + {"owner", owner}, + {"this_url", req.target}, + {"items", std::move(items_data)}, + }; + std::string result = templates.render_file("dir.html", std::move(data)); + res.set_content(result, "text/html"); } void App::handleLogin(Response& res) const @@ -253,11 +326,8 @@ void App::setup() } } - server.Get(getPath("index"), [&]([[maybe_unused]] const Request& req, Response& res) - { - handleIndex(res); - }); - server.Get(getPath("login"), [&]([[maybe_unused]] const Request& req, Response& res) + server.Get(getPath("login"), [&]([[maybe_unused]] const Request& req, + Response& res) { handleLogin(res); }); @@ -265,6 +335,16 @@ void App::setup() { handleOpenIDRedirect(req, res); }); + server.Get(getPath("index"), [&]([[maybe_unused]] const Request& req, + Response& res) + { + handleIndex(res); + }); + server.Get(getPath("dir", "owner_or_id"), + [&]([[maybe_unused]] const Request& req, Response& res) + { + handleDir(req, res); + }); } mw::E App::validateSession(const Request& req) const @@ -276,7 +356,7 @@ mw::E App::validateSession(const Request& req) const } auto cookies = parseCookies(req.get_header_value("Cookie")); - if(auto it = cookies.find("shrt-access-token"); + if(auto it = cookies.find("webdir-access-token"); it != std::end(cookies)) { spdlog::debug("Cookie has access token."); @@ -289,7 +369,7 @@ mw::E App::validateSession(const Request& req) const } } // No access token or access token expired - if(auto it = cookies.find("shrt-refresh-token"); + if(auto it = cookies.find("webdir-refresh-token"); it != std::end(cookies)) { spdlog::debug("Cookie has refresh token."); -- cgit v1.2.3-70-g09d2