Skip to content
This repository was archived by the owner on Jun 7, 2026. It is now read-only.

Commit 4aad674

Browse files
committed
Move project management code to service
1 parent e074192 commit 4aad674

28 files changed

Lines changed: 428 additions & 449 deletions

src/api/v1/base.cc

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,7 @@
22

33
#include <auth.h>
44
#include <service/system/lang.h>
5-
6-
#include "../../service/project/cached/cached.h"
5+
#include <service/project/cached/cached.h>
76

87
using namespace drogon;
98
using namespace service;
@@ -15,6 +14,16 @@ namespace api::v1 {
1514
}
1615
}
1716

17+
void assertNonEmptyParam(const std::string& str) {
18+
if (str.empty()) {
19+
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
20+
}
21+
}
22+
23+
void notFound(const std::string &msg) {
24+
throw ApiException(Error::ErrNotFound, msg);
25+
}
26+
1827
Task<ProjectBasePtr> BaseProjectController::getProjectWithParamsCached(const HttpRequestPtr req, const std::string project) {
1928
const auto resolved(co_await getProjectWithParams(req, project));
2029
co_return std::make_shared<CachedProject>(resolved);
@@ -34,9 +43,7 @@ namespace api::v1 {
3443

3544
Task<ProjectBasePtr> BaseProjectController::getProject(const std::string &project, const std::optional<std::string> &version,
3645
const std::optional<std::string> &locale) {
37-
if (project.empty()) {
38-
throw ApiException(Error::ErrBadRequest, "Missing project parameter");
39-
}
46+
assertNonEmptyParam(project);
4047

4148
const auto resolved = co_await global::storage->getProject(project, version, locale);
4249
if (!resolved) {

src/api/v1/base.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,17 @@
77
namespace api::v1 {
88
void requireNonVirtual(const service::ProjectBasePtr &project);
99

10+
void assertNonEmptyParam(const std::string &str);
11+
12+
void notFound(const std::string &msg = "not_found");
13+
14+
template<typename T>
15+
void assertFound(T &&t, const std::string &&msg = "not_found") {
16+
if (!t) {
17+
notFound(std::move(msg));
18+
}
19+
}
20+
1021
class BaseProjectController {
1122
public:
1223
static drogon::Task<service::ProjectBasePtr> getProjectWithParams(drogon::HttpRequestPtr req, std::string project);

src/api/v1/browse.cc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
#include <log/log.h>
66
#include <models/Project.h>
77
#include <service/util.h>
8+
#include <service/database/database.h>
89

910
using namespace std;
1011
using namespace drogon;

src/api/v1/browse.h

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,6 @@
22

33
#include <drogon/HttpController.h>
44

5-
#include <service/database/database.h>
6-
75
namespace api::v1 {
86
class BrowseController final : public drogon::HttpController<BrowseController, false> {
97
public:

src/api/v1/docs.cc

Lines changed: 43 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -22,48 +22,37 @@ namespace api::v1 {
2222
requireNonVirtual(resolved);
2323

2424
if (version && !co_await resolved->hasVersion(*version)) {
25-
throw ApiException(Error::ErrNotFound, "Version not found");
25+
notFound("Version not found");
2626
}
2727

2828
const auto json = co_await resolved->toJsonVerbose();
2929
callback(HttpResponse::newHttpJsonResponse(json));
3030
}
3131

3232
Task<> DocsController::page(HttpRequestPtr req, std::function<void(const HttpResponsePtr &)> callback, std::string project) const {
33-
try {
34-
std::string prefix = std::format("/api/v1/docs/{}/page/", project);
35-
std::string path = req->getPath().substr(prefix.size());
36-
37-
if (path.empty()) {
38-
throw ApiException(Error::ErrBadRequest, "Missing path parameter");
39-
}
40-
41-
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
42-
requireNonVirtual(resolved);
43-
44-
const auto page(resolved->readPageFile(path + DOCS_FILE_EXT));
45-
if (!page) {
46-
const auto optionalParam = req->getOptionalParameter<std::string>("optional");
47-
const auto optional = optionalParam.has_value() && optionalParam == "true";
48-
49-
throw ApiException(optional ? Error::Ok : page.error(), "File not found");
50-
}
51-
52-
Json::Value root;
53-
root["project"] = co_await resolved->toJson();
54-
root["content"] = page->content;
55-
if (resolved->getProject().getValueOfIsPublic() && !page->editUrl.empty()) {
56-
root["edit_url"] = page->editUrl;
57-
}
58-
59-
callback(HttpResponse::newHttpJsonResponse(root));
60-
} catch (const HttpException &err) {
61-
const auto resp = HttpResponse::newHttpResponse();
62-
resp->setBody(err.what());
63-
callback(resp);
33+
std::string prefix = std::format("/api/v1/docs/{}/page/", project);
34+
std::string path = req->getPath().substr(prefix.size());
35+
assertNonEmptyParam(path);
36+
37+
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
38+
requireNonVirtual(resolved);
39+
40+
const auto page(resolved->readPageFile(path + DOCS_FILE_EXT));
41+
if (!page) {
42+
const auto optionalParam = req->getOptionalParameter<std::string>("optional");
43+
const auto optional = optionalParam.has_value() && optionalParam == "true";
44+
45+
throw ApiException(optional ? Error::Ok : page.error(), "File not found");
6446
}
6547

66-
co_return;
48+
Json::Value root;
49+
root["project"] = co_await resolved->toJson();
50+
root["content"] = page->content;
51+
if (resolved->getProject().getValueOfIsPublic() && !page->editUrl.empty()) {
52+
root["edit_url"] = page->editUrl;
53+
}
54+
55+
callback(HttpResponse::newHttpJsonResponse(root));
6756
}
6857

6958
Task<> DocsController::tree(const HttpRequestPtr req, const std::function<void(const HttpResponsePtr &)> callback,
@@ -84,40 +73,29 @@ namespace api::v1 {
8473
}
8574

8675
Task<> DocsController::asset(HttpRequestPtr req, std::function<void(const HttpResponsePtr &)> callback, std::string project) const {
87-
try {
88-
const auto resolved = co_await BaseProjectController::getProject(project, req->getOptionalParameter<std::string>("version"), std::nullopt);
89-
requireNonVirtual(resolved);
90-
91-
std::string prefix = std::format("/api/v1/docs/{}/asset/", project);
92-
std::string location = req->getPath().substr(prefix.size());
93-
94-
if (location.empty()) {
95-
throw ApiException(Error::ErrBadRequest, "Missing location parameter");
96-
}
97-
98-
const auto resourceLocation = ResourceLocation::parse(location);
99-
if (!resourceLocation) {
100-
throw ApiException(Error::ErrBadRequest, "Invalid location specified");
101-
}
102-
103-
const auto asset = resolved->getAsset(*resourceLocation);
104-
if (!asset) {
105-
const auto optionalParam = req->getOptionalParameter<std::string>("optional");
106-
const auto optional = optionalParam.has_value() && optionalParam == "true";
107-
108-
throw ApiException(optional ? Error::Ok : Error::ErrNotFound, "Asset not found");
109-
}
110-
111-
const auto response = HttpResponse::newFileResponse(absolute(*asset).string());
112-
response->setStatusCode(k200OK);
113-
callback(response);
114-
} catch (const HttpException &err) {
115-
const auto resp = HttpResponse::newHttpResponse();
116-
resp->setBody(err.what());
117-
resp->setStatusCode(k500InternalServerError);
118-
callback(resp);
76+
const auto resolved = co_await BaseProjectController::getProject(project, req->getOptionalParameter<std::string>("version"), std::nullopt);
77+
requireNonVirtual(resolved);
78+
79+
std::string prefix = std::format("/api/v1/docs/{}/asset/", project);
80+
std::string location = req->getPath().substr(prefix.size());
81+
82+
const auto resourceLocation = ResourceLocation::parse(location);
83+
if (!resourceLocation) {
84+
throw ApiException(Error::ErrBadRequest, "Invalid location specified");
11985
}
12086

87+
const auto asset = resolved->getAsset(*resourceLocation);
88+
if (!asset) {
89+
const auto optionalParam = req->getOptionalParameter<std::string>("optional");
90+
const auto optional = optionalParam.has_value() && optionalParam == "true";
91+
92+
throw ApiException(optional ? Error::Ok : Error::ErrNotFound, "Asset not found");
93+
}
94+
95+
const auto response = HttpResponse::newFileResponse(absolute(*asset).string());
96+
response->setStatusCode(k200OK);
97+
callback(response);
98+
12199
co_return;
122100
}
123101
}

src/api/v1/game.cc

Lines changed: 22 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,9 @@
11
#include "game.h"
22

3-
#include <string>
43
#include <service/database/project_database.h>
5-
#include <service/system/lang.h>
64
#include <service/storage/ingestor/recipe/recipe_parser.h>
5+
#include <service/system/lang.h>
6+
#include <string>
77

88
using namespace std;
99
using namespace drogon;
@@ -20,26 +20,18 @@ namespace api::v1 {
2020
requireNonVirtual(resolved);
2121

2222
const auto contents = co_await resolved->getProjectContents();
23-
if (!contents) {
24-
throw ApiException(contents.error(), "Contents directory not found");
25-
}
23+
assertFound(contents);
2624

2725
callback(jsonResponse(*contents));
2826
}
2927

3028
Task<> GameController::contentItem(const HttpRequestPtr req, std::function<void(const HttpResponsePtr &)> callback,
3129
const std::string project, const std::string id) const {
32-
if (id.empty()) {
33-
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
34-
}
35-
3630
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
3731
requireNonVirtual(resolved);
3832

3933
const auto page = co_await resolved->readContentPage(id);
40-
if (!page) {
41-
throw ApiException(Error::ErrNotFound, "Content ID not found");
42-
}
34+
assertFound(page, "Content ID not found");
4335

4436
Json::Value root;
4537
root["project"] = co_await resolved->toJson();
@@ -54,9 +46,7 @@ namespace api::v1 {
5446

5547
Task<> GameController::contentItemRecipe(const HttpRequestPtr req, const std::function<void(const HttpResponsePtr &)> callback,
5648
const std::string project, const std::string item) const {
57-
if (item.empty()) {
58-
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
59-
}
49+
assertNonEmptyParam(item);
6050

6151
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
6252
requireNonVirtual(resolved);
@@ -70,6 +60,7 @@ namespace api::v1 {
7060
logger.error("Missing recipe {} for item {} / {}", id, project, item);
7161
}
7262
}
63+
7364
callback(jsonResponse(root));
7465
}
7566

@@ -81,87 +72,70 @@ namespace api::v1 {
8172
logger.error("Missing project for item {}:{}:{}", id, loc, project);
8273
continue;
8374
}
84-
const auto name = co_await (*resolved)->getItemName(loc);
75+
const auto itemName = co_await (*resolved)->getItemName(loc);
8576

8677
nlohmann::json itemJson;
8778
itemJson["project"] = project.empty() ? nlohmann::json(nullptr) : nlohmann::json(project);
8879
itemJson["id"] = loc;
89-
if (!name.name.empty()) {
90-
itemJson["name"] = name.name;
80+
if (itemName) {
81+
itemJson["name"] = itemName->name;
9182
}
9283
itemJson["has_page"] = !path.empty();
9384
root.push_back(itemJson);
9485
}
9586
co_return root;
9687
}
9788

98-
// TODO Remove unused param
9989
Task<> GameController::contentItemUsage(const HttpRequestPtr req, const std::function<void(const HttpResponsePtr &)> callback,
10090
const std::string project, const std::string item) const {
101-
if (item.empty()) {
102-
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
103-
}
91+
assertNonEmptyParam(item);
10492

105-
const auto obtainable = co_await global::database->getObtainableItemsBy(item);
93+
const auto resolved = co_await BaseProjectController::getProjectWithParamsCached(req, project);
94+
const auto obtainable = co_await resolved->getProjectDatabase().getObtainableItemsBy(item);
10695

10796
const auto json = co_await resolveContentUsage(obtainable);
10897
callback(jsonResponse(json));
10998
}
11099

111100
Task<> GameController::contentItemName(const HttpRequestPtr req, std::function<void(const HttpResponsePtr &)> callback,
112101
const std::string project, const std::string id) const {
113-
if (id.empty()) {
114-
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
115-
}
102+
assertNonEmptyParam(id);
116103

117104
const auto resolved = co_await BaseProjectController::getProjectWithParamsCached(req, project);
118-
const auto [name, path] = co_await resolved->getItemName(id);
119-
if (name.empty()) {
120-
throw ApiException(Error::ErrNotFound, "not_found");
121-
}
105+
const auto itemName = co_await resolved->getItemName(id);
106+
assertFound(itemName);
122107

123108
Json::Value root;
124109
root["source"] = resolved->getId();
125110
root["id"] = id;
126-
root["name"] = name;
111+
root["name"] = itemName->name;
127112

128113
callback(HttpResponse::newHttpJsonResponse(root));
129114
}
130115

131116
Task<> GameController::recipe(const HttpRequestPtr req, const std::function<void(const HttpResponsePtr &)> callback,
132117
const std::string project, const std::string recipe) const {
133-
if (recipe.empty()) {
134-
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
135-
}
118+
assertNonEmptyParam(recipe);
136119

137120
const auto resolved = co_await BaseProjectController::getProjectWithParamsCached(req, project);
138121
requireNonVirtual(resolved);
139122

140123
const auto resolvedResult = co_await resolved->getRecipe(recipe);
141-
if (!resolvedResult) {
142-
throw ApiException(Error::ErrNotFound, "not_found");
143-
}
124+
assertFound(resolvedResult);
144125

145126
callback(jsonResponse(*resolvedResult));
146127
}
147128

148129
Task<> GameController::recipeType(const HttpRequestPtr req, const std::function<void(const HttpResponsePtr &)> callback,
149130
const std::string project, const std::string type) const {
150-
if (type.empty()) {
151-
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
152-
}
131+
assertNonEmptyParam(type);
153132

154-
// TODO Use cached + move type getter to project
155-
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
133+
const auto resolved = co_await BaseProjectController::getProjectWithParamsCached(req, project);
156134
const auto recipeType = co_await resolved->getProjectDatabase().getRecipeType(type);
157-
if (!recipeType) {
158-
throw ApiException(Error::ErrNotFound, "not_found"); // TODO Shorthand
159-
}
135+
assertFound(recipeType);
160136

161137
const auto layout = co_await content::getRecipeType(resolved, unwrap(ResourceLocation::parse(type)));
162-
if (!layout) {
163-
throw ApiException(Error::ErrNotFound, "not_found");
164-
}
138+
assertFound(layout);
165139

166140
const auto workbenches = co_await global::database->getRecipeTypeWorkbenches(recipeType->getValueOfId());
167141
const auto workbenchItems = co_await resolveContentUsage(workbenches);

src/api/v1/moderation.cc

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -78,9 +78,7 @@ namespace api::v1 {
7878
co_await global::auth->ensurePrivilegedAccess(req);
7979

8080
const auto report(co_await global::database->findByPrimaryKey<Report>(id));
81-
if (!report) {
82-
throw ApiException(Error::ErrNotFound, "not_found");
83-
}
81+
assertFound(report);
8482

8583
nlohmann::json root(*report);
8684
if (const auto versionId = report->getVersionId()) {
@@ -98,9 +96,7 @@ namespace api::v1 {
9896
const auto json(BaseProjectController::validatedBody(req, schemas::ruleReport));
9997

10098
auto report(co_await global::database->findByPrimaryKey<Report>(id));
101-
if (!report) {
102-
throw ApiException(Error::ErrNotFound, "not_found");
103-
}
99+
assertFound(report);
104100

105101
const auto resolution = json["resolution"];
106102
const auto status = resolution == "accept" ? ReportStatus::ACCEPTED : ReportStatus::DISMISSED;

0 commit comments

Comments
 (0)