ADD: added first version of redmine api client

This commit is contained in:
Dominik Meyer 2024-02-18 13:14:20 +01:00
parent 703d952ad3
commit 4adbf858a9
Signed by: byterazor
GPG Key ID: EABDA0FD5981BC97
2 changed files with 257 additions and 0 deletions

107
include/Redmine/API.hpp Normal file
View File

@ -0,0 +1,107 @@
#pragma once
/*
* Copyright (C) 2024 Dominik Meyer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nlohmann/json_fwd.hpp"
#include <httplib.h>
#include <memory>
#include <string>
#include <nlohmann/json.hpp>
#include <Redmine/User.hpp>
/**
* @brief The main namespace of this library for Redmine related datatypes, classes, and functions
*
*/
namespace Redmine
{
/**
* @brief Client side implementation of the redmine API
*
* This implementation of the redmine API always uses the json rest api.
*
*/
class API
{
private:
/// the URL to the redmine server api
std::string redmineApiURL_;
/// the token to authenticate against the server in redmine called API Key
std::string authToken_;
nlohmann::json get(const std::string &path) const;
void put(const std::string &path, const nlohmann::json &data) const;
public:
/**
* @name Constructors
*/
/*@{*/
/**
* @brief Only Constructor of the Readmine::API t
*
* Only this constructor exists to ensure url and token are available
*
* @param serverURL - full server URL (e.g. https://redmine.de)
* @param authToken - authToken taken from the redmine instance to access the API
*/
API(const std::string &serverURL, const std::string &authToken);
/*@}*/
/**
* @name General Methods independent of API
*/
/*@{*/
/**
* @brief verify that the API is accessible with the provided URL and authToken
*
* @return true - everything is fine and API is accessible
* @return false - something went wrong
*/
bool ready() const;
/*@}*/
/**
* @name MyAccount-API
*/
/*@{*/
/**
* @brief Return the user information of the owner of the used auth token
*
* @return Redmine::User
*/
Redmine::User getMyAccount() const;
/**
* @brief Set the user information of the owner of the used auth token
*
* @param user - the used to set with all required information set
*/
void setMyAccount(const Redmine::User &user) const;
/*@}*/
};
}

150
src/Redmine/API.cpp Normal file
View File

@ -0,0 +1,150 @@
/*
* Copyright (C) 2024 Dominik Meyer
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "nlohmann/json_fwd.hpp"
#include <Redmine/API.hpp>
#include <Redmine/Exception/Api.hpp>
#include <exception>
#include <memory>
#include <stdexcept>
#include <vector>
#define LOGURU_WITH_STREAMS 1
#include <loguru.hpp>
#include <httplib.h>
Redmine::API::API(const std::string &serverURL, const std::string &authToken)
{
if (serverURL.empty() || serverURL.size() < 7)
{
DLOG_S(ERROR) << "server URL not valid";
throw std::invalid_argument("server url invalid");
}
if (!serverURL.starts_with("https://"))
{
DLOG_S(ERROR) << "server URL not starting with https://";
throw std::invalid_argument("server URL does not start with https://");
}
redmineApiURL_=serverURL;
if (authToken.empty() || authToken.length() < 10)
{
DLOG_S(ERROR) << "auth token not valid";
throw std::invalid_argument("auth token is not valid");
}
authToken_=authToken;
}
nlohmann::json Redmine::API::get(const std::string &path) const
{
DLOG_S(INFO) << "getting API endpoint " << path+".json" << " from " <<redmineApiURL_;
httplib::Client client{redmineApiURL_};
httplib::Headers headers =
{
{ "X-Redmine-API-Key", authToken_ },
{ "Content-Type", "application/json"}
};
auto res = client.Get(path + ".json" , headers);
if (res->status == httplib::StatusCode::Unauthorized_401)
{
DLOG_S(ERROR) << "authentication with API server failed (401)";
throw std::runtime_error("authentication failed");
}
else if (res->status == httplib::StatusCode::NotFound_404)
{
DLOG_S(ERROR) << "unknown API endpoint (404)";
throw std::runtime_error("unknown API endpoint");
}
else if (res->status != httplib::StatusCode::OK_200)
{
LOG_S(ERROR) << "unsupported error " << httplib::status_message(res->status);
throw std::runtime_error("unsupported error");
}
DLOG_S(INFO) << "get successful";
return nlohmann::json::parse(res->body);
}
void Redmine::API::put(const std::string &path, const nlohmann::json &data) const
{
DLOG_S(INFO) << "putting API endpoint " << path+".json" << " from " <<redmineApiURL_;
httplib::Client client{redmineApiURL_};
client.set_basic_auth(authToken_, "");
auto res = client.Put(path + ".json", data.dump(),"application/json");
if (res->status == httplib::StatusCode::Unauthorized_401)
{
DLOG_S(ERROR) << "authentication with API server failed (401)";
throw std::runtime_error("authentication failed");
}
else if (res->status == httplib::StatusCode::NotFound_404)
{
DLOG_S(ERROR) << "unknown API endpoint (404)";
throw std::runtime_error("unknown API endpoint");
}
else if (res->status == httplib::StatusCode::UnprocessableContent_422)
{
nlohmann::json error=nlohmann::json::parse(res->body);
std::vector<std::string> errors=error["errors"].get<std::vector<std::string>>();
throw Redmine::Exception::Api("Unprocessible Content",errors);
}
else if (res->status != httplib::StatusCode::OK_200 && res->status != httplib::StatusCode::NoContent_204)
{
LOG_S(ERROR) << "unsupported error " << httplib::status_message(res->status);
LOG_S(ERROR) << res->body;
throw std::runtime_error("unsupported error");
}
DLOG_S(INFO) << "put successful";
}
Redmine::User Redmine::API::getMyAccount() const
{
nlohmann::json userInfo = get("/my/account");
return Redmine::User{userInfo};
}
void Redmine::API::setMyAccount(const Redmine::User &user) const
{
put("/my/account", user.get());
}
bool Redmine::API::ready() const
{
try {
getMyAccount();
} catch (std::exception &e)
{
return false;
}
return true;
}