Size: 5828 bytes.


  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
// cs/apps/cite.pub/main.cc
#include <string>
#include <vector>

#include "cs/apps/cite.pub/protos/config.proto.hh"
#include "cs/apps/cite.pub/protos/get_cached_page_request.gpt.proto.hh"
#include "cs/apps/cite.pub/protos/lookup_mapping.proto.hh"
#include "cs/apps/cite.pub/protos/save_resource_form.gpt.proto.hh"
#include "cs/apps/common/health_endpoint.hh"
#include "cs/net/html/bootstrap/dsl.hh"
#include "cs/net/html/dom.hh"
#include "cs/net/http/client.hh"
#include "cs/net/http/request.hh"
#include "cs/net/http/response.hh"
#include "cs/net/http/web_app.hh"
#include "cs/net/proto/db/client.gpt.hh"
#include "cs/net/proto/db/database_base_url.gpt.hh"
#include "cs/net/proto/db/db.hh"
#include "cs/net/proto/form/proto_form.gpt.hh"
#include "cs/net/rpc/rpc.hh"
#include "cs/parsers/arg_parser.gpt.hh"
#include "cs/result.hh"
#include "cs/util/di/context.gpt.hh"
#include "cs/util/time.hh"
#include "cs/util/uuid.hh"

namespace {  // use_usings
using ::cs::Fetch;
using ::cs::Result;
using ::cs::ResultOr;
using ::cs::apps::citepub::protos::Config;
using ::cs::apps::citepub::protos::GetCachedPageRequest;
using ::cs::apps::citepub::protos::LookupMapping;
using ::cs::apps::citepub::protos::SaveResourceForm;
using ::cs::apps::common::GetHealth;
using ::cs::net::http::HtmlResponse;
using ::cs::net::http::HTTP_200_OK;
using ::cs::net::http::HTTP_400_BAD_REQUEST;
using ::cs::net::http::HTTP_404_NOT_FOUND;
using ::cs::net::http::kContentTypeTextHtml;
using ::cs::net::http::kContentTypeTextPlain;
using ::cs::net::http::Request;
using ::cs::net::http::Response;
using ::cs::net::http::WebApp;
using ::cs::net::proto::db::DatabaseBaseUrl;
using ::cs::net::proto::db::DatabaseClientImpl;
using ::cs::net::proto::db::EQUALS;
using ::cs::net::proto::db::IDatabaseClient;
using ::cs::net::proto::db::QueryView;
using ::cs::net::proto::db::Upsert;
using ::cs::net::proto::form::GenerateProtoForm;
using ::cs::net::rpc::ExtractProtoFromRequest;
using ::cs::parsers::ParseArgs;
using ::cs::util::di::Context;
using ::cs::util::di::ContextBuilder;
using ::cs::util::random::uuid::generate_uuid_v4;
using ::cs::util::time::NowAsISO8601TimeUTC;
}  // namespace

// Generate HTML content for a URL.
ResultOr<std::string> MakeSingleFileHTML(std::string url) {
  return Fetch(url, "GET");
}

using AppContext = ::cs::util::di::Context<
    ::cs::net::proto::db::DatabaseBaseUrl,
    ::cs::net::proto::db::IDatabaseClient>;

Response GetIndexPage(Request request, AppContext&) {
  using namespace cs::net::html::dom;
  namespace bs = ::cs::net::html::bootstrap;

  bs::NavbarConfig navbar_config;
  navbar_config.brand_href = "/";
  navbar_config.brand_label = "cite.pub";
  navbar_config.expand_breakpoint = "lg";
  navbar_config.collapse_id = "navbarMain";
  navbar_config.active_path = "/";

  return HtmlResponse(html(
      head(meta({{"charset", "utf-8"}}),
           meta({{"name", "viewport"},
                 {"content",
                  "width=device-width, initial-scale=1"}}),
           title("cite.pub - Save Web Pages"),
           bs::StylesheetLink()),
      body(
          bs::Navbar(navbar_config),
          bs::Container(
              h1("cite.pub") +
              p("Save any web page as a single HTML file") +
              GenerateProtoForm<SaveResourceForm>(
                  "/save/")),
          bs::ScriptTag())));
}

// POST /save/ - Save a web resource (form submission).
Response SaveResource(Request request, AppContext& ctx) {
  SET_OR_RET(
      SaveResourceForm form,
      ExtractProtoFromRequest<SaveResourceForm>(request));

  // Generate HTML content for the page
  SET_OR_RET(std::string html,
             MakeSingleFileHTML(form.url));

  // Generate UUID and short code (first 8 characters)
  std::string uuid = generate_uuid_v4();
  std::string short_code = uuid.substr(0, 8);

  // Create lookup mapping
  LookupMapping mapping;
  mapping.uuid = uuid;
  mapping.short_code = short_code;
  mapping.original_url = form.url;
  mapping.created_at = NowAsISO8601TimeUTC();
  mapping.content = html;

  OK_OR_RET(Upsert("lookups", mapping,
                   *ctx.Get<IDatabaseClient>()));

  // Return short code
  return Response(HTTP_200_OK, kContentTypeTextPlain,
                  short_code);
}

// GET /{shortcode} - Serve cached page for short code
Response GetCachedPage(Request request, AppContext& ctx) {
  GetCachedPageRequest get_cache_request;
  get_cache_request.short_code = request.path().substr(1);

  auto db = ctx.Get<IDatabaseClient>();
  QueryView<LookupMapping> query("lookups", db);
  SET_OR_RET(
      LookupMapping mapping,
      query
          .where(EQUALS(&LookupMapping::short_code,
                        get_cache_request.short_code))
          .first());

  SET_OR_RET(std::string html,
             MakeSingleFileHTML(mapping.original_url));

  return Response(HTTP_200_OK, kContentTypeTextHtml, html);
}

Result RunMyWebApp(std::vector<std::string> argv) {
  SET_OR_RET(auto config, ParseArgs<Config>(argv));

  if (config.host.empty()) {
    config.host = "0.0.0.0";
  }
  if (config.port == 0) {
    config.port = 8080;
  }

  auto app_ctx =
      ContextBuilder<AppContext>()
          .bind<DatabaseBaseUrl>()
          .with(std::string("http://database-service:8080"))
          .bind<IDatabaseClient>()
          .from([](AppContext& ctx) {
            return std::make_shared<DatabaseClientImpl>(
                ctx.Get<DatabaseBaseUrl>()->value());
          })
          .build();

  WebApp<AppContext> app(app_ctx);
  ADD_ROUTE(app, "GET", "/", GetIndexPage);
  ADD_ROUTE(app, "POST", "/save/", SaveResource);
  ADD_ROUTE(app, "GET", "/health/", GetHealth);
  ADD_ROUTE(app, "GET", "/*", GetCachedPage);

  return app.RunServer(config.host, config.port);
}

int main(int argc, char** argv) {
  return Result::Main(argc, argv, RunMyWebApp);
}
v0 (commit) © 2025 @p13i.io | Load balancer proxied to: cs-code-viewer-1:8080 in 4ms.