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
This commit is contained in:
Jacob Schmidt 2026-04-16 19:55:05 -05:00
parent 4532e7b73d
commit 06c634c642
31 changed files with 3621 additions and 2228 deletions

View File

@ -8,13 +8,12 @@ Server State
Phone contacts, messages, and emails are owned by the server extension. SQF phone stores act as bridge objects for CBA events and UI sync only.
Persistent Redis keys:
Persistent SurrealDB tables:
- `phone:{uid}:contacts`: set of contact actor UIDs
- `phone:message:{messageId}`: hash containing message record fields
- `phone:{uid}:messages`: list of message IDs visible to the user
- `phone:{uid}:thread:{otherUid}`: list of message IDs for a conversation
- `phone:{uid}:message_read`: hash of message ID to per-user read state
- `phone:email:{emailId}`: hash containing email record fields
- `phone:{uid}:emails`: list of email IDs visible to the user
- `phone:{uid}:email_read`: hash of email ID to per-user read state
- `phone_user`: owner row for an initialized phone profile.
- `phone_contact`: per-owner contact rows keyed by owner UID and contact UID.
- `phone_message`: shared message records.
- `phone_message_index`: per-owner message visibility and read state.
- `phone_email`: shared email records.
- `phone_email_index`: per-owner email visibility and read state.
- `phone_sequence`: global sequence state for generated message and email IDs.

View File

@ -23,6 +23,7 @@ mod log;
pub mod org;
pub mod phone;
pub mod redis;
pub mod schema;
pub mod storage;
pub mod store;
pub mod surreal;

View File

@ -0,0 +1,15 @@
DEFINE TABLE IF NOT EXISTS actor SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON actor TYPE string;
DEFINE FIELD IF NOT EXISTS name ON actor TYPE option<string>;
DEFINE FIELD IF NOT EXISTS loadout ON actor TYPE any;
DEFINE FIELD IF NOT EXISTS position ON actor TYPE option<array>;
DEFINE FIELD IF NOT EXISTS direction ON actor TYPE number;
DEFINE FIELD IF NOT EXISTS stance ON actor TYPE option<string>;
DEFINE FIELD IF NOT EXISTS email ON actor TYPE string;
DEFINE FIELD IF NOT EXISTS phone_number ON actor TYPE string;
DEFINE FIELD IF NOT EXISTS state ON actor TYPE string;
DEFINE FIELD IF NOT EXISTS holster ON actor TYPE bool;
DEFINE FIELD IF NOT EXISTS rank ON actor TYPE option<string>;
DEFINE FIELD IF NOT EXISTS organization ON actor TYPE string;
DEFINE FIELD OVERWRITE updated_at ON actor TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS actor_uid ON actor COLUMNS uid UNIQUE;

View File

@ -0,0 +1,17 @@
DEFINE TABLE IF NOT EXISTS bank SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON bank TYPE string;
DEFINE FIELD IF NOT EXISTS name ON bank TYPE string;
DEFINE FIELD IF NOT EXISTS bank ON bank TYPE number;
DEFINE FIELD IF NOT EXISTS cash ON bank TYPE number;
DEFINE FIELD IF NOT EXISTS earnings ON bank TYPE number;
DEFINE FIELD IF NOT EXISTS pin ON bank TYPE int;
DEFINE FIELD OVERWRITE updated_at ON bank TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS bank_uid ON bank COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS bank_transaction SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON bank_transaction TYPE string;
DEFINE FIELD IF NOT EXISTS ordinal ON bank_transaction TYPE int;
DEFINE FIELD IF NOT EXISTS message ON bank_transaction TYPE string;
DEFINE FIELD OVERWRITE created_at ON bank_transaction TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS bank_transaction_owner ON bank_transaction COLUMNS uid;
DEFINE INDEX IF NOT EXISTS bank_transaction_unique ON bank_transaction COLUMNS uid, ordinal UNIQUE;

View File

@ -0,0 +1,29 @@
DEFINE TABLE IF NOT EXISTS garage SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON garage TYPE string;
DEFINE FIELD OVERWRITE updated_at ON garage TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS garage_uid ON garage COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS garage_vehicle SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON garage_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS plate ON garage_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON garage_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS fuel ON garage_vehicle TYPE number;
DEFINE FIELD IF NOT EXISTS damage ON garage_vehicle TYPE number;
DEFINE FIELD IF NOT EXISTS hit_points ON garage_vehicle TYPE object;
DEFINE FIELD OVERWRITE updated_at ON garage_vehicle TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS garage_vehicle_owner ON garage_vehicle COLUMNS uid;
DEFINE INDEX IF NOT EXISTS garage_vehicle_unique ON garage_vehicle COLUMNS uid, plate UNIQUE;
DEFINE TABLE IF NOT EXISTS owned_garage SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON owned_garage TYPE string;
DEFINE FIELD OVERWRITE updated_at ON owned_garage TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS owned_garage_uid ON owned_garage COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS garage_unlock SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON garage_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS category ON garage_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON garage_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS source ON garage_unlock TYPE option<string>;
DEFINE FIELD OVERWRITE unlocked_at ON garage_unlock TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS garage_unlock_owner ON garage_unlock COLUMNS uid;
DEFINE INDEX IF NOT EXISTS garage_unlock_unique ON garage_unlock COLUMNS uid, category, classname UNIQUE;

View File

@ -0,0 +1,27 @@
DEFINE TABLE IF NOT EXISTS locker SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON locker TYPE string;
DEFINE FIELD OVERWRITE updated_at ON locker TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS locker_uid ON locker COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS locker_item SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON locker_item TYPE string;
DEFINE FIELD IF NOT EXISTS category ON locker_item TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON locker_item TYPE string;
DEFINE FIELD IF NOT EXISTS amount ON locker_item TYPE int;
DEFINE FIELD OVERWRITE updated_at ON locker_item TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS locker_item_owner ON locker_item COLUMNS uid;
DEFINE INDEX IF NOT EXISTS locker_item_unique ON locker_item COLUMNS uid, classname UNIQUE;
DEFINE TABLE IF NOT EXISTS owned_locker SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON owned_locker TYPE string;
DEFINE FIELD OVERWRITE updated_at ON owned_locker TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS owned_locker_uid ON owned_locker COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS locker_unlock SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON locker_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS category ON locker_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON locker_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS source ON locker_unlock TYPE option<string>;
DEFINE FIELD OVERWRITE unlocked_at ON locker_unlock TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS locker_unlock_owner ON locker_unlock COLUMNS uid;
DEFINE INDEX IF NOT EXISTS locker_unlock_unique ON locker_unlock COLUMNS uid, category, classname UNIQUE;

View File

@ -0,0 +1,48 @@
use crate::log;
use crate::surreal::SurrealDb;
const SCHEMAS: &[(&str, &str)] = &[
("actor", include_str!("actor.surql")),
("bank", include_str!("bank.surql")),
("org", include_str!("org.surql")),
("locker", include_str!("locker.surql")),
("garage", include_str!("garage.surql")),
("phone", include_str!("phone.surql")),
];
pub async fn apply_all(db: &SurrealDb) -> Result<(), String> {
for (name, schema) in SCHEMAS {
for statement in schema_statements(schema) {
db.query(statement)
.await
.map_err(|error| {
format!(
"SurrealDB {} schema bootstrap failed for statement '{}': {}",
name, statement, error
)
})?
.check()
.map_err(|error| {
format!(
"SurrealDB {} schema bootstrap failed for statement '{}': {}",
name, statement, error
)
})?;
}
log::log(
"surreal",
"DEBUG",
&format!("Applied SurrealDB {} schema", name),
);
}
Ok(())
}
fn schema_statements(schema: &'static str) -> impl Iterator<Item = &'static str> {
schema
.split(';')
.map(str::trim)
.filter(|statement| !statement.is_empty())
}

View File

@ -0,0 +1,51 @@
DEFINE TABLE IF NOT EXISTS org SCHEMALESS;
DEFINE FIELD IF NOT EXISTS org_id ON org TYPE string;
DEFINE FIELD IF NOT EXISTS owner ON org TYPE string;
DEFINE FIELD IF NOT EXISTS name ON org TYPE string;
DEFINE FIELD IF NOT EXISTS funds ON org TYPE number;
DEFINE FIELD IF NOT EXISTS reputation ON org TYPE int;
DEFINE FIELD OVERWRITE updated_at ON org TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS org_org_id ON org COLUMNS org_id UNIQUE;
DEFINE TABLE IF NOT EXISTS org_member SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS org_id ON org_member TYPE string;
DEFINE FIELD IF NOT EXISTS member_uid ON org_member TYPE string;
DEFINE FIELD OVERWRITE joined_at ON org_member TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS org_member_org ON org_member COLUMNS org_id;
DEFINE INDEX IF NOT EXISTS org_member_unique ON org_member COLUMNS org_id, member_uid UNIQUE;
DEFINE TABLE IF NOT EXISTS org_credit_line SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS org_id ON org_credit_line TYPE string;
DEFINE FIELD IF NOT EXISTS uid ON org_credit_line TYPE string;
DEFINE FIELD IF NOT EXISTS name ON org_credit_line TYPE string;
DEFINE FIELD IF NOT EXISTS approved_amount ON org_credit_line TYPE number;
DEFINE FIELD IF NOT EXISTS available_amount ON org_credit_line TYPE number;
DEFINE FIELD IF NOT EXISTS outstanding_principal ON org_credit_line TYPE number;
DEFINE FIELD IF NOT EXISTS interest_rate ON org_credit_line TYPE number;
DEFINE FIELD IF NOT EXISTS amount_due ON org_credit_line TYPE number;
DEFINE FIELD IF NOT EXISTS amount ON org_credit_line TYPE number;
DEFINE FIELD OVERWRITE updated_at ON org_credit_line TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS org_credit_line_org ON org_credit_line COLUMNS org_id;
DEFINE INDEX IF NOT EXISTS org_credit_line_unique ON org_credit_line COLUMNS org_id, uid UNIQUE;
DEFINE TABLE IF NOT EXISTS org_asset SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS org_id ON org_asset TYPE string;
DEFINE FIELD IF NOT EXISTS category ON org_asset TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON org_asset TYPE string;
DEFINE FIELD IF NOT EXISTS asset_type ON org_asset TYPE string;
DEFINE FIELD IF NOT EXISTS quantity ON org_asset TYPE int;
DEFINE FIELD OVERWRITE updated_at ON org_asset TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS org_asset_org ON org_asset COLUMNS org_id;
DEFINE INDEX IF NOT EXISTS org_asset_unique ON org_asset COLUMNS org_id, category, classname UNIQUE;
DEFINE TABLE IF NOT EXISTS org_fleet_vehicle SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS org_id ON org_fleet_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS fleet_key ON org_fleet_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON org_fleet_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS name ON org_fleet_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS fleet_type ON org_fleet_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS status ON org_fleet_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS damage ON org_fleet_vehicle TYPE string;
DEFINE FIELD OVERWRITE updated_at ON org_fleet_vehicle TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS org_fleet_vehicle_org ON org_fleet_vehicle COLUMNS org_id;
DEFINE INDEX IF NOT EXISTS org_fleet_vehicle_unique ON org_fleet_vehicle COLUMNS org_id, fleet_key UNIQUE;

View File

@ -0,0 +1,53 @@
DEFINE TABLE IF NOT EXISTS phone_user SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON phone_user TYPE string;
DEFINE FIELD OVERWRITE updated_at ON phone_user TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_user_uid ON phone_user COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_contact SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON phone_contact TYPE string;
DEFINE FIELD IF NOT EXISTS contact_uid ON phone_contact TYPE string;
DEFINE FIELD OVERWRITE created_at ON phone_contact TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_contact_owner ON phone_contact COLUMNS uid;
DEFINE INDEX IF NOT EXISTS phone_contact_unique ON phone_contact COLUMNS uid, contact_uid UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_message SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS message_id ON phone_message TYPE string;
DEFINE FIELD IF NOT EXISTS from_uid ON phone_message TYPE string;
DEFINE FIELD IF NOT EXISTS to_uid ON phone_message TYPE string;
DEFINE FIELD IF NOT EXISTS message ON phone_message TYPE string;
DEFINE FIELD IF NOT EXISTS timestamp ON phone_message TYPE number;
DEFINE FIELD OVERWRITE created_at ON phone_message TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_message_message_id ON phone_message COLUMNS message_id UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_message_index SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON phone_message_index TYPE string;
DEFINE FIELD IF NOT EXISTS message_id ON phone_message_index TYPE string;
DEFINE FIELD IF NOT EXISTS is_read ON phone_message_index TYPE bool;
DEFINE FIELD OVERWRITE updated_at ON phone_message_index TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_message_index_owner ON phone_message_index COLUMNS uid;
DEFINE INDEX IF NOT EXISTS phone_message_index_message ON phone_message_index COLUMNS message_id;
DEFINE INDEX IF NOT EXISTS phone_message_index_unique ON phone_message_index COLUMNS uid, message_id UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_email SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS email_id ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS from_uid ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS to_uid ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS subject ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS body ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS timestamp ON phone_email TYPE number;
DEFINE FIELD OVERWRITE created_at ON phone_email TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_email_email_id ON phone_email COLUMNS email_id UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_email_index SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON phone_email_index TYPE string;
DEFINE FIELD IF NOT EXISTS email_id ON phone_email_index TYPE string;
DEFINE FIELD IF NOT EXISTS is_read ON phone_email_index TYPE bool;
DEFINE FIELD OVERWRITE updated_at ON phone_email_index TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_email_index_owner ON phone_email_index COLUMNS uid;
DEFINE INDEX IF NOT EXISTS phone_email_index_email ON phone_email_index COLUMNS email_id;
DEFINE INDEX IF NOT EXISTS phone_email_index_unique ON phone_email_index COLUMNS uid, email_id UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_sequence SCHEMALESS;
DEFINE FIELD IF NOT EXISTS sequence_id ON phone_sequence TYPE string;
DEFINE FIELD IF NOT EXISTS value ON phone_sequence TYPE int;
DEFINE INDEX IF NOT EXISTS phone_sequence_id ON phone_sequence COLUMNS sequence_id UNIQUE;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,135 @@
use super::common::*;
use super::*;
pub enum ActorStorageRepository {
Redis(RedisActorRepository<ExtensionRedisClient>),
Surreal(SurrealActorRepository),
}
impl ActorStorageRepository {
pub fn configured() -> Self {
match load().storage.backend {
StorageBackend::Surreal => Self::Surreal(SurrealActorRepository),
StorageBackend::Redis => {
Self::Redis(RedisActorRepository::new(ExtensionRedisClient::new()))
}
}
}
}
impl ActorRepository for ActorStorageRepository {
fn create(&self, actor: &Actor) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.create(actor),
Self::Surreal(repository) => repository.create(actor),
}
}
fn get_by_id(&self, id: &str) -> Result<Option<Actor>, String> {
match self {
Self::Redis(repository) => repository.get_by_id(id),
Self::Surreal(repository) => repository.get_by_id(id),
}
}
fn update(&self, actor: &Actor) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update(actor),
Self::Surreal(repository) => repository.update(actor),
}
}
fn delete(&self, id: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.delete(id),
Self::Surreal(repository) => repository.delete(id),
}
}
fn exists(&self, id: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.exists(id),
Self::Surreal(repository) => repository.exists(id),
}
}
}
pub struct SurrealActorRepository;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct ActorRecord {
uid: String,
name: Option<String>,
loadout: serde_json::Value,
position: Option<Vec<f64>>,
direction: f64,
stance: Option<String>,
email: String,
phone_number: String,
state: String,
holster: bool,
rank: Option<String>,
organization: String,
}
impl ActorRecord {
fn into_actor(self) -> Actor {
Actor {
uid: self.uid,
name: self.name,
loadout: self.loadout,
position: self.position,
direction: self.direction,
stance: self.stance,
email: self.email,
phone_number: self.phone_number,
state: self.state,
holster: self.holster,
rank: self.rank,
organization: self.organization,
}
}
}
impl From<&Actor> for ActorRecord {
fn from(actor: &Actor) -> Self {
Self {
uid: actor.uid.clone(),
name: actor.name.clone(),
loadout: actor.loadout.clone(),
position: actor.position.clone(),
direction: actor.direction,
stance: actor.stance.clone(),
email: actor.email.clone(),
phone_number: actor.phone_number.clone(),
state: actor.state.clone(),
holster: actor.holster,
rank: actor.rank.clone(),
organization: actor.organization.clone(),
}
}
}
impl ActorRepository for SurrealActorRepository {
fn create(&self, actor: &Actor) -> Result<(), String> {
self.update(actor)
}
fn get_by_id(&self, id: &str) -> Result<Option<Actor>, String> {
surreal_select::<ActorRecord>("actor", id, "actor")
.map(|record| record.map(ActorRecord::into_actor))
}
fn update(&self, actor: &Actor) -> Result<(), String> {
let record = ActorRecord::from(actor);
surreal_upsert("actor", actor.uid.as_str(), "actor", &record)
}
fn delete(&self, id: &str) -> Result<(), String> {
surreal_delete::<ActorRecord>("actor", id, "actor")
}
fn exists(&self, id: &str) -> Result<bool, String> {
self.get_by_id(id).map(|actor| actor.is_some())
}
}

View File

@ -0,0 +1,165 @@
use super::common::*;
use super::*;
pub enum BankStorageRepository {
Redis(RedisBankRepository<ExtensionRedisClient>),
Surreal(SurrealBankRepository),
}
impl BankStorageRepository {
pub fn configured() -> Self {
match load().storage.backend {
StorageBackend::Surreal => Self::Surreal(SurrealBankRepository),
StorageBackend::Redis => {
Self::Redis(RedisBankRepository::new(ExtensionRedisClient::new()))
}
}
}
}
impl BankRepository for BankStorageRepository {
fn create(&self, bank: &Bank) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.create(bank),
Self::Surreal(repository) => repository.create(bank),
}
}
fn get_by_id(&self, id: &str) -> Result<Option<Bank>, String> {
match self {
Self::Redis(repository) => repository.get_by_id(id),
Self::Surreal(repository) => repository.get_by_id(id),
}
}
fn update(&self, bank: &Bank) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update(bank),
Self::Surreal(repository) => repository.update(bank),
}
}
fn delete(&self, id: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.delete(id),
Self::Surreal(repository) => repository.delete(id),
}
}
fn exists(&self, id: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.exists(id),
Self::Surreal(repository) => repository.exists(id),
}
}
}
pub struct SurrealBankRepository;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct BankAccountRecord {
uid: String,
name: String,
bank: f64,
cash: f64,
earnings: f64,
pin: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct BankTransactionRecord {
uid: String,
ordinal: u64,
message: String,
}
impl BankAccountRecord {
fn into_bank(self, transactions: Vec<String>) -> Bank {
Bank {
uid: self.uid,
name: self.name,
bank: self.bank,
cash: self.cash,
earnings: self.earnings,
pin: self.pin,
transactions,
}
}
}
impl From<&Bank> for BankAccountRecord {
fn from(bank: &Bank) -> Self {
Self {
uid: bank.uid.clone(),
name: bank.name.clone(),
bank: bank.bank,
cash: bank.cash,
earnings: bank.earnings,
pin: bank.pin,
}
}
}
fn bank_transaction_id(uid: &str, ordinal: usize) -> String {
format!("{}:{}", uid, ordinal)
}
fn bank_transactions_from_records(mut records: Vec<BankTransactionRecord>) -> Vec<String> {
records.sort_by_key(|record| record.ordinal);
records
.into_iter()
.map(|record| record.message)
.collect::<Vec<_>>()
}
impl BankRepository for SurrealBankRepository {
fn create(&self, bank: &Bank) -> Result<(), String> {
self.update(bank)
}
fn get_by_id(&self, id: &str) -> Result<Option<Bank>, String> {
let Some(record) = surreal_select::<BankAccountRecord>("bank", id, "bank")? else {
return Ok(None);
};
let transaction_records = surreal_select_by_uid::<BankTransactionRecord>(
"bank_transaction",
"bank transactions",
id,
)?;
let transactions = bank_transactions_from_records(transaction_records);
Ok(Some(record.into_bank(transactions)))
}
fn update(&self, bank: &Bank) -> Result<(), String> {
let account = BankAccountRecord::from(bank);
surreal_upsert("bank", bank.uid.as_str(), "bank", &account)?;
surreal_delete_by_uid("bank_transaction", "bank transactions", &bank.uid)?;
for (ordinal, message) in bank.transactions.iter().enumerate() {
let record = BankTransactionRecord {
uid: bank.uid.clone(),
ordinal: ordinal as u64,
message: message.clone(),
};
surreal_upsert(
"bank_transaction",
&bank_transaction_id(&bank.uid, ordinal),
"bank transaction",
&record,
)?;
}
Ok(())
}
fn delete(&self, id: &str) -> Result<(), String> {
surreal_delete_by_uid("bank_transaction", "bank transactions", id)?;
surreal_delete::<BankAccountRecord>("bank", id, "bank")
}
fn exists(&self, id: &str) -> Result<bool, String> {
self.get_by_id(id).map(|bank| bank.is_some())
}
}

View File

@ -0,0 +1,132 @@
use super::*;
pub(super) fn surreal_select<T>(
table: &'static str,
id: &str,
label: &str,
) -> Result<Option<T>, String>
where
T: DeserializeOwned,
{
let id = id.to_string();
RUNTIME.block_on(async move {
surreal::client()
.await?
.select((table, id.as_str()))
.await
.map_err(|error| format!("SurrealDB {} select failed: {}", label, error))
})
}
pub(super) fn surreal_select_all<T>(table: &'static str, label: &str) -> Result<Vec<T>, String>
where
T: DeserializeOwned,
{
RUNTIME.block_on(async move {
surreal::client()
.await?
.select(table)
.await
.map_err(|error| format!("SurrealDB {} select all failed: {}", label, error))
})
}
pub(super) fn surreal_upsert<T>(
table: &'static str,
id: &str,
label: &str,
record: &T,
) -> Result<(), String>
where
T: Serialize + DeserializeOwned,
{
let id = id.to_string();
let record = serde_json::to_value(record)
.map_err(|error| format!("SurrealDB {} serialize failed: {}", label, error))?;
RUNTIME.block_on(async move {
let _: Option<T> = surreal::client()
.await?
.upsert((table, id.as_str()))
.content(record)
.await
.map_err(|error| format!("SurrealDB {} upsert failed: {}", label, error))?;
Ok(())
})
}
pub(super) fn surreal_delete<T>(table: &'static str, id: &str, label: &str) -> Result<(), String>
where
T: DeserializeOwned,
{
let id = id.to_string();
RUNTIME.block_on(async move {
let _: Option<T> = surreal::client()
.await?
.delete((table, id.as_str()))
.await
.map_err(|error| format!("SurrealDB {} delete failed: {}", label, error))?;
Ok(())
})
}
pub(super) fn surreal_select_by_uid<T>(
table: &'static str,
label: &str,
uid: &str,
) -> Result<Vec<T>, String>
where
T: DeserializeOwned,
{
surreal_select_by_field(table, label, "uid", uid)
}
pub(super) fn surreal_select_by_field<T>(
table: &'static str,
label: &str,
field: &'static str,
value: &str,
) -> Result<Vec<T>, String>
where
T: DeserializeOwned,
{
let value = value.to_string();
RUNTIME.block_on(async move {
let mut response = surreal::client()
.await?
.query(format!("SELECT * FROM {} WHERE {} = $value", table, field))
.bind(("value", value))
.await
.map_err(|error| format!("SurrealDB {} select by field failed: {}", label, error))?;
response
.take(0)
.map_err(|error| format!("SurrealDB {} select by field failed: {}", label, error))
})
}
pub(super) fn surreal_delete_by_uid(
table: &'static str,
label: &str,
uid: &str,
) -> Result<(), String> {
surreal_delete_by_field(table, label, "uid", uid)
}
pub(super) fn surreal_delete_by_field(
table: &'static str,
label: &str,
field: &'static str,
value: &str,
) -> Result<(), String> {
let value = value.to_string();
RUNTIME.block_on(async move {
surreal::client()
.await?
.query(format!("DELETE {} WHERE {} = $value", table, field))
.bind(("value", value))
.await
.map_err(|error| format!("SurrealDB {} delete by field failed: {}", label, error))?
.check()
.map_err(|error| format!("SurrealDB {} delete by field failed: {}", label, error))?;
Ok(())
})
}

View File

@ -0,0 +1,339 @@
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())
}
}

View File

@ -0,0 +1,320 @@
use super::common::*;
use super::*;
pub enum LockerStorageRepository {
Redis(RedisLockerRepository<ExtensionRedisClient>),
Surreal(SurrealLockerRepository),
}
impl LockerStorageRepository {
pub fn configured() -> Self {
match load().storage.backend {
StorageBackend::Surreal => Self::Surreal(SurrealLockerRepository),
StorageBackend::Redis => {
Self::Redis(RedisLockerRepository::new(ExtensionRedisClient::new()))
}
}
}
}
impl LockerRepository for LockerStorageRepository {
fn create(&self, uid: &str, locker: &Locker) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.create(uid, locker),
Self::Surreal(repository) => repository.create(uid, locker),
}
}
fn update(&self, uid: &str, locker: &Locker) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update(uid, locker),
Self::Surreal(repository) => repository.update(uid, locker),
}
}
fn get(&self, uid: &str) -> Result<Option<Locker>, 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 SurrealLockerRepository;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LockerOwnerRecord {
uid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LockerItemRecord {
uid: String,
category: String,
classname: String,
amount: u32,
}
fn locker_item_id(uid: &str, classname: &str) -> String {
format!("{}:{}", uid, classname)
}
fn locker_from_item_records(records: Vec<LockerItemRecord>) -> Locker {
let items = records
.into_iter()
.map(|record| {
let item = Item {
category: record.category,
classname: record.classname.clone(),
amount: record.amount,
};
(record.classname, item)
})
.collect();
Locker { items }
}
impl LockerRepository for SurrealLockerRepository {
fn create(&self, uid: &str, locker: &Locker) -> Result<(), String> {
self.update(uid, locker)
}
fn update(&self, uid: &str, locker: &Locker) -> Result<(), String> {
let owner = LockerOwnerRecord {
uid: uid.to_string(),
};
surreal_upsert("locker", uid, "locker owner", &owner)?;
surreal_delete_by_uid("locker_item", "locker items", uid)?;
for item in locker.items.values() {
let record = LockerItemRecord {
uid: uid.to_string(),
category: item.category.clone(),
classname: item.classname.clone(),
amount: item.amount,
};
surreal_upsert(
"locker_item",
&locker_item_id(uid, &item.classname),
"locker item",
&record,
)?;
}
Ok(())
}
fn get(&self, uid: &str) -> Result<Option<Locker>, String> {
if surreal_select::<LockerOwnerRecord>("locker", uid, "locker owner")?.is_none() {
return Ok(None);
}
let item_records =
surreal_select_by_uid::<LockerItemRecord>("locker_item", "locker items", uid)?;
Ok(Some(locker_from_item_records(item_records)))
}
fn delete(&self, uid: &str) -> Result<(), String> {
surreal_delete_by_uid("locker_item", "locker items", uid)?;
surreal_delete::<LockerOwnerRecord>("locker", uid, "locker owner")
}
fn exists(&self, uid: &str) -> Result<bool, String> {
surreal_select::<LockerOwnerRecord>("locker", uid, "locker owner")
.map(|locker| locker.is_some())
}
}
pub enum VLockerStorageRepository {
Redis(RedisVLockerRepository<ExtensionRedisClient>),
Surreal(SurrealVLockerRepository),
}
impl VLockerStorageRepository {
pub fn configured() -> Self {
match load().storage.backend {
StorageBackend::Surreal => Self::Surreal(SurrealVLockerRepository),
StorageBackend::Redis => {
Self::Redis(RedisVLockerRepository::new(ExtensionRedisClient::new()))
}
}
}
}
impl VLockerRepository for VLockerStorageRepository {
fn create(&self, uid: &str, locker: &VLocker) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.create(uid, locker),
Self::Surreal(repository) => repository.create(uid, locker),
}
}
fn update(&self, uid: &str, locker: &VLocker) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update(uid, locker),
Self::Surreal(repository) => repository.update(uid, locker),
}
}
fn fetch(&self, uid: &str) -> Result<Option<VLocker>, 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 SurrealVLockerRepository;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct VLockerOwnerRecord {
uid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct LockerUnlockRecord {
uid: String,
category: String,
classname: String,
source: Option<String>,
}
fn locker_unlock_id(uid: &str, category: &str, classname: &str) -> String {
format!("{}:{}:{}", uid, category, classname)
}
fn push_locker_unlock(locker: &mut VLocker, category: &str, classname: String) {
let target = match category {
"items" => &mut locker.items,
"weapons" => &mut locker.weapons,
"magazines" => &mut locker.magazines,
"backpacks" => &mut locker.backpacks,
_ => return,
};
if !target.contains(&classname) {
target.push(classname);
}
}
fn vlocker_from_unlock_records(records: Vec<LockerUnlockRecord>) -> VLocker {
let mut locker = VLocker {
items: Vec::new(),
weapons: Vec::new(),
magazines: Vec::new(),
backpacks: Vec::new(),
};
for record in records {
push_locker_unlock(&mut locker, &record.category, record.classname);
}
locker
}
fn upsert_locker_unlocks(uid: &str, category: &str, classnames: &[String]) -> Result<(), String> {
for classname in classnames {
let record = LockerUnlockRecord {
uid: uid.to_string(),
category: category.to_string(),
classname: classname.clone(),
source: None,
};
surreal_upsert(
"locker_unlock",
&locker_unlock_id(uid, category, classname),
"locker unlock",
&record,
)?;
}
Ok(())
}
impl VLockerRepository for SurrealVLockerRepository {
fn create(&self, uid: &str, locker: &VLocker) -> Result<(), String> {
self.update(uid, locker)
}
fn update(&self, uid: &str, locker: &VLocker) -> Result<(), String> {
let owner = VLockerOwnerRecord {
uid: uid.to_string(),
};
surreal_upsert("owned_locker", uid, "virtual locker owner", &owner)?;
surreal_delete_by_uid("locker_unlock", "locker unlocks", uid)?;
upsert_locker_unlocks(uid, "items", &locker.items)?;
upsert_locker_unlocks(uid, "weapons", &locker.weapons)?;
upsert_locker_unlocks(uid, "magazines", &locker.magazines)?;
upsert_locker_unlocks(uid, "backpacks", &locker.backpacks)?;
Ok(())
}
fn fetch(&self, uid: &str) -> Result<Option<VLocker>, String> {
if surreal_select::<VLockerOwnerRecord>("owned_locker", uid, "virtual locker owner")?
.is_none()
{
return Ok(None);
}
let unlock_records =
surreal_select_by_uid::<LockerUnlockRecord>("locker_unlock", "locker unlocks", uid)?;
Ok(Some(vlocker_from_unlock_records(unlock_records)))
}
fn get(&self, uid: &str, field: &str) -> Result<Vec<String>, String> {
let locker = self.fetch(uid)?.unwrap_or_else(VLocker::new);
match field {
"items" => Ok(locker.items),
"weapons" => Ok(locker.weapons),
"magazines" => Ok(locker.magazines),
"backpacks" => Ok(locker.backpacks),
_ => Err(format!("Unknown virtual locker field '{}'", field)),
}
}
fn delete(&self, uid: &str) -> Result<(), String> {
surreal_delete_by_uid("locker_unlock", "locker unlocks", uid)?;
surreal_delete::<VLockerOwnerRecord>("owned_locker", uid, "virtual locker owner")
}
fn exists(&self, uid: &str) -> Result<bool, String> {
surreal_select::<VLockerOwnerRecord>("owned_locker", uid, "virtual locker owner")
.map(|locker| locker.is_some())
}
}

View File

@ -0,0 +1,501 @@
use super::common::*;
use super::*;
pub enum OrgStorageRepository {
Redis(RedisOrgRepository<ExtensionRedisClient>),
Surreal(SurrealOrgRepository),
}
impl OrgStorageRepository {
pub fn configured() -> Self {
match load().storage.backend {
StorageBackend::Surreal => Self::Surreal(SurrealOrgRepository),
StorageBackend::Redis => {
Self::Redis(RedisOrgRepository::new(ExtensionRedisClient::new()))
}
}
}
}
impl OrgRepository for OrgStorageRepository {
fn create(&self, org: &Org) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.create(org),
Self::Surreal(repository) => repository.create(org),
}
}
fn get_by_id(&self, id: &str) -> Result<Option<Org>, String> {
match self {
Self::Redis(repository) => repository.get_by_id(id),
Self::Surreal(repository) => repository.get_by_id(id),
}
}
fn update(&self, org: &Org) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update(org),
Self::Surreal(repository) => repository.update(org),
}
}
fn delete(&self, id: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.delete(id),
Self::Surreal(repository) => repository.delete(id),
}
}
fn exists(&self, id: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.exists(id),
Self::Surreal(repository) => repository.exists(id),
}
}
fn add_member(&self, org_id: &str, member_uid: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.add_member(org_id, member_uid),
Self::Surreal(repository) => repository.add_member(org_id, member_uid),
}
}
fn get_members(&self, org_id: &str) -> Result<Vec<MemberSummary>, String> {
match self {
Self::Redis(repository) => repository.get_members(org_id),
Self::Surreal(repository) => repository.get_members(org_id),
}
}
fn remove_member(&self, org_id: &str, member_uid: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.remove_member(org_id, member_uid),
Self::Surreal(repository) => repository.remove_member(org_id, member_uid),
}
}
fn get_assets(
&self,
org_id: &str,
) -> Result<HashMap<String, HashMap<String, OrgAssetEntry>>, String> {
match self {
Self::Redis(repository) => repository.get_assets(org_id),
Self::Surreal(repository) => repository.get_assets(org_id),
}
}
fn update_assets(
&self,
org_id: &str,
assets: &HashMap<String, HashMap<String, OrgAssetEntry>>,
) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update_assets(org_id, assets),
Self::Surreal(repository) => repository.update_assets(org_id, assets),
}
}
fn get_fleet(&self, org_id: &str) -> Result<HashMap<String, OrgFleetEntry>, String> {
match self {
Self::Redis(repository) => repository.get_fleet(org_id),
Self::Surreal(repository) => repository.get_fleet(org_id),
}
}
fn update_fleet(
&self,
org_id: &str,
fleet: &HashMap<String, OrgFleetEntry>,
) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.update_fleet(org_id, fleet),
Self::Surreal(repository) => repository.update_fleet(org_id, fleet),
}
}
}
#[derive(Debug, Default, Serialize, Deserialize)]
struct SurrealOrgRecord {
#[serde(default)]
org_id: String,
#[serde(default)]
owner: String,
#[serde(default)]
name: String,
#[serde(default)]
funds: f64,
#[serde(default)]
reputation: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct OrgMemberRow {
org_id: String,
member_uid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct OrgCreditLineRow {
org_id: String,
uid: String,
name: String,
approved_amount: f64,
available_amount: f64,
outstanding_principal: f64,
interest_rate: f64,
amount_due: f64,
amount: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct OrgAssetRow {
org_id: String,
category: String,
classname: String,
asset_type: String,
quantity: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct OrgFleetRow {
org_id: String,
fleet_key: String,
classname: String,
name: String,
fleet_type: String,
status: String,
damage: String,
}
impl SurrealOrgRecord {
fn into_org(self, fallback_id: &str, credit_lines: HashMap<String, CreditLineSummary>) -> Org {
let id = if self.org_id.trim().is_empty() {
fallback_id.to_string()
} else {
self.org_id
};
let mut org = Org {
id,
owner: self.owner,
name: self.name,
funds: self.funds,
reputation: self.reputation,
credit_lines,
};
org.normalize_credit_lines();
org
}
}
impl From<&Org> for SurrealOrgRecord {
fn from(org: &Org) -> Self {
Self {
org_id: org.id.clone(),
owner: org.owner.clone(),
name: org.name.clone(),
funds: org.funds,
reputation: org.reputation,
}
}
}
pub struct SurrealOrgRepository;
fn org_member_id(org_id: &str, member_uid: &str) -> String {
format!("{}:{}", org_id, member_uid)
}
fn org_credit_line_id(org_id: &str, uid: &str) -> String {
format!("{}:{}", org_id, uid)
}
fn org_asset_id(org_id: &str, category: &str, classname: &str) -> String {
format!("{}:{}:{}", org_id, category, classname)
}
fn org_fleet_id(org_id: &str, fleet_key: &str) -> String {
format!("{}:{}", org_id, fleet_key)
}
fn org_credit_lines_from_rows(rows: Vec<OrgCreditLineRow>) -> HashMap<String, CreditLineSummary> {
rows.into_iter()
.map(|row| {
let mut credit_line = CreditLineSummary {
uid: row.uid.clone(),
name: row.name,
approved_amount: row.approved_amount,
available_amount: row.available_amount,
outstanding_principal: row.outstanding_principal,
interest_rate: row.interest_rate,
amount_due: row.amount_due,
amount: row.amount,
};
credit_line.normalize();
(row.uid, credit_line)
})
.collect()
}
fn org_member_uids(org_id: &str) -> Result<Vec<String>, String> {
let rows =
surreal_select_by_field::<OrgMemberRow>("org_member", "org members", "org_id", org_id)?;
let mut uids = rows
.into_iter()
.map(|row| row.member_uid)
.filter(|uid| !uid.trim().is_empty())
.collect::<Vec<_>>();
uids.sort();
uids.dedup();
Ok(uids)
}
fn upsert_org_member(org_id: &str, member_uid: &str) -> Result<(), String> {
let row = OrgMemberRow {
org_id: org_id.to_string(),
member_uid: member_uid.to_string(),
};
surreal_upsert(
"org_member",
&org_member_id(org_id, member_uid),
"org member",
&row,
)
}
fn org_assets_from_rows(rows: Vec<OrgAssetRow>) -> HashMap<String, HashMap<String, OrgAssetEntry>> {
let mut assets = HashMap::new();
for row in rows {
let category_assets = assets
.entry(row.category.clone())
.or_insert_with(HashMap::new);
category_assets.insert(
row.classname.clone(),
OrgAssetEntry {
classname: row.classname,
asset_type: row.asset_type,
quantity: row.quantity,
},
);
}
assets
}
fn org_fleet_from_rows(rows: Vec<OrgFleetRow>) -> HashMap<String, OrgFleetEntry> {
rows.into_iter()
.map(|row| {
(
row.fleet_key,
OrgFleetEntry {
classname: row.classname,
name: row.name,
fleet_type: row.fleet_type,
status: row.status,
damage: row.damage,
},
)
})
.collect()
}
impl OrgRepository for SurrealOrgRepository {
fn create(&self, org: &Org) -> Result<(), String> {
self.update(org)
}
fn get_by_id(&self, id: &str) -> Result<Option<Org>, String> {
let Some(record) = surreal_select::<SurrealOrgRecord>("org", id, "org")? else {
return Ok(None);
};
let credit_line_rows = surreal_select_by_field::<OrgCreditLineRow>(
"org_credit_line",
"org credit lines",
"org_id",
id,
)?;
let credit_lines = org_credit_lines_from_rows(credit_line_rows);
Ok(Some(record.into_org(id, credit_lines)))
}
fn update(&self, org: &Org) -> Result<(), String> {
let record = SurrealOrgRecord::from(org);
surreal_upsert("org", org.id.as_str(), "org", &record)?;
surreal_delete_by_field("org_credit_line", "org credit lines", "org_id", &org.id)?;
for (uid, credit_line) in &org.credit_lines {
let resolved_uid = if credit_line.uid.trim().is_empty() {
uid.clone()
} else {
credit_line.uid.clone()
};
let mut normalized = credit_line.clone();
normalized.uid = resolved_uid.clone();
normalized.normalize();
let row = OrgCreditLineRow {
org_id: org.id.clone(),
uid: resolved_uid.clone(),
name: normalized.name,
approved_amount: normalized.approved_amount,
available_amount: normalized.available_amount,
outstanding_principal: normalized.outstanding_principal,
interest_rate: normalized.interest_rate,
amount_due: normalized.amount_due,
amount: normalized.amount,
};
surreal_upsert(
"org_credit_line",
&org_credit_line_id(&org.id, &resolved_uid),
"org credit line",
&row,
)?;
}
Ok(())
}
fn delete(&self, id: &str) -> Result<(), String> {
surreal_delete::<SurrealOrgRecord>("org", id, "org")?;
surreal_delete_by_field("org_member", "org members", "org_id", id)?;
surreal_delete_by_field("org_credit_line", "org credit lines", "org_id", id)?;
surreal_delete_by_field("org_asset", "org assets", "org_id", id)?;
surreal_delete_by_field("org_fleet_vehicle", "org fleet", "org_id", id)
}
fn exists(&self, id: &str) -> Result<bool, String> {
self.get_by_id(id).map(|org| org.is_some())
}
fn add_member(&self, org_id: &str, member_uid: &str) -> Result<(), String> {
if !self.exists(org_id)? {
return Err(format!("Organization {} does not exist", org_id));
}
let mut member_uids = org_member_uids(org_id)?;
if !member_uids.iter().any(|uid| uid == member_uid) {
member_uids.push(member_uid.to_string());
}
surreal_delete_by_field("org_member", "org members", "org_id", org_id)?;
for uid in member_uids {
upsert_org_member(org_id, &uid)?;
}
Ok(())
}
fn get_members(&self, org_id: &str) -> Result<Vec<MemberSummary>, String> {
let member_uids = org_member_uids(org_id)?;
let mut members = Vec::with_capacity(member_uids.len());
let actor_repository = SurrealActorRepository;
for uid in member_uids {
if uid.trim().is_empty() {
continue;
}
let name = match actor_repository.get_by_id(&uid)? {
Some(actor) => actor
.name
.filter(|name| !name.trim().is_empty())
.unwrap_or_else(|| "Unknown".to_string()),
None => "Unknown".to_string(),
};
members.push(MemberSummary { uid, name });
}
Ok(members)
}
fn remove_member(&self, org_id: &str, member_uid: &str) -> Result<(), String> {
let mut member_uids = org_member_uids(org_id)?;
member_uids.retain(|uid| uid != member_uid);
surreal_delete_by_field("org_member", "org members", "org_id", org_id)?;
for uid in member_uids {
upsert_org_member(org_id, &uid)?;
}
Ok(())
}
fn get_assets(
&self,
org_id: &str,
) -> Result<HashMap<String, HashMap<String, OrgAssetEntry>>, String> {
let rows =
surreal_select_by_field::<OrgAssetRow>("org_asset", "org assets", "org_id", org_id)?;
Ok(org_assets_from_rows(rows))
}
fn update_assets(
&self,
org_id: &str,
assets: &HashMap<String, HashMap<String, OrgAssetEntry>>,
) -> Result<(), String> {
surreal_delete_by_field("org_asset", "org assets", "org_id", org_id)?;
for (category, category_assets) in assets {
for (classname, asset) in category_assets {
let row = OrgAssetRow {
org_id: org_id.to_string(),
category: category.clone(),
classname: if asset.classname.trim().is_empty() {
classname.clone()
} else {
asset.classname.clone()
},
asset_type: asset.asset_type.clone(),
quantity: asset.quantity,
};
surreal_upsert(
"org_asset",
&org_asset_id(org_id, category, &row.classname),
"org asset",
&row,
)?;
}
}
Ok(())
}
fn get_fleet(&self, org_id: &str) -> Result<HashMap<String, OrgFleetEntry>, String> {
let rows = surreal_select_by_field::<OrgFleetRow>(
"org_fleet_vehicle",
"org fleet",
"org_id",
org_id,
)?;
if !rows.is_empty() {
return Ok(org_fleet_from_rows(rows));
}
Ok(HashMap::new())
}
fn update_fleet(
&self,
org_id: &str,
fleet: &HashMap<String, OrgFleetEntry>,
) -> Result<(), String> {
surreal_delete_by_field("org_fleet_vehicle", "org fleet", "org_id", org_id)?;
for (fleet_key, entry) in fleet {
let row = OrgFleetRow {
org_id: org_id.to_string(),
fleet_key: fleet_key.clone(),
classname: entry.classname.clone(),
name: entry.name.clone(),
fleet_type: entry.fleet_type.clone(),
status: entry.status.clone(),
damage: entry.damage.clone(),
};
surreal_upsert(
"org_fleet_vehicle",
&org_fleet_id(org_id, fleet_key),
"org fleet",
&row,
)?;
}
Ok(())
}
}

View File

@ -0,0 +1,536 @@
use super::common::*;
use super::*;
pub enum PhoneStorageRepository {
Redis(RedisPhoneRepository<ExtensionRedisClient>),
Surreal(SurrealPhoneRepository),
}
impl PhoneStorageRepository {
pub fn configured() -> Self {
match load().storage.backend {
StorageBackend::Surreal => Self::Surreal(SurrealPhoneRepository),
StorageBackend::Redis => {
Self::Redis(RedisPhoneRepository::new(ExtensionRedisClient::new()))
}
}
}
}
impl PhoneRepository for PhoneStorageRepository {
fn init(&self, uid: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.init(uid),
Self::Surreal(repository) => repository.init(uid),
}
}
fn add_contact(&self, uid: &str, contact_uid: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.add_contact(uid, contact_uid),
Self::Surreal(repository) => repository.add_contact(uid, contact_uid),
}
}
fn remove_contact(&self, uid: &str, contact_uid: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.remove_contact(uid, contact_uid),
Self::Surreal(repository) => repository.remove_contact(uid, contact_uid),
}
}
fn list_contacts(&self, uid: &str) -> Result<Vec<String>, String> {
match self {
Self::Redis(repository) => repository.list_contacts(uid),
Self::Surreal(repository) => repository.list_contacts(uid),
}
}
fn remove_phone(&self, uid: &str) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.remove_phone(uid),
Self::Surreal(repository) => repository.remove_phone(uid),
}
}
fn append_message(&self, uid: &str, message: PhoneMessage) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.append_message(uid, message),
Self::Surreal(repository) => repository.append_message(uid, message),
}
}
fn list_messages(&self, uid: &str) -> Result<Vec<PhoneMessage>, String> {
match self {
Self::Redis(repository) => repository.list_messages(uid),
Self::Surreal(repository) => repository.list_messages(uid),
}
}
fn mark_message_read(&self, uid: &str, message_id: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.mark_message_read(uid, message_id),
Self::Surreal(repository) => repository.mark_message_read(uid, message_id),
}
}
fn delete_message(&self, uid: &str, message_id: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.delete_message(uid, message_id),
Self::Surreal(repository) => repository.delete_message(uid, message_id),
}
}
fn append_email(&self, uid: &str, email: PhoneEmail) -> Result<(), String> {
match self {
Self::Redis(repository) => repository.append_email(uid, email),
Self::Surreal(repository) => repository.append_email(uid, email),
}
}
fn list_emails(&self, uid: &str) -> Result<Vec<PhoneEmail>, String> {
match self {
Self::Redis(repository) => repository.list_emails(uid),
Self::Surreal(repository) => repository.list_emails(uid),
}
}
fn mark_email_read(&self, uid: &str, email_id: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.mark_email_read(uid, email_id),
Self::Surreal(repository) => repository.mark_email_read(uid, email_id),
}
}
fn delete_email(&self, uid: &str, email_id: &str) -> Result<bool, String> {
match self {
Self::Redis(repository) => repository.delete_email(uid, email_id),
Self::Surreal(repository) => repository.delete_email(uid, email_id),
}
}
fn next_sequence(&self) -> Result<u64, String> {
match self {
Self::Redis(repository) => repository.next_sequence(),
Self::Surreal(repository) => repository.next_sequence(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PhoneUserRecord {
uid: String,
}
impl PhoneUserRecord {
fn new(uid: &str) -> Self {
Self {
uid: uid.to_string(),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PhoneContactRecord {
uid: String,
contact_uid: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PhoneMessageIndexRecord {
uid: String,
message_id: String,
is_read: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PhoneEmailIndexRecord {
uid: String,
email_id: String,
is_read: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PhoneMessageRecord {
message_id: String,
from_uid: String,
to_uid: String,
message: String,
timestamp: f64,
}
impl PhoneMessageRecord {
fn into_message(self, read: bool) -> PhoneMessage {
PhoneMessage {
id: self.message_id,
from: self.from_uid,
to: self.to_uid,
message: self.message,
timestamp: self.timestamp,
read,
}
}
}
impl From<&PhoneMessage> for PhoneMessageRecord {
fn from(message: &PhoneMessage) -> Self {
Self {
message_id: message.id.clone(),
from_uid: message.from.clone(),
to_uid: message.to.clone(),
message: message.message.clone(),
timestamp: message.timestamp,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
struct PhoneEmailRecord {
email_id: String,
from_uid: String,
to_uid: String,
subject: String,
body: String,
timestamp: f64,
}
impl PhoneEmailRecord {
fn into_email(self, read: bool) -> PhoneEmail {
PhoneEmail {
id: self.email_id,
from: self.from_uid,
to: self.to_uid,
subject: self.subject,
body: self.body,
timestamp: self.timestamp,
read,
}
}
}
impl From<&PhoneEmail> for PhoneEmailRecord {
fn from(email: &PhoneEmail) -> Self {
Self {
email_id: email.id.clone(),
from_uid: email.from.clone(),
to_uid: email.to.clone(),
subject: email.subject.clone(),
body: email.body.clone(),
timestamp: email.timestamp,
}
}
}
#[derive(Debug, Default, Clone, Serialize, Deserialize)]
struct PhoneSequenceRecord {
#[serde(default)]
sequence_id: String,
#[serde(default)]
value: u64,
}
pub struct SurrealPhoneRepository;
impl SurrealPhoneRepository {
fn save_user(&self, uid: &str) -> Result<(), String> {
let record = PhoneUserRecord::new(uid);
surreal_upsert("phone_user", uid, "phone user", &record)
}
fn message_is_referenced(&self, message_id: &str) -> Result<bool, String> {
Ok(!surreal_select_by_field::<PhoneMessageIndexRecord>(
"phone_message_index",
"phone message indexes",
"message_id",
message_id,
)?
.is_empty())
}
fn email_is_referenced(&self, email_id: &str) -> Result<bool, String> {
Ok(!surreal_select_by_field::<PhoneEmailIndexRecord>(
"phone_email_index",
"phone email indexes",
"email_id",
email_id,
)?
.is_empty())
}
fn cleanup_orphaned_records(&self) -> Result<(), String> {
let referenced_messages = surreal_select_all::<PhoneMessageIndexRecord>(
"phone_message_index",
"phone message indexes",
)?
.into_iter()
.map(|record| record.message_id)
.collect::<HashSet<_>>();
let referenced_emails = surreal_select_all::<PhoneEmailIndexRecord>(
"phone_email_index",
"phone email indexes",
)?
.into_iter()
.map(|record| record.email_id)
.collect::<HashSet<_>>();
for record in surreal_select_all::<PhoneMessageRecord>("phone_message", "phone messages")? {
let message_id = record.message_id.trim();
if !message_id.is_empty() && !referenced_messages.contains(message_id) {
surreal_delete::<PhoneMessageRecord>("phone_message", message_id, "phone message")?;
}
}
for record in surreal_select_all::<PhoneEmailRecord>("phone_email", "phone emails")? {
let email_id = record.email_id.trim();
if !email_id.is_empty() && !referenced_emails.contains(email_id) {
surreal_delete::<PhoneEmailRecord>("phone_email", email_id, "phone email")?;
}
}
Ok(())
}
fn contact_id(uid: &str, contact_uid: &str) -> String {
format!("{}:{}", uid, contact_uid)
}
fn message_index_id(uid: &str, message_id: &str) -> String {
format!("{}:{}", uid, message_id)
}
fn email_index_id(uid: &str, email_id: &str) -> String {
format!("{}:{}", uid, email_id)
}
}
impl PhoneRepository for SurrealPhoneRepository {
fn init(&self, uid: &str) -> Result<(), String> {
if surreal_select::<PhoneUserRecord>("phone_user", uid, "phone user")?.is_none() {
self.save_user(uid)?;
}
self.cleanup_orphaned_records()?;
Ok(())
}
fn add_contact(&self, uid: &str, contact_uid: &str) -> Result<bool, String> {
self.save_user(uid)?;
let record = PhoneContactRecord {
uid: uid.to_string(),
contact_uid: contact_uid.to_string(),
};
surreal_upsert(
"phone_contact",
&Self::contact_id(uid, contact_uid),
"phone contact",
&record,
)?;
Ok(true)
}
fn remove_contact(&self, uid: &str, contact_uid: &str) -> Result<bool, String> {
let id = Self::contact_id(uid, contact_uid);
let exists =
surreal_select::<PhoneContactRecord>("phone_contact", &id, "phone contact")?.is_some();
if !exists {
return Ok(false);
}
surreal_delete::<PhoneContactRecord>("phone_contact", &id, "phone contact")?;
Ok(true)
}
fn list_contacts(&self, uid: &str) -> Result<Vec<String>, String> {
let mut contacts =
surreal_select_by_uid::<PhoneContactRecord>("phone_contact", "phone contacts", uid)?
.into_iter()
.map(|record| record.contact_uid)
.collect::<Vec<_>>();
contacts.sort();
contacts.dedup();
Ok(contacts)
}
fn remove_phone(&self, uid: &str) -> Result<(), String> {
surreal_delete_by_uid("phone_contact", "phone contacts", uid)?;
surreal_delete_by_uid("phone_message_index", "phone message indexes", uid)?;
surreal_delete_by_uid("phone_email_index", "phone email indexes", uid)?;
surreal_delete::<PhoneUserRecord>("phone_user", uid, "phone user")?;
self.cleanup_orphaned_records()
}
fn append_message(&self, uid: &str, message: PhoneMessage) -> Result<(), String> {
self.save_user(uid)?;
let record = PhoneMessageRecord::from(&message);
surreal_upsert("phone_message", &message.id, "phone message", &record)?;
let index = PhoneMessageIndexRecord {
uid: uid.to_string(),
message_id: message.id.clone(),
is_read: message.from == uid,
};
surreal_upsert(
"phone_message_index",
&Self::message_index_id(uid, &message.id),
"phone message index",
&index,
)
}
fn list_messages(&self, uid: &str) -> Result<Vec<PhoneMessage>, String> {
let indexes = surreal_select_by_uid::<PhoneMessageIndexRecord>(
"phone_message_index",
"phone message indexes",
uid,
)?;
let mut messages = Vec::with_capacity(indexes.len());
for index in indexes {
if index.message_id.trim().is_empty() {
continue;
}
if let Some(record) = surreal_select::<PhoneMessageRecord>(
"phone_message",
&index.message_id,
"phone message",
)? {
messages.push(record.into_message(index.is_read));
}
}
messages.sort_by(|left, right| {
left.timestamp
.partial_cmp(&right.timestamp)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(messages)
}
fn mark_message_read(&self, uid: &str, message_id: &str) -> Result<bool, String> {
let id = Self::message_index_id(uid, message_id);
let Some(mut index) = surreal_select::<PhoneMessageIndexRecord>(
"phone_message_index",
&id,
"phone message index",
)?
else {
return Ok(false);
};
index.is_read = true;
surreal_upsert("phone_message_index", &id, "phone message index", &index)?;
Ok(true)
}
fn delete_message(&self, uid: &str, message_id: &str) -> Result<bool, String> {
let id = Self::message_index_id(uid, message_id);
let exists = surreal_select::<PhoneMessageIndexRecord>(
"phone_message_index",
&id,
"phone message index",
)?
.is_some();
if !exists {
return Ok(false);
}
surreal_delete::<PhoneMessageIndexRecord>(
"phone_message_index",
&id,
"phone message index",
)?;
if !self.message_is_referenced(message_id)? {
surreal_delete::<PhoneMessageRecord>("phone_message", message_id, "phone message")?;
}
Ok(true)
}
fn append_email(&self, uid: &str, email: PhoneEmail) -> Result<(), String> {
self.save_user(uid)?;
let record = PhoneEmailRecord::from(&email);
surreal_upsert("phone_email", &email.id, "phone email", &record)?;
let index = PhoneEmailIndexRecord {
uid: uid.to_string(),
email_id: email.id.clone(),
is_read: false,
};
surreal_upsert(
"phone_email_index",
&Self::email_index_id(uid, &email.id),
"phone email index",
&index,
)
}
fn list_emails(&self, uid: &str) -> Result<Vec<PhoneEmail>, String> {
let indexes = surreal_select_by_uid::<PhoneEmailIndexRecord>(
"phone_email_index",
"phone email indexes",
uid,
)?;
let mut emails = Vec::with_capacity(indexes.len());
for index in indexes {
if index.email_id.trim().is_empty() {
continue;
}
if let Some(record) =
surreal_select::<PhoneEmailRecord>("phone_email", &index.email_id, "phone email")?
{
emails.push(record.into_email(index.is_read));
}
}
emails.sort_by(|left, right| {
right
.timestamp
.partial_cmp(&left.timestamp)
.unwrap_or(std::cmp::Ordering::Equal)
});
Ok(emails)
}
fn mark_email_read(&self, uid: &str, email_id: &str) -> Result<bool, String> {
let id = Self::email_index_id(uid, email_id);
let Some(mut index) =
surreal_select::<PhoneEmailIndexRecord>("phone_email_index", &id, "phone email index")?
else {
return Ok(false);
};
index.is_read = true;
surreal_upsert("phone_email_index", &id, "phone email index", &index)?;
Ok(true)
}
fn delete_email(&self, uid: &str, email_id: &str) -> Result<bool, String> {
let id = Self::email_index_id(uid, email_id);
let exists =
surreal_select::<PhoneEmailIndexRecord>("phone_email_index", &id, "phone email index")?
.is_some();
if !exists {
return Ok(false);
}
surreal_delete::<PhoneEmailIndexRecord>("phone_email_index", &id, "phone email index")?;
if !self.email_is_referenced(email_id)? {
surreal_delete::<PhoneEmailRecord>("phone_email", email_id, "phone email")?;
}
Ok(true)
}
fn next_sequence(&self) -> Result<u64, String> {
let mut record =
surreal_select::<PhoneSequenceRecord>("phone_sequence", "global", "phone sequence")?
.unwrap_or_default();
record.sequence_id = "global".to_string();
record.value = record
.value
.checked_add(1)
.ok_or_else(|| "Phone sequence overflowed.".to_string())?;
surreal_upsert("phone_sequence", "global", "phone sequence", &record)?;
Ok(record.value)
}
}

View File

@ -9,6 +9,7 @@ use tokio::time::{Duration, sleep, timeout};
use crate::log;
use crate::redis::config::SurrealConfig;
use crate::schema;
pub type SurrealDb = Surreal<Client>;
@ -120,6 +121,8 @@ async fn connect(config: SurrealConfig) -> Result<SurrealDb, String> {
.await
.map_err(|error| error.to_string())?;
schema::apply_all(&db).await?;
Ok(db)
}

View File

@ -10,11 +10,12 @@ use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{LazyLock, Mutex as StdMutex};
use crate::{actor, bank, cad, garage, locker, org, phone, v_garage, v_locker};
mod routes;
use routes::route_command;
const CHUNK_PREFIX: &str = "FORGE_TRANSPORT_CHUNK:";
const RESPONSE_CHUNK_SIZE: usize = 12_000;
const UNSUPPORTED_ROUTE_PREFIX: &str = "Unsupported transport route";
static REQUEST_STORE: LazyLock<StdMutex<HashMap<String, String>>> =
LazyLock::new(|| StdMutex::new(HashMap::new()));
@ -143,903 +144,6 @@ fn parse_transport_argument_value(value: serde_json::Value) -> Result<Vec<String
}
}
fn route_command(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
match function_name {
"actor:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::get_actor(call_context, arguments[0].clone()))
}
"actor:create" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(actor::create_actor(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"actor:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(actor::update_actor(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"actor:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::actor_exists(call_context, arguments[0].clone()))
}
"actor:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::delete_actor(call_context, arguments[0].clone()))
}
"actor:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::init_hot_actor(call_context, arguments[0].clone()))
}
"actor:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::get_hot_actor(call_context, arguments[0].clone()))
}
"actor:hot:keys" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(actor::list_hot_actor_keys())
}
"actor:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(actor::override_hot_actor(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"actor:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::save_hot_actor(call_context, arguments[0].clone()))
}
"actor:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::remove_hot_actor(call_context, arguments[0].clone()))
}
"bank:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::get_bank(call_context, arguments[0].clone()))
}
"bank:create" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(bank::create_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"bank:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(bank::update_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"bank:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::bank_exists(call_context, arguments[0].clone()))
}
"bank:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::delete_bank(call_context, arguments[0].clone()))
}
"bank:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::init_hot_bank(call_context, arguments[0].clone()))
}
"bank:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::get_hot_bank(call_context, arguments[0].clone()))
}
"bank:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(bank::override_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"bank:hot:patch" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(bank::patch_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"bank:hot:charge_checkout" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::charge_checkout_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:deposit" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::deposit_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:withdraw" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::withdraw_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:deposit_earnings" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::deposit_earnings_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:transfer" => {
expect_arg_count(function_name, &arguments, 4)?;
Ok(bank::transfer_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
arguments[3].clone(),
))
}
"bank:hot:validate_pin" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::validate_pin_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::save_hot_bank(call_context, arguments[0].clone()))
}
"bank:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::remove_hot_bank(call_context, arguments[0].clone()))
}
"org:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_org(arguments[0].clone()))
}
"org:create" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::create_org(arguments[0].clone(), arguments[1].clone()))
}
"org:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::update_org(arguments[0].clone(), arguments[1].clone()))
}
"org:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::org_exists(arguments[0].clone()))
}
"org:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::delete_org(arguments[0].clone()))
}
"org:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::init_hot_org(arguments[0].clone()))
}
"org:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_hot_org(arguments[0].clone()))
}
"org:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::override_hot_org(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:hot:ensure_member" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::ensure_hot_org_member(arguments[0].clone()))
}
"org:hot:member_invites" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_hot_org_member_invites(arguments[0].clone()))
}
"org:hot:register" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::register_hot_org(arguments[0].clone()))
}
"org:hot:invite_member" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::invite_hot_org_member(arguments[0].clone()))
}
"org:hot:accept_invite" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::accept_hot_org_invite(arguments[0].clone()))
}
"org:hot:decline_invite" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::decline_hot_org_invite(arguments[0].clone()))
}
"org:hot:assign_credit_line" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::assign_credit_line_hot_org(arguments[0].clone()))
}
"org:hot:repay_credit_line" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::repay_credit_line_hot_org(arguments[0].clone()))
}
"org:hot:charge_checkout" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::charge_checkout_hot_org(arguments[0].clone()))
}
"org:hot:add_assets" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::add_assets_hot_org(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:hot:add_fleet" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::add_fleet_hot_org(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:hot:leave" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::leave_hot_org(arguments[0].clone()))
}
"org:hot:disband" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::disband_hot_org(arguments[0].clone()))
}
"org:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::save_hot_org(arguments[0].clone()))
}
"org:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::remove_hot_org(arguments[0].clone()))
}
"org:assets:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_assets(arguments[0].clone()))
}
"org:assets:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::update_assets(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:fleet:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_fleet(arguments[0].clone()))
}
"org:fleet:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::update_fleet(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:members:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_members(arguments[0].clone()))
}
"org:members:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::add_member(arguments[0].clone(), arguments[1].clone()))
}
"org:members:remove" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::remove_member(
arguments[0].clone(),
arguments[1].clone(),
))
}
"store:checkout" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(crate::store::checkout(arguments[0].clone()))
}
"garage:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::create_garage(call_context, arguments[0].clone()))
}
"garage:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::get_garage(call_context, arguments[0].clone()))
}
"garage:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::add_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::update_garage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:patch" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::patch_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:remove" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::remove_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::delete_garage(call_context, arguments[0].clone()))
}
"garage:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::garage_exists(call_context, arguments[0].clone()))
}
"garage:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::init_hot_garage(call_context, arguments[0].clone()))
}
"garage:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::get_hot_garage(call_context, arguments[0].clone()))
}
"garage:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::override_hot_garage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::save_hot_garage(call_context, arguments[0].clone()))
}
"garage:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::remove_hot_garage(
call_context,
arguments[0].clone(),
))
}
"garage:hot:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::add_hot_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:hot:remove_vehicle" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::remove_hot_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::create_locker(call_context, arguments[0].clone()))
}
"locker:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::get_locker(call_context, arguments[0].clone()))
}
"locker:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::add_item(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::update_locker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:patch" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::patch_item(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:remove" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::remove_item(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::delete_locker(call_context, arguments[0].clone()))
}
"locker:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::locker_exists(call_context, arguments[0].clone()))
}
"locker:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::init_hot_locker(call_context, arguments[0].clone()))
}
"locker:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::get_hot_locker(call_context, arguments[0].clone()))
}
"locker:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::override_hot_locker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::save_hot_locker(call_context, arguments[0].clone()))
}
"locker:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::remove_hot_locker(
call_context,
arguments[0].clone(),
))
}
"owned:garage:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::create_vgarage(call_context, arguments[0].clone()))
}
"owned:garage:fetch" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::fetch_vgarage(call_context, arguments[0].clone()))
}
"owned:garage:get" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_garage::get_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:garage:add" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_garage::add_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:garage:remove" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_garage::remove_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:garage:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::delete_vgarage(call_context, arguments[0].clone()))
}
"owned:garage:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::vgarage_exists(call_context, arguments[0].clone()))
}
"owned:garage:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::init_hot_vgarage(
call_context,
arguments[0].clone(),
))
}
"owned:garage:hot:fetch" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::fetch_hot_vgarage(
call_context,
arguments[0].clone(),
))
}
"owned:garage:hot:get" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_garage::get_hot_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:garage:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_garage::override_hot_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:garage:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::save_hot_vgarage(
call_context,
arguments[0].clone(),
))
}
"owned:garage:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::remove_hot_vgarage(
call_context,
arguments[0].clone(),
))
}
"owned:garage:hot:add" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_garage::add_hot_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:garage:hot:remove_item" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_garage::remove_hot_vgarage_item(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:locker:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::create_vlocker(call_context, arguments[0].clone()))
}
"owned:locker:fetch" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::fetch_vlocker(call_context, arguments[0].clone()))
}
"owned:locker:get" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_locker::get_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:locker:add" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_locker::add_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:locker:remove" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_locker::remove_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:locker:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::delete_vlocker(call_context, arguments[0].clone()))
}
"owned:locker:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::vlocker_exists(call_context, arguments[0].clone()))
}
"owned:locker:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::init_hot_vlocker(
call_context,
arguments[0].clone(),
))
}
"owned:locker:hot:fetch" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::fetch_hot_vlocker(
call_context,
arguments[0].clone(),
))
}
"owned:locker:hot:get" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_locker::get_hot_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:locker:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_locker::override_hot_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:locker:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::save_hot_vlocker(
call_context,
arguments[0].clone(),
))
}
"owned:locker:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::remove_hot_vlocker(
call_context,
arguments[0].clone(),
))
}
"cad:activity:append" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::append_activity(arguments[0].clone()))
}
"cad:activity:recent" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::recent_activity(arguments[0].clone()))
}
"cad:assignments:list" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(cad::list_assignments())
}
"cad:assignments:assign" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::assign_assignment(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:assignments:acknowledge" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::acknowledge_assignment(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:assignments:decline" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::decline_assignment(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:assignments:upsert" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::upsert_assignment(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:assignments:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::delete_assignment(arguments[0].clone()))
}
"cad:orders:list" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(cad::list_orders())
}
"cad:orders:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::create_order(arguments[0].clone()))
}
"cad:orders:create_from_context" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::create_order_from_context(arguments[0].clone()))
}
"cad:orders:close" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::close_order(arguments[0].clone()))
}
"cad:orders:upsert" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::upsert_order(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:orders:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::delete_order(arguments[0].clone()))
}
"cad:requests:list" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(cad::list_requests())
}
"cad:requests:submit" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::submit_request(arguments[0].clone()))
}
"cad:requests:submit_from_context" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::submit_request_from_context(arguments[0].clone()))
}
"cad:requests:close" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::close_request(arguments[0].clone()))
}
"cad:requests:upsert" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::upsert_request(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:requests:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::delete_request(arguments[0].clone()))
}
"cad:profiles:list" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(cad::list_profiles())
}
"cad:profiles:update_from_context" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::update_profile_from_context(arguments[0].clone()))
}
"cad:profiles:upsert" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::upsert_profile(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:profiles:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::delete_profile(arguments[0].clone()))
}
"cad:groups:build" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::build_groups(arguments[0].clone()))
}
"cad:view:hydrate" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::hydrate_view(arguments[0].clone()))
}
"phone:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::init_phone(arguments[0].clone()))
}
"phone:contacts:list" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::list_contacts(arguments[0].clone()))
}
"phone:contacts:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::add_contact(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:contacts:remove" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::remove_contact(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:messages:list" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::list_messages(arguments[0].clone()))
}
"phone:messages:thread" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::message_thread(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:messages:send" => {
expect_arg_count(function_name, &arguments, 4)?;
Ok(phone::send_message(
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
arguments[3].clone(),
))
}
"phone:messages:mark_read" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::mark_message_read(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:emails:list" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::list_emails(arguments[0].clone()))
}
"phone:emails:send" => {
expect_arg_count(function_name, &arguments, 5)?;
Ok(phone::send_email(
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
arguments[3].clone(),
arguments[4].clone(),
))
}
"phone:emails:mark_read" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::mark_email_read(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::remove_phone(arguments[0].clone()))
}
_ => Err(format!(
"{UNSUPPORTED_ROUTE_PREFIX} for function '{function_name}'"
)),
}
}
fn expect_arg_count(
function_name: &str,
arguments: &[String],
expected_count: usize,
) -> Result<(), String> {
if arguments.len() == expected_count {
return Ok(());
}
Err(format!(
"Transport route '{}' expected {} arguments but received {}",
function_name,
expected_count,
arguments.len()
))
}
fn chunk_response_if_needed(result: String) -> String {
if result.len() <= RESPONSE_CHUNK_SIZE {
return result;

View File

@ -0,0 +1,74 @@
use arma_rs::CallContext;
mod actor;
mod bank;
mod cad;
mod garage;
mod locker;
mod org;
mod phone;
mod store;
mod v_garage;
mod v_locker;
const UNSUPPORTED_ROUTE_PREFIX: &str = "Unsupported transport route";
pub(super) fn route_command(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
if function_name.starts_with("actor:") {
return actor::route(call_context, function_name, arguments);
}
if function_name.starts_with("bank:") {
return bank::route(call_context, function_name, arguments);
}
if function_name.starts_with("org:") {
return org::route(call_context, function_name, arguments);
}
if function_name == "store:checkout" {
return store::route(call_context, function_name, arguments);
}
if function_name.starts_with("garage:") {
return garage::route(call_context, function_name, arguments);
}
if function_name.starts_with("locker:") {
return locker::route(call_context, function_name, arguments);
}
if function_name.starts_with("owned:garage:") {
return v_garage::route(call_context, function_name, arguments);
}
if function_name.starts_with("owned:locker:") {
return v_locker::route(call_context, function_name, arguments);
}
if function_name.starts_with("cad:") {
return cad::route(call_context, function_name, arguments);
}
if function_name.starts_with("phone:") {
return phone::route(call_context, function_name, arguments);
}
Err(unsupported_route(function_name))
}
pub(super) fn expect_arg_count(
function_name: &str,
arguments: &[String],
expected_count: usize,
) -> Result<(), String> {
if arguments.len() == expected_count {
return Ok(());
}
Err(format!(
"Transport route '{}' expected {} arguments but received {}",
function_name,
expected_count,
arguments.len()
))
}
pub(super) fn unsupported_route(function_name: &str) -> String {
format!("{UNSUPPORTED_ROUTE_PREFIX} for function '{function_name}'")
}

View File

@ -0,0 +1,72 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::actor;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"actor:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::get_actor(call_context, arguments[0].clone()))
}
"actor:create" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(actor::create_actor(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"actor:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(actor::update_actor(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"actor:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::actor_exists(call_context, arguments[0].clone()))
}
"actor:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::delete_actor(call_context, arguments[0].clone()))
}
"actor:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::init_hot_actor(call_context, arguments[0].clone()))
}
"actor:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::get_hot_actor(call_context, arguments[0].clone()))
}
"actor:hot:keys" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(actor::list_hot_actor_keys())
}
"actor:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(actor::override_hot_actor(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"actor:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::save_hot_actor(call_context, arguments[0].clone()))
}
"actor:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(actor::remove_hot_actor(call_context, arguments[0].clone()))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,131 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::bank;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"bank:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::get_bank(call_context, arguments[0].clone()))
}
"bank:create" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(bank::create_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"bank:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(bank::update_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"bank:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::bank_exists(call_context, arguments[0].clone()))
}
"bank:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::delete_bank(call_context, arguments[0].clone()))
}
"bank:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::init_hot_bank(call_context, arguments[0].clone()))
}
"bank:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::get_hot_bank(call_context, arguments[0].clone()))
}
"bank:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(bank::override_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"bank:hot:patch" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(bank::patch_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"bank:hot:charge_checkout" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::charge_checkout_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:deposit" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::deposit_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:withdraw" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::withdraw_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:deposit_earnings" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::deposit_earnings_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:transfer" => {
expect_arg_count(function_name, &arguments, 4)?;
Ok(bank::transfer_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
arguments[3].clone(),
))
}
"bank:hot:validate_pin" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(bank::validate_pin_hot_bank(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"bank:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::save_hot_bank(call_context, arguments[0].clone()))
}
"bank:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(bank::remove_hot_bank(call_context, arguments[0].clone()))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,141 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::cad;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"cad:activity:append" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::append_activity(arguments[0].clone()))
}
"cad:activity:recent" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::recent_activity(arguments[0].clone()))
}
"cad:assignments:list" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(cad::list_assignments())
}
"cad:assignments:assign" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::assign_assignment(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:assignments:acknowledge" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::acknowledge_assignment(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:assignments:decline" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::decline_assignment(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:assignments:upsert" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::upsert_assignment(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:assignments:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::delete_assignment(arguments[0].clone()))
}
"cad:orders:list" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(cad::list_orders())
}
"cad:orders:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::create_order(arguments[0].clone()))
}
"cad:orders:create_from_context" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::create_order_from_context(arguments[0].clone()))
}
"cad:orders:close" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::close_order(arguments[0].clone()))
}
"cad:orders:upsert" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::upsert_order(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:orders:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::delete_order(arguments[0].clone()))
}
"cad:requests:list" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(cad::list_requests())
}
"cad:requests:submit" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::submit_request(arguments[0].clone()))
}
"cad:requests:submit_from_context" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::submit_request_from_context(arguments[0].clone()))
}
"cad:requests:close" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::close_request(arguments[0].clone()))
}
"cad:requests:upsert" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::upsert_request(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:requests:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::delete_request(arguments[0].clone()))
}
"cad:profiles:list" => {
expect_arg_count(function_name, &arguments, 0)?;
Ok(cad::list_profiles())
}
"cad:profiles:update_from_context" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::update_profile_from_context(arguments[0].clone()))
}
"cad:profiles:upsert" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(cad::upsert_profile(
arguments[0].clone(),
arguments[1].clone(),
))
}
"cad:profiles:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::delete_profile(arguments[0].clone()))
}
"cad:groups:build" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::build_groups(arguments[0].clone()))
}
"cad:view:hydrate" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(cad::hydrate_view(arguments[0].clone()))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,107 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::garage;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"garage:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::create_garage(call_context, arguments[0].clone()))
}
"garage:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::get_garage(call_context, arguments[0].clone()))
}
"garage:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::add_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::update_garage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:patch" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::patch_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:remove" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::remove_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::delete_garage(call_context, arguments[0].clone()))
}
"garage:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::garage_exists(call_context, arguments[0].clone()))
}
"garage:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::init_hot_garage(call_context, arguments[0].clone()))
}
"garage:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::get_hot_garage(call_context, arguments[0].clone()))
}
"garage:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::override_hot_garage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::save_hot_garage(call_context, arguments[0].clone()))
}
"garage:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(garage::remove_hot_garage(
call_context,
arguments[0].clone(),
))
}
"garage:hot:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::add_hot_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"garage:hot:remove_vehicle" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(garage::remove_hot_vehicle(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,91 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::locker;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"locker:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::create_locker(call_context, arguments[0].clone()))
}
"locker:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::get_locker(call_context, arguments[0].clone()))
}
"locker:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::add_item(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::update_locker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:patch" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::patch_item(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:remove" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::remove_item(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::delete_locker(call_context, arguments[0].clone()))
}
"locker:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::locker_exists(call_context, arguments[0].clone()))
}
"locker:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::init_hot_locker(call_context, arguments[0].clone()))
}
"locker:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::get_hot_locker(call_context, arguments[0].clone()))
}
"locker:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(locker::override_hot_locker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"locker:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::save_hot_locker(call_context, arguments[0].clone()))
}
"locker:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(locker::remove_hot_locker(
call_context,
arguments[0].clone(),
))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,154 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::org;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"org:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_org(arguments[0].clone()))
}
"org:create" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::create_org(arguments[0].clone(), arguments[1].clone()))
}
"org:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::update_org(arguments[0].clone(), arguments[1].clone()))
}
"org:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::org_exists(arguments[0].clone()))
}
"org:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::delete_org(arguments[0].clone()))
}
"org:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::init_hot_org(arguments[0].clone()))
}
"org:hot:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_hot_org(arguments[0].clone()))
}
"org:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::override_hot_org(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:hot:ensure_member" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::ensure_hot_org_member(arguments[0].clone()))
}
"org:hot:member_invites" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_hot_org_member_invites(arguments[0].clone()))
}
"org:hot:register" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::register_hot_org(arguments[0].clone()))
}
"org:hot:invite_member" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::invite_hot_org_member(arguments[0].clone()))
}
"org:hot:accept_invite" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::accept_hot_org_invite(arguments[0].clone()))
}
"org:hot:decline_invite" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::decline_hot_org_invite(arguments[0].clone()))
}
"org:hot:assign_credit_line" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::assign_credit_line_hot_org(arguments[0].clone()))
}
"org:hot:repay_credit_line" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::repay_credit_line_hot_org(arguments[0].clone()))
}
"org:hot:charge_checkout" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::charge_checkout_hot_org(arguments[0].clone()))
}
"org:hot:add_assets" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::add_assets_hot_org(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:hot:add_fleet" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::add_fleet_hot_org(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:hot:leave" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::leave_hot_org(arguments[0].clone()))
}
"org:hot:disband" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::disband_hot_org(arguments[0].clone()))
}
"org:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::save_hot_org(arguments[0].clone()))
}
"org:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::remove_hot_org(arguments[0].clone()))
}
"org:assets:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_assets(arguments[0].clone()))
}
"org:assets:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::update_assets(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:fleet:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_fleet(arguments[0].clone()))
}
"org:fleet:update" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::update_fleet(
arguments[0].clone(),
arguments[1].clone(),
))
}
"org:members:get" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(org::get_members(arguments[0].clone()))
}
"org:members:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::add_member(arguments[0].clone(), arguments[1].clone()))
}
"org:members:remove" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(org::remove_member(
arguments[0].clone(),
arguments[1].clone(),
))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,104 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::phone;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"phone:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::init_phone(arguments[0].clone()))
}
"phone:contacts:list" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::list_contacts(arguments[0].clone()))
}
"phone:contacts:add" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::add_contact(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:contacts:remove" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::remove_contact(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:messages:list" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::list_messages(arguments[0].clone()))
}
"phone:messages:thread" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::message_thread(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:messages:send" => {
expect_arg_count(function_name, &arguments, 4)?;
Ok(phone::send_message(
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
arguments[3].clone(),
))
}
"phone:messages:mark_read" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::mark_message_read(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:messages:delete" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::delete_message(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:emails:list" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::list_emails(arguments[0].clone()))
}
"phone:emails:send" => {
expect_arg_count(function_name, &arguments, 5)?;
Ok(phone::send_email(
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
arguments[3].clone(),
arguments[4].clone(),
))
}
"phone:emails:mark_read" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::mark_email_read(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:emails:delete" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(phone::delete_email(
arguments[0].clone(),
arguments[1].clone(),
))
}
"phone:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(phone::remove_phone(arguments[0].clone()))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,18 @@
use arma_rs::CallContext;
use super::expect_arg_count;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"store:checkout" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(crate::store::checkout(arguments[0].clone()))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,120 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::v_garage;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"owned:garage:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::create_vgarage(call_context, arguments[0].clone()))
}
"owned:garage:fetch" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::fetch_vgarage(call_context, arguments[0].clone()))
}
"owned:garage:get" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_garage::get_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:garage:add" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_garage::add_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:garage:remove" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_garage::remove_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:garage:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::delete_vgarage(call_context, arguments[0].clone()))
}
"owned:garage:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::vgarage_exists(call_context, arguments[0].clone()))
}
"owned:garage:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::init_hot_vgarage(
call_context,
arguments[0].clone(),
))
}
"owned:garage:hot:fetch" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::fetch_hot_vgarage(
call_context,
arguments[0].clone(),
))
}
"owned:garage:hot:get" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_garage::get_hot_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:garage:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_garage::override_hot_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:garage:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::save_hot_vgarage(
call_context,
arguments[0].clone(),
))
}
"owned:garage:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_garage::remove_hot_vgarage(
call_context,
arguments[0].clone(),
))
}
"owned:garage:hot:add" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_garage::add_hot_vgarage(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:garage:hot:remove_item" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_garage::remove_hot_vgarage_item(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
_ => Err(super::unsupported_route(function_name)),
}
}

View File

@ -0,0 +1,102 @@
use arma_rs::CallContext;
use super::expect_arg_count;
use crate::v_locker;
pub(super) fn route(
call_context: CallContext,
function_name: &str,
arguments: Vec<String>,
) -> Result<String, String> {
let _ = &call_context;
match function_name {
"owned:locker:create" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::create_vlocker(call_context, arguments[0].clone()))
}
"owned:locker:fetch" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::fetch_vlocker(call_context, arguments[0].clone()))
}
"owned:locker:get" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_locker::get_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:locker:add" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_locker::add_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:locker:remove" => {
expect_arg_count(function_name, &arguments, 3)?;
Ok(v_locker::remove_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
arguments[2].clone(),
))
}
"owned:locker:delete" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::delete_vlocker(call_context, arguments[0].clone()))
}
"owned:locker:exists" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::vlocker_exists(call_context, arguments[0].clone()))
}
"owned:locker:hot:init" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::init_hot_vlocker(
call_context,
arguments[0].clone(),
))
}
"owned:locker:hot:fetch" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::fetch_hot_vlocker(
call_context,
arguments[0].clone(),
))
}
"owned:locker:hot:get" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_locker::get_hot_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:locker:hot:override" => {
expect_arg_count(function_name, &arguments, 2)?;
Ok(v_locker::override_hot_vlocker(
call_context,
arguments[0].clone(),
arguments[1].clone(),
))
}
"owned:locker:hot:save" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::save_hot_vlocker(
call_context,
arguments[0].clone(),
))
}
"owned:locker:hot:remove" => {
expect_arg_count(function_name, &arguments, 1)?;
Ok(v_locker::remove_hot_vlocker(
call_context,
arguments[0].clone(),
))
}
_ => Err(super::unsupported_route(function_name)),
}
}

101
history.txt Normal file
View File

@ -0,0 +1,101 @@
#V2
DEFINE FIELD OVERWRITE updated_at ON org_fleet_vehicle TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS org_fleet_vehicle_org ON org_fleet_vehicle COLUMNS org_id;
DEFINE INDEX IF NOT EXISTS org_fleet_vehicle_unique ON org_fleet_vehicle COLUMNS org_id, fleet_key UNIQUE;
DEFINE TABLE IF NOT EXISTS locker SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON locker TYPE string;
DEFINE FIELD OVERWRITE updated_at ON locker TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS locker_uid ON locker COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS locker_item SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON locker_item TYPE string;
DEFINE FIELD IF NOT EXISTS category ON locker_item TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON locker_item TYPE string;
DEFINE FIELD IF NOT EXISTS amount ON locker_item TYPE int;
DEFINE FIELD OVERWRITE updated_at ON locker_item TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS locker_item_owner ON locker_item COLUMNS uid;
DEFINE INDEX IF NOT EXISTS locker_item_unique ON locker_item COLUMNS uid, classname UNIQUE;
DEFINE TABLE IF NOT EXISTS owned_locker SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON owned_locker TYPE string;
DEFINE FIELD OVERWRITE updated_at ON owned_locker TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS owned_locker_uid ON owned_locker COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS locker_unlock SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON locker_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS category ON locker_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON locker_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS source ON locker_unlock TYPE option<string>;
DEFINE FIELD OVERWRITE unlocked_at ON locker_unlock TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS locker_unlock_owner ON locker_unlock COLUMNS uid;
DEFINE INDEX IF NOT EXISTS locker_unlock_unique ON locker_unlock COLUMNS uid, category, classname UNIQUE;
DEFINE TABLE IF NOT EXISTS garage SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON garage TYPE string;
DEFINE FIELD OVERWRITE updated_at ON garage TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS garage_uid ON garage COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS garage_vehicle SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON garage_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS plate ON garage_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON garage_vehicle TYPE string;
DEFINE FIELD IF NOT EXISTS fuel ON garage_vehicle TYPE number;
DEFINE FIELD IF NOT EXISTS damage ON garage_vehicle TYPE number;
DEFINE FIELD IF NOT EXISTS hit_points ON garage_vehicle TYPE object;
DEFINE FIELD OVERWRITE updated_at ON garage_vehicle TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS garage_vehicle_owner ON garage_vehicle COLUMNS uid;
DEFINE INDEX IF NOT EXISTS garage_vehicle_unique ON garage_vehicle COLUMNS uid, plate UNIQUE;
DEFINE TABLE IF NOT EXISTS owned_garage SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON owned_garage TYPE string;
DEFINE FIELD OVERWRITE updated_at ON owned_garage TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS owned_garage_uid ON owned_garage COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS garage_unlock SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON garage_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS category ON garage_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS classname ON garage_unlock TYPE string;
DEFINE FIELD IF NOT EXISTS source ON garage_unlock TYPE option<string>;
DEFINE FIELD OVERWRITE unlocked_at ON garage_unlock TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS garage_unlock_owner ON garage_unlock COLUMNS uid;
DEFINE INDEX IF NOT EXISTS garage_unlock_unique ON garage_unlock COLUMNS uid, category, classname UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_user SCHEMALESS;
DEFINE FIELD IF NOT EXISTS uid ON phone_user TYPE string;
DEFINE FIELD OVERWRITE updated_at ON phone_user TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_user_uid ON phone_user COLUMNS uid UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_contact SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON phone_contact TYPE string;
DEFINE FIELD IF NOT EXISTS contact_uid ON phone_contact TYPE string;
DEFINE FIELD OVERWRITE created_at ON phone_contact TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_contact_owner ON phone_contact COLUMNS uid;
DEFINE INDEX IF NOT EXISTS phone_contact_unique ON phone_contact COLUMNS uid, contact_uid UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_message SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS message_id ON phone_message TYPE string;
DEFINE FIELD IF NOT EXISTS from_uid ON phone_message TYPE string;
DEFINE FIELD IF NOT EXISTS to_uid ON phone_message TYPE string;
DEFINE FIELD IF NOT EXISTS message ON phone_message TYPE string;
DEFINE FIELD IF NOT EXISTS timestamp ON phone_message TYPE number;
DEFINE FIELD OVERWRITE created_at ON phone_message TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_message_message_id ON phone_message COLUMNS message_id UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_message_index SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON phone_message_index TYPE string;
DEFINE FIELD IF NOT EXISTS message_id ON phone_message_index TYPE string;
DEFINE FIELD IF NOT EXISTS is_read ON phone_message_index TYPE bool;
DEFINE FIELD OVERWRITE updated_at ON phone_message_index TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_message_index_owner ON phone_message_index COLUMNS uid;
DEFINE INDEX IF NOT EXISTS phone_message_index_message ON phone_message_index COLUMNS message_id;
DEFINE INDEX IF NOT EXISTS phone_message_index_unique ON phone_message_index COLUMNS uid, message_id UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_email SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS email_id ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS from_uid ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS to_uid ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS subject ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS body ON phone_email TYPE string;
DEFINE FIELD IF NOT EXISTS timestamp ON phone_email TYPE number;
DEFINE FIELD OVERWRITE created_at ON phone_email TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_email_email_id ON phone_email COLUMNS email_id UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_email_index SCHEMAFULL;
DEFINE FIELD IF NOT EXISTS uid ON phone_email_index TYPE string;
DEFINE FIELD IF NOT EXISTS email_id ON phone_email_index TYPE string;
DEFINE FIELD IF NOT EXISTS is_read ON phone_email_index TYPE bool;
DEFINE FIELD OVERWRITE updated_at ON phone_email_index TYPE option<datetime>;
DEFINE INDEX IF NOT EXISTS phone_email_index_owner ON phone_email_index COLUMNS uid;
DEFINE INDEX IF NOT EXISTS phone_email_index_email ON phone_email_index COLUMNS email_id;
DEFINE INDEX IF NOT EXISTS phone_email_index_unique ON phone_email_index COLUMNS uid, email_id UNIQUE;
DEFINE TABLE IF NOT EXISTS phone_sequence SCHEMALESS;
DEFINE FIELD IF NOT EXISTS sequence_id ON phone_sequence TYPE string;
DEFINE FIELD IF NOT EXISTS value ON phone_sequence TYPE int;
DEFINE INDEX IF NOT EXISTS phone_sequence_id ON phone_sequence COLUMNS sequence_id UNIQUE;