Compare commits

...

2 Commits

Author SHA1 Message Date
7f269901c5
feat: some last changes
All checks were successful
continuous-integration/drone/push Build is passing
2024-10-09 15:20:32 +02:00
3e508c344f
refactor: do not automatically extend fetch path 2024-08-11 16:46:53 +02:00
15 changed files with 585 additions and 26 deletions

View File

@ -85,6 +85,12 @@ SET(REDMINE_API_SOURCES
include/Redmine/API.hpp
src/Redmine/API.cpp
include/Redmine/Filter.hpp
src/Redmine/Filter.cpp
include/Redmine/Issue.hpp
src/Redmine/Issue.cpp
)
add_library(redmine-api-cpp-objlib OBJECT ${REDMINE_API_SOURCES})
@ -129,6 +135,8 @@ add_executable(redmine-cli
src/Redmine-CLI/Command/Project.cpp
src/Redmine-CLI/Command/IssueStatus.hpp
src/Redmine-CLI/Command/IssueStatus.cpp
src/Redmine-CLI/Command/Issue.hpp
src/Redmine-CLI/Command/Issue.cpp
)
target_link_libraries(redmine-cli redmine-api-cpp-static loguru CLI11 tableprinter::tableprinter)
target_include_directories(redmine-cli

View File

@ -16,6 +16,8 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
#include "Redmine/Filter.hpp"
#include "Redmine/Issue.hpp"
#include "Redmine/IssueStatus.hpp"
#include "nlohmann/json_fwd.hpp"
#include <cstdint>
@ -127,9 +129,19 @@ namespace Redmine
*
* @return std::vector<Redmine::Project>
*/
std::vector<Redmine::Project> getProjects() const;
std::vector<Redmine::Project> getProjects(const std::uint32_t limit) const;
void uploadFileToProject(const std::uint64_t projectId, const std::string &filePath, const std::string &fileName, const std::string &description, const std::uint32_t version) const;
/**
* @brief Get issues with the given filters from the redmine server
*
* @param filter - the filter to apply on the issues
* @return std::vector<Redmine::Issue>
*/
std::vector<Redmine::Issue> getIssues(const Redmine::Filter& filter = {}) const;
/*@}*/
};

View File

@ -0,0 +1,64 @@
#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/>.
*/
/**
* @brief The main namespace of this library for Redmine related datatypes, classes, and functions
*
*/
#include <cstdint>
#include <string>
#include <vector>
namespace Redmine
{
/**
* @brief Class for filering data in a Redmine API query
*
*/
class Filter
{
private:
/// filter the issued by project id
bool filterByProjectID_;
/// the project ID to filter
std::uint64_t projectID_;
/// possible project stati for a filter
enum class ProjectStatus {open, closed, both};
/// the project status to filter by
ProjectStatus projectStatus_;
/// whether to filter by assigned user or not
bool filterByAssignedUser_;
/// the user ID to filter by
std::uint64_t assignedUserID_;
public:
Filter();
void filterByProjectID(const std::uint64_t id);
void setProjectStatus(const ProjectStatus& status);
void filterByAssignedUserId(const std::uint64_t id);
std::string toQueryString() const;
};
}; // namespace Redmine

View File

@ -63,10 +63,20 @@ namespace Redmine
*/
nlohmann::json get() const;
/**
* @brief return the id of the issue
*
* @return std::uint64_t
*/
std::uint64_t getId() const;
std::string getProject() const;
std::uint64_t getProjectId() const;
std::string getStatus() const;
std::uint64_t getStatusID() const;
std::string getSubject() const;
std::string getDescription() const;
std::string to_string() const;
}; // class Issue

View File

@ -0,0 +1,79 @@
#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 <cstdint>
#include <nlohmann/json.hpp>
#include <Redmine/Object.hpp>
#include <string>
/**
* @brief The main namespace of this library for Redmine related datatypes, classes, and functions
*
*/
namespace Redmine
{
/**
* @brief This class represents an Issue Tracker within the redmine server.
*
*/
class Tracker : public Redmine::Object
{
private:
/// the data store for the issue
nlohmann::json data_;
/**
* @brief verify issue data
*
* @param data - the json object containing the issue data
*/
void _verify(const nlohmann::json &data);
public:
explicit Tracker(const nlohmann::json &issue);
/**
* @brief set the issue object from a json object
*
* @param data
*/
void set(const nlohmann::json &data);
/**
* @brief return a json object from the issue object
*
* @return nlohmann::json
*/
nlohmann::json get() const;
/**
* @brief Get the Id of the tracker
*
* @return std::uint64_t
*/
std::uint64_t getId() const;
}; // class Issue
}; // namespace Redmine

View File

@ -0,0 +1,116 @@
/*
* 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 <Redmine-CLI/Command/Issue.hpp>
#include <Redmine-CLI/Redmine.hpp>
#include "Redmine/API.hpp"
#include "Redmine/Filter.hpp"
#include "nlohmann/json_fwd.hpp"
#include <tableprinter/tableprinter.hpp>
Command::Issue::Issue(RedmineCLI::Redmine *r)
:
redmine_(r),
output_(TEXT),
projectId_(-1),
userId_(-1)
{
CLI::App* issue = redmine_->getCLI().add_subcommand("issue",
"Api Calls on issues accessible by token owner")->require_subcommand();
list_ = issue->add_subcommand("list", "List issues accessible by the token owner");
list_->needs(redmine_->getTokenOption())->needs(redmine_->getUrlOption());
list_->callback([&](){getList_();});
list_->add_option("-o,--output", output_, "The Output Format to use. Supported: TEXT, YAML")->transform(CLI::CheckedTransformer(outputMap, CLI::ignore_case));
list_->add_option("-p,--project",projectId_,"Filter issues by project id");
list_->add_option("--userid", userId_,"Filter issues by user id");
}
void Command::Issue::printIssueListText_(const std::vector<Redmine::Issue> &issues) const
{
tableprinter::printer p
{
{
{ tableprinter::name { "ID" } , tableprinter::width { 8 } } ,
{ tableprinter::name { "Subject" } , tableprinter::width { 80 } } ,
{ tableprinter::name { "Project" } , tableprinter::width { 25 } } ,
{tableprinter::name { "Status" }, tableprinter::width { 15 }}
} ,
{ std::cout }
};
p.sanity_check();
p.print_headers();
for (auto it = issues.begin(); it!= issues.end(); ++it)
{
p.print(it->getId(), it->getSubject(), it->getProject(), it->getStatus());
}
}
void Command::Issue::printIssueListJson_(const std::vector<Redmine::Issue> &issues) const
{
nlohmann::json data = nlohmann::json::array();
for (auto it = issues.begin(); it!= issues.end(); ++it)
{
nlohmann::json issue;
issue["id"] = it->getId();
issue["subject"] = it->getSubject();
issue["project"] = it->getProject();
issue["project_id"] = it->getProjectId();
issue["status"] = it->getStatus();
issue["status_id"] = it->getStatusID();
data.push_back(issue);
}
std::cout << data.dump(2);
}
void Command::Issue::getList_() const
{
Redmine::API client{redmine_->getUrl(), redmine_->getToken()};
Redmine::Filter filter{};
if (projectId_ > -1)
{
filter.filterByProjectID(projectId_);
}
if (userId_ > -1)
{
filter.filterByAssignedUserId(userId_);
}
auto issues = client.getIssues(filter);
if (output_ == TEXT)
{
printIssueListText_(issues);
}
else if (output_ == JSON)
{
printIssueListJson_(issues);
}
return;
}

View File

@ -0,0 +1,99 @@
#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 "Redmine/Filter.hpp"
#include "Redmine/Project.hpp"
#include "nlohmann/json_fwd.hpp"
#include <CLI/App.hpp>
#include <Redmine/API.hpp>
#include <atomic>
#include <cstdint>
#include <memory>
#include <string>
#include <vector>
#define LOGURU_WITH_STREAMS 1
#include <loguru.hpp>
#include <CLI/CLI.hpp>
// forward declaration
namespace RedmineCLI
{
class Redmine;
};
namespace Command
{
/**
* @brief class for processing the CLI11 parsed command lines
*
*/
class Issue
{
private:
/// the main redmine cli application context
RedmineCLI::Redmine *redmine_;
/// enum for identifying supported output types
enum outputType : std::uint16_t
{
/// output is just text
TEXT,
/// output is json
JSON
};
/// requested output type
outputType output_;
std::map<std::string, outputType> outputMap{{"text", TEXT}, {"json", JSON}};
/// vector to hold requested attribute fields
std::vector<std::string> fields_;
/// the project id to work on
std::int64_t projectId_;
/// the userid to filter on
std::uint64_t userId_;
/// the status to filter on
std::string status_;
/// pointer to the list subcommand
CLI::App* list_;
void printIssueListJson_(const std::vector<Redmine::Issue> &issues) const;
void printIssueListText_(const std::vector<Redmine::Issue> &issues) const;
/// the actual implementation of the list command
void getList_() const;
public:
/**
* @brief Construct a new CLIProcessor object
*
*/
explicit Issue(RedmineCLI::Redmine *r);
}; // class Issue
}; // namespace Command

View File

@ -19,12 +19,14 @@
#include <Redmine-CLI/Redmine.hpp>
#include "Redmine/API.hpp"
#include "nlohmann/json_fwd.hpp"
#include <string>
#include <tableprinter/tableprinter.hpp>
Command::Project::Project(RedmineCLI::Redmine *r)
:
redmine_(r),
output_(TEXT)
output_(TEXT),
limit_(100)
{
CLI::App* project = redmine_->getCLI().add_subcommand("project",
@ -36,6 +38,7 @@ output_(TEXT)
list_->callback([&](){getList_();});
list_->add_option("-f,--fields", fields_, "The fields of the project object to print. Supported: name, id");
list_->add_option("-o,--output", output_, "The Output Format to use. Supported: TEXT, YAML")->transform(CLI::CheckedTransformer(outputMap, CLI::ignore_case));
list_->add_option("-l,--limit",limit_,"The maximum number of projects to return. (Default: " + std::to_string(limit_) +" )");
upload_ = project->add_subcommand("upload", "Upload a File to the Project");
@ -81,8 +84,8 @@ void Command::Project::printProjectListText_(const std::vector<Redmine::Project>
{
{
{ tableprinter::name { "ID" } , tableprinter::width { 8 } } ,
{ tableprinter::name { "Identifier" } , tableprinter::width { 20 } } ,
{ tableprinter::name { "Name" } , tableprinter::width { 30 } } ,
{ tableprinter::name { "Identifier" } , tableprinter::width { 30 } } ,
{ tableprinter::name { "Name" } , tableprinter::width { 40 } } ,
} ,
{ std::cout }
};
@ -145,9 +148,9 @@ void Command::Project::getList_() const
{
Redmine::API client{redmine_->getUrl(), redmine_->getToken()};
auto projects = client.getProjects();
auto projects = client.getProjects(limit_);
printProjectList_(projects);
return;
}
}

View File

@ -82,12 +82,19 @@ namespace Command
/// the version of the uploading files
std::uint32_t fileVersion_;
/// the maximum number of projects to list
std::uint32_t limit_;
/// pointer to the list subcommand
CLI::App* list_;
/// pointer to the upload subcommand
CLI::App* upload_;
/// point to the list issues subcommand
CLI::App* issuesList_;
/**
* @brief method to print the project list
*

View File

@ -40,5 +40,5 @@ version_(nullptr)
myAccount_ = std::make_unique<Command::MyAccount>(this);
project_ = std::make_unique<Command::Project>(this);
issueStatus_ = std::make_unique<Command::IssueStatus>(this);
issue_ = std::make_unique<Command::Issue>(this);
}

View File

@ -15,6 +15,7 @@
* 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 "Redmine-CLI/Command/Issue.hpp"
#include "Redmine-CLI/Command/IssueStatus.hpp"
#include "Redmine-CLI/Command/MyAccount.hpp"
#include "Redmine-CLI/Command/Project.hpp"
@ -63,6 +64,10 @@ namespace RedmineCLI
/// pointer to the project subcommand
std::unique_ptr<Command::Project> project_;
/// pointer to the issue subcommand
std::unique_ptr<Command::Issue> issue_;
public:
/**

View File

@ -14,13 +14,16 @@
* 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 "Redmine/Issue.hpp"
#include "Redmine/IssueStatus.hpp"
#include "Redmine/Project.hpp"
#include "nlohmann/json_fwd.hpp"
#include <Redmine/API.hpp>
#include <Redmine/Exception/Api.hpp>
#include <cstdint>
#include <exception>
#include <filesystem>
#include <iterator>
#include <sstream>
#include <stdexcept>
#include <vector>
@ -98,7 +101,7 @@ void Redmine::API::processGenericErrors_(httplib::Result &res) const
nlohmann::json Redmine::API::get(const std::string &path) const
{
DLOG_S(INFO) << "getting API endpoint " << path+".json" << " from " <<redmineApiURL_;
DLOG_S(INFO) << "getting API endpoint " << path << " from " <<redmineApiURL_;
httplib::Client client{redmineApiURL_};
httplib::Headers headers =
@ -107,7 +110,7 @@ void Redmine::API::processGenericErrors_(httplib::Result &res) const
{ "Content-Type", "application/json"}
};
auto res = client.Get(path + ".json" , headers);
auto res = client.Get(path , headers);
processGenericErrors_(res);
@ -118,11 +121,11 @@ void Redmine::API::processGenericErrors_(httplib::Result &res) const
void Redmine::API::put(const std::string &path, const nlohmann::json &data) const
{
DLOG_S(INFO) << "putting API endpoint " << path+".json" << " from " <<redmineApiURL_;
DLOG_S(INFO) << "putting API endpoint " << path << " from " <<redmineApiURL_;
httplib::Client client{redmineApiURL_};
client.set_basic_auth(authToken_, "");
auto res = client.Put(path + ".json", data.dump(),"application/json");
auto res = client.Put(path, data.dump(),"application/json");
processGenericErrors_(res);
@ -131,11 +134,11 @@ void Redmine::API::put(const std::string &path, const nlohmann::json &data) con
void Redmine::API::post(const std::string &path, const nlohmann::json &data) const
{
DLOG_S(INFO) << "posting to API endpoint " << path+".json" << " at " <<redmineApiURL_;
DLOG_S(INFO) << "posting to API endpoint " << path << " at " <<redmineApiURL_;
httplib::Client client{redmineApiURL_};
client.set_basic_auth(authToken_, "");
auto res = client.Post(path + ".json", data.dump(),"application/json");
auto res = client.Post(path, data.dump(),"application/json");
processGenericErrors_(res);
@ -173,7 +176,7 @@ std::string Redmine::API::upload_(const std::filesystem::path &file, const std::
Redmine::User Redmine::API::getMyAccount() const
{
nlohmann::json userInfo = get("/my/account");
nlohmann::json userInfo = get("/my/account.json");
return Redmine::User{userInfo};
}
@ -181,13 +184,13 @@ Redmine::User Redmine::API::getMyAccount() const
void Redmine::API::setMyAccount(const Redmine::User &user) const
{
put("/my/account", user.get());
put("/my/account.json", user.get());
}
std::vector<Redmine::Project> Redmine::API::getProjects() const
std::vector<Redmine::Project> Redmine::API::getProjects(const std::uint32_t limit) const
{
nlohmann::json projectList = get("/projects");
nlohmann::json projectList = get("/projects.json?limit="+std::to_string(limit));
std::vector<Redmine::Project> projects;
for (auto it = projectList["projects"].begin(); it != projectList["projects"].end(); ++it)
@ -202,7 +205,7 @@ std::vector<Redmine::Project> Redmine::API::getProjects() const
std::vector<Redmine::IssueStatus> Redmine::API::getIssueStatusList()
{
nlohmann::json statusList = get("/issue_statuses");
nlohmann::json statusList = get("/issue_statuses.json");
issueStatusList_.clear();
@ -241,5 +244,18 @@ void Redmine::API::uploadFileToProject(const std::uint64_t projectId, const std:
nlohmann::json upload;
upload["file"] = file;
post("/projects/"+std::to_string(projectId)+"/files", upload);
}
post("/projects/"+std::to_string(projectId)+"/files.json", upload);
}
std::vector<Redmine::Issue> Redmine::API::getIssues(const Redmine::Filter& filter) const
{
std::vector<Redmine::Issue> issues;
nlohmann::json issueList = get("/issues.json?" + filter.toQueryString());
for (auto &issue : issueList["issues"])
{
issues.push_back(Redmine::Issue(issue));
}
return issues;
}

75
src/Redmine/Filter.cpp Normal file
View File

@ -0,0 +1,75 @@
/*
* 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 <Redmine/Filter.hpp>
#include <sstream>
Redmine::Filter::Filter()
:
filterByProjectID_(false),
projectStatus_(Redmine::Filter::ProjectStatus::open),
filterByAssignedUser_(false)
{
}
void Redmine::Filter::filterByProjectID(const std::uint64_t id)
{
filterByProjectID_ = true;
projectID_ = id;
}
void Redmine::Filter::filterByAssignedUserId(const std::uint64_t id)
{
filterByAssignedUser_ = true;
assignedUserID_ = id;
}
std::string Redmine::Filter::toQueryString() const
{
std::stringstream filter;
if (filterByProjectID_)
{
filter << "project_id=" << projectID_ << "&";
}
switch (projectStatus_)
{
case Redmine::Filter::ProjectStatus::both:
filter << "status_id=*";
break;
case Redmine::Filter::ProjectStatus::open:
filter << "status=open";
break;
case Redmine::Filter::ProjectStatus::closed:
filter << "status=closed";
break;
}; // switch projectStatus
if (filterByAssignedUser_)
{
if (filter.rdbuf()->in_avail() > 0)
{
filter << "&";
}
filter << "assigned_to_id=" << assignedUserID_;
}
return filter.str();
}

68
src/Redmine/Issue.cpp Normal file
View File

@ -0,0 +1,68 @@
#include <Redmine/Issue.hpp>
#define LOGURU_WITH_STREAMS 1
#include <loguru.hpp>
Redmine::Issue::Issue(const nlohmann::json &issue) : Redmine::Object()
{
set(issue);
}
void Redmine::Issue::set(const nlohmann::json &data)
{
_verify(data);
data_=data;
}
nlohmann::json Redmine::Issue::get() const
{
return data_;
}
void Redmine::Issue::_verify(const nlohmann::json &data)
{
_baseVerify(data, "");
}
std::uint64_t Redmine::Issue::getId() const
{
return data_["id"].get<std::uint64_t>();
}
std::string Redmine::Issue::getProject() const
{
return data_["project"]["name"].get<std::string>();
}
std::uint64_t Redmine::Issue::getProjectId() const
{
return data_["project"]["id"].get<std::uint64_t>();
}
std::string Redmine::Issue::getStatus() const
{
return data_["status"]["name"].get<std::string>();
}
std::uint64_t Redmine::Issue::getStatusID() const
{
return data_["status"]["id"].get<std::uint64_t>();
}
std::string Redmine::Issue::getSubject() const
{
return data_["subject"].get<std::string>();
}
std::string Redmine::Issue::getDescription() const
{
return data_["description"].get<std::string>();
}
std::string Redmine::Issue::to_string() const
{
return data_.dump(2);
}

View File

@ -1,5 +1,4 @@
#ifndef __LIBCMS_CONFIG_HPP__
#define __LIBCMS_CONFIG_HPP__
#pragma once
/*
* Copyright (C) 2024 Dominik Meyer
*
@ -24,5 +23,3 @@
#define VERSION_MINOR 0
#define VERSION_PATCH 1
#define PROJECT_VERSION "0.0.1"
#endif