diff options
Diffstat (limited to 'server/src/rwgps.cppm')
| -rw-r--r-- | server/src/rwgps.cppm | 211 |
1 files changed, 119 insertions, 92 deletions
diff --git a/server/src/rwgps.cppm b/server/src/rwgps.cppm index 7ad6605..8269ee8 100644 --- a/server/src/rwgps.cppm +++ b/server/src/rwgps.cppm | |||
| @@ -17,121 +17,148 @@ namespace json = boost::json; | |||
| 17 | 17 | ||
| 18 | namespace routemon::rwgps { | 18 | namespace routemon::rwgps { |
| 19 | 19 | ||
| 20 | export struct route_summary { | 20 | export struct route_summary |
| 21 | std::int64_t id; | 21 | { |
| 22 | std::int64_t user_id; | 22 | std::int64_t id; |
| 23 | std::string url; | 23 | std::int64_t user_id; |
| 24 | std::string name; | 24 | std::string url; |
| 25 | std::string description; | 25 | std::string name; |
| 26 | }; | 26 | std::string description; |
| 27 | }; | ||
| 27 | 28 | ||
| 28 | struct pagination { | 29 | struct pagination |
| 29 | std::size_t record_count; | 30 | { |
| 30 | std::size_t page_count; | 31 | std::size_t record_count; |
| 31 | std::size_t page_size; | 32 | std::size_t page_count; |
| 32 | std::optional<std::string> next_page_url; | 33 | std::size_t page_size; |
| 33 | }; | 34 | std::optional<std::string> next_page_url; |
| 35 | }; | ||
| 34 | 36 | ||
| 35 | struct get_routes_meta { | 37 | struct get_routes_meta |
| 36 | pagination pagination; | 38 | { |
| 37 | }; | 39 | pagination pagination; |
| 40 | }; | ||
| 38 | 41 | ||
| 39 | struct get_routes_response { | 42 | struct get_routes_response |
| 40 | std::vector<route_summary> routes; | 43 | { |
| 41 | get_routes_meta meta; | 44 | std::vector<route_summary> routes; |
| 42 | }; | 45 | get_routes_meta meta; |
| 46 | }; | ||
| 43 | 47 | ||
| 44 | auto tag_invoke(json::value_to_tag<route_summary> const&, json::value const& jv) -> route_summary { | 48 | auto tag_invoke(json::value_to_tag<route_summary> const&, json::value const& jv) |
| 45 | return { | 49 | -> route_summary |
| 46 | .id = json::value_to<std::int64_t>(jv.at("id")), | 50 | { |
| 47 | .user_id = json::value_to<std::int64_t>(jv.at("user_id")), | 51 | return { |
| 48 | .url = json::value_to<std::string>(jv.at("url")), | 52 | .id = json::value_to<std::int64_t>(jv.at("id")), |
| 49 | .name = json::value_to<std::string>(jv.at("name")), | 53 | .user_id = json::value_to<std::int64_t>(jv.at("user_id")), |
| 54 | .url = json::value_to<std::string>(jv.at("url")), | ||
| 55 | .name = json::value_to<std::string>(jv.at("name")), | ||
| 50 | .description = json::value_to<std::string>(jv.at("description")), | 56 | .description = json::value_to<std::string>(jv.at("description")), |
| 51 | }; | 57 | }; |
| 52 | } | 58 | } |
| 53 | 59 | ||
| 54 | auto tag_invoke(json::value_to_tag<pagination> const&, json::value const& jv) -> pagination { | 60 | auto tag_invoke(json::value_to_tag<pagination> const&, json::value const& jv) |
| 55 | return { | 61 | -> pagination |
| 56 | .record_count = json::value_to<std::size_t>(jv.at("record_count")), | 62 | { |
| 57 | .page_count = json::value_to<std::size_t>(jv.at("page_count")), | 63 | return { |
| 58 | .page_size = json::value_to<std::size_t>(jv.at("page_size")), | 64 | .record_count = json::value_to<std::size_t>(jv.at("record_count")), |
| 59 | .next_page_url = json::value_to<std::optional<std::string>>(jv.at("next_page_url")), | 65 | .page_count = json::value_to<std::size_t>(jv.at("page_count")), |
| 60 | }; | 66 | .page_size = json::value_to<std::size_t>(jv.at("page_size")), |
| 61 | } | 67 | .next_page_url = |
| 68 | json::value_to<std::optional<std::string>>(jv.at("next_page_url")), | ||
| 69 | }; | ||
| 70 | } | ||
| 62 | 71 | ||
| 63 | auto tag_invoke(json::value_to_tag<get_routes_meta> const&, json::value const& jv) -> get_routes_meta { | 72 | auto tag_invoke( |
| 64 | return { | 73 | json::value_to_tag<get_routes_meta> const&, json::value const& jv) |
| 74 | -> get_routes_meta | ||
| 75 | { | ||
| 76 | return { | ||
| 65 | .pagination = json::value_to<pagination>(jv.at("pagination")), | 77 | .pagination = json::value_to<pagination>(jv.at("pagination")), |
| 66 | }; | 78 | }; |
| 67 | } | 79 | } |
| 68 | 80 | ||
| 69 | auto tag_invoke(json::value_to_tag<get_routes_response> const&, json::value const& jv) -> get_routes_response { | 81 | auto tag_invoke( |
| 70 | return { | 82 | json::value_to_tag<get_routes_response> const&, json::value const& jv) |
| 83 | -> get_routes_response | ||
| 84 | { | ||
| 85 | return { | ||
| 71 | .routes = json::value_to<std::vector<route_summary>>(jv.at("routes")), | 86 | .routes = json::value_to<std::vector<route_summary>>(jv.at("routes")), |
| 72 | .meta = json::value_to<get_routes_meta>(jv.at("meta")), | 87 | .meta = json::value_to<get_routes_meta>(jv.at("meta")), |
| 73 | }; | 88 | }; |
| 74 | } | 89 | } |
| 75 | 90 | ||
| 76 | auto json_value_to_get_routes_response(json::value const& jv) -> get_routes_response { | 91 | auto json_value_to_get_routes_response(json::value const& jv) |
| 77 | return json::value_to<get_routes_response>(jv); | 92 | -> get_routes_response |
| 78 | } | 93 | { |
| 94 | return json::value_to<get_routes_response>(jv); | ||
| 95 | } | ||
| 79 | 96 | ||
| 80 | export class client { | 97 | export class client |
| 81 | log::logger l_; | 98 | { |
| 82 | http::client hc_; | 99 | log::logger l_; |
| 83 | std::string api_key_; | 100 | http::client hc_; |
| 84 | std::string auth_token_; | 101 | std::string api_key_; |
| 102 | std::string auth_token_; | ||
| 85 | 103 | ||
| 86 | static constexpr std::string host = "ridewithgps.com"; | 104 | static constexpr std::string host = "ridewithgps.com"; |
| 87 | 105 | ||
| 88 | // TODO: handle failure appropriately | 106 | // TODO: handle failure appropriately |
| 89 | auto get_routes_page(std::size_t page) -> get_routes_response { | 107 | auto get_routes_page(std::size_t page) -> get_routes_response |
| 90 | auto req = bhttp::request<bhttp::string_body>{ | 108 | { |
| 109 | auto req = bhttp::request<bhttp::string_body>{ | ||
| 91 | bhttp::verb::get, | 110 | bhttp::verb::get, |
| 92 | std::format("/api/v1/routes.json?page_size=200?page={}", page), | 111 | std::format("/api/v1/routes.json?page_size=200?page={}", page), |
| 93 | 11, // HTTP 1.1 | 112 | 11, // HTTP 1.1 |
| 94 | }; | 113 | }; |
| 95 | req.set(bhttp::field::host, host); | 114 | req.set(bhttp::field::host, host); |
| 96 | req.set("x-rwgps-api-key", api_key_); | 115 | req.set("x-rwgps-api-key", api_key_); |
| 97 | req.set("x-rwgps-auth-token", auth_token_); | 116 | req.set("x-rwgps-auth-token", auth_token_); |
| 98 | 117 | ||
| 99 | auto rsp = hc_.do_request(req); | 118 | auto rsp = hc_.do_request(req); |
| 100 | auto p = json::stream_parser{}; | 119 | auto p = json::stream_parser{}; |
| 101 | for (auto const frag : rsp.body().cdata()) { | 120 | for (auto const frag : rsp.body().cdata()) |
| 102 | p.write(static_cast<char const*>(frag.data()), frag.size()); | 121 | { |
| 103 | } | 122 | p.write(static_cast<char const*>(frag.data()), frag.size()); |
| 104 | assert(p.done()); | ||
| 105 | return json_value_to_get_routes_response(p.release()); | ||
| 106 | } | 123 | } |
| 124 | assert(p.done()); | ||
| 125 | return json_value_to_get_routes_response(p.release()); | ||
| 126 | } | ||
| 107 | 127 | ||
| 108 | public: | 128 | public: |
| 109 | explicit client(net::io_context& ioc, log::logger const& l, std::string api_key, std::string auth_token) | 129 | explicit client( |
| 110 | : l_{l.sub("rwgps-client")}, hc_{ioc}, api_key_{std::move(api_key)}, auth_token_{std::move(auth_token)} | 130 | net::io_context& ioc, log::logger const& l, std::string api_key, |
| 111 | {} | 131 | std::string auth_token) |
| 132 | : l_{l.sub("rwgps-client")}, hc_{ioc}, api_key_{std::move(api_key)}, | ||
| 133 | auth_token_{std::move(auth_token)} | ||
| 134 | { | ||
| 135 | } | ||
| 112 | 136 | ||
| 113 | auto get_all_routes() -> std::vector<route_summary> { | 137 | auto get_all_routes() -> std::vector<route_summary> |
| 114 | // TODO: make sure that there are no duplicates here. | 138 | { |
| 115 | // What does RWGPS sort on, by default? | 139 | // TODO: make sure that there are no duplicates here. |
| 116 | // Consider using an associative container instead of a vector. | 140 | // What does RWGPS sort on, by default? |
| 117 | auto record_count = 0uz; | 141 | // Consider using an associative container instead of a vector. |
| 118 | auto current_page = 0uz; | 142 | auto record_count = 0uz; |
| 119 | auto routes = std::vector<route_summary>{}; | 143 | auto current_page = 0uz; |
| 144 | auto routes = std::vector<route_summary>{}; | ||
| 120 | 145 | ||
| 121 | while (true) { | 146 | while (true) |
| 122 | auto rsp = get_routes_page(current_page); | 147 | { |
| 123 | if (rsp.meta.pagination.next_page_url) | 148 | auto rsp = get_routes_page(current_page); |
| 124 | l_.debug("Next page URL: {}", *rsp.meta.pagination.next_page_url); | 149 | if (rsp.meta.pagination.next_page_url) |
| 125 | routes.append_range(rsp.routes); | 150 | l_.debug("Next page URL: {}", *rsp.meta.pagination.next_page_url); |
| 126 | if (rsp.meta.pagination.record_count > 0) | 151 | routes.append_range(rsp.routes); |
| 127 | record_count = rsp.meta.pagination.record_count; | 152 | if (rsp.meta.pagination.record_count > 0) |
| 128 | if (rsp.routes.empty() || routes.size() >= record_count) { | 153 | record_count = rsp.meta.pagination.record_count; |
| 129 | break; | 154 | if (rsp.routes.empty() || routes.size() >= record_count) |
| 130 | } | 155 | { |
| 156 | break; | ||
| 131 | } | 157 | } |
| 132 | |||
| 133 | return routes; | ||
| 134 | } | 158 | } |
| 135 | }; | 159 | |
| 160 | return routes; | ||
| 161 | } | ||
| 162 | }; | ||
| 136 | 163 | ||
| 137 | } // namespace routemon::rwgps | 164 | } // namespace routemon::rwgps |