mnml/server/src/mtx.rs
2019-09-20 14:56:49 +10:00

250 lines
6.7 KiB
Rust

use std::convert::TryFrom;
use uuid::Uuid;
// use rand::prelude::*;
use postgres::transaction::Transaction;
use failure::Error;
use failure::err_msg;
use account;
use account::Account;
use construct::{Construct, construct_select, construct_write, construct_spawn};
use names::{name as generate_name};
use img;
pub const FREE_MTX: [MtxVariant; 2] = [
MtxVariant::Rename,
MtxVariant::Shapes,
];
pub const SHOP_LISTINGS: [Listing; 2] = [
Listing { variant: MtxVariant::Molecular, credits: 10 },
Listing { variant: MtxVariant::Invader, credits: 10 },
];
const NEW_IMAGE_COST: i64 = 1;
const NEW_NAME_COST: i64 = 5;
const NEW_CONSTRUCT_COST: i64 = 50;
#[derive(Debug,Clone,PartialEq,Serialize,Deserialize)]
pub struct Shop {
owned: Vec<MtxVariant>,
available: Vec<Listing>,
}
#[derive(Debug,Copy,Clone,PartialEq,Serialize,Deserialize)]
pub struct Listing {
variant: MtxVariant,
credits: usize,
}
#[derive(Debug,Copy,Clone,PartialEq,Serialize,Deserialize)]
pub enum MtxVariant {
Rename,
Molecular,
Invader,
Shapes,
}
impl MtxVariant {
fn to_sql(&self) -> String {
format!("{:?}", *self)
}
}
// could use postgres-derive
// but enums in pg are a bit of a pain
impl TryFrom<String> for MtxVariant {
type Error = Error;
fn try_from(v: String) -> Result<MtxVariant, Error> {
match v.as_ref() {
"Rename" => Ok(MtxVariant::Rename),
"Molecular" => Ok(MtxVariant::Molecular),
"Invader" => Ok(MtxVariant::Invader),
"Shapes" => Ok(MtxVariant::Shapes),
_ => Err(format_err!("mtx variant not found variant={:?}", v)),
}
}
}
#[derive(Debug,Copy,Clone,Serialize,Deserialize)]
pub struct Mtx {
id: Uuid,
account: Uuid,
variant: MtxVariant,
}
impl Mtx {
pub fn new(variant: MtxVariant, account: Uuid) -> Mtx {
match variant {
_ => Mtx { id: Uuid::new_v4(), account, variant },
// MtxVariant::Invader => Mtx { id: Uuid::new_v4(), account, variant: self },
// MtxVariant::Molecular => Mtx { id: Uuid::new_v4(), account, variant: self },
}
}
pub fn delete(tx: &mut Transaction, id: Uuid) -> Result<(), Error> {
let query = "
DELETE
FROM mtx
WHERE id = $1;
";
let result = tx
.execute(query, &[&id])?;
if result != 1 {
return Err(format_err!("unable to delete mtx {:?}", id));
}
info!("mtx deleted {:?}", id);
return Ok(());
}
pub fn insert(&self, tx: &mut Transaction) -> Result<&Mtx, Error> {
let query = "
INSERT INTO mtx (id, account, variant)
VALUES ($1, $2, $3)
RETURNING id, account;
";
let result = tx
.query(query, &[&self.id, &self.account, &self.variant.to_sql()])?;
result.iter().next().ok_or(err_msg("mtx not written"))?;
info!("wrote mtx {:?}", self);
return Ok(self);
}
// pub fn update(&self, tx: &mut Transaction) -> Result<&Mtx, Error> {
// let query = "
// UPDATE mtx
// SET data = $1, updated_at = now()
// WHERE id = $2
// RETURNING id, data;
// ";
// let result = tx
// .query(query, &[&self.id, &to_vec(self)?])?;
// if let None = result.iter().next() {
// return Err(err_msg("mtx not written"));
// }
// info!("wrote mtx {:?}", self);
// return Ok(self);
// }
}
pub fn apply(tx: &mut Transaction, account: &Account, variant: MtxVariant, construct_id: Uuid, name: String) -> Result<Vec<Construct>, Error> {
let mtx = select(tx, variant, account.id)?;
let mut construct = construct_select(tx, construct_id, account.id)?;
let cost = match mtx.variant {
MtxVariant::Rename => NEW_NAME_COST,
_ => NEW_IMAGE_COST,
};
account::debit(tx, account.id, cost)?;
construct = match mtx.variant {
MtxVariant::Rename => construct.new_name(name)?,
_ => construct.new_img(),
};
match mtx.variant {
MtxVariant::Invader => img::invader_write(construct.img)?,
MtxVariant::Molecular => img::molecular_write(construct.img)?,
MtxVariant::Shapes => img::shapes_write(construct.img)?,
_ => construct.img,
};
construct_write(tx, construct)?;
account::team(tx, account)
}
pub fn select(tx: &mut Transaction, variant: MtxVariant, account: Uuid) -> Result<Mtx, Error> {
let query = "
SELECT id, account, variant
FROM mtx
WHERE account = $1
AND variant = $2
FOR UPDATE;
";
let result = tx
.query(query, &[&account, &variant.to_sql()])?;
if let Some(row) = result.iter().next() {
let id: Uuid = row.get(0);
let account: Uuid = row.get(1);
let v_str: String = row.get(2);
let variant = MtxVariant::try_from(v_str)?;
Ok(Mtx { id, account, variant })
} else {
Err(format_err!("mtx not found account={:?} variant={:?}", account, variant))
}
}
pub fn new_construct(tx: &mut Transaction, account: &Account) -> Result<Construct, Error> {
account::debit(tx, account.id, NEW_CONSTRUCT_COST)?;
let construct = construct_spawn(tx, account.id, generate_name(), false)?;
Ok(construct)
}
pub fn account_shop(tx: &mut Transaction, account: &Account) -> Result<Shop, Error> {
let query = "
SELECT id, variant
FROM mtx
WHERE account = $1;
";
let result = tx
.query(query, &[&account.id])?;
let mut owned = vec![];
for row in result.iter() {
let id: Uuid = row.get(0);
let v_str: String = row.get(1);
let variant = match MtxVariant::try_from(v_str) {
Ok(v) => v,
Err(e) => {
warn!("id={:?} {:?}", id, e);
continue;
},
};
owned.push(variant);
};
let available = SHOP_LISTINGS.iter()
.filter(|l| !owned.contains(&l.variant))
.map(|l| *l)
.collect::<Vec<Listing>>();
debug!("account shop acount={:?} owned={:?} available={:?}",
account.name, owned, available);
return Ok(Shop { owned, available });
}
pub fn buy(tx: &mut Transaction, account: &Account, mtx: MtxVariant) -> Result<Shop, Error> {
let listing = SHOP_LISTINGS
.iter()
.find(|l| l.variant == mtx)
.ok_or(format_err!("mtx variant not found variant={:?}", mtx))?;
let account = account::debit(tx, account.id, listing.credits as i64)?;
Mtx::new(mtx, account.id)
.insert(tx)?;
info!("account purchased mtx account={:?} mtx={:?} cost={:?} balance={:?}",
account.name, mtx, listing.credits, account.balance);
account_shop(tx, &account)
}