feat: support updating, getting, and deleting wiki pages
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
Dominik Meyer 2024-10-10 13:41:07 +02:00
parent e0450de6d9
commit 0336b38a45
Signed by: byterazor
GPG Key ID: EABDA0FD5981BC97
8 changed files with 323 additions and 2 deletions

View File

@ -139,6 +139,8 @@ add_executable(redmine-cli
src/Redmine-CLI/Command/Issue.cpp
src/Redmine-CLI/Command/Upload.hpp
src/Redmine-CLI/Command/Upload.cpp
src/Redmine-CLI/Command/Wiki.hpp
src/Redmine-CLI/Command/Wiki.cpp
)
target_link_libraries(redmine-cli redmine-api-cpp-static loguru CLI11 tableprinter::tableprinter)
target_include_directories(redmine-cli

View File

@ -17,7 +17,9 @@ It also provides a small CLI application to interact with a remine server throug
|News | * | no |
|Issue Relation | * | no |
|Versions | * | no |
|Wiki Pages | * | no |
|Wiki Pages | list | no |
|Wiki Pages | create/update | yes |
|Wiki Pages | delete | yes |
|Queries | * | no |
|Attachments | * | no |
|Issue Statuses | * | no |

View File

@ -58,6 +58,7 @@ namespace Redmine
nlohmann::json get(const std::string &path) const;
void put(const std::string &path, const nlohmann::json &data) const;
void post(const std::string &path, const nlohmann::json &data) const;
void del(const std::string &path) const;
void processGenericErrors_(httplib::Result &res) const;
@ -143,6 +144,42 @@ namespace Redmine
std::string upload(const std::filesystem::path &file, const std::string &filename) const;
/**
* @brief Get a Wiki Page from the redmine server
*
* @param projectIdentifier - the project identifier (only string not number)
* @param page - the name of the page
* @return std::string - the content of the wiki page
*/
std::string getWikiPage(const std::string &projectIdentifier, const std::string &page);
/**
* @brief update a Wiki Page on the redmine server
*
* @param projectIdentifier - the project identifier (only string not number)
* @param page - the name of the page
* @param content - the content of the page
*/
void updateWikiPage(const std::string &projectIdentifier, const std::string &page, const std::string &content);
/**
* @brief update a Wiki Page on the redmine server with attachments
*
* @param projectIdentifier - the project identifier (only string not number)
* @param page - the name of the page
* @param attachements - vector of attachment paths
* @param content - the content of the page
*/
void updateWikiPage(const std::string &projectIdentifier, const std::string &page, const std::string &content, const std::vector<std::string> &attachments );
/**
* @brief delete a Wiki Page on the redmine server
*
* @param projectIdentifier - the project identifier (only string not number)
* @param page - the name of the page
*/
void deleteWikiPage(const std::string &projectIdentifier, const std::string &page);
/*@}*/
};

View File

@ -0,0 +1,101 @@
/*
* 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/Wiki.hpp>
#include <Redmine-CLI/Redmine.hpp>
#include "Redmine/API.hpp"
#include "nlohmann/json_fwd.hpp"
#include <filesystem>
#include <string>
#include <tableprinter/tableprinter.hpp>
#include <vector>
Command::Wiki::Wiki(RedmineCLI::Redmine *r)
:
redmine_(r),
output_(TEXT)
{
CLI::App* wiki = redmine_->getCLI().add_subcommand("wiki",
"Api Calls on wikis accessible by token owner");
getPage_ = wiki->add_subcommand("getPage", "get a specific Wiki page from server");
getPage_->needs(redmine_->getTokenOption())->needs(redmine_->getUrlOption());
getPage_->callback([&](){getWikiPage_();});
getPage_->add_option("-p,--project", projectIdentifier_,"string identifier of the project to get wiki page from")->required();
getPage_->add_option("--page",pageIdentifier_,"string identifier of the page to get")->required();
deletePage_ = wiki->add_subcommand("deletePage", "delete^ a specific Wiki page from server");
deletePage_->needs(redmine_->getTokenOption())->needs(redmine_->getUrlOption());
deletePage_->callback([&](){deleteWikiPage_();});
deletePage_->add_option("-p,--project", projectIdentifier_,"string identifier of the project to delete wiki page from")->required();
deletePage_->add_option("--page",pageIdentifier_,"string identifier of the page to delete")->required();
updatePage_ = wiki->add_subcommand("updatePage", "Update a specific Wiki page on the server");
updatePage_->needs(redmine_->getTokenOption())->needs(redmine_->getUrlOption());
updatePage_->add_option("-p,--project", projectIdentifier_,"string identifier of the project to update the wiki page in")->required();
updatePage_->add_option("--page",pageIdentifier_,"string identifier of the page to update")->required();
updatePage_->add_option("-c,--content", content_,"the new content of the wiki page");
updatePage_->add_option("-f,--file", filePath_,"the new content of the wiki page as a file");
updatePage_->add_option("-a",attachments_,"list of file paths to attach to the wiki page");
updatePage_->callback([&](){updateWikiPage_();});
}
void Command::Wiki::getWikiPage_()
{
Redmine::API client{redmine_->getUrl(), redmine_->getToken()};
std::cout << client.getWikiPage(projectIdentifier_, pageIdentifier_) << std::endl;
}
void Command::Wiki::deleteWikiPage_()
{
Redmine::API client{redmine_->getUrl(), redmine_->getToken()};
client.deleteWikiPage(projectIdentifier_, pageIdentifier_);
}
void Command::Wiki::updateWikiPage_()
{
Redmine::API client{redmine_->getUrl(), redmine_->getToken()};
if (!content_.empty()) {
client.updateWikiPage(projectIdentifier_, pageIdentifier_, content_);
}
else if (!filePath_.empty())
{
std::filesystem::path path = filePath_;
if (!std::filesystem::exists(path))
{
LOG_S(ERROR) << "File does not exist: " << filePath_;
throw std::runtime_error("File does not exist: " + filePath_);
}
std::ifstream file(filePath_);
std::string content((std::istreambuf_iterator<char>(file)), std::istreambuf_iterator<char>());
client.updateWikiPage(projectIdentifier_, pageIdentifier_, content, attachments_);
}
}

View File

@ -0,0 +1,113 @@
#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 <CLI/App.hpp>
#include <Redmine/API.hpp>
#include <cstdint>
#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 Wiki
{
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}};
/// command line argument project identifier
std::string projectIdentifier_;
/// command line argument page identifier
std::string pageIdentifier_;
/// command line argument page content
std::string content_;
/// command line argument file path for page content
std::string filePath_;
/// command line argument file path for attachments
std::vector<std::string> attachments_;
/// pointer to the getPage subcommand
CLI::App* getPage_;
/// pointer to the updatePage subcommand
CLI::App* updatePage_;
/// pointer to the deletePage subcommand
CLI::App* deletePage_;
/**
* @brief Get a Wiki Page from the server
*
*/
void getWikiPage_();
/**
* @brief update a wiki page on the server
*
*/
void updateWikiPage_();
/**
* @brief delete a wiki page on the server
*
*/
void deleteWikiPage_();
public:
/**
* @brief Construct a new CLIProcessor object
*
*/
explicit Wiki(RedmineCLI::Redmine *r);
}; // class Project
}; // namespace Command

View File

@ -17,6 +17,7 @@
#include <Redmine-CLI/Command/MyAccount.hpp>
#include "Redmine-CLI/Command/IssueStatus.hpp"
#include "Redmine-CLI/Command/Version.hpp"
#include "Redmine-CLI/Command/Wiki.hpp"
#include <Redmine-CLI/Redmine.hpp>
#include <memory>
#define LOGURU_WITH_STREAMS 1
@ -42,4 +43,5 @@ version_(nullptr)
issueStatus_ = std::make_unique<Command::IssueStatus>(this);
issue_ = std::make_unique<Command::Issue>(this);
upload_ = std::make_unique<Command::Upload>(this);
wiki_ = std::make_unique<Command::Wiki>(this);
}

View File

@ -21,6 +21,7 @@
#include "Redmine-CLI/Command/Project.hpp"
#include "Redmine-CLI/Command/Upload.hpp"
#include "Redmine-CLI/Command/Version.hpp"
#include "Redmine-CLI/Command/Wiki.hpp"
#include <CLI/App.hpp>
#include <CLI/Option.hpp>
#include <memory>
@ -72,6 +73,9 @@ namespace RedmineCLI
/// pointer to the upload subcommand
std::unique_ptr<Command::Upload> upload_;
/// pointer to the wiki subcommand
std::unique_ptr<Command::Wiki> wiki_;
public:
/**

View File

@ -132,6 +132,19 @@ void Redmine::API::put(const std::string &path, const nlohmann::json &data) con
DLOG_S(INFO) << "put successful";
}
void Redmine::API::del(const std::string &path) const
{
DLOG_S(INFO) << "deleting API endpoint " << path << " from " <<redmineApiURL_;
httplib::Client client{redmineApiURL_};
client.set_basic_auth(authToken_, "");
auto res = client.Delete(path);
processGenericErrors_(res);
DLOG_S(INFO) << "delete successful";
}
void Redmine::API::post(const std::string &path, const nlohmann::json &data) const
{
DLOG_S(INFO) << "posting to API endpoint " << path << " at " <<redmineApiURL_;
@ -259,3 +272,50 @@ void Redmine::API::uploadFileToProject(const std::uint64_t projectId, const std:
return issues;
}
std::string Redmine::API::getWikiPage(const std::string& project, const std::string& page)
{
nlohmann::json wiki = get("/projects/" + project + "/wiki/" + page + ".json");
return wiki["wiki_page"]["text"].get<std::string>();
}
void Redmine::API::updateWikiPage(const std::string &projectIdentifier, const std::string &page, const std::string &content, const std::vector<std::string> &attachments )
{
nlohmann::json wikiPage;
wikiPage["wiki_page"]["text"] = content;
wikiPage["wiki_page"]["uploads"]=nlohmann::json::array();
for (auto attachment : attachments)
{
std::filesystem::path attachmentPath = attachment;
if (!std::filesystem::exists(attachmentPath))
{
LOG_S(ERROR) << "Attachment " << attachmentPath.string() << " does not exist.";
throw std::runtime_error("Attachment " + attachmentPath.string() + " does not exist.");
}
std::string token = upload(attachmentPath, attachmentPath.filename().string());
nlohmann::json attachmentJson;
attachmentJson["token"] = token;
attachmentJson["filename"] = attachmentPath.filename().string();
wikiPage["wiki_page"]["uploads"].push_back(attachmentJson);
}
put("/projects/" + projectIdentifier + "/wiki/" + page + ".json", wikiPage);
}
void Redmine::API::updateWikiPage(const std::string& project, const std::string& page, const std::string &content)
{
std::vector<std::string> attachments;
updateWikiPage(project, page, content, attachments);
}
void Redmine::API::deleteWikiPage(const std::string& project, const std::string& page)
{
del("/projects/" + project + "/wiki/" + page + ".json");
}