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