feat: support updating, getting, and deleting wiki pages
All checks were successful
continuous-integration/drone/push Build is passing
All checks were successful
continuous-integration/drone/push Build is passing
This commit is contained in:
parent
e0450de6d9
commit
0336b38a45
@ -139,6 +139,8 @@ add_executable(redmine-cli
|
|||||||
src/Redmine-CLI/Command/Issue.cpp
|
src/Redmine-CLI/Command/Issue.cpp
|
||||||
src/Redmine-CLI/Command/Upload.hpp
|
src/Redmine-CLI/Command/Upload.hpp
|
||||||
src/Redmine-CLI/Command/Upload.cpp
|
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_link_libraries(redmine-cli redmine-api-cpp-static loguru CLI11 tableprinter::tableprinter)
|
||||||
target_include_directories(redmine-cli
|
target_include_directories(redmine-cli
|
||||||
|
@ -17,7 +17,9 @@ It also provides a small CLI application to interact with a remine server throug
|
|||||||
|News | * | no |
|
|News | * | no |
|
||||||
|Issue Relation | * | no |
|
|Issue Relation | * | no |
|
||||||
|Versions | * | no |
|
|Versions | * | no |
|
||||||
|Wiki Pages | * | no |
|
|Wiki Pages | list | no |
|
||||||
|
|Wiki Pages | create/update | yes |
|
||||||
|
|Wiki Pages | delete | yes |
|
||||||
|Queries | * | no |
|
|Queries | * | no |
|
||||||
|Attachments | * | no |
|
|Attachments | * | no |
|
||||||
|Issue Statuses | * | no |
|
|Issue Statuses | * | no |
|
||||||
|
@ -58,6 +58,7 @@ namespace Redmine
|
|||||||
nlohmann::json get(const std::string &path) const;
|
nlohmann::json get(const std::string &path) const;
|
||||||
void put(const std::string &path, const nlohmann::json &data) const;
|
void put(const std::string &path, const nlohmann::json &data) const;
|
||||||
void post(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;
|
void processGenericErrors_(httplib::Result &res) const;
|
||||||
|
|
||||||
|
|
||||||
@ -141,7 +142,43 @@ namespace Redmine
|
|||||||
* @return std::string - the redmine internal token (reference) for the uploaded file
|
* @return std::string - the redmine internal token (reference) for the uploaded file
|
||||||
*/
|
*/
|
||||||
std::string upload(const std::filesystem::path &file, const std::string &filename) const;
|
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);
|
||||||
|
|
||||||
/*@}*/
|
/*@}*/
|
||||||
};
|
};
|
||||||
|
101
src/Redmine-CLI/Command/Wiki.cpp
Normal file
101
src/Redmine-CLI/Command/Wiki.cpp
Normal 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_);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
113
src/Redmine-CLI/Command/Wiki.hpp
Normal file
113
src/Redmine-CLI/Command/Wiki.hpp
Normal 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
|
@ -17,6 +17,7 @@
|
|||||||
#include <Redmine-CLI/Command/MyAccount.hpp>
|
#include <Redmine-CLI/Command/MyAccount.hpp>
|
||||||
#include "Redmine-CLI/Command/IssueStatus.hpp"
|
#include "Redmine-CLI/Command/IssueStatus.hpp"
|
||||||
#include "Redmine-CLI/Command/Version.hpp"
|
#include "Redmine-CLI/Command/Version.hpp"
|
||||||
|
#include "Redmine-CLI/Command/Wiki.hpp"
|
||||||
#include <Redmine-CLI/Redmine.hpp>
|
#include <Redmine-CLI/Redmine.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#define LOGURU_WITH_STREAMS 1
|
#define LOGURU_WITH_STREAMS 1
|
||||||
@ -42,4 +43,5 @@ version_(nullptr)
|
|||||||
issueStatus_ = std::make_unique<Command::IssueStatus>(this);
|
issueStatus_ = std::make_unique<Command::IssueStatus>(this);
|
||||||
issue_ = std::make_unique<Command::Issue>(this);
|
issue_ = std::make_unique<Command::Issue>(this);
|
||||||
upload_ = std::make_unique<Command::Upload>(this);
|
upload_ = std::make_unique<Command::Upload>(this);
|
||||||
|
wiki_ = std::make_unique<Command::Wiki>(this);
|
||||||
}
|
}
|
@ -21,6 +21,7 @@
|
|||||||
#include "Redmine-CLI/Command/Project.hpp"
|
#include "Redmine-CLI/Command/Project.hpp"
|
||||||
#include "Redmine-CLI/Command/Upload.hpp"
|
#include "Redmine-CLI/Command/Upload.hpp"
|
||||||
#include "Redmine-CLI/Command/Version.hpp"
|
#include "Redmine-CLI/Command/Version.hpp"
|
||||||
|
#include "Redmine-CLI/Command/Wiki.hpp"
|
||||||
#include <CLI/App.hpp>
|
#include <CLI/App.hpp>
|
||||||
#include <CLI/Option.hpp>
|
#include <CLI/Option.hpp>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
@ -72,6 +73,9 @@ namespace RedmineCLI
|
|||||||
/// pointer to the upload subcommand
|
/// pointer to the upload subcommand
|
||||||
std::unique_ptr<Command::Upload> upload_;
|
std::unique_ptr<Command::Upload> upload_;
|
||||||
|
|
||||||
|
/// pointer to the wiki subcommand
|
||||||
|
std::unique_ptr<Command::Wiki> wiki_;
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -131,6 +131,19 @@ void Redmine::API::put(const std::string &path, const nlohmann::json &data) con
|
|||||||
|
|
||||||
DLOG_S(INFO) << "put successful";
|
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
|
void Redmine::API::post(const std::string &path, const nlohmann::json &data) const
|
||||||
{
|
{
|
||||||
@ -258,4 +271,51 @@ void Redmine::API::uploadFileToProject(const std::uint64_t projectId, const std:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return issues;
|
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");
|
||||||
|
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user