Jacob Schmidt 06c634c642 Move extension storage to SurrealDB schema modules
- Split storage repositories into per-domain modules
- Add SurrealDB schema definitions and route handlers
- Update phone docs for new persistent table layout
2026-04-16 19:55:05 -05:00

340 lines
10 KiB
Rust

use super::common::*;
use super::*;
pub enum GarageStorageRepository {
Redis(RedisGarageRepository<ExtensionRedisClient>),
Surreal(SurrealGarageRepository),
}
impl GarageStorageRepository {
pub fn configured() -> Self {
match load().storage.backend {
StorageBackend::Surreal => Self::Surreal(SurrealGarageRepository),
StorageBackend::Redis => {
Self::Redis(RedisGarageRepository::new(ExtensionRedisClient::new()))
}
}
}
}
impl GarageRepository for GarageStorageRepository {
fn create(&self, uid: &str, garage: &Garage) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.create(uid, garage),
Self::Surreal(repository) => repository.create(uid, garage),
}
}
fn update(&self, uid: &str, garage: &Garage) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update(uid, garage),
Self::Surreal(repository) => repository.update(uid, garage),
}
}
fn get(&self, uid: &str) -> Result<Option<Garage>, String> {
match self {
Self::Redis(repository) => repository.get(uid),
Self::Surreal(repository) => repository.get(uid),
}
}
fn delete(&self, uid: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.delete(uid),
Self::Surreal(repository) => repository.delete(uid),
}
}
fn exists(&self, uid: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.exists(uid),
Self::Surreal(repository) => repository.exists(uid),
}
}
}
pub struct SurrealGarageRepository;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GarageOwnerRecord {
uid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GarageVehicleRecord {
uid: String,
plate: String,
classname: String,
fuel: f64,
damage: f64,
hit_points: HitPoints,
}
fn garage_vehicle_id(uid: &str, plate: &str) -> String {
format!("{}:{}", uid, plate)
}
fn garage_from_vehicle_records(records: Vec<GarageVehicleRecord>) -> Garage {
let vehicles = records
.into_iter()
.map(|record| {
let vehicle = Vehicle {
plate: record.plate.clone(),
classname: record.classname,
fuel: record.fuel,
damage: record.damage,
hit_points: record.hit_points,
};
(record.plate, vehicle)
})
.collect();
Garage { vehicles }
}
impl GarageRepository for SurrealGarageRepository {
fn create(&self, uid: &str, garage: &Garage) -> Result<(), String> {
self.update(uid, garage)
}
fn update(&self, uid: &str, garage: &Garage) -> Result<(), String> {
let owner = GarageOwnerRecord {
uid: uid.to_string(),
};
surreal_upsert("garage", uid, "garage owner", &owner)?;
surreal_delete_by_uid("garage_vehicle", "garage vehicles", uid)?;
for (plate_key, vehicle) in &garage.vehicles {
let plate = if vehicle.plate.trim().is_empty() {
plate_key.clone()
} else {
vehicle.plate.clone()
};
let record = GarageVehicleRecord {
uid: uid.to_string(),
plate: plate.clone(),
classname: vehicle.classname.clone(),
fuel: vehicle.fuel,
damage: vehicle.damage,
hit_points: vehicle.hit_points.clone(),
};
surreal_upsert(
"garage_vehicle",
&garage_vehicle_id(uid, &plate),
"garage vehicle",
&record,
)?;
}
Ok(())
}
fn get(&self, uid: &str) -> Result<Option<Garage>, String> {
if surreal_select::<GarageOwnerRecord>("garage", uid, "garage owner")?.is_none() {
return Ok(None);
}
let vehicle_records =
surreal_select_by_uid::<GarageVehicleRecord>("garage_vehicle", "garage vehicles", uid)?;
Ok(Some(garage_from_vehicle_records(vehicle_records)))
}
fn delete(&self, uid: &str) -> Result<(), String> {
surreal_delete_by_uid("garage_vehicle", "garage vehicles", uid)?;
surreal_delete::<GarageOwnerRecord>("garage", uid, "garage owner")
}
fn exists(&self, uid: &str) -> Result<bool, String> {
surreal_select::<GarageOwnerRecord>("garage", uid, "garage owner")
.map(|garage| garage.is_some())
}
}
pub enum VGarageStorageRepository {
Redis(RedisVGarageRepository<ExtensionRedisClient>),
Surreal(SurrealVGarageRepository),
}
impl VGarageStorageRepository {
pub fn configured() -> Self {
match load().storage.backend {
StorageBackend::Surreal => Self::Surreal(SurrealVGarageRepository),
StorageBackend::Redis => {
Self::Redis(RedisVGarageRepository::new(ExtensionRedisClient::new()))
}
}
}
}
impl VGarageRepository for VGarageStorageRepository {
fn create(&self, uid: &str, garage: &VGarage) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.create(uid, garage),
Self::Surreal(repository) => repository.create(uid, garage),
}
}
fn update(&self, uid: &str, garage: &VGarage) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update(uid, garage),
Self::Surreal(repository) => repository.update(uid, garage),
}
}
fn fetch(&self, uid: &str) -> Result<Option<VGarage>, String> {
match self {
Self::Redis(repository) => repository.fetch(uid),
Self::Surreal(repository) => repository.fetch(uid),
}
}
fn get(&self, uid: &str, field: &str) -> Result<Vec<String>, String> {
match self {
Self::Redis(repository) => repository.get(uid, field),
Self::Surreal(repository) => repository.get(uid, field),
}
}
fn delete(&self, uid: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.delete(uid),
Self::Surreal(repository) => repository.delete(uid),
}
}
fn exists(&self, uid: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.exists(uid),
Self::Surreal(repository) => repository.exists(uid),
}
}
}
pub struct SurrealVGarageRepository;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct VGarageOwnerRecord {
uid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct GarageUnlockRecord {
uid: String,
category: String,
classname: String,
source: Option<String>,
}
fn garage_unlock_id(uid: &str, category: &str, classname: &str) -> String {
format!("{}:{}:{}", uid, category, classname)
}
fn push_garage_unlock(garage: &mut VGarage, category: &str, classname: String) {
let target = match category {
"cars" => &mut garage.cars,
"armor" => &mut garage.armor,
"helis" => &mut garage.helis,
"planes" => &mut garage.planes,
"naval" => &mut garage.naval,
"other" => &mut garage.other,
_ => return,
};
if !target.contains(&classname) {
target.push(classname);
}
}
fn vgarage_from_unlock_records(records: Vec<GarageUnlockRecord>) -> VGarage {
let mut garage = VGarage {
cars: Vec::new(),
armor: Vec::new(),
helis: Vec::new(),
planes: Vec::new(),
naval: Vec::new(),
other: Vec::new(),
};
for record in records {
push_garage_unlock(&mut garage, &record.category, record.classname);
}
garage
}
fn upsert_garage_unlocks(uid: &str, category: &str, classnames: &[String]) -> Result<(), String> {
for classname in classnames {
let record = GarageUnlockRecord {
uid: uid.to_string(),
category: category.to_string(),
classname: classname.clone(),
source: None,
};
surreal_upsert(
"garage_unlock",
&garage_unlock_id(uid, category, classname),
"garage unlock",
&record,
)?;
}
Ok(())
}
impl VGarageRepository for SurrealVGarageRepository {
fn create(&self, uid: &str, garage: &VGarage) -> Result<(), String> {
self.update(uid, garage)
}
fn update(&self, uid: &str, garage: &VGarage) -> Result<(), String> {
let owner = VGarageOwnerRecord {
uid: uid.to_string(),
};
surreal_upsert("owned_garage", uid, "virtual garage owner", &owner)?;
surreal_delete_by_uid("garage_unlock", "garage unlocks", uid)?;
upsert_garage_unlocks(uid, "cars", &garage.cars)?;
upsert_garage_unlocks(uid, "armor", &garage.armor)?;
upsert_garage_unlocks(uid, "helis", &garage.helis)?;
upsert_garage_unlocks(uid, "planes", &garage.planes)?;
upsert_garage_unlocks(uid, "naval", &garage.naval)?;
upsert_garage_unlocks(uid, "other", &garage.other)?;
Ok(())
}
fn fetch(&self, uid: &str) -> Result<Option<VGarage>, String> {
if surreal_select::<VGarageOwnerRecord>("owned_garage", uid, "virtual garage owner")?
.is_none()
{
return Ok(None);
}
let unlock_records =
surreal_select_by_uid::<GarageUnlockRecord>("garage_unlock", "garage unlocks", uid)?;
Ok(Some(vgarage_from_unlock_records(unlock_records)))
}
fn get(&self, uid: &str, field: &str) -> Result<Vec<String>, String> {
let garage = self.fetch(uid)?.unwrap_or_else(VGarage::new);
match field {
"cars" => Ok(garage.cars),
"armor" => Ok(garage.armor),
"helis" => Ok(garage.helis),
"planes" => Ok(garage.planes),
"naval" => Ok(garage.naval),
"other" => Ok(garage.other),
_ => Err(format!("Unknown virtual garage field '{}'", field)),
}
}
fn delete(&self, uid: &str) -> Result<(), String> {
surreal_delete_by_uid("garage_unlock", "garage unlocks", uid)?;
surreal_delete::<VGarageOwnerRecord>("owned_garage", uid, "virtual garage owner")
}
fn exists(&self, uid: &str) -> Result<bool, String> {
surreal_select::<VGarageOwnerRecord>("owned_garage", uid, "virtual garage owner")
.map(|garage| garage.is_some())
}
}