code cleanup, starts testing
This commit is contained in:
1
.gitignore
vendored
1
.gitignore
vendored
@@ -3,6 +3,7 @@
|
||||
|
||||
node_modules
|
||||
fontawesome
|
||||
package-lock.json
|
||||
|
||||
Cargo.lock
|
||||
**/*.sqlite3
|
||||
|
||||
@@ -1,3 +1,8 @@
|
||||
//! Loot-a-lot Database module
|
||||
//!
|
||||
//! # Description
|
||||
//! This module wraps all needed database operations.
|
||||
//! It exports a public API for integration with various clients (REST Api, CLI, ...)
|
||||
extern crate dotenv;
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
@@ -86,25 +91,19 @@ impl<'q> DbApi<'q> {
|
||||
Self(conn)
|
||||
}
|
||||
/// Fetch the list of all players
|
||||
///
|
||||
/// This method consumes the DbApi object.
|
||||
pub fn fetch_players(self) -> QueryResult<Vec<models::Player>> {
|
||||
Ok(schema::players::table.load::<models::Player>(self.0)?)
|
||||
}
|
||||
/// Fetch the inventory of items
|
||||
///
|
||||
/// Consumes the DbApi instance
|
||||
pub fn fetch_inventory(self) -> QueryResult<Vec<models::Item>> {
|
||||
Ok(schema::items::table.load::<models::Item>(self.0)?)
|
||||
}
|
||||
|
||||
/// Fetch all existing claims
|
||||
pub fn fetch_claims(self) -> QueryResult<Vec<models::Claim>> {
|
||||
Ok(schema::claims::table.load::<models::Claim>(self.0)?)
|
||||
}
|
||||
/// Wrapper for acting as a specific player
|
||||
///
|
||||
/// The DbApi is moved inside a new AsPlayer object.
|
||||
///
|
||||
/// # Usage
|
||||
/// ```
|
||||
/// # use lootalot_db::{DbConnection, DbApi};
|
||||
@@ -124,7 +123,7 @@ impl<'q> DbApi<'q> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A wrapper for interactions of players with the database
|
||||
/// A wrapper for interactions of players with the database.
|
||||
/// Possible actions are exposed as methods
|
||||
pub struct AsPlayer<'q> {
|
||||
id: i32,
|
||||
@@ -157,10 +156,10 @@ impl<'q> AsPlayer<'q> {
|
||||
let current_wealth = players
|
||||
.find(self.id)
|
||||
.select((cp, sp, gp, pp))
|
||||
.first::<models::WealthUpdate>(self.conn)?;
|
||||
.first::<models::Wealth>(self.conn)?;
|
||||
// TODO: improve this
|
||||
// should be move inside a WealthUpdate method
|
||||
let update = models::WealthUpdate::from_gp(current_wealth.to_gp() + value_in_gp);
|
||||
let update = models::Wealth::from_gp(current_wealth.to_gp() + value_in_gp);
|
||||
// Difference in coins that is sent back
|
||||
let (old, new) = (current_wealth.as_tuple(), update.as_tuple());
|
||||
let diff = (new.0 - old.0, new.1 - old.1, new.2 - old.2, new.3 - old.3);
|
||||
@@ -187,12 +186,9 @@ impl<'q> AsPlayer<'q> {
|
||||
if !exists {
|
||||
return Ok(ActionStatus::nop());
|
||||
};
|
||||
let request = models::NewClaim {
|
||||
player_id: self.id,
|
||||
loot_id: item,
|
||||
};
|
||||
let claim = models::claim::NewClaim::new(self.id, item);
|
||||
diesel::insert_into(schema::claims::table)
|
||||
.values(&request)
|
||||
.values(&claim)
|
||||
.execute(self.conn)
|
||||
.map(|r| match r {
|
||||
1 => ActionStatus::ok(),
|
||||
@@ -221,7 +217,7 @@ pub struct AsAdmin<'q>(&'q DbConnection);
|
||||
impl<'q> AsAdmin<'q> {
|
||||
pub fn add_player(self, name: String, start_wealth: f32) -> ActionResult<()> {
|
||||
diesel::insert_into(schema::players::table)
|
||||
.values(&models::NewPlayer::create(&name, start_wealth))
|
||||
.values(&models::player::NewPlayer::create(&name, start_wealth))
|
||||
.execute(self.0)
|
||||
.map(|r| match r {
|
||||
1 => ActionStatus::ok(),
|
||||
@@ -256,4 +252,39 @@ pub fn create_pool() -> Pool {
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {}
|
||||
mod tests {
|
||||
use super::*;
|
||||
type TestConnection = DbConnection;
|
||||
|
||||
fn test_connection() -> TestConnection {
|
||||
let test_conn = DbConnection::establish(":memory:").unwrap();
|
||||
diesel_migrations::run_pending_migrations(&test_conn).unwrap();
|
||||
test_conn
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_group_is_autocreated() {
|
||||
let conn = test_connection();
|
||||
let players = DbApi::with_conn(&conn).fetch_players().unwrap();
|
||||
assert_eq!(players.len(), 1);
|
||||
let group = players.get(0).unwrap();
|
||||
assert_eq!(group.id, 0);
|
||||
assert_eq!(group.name, "Groupe".to_string());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_add_player() {
|
||||
let conn = test_connection();
|
||||
let result = DbApi::with_conn(&conn).as_admin()
|
||||
.add_player("PlayerName".to_string(), 403.21)
|
||||
.unwrap();
|
||||
assert_eq!(result.executed, true);
|
||||
let players = DbApi::with_conn(&conn).fetch_players().unwrap();
|
||||
assert_eq!(players.len(), 2);
|
||||
let new_player = players.get(1).unwrap();
|
||||
assert_eq!(new_player.name, "PlayerName");
|
||||
assert_eq!((new_player.cp, new_player.sp, new_player.gp, new_player.pp), (1, 2, 3, 4));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -1,19 +1,32 @@
|
||||
use crate::models::item::Loot;
|
||||
use crate::schema::claims;
|
||||
use diesel::prelude::*;
|
||||
|
||||
/// A Claim is a request by a single player on an item from group chest.
|
||||
#[derive(Identifiable, Queryable, Associations, Serialize, Debug)]
|
||||
#[belongs_to(Loot)]
|
||||
pub struct Claim {
|
||||
id: i32,
|
||||
player_id: i32,
|
||||
loot_id: i32,
|
||||
resolve: i32,
|
||||
/// DB Identifier
|
||||
pub id: i32,
|
||||
/// ID that references the player making this claim
|
||||
pub player_id: i32,
|
||||
/// ID that references the loot claimed
|
||||
pub loot_id: i32,
|
||||
/// WIP: How bad the player wants this item
|
||||
pub resolve: i32,
|
||||
}
|
||||
|
||||
#[derive(Insertable, Debug)]
|
||||
#[table_name = "claims"]
|
||||
pub struct NewClaim {
|
||||
pub player_id: i32,
|
||||
pub loot_id: i32,
|
||||
pub(crate) struct NewClaim {
|
||||
player_id: i32,
|
||||
loot_id: i32,
|
||||
}
|
||||
|
||||
impl NewClaim {
|
||||
pub(crate) fn new(player_id: i32, loot_id: i32) -> Self {
|
||||
Self {
|
||||
player_id,
|
||||
loot_id
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
use crate::schema::{items, looted};
|
||||
use crate::DbConnection;
|
||||
use crate::schema::{looted};
|
||||
use diesel::dsl::{exists, Eq, Filter, Find, Select};
|
||||
use diesel::expression::exists::Exists;
|
||||
use diesel::prelude::*;
|
||||
@@ -60,7 +59,7 @@ type ItemDesc<'a> = (&'a str, i32);
|
||||
/// to the id of buying player otherwise.
|
||||
#[derive(Insertable)]
|
||||
#[table_name = "looted"]
|
||||
struct NewLoot<'a> {
|
||||
pub(crate) struct NewLoot<'a> {
|
||||
name: &'a str,
|
||||
base_price: i32,
|
||||
owner_id: i32,
|
||||
@@ -68,7 +67,7 @@ struct NewLoot<'a> {
|
||||
|
||||
impl<'a> NewLoot<'a> {
|
||||
/// A new loot going to the group (loot procedure)
|
||||
fn to_group(desc: ItemDesc<'a>) -> Self {
|
||||
pub(crate) fn to_group(desc: ItemDesc<'a>) -> Self {
|
||||
Self {
|
||||
name: desc.0,
|
||||
base_price: desc.1,
|
||||
@@ -77,7 +76,7 @@ impl<'a> NewLoot<'a> {
|
||||
}
|
||||
|
||||
/// A new loot going to a specific player (buy procedure)
|
||||
fn to_player(player: i32, desc: ItemDesc<'a>) -> Self {
|
||||
pub(crate) fn to_player(player: i32, desc: ItemDesc<'a>) -> Self {
|
||||
Self {
|
||||
name: desc.0,
|
||||
base_price: desc.1,
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
mod claim;
|
||||
mod item;
|
||||
mod player;
|
||||
pub(super) mod claim;
|
||||
pub(super) mod item;
|
||||
pub(super) mod player;
|
||||
|
||||
pub use claim::{Claim, NewClaim};
|
||||
pub use claim::Claim;
|
||||
pub use item::Item;
|
||||
pub(crate) use item::Loot;
|
||||
pub use player::{NewPlayer, Player, WealthUpdate};
|
||||
pub use player::{Player, Wealth};
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
use crate::schema::players;
|
||||
use crate::DbConnection;
|
||||
use diesel::prelude::*;
|
||||
|
||||
/// Representation of a player in database
|
||||
#[derive(Debug, Queryable, Serialize)]
|
||||
pub struct Player {
|
||||
id: i32,
|
||||
name: String,
|
||||
debt: i32,
|
||||
cp: i32,
|
||||
sp: i32,
|
||||
gp: i32,
|
||||
pp: i32,
|
||||
/// DB Identitier
|
||||
pub id: i32,
|
||||
/// Full name of the character
|
||||
pub name: String,
|
||||
/// Amount of gold coins owed to the group.
|
||||
/// Taking a looted items will increase the debt by it's sell value
|
||||
pub debt: i32,
|
||||
/// Count of copper pieces
|
||||
pub cp: i32,
|
||||
/// Count of silver pieces
|
||||
pub sp: i32,
|
||||
/// Count of gold pieces
|
||||
pub gp: i32,
|
||||
/// Count of platinum pieces
|
||||
pub pp: i32,
|
||||
}
|
||||
|
||||
/// Unpack a floating value in gold pieces to integer
|
||||
@@ -32,21 +38,28 @@ fn unpack_gold_value(gold: f32) -> (i32, i32, i32, i32) {
|
||||
(cp, sp, gp, pp)
|
||||
}
|
||||
|
||||
/// Represent an update on a player's wealth
|
||||
/// State of a player's wealth
|
||||
///
|
||||
/// The values held here are the amount of pieces to add or
|
||||
/// substract to player wealth.
|
||||
/// Values are held as individual pieces counts.
|
||||
/// Allows conversion from and to a floating amount of gold pieces.
|
||||
#[derive(Queryable, AsChangeset, Debug)]
|
||||
#[table_name = "players"]
|
||||
pub struct WealthUpdate {
|
||||
cp: i32,
|
||||
sp: i32,
|
||||
gp: i32,
|
||||
pp: i32,
|
||||
pub struct Wealth {
|
||||
pub cp: i32,
|
||||
pub sp: i32,
|
||||
pub gp: i32,
|
||||
pub pp: i32,
|
||||
}
|
||||
|
||||
impl WealthUpdate {
|
||||
impl Wealth {
|
||||
/// Unpack individual pieces counts from gold value
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use lootalot_db::models::Wealth;
|
||||
/// let wealth = Wealth::from_gp(403.21);
|
||||
/// assert_eq!(wealth.as_tuple(), (1, 2, 3, 4));
|
||||
/// ```
|
||||
pub fn from_gp(gp: f32) -> Self {
|
||||
let (cp, sp, gp, pp) = unpack_gold_value(gp);
|
||||
Self { cp, sp, gp, pp }
|
||||
@@ -55,8 +68,8 @@ impl WealthUpdate {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use lootalot_db::models::WealthUpdate;
|
||||
/// let wealth = WealthUpdate::from_gp(403.21);
|
||||
/// # use lootalot_db::models::Wealth;
|
||||
/// let wealth = Wealth{ pp: 4, gp: 3, sp: 2, cp: 1};
|
||||
/// assert_eq!(wealth.to_gp(), 403.21);
|
||||
/// ```
|
||||
pub fn to_gp(&self) -> f32 {
|
||||
@@ -73,7 +86,7 @@ impl WealthUpdate {
|
||||
/// Representation of a new player record
|
||||
#[derive(Insertable)]
|
||||
#[table_name = "players"]
|
||||
pub struct NewPlayer<'a> {
|
||||
pub(crate) struct NewPlayer<'a> {
|
||||
name: &'a str,
|
||||
cp: i32,
|
||||
sp: i32,
|
||||
@@ -82,22 +95,17 @@ pub struct NewPlayer<'a> {
|
||||
}
|
||||
|
||||
impl<'a> NewPlayer<'a> {
|
||||
pub fn create(name: &'a str, wealth: f32) -> Self {
|
||||
let wealth = WealthUpdate::from_gp(wealth);
|
||||
pub(crate) fn create(name: &'a str, wealth_in_gp: f32) -> Self {
|
||||
let (cp, sp, gp, pp) = Wealth::from_gp(wealth_in_gp).as_tuple();
|
||||
Self {
|
||||
name,
|
||||
cp: wealth.cp,
|
||||
sp: wealth.sp,
|
||||
gp: wealth.gp,
|
||||
pp: wealth.pp,
|
||||
cp, sp, gp, pp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests;
|
||||
|
||||
#[test]
|
||||
fn test_unpack_gold_values() {
|
||||
|
||||
@@ -63,54 +63,49 @@ pub(crate) fn serve() -> std::io::Result<()> {
|
||||
.allowed_methods(vec!["GET", "POST"])
|
||||
.max_age(3600),
|
||||
)
|
||||
.service(
|
||||
web::scope("/api")
|
||||
.route("/players", web::get().to_async(move |pool: AppPool| db_call(pool, move |api| api.fetch_players())))
|
||||
.route("/claims", web::get().to_async(move |pool: AppPool| db_call(pool, move |api| api.fetch_claims())))
|
||||
.route(
|
||||
"/api/players",
|
||||
web::get()
|
||||
.to_async(move |pool: AppPool| db_call(pool, move |api| api.fetch_players())),
|
||||
)
|
||||
.route(
|
||||
"/api/claims",
|
||||
web::get()
|
||||
.to_async(move |pool: AppPool| db_call(pool, move |api| api.fetch_claims())),
|
||||
)
|
||||
.route(
|
||||
"/api/{player_id}/update-wealth/{amount}",
|
||||
"/{player_id}/update-wealth/{amount}",
|
||||
web::get().to_async(move |pool: AppPool, data: web::Path<(i32, f32)>| {
|
||||
db_call(pool, move |api| api.as_player(data.0).update_wealth(data.1))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/{player_id}/loot",
|
||||
"/{player_id}/loot",
|
||||
web::get().to_async(move |pool: AppPool, player_id: web::Path<i32>| {
|
||||
db_call(pool, move |api| api.as_player(*player_id).loot())
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/{player_id}/claim/{item_id}",
|
||||
"/{player_id}/claim/{item_id}",
|
||||
web::get().to_async(move |pool: AppPool, data: web::Path<(i32, i32)>| {
|
||||
db_call(pool, move |api| api.as_player(data.0).claim(data.1))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/{player_id}/unclaim/{item_id}",
|
||||
"/{player_id}/unclaim/{item_id}",
|
||||
web::get().to_async(move |pool: AppPool, data: web::Path<(i32, i32)>| {
|
||||
db_call(pool, move |api| api.as_player(data.0).unclaim(data.1))
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/admin/resolve-claims",
|
||||
"/admin/resolve-claims",
|
||||
web::get().to_async(move |pool: AppPool| {
|
||||
db_call(pool, move |api| api.as_admin().resolve_claims())
|
||||
}),
|
||||
)
|
||||
.route(
|
||||
"/api/admin/add-player/{name}/{wealth}",
|
||||
"/admin/add-player/{name}/{wealth}",
|
||||
web::get().to_async(move |pool: AppPool, data: web::Path<(String, f32)>| {
|
||||
db_call(pool, move |api| {
|
||||
api.as_admin().add_player(data.0.clone(), data.1)
|
||||
})
|
||||
}),
|
||||
)
|
||||
)
|
||||
.service(fs::Files::new("/", www_root.clone()).index_file("index.html"))
|
||||
})
|
||||
.bind("127.0.0.1:8088")?
|
||||
|
||||
Reference in New Issue
Block a user