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

Commit d84065c

Browse files
committed
Validate locale
1 parent 29ac783 commit d84065c

14 files changed

Lines changed: 89 additions & 32 deletions

File tree

src/api/v1/base.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22

33
#include <auth.h>
44

5+
#include "lang/lang.h"
6+
57
using namespace drogon;
68
using namespace service;
79

@@ -10,7 +12,8 @@ namespace api::v1 {
1012
BaseProjectController::getProjectWithParams(const HttpRequestPtr req, const std::string project) {
1113
const auto version = req->getOptionalParameter<std::string>("version");
1214
const auto locale = req->getOptionalParameter<std::string>("locale");
13-
co_return co_await getProject(project, version, locale);
15+
const auto validatedLocale = co_await validateLocale(locale);
16+
co_return co_await getProject(project, version, validatedLocale);
1417
}
1518

1619
Task<ResolvedProject> BaseProjectController::getVersionedProject(const HttpRequestPtr req, const std::string project) {

src/api/v1/docs.cc

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,7 @@ namespace api::v1 {
3737
throw ApiException(Error::ErrBadRequest, "Missing path parameter");
3838
}
3939

40-
const auto resolved = co_await BaseProjectController::getProject(project, req->getOptionalParameter<std::string>("version"),
41-
req->getOptionalParameter<std::string>("locale"));
40+
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
4241

4342
const auto [page, pageError](resolved.readFile(path + DOCS_FILE_EXT));
4443
if (pageError != Error::Ok) {
@@ -67,8 +66,7 @@ namespace api::v1 {
6766

6867
Task<> DocsController::tree(const HttpRequestPtr req, const std::function<void(const HttpResponsePtr &)> callback,
6968
const std::string project) const {
70-
const auto resolved = co_await BaseProjectController::getProject(project, req->getOptionalParameter<std::string>("version"),
71-
req->getOptionalParameter<std::string>("locale"));
69+
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
7270

7371
const auto [tree, treeError](resolved.getDirectoryTree());
7472
if (treeError != Error::Ok) {

src/api/v1/game.cc

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,10 +102,7 @@ namespace api::v1 {
102102
if (recipe.empty()) {
103103
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
104104
}
105-
106-
const auto resolved = co_await BaseProjectController::getVersionedProject(req, project);
107-
108-
const auto locale = req->getOptionalParameter<std::string>("locale");
105+
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
109106
const auto resolvedResult = co_await resolved.getRecipe(recipe);
110107
if (!resolvedResult) {
111108
throw ApiException(Error::ErrNotFound, "not_found");
@@ -120,7 +117,7 @@ namespace api::v1 {
120117
throw ApiException(Error::ErrBadRequest, "Insufficient parameters");
121118
}
122119

123-
const auto resolved = co_await BaseProjectController::getVersionedProject(req, project);
120+
const auto resolved = co_await BaseProjectController::getProjectWithParams(req, project);
124121
const auto recipeType = co_await resolved.getProjectDatabase().getRecipeType(type);
125122
if (!recipeType) {
126123
throw ApiException(Error::ErrNotFound, "not_found");

src/api/v1/projects.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -533,8 +533,7 @@ namespace api::v1 {
533533
throw ApiException(Error::ErrBadRequest, error->msg);
534534
}
535535

536-
const auto resolved(co_await BaseProjectController::getProject(id, req->getOptionalParameter<std::string>("version"),
537-
req->getOptionalParameter<std::string>("locale")));
536+
const auto resolved(co_await BaseProjectController::getProjectWithParams(req, id));
538537

539538
const auto parsedLevel = parseProjectIssueLevel((*json)["level"].asString());
540539
if (parsedLevel == ProjectIssueLevel::UNKNOWN) {

src/api/v1/system.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
#include <version.h>
99

1010
#include "lang/crowdin.h"
11+
#include "lang/lang.h"
1112
#include "service/auth.h"
1213
#include "service/system_data/system_data_import.h"
1314

@@ -21,7 +22,9 @@ using namespace drogon_model::postgres;
2122

2223
namespace api::v1 {
2324
Task<> SystemController::getLocales(const HttpRequestPtr req, const std::function<void(const HttpResponsePtr &)> callback) const {
24-
const auto locales = co_await global::crowdin->getAvailableLocales();
25+
auto locales = co_await global::crowdin->getAvailableLocales();
26+
std::ranges::sort(locales, [](const Locale &a, const Locale &b) { return a.id < b.id; });
27+
locales.insert(locales.begin(), {"en", "English", DEFAULT_LOCALE});
2528
callback(jsonResponse(locales));
2629
}
2730

src/service/cache.cc

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,26 @@
33
#include <drogon/drogon.h>
44
#include "util.h"
55

6+
#define INVALID_SET_MEMBER "_empty_"
7+
68
using namespace drogon;
79
using namespace std::chrono_literals;
810

911
namespace service {
1012
MemoryCache::MemoryCache() = default;
1113

14+
Task<bool> MemoryCache::exists(std::string key) const {
15+
const auto client = app().getFastRedisClient();
16+
const auto resp = co_await client->execCommandCoro("EXISTS %s", key.data());
17+
co_return !resp.isNil() && resp.asInteger() == true;
18+
}
19+
20+
Task<bool> MemoryCache::isSetMember(std::string key, std::string value) const {
21+
const auto client = app().getFastRedisClient();
22+
const auto resp = co_await client->execCommandCoro("SISMEMBER %s %s", key.data(), value.data());
23+
co_return !resp.isNil() && resp.asInteger() == true;
24+
}
25+
1226
Task<std::optional<std::string>> MemoryCache::getFromCache(std::string key) const {
1327
const auto client = app().getFastRedisClient();
1428
const auto resp = co_await client->execCommandCoro("GET %s", key.data());
@@ -44,6 +58,24 @@ namespace service {
4458
co_await trans->executeCoro();
4559
}
4660

61+
Task<> MemoryCache::updateCacheSet(std::string key, const std::vector<std::string> value,
62+
const std::chrono::duration<long> expire) const {
63+
const auto client = app().getFastRedisClient();
64+
const auto expireSeconds = std::chrono::seconds(expire).count();
65+
66+
std::vector valueCopy(value);
67+
if (valueCopy.empty()) {
68+
valueCopy.push_back(INVALID_SET_MEMBER);
69+
}
70+
71+
const auto trans = co_await client->newTransactionCoro();
72+
for (const auto &item: valueCopy) {
73+
co_await trans->execCommandCoro("SADD %s %s", key.data(), item.data());
74+
}
75+
co_await trans->execCommandCoro("EXPIRE %s %ld", key.data(), expireSeconds);
76+
co_await trans->executeCoro();
77+
}
78+
4779
Task<> MemoryCache::erase(std::string key) const {
4880
const auto client = app().getFastRedisClient();
4981
co_await client->execCommandCoro("DEL %s", key.data());

src/service/cache.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,12 +33,16 @@ namespace service {
3333
public:
3434
MemoryCache();
3535

36+
drogon::Task<bool> exists(std::string key) const;
37+
drogon::Task<bool> isSetMember(std::string key, std::string value) const;
38+
3639
drogon::Task<std::optional<std::string>> getFromCache(std::string key) const;
3740

3841
drogon::Task<std::optional<std::string>> getHashMember(std::string key, std::string value) const;
3942

4043
drogon::Task<> updateCache(std::string key, std::string value, std::chrono::duration<long> expire) const;
4144
drogon::Task<> updateCacheHash(std::string key, std::unordered_map<std::string, std::string> values, std::chrono::duration<long> expire) const;
45+
drogon::Task<> updateCacheSet(std::string key, std::vector<std::string> value, std::chrono::duration<long> expire) const;
4246

4347
drogon::Task<> erase(std::string key) const;
4448
};

src/service/content/recipe_custom.cc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,7 @@ namespace content {
4444
if (auto result = project.getRecipeType(type)) {
4545
result->id = type;
4646
const auto langKey = std::format("recipe_type.{}.{}", type.namespace_, type.path_);
47-
// TODO Locale
48-
if (const auto localName = project.readLangKey("en_en", langKey)) {
47+
if (const auto localName = project.readLangKey(langKey)) {
4948
result->localizedName = *localName;
5049
}
5150
return result;

src/service/lang/crowdin.cc

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,17 @@ using namespace std::chrono_literals;
1212

1313
namespace service {
1414
const static std::string localesCacheKey = "crowdin:languages";
15+
const static std::string langKeysCacheKey = "crowdin:languages:keys";
1516

1617
Crowdin::Crowdin(const config::Crowdin &config) : config_(config) {}
1718

19+
Task<bool> Crowdin::hasLocaleKey(const std::string locale) {
20+
if (!co_await global::cache->exists(langKeysCacheKey)) {
21+
co_await getAvailableLocales();
22+
}
23+
co_return co_await global::cache->isSetMember(langKeysCacheKey, locale);
24+
}
25+
1826
Task<std::vector<Locale>> Crowdin::getAvailableLocales() {
1927
if (const auto cached = co_await global::cache->getFromCache(localesCacheKey)) {
2028
co_return nlohmann::json::parse(*cached);
@@ -37,6 +45,11 @@ namespace service {
3745

3846
co_await global::cache->updateCache(localesCacheKey, nlohmann::json(locales).dump(), 7 * 24h);
3947

48+
std::vector<std::string> keys;
49+
for (const auto &locale : locales)
50+
keys.push_back(locale.code);
51+
co_await global::cache->updateCacheSet(langKeysCacheKey, keys, 7 * 24h);
52+
4053
co_return co_await completeTask<std::vector<Locale>>(localesCacheKey, locales);
4154
}
4255

src/service/lang/crowdin.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ namespace service {
2020
explicit Crowdin(const config::Crowdin &);
2121

2222
drogon::Task<std::vector<Locale>> getAvailableLocales();
23+
drogon::Task<bool> hasLocaleKey(std::string locale);
2324
drogon::Task<> reloadAvailableLocales();
2425
private:
2526
drogon::Task<std::vector<Locale>> computeLanguages() const;

0 commit comments

Comments
 (0)