mirror of
https://github.com/aaronpo97/the-biergarten-app.git
synced 2026-07-16 09:37:23 +00:00
Add user and brewery address tables
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
* enriched data, and complete generation results.
|
* enriched data, and complete generation results.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
#include <optional>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
#include "data_model/models.h"
|
#include "data_model/models.h"
|
||||||
@@ -92,22 +93,40 @@ struct EnrichedCity {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief A brewery's street-level address, distinct from the shared `City`
|
* @brief A brewery's address. Owns the `City` it belongs to alongside its
|
||||||
* it belongs to. Mirrors the backend's `BreweryPostLocation` shape.
|
* street-level fields. Mirrors the `brewery_addresses` table and the
|
||||||
|
* backend's `BreweryPostLocation` shape.
|
||||||
*
|
*
|
||||||
* Only `postal_code` is populated today -- street-address generation has no
|
* Only `city` and `postal_code` are populated today -- street-address
|
||||||
* fixture data or service yet.
|
* generation has no fixture data or service yet, so the address lines stay
|
||||||
|
* empty.
|
||||||
*/
|
*/
|
||||||
struct Address {
|
struct BreweryAddress {
|
||||||
|
City city{};
|
||||||
|
std::optional<std::string> address_line_1{};
|
||||||
|
std::optional<std::string> address_line_2{};
|
||||||
std::string postal_code{};
|
std::string postal_code{};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief A user's address. Owns the `City` it belongs to alongside its
|
||||||
|
* street-level fields. Mirrors the `user_addresses` table.
|
||||||
|
*
|
||||||
|
* Only `city` is populated today; the street-level columns exist for future
|
||||||
|
* enrichment.
|
||||||
|
*/
|
||||||
|
struct UserAddress {
|
||||||
|
City city{};
|
||||||
|
std::optional<std::string> address_line_1{};
|
||||||
|
std::optional<std::string> address_line_2{};
|
||||||
|
std::optional<std::string> postal_code{};
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Helper struct to store generated brewery data.
|
* @brief Helper struct to store generated brewery data.
|
||||||
*/
|
*/
|
||||||
struct BreweryRecord {
|
struct BreweryRecord {
|
||||||
City location;
|
BreweryAddress address;
|
||||||
Address address;
|
|
||||||
BreweryResult brewery;
|
BreweryResult brewery;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -119,7 +138,7 @@ struct BreweryRecord {
|
|||||||
* consumer can register real accounts from the pipeline's SQLite export.
|
* consumer can register real accounts from the pipeline's SQLite export.
|
||||||
*/
|
*/
|
||||||
struct UserRecord {
|
struct UserRecord {
|
||||||
City location;
|
UserAddress address{};
|
||||||
UserResult user;
|
UserResult user;
|
||||||
std::string email{};
|
std::string email{};
|
||||||
std::string date_of_birth{};
|
std::string date_of_birth{};
|
||||||
|
|||||||
@@ -60,16 +60,6 @@ struct City {
|
|||||||
* @brief Local language codes in priority order.
|
* @brief Local language codes in priority order.
|
||||||
*/
|
*/
|
||||||
std::vector<std::string> local_languages{};
|
std::vector<std::string> local_languages{};
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Latitude in decimal degrees.
|
|
||||||
*/
|
|
||||||
double latitude{};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @brief Longitude in decimal degrees.
|
|
||||||
*/
|
|
||||||
double longitude{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|||||||
@@ -63,7 +63,9 @@ class SqliteExportService final : public IExportService {
|
|||||||
SqliteDatabaseHandle db_handle_;
|
SqliteDatabaseHandle db_handle_;
|
||||||
SqliteStatementHandle insert_city_stmt_;
|
SqliteStatementHandle insert_city_stmt_;
|
||||||
SqliteStatementHandle insert_brewery_stmt_;
|
SqliteStatementHandle insert_brewery_stmt_;
|
||||||
|
SqliteStatementHandle insert_brewery_address_stmt_;
|
||||||
SqliteStatementHandle insert_user_stmt_;
|
SqliteStatementHandle insert_user_stmt_;
|
||||||
|
SqliteStatementHandle insert_user_address_stmt_;
|
||||||
bool transaction_open_ = false;
|
bool transaction_open_ = false;
|
||||||
std::unordered_map<std::string, sqlite3_int64> city_cache_;
|
std::unordered_map<std::string, sqlite3_int64> city_cache_;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -27,9 +27,7 @@ CREATE TABLE IF NOT EXISTS cities (
|
|||||||
country TEXT NOT NULL,
|
country TEXT NOT NULL,
|
||||||
iso3166_1 TEXT NOT NULL,
|
iso3166_1 TEXT NOT NULL,
|
||||||
local_languages_json TEXT NOT NULL,
|
local_languages_json TEXT NOT NULL,
|
||||||
latitude REAL NOT NULL,
|
UNIQUE(city, state_province, iso3166_2, country)
|
||||||
longitude REAL NOT NULL,
|
|
||||||
UNIQUE(city, state_province, iso3166_2, country, latitude, longitude)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
)sql";
|
)sql";
|
||||||
@@ -43,19 +41,16 @@ CREATE TABLE IF NOT EXISTS breweries (
|
|||||||
description_en TEXT NOT NULL,
|
description_en TEXT NOT NULL,
|
||||||
name_local TEXT NOT NULL,
|
name_local TEXT NOT NULL,
|
||||||
description_local TEXT NOT NULL,
|
description_local TEXT NOT NULL,
|
||||||
postal_code TEXT NOT NULL,
|
|
||||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_breweries_city_id ON breweries(city_id);
|
CREATE INDEX IF NOT EXISTS idx_breweries_city_id ON breweries(city_id);
|
||||||
|
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
inline constexpr std::string_view kCreateUsersTableSql = R"sql(
|
inline constexpr std::string_view kCreateUsersTableSql = R"sql(
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
city_id INTEGER NOT NULL,
|
|
||||||
first_name TEXT NOT NULL,
|
first_name TEXT NOT NULL,
|
||||||
last_name TEXT NOT NULL,
|
last_name TEXT NOT NULL,
|
||||||
gender TEXT NOT NULL,
|
gender TEXT NOT NULL,
|
||||||
@@ -63,12 +58,45 @@ CREATE TABLE IF NOT EXISTS users (
|
|||||||
bio TEXT NOT NULL,
|
bio TEXT NOT NULL,
|
||||||
activity_weight REAL NOT NULL,
|
activity_weight REAL NOT NULL,
|
||||||
email TEXT NOT NULL UNIQUE,
|
email TEXT NOT NULL UNIQUE,
|
||||||
date_of_birth TEXT NOT NULL,
|
date_of_birth TEXT NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
)sql";
|
||||||
|
|
||||||
|
inline constexpr std::string_view kCreateBreweryAddressTableSql = R"sql(
|
||||||
|
CREATE TABLE IF NOT EXISTS brewery_addresses (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
brewery_id INTEGER NOT NULL,
|
||||||
|
address_line_1 TEXT,
|
||||||
|
address_line_2 TEXT,
|
||||||
|
postal_code TEXT NOT NULL,
|
||||||
|
city_id INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(brewery_id) REFERENCES breweries(id) ON DELETE CASCADE,
|
||||||
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX IF NOT EXISTS idx_users_city_id ON users(city_id);
|
CREATE INDEX IF NOT EXISTS idx_brewery_addresses_brewery_id
|
||||||
|
ON brewery_addresses(brewery_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_brewery_addresses_city_id
|
||||||
|
ON brewery_addresses(city_id);
|
||||||
|
)sql";
|
||||||
|
|
||||||
|
inline constexpr std::string_view kCreateUserAddressTableSql = R"sql(
|
||||||
|
CREATE TABLE IF NOT EXISTS user_addresses (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
user_id INTEGER NOT NULL,
|
||||||
|
address_line_1 TEXT,
|
||||||
|
address_line_2 TEXT,
|
||||||
|
postal_code TEXT,
|
||||||
|
city_id INTEGER NOT NULL,
|
||||||
|
FOREIGN KEY(user_id) REFERENCES users(id) ON DELETE CASCADE,
|
||||||
|
FOREIGN KEY(city_id) REFERENCES cities(id) ON DELETE CASCADE
|
||||||
|
);
|
||||||
|
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_addresses_user_id
|
||||||
|
ON user_addresses(user_id);
|
||||||
|
CREATE INDEX IF NOT EXISTS idx_user_addresses_city_id
|
||||||
|
ON user_addresses(city_id);
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
inline constexpr std::string_view kInsertCitySql = R"sql(
|
inline constexpr std::string_view kInsertCitySql = R"sql(
|
||||||
@@ -78,10 +106,8 @@ INSERT INTO cities (
|
|||||||
iso3166_2,
|
iso3166_2,
|
||||||
country,
|
country,
|
||||||
iso3166_1,
|
iso3166_1,
|
||||||
local_languages_json,
|
local_languages_json
|
||||||
latitude,
|
) VALUES (?, ?, ?, ?, ?, ?);
|
||||||
longitude
|
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
inline constexpr std::string_view kInsertBrewerySql = R"sql(
|
inline constexpr std::string_view kInsertBrewerySql = R"sql(
|
||||||
@@ -90,14 +116,12 @@ INSERT INTO breweries (
|
|||||||
name_en,
|
name_en,
|
||||||
description_en,
|
description_en,
|
||||||
name_local,
|
name_local,
|
||||||
description_local,
|
description_local
|
||||||
postal_code
|
) VALUES (?, ?, ?, ?, ?);
|
||||||
) VALUES (?, ?, ?, ?, ?, ?);
|
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
inline constexpr std::string_view kInsertUserSql = R"sql(
|
inline constexpr std::string_view kInsertUserSql = R"sql(
|
||||||
INSERT INTO users (
|
INSERT INTO users (
|
||||||
city_id,
|
|
||||||
first_name,
|
first_name,
|
||||||
last_name,
|
last_name,
|
||||||
gender,
|
gender,
|
||||||
@@ -106,7 +130,22 @@ INSERT INTO users (
|
|||||||
activity_weight,
|
activity_weight,
|
||||||
email,
|
email,
|
||||||
date_of_birth
|
date_of_birth
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?);
|
||||||
|
)sql";
|
||||||
|
|
||||||
|
inline constexpr std::string_view kInsertBreweryAddressSql = R"sql(
|
||||||
|
INSERT INTO brewery_addresses (
|
||||||
|
brewery_id,
|
||||||
|
postal_code,
|
||||||
|
city_id
|
||||||
|
) VALUES (?, ?, ?);
|
||||||
|
)sql";
|
||||||
|
|
||||||
|
inline constexpr std::string_view kInsertUserAddressSql = R"sql(
|
||||||
|
INSERT INTO user_addresses (
|
||||||
|
user_id,
|
||||||
|
city_id
|
||||||
|
) VALUES (?, ?);
|
||||||
)sql";
|
)sql";
|
||||||
|
|
||||||
// sqlite3_bind_*() parameter indices are 1-based, matching the "?"
|
// sqlite3_bind_*() parameter indices are 1-based, matching the "?"
|
||||||
@@ -118,8 +157,6 @@ enum CityBindIndex {
|
|||||||
kCityCountryBindIndex,
|
kCityCountryBindIndex,
|
||||||
kCityIso31661BindIndex,
|
kCityIso31661BindIndex,
|
||||||
kCityLanguagesBindIndex,
|
kCityLanguagesBindIndex,
|
||||||
kCityLatitudeBindIndex,
|
|
||||||
kCityLongitudeBindIndex,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum BreweryBindIndex {
|
enum BreweryBindIndex {
|
||||||
@@ -128,12 +165,10 @@ enum BreweryBindIndex {
|
|||||||
kBreweryEnglishDescriptionBindIndex,
|
kBreweryEnglishDescriptionBindIndex,
|
||||||
kBreweryLocalNameBindIndex,
|
kBreweryLocalNameBindIndex,
|
||||||
kBreweryLocalDescriptionBindIndex,
|
kBreweryLocalDescriptionBindIndex,
|
||||||
kBreweryPostalCodeBindIndex,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
enum UserBindIndex {
|
enum UserBindIndex {
|
||||||
kUserCityIdBindIndex = 1,
|
kUserFirstNameBindIndex = 1,
|
||||||
kUserFirstNameBindIndex,
|
|
||||||
kUserLastNameBindIndex,
|
kUserLastNameBindIndex,
|
||||||
kUserGenderBindIndex,
|
kUserGenderBindIndex,
|
||||||
kUserUsernameBindIndex,
|
kUserUsernameBindIndex,
|
||||||
@@ -143,6 +178,17 @@ enum UserBindIndex {
|
|||||||
kUserDateOfBirthBindIndex,
|
kUserDateOfBirthBindIndex,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
enum BreweryAddressBindIndex {
|
||||||
|
kBreweryAddressBreweryIdBindIndex = 1,
|
||||||
|
kBreweryAddressPostalCodeBindIndex,
|
||||||
|
kBreweryAddressCityIdBindIndex,
|
||||||
|
};
|
||||||
|
|
||||||
|
enum UserAddressBindIndex {
|
||||||
|
kUserAddressUserIdBindIndex = 1,
|
||||||
|
kUserAddressCityIdBindIndex,
|
||||||
|
};
|
||||||
|
|
||||||
SqliteStatementHandle PrepareStatement(const SqliteDatabaseHandle& db_handle,
|
SqliteStatementHandle PrepareStatement(const SqliteDatabaseHandle& db_handle,
|
||||||
std::string_view sql,
|
std::string_view sql,
|
||||||
const char* action);
|
const char* action);
|
||||||
|
|||||||
@@ -29,8 +29,9 @@ void BiergartenPipelineOrchestrator::GenerateBreweries(
|
|||||||
generator_->GenerateBrewery(location, region_context);
|
generator_->GenerateBrewery(location, region_context);
|
||||||
const std::string postal_code =
|
const std::string postal_code =
|
||||||
postal_code_service_->GeneratePostalCode(location);
|
postal_code_service_->GeneratePostalCode(location);
|
||||||
return BreweryRecord{.location = location,
|
return BreweryRecord{
|
||||||
.address = Address{.postal_code = postal_code},
|
.address =
|
||||||
|
BreweryAddress{.city = location, .postal_code = postal_code},
|
||||||
.brewery = brewery};
|
.brewery = brewery};
|
||||||
} catch (const std::exception& e) {
|
} catch (const std::exception& e) {
|
||||||
++skipped_count;
|
++skipped_count;
|
||||||
@@ -51,12 +52,12 @@ void BiergartenPipelineOrchestrator::GenerateBreweries(
|
|||||||
exporter_->ProcessRecord(record);
|
exporter_->ProcessRecord(record);
|
||||||
} catch (const std::exception& export_exception) {
|
} catch (const std::exception& export_exception) {
|
||||||
++export_failed_count;
|
++export_failed_count;
|
||||||
logger_->Log(
|
logger_->Log({.level = LogLevel::Warn,
|
||||||
{.level = LogLevel::Warn,
|
|
||||||
.phase = PipelinePhase::BreweryAndBeerGeneration,
|
.phase = PipelinePhase::BreweryAndBeerGeneration,
|
||||||
.message = std::format("[Pipeline] Generated brewery for '{}' ({}) "
|
.message = std::format(
|
||||||
|
"[Pipeline] Generated brewery for '{}' ({}) "
|
||||||
"but SQLite export failed: {}",
|
"but SQLite export failed: {}",
|
||||||
record.location.city, record.location.country,
|
record.address.city.city, record.address.city.country,
|
||||||
export_exception.what())});
|
export_exception.what())});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -137,7 +137,7 @@ void BiergartenPipelineOrchestrator::GenerateUsers(
|
|||||||
generator_->GenerateUser(city, persona, sampled_name);
|
generator_->GenerateUser(city, persona, sampled_name);
|
||||||
|
|
||||||
return UserRecord{
|
return UserRecord{
|
||||||
.location = city.location,
|
.address = UserAddress{.city = city.location},
|
||||||
.user = user,
|
.user = user,
|
||||||
.email = BuildEmail(sampled_name, used_email_local_parts),
|
.email = BuildEmail(sampled_name, used_email_local_parts),
|
||||||
.date_of_birth = GenerateDateOfBirth(rng),
|
.date_of_birth = GenerateDateOfBirth(rng),
|
||||||
@@ -161,12 +161,12 @@ void BiergartenPipelineOrchestrator::GenerateUsers(
|
|||||||
} catch (const std::exception& export_exception) {
|
} catch (const std::exception& export_exception) {
|
||||||
++export_failed_count;
|
++export_failed_count;
|
||||||
|
|
||||||
logger_->Log(
|
logger_->Log({.level = LogLevel::Warn,
|
||||||
{.level = LogLevel::Warn,
|
|
||||||
.phase = PipelinePhase::UserGeneration,
|
.phase = PipelinePhase::UserGeneration,
|
||||||
.message = std::format("[Pipeline] Generated user for '{}' ({}) but "
|
.message = std::format(
|
||||||
|
"[Pipeline] Generated user for '{}' ({}) but "
|
||||||
"SQLite export failed: {}",
|
"SQLite export failed: {}",
|
||||||
record.location.city, record.location.country,
|
record.address.city.city, record.address.city.country,
|
||||||
export_exception.what())});
|
export_exception.what())});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -14,7 +14,8 @@
|
|||||||
void BiergartenPipelineOrchestrator::LogResults() const {
|
void BiergartenPipelineOrchestrator::LogResults() const {
|
||||||
boost::json::array brewery_output;
|
boost::json::array brewery_output;
|
||||||
|
|
||||||
for (const auto& [location, address, brewery] : generated_breweries_) {
|
for (const auto& [address, brewery] : generated_breweries_) {
|
||||||
|
const City& location = address.city;
|
||||||
brewery_output.push_back(boost::json::object{
|
brewery_output.push_back(boost::json::object{
|
||||||
{"name_en", brewery.name_en},
|
{"name_en", brewery.name_en},
|
||||||
{"description_en", brewery.description_en},
|
{"description_en", brewery.description_en},
|
||||||
@@ -26,8 +27,6 @@ void BiergartenPipelineOrchestrator::LogResults() const {
|
|||||||
{"country", location.country},
|
{"country", location.country},
|
||||||
{"state_province", location.state_province},
|
{"state_province", location.state_province},
|
||||||
{"iso3166_2", location.iso3166_2},
|
{"iso3166_2", location.iso3166_2},
|
||||||
{"latitude", location.latitude},
|
|
||||||
{"longitude", location.longitude},
|
|
||||||
}}});
|
}}});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -136,8 +136,7 @@ const CityList& CuratedJsonDataService::LoadCities() {
|
|||||||
|
|
||||||
const boost::json::value* postal_code = object.if_contains("postal_code");
|
const boost::json::value* postal_code = object.if_contains("postal_code");
|
||||||
if (postal_code == nullptr || !postal_code->is_object()) {
|
if (postal_code == nullptr || !postal_code->is_object()) {
|
||||||
throw std::runtime_error(
|
throw std::runtime_error("Missing or invalid object field: postal_code");
|
||||||
"Missing or invalid object field: postal_code");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
locations.push_back(City{
|
locations.push_back(City{
|
||||||
@@ -147,8 +146,6 @@ const CityList& CuratedJsonDataService::LoadCities() {
|
|||||||
.country = ReadRequiredString(object, "country"),
|
.country = ReadRequiredString(object, "country"),
|
||||||
.iso3166_1 = ReadRequiredString(object, "iso3166_1"),
|
.iso3166_1 = ReadRequiredString(object, "iso3166_1"),
|
||||||
.local_languages = ReadRequiredStringArray(object, "local_languages"),
|
.local_languages = ReadRequiredStringArray(object, "local_languages"),
|
||||||
.latitude = ReadRequiredNumber(object, "latitude"),
|
|
||||||
.longitude = ReadRequiredNumber(object, "longitude"),
|
|
||||||
.postal_regex =
|
.postal_regex =
|
||||||
ReadRequiredStringArray(postal_code->as_object(), "city_regex"),
|
ReadRequiredStringArray(postal_code->as_object(), "city_regex"),
|
||||||
.postal_code_examples =
|
.postal_code_examples =
|
||||||
@@ -159,13 +156,21 @@ const CityList& CuratedJsonDataService::LoadCities() {
|
|||||||
for (auto location : locations) {
|
for (auto location : locations) {
|
||||||
std::cout << "Location: " << location.city << ", "
|
std::cout << "Location: " << location.city << ", "
|
||||||
<< location.state_province << ", " << location.iso3166_2 << ", "
|
<< location.state_province << ", " << location.iso3166_2 << ", "
|
||||||
<< location.country << ", " << location.iso3166_1 << ", "
|
<< location.country << ", " << location.iso3166_1 << "\n";
|
||||||
<< location.latitude << ", " << location.longitude << std::endl;
|
|
||||||
|
|
||||||
for (const auto& regex : location.postal_regex) {
|
for (const auto& regex : location.postal_regex) {
|
||||||
std::cout << " Postal regex: " << regex << std::endl;
|
std::cout << " Postal regex: " << regex << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& example : location.postal_code_examples) {
|
||||||
|
std::cout << " Postal example: " << example << "\n";
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const auto& lang : location.local_languages) {
|
||||||
|
std::cout << " Local language: " << lang << "\n";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cache_.locations = std::move(locations);
|
cache_.locations = std::move(locations);
|
||||||
return cache_.locations;
|
return cache_.locations;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,44 +7,36 @@
|
|||||||
|
|
||||||
MockCuratedDataService::MockCuratedDataService()
|
MockCuratedDataService::MockCuratedDataService()
|
||||||
: locations_{
|
: locations_{
|
||||||
City{.city = "Portland",
|
{.city = "Portland",
|
||||||
.state_province = "Oregon",
|
.state_province = "Oregon",
|
||||||
.iso3166_2 = "US-OR",
|
.iso3166_2 = "US-OR",
|
||||||
.country = "United States",
|
.country = "United States",
|
||||||
.iso3166_1 = "US",
|
.iso3166_1 = "US",
|
||||||
.local_languages = {"en"},
|
.local_languages = {"en"},
|
||||||
.latitude = 45.5152,
|
|
||||||
.longitude = -122.6784,
|
|
||||||
.postal_regex = {"^972[0-9]{2}(?:-[0-9]{4})?$"},
|
.postal_regex = {"^972[0-9]{2}(?:-[0-9]{4})?$"},
|
||||||
.postal_code_examples = {"97201", "97294"}},
|
.postal_code_examples = {"97201", "97294"}},
|
||||||
City{.city = "Munich",
|
{.city = "Munich",
|
||||||
.state_province = "Bavaria",
|
.state_province = "Bavaria",
|
||||||
.iso3166_2 = "DE-BY",
|
.iso3166_2 = "DE-BY",
|
||||||
.country = "Germany",
|
.country = "Germany",
|
||||||
.iso3166_1 = "DE",
|
.iso3166_1 = "DE",
|
||||||
.local_languages = {"de"},
|
.local_languages = {"de"},
|
||||||
.latitude = 48.1351,
|
|
||||||
.longitude = 11.5820,
|
|
||||||
.postal_regex = {"^8[01][0-9]{3}$"},
|
.postal_regex = {"^8[01][0-9]{3}$"},
|
||||||
.postal_code_examples = {"80331", "81929"}},
|
.postal_code_examples = {"80331", "81929"}},
|
||||||
City{.city = "Lyon",
|
{.city = "Lyon",
|
||||||
.state_province = "Auvergne-Rhone-Alpes",
|
.state_province = "Auvergne-Rhone-Alpes",
|
||||||
.iso3166_2 = "FR-ARA",
|
.iso3166_2 = "FR-ARA",
|
||||||
.country = "France",
|
.country = "France",
|
||||||
.iso3166_1 = "FR",
|
.iso3166_1 = "FR",
|
||||||
.local_languages = {"fr"},
|
.local_languages = {"fr"},
|
||||||
.latitude = 45.7640,
|
|
||||||
.longitude = 4.8357,
|
|
||||||
.postal_regex = {"^6900[1-9]$"},
|
.postal_regex = {"^6900[1-9]$"},
|
||||||
.postal_code_examples = {"69001", "69009"}},
|
.postal_code_examples = {"69001", "69009"}},
|
||||||
City{.city = "Brussels",
|
{.city = "Brussels",
|
||||||
.state_province = "Brussels-Capital",
|
.state_province = "Brussels-Capital",
|
||||||
.iso3166_2 = "BE-BRU",
|
.iso3166_2 = "BE-BRU",
|
||||||
.country = "Belgium",
|
.country = "Belgium",
|
||||||
.iso3166_1 = "BE",
|
.iso3166_1 = "BE",
|
||||||
.local_languages = {"nl", "fr"},
|
.local_languages = {"nl", "fr"},
|
||||||
.latitude = 50.8503,
|
|
||||||
.longitude = 4.3517,
|
|
||||||
.postal_regex = {"^1(?:0[0-9]{2}|1[0-9]{2}|20[0-9]|210)$"},
|
.postal_regex = {"^1(?:0[0-9]{2}|1[0-9]{2}|20[0-9]|210)$"},
|
||||||
.postal_code_examples = {"1000", "1210"}},
|
.postal_code_examples = {"1000", "1210"}},
|
||||||
},
|
},
|
||||||
@@ -91,9 +83,7 @@ MockCuratedDataService::MockCuratedDataService()
|
|||||||
{"BE", SurnameList{"Peeters", "Janssens"}},
|
{"BE", SurnameList{"Peeters", "Janssens"}},
|
||||||
} {}
|
} {}
|
||||||
|
|
||||||
const CityList& MockCuratedDataService::LoadCities() {
|
const CityList& MockCuratedDataService::LoadCities() { return locations_; }
|
||||||
return locations_;
|
|
||||||
}
|
|
||||||
|
|
||||||
const PersonasList& MockCuratedDataService::LoadPersonas() { return personas_; }
|
const PersonasList& MockCuratedDataService::LoadPersonas() { return personas_; }
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ void SqliteExportService::Finalize() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
insert_user_address_stmt_.reset();
|
||||||
insert_user_stmt_.reset();
|
insert_user_stmt_.reset();
|
||||||
|
insert_brewery_address_stmt_.reset();
|
||||||
insert_brewery_stmt_.reset();
|
insert_brewery_stmt_.reset();
|
||||||
insert_city_stmt_.reset();
|
insert_city_stmt_.reset();
|
||||||
if (transaction_open_) {
|
if (transaction_open_) {
|
||||||
|
|||||||
@@ -36,6 +36,12 @@ void SqliteExportService::InitializeSchema() const {
|
|||||||
sqlite_export_service_internal::ExecSql(
|
sqlite_export_service_internal::ExecSql(
|
||||||
db_handle_, sqlite_export_service_internal::kCreateUsersTableSql,
|
db_handle_, sqlite_export_service_internal::kCreateUsersTableSql,
|
||||||
"Failed to create SQLite users table");
|
"Failed to create SQLite users table");
|
||||||
|
sqlite_export_service_internal::ExecSql(
|
||||||
|
db_handle_, sqlite_export_service_internal::kCreateBreweryAddressTableSql,
|
||||||
|
"Failed to create SQLite brewery addresses table");
|
||||||
|
sqlite_export_service_internal::ExecSql(
|
||||||
|
db_handle_, sqlite_export_service_internal::kCreateUserAddressTableSql,
|
||||||
|
"Failed to create SQLite user addresses table");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SqliteExportService::PrepareStatements() {
|
void SqliteExportService::PrepareStatements() {
|
||||||
@@ -45,9 +51,16 @@ void SqliteExportService::PrepareStatements() {
|
|||||||
insert_brewery_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
insert_brewery_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
||||||
db_handle_, sqlite_export_service_internal::kInsertBrewerySql,
|
db_handle_, sqlite_export_service_internal::kInsertBrewerySql,
|
||||||
"Failed to prepare SQLite brewery insert statement");
|
"Failed to prepare SQLite brewery insert statement");
|
||||||
|
insert_brewery_address_stmt_ =
|
||||||
|
sqlite_export_service_internal::PrepareStatement(
|
||||||
|
db_handle_, sqlite_export_service_internal::kInsertBreweryAddressSql,
|
||||||
|
"Failed to prepare SQLite brewery address insert statement");
|
||||||
insert_user_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
insert_user_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
||||||
db_handle_, sqlite_export_service_internal::kInsertUserSql,
|
db_handle_, sqlite_export_service_internal::kInsertUserSql,
|
||||||
"Failed to prepare SQLite user insert statement");
|
"Failed to prepare SQLite user insert statement");
|
||||||
|
insert_user_address_stmt_ = sqlite_export_service_internal::PrepareStatement(
|
||||||
|
db_handle_, sqlite_export_service_internal::kInsertUserAddressSql,
|
||||||
|
"Failed to prepare SQLite user address insert statement");
|
||||||
}
|
}
|
||||||
|
|
||||||
void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
|
void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
|
||||||
@@ -60,7 +73,9 @@ void SqliteExportService::RollbackAndCloseNoThrow() noexcept {
|
|||||||
transaction_open_ = false;
|
transaction_open_ = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
insert_user_address_stmt_.reset();
|
||||||
insert_user_stmt_.reset();
|
insert_user_stmt_.reset();
|
||||||
|
insert_brewery_address_stmt_.reset();
|
||||||
insert_brewery_stmt_.reset();
|
insert_brewery_stmt_.reset();
|
||||||
insert_city_stmt_.reset();
|
insert_city_stmt_.reset();
|
||||||
db_handle_.reset();
|
db_handle_.reset();
|
||||||
|
|||||||
@@ -4,7 +4,6 @@
|
|||||||
* and the shared city-resolution helper.
|
* and the shared city-resolution helper.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include <iomanip>
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <stdexcept>
|
#include <stdexcept>
|
||||||
#include <string>
|
#include <string>
|
||||||
@@ -12,8 +11,6 @@
|
|||||||
#include "services/database/sqlite_export_service.h"
|
#include "services/database/sqlite_export_service.h"
|
||||||
#include "services/database/sqlite_export_service_helpers.h"
|
#include "services/database/sqlite_export_service_helpers.h"
|
||||||
|
|
||||||
constexpr int kLocationPrecision = 17;
|
|
||||||
|
|
||||||
std::string SqliteExportService::BuildCityKey(const City& location) {
|
std::string SqliteExportService::BuildCityKey(const City& location) {
|
||||||
std::ostringstream key_stream;
|
std::ostringstream key_stream;
|
||||||
key_stream << location.city << '\n'
|
key_stream << location.city << '\n'
|
||||||
@@ -21,10 +18,6 @@ std::string SqliteExportService::BuildCityKey(const City& location) {
|
|||||||
<< location.iso3166_2 << '\n'
|
<< location.iso3166_2 << '\n'
|
||||||
<< location.country << '\n'
|
<< location.country << '\n'
|
||||||
<< location.iso3166_1 << '\n'
|
<< location.iso3166_1 << '\n'
|
||||||
<< std::setprecision(kLocationPrecision) << location.latitude
|
|
||||||
<< '\n'
|
|
||||||
<< std::setprecision(kLocationPrecision) << location.longitude
|
|
||||||
<< '\n'
|
|
||||||
<< sqlite_export_service_internal::SerializeVector(
|
<< sqlite_export_service_internal::SerializeVector(
|
||||||
location.local_languages);
|
location.local_languages);
|
||||||
return key_stream.str();
|
return key_stream.str();
|
||||||
@@ -49,8 +42,7 @@ sqlite3_int64 SqliteExportService::ResolveCityId(const City& location) {
|
|||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_city_stmt_,
|
insert_city_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BoundParam<std::string_view>{
|
||||||
.index =
|
.index = sqlite_export_service_internal::kCityStateProvinceBindIndex,
|
||||||
sqlite_export_service_internal::kCityStateProvinceBindIndex,
|
|
||||||
.value = location.state_province,
|
.value = location.state_province,
|
||||||
.action = "Failed to bind SQLite city state/province"});
|
.action = "Failed to bind SQLite city state/province"});
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
@@ -77,22 +69,9 @@ sqlite3_int64 SqliteExportService::ResolveCityId(const City& location) {
|
|||||||
.index = sqlite_export_service_internal::kCityLanguagesBindIndex,
|
.index = sqlite_export_service_internal::kCityLanguagesBindIndex,
|
||||||
.value = local_languages_json,
|
.value = local_languages_json,
|
||||||
.action = "Failed to bind SQLite city languages"});
|
.action = "Failed to bind SQLite city languages"});
|
||||||
sqlite_export_service_internal::Bind(
|
|
||||||
insert_city_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam{
|
|
||||||
.index = sqlite_export_service_internal::kCityLatitudeBindIndex,
|
|
||||||
.value = location.latitude,
|
|
||||||
.action = "Failed to bind SQLite city latitude"});
|
|
||||||
sqlite_export_service_internal::Bind(
|
|
||||||
insert_city_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam{
|
|
||||||
.index = sqlite_export_service_internal::kCityLongitudeBindIndex,
|
|
||||||
.value = location.longitude,
|
|
||||||
.action = "Failed to bind SQLite city longitude"});
|
|
||||||
|
|
||||||
sqlite_export_service_internal::StepStatement(
|
sqlite_export_service_internal::StepStatement(
|
||||||
db_handle_, insert_city_stmt_,
|
db_handle_, insert_city_stmt_, "Failed to insert SQLite city row");
|
||||||
"Failed to insert SQLite city row");
|
|
||||||
|
|
||||||
const sqlite3_int64 city_id =
|
const sqlite3_int64 city_id =
|
||||||
sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
||||||
@@ -107,7 +86,7 @@ uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
|
|||||||
throw std::runtime_error("SQLite export service is not initialized");
|
throw std::runtime_error("SQLite export service is not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const sqlite3_int64 city_id = ResolveCityId(brewery.location);
|
const sqlite3_int64 city_id = ResolveCityId(brewery.address.city);
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_brewery_stmt_,
|
insert_brewery_stmt_,
|
||||||
@@ -146,17 +125,40 @@ uint64_t SqliteExportService::ProcessRecord(const BreweryRecord& brewery) {
|
|||||||
.value = brewery.brewery.description_local,
|
.value = brewery.brewery.description_local,
|
||||||
.action = "Failed to bind SQLite brewery local description"});
|
.action = "Failed to bind SQLite brewery local description"});
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
|
||||||
insert_brewery_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
|
||||||
.index = sqlite_export_service_internal::kBreweryPostalCodeBindIndex,
|
|
||||||
.value = brewery.address.postal_code,
|
|
||||||
.action = "Failed to bind SQLite brewery postal code"});
|
|
||||||
|
|
||||||
sqlite_export_service_internal::StepStatement(
|
sqlite_export_service_internal::StepStatement(
|
||||||
db_handle_, insert_brewery_stmt_, "Failed to insert SQLite brewery row");
|
db_handle_, insert_brewery_stmt_, "Failed to insert SQLite brewery row");
|
||||||
|
|
||||||
|
const sqlite3_int64 brewery_id =
|
||||||
|
sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
||||||
sqlite_export_service_internal::ResetStatement(insert_brewery_stmt_);
|
sqlite_export_service_internal::ResetStatement(insert_brewery_stmt_);
|
||||||
|
|
||||||
return sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
sqlite_export_service_internal::Bind(
|
||||||
|
insert_brewery_address_stmt_,
|
||||||
|
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
||||||
|
.index =
|
||||||
|
sqlite_export_service_internal::kBreweryAddressBreweryIdBindIndex,
|
||||||
|
.value = brewery_id,
|
||||||
|
.action = "Failed to bind SQLite brewery address brewery id"});
|
||||||
|
sqlite_export_service_internal::Bind(
|
||||||
|
insert_brewery_address_stmt_,
|
||||||
|
sqlite_export_service_internal::BoundParam<std::string_view>{
|
||||||
|
.index = sqlite_export_service_internal::
|
||||||
|
kBreweryAddressPostalCodeBindIndex,
|
||||||
|
.value = brewery.address.postal_code,
|
||||||
|
.action = "Failed to bind SQLite brewery address postal code"});
|
||||||
|
sqlite_export_service_internal::Bind(
|
||||||
|
insert_brewery_address_stmt_,
|
||||||
|
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
||||||
|
.index =
|
||||||
|
sqlite_export_service_internal::kBreweryAddressCityIdBindIndex,
|
||||||
|
.value = city_id,
|
||||||
|
.action = "Failed to bind SQLite brewery address city id"});
|
||||||
|
|
||||||
|
sqlite_export_service_internal::StepStatement(
|
||||||
|
db_handle_, insert_brewery_address_stmt_,
|
||||||
|
"Failed to insert SQLite brewery address row");
|
||||||
|
|
||||||
|
sqlite_export_service_internal::ResetStatement(insert_brewery_address_stmt_);
|
||||||
|
|
||||||
|
return brewery_id;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,14 +13,8 @@ uint64_t SqliteExportService::ProcessRecord(const UserRecord& user) {
|
|||||||
throw std::runtime_error("SQLite export service is not initialized");
|
throw std::runtime_error("SQLite export service is not initialized");
|
||||||
}
|
}
|
||||||
|
|
||||||
const sqlite3_int64 city_id = ResolveCityId(user.location);
|
const sqlite3_int64 city_id = ResolveCityId(user.address.city);
|
||||||
|
|
||||||
sqlite_export_service_internal::Bind(
|
|
||||||
insert_user_stmt_,
|
|
||||||
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
|
||||||
.index = sqlite_export_service_internal::kUserCityIdBindIndex,
|
|
||||||
.value = city_id,
|
|
||||||
.action = "Failed to bind SQLite user city id"});
|
|
||||||
sqlite_export_service_internal::Bind(
|
sqlite_export_service_internal::Bind(
|
||||||
insert_user_stmt_,
|
insert_user_stmt_,
|
||||||
sqlite_export_service_internal::BoundParam<std::string_view>{
|
sqlite_export_service_internal::BoundParam<std::string_view>{
|
||||||
@@ -73,7 +67,28 @@ uint64_t SqliteExportService::ProcessRecord(const UserRecord& user) {
|
|||||||
sqlite_export_service_internal::StepStatement(
|
sqlite_export_service_internal::StepStatement(
|
||||||
db_handle_, insert_user_stmt_, "Failed to insert SQLite user row");
|
db_handle_, insert_user_stmt_, "Failed to insert SQLite user row");
|
||||||
|
|
||||||
|
const sqlite3_int64 user_id =
|
||||||
|
sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
||||||
sqlite_export_service_internal::ResetStatement(insert_user_stmt_);
|
sqlite_export_service_internal::ResetStatement(insert_user_stmt_);
|
||||||
|
|
||||||
return sqlite_export_service_internal::LastInsertRowId(db_handle_);
|
sqlite_export_service_internal::Bind(
|
||||||
|
insert_user_address_stmt_,
|
||||||
|
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
||||||
|
.index = sqlite_export_service_internal::kUserAddressUserIdBindIndex,
|
||||||
|
.value = user_id,
|
||||||
|
.action = "Failed to bind SQLite user address user id"});
|
||||||
|
sqlite_export_service_internal::Bind(
|
||||||
|
insert_user_address_stmt_,
|
||||||
|
sqlite_export_service_internal::BoundParam<sqlite3_int64>{
|
||||||
|
.index = sqlite_export_service_internal::kUserAddressCityIdBindIndex,
|
||||||
|
.value = city_id,
|
||||||
|
.action = "Failed to bind SQLite user address city id"});
|
||||||
|
|
||||||
|
sqlite_export_service_internal::StepStatement(
|
||||||
|
db_handle_, insert_user_address_stmt_,
|
||||||
|
"Failed to insert SQLite user address row");
|
||||||
|
|
||||||
|
sqlite_export_service_internal::ResetStatement(insert_user_address_stmt_);
|
||||||
|
|
||||||
|
return user_id;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user