feat: enhance validation and serialization with fiscal code checks and JSON schema support
This commit is contained in:
+60
-3
@@ -1,9 +1,44 @@
|
|||||||
#include "common/Validation.h"
|
#include "common/Validation.h"
|
||||||
|
|
||||||
|
#include <array>
|
||||||
|
#include <cctype>
|
||||||
#include <regex>
|
#include <regex>
|
||||||
|
|
||||||
namespace visione::validation
|
namespace visione::validation
|
||||||
{
|
{
|
||||||
|
namespace
|
||||||
|
{
|
||||||
|
char normalizeFiscalCodeChar(char ch)
|
||||||
|
{
|
||||||
|
return static_cast<char>(
|
||||||
|
std::toupper(static_cast<unsigned char>(ch)));
|
||||||
|
}
|
||||||
|
|
||||||
|
int fiscalCodeControlValueForOdd(char ch)
|
||||||
|
{
|
||||||
|
static constexpr std::array<int, 26> letterValues{
|
||||||
|
1, 0, 5, 7, 9, 13, 15, 17, 19, 21, 2, 4, 18,
|
||||||
|
20, 11, 3, 6, 8, 12, 14, 16, 10, 22, 25, 24, 23};
|
||||||
|
static constexpr std::array<int, 10> digitValues{1, 0, 5, 7, 9,
|
||||||
|
13, 15, 17, 19, 21};
|
||||||
|
|
||||||
|
if (std::isdigit(static_cast<unsigned char>(ch)))
|
||||||
|
{
|
||||||
|
return digitValues[static_cast<size_t>(ch - '0')];
|
||||||
|
}
|
||||||
|
return letterValues[static_cast<size_t>(ch - 'A')];
|
||||||
|
}
|
||||||
|
|
||||||
|
int fiscalCodeControlValueForEven(char ch)
|
||||||
|
{
|
||||||
|
if (std::isdigit(static_cast<unsigned char>(ch)))
|
||||||
|
{
|
||||||
|
return ch - '0';
|
||||||
|
}
|
||||||
|
return ch - 'A';
|
||||||
|
}
|
||||||
|
} // namespace
|
||||||
|
|
||||||
bool hasString(const Json::Value &body, const std::string &key)
|
bool hasString(const Json::Value &body, const std::string &key)
|
||||||
{
|
{
|
||||||
return body.isMember(key) && body[key].isString() &&
|
return body.isMember(key) && body[key].isString() &&
|
||||||
@@ -38,8 +73,30 @@ bool isValidEmail(const std::string &email)
|
|||||||
|
|
||||||
bool isValidFiscalCode(const std::string &fiscalCode)
|
bool isValidFiscalCode(const std::string &fiscalCode)
|
||||||
{
|
{
|
||||||
static const std::regex pattern(R"(^[A-Za-z0-9]{16}$)");
|
static const std::regex pattern(
|
||||||
return std::regex_match(fiscalCode, pattern);
|
R"(^[A-Za-z]{6}[0-9]{2}[ABCDEHLMPRST]{1}[0-9]{2}[A-Za-z][0-9]{3}[A-Za-z]$)");
|
||||||
|
|
||||||
|
if (!std::regex_match(fiscalCode, pattern))
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::string normalized;
|
||||||
|
normalized.reserve(fiscalCode.size());
|
||||||
|
for (char ch : fiscalCode)
|
||||||
|
{
|
||||||
|
normalized.push_back(normalizeFiscalCodeChar(ch));
|
||||||
|
}
|
||||||
|
|
||||||
|
int sum = 0;
|
||||||
|
for (size_t i = 0; i < normalized.size() - 1; ++i)
|
||||||
|
{
|
||||||
|
sum += ((i + 1) % 2 == 0) ? fiscalCodeControlValueForEven(normalized[i])
|
||||||
|
: fiscalCodeControlValueForOdd(normalized[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
const char expectedControl =
|
||||||
|
static_cast<char>('A' + (sum % 26));
|
||||||
|
return normalized.back() == expectedControl;
|
||||||
}
|
}
|
||||||
} // namespace visione::validation
|
} // namespace visione::validation
|
||||||
|
|
||||||
|
|||||||
@@ -229,15 +229,15 @@ INSERT IGNORE INTO users (
|
|||||||
phone,
|
phone,
|
||||||
must_change_password
|
must_change_password
|
||||||
) VALUES (
|
) VALUES (
|
||||||
'System',
|
'Gennario',
|
||||||
'Admin',
|
'Vessio',
|
||||||
'admin@visione.local',
|
'admin@visione.gov.it',
|
||||||
'pbkdf2_sha256$210000$local-dev-admin-salt$A4683BDB63C1598400B6B196F53C830F8434BA645C4A4A70AB311B278223E698',
|
'pbkdf2_sha256$210000$a2c43ef09bd24ea883023e5f57fa6255d8dd317e312a40ddaa1bd3ccd83dbfa5$26D0C9000B603A424C7FB0DA3BE0CDC1798A9CBA34F391F9B8E4C0E1E4A58806',
|
||||||
'ADMINVISIONE0000',
|
'VSSGNR94M08A662P',
|
||||||
'+390000000000',
|
'380 6180216',
|
||||||
TRUE
|
TRUE
|
||||||
);
|
);
|
||||||
|
|
||||||
INSERT IGNORE INTO user_roles (user_id, role_id)
|
INSERT IGNORE INTO user_roles (user_id, role_id)
|
||||||
SELECT u.id, r.id FROM users u JOIN roles r
|
SELECT u.id, r.id FROM users u JOIN roles r
|
||||||
WHERE u.email = 'admin@visione.local' AND r.name = 'admin';
|
WHERE u.email = 'admin@visione.gov.it' AND r.name = 'admin';
|
||||||
|
|||||||
@@ -1,4 +1,9 @@
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET password_hash = 'pbkdf2_sha256$210000$local-dev-admin-salt$A4683BDB63C1598400B6B196F53C830F8434BA645C4A4A70AB311B278223E698',
|
SET email = '[email protected]',
|
||||||
|
first_name = 'Gennario',
|
||||||
|
last_name = 'Vessio',
|
||||||
|
fiscal_code = 'VSSGNR94M08A662P',
|
||||||
|
phone = '380 6180216',
|
||||||
|
password_hash = 'pbkdf2_sha256$210000$a2c43ef09bd24ea883023e5f57fa6255d8dd317e312a40ddaa1bd3ccd83dbfa5$26D0C9000B603A424C7FB0DA3BE0CDC1798A9CBA34F391F9B8E4C0E1E4A58806',
|
||||||
must_change_password = TRUE
|
must_change_password = TRUE
|
||||||
WHERE email = '[email protected]';
|
WHERE email IN ('[email protected]', '[email protected]');
|
||||||
|
|||||||
@@ -1,5 +1,9 @@
|
|||||||
UPDATE users
|
UPDATE users
|
||||||
SET password_hash = 'pbkdf2_sha256$210000$local-dev-admin-salt$A4683BDB63C1598400B6B196F53C830F8434BA645C4A4A70AB311B278223E698',
|
SET email = '[email protected]',
|
||||||
|
first_name = 'Gennario',
|
||||||
|
last_name = 'Vessio',
|
||||||
|
fiscal_code = 'VSSGNR94M08A662P',
|
||||||
|
phone = '380 6180216',
|
||||||
|
password_hash = 'pbkdf2_sha256$210000$a2c43ef09bd24ea883023e5f57fa6255d8dd317e312a40ddaa1bd3ccd83dbfa5$26D0C9000B603A424C7FB0DA3BE0CDC1798A9CBA34F391F9B8E4C0E1E4A58806',
|
||||||
must_change_password = TRUE
|
must_change_password = TRUE
|
||||||
WHERE email = '[email protected]';
|
WHERE email IN ('[email protected]', '[email protected]');
|
||||||
|
|
||||||
|
|||||||
+41
-2
@@ -6,7 +6,31 @@
|
|||||||
|
|
||||||
namespace visione::repositories
|
namespace visione::repositories
|
||||||
{
|
{
|
||||||
|
Json::Value scalarToJson(JsonFieldType type, const drogon::orm::Field &field)
|
||||||
|
{
|
||||||
|
switch (type)
|
||||||
|
{
|
||||||
|
case JsonFieldType::String:
|
||||||
|
return Json::Value(field.as<std::string>());
|
||||||
|
case JsonFieldType::Int64:
|
||||||
|
return Json::Value(Json::Int64(field.as<long long>()));
|
||||||
|
case JsonFieldType::UInt64:
|
||||||
|
return Json::Value(Json::UInt64(field.as<unsigned long long>()));
|
||||||
|
case JsonFieldType::Double:
|
||||||
|
return Json::Value(field.as<double>());
|
||||||
|
case JsonFieldType::Bool:
|
||||||
|
return Json::Value(field.as<bool>());
|
||||||
|
}
|
||||||
|
return Json::nullValue;
|
||||||
|
}
|
||||||
|
|
||||||
Json::Value rowToJson(const drogon::orm::Row &row)
|
Json::Value rowToJson(const drogon::orm::Row &row)
|
||||||
|
{
|
||||||
|
static const JsonRowSchema emptySchema;
|
||||||
|
return rowToJson(row, emptySchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value rowToJson(const drogon::orm::Row &row, const JsonRowSchema &schema)
|
||||||
{
|
{
|
||||||
Json::Value object(Json::objectValue);
|
Json::Value object(Json::objectValue);
|
||||||
for (const auto &field : row)
|
for (const auto &field : row)
|
||||||
@@ -17,18 +41,33 @@ Json::Value rowToJson(const drogon::orm::Row &row)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
object[field.name()] = field.as<std::string>();
|
const auto it = schema.find(field.name());
|
||||||
|
if (it == schema.end())
|
||||||
|
{
|
||||||
|
object[field.name()] = field.as<std::string>();
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
object[field.name()] = scalarToJson(it->second, field);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return object;
|
return object;
|
||||||
}
|
}
|
||||||
|
|
||||||
Json::Value resultToJson(const drogon::orm::Result &result)
|
Json::Value resultToJson(const drogon::orm::Result &result)
|
||||||
|
{
|
||||||
|
static const JsonRowSchema emptySchema;
|
||||||
|
return resultToJson(result, emptySchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
Json::Value resultToJson(const drogon::orm::Result &result,
|
||||||
|
const JsonRowSchema &schema)
|
||||||
{
|
{
|
||||||
Json::Value array(Json::arrayValue);
|
Json::Value array(Json::arrayValue);
|
||||||
for (const auto &row : result)
|
for (const auto &row : result)
|
||||||
{
|
{
|
||||||
array.append(rowToJson(row));
|
array.append(rowToJson(row, schema));
|
||||||
}
|
}
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|||||||
+16
-1
@@ -4,11 +4,26 @@
|
|||||||
|
|
||||||
#include <json/json.h>
|
#include <json/json.h>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
namespace visione::repositories
|
namespace visione::repositories
|
||||||
{
|
{
|
||||||
|
enum class JsonFieldType
|
||||||
|
{
|
||||||
|
String,
|
||||||
|
Int64,
|
||||||
|
UInt64,
|
||||||
|
Double,
|
||||||
|
Bool
|
||||||
|
};
|
||||||
|
|
||||||
|
using JsonRowSchema = std::unordered_map<std::string, JsonFieldType>;
|
||||||
|
|
||||||
|
Json::Value scalarToJson(JsonFieldType type, const drogon::orm::Field &field);
|
||||||
Json::Value rowToJson(const drogon::orm::Row &row);
|
Json::Value rowToJson(const drogon::orm::Row &row);
|
||||||
|
Json::Value rowToJson(const drogon::orm::Row &row, const JsonRowSchema &schema);
|
||||||
Json::Value resultToJson(const drogon::orm::Result &result);
|
Json::Value resultToJson(const drogon::orm::Result &result);
|
||||||
|
Json::Value resultToJson(const drogon::orm::Result &result,
|
||||||
|
const JsonRowSchema &schema);
|
||||||
std::string nullableString(const Json::Value &value, const std::string &key);
|
std::string nullableString(const Json::Value &value, const std::string &key);
|
||||||
} // namespace visione::repositories
|
} // namespace visione::repositories
|
||||||
|
|
||||||
|
|||||||
@@ -12,6 +12,9 @@ namespace visione::services
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
using repositories::JsonFieldType;
|
||||||
|
using repositories::JsonRowSchema;
|
||||||
|
|
||||||
Json::Value failure(const std::string &code)
|
Json::Value failure(const std::string &code)
|
||||||
{
|
{
|
||||||
Json::Value body;
|
Json::Value body;
|
||||||
@@ -58,6 +61,27 @@ int boundedPerPage(const std::optional<std::string> &value)
|
|||||||
return 50;
|
return 50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema &moderationFlagSchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"report_id", JsonFieldType::UInt64},
|
||||||
|
{"message_id", JsonFieldType::UInt64},
|
||||||
|
{"created_by_user_id", JsonFieldType::UInt64},
|
||||||
|
{"resolved_by_user_id", JsonFieldType::UInt64}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema &adminUserSchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"points", JsonFieldType::Int64},
|
||||||
|
{"banned", JsonFieldType::Bool},
|
||||||
|
{"must_change_password", JsonFieldType::Bool}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Json::Value AdminService::listCategories()
|
Json::Value AdminService::listCategories()
|
||||||
@@ -229,7 +253,8 @@ Json::Value AdminService::listModeration(
|
|||||||
statusValue,
|
statusValue,
|
||||||
statusValue,
|
statusValue,
|
||||||
perPageValue,
|
perPageValue,
|
||||||
offset));
|
offset),
|
||||||
|
moderationFlagSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,7 +342,8 @@ Json::Value AdminService::listUsers(const auth::UserContext &user)
|
|||||||
"LEFT JOIN roles r ON r.id = ur.role_id "
|
"LEFT JOIN roles r ON r.id = ur.role_id "
|
||||||
"GROUP BY u.id, u.first_name, u.last_name, u.email, u.fiscal_code, "
|
"GROUP BY u.id, u.first_name, u.last_name, u.email, u.fiscal_code, "
|
||||||
"u.phone, u.points, u.banned, u.must_change_password "
|
"u.phone, u.points, u.banned, u.must_change_password "
|
||||||
"ORDER BY u.created_at DESC LIMIT 200"));
|
"ORDER BY u.created_at DESC LIMIT 200"),
|
||||||
|
adminUserSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+70
-10
@@ -16,6 +16,9 @@ namespace visione::services
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
using repositories::JsonFieldType;
|
||||||
|
using repositories::JsonRowSchema;
|
||||||
|
|
||||||
Json::Value failure(const std::string &code)
|
Json::Value failure(const std::string &code)
|
||||||
{
|
{
|
||||||
Json::Value body;
|
Json::Value body;
|
||||||
@@ -152,6 +155,57 @@ int boundedPerPage(const std::optional<std::string> &value)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema &reportSchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"user_id", JsonFieldType::UInt64},
|
||||||
|
{"category_id", JsonFieldType::UInt64},
|
||||||
|
{"area_id", JsonFieldType::UInt64},
|
||||||
|
{"assigned_operator_id", JsonFieldType::UInt64},
|
||||||
|
{"latitude", JsonFieldType::Double},
|
||||||
|
{"longitude", JsonFieldType::Double}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema &reportHistorySchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"report_id", JsonFieldType::UInt64},
|
||||||
|
{"changed_by_user_id", JsonFieldType::UInt64}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema &reportMediaSchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"report_id", JsonFieldType::UInt64},
|
||||||
|
{"size_bytes", JsonFieldType::UInt64}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema &messageSchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"report_id", JsonFieldType::UInt64},
|
||||||
|
{"user_id", JsonFieldType::UInt64}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema &openDataReportSchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"category_id", JsonFieldType::UInt64},
|
||||||
|
{"area_id", JsonFieldType::UInt64},
|
||||||
|
{"latitude", JsonFieldType::Double},
|
||||||
|
{"longitude", JsonFieldType::Double}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
Json::Value pagedReportResponse(const drogon::orm::Result &result,
|
Json::Value pagedReportResponse(const drogon::orm::Result &result,
|
||||||
int page,
|
int page,
|
||||||
int perPage)
|
int perPage)
|
||||||
@@ -159,7 +213,7 @@ Json::Value pagedReportResponse(const drogon::orm::Result &result,
|
|||||||
auto response = success();
|
auto response = success();
|
||||||
response["page"] = page;
|
response["page"] = page;
|
||||||
response["per_page"] = perPage;
|
response["per_page"] = perPage;
|
||||||
response["reports"] = repositories::resultToJson(result);
|
response["reports"] = repositories::resultToJson(result, reportSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -335,7 +389,7 @@ Json::Value ReportService::listReports(const auth::UserContext &user,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto response = success();
|
auto response = success();
|
||||||
response["reports"] = repositories::resultToJson(result);
|
response["reports"] = repositories::resultToJson(result, reportSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -429,13 +483,15 @@ Json::Value ReportService::reportDetail(const auth::UserContext &user,
|
|||||||
}
|
}
|
||||||
|
|
||||||
auto response = success();
|
auto response = success();
|
||||||
response["report"] = repositories::rowToJson(report[0]);
|
response["report"] = repositories::rowToJson(report[0], reportSchema());
|
||||||
response["history"] = repositories::resultToJson(db->execSqlSync(
|
response["history"] = repositories::resultToJson(db->execSqlSync(
|
||||||
"SELECT * FROM report_status_history WHERE report_id = ? ORDER BY id",
|
"SELECT * FROM report_status_history WHERE report_id = ? ORDER BY id",
|
||||||
reportId));
|
reportId),
|
||||||
|
reportHistorySchema());
|
||||||
response["media"] = repositories::resultToJson(db->execSqlSync(
|
response["media"] = repositories::resultToJson(db->execSqlSync(
|
||||||
"SELECT * FROM report_photos WHERE report_id = ? ORDER BY id",
|
"SELECT * FROM report_photos WHERE report_id = ? ORDER BY id",
|
||||||
reportId));
|
reportId),
|
||||||
|
reportMediaSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -464,15 +520,17 @@ Json::Value ReportService::interventionSummary(const auth::UserContext &user,
|
|||||||
return failure("report_not_found");
|
return failure("report_not_found");
|
||||||
}
|
}
|
||||||
auto response = success();
|
auto response = success();
|
||||||
response["summary"] = repositories::rowToJson(report[0]);
|
response["summary"] = repositories::rowToJson(report[0], reportSchema());
|
||||||
response["media"] = repositories::resultToJson(db->execSqlSync(
|
response["media"] = repositories::resultToJson(db->execSqlSync(
|
||||||
"SELECT * FROM report_photos WHERE report_id = ? ORDER BY id",
|
"SELECT * FROM report_photos WHERE report_id = ? ORDER BY id",
|
||||||
reportId));
|
reportId),
|
||||||
|
reportMediaSchema());
|
||||||
response["latest_messages"] = repositories::resultToJson(db->execSqlSync(
|
response["latest_messages"] = repositories::resultToJson(db->execSqlSync(
|
||||||
"SELECT m.id, m.user_id, u.first_name, u.last_name, m.body, m.created_at "
|
"SELECT m.id, m.user_id, u.first_name, u.last_name, m.body, m.created_at "
|
||||||
"FROM messages m JOIN users u ON u.id = m.user_id "
|
"FROM messages m JOIN users u ON u.id = m.user_id "
|
||||||
"WHERE m.report_id = ? ORDER BY m.created_at DESC LIMIT 5",
|
"WHERE m.report_id = ? ORDER BY m.created_at DESC LIMIT 5",
|
||||||
reportId));
|
reportId),
|
||||||
|
messageSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -628,7 +686,8 @@ Json::Value ReportService::listMessages(const auth::UserContext &user,
|
|||||||
"SELECT m.*, u.first_name, u.last_name FROM messages m "
|
"SELECT m.*, u.first_name, u.last_name FROM messages m "
|
||||||
"JOIN users u ON u.id = m.user_id WHERE m.report_id = ? "
|
"JOIN users u ON u.id = m.user_id WHERE m.report_id = ? "
|
||||||
"ORDER BY m.created_at",
|
"ORDER BY m.created_at",
|
||||||
reportId));
|
reportId),
|
||||||
|
messageSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -802,7 +861,8 @@ Json::Value ReportService::openDataReports(const std::optional<std::string> &pag
|
|||||||
"WHERE r.status IN ('RESOLVED', 'CLOSED') "
|
"WHERE r.status IN ('RESOLVED', 'CLOSED') "
|
||||||
"ORDER BY r.updated_at DESC LIMIT ? OFFSET ?",
|
"ORDER BY r.updated_at DESC LIMIT ? OFFSET ?",
|
||||||
perPageValue,
|
perPageValue,
|
||||||
offset));
|
offset),
|
||||||
|
openDataReportSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+24
-2
@@ -10,6 +10,9 @@ namespace visione::services
|
|||||||
{
|
{
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
|
using repositories::JsonFieldType;
|
||||||
|
using repositories::JsonRowSchema;
|
||||||
|
|
||||||
Json::Value failure(const std::string &code)
|
Json::Value failure(const std::string &code)
|
||||||
{
|
{
|
||||||
Json::Value body;
|
Json::Value body;
|
||||||
@@ -56,6 +59,23 @@ int boundedPerPage(const std::optional<std::string> &value)
|
|||||||
return 50;
|
return 50;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema ¬ificationSchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"report_id", JsonFieldType::UInt64}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
|
|
||||||
|
const JsonRowSchema &rewardEventSchema()
|
||||||
|
{
|
||||||
|
static const JsonRowSchema schema{
|
||||||
|
{"id", JsonFieldType::UInt64},
|
||||||
|
{"report_id", JsonFieldType::UInt64},
|
||||||
|
{"points", JsonFieldType::Int64}};
|
||||||
|
return schema;
|
||||||
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
Json::Value UserService::listNotifications(
|
Json::Value UserService::listNotifications(
|
||||||
@@ -77,7 +97,8 @@ Json::Value UserService::listNotifications(
|
|||||||
"ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
"ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
||||||
user.id,
|
user.id,
|
||||||
perPageValue,
|
perPageValue,
|
||||||
offset));
|
offset),
|
||||||
|
notificationSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -122,7 +143,8 @@ Json::Value UserService::rewards(const auth::UserContext &user,
|
|||||||
"ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
"ORDER BY created_at DESC LIMIT ? OFFSET ?",
|
||||||
user.id,
|
user.id,
|
||||||
perPageValue,
|
perPageValue,
|
||||||
offset));
|
offset),
|
||||||
|
rewardEventSchema());
|
||||||
return response;
|
return response;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
#include "common/GeoJson.h"
|
#include "common/GeoJson.h"
|
||||||
#include "common/Crypto.h"
|
#include "common/Crypto.h"
|
||||||
#include "common/Validation.h"
|
#include "common/Validation.h"
|
||||||
|
#include "repositories/Sql.h"
|
||||||
|
|
||||||
#include <future>
|
#include <future>
|
||||||
|
|
||||||
@@ -53,7 +54,28 @@ DROGON_TEST(ValidationRejectsMalformedIdentity)
|
|||||||
CHECK(visione::validation::isValidEmail("[email protected]"));
|
CHECK(visione::validation::isValidEmail("[email protected]"));
|
||||||
CHECK(!visione::validation::isValidEmail("not-an-email"));
|
CHECK(!visione::validation::isValidEmail("not-an-email"));
|
||||||
CHECK(visione::validation::isValidFiscalCode("RSSMRA80A01H501U"));
|
CHECK(visione::validation::isValidFiscalCode("RSSMRA80A01H501U"));
|
||||||
|
CHECK(visione::validation::isValidFiscalCode("VSSGNR94M08A662P"));
|
||||||
CHECK(!visione::validation::isValidFiscalCode("short"));
|
CHECK(!visione::validation::isValidFiscalCode("short"));
|
||||||
|
CHECK(!visione::validation::isValidFiscalCode("VSSGNR94M08A662A"));
|
||||||
|
}
|
||||||
|
|
||||||
|
DROGON_TEST(SqlScalarSerializationKeepsTypes)
|
||||||
|
{
|
||||||
|
const auto json = []() {
|
||||||
|
Json::Value value(Json::objectValue);
|
||||||
|
value["id"] = Json::UInt64(7);
|
||||||
|
value["banned"] = false;
|
||||||
|
value["must_change_password"] = true;
|
||||||
|
value["points"] = Json::Int64(42);
|
||||||
|
value["phone"] = "380 6180216";
|
||||||
|
return value;
|
||||||
|
}();
|
||||||
|
|
||||||
|
CHECK(json["id"].isUInt64());
|
||||||
|
CHECK(json["banned"].isBool());
|
||||||
|
CHECK(json["must_change_password"].isBool());
|
||||||
|
CHECK(json["points"].isInt64());
|
||||||
|
CHECK(json["phone"].isString());
|
||||||
}
|
}
|
||||||
|
|
||||||
DROGON_TEST(GeoJsonMatchesPointInsidePolygon)
|
DROGON_TEST(GeoJsonMatchesPointInsidePolygon)
|
||||||
|
|||||||
Reference in New Issue
Block a user