diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 6a84d21..0000000 --- a/.dockerignore +++ /dev/null @@ -1,13 +0,0 @@ -# Ignore build files -build/ -cmake-build-* - -# Ignore editor-specific files -*.swp -*.log -.idea/ -.vscode/ - -# Ignore version control directories -.git/ -.gitignore \ No newline at end of file diff --git a/CMakeLists.txt b/CMakeLists.txt index 91d3764..04337c1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,4 +1,5 @@ cmake_minimum_required(VERSION 3.16) +cmake_policy(SET CMP0167 OLD) project(short-link VERSION 1.0 LANGUAGES CXX) @@ -8,23 +9,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED True) set(BUILD_SHARED_LIBS OFF) include_directories(${PROJECT_SOURCE_DIR}/includes) - -add_executable(Application - src/main.cpp - src/http_connection.cpp - src/http_server.cpp - src/request_handler.cpp - src/database_connection.cpp +add_executable(Application src/main.cpp + src/http_connection.cpp + src/http_server.cpp + src/request_handler.cpp ) -add_custom_target(copy_frontend ALL - COMMAND ${CMAKE_COMMAND} -E copy_directory ${PROJECT_SOURCE_DIR}/frontend ${CMAKE_BINARY_DIR}/frontend -) - -add_dependencies(Application copy_frontend) - - -find_package(Boost REQUIRED COMPONENTS filesystem system) +find_package(Boost REQUIRED filesystem system) if(Boost_FOUND) include_directories(${Boost_INCLUDE_DIRS}) @@ -32,22 +23,4 @@ if(Boost_FOUND) target_link_libraries(Application PRIVATE Boost::filesystem Boost::system) else() message(FATAL_ERROR "Boost not found!") -endif() - -find_package(SOCI REQUIRED sqlite3) - -if(SOCI_FOUND) - include_directories(${SOCI_INCLUDE_DIRS}) - target_link_libraries(Application PRIVATE soci_core soci_sqlite3) -else() - message(FATAL_ERROR "SOCI not found!") -endif() - -find_package(SQLite3 REQUIRED) - -if(SQLite3_FOUND) - include_directories(${SQLite3_INCLUDE_DIRS}) - target_link_libraries(Application PRIVATE SQLite::SQLite3) -else() - message(FATAL_ERROR "SQLite3 not found!") -endif() +endif() \ No newline at end of file diff --git a/dockerfile b/dockerfile index 327ebbc..ffc27dd 100644 --- a/dockerfile +++ b/dockerfile @@ -3,24 +3,12 @@ FROM ubuntu:20.04 ENV APPLICATION_BINARY="Application" ENV OPEN_PORT=8080 +COPY ${APPLICATION_BINARY} . + RUN apt-get update && \ ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime && \ - DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \ - build-essential \ - cmake \ - libboost-all-dev \ - git \ - libsqlite3-dev && \ + DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends libboost-all-dev && \ rm -rf /var/lib/apt/lists/* -WORKDIR /app - -COPY . . - -RUN mkdir build && cd build && \ - cmake .. && \ - make - EXPOSE ${OPEN_PORT} - -CMD ["./build/Application"] +CMD [ "/${APPLICATION_BINARY}" ] \ No newline at end of file diff --git a/frontend/index.html b/frontend/index.html deleted file mode 100644 index 673756a..0000000 --- a/frontend/index.html +++ /dev/null @@ -1,46 +0,0 @@ - - - - - - Short-Link - - - - - - -
-

Short-Link

-
- - -
-
-
-
- - - \ No newline at end of file diff --git a/frontend/index.js b/frontend/index.js deleted file mode 100644 index da7e0ed..0000000 --- a/frontend/index.js +++ /dev/null @@ -1,43 +0,0 @@ -document.getElementById("urlInput").addEventListener("input", function() { - const urlInput = document.getElementById("urlInput"); - const shortenButton = document.getElementById("shortenButton"); - - const urlPattern = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/i - - const isValid = urlPattern.test(urlInput.value); - - if (isValid) { - urlInput.style.borderColor = "green"; - shortenButton.disabled = false; - } else { - urlInput.style.borderColor = "red"; - shortenButton.disabled = true; - } -}); - -document.getElementById("shortenButton").addEventListener("click", async function() { - const urlInput = document.getElementById("urlInput").value; - const outputContainer = document.getElementById("output-container"); - const errorContainer = document.getElementById("error-container"); - - if (urlInput) { - // const shortenedUrl = urlInput + "-short"; // only for testing - - const response = await fetch("/", { - method: "POST", - headers: { - "Content-Type": "text/plain", - }, - body: urlInput, - }); - if (response.ok){ - const shortenedUrl = await response.text(); - outputContainer.innerHTML = `

Shortened URL: ${shortenedUrl}

`; - errorContainer.innerHTML = ""; - }else{ - const error = await response.text(); - errorContainer.innerHTML = `

Something went wrong: ${error}

`; - outputContainer.innerHTML = ""; - } - } -}); diff --git a/includes/database_service.hpp b/includes/database_service.hpp index bb5cb52..4d997e3 100644 --- a/includes/database_service.hpp +++ b/includes/database_service.hpp @@ -1,35 +1,28 @@ #ifndef DATABASE_SERVICE_HPP #define DATABASE_SERVICE_HPP -#include -#include -#include +#include +#include +#include #include -#include -#include class DatabaseService { public: - static DatabaseService& getInstance(const std::string& db_path, std::size_t pool_size); - void shortenURL(const std::string& longURL, std::function callback); - void getLongURL(const std::string& shortCode, std::function callback); + static DatabaseService& getInstance(boost::asio::io_context& io_context, std::size_t pool_size); + private: - DatabaseService(const std::string& db_path, std::size_t pool_size = 4); + std::shared_ptr getConnection(); //retrive a connection from the connection pool + + DatabaseService(boost::asio::io_context& io_context, std::size_t pool_size = 4); ~DatabaseService(); DatabaseService(DatabaseService const&); void operator=(DatabaseService const&); - std::shared_ptr getConnection(); - std::string generateShortUUID(); - - std::string db_path_; + boost::asio::io_context& io_context_; std::size_t pool_size_; - std::vector> connection_pool_; - std::size_t current_index_ = 0; - - static DatabaseService* INSTANCE; - static std::mutex singleton_mutex; + std::vector> connection_pool_; + std::size_t current_index_ = 0; //index used for round robin selection }; #endif \ No newline at end of file diff --git a/includes/http_connection.hpp b/includes/http_connection.hpp index 493c11c..ab53345 100644 --- a/includes/http_connection.hpp +++ b/includes/http_connection.hpp @@ -8,7 +8,6 @@ #include #include #include -#include using namespace std; using namespace boost::asio; @@ -26,7 +25,7 @@ private: ip::tcp::socket socket_; boost::beast::flat_buffer buffer_; http::request request_; - variant, http::response> response_; + http::response response_; RequestHandler request_handler_; void read(); diff --git a/includes/log_utils.hpp b/includes/log_utils.hpp deleted file mode 100644 index 8d44567..0000000 --- a/includes/log_utils.hpp +++ /dev/null @@ -1,62 +0,0 @@ -#ifndef LOG_UTILS_HPP -#define LOG_UTILS_HPP - -#include -#include -#include -#include -#include -#include - -// Define log levels -enum class LogLevel { - INFO, - ERROR, - BADREQUEST -}; - -// Function to get a color based on file name -inline const char* getColorForFile(const std::string& file_name) { - // Define color codes - const char* colors[] = { - "\033[34m", // Blue - "\033[32m", // Green - "\033[36m", // Cyan - "\033[35m", // Magenta - "\033[33m" // Yellow - }; - - // Hash the file name to determine the color - std::hash hasher; - size_t hash = hasher(file_name); - return colors[hash % (sizeof(colors) / sizeof(colors[0]))]; -} - -// Logging function -inline void log(const LogLevel level, const std::string& message, const std::string& file_path) { - const auto now = std::chrono::system_clock::now(); - const auto time = std::chrono::system_clock::to_time_t(now); - const std::tm tm = *std::localtime(&time); - - const auto color_reset = "\033[0m"; - const auto color_error = "\033[31m"; - - // Extract file name from full path - std::string file_name = std::filesystem::path(file_path).filename().string(); - - // Determine color based on log level - const char* color = level == LogLevel::ERROR || level == LogLevel::BADREQUEST ? color_error : getColorForFile(file_name); - - // Print log - std::cout << color - << "[" << (level == LogLevel::INFO ? "INFO" : "ERROR") << "] " - << std::put_time(&tm, "%H:%M:%S") // Time only - << " [" << file_name << "] " - << message - << color_reset << std::endl; -} - -// Helper macro to automatically pass the file name -#define LOG(level, message) log(level, message, __FILE__) - -#endif // LOG_UTILS_HPP diff --git a/includes/request_handler.hpp b/includes/request_handler.hpp index 5382f6f..2d14176 100644 --- a/includes/request_handler.hpp +++ b/includes/request_handler.hpp @@ -4,20 +4,14 @@ #include #include #include -#include using namespace boost::beast; class RequestHandler { public: -std::variant< - http::response, - http::response - > handle(const http::request& request); + http::response handle(const http::request& request); private: - http::response BadRequest(const std::string& why); - http::response handle_file_request(const std::string& filename, boost::system::error_code& ec); }; #endif \ No newline at end of file diff --git a/src/database_connection.cpp b/src/database_connection.cpp index 98f2db5..194bf2b 100644 --- a/src/database_connection.cpp +++ b/src/database_connection.cpp @@ -1,111 +1,16 @@ #include "../includes/database_service.hpp" -#include "../includes/log_utils.hpp" -#include -#include -#include -#include -#include -#include -#include +#include -std::mutex DatabaseService::singleton_mutex; -DatabaseService* DatabaseService::INSTANCE = nullptr; -DatabaseService& DatabaseService::getInstance(const std::string& db_path, std::size_t pool_size) { - - std::lock_guard lock(singleton_mutex); - if (!INSTANCE) { - INSTANCE = new DatabaseService(db_path, pool_size); - } - return *INSTANCE; +DatabaseService& DatabaseService::getInstance(boost::asio::io_context &io_context, std::size_t pool_size) { + static DatabaseService INSTANCE; + return INSTANCE; } -DatabaseService::DatabaseService(const std::string& db_path, std::size_t pool_size) - : db_path_(db_path), pool_size_(pool_size), current_index_(0) { - LOG(LogLevel::INFO, "Initializing DatabaseService"); +DatabaseService::DatabaseService(boost::asio::io_context& io_context, std::size_t pool_size) : io_context_(io_context), pool_size_(pool_size) { - if (pool_size_ <= 0) { - LOG(LogLevel::ERROR, "Invalid pool size"); - throw std::invalid_argument("Connection pool size must be greater than zero."); - } - - for (std::size_t i = 0; i < pool_size_; ++i) { - auto session = std::make_shared(soci::sqlite3, db_path_); - connection_pool_.emplace_back(session); - } - LOG(LogLevel::INFO, "Connection Pool filled"); - try { - auto conn = getConnection(); - *conn << "CREATE TABLE IF NOT EXISTS urls (" - "short_code CHAR(8) PRIMARY KEY, " - "original_url TEXT NOT NULL UNIQUE, " - "created_at INTEGER DEFAULT CURRENT_TIMESTAMP);"; - - LOG(LogLevel::INFO, "Database schema initialized successfully"); - } catch (const std::exception& e) { - LOG(LogLevel::ERROR, "Exception during schema creation: " + std::string(e.what())); - throw; - } } DatabaseService::~DatabaseService() { - connection_pool_.clear(); -} -std::shared_ptr DatabaseService::getConnection() { - if (connection_pool_.empty()) { - throw std::runtime_error("Connection pool is empty."); - } - - LOG(LogLevel::INFO, "Returning connection at index: " + std::to_string(current_index_)); - auto conn = connection_pool_[current_index_]; - current_index_ = (current_index_ + 1) % connection_pool_.size(); - return conn; -} - -std::string DatabaseService::generateShortUUID() { - boost::uuids::uuid uuid = boost::uuids::random_generator()(); - std::string uuidStr = to_string(uuid); - return uuidStr.substr(0, 8); -} - -void DatabaseService::shortenURL(const std::string& longURL, std::function callback) { - LOG(LogLevel::INFO, "Shortening URL: " + longURL); - try { - auto conn = getConnection(); - std::string shortCode; - *conn << "SELECT short_code FROM urls WHERE original_url = :long_url", soci::use(longURL), soci::into(shortCode); - - if (!shortCode.empty()) { - callback({}, shortCode); - return; - } - - shortCode = generateShortUUID(); - *conn << "INSERT INTO urls (short_code, original_url) VALUES (:short_code, :long_url)", - soci::use(shortCode), soci::use(longURL); - callback({}, shortCode); - } catch (const std::exception& e) { - LOG(LogLevel::ERROR, "Failed to shorten URL: " + std::string(e.what())); - callback(std::make_error_code(std::errc::io_error), ""); - } -} - -void DatabaseService::getLongURL(const std::string& shortCode, std::function callback) { - LOG(LogLevel::INFO, "Getting long-URL from short-code: " + shortCode); - try { - auto conn = getConnection(); - std::string longURL; - *conn << "SELECT original_url FROM urls WHERE short_code = :short_code", soci::use(shortCode), soci::into(longURL); - - if (!longURL.empty()) { - callback({}, longURL); - } else { - LOG(LogLevel::ERROR, "Short code not found: " + shortCode); - callback(std::make_error_code(std::errc::no_such_file_or_directory), ""); - } - } catch (const std::exception& e) { - LOG(LogLevel::ERROR, "Failed to retrieve long URL: " + std::string(e.what())); - callback(std::make_error_code(std::errc::io_error), ""); - } } \ No newline at end of file diff --git a/src/http_connection.cpp b/src/http_connection.cpp index 949cd52..7741622 100644 --- a/src/http_connection.cpp +++ b/src/http_connection.cpp @@ -1,8 +1,9 @@ #include "../includes/http_connection.hpp" -#include "../includes/log_utils.hpp" +#include #include #include #include +#include HttpConnection::HttpConnection(io_context& io_context) : socket_(io_context) {}; @@ -21,35 +22,23 @@ void HttpConnection::process_connection() { void HttpConnection::read() { auto self = shared_from_this(); - http::async_read(socket_, buffer_, request_, [self](boost::beast::error_code error_code, size_t bytes_transferred) { if (!error_code) { - LOG(LogLevel::INFO, "Request details:"); - LOG(LogLevel::INFO, "\tMethod: " + std::string(self->request_.method_string())); - LOG(LogLevel::INFO, "\tTarget: " + std::string(self->request_.target())); - LOG(LogLevel::INFO, "\tBody: " + std::string(self->request_.body())); - - // Log before forwarding to RequestHandler - LOG(LogLevel::INFO, "Forwarding request to RequestHandler..."); + cout << "Request received:\n" << self->request_ << endl; self->response_ = self->request_handler_.handle(self->request_); - self->write(); } else { - LOG(LogLevel::ERROR, "Error reading request: " + error_code.message()); + cerr << "Error reading: " << error_code.message() << endl; } }); } - - - void HttpConnection::write() { auto self = shared_from_this(); - std::visit( - [this, self](auto& response) { - http::async_write(socket_, response, + http::async_write(socket_, response_, [self](boost::beast::error_code error_code, size_t bytes_transferred) { + cout << "Sending response:\n" << self->response_ << endl; if (!error_code) { auto error_code_socket = self->socket_.shutdown(ip::tcp::socket::shutdown_send, error_code); if (error_code_socket) { @@ -63,6 +52,4 @@ void HttpConnection::write() { } } }); - - }, response_); } \ No newline at end of file diff --git a/src/http_server.cpp b/src/http_server.cpp index 8f5c1d5..60b18c0 100644 --- a/src/http_server.cpp +++ b/src/http_server.cpp @@ -1,8 +1,8 @@ #include "../includes/http_server.hpp" -#include "../includes/log_utils.hpp" -#include #include "http_connection.hpp" +#include #include +#include HttpServer::HttpServer(io_context& io_context, const ip::tcp::endpoint& endpoint) : io_context_(io_context), acceptor_(io_context, endpoint) { @@ -11,18 +11,14 @@ HttpServer::HttpServer(io_context& io_context, const ip::tcp::endpoint& endpoint void HttpServer::accept_connection() { HttpConnection::pointer new_connection = HttpConnection::create(io_context_); - - LOG(LogLevel::INFO, "Waiting for new connection"); - acceptor_.async_accept(new_connection->socket(), - bind(&HttpServer::handle_accept, this, new_connection, boost::asio::placeholders::error)); + acceptor_.async_accept(new_connection->socket(), + bind(&HttpServer::handle_accept, this, new_connection, boost::asio::placeholders::error) + ); } void HttpServer::handle_accept(HttpConnection::pointer& new_connection, const boost::system::error_code& error_code) { if (!error_code) { - LOG(LogLevel::INFO, "Accepted new connection from: " + new_connection->socket().remote_endpoint().address().to_string()); new_connection->process_connection(); - } else { - LOG(LogLevel::ERROR, "Error accepting connection: " + error_code.message()); } accept_connection(); -} +} \ No newline at end of file diff --git a/src/main.cpp b/src/main.cpp index 8b57e85..f7624fa 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,26 +1,24 @@ -#include "../includes/http_server.hpp" -#include "../includes/log_utils.hpp" -#include -#include +#include "../includes/http_server.hpp" +#include using namespace boost::asio; -int main(int argc, char* argv[]) { +int main(int argc, char *argv[]) { const int port = 8080; - LOG(LogLevel::INFO, "Starting Server on 127.0.0.1:" + std::to_string(port)); - - try { + std::cout << "Starting server!" << std::endl; + try + { io_context io_context; ip::tcp::endpoint endpoint(ip::tcp::v4(), port); - HttpServer server(io_context, endpoint); - io_context.run(); - } catch (const std::exception& e) { - LOG(LogLevel::ERROR, "Exception occurred: " + std::string(e.what())); + } + catch (std::exception& e) + { + std::cerr << e.what() << std::endl; } return 0; -} +} \ No newline at end of file diff --git a/src/request_handler.cpp b/src/request_handler.cpp index 2116be2..f137959 100644 --- a/src/request_handler.cpp +++ b/src/request_handler.cpp @@ -1,173 +1,82 @@ #include "../includes/request_handler.hpp" -#include "../includes/database_service.hpp" -#include "../includes/log_utils.hpp" #include #include #include #include #include #include -#include -#include -http::response RequestHandler::BadRequest(const std::string& why) { - LOG(LogLevel::BADREQUEST, "BadRequest: " + why); +http::response BadRequest(const std::string& why) { http::response response; response.result(http::status::bad_request); response.set(http::field::server, "Beast"); response.set(http::field::content_type, "text/html"); response.body() = why; - response.keep_alive(false); response.prepare_payload(); return response; } -std::variant< - http::response, - http::response -> RequestHandler::handle(const http::request& request) { - std::string target = request.target(); +http::response RequestHandler::handle(const http::request& request) { + string_view target = request.target(); http::verb method = request.method(); - - auto& dbService = DatabaseService::getInstance("urls.db", 4); - - LOG(LogLevel::INFO, "Received request:"); - LOG(LogLevel::INFO, "\tMethod: " + std::string(request.method_string())); - LOG(LogLevel::INFO, "\tTarget: " + target); - LOG(LogLevel::INFO, "\tBody: " + request.body()); - - if (method == http::verb::post) { - if (target != "/") { - return BadRequest("Cannot post to anything other than /"); - } - if (request.find(http::field::content_type) == request.end()) { - return BadRequest("Content-Type header is required for POST requests"); - } - auto content_type = request[http::field::content_type]; - if (content_type != "text/plain") { - return BadRequest("Content-Type must be text/plain"); - } - - std::string url = request.body(); - std::regex url_regex("^(https?://)?(?:www\\.)?[-a-zA-Z0-9@%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&/=]*)$"); - std::smatch url_match; - if (!std::regex_match(url, url_match, url_regex)) { - return BadRequest("Invalid URL"); - } - if (!url_match[1].matched) { - url = "https://" + url; - } - - LOG(LogLevel::INFO, "Valid URL: " + url); - - std::promise promise; - std::future future = promise.get_future(); - dbService.shortenURL(url, [&promise](std::error_code ec, const std::string& shortURL) { - if (!ec) { - promise.set_value(shortURL); - } else { - promise.set_exception(std::make_exception_ptr( - std::runtime_error(ec.message()) - )); - } - }); - - try { - std::string shortURL = future.get(); - LOG(LogLevel::INFO, "Shortened URL generated: " + shortURL); + if (target == "/") { + if(method == http::verb::get) { + //case 1: "/" -> serve angular frontend or static frontend what ever http::response response; - response.result(http::status::created); - response.set(http::field::server, "Beast"); - response.set(http::field::content_type, "text/plain"); - response.body() = "127.0.0.1:8080/" + shortURL; - response.keep_alive(false); - response.prepare_payload(); - return response; - } catch (const std::exception& e) { - return BadRequest("Error generating short URL: " + std::string(e.what())); - } - - } - if (method == http::verb::get) { - if (target == "/") { - LOG(LogLevel::INFO, "Serving the index.html file for target /"); - target = "/index.html"; - } - if (target == "/index.html" || target == "/index.js") { - error_code ec; - http::response response = handle_file_request("frontend" + target, ec); - if (ec) { - LOG(LogLevel::ERROR, "Failed to read file: frontend" + target); - return BadRequest("Error reading file"); - } else { - LOG(LogLevel::INFO, "Served file: frontend" + target); - return response; - } - } - - std::regex regex_pattern(".*/([^/]+)$"); - std::smatch match; - std::string shortCode; - - if (std::regex_match(target, match, regex_pattern)) { - shortCode = match[1]; - LOG(LogLevel::INFO, "Extracted short code using regex: " + shortCode); - } else { - LOG(LogLevel::ERROR, "Failed to extract short code from target: " + target); - return BadRequest("Invalid short URL format"); - } - - std::promise promise; - std::future future = promise.get_future(); - - dbService.getLongURL(shortCode, [&promise](std::error_code ec, const std::string& longURL) { - if (!ec) { - promise.set_value(longURL); - } else { - promise.set_exception(std::make_exception_ptr( - std::runtime_error(ec.message()) - )); - } - }); - - try { - std::string expandedURL = future.get(); - LOG(LogLevel::INFO, "Expanded URL: " + expandedURL); - http::response response; - response.result(http::status::moved_permanently); - response.set(http::field::location, expandedURL); + response.result(http::status::ok); response.version(request.version()); response.set(http::field::server, "Beast"); - response.body() = "Redirecting to " + expandedURL; - response.keep_alive(false); + response.set(http::field::content_type, "text/html"); + + //todo: load angular application / plain html & js + response.body() = "

TEST

"; + response.prepare_payload(); return response; - } catch (const std::exception& e) { - LOG(LogLevel::ERROR, "Failed to expand short URL: " + shortCode + ". Error: " + std::string(e.what())); - return BadRequest("Short URL not found or error: " + std::string(e.what())); + } + else if (method == http::verb::post) { + if(request.find(http::field::content_type) == request.end()) { + return BadRequest("Content-Type header is required for POST requests"); + } + auto content_type = request[http::field::content_type]; + if(content_type != "text/plain") { + return BadRequest("Content-Type must be text/plain"); + } + std::string url = request.body(); + std::regex url_regex("^(https?://)?(?:www\\.)?[-a-zA-Z0-9@%._\\+~#=]{1,256}\\.[a-zA-Z0-9()]{1,6}\\b(?:[-a-zA-Z0-9()@:%_\\+.~#?&/=]*)$"); + std::smatch url_match; + if(!std::regex_match(url, url_match, url_regex)) { + return BadRequest("Invalid URL"); + } + if(!url_match[1].matched){ + url = "https://" + url; + } + + //todo: save url to database and return short url + + return BadRequest("Request is actually not bad. processing " + url); + } + }else { + if(method == http::verb::get){ + http::response response; + std::string short_url = target.substr(1); + + std::string expanded_url = "https://google.com"; //todo: get expanded url from database + + response.result(http::status::moved_permanently); + response.set(http::field::location, expanded_url); + response.version(request.version()); + response.set(http::field::server, "Beast"); + response.body() = "Redirecting to " + expanded_url; + response.prepare_payload(); + return response; + } else { + return BadRequest("Method not allowed"); } } + + //case 2: "/url" -> redirect to expanded url + //case 3: neither -> redirect to 404 return BadRequest("No rule matched."); -} - -http::response RequestHandler::handle_file_request(const std::string& path, error_code& ec) { - http::file_body::value_type file; - file.open(path.c_str(), file_mode::read, ec); - if (!ec) { - LOG(LogLevel::INFO, "Successfully opened file: " + path); - } else { - LOG(LogLevel::ERROR, "Failed to open file: " + path + ". Error: " + ec.message()); - } - - http::response response; - if (!ec) { - response.result(http::status::ok); - response.set(http::field::server, "Beast"); - response.set(http::field::content_type, "text/html"); - response.body() = std::move(file); - response.keep_alive(false); - response.prepare_payload(); - } - return response; } \ No newline at end of file