Remove Redis backend support #4

Merged
J.Schmidt92 merged 5 commits from feature/surrealdb-storage into master 2026-04-17 17:29:34 -05:00
31 changed files with 3621 additions and 2228 deletions
Showing only changes of commit 06c634c642 - Show all commits

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. 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_user`: owner row for an initialized phone profile.
- `phone:message:{messageId}`: hash containing message record fields - `phone_contact`: per-owner contact rows keyed by owner UID and contact UID.
- `phone:{uid}:messages`: list of message IDs visible to the user - `phone_message`: shared message records.
- `phone:{uid}:thread:{otherUid}`: list of message IDs for a conversation - `phone_message_index`: per-owner message visibility and read state.
- `phone:{uid}:message_read`: hash of message ID to per-user read state - `phone_email`: shared email records.
- `phone:email:{emailId}`: hash containing email record fields - `phone_email_index`: per-owner email visibility and read state.
- `phone:{uid}:emails`: list of email IDs visible to the user - `phone_sequence`: global sequence state for generated message and email IDs.
- `phone:{uid}:email_read`: hash of email ID to per-user read state

View File

@ -23,6 +23,7 @@ mod log;
pub mod org; pub mod org;
pub mod phone; pub mod phone;
pub mod redis; pub mod redis;
pub mod schema;
pub mod storage; pub mod storage;
pub mod store; pub mod store;
pub mod surreal; 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::log;
use crate::redis::config::SurrealConfig; use crate::redis::config::SurrealConfig;
use crate::schema;
pub type SurrealDb = Surreal<Client>; pub type SurrealDb = Surreal<Client>;
@ -120,6 +121,8 @@ async fn connect(config: SurrealConfig) -> Result<SurrealDb, String> {
.await .await
.map_err(|error| error.to_string())?; .map_err(|error| error.to_string())?;
schema::apply_all(&db).await?;
Ok(db) Ok(db)
} }

View File

@ -10,11 +10,12 @@ use std::collections::HashMap;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
use std::sync::{LazyLock, Mutex as StdMutex}; 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 CHUNK_PREFIX: &str = "FORGE_TRANSPORT_CHUNK:";
const RESPONSE_CHUNK_SIZE: usize = 12_000; const RESPONSE_CHUNK_SIZE: usize = 12_000;
const UNSUPPORTED_ROUTE_PREFIX: &str = "Unsupported transport route";
static REQUEST_STORE: LazyLock<StdMutex<HashMap<String, String>>> = static REQUEST_STORE: LazyLock<StdMutex<HashMap<String, String>>> =
LazyLock::new(|| StdMutex::new(HashMap::new())); 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 { fn chunk_response_if_needed(result: String) -> String {
if result.len() <= RESPONSE_CHUNK_SIZE { if result.len() <= RESPONSE_CHUNK_SIZE {
return result; 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;