First pass of an API

+ Implemented register, find, and add_location
+ Bunch of changes to the DB and the Models to make this work
+ API models are defined in GeoffreyModels so things that call the API don't need to define their own types
+ Still needs a lot of work, and need to design the api a bit more
+ Clippy + fmt
main
Joey Hines 2021-10-03 14:03:32 -06:00
parent 9bf31f17f6
commit b92308da67
No known key found for this signature in database
GPG Key ID: 80F567B5C968F91B
29 changed files with 507 additions and 117 deletions

1
.gitignore vendored
View File

@ -3,3 +3,4 @@
/database /database
/test_database /test_database
config.toml config.toml
*.http

View File

@ -0,0 +1,48 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::helper::get_player_from_req;
use crate::Result;
use geoffrey_models::models::locations::Location;
use geoffrey_models::models::parameters::add_location_params::AddLocationParams;
use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
pub struct AddLocation {}
impl Command for AddLocation {
type Req = CommandRequest<AddLocationParams>;
type Resp = Location;
fn command_name() -> String {
"add_location".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED
}
fn run_command(ctx: Arc<Context>, req: Self::Req) -> Result<Self::Resp> {
if let Some(player) = get_player_from_req(&ctx.db, &req)? {
let args = &req.arguments;
let location = Location::new(
args.name.as_str(),
args.position,
player.id.unwrap(),
args.tunnel.clone(),
args.loc_type.clone(),
);
ctx.db
.insert(location)
.map_err(|err| GeoffreyAPIError::DatabaseError(err.to_string()))
} else {
Err(GeoffreyAPIError::PlayerNotFound)
}
}
}

View File

@ -0,0 +1,43 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use geoffrey_models::models::locations::Location;
use geoffrey_models::models::parameters::find_params::FindParams;
use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
pub struct FindCommand {}
impl Command for FindCommand {
type Req = CommandRequest<FindParams>;
type Resp = Vec<Location>;
fn command_name() -> String {
"find".to_string()
}
fn request_type() -> RequestType {
RequestType::GET
}
fn command_level() -> CommandLevel {
CommandLevel::ALL
}
fn run_command(ctx: Arc<Context>, req: Self::Req) -> Result<Self::Resp> {
let locations = ctx
.db
.filter(|_, loc: &Location| {
let name = loc.name.to_lowercase();
let query = req.arguments.query.to_lowercase();
name.contains(&query)
})
.map_err(|err| GeoffreyAPIError::DatabaseError(err.to_string()))?
.collect();
Ok(locations)
}
}

View File

@ -0,0 +1,64 @@
use crate::commands::add_location::AddLocation;
use crate::commands::find::FindCommand;
use crate::commands::register::Register;
use crate::context::Context;
use crate::Result;
use geoffrey_models::models::response::APIResponse;
use geoffrey_models::models::CommandLevel;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::sync::Arc;
use warp::filters::BoxedFilter;
use warp::Filter;
pub mod add_location;
pub mod find;
pub mod register;
#[derive(Debug, Clone, PartialEq)]
#[allow(clippy::upper_case_acronyms)]
pub enum RequestType {
POST,
GET,
}
pub trait Command {
type Req: Serialize + DeserializeOwned + Send + 'static;
type Resp: Serialize + DeserializeOwned + Send;
fn command_name() -> String;
fn request_type() -> RequestType;
fn command_level() -> CommandLevel;
fn run_command(ctx: Arc<Context>, req: Self::Req) -> Result<Self::Resp>;
}
#[allow(clippy::needless_return)]
pub fn create_command_filter<T: Command>(ctx: Arc<Context>) -> BoxedFilter<(impl warp::Reply,)> {
let filter = warp::path(T::command_name())
.and(warp::any().map(move || ctx.clone()))
.and(warp::body::json())
.map(|ctx: Arc<Context>, req: T::Req| {
let reply = T::run_command(ctx, req);
if let Ok(reply) = reply {
warp::reply::json(&APIResponse::Response::<T::Resp>(reply))
} else {
warp::reply::json(&APIResponse::<T::Resp>::Error(reply.err().unwrap()))
}
});
if T::request_type() == RequestType::POST {
return filter.and(warp::post()).boxed();
} else {
return filter.and(warp::get()).boxed();
}
}
pub fn command_filter(
ctx: Arc<Context>,
) -> impl Filter<Extract = impl warp::Reply, Error = warp::Rejection> + Clone {
warp::path("command").and(
create_command_filter::<FindCommand>(ctx.clone())
.or(create_command_filter::<AddLocation>(ctx.clone()))
.or(create_command_filter::<Register>(ctx)),
)
}

View File

@ -0,0 +1,35 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use geoffrey_models::models::parameters::register_params::RegisterParameters;
use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::CommandLevel;
use std::sync::Arc;
pub struct Register {}
impl Command for Register {
type Req = CommandRequest<RegisterParameters>;
type Resp = Player;
fn command_name() -> String {
"register".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
fn command_level() -> CommandLevel {
CommandLevel::ALL
}
fn run_command(ctx: Arc<Context>, req: Self::Req) -> crate::Result<Self::Resp> {
let player = Player::new(req.arguments.username.as_str(), req.arguments.user_id);
ctx.db
.insert(player)
.map_err(|err| GeoffreyAPIError::DatabaseError(err.to_string()))
}
}

View File

@ -0,0 +1,18 @@
use config::{Config, ConfigError, File};
use serde::Deserialize;
use std::path::{Path, PathBuf};
#[derive(Debug, Deserialize, Clone)]
pub struct GeoffreyAPIConfig {
pub db_path: PathBuf,
pub host: String,
}
impl GeoffreyAPIConfig {
pub fn new(config_path: &Path) -> Result<Self, ConfigError> {
let mut cfg = Config::new();
cfg.merge(File::from(config_path.to_path_buf()))?;
cfg.try_into()
}
}

View File

@ -0,0 +1,20 @@
use crate::config::GeoffreyAPIConfig;
use crate::Result;
use geoffrey_db::database::Database;
use std::sync::Arc;
pub struct Context {
pub db: Database,
pub cfg: GeoffreyAPIConfig,
}
impl Context {
pub fn new(cfg: GeoffreyAPIConfig) -> Result<Arc<Self>> {
let ctx = Self {
db: Database::new(cfg.db_path.as_path()).unwrap(),
cfg,
};
Ok(Arc::new(ctx))
}
}

View File

@ -0,0 +1,16 @@
use crate::Result;
use geoffrey_db::database::Database;
use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
pub fn get_player_from_req<T>(db: &Database, req: &CommandRequest<T>) -> Result<Option<Player>> {
if let Some(user_id) = &req.user {
Ok(db
.filter(|_, player: &Player| player.has_user_id(user_id))
.map_err(|err| GeoffreyAPIError::DatabaseError(err.to_string()))?
.next())
} else {
Ok(None)
}
}

View File

@ -1,16 +1,18 @@
mod commands; mod commands;
mod error;
mod context;
mod config; mod config;
mod responses; mod context;
mod helper;
use structopt::StructOpt;
use std::path::PathBuf;
use crate::config::GeoffreyAPIConfig;
use std::net::SocketAddr;
use std::str::FromStr;
use crate::context::Context;
use crate::commands::command_filter; use crate::commands::command_filter;
use crate::config::GeoffreyAPIConfig;
use crate::context::Context;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use std::net::SocketAddr;
use std::path::PathBuf;
use std::str::FromStr;
use structopt::StructOpt;
pub type Result<T> = std::result::Result<T, GeoffreyAPIError>;
#[derive(Debug, StructOpt, Clone)] #[derive(Debug, StructOpt, Clone)]
#[structopt(name = "GeoffreyAPI", about = "Geoffrey Central API")] #[structopt(name = "GeoffreyAPI", about = "Geoffrey Central API")]
@ -23,7 +25,7 @@ struct Args {
async fn main() { async fn main() {
let args: Args = Args::from_args(); let args: Args = Args::from_args();
let cfg= GeoffreyAPIConfig::new(args.config.as_path()).unwrap(); let cfg = GeoffreyAPIConfig::new(args.config.as_path()).unwrap();
let ctx = Context::new(cfg).unwrap(); let ctx = Context::new(cfg).unwrap();
@ -32,4 +34,4 @@ async fn main() {
warp::serve(api) warp::serve(api)
.run(SocketAddr::from_str(ctx.cfg.host.as_str()).unwrap()) .run(SocketAddr::from_str(ctx.cfg.host.as_str()).unwrap())
.await; .await;
} }

View File

@ -1,7 +1,7 @@
use crate::error::{Result, GeoffreyDBError}; use crate::error::{GeoffreyDBError, Result};
use std::path::Path;
use geoffrey_models::GeoffreyDatabaseModel; use geoffrey_models::GeoffreyDatabaseModel;
use std::convert::TryInto; use std::convert::TryInto;
use std::path::Path;
pub struct Database { pub struct Database {
db: sled::Db, db: sled::Db,
@ -11,16 +11,20 @@ impl Database {
pub fn new(db_path: &Path) -> Result<Self> { pub fn new(db_path: &Path) -> Result<Self> {
let db = sled::open(db_path)?; let db = sled::open(db_path)?;
Ok(Self { Ok(Self { db })
db,
})
} }
fn get_tree<T>(&self) -> Result<sled::Tree> where T: GeoffreyDatabaseModel { fn get_tree<T>(&self) -> Result<sled::Tree>
where
T: GeoffreyDatabaseModel,
{
Ok(self.db.open_tree::<String>(T::tree())?) Ok(self.db.open_tree::<String>(T::tree())?)
} }
pub fn insert<T>(&self, mut model: T) -> Result<T> where T: GeoffreyDatabaseModel { pub fn insert<T>(&self, mut model: T) -> Result<T>
where
T: GeoffreyDatabaseModel,
{
let id = match model.id() { let id = match model.id() {
Some(id) => id, Some(id) => id,
None => { None => {
@ -30,12 +34,10 @@ impl Database {
} }
}; };
let match_count = self.filter(|_, o: &T| { let match_count = self.filter(|_, o: &T| !o.check_unique(&model))?.count();
!o.check_unique(&model)
})?.count();
if match_count > 0 { if match_count > 0 {
return Err(GeoffreyDBError::NotUnique) return Err(GeoffreyDBError::NotUnique);
} }
let tree = self.get_tree::<T>()?; let tree = self.get_tree::<T>()?;
@ -46,25 +48,34 @@ impl Database {
Ok(model) Ok(model)
} }
pub fn get<T>(&self, id: u64) -> Result<Option<T>>
pub fn get<T>(&self, id: u64) -> Result<Option<T>> where T: GeoffreyDatabaseModel { where
T: GeoffreyDatabaseModel,
{
let tree = self.get_tree::<T>()?; let tree = self.get_tree::<T>()?;
let id_bytes = id.to_be_bytes(); let id_bytes = id.to_be_bytes();
if let Some(bytes) = tree.get(id_bytes)? { if let Some(bytes) = tree.get(id_bytes)? {
Ok(Some(T::try_from_bytes(&bytes)?)) Ok(Some(T::try_from_bytes(&bytes)?))
} } else {
else {
Ok(None) Ok(None)
} }
} }
pub fn clear_tree<T>(&self) -> Result<()> where T: GeoffreyDatabaseModel { pub fn clear_tree<T>(&self) -> Result<()>
where
T: GeoffreyDatabaseModel,
{
Ok(self.db.open_tree(T::tree())?.clear()?) Ok(self.db.open_tree(T::tree())?.clear()?)
} }
pub fn filter<'a, T>(&self, f: impl Fn(u64, &T) -> bool + 'a) -> Result<impl Iterator<Item=T> + 'a> where T: GeoffreyDatabaseModel { pub fn filter<'a, T>(
&self,
f: impl Fn(u64, &T) -> bool + 'a,
) -> Result<impl Iterator<Item = T> + 'a>
where
T: GeoffreyDatabaseModel,
{
let tree = self.db.open_tree(T::tree())?; let tree = self.db.open_tree(T::tree())?;
Ok(tree.iter().filter_map(move |e| { Ok(tree.iter().filter_map(move |e| {
@ -74,31 +85,32 @@ impl Database {
if f(id, &data) { if f(id, &data) {
Some(data) Some(data)
} } else {
else {
None None
} }
} } else {
else {
None None
} }
})) }))
} }
pub fn tree_iter<T>(&self) -> Result<sled::Iter> where T: GeoffreyDatabaseModel { pub fn tree_iter<T>(&self) -> Result<sled::Iter>
where
T: GeoffreyDatabaseModel,
{
Ok(self.db.open_tree(T::tree()).map(|tree| tree.iter())?) Ok(self.db.open_tree(T::tree()).map(|tree| tree.iter())?)
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::database::{Database}; use crate::database::Database;
use std::path::Path;
use geoffrey_models::models::player::{Player, UserID};
use lazy_static::lazy_static;
use geoffrey_models::GeoffreyDatabaseModel;
use geoffrey_models::models::locations::{Location, LocationType}; use geoffrey_models::models::locations::{Location, LocationType};
use geoffrey_models::models::{Position, Dimension}; use geoffrey_models::models::player::{Player, UserID};
use geoffrey_models::models::{Dimension, Position};
use geoffrey_models::GeoffreyDatabaseModel;
use lazy_static::lazy_static;
use std::path::Path;
use std::time::Instant; use std::time::Instant;
lazy_static! { lazy_static! {
@ -120,7 +132,6 @@ mod tests {
assert!(p2.id().is_some()); assert!(p2.id().is_some());
assert_eq!(player.name, p2.name); assert_eq!(player.name, p2.name);
cleanup(); cleanup();
} }
@ -152,13 +163,22 @@ mod tests {
fn test_filter() { fn test_filter() {
let player = Player::new("CoolZero123", UserID::DiscordUUID(0u64)); let player = Player::new("CoolZero123", UserID::DiscordUUID(0u64));
let player = DB.insert::<Player>(player.clone()).unwrap(); let player = DB.insert::<Player>(player.clone()).unwrap();
let loc = Location::new("Test Shop", Position::new(0, 0, Dimension::Overworld), player.id.unwrap(), None, LocationType::Shop(0)); let loc = Location::new(
"Test Shop",
Position::new(0, 0, Dimension::Overworld),
player.id.unwrap(),
None,
LocationType::Shop(0),
);
let loc = DB.insert::<Location>(loc.clone()).unwrap(); let loc = DB.insert::<Location>(loc.clone()).unwrap();
let count = DB.filter(|id: u64, l: &Location| { let count = DB
assert_eq!(id, l.id().unwrap()); .filter(|id: u64, l: &Location| {
loc.id().unwrap() == id assert_eq!(id, l.id().unwrap());
}).unwrap().count(); loc.id().unwrap() == id
})
.unwrap()
.count();
assert_eq!(count, 1); assert_eq!(count, 1);
DB.db.flush().unwrap(); DB.db.flush().unwrap();
@ -178,9 +198,12 @@ mod tests {
DB.db.flush().unwrap(); DB.db.flush().unwrap();
let sec_elapsed = timer.elapsed().as_secs_f32(); let sec_elapsed = timer.elapsed().as_secs_f32();
println!("Completed in {}s. {} inserts per second", sec_elapsed, insert_count as f32/sec_elapsed); println!(
"Completed in {}s. {} inserts per second",
sec_elapsed,
insert_count as f32 / sec_elapsed
);
cleanup() cleanup()
} }
} }

View File

@ -14,7 +14,7 @@ impl std::fmt::Display for GeoffreyDBError {
match self { match self {
GeoffreyDBError::SledError(e) => write!(f, "Sled Error: {}", e), GeoffreyDBError::SledError(e) => write!(f, "Sled Error: {}", e),
GeoffreyDBError::SerdeJsonError(e) => write!(f, "Serde JSON Error: {}", e), GeoffreyDBError::SerdeJsonError(e) => write!(f, "Serde JSON Error: {}", e),
GeoffreyDBError::NotUnique => write!(f, "Entry is not unique.") GeoffreyDBError::NotUnique => write!(f, "Entry is not unique."),
} }
} }
} }

View File

@ -1,4 +1,4 @@
#![allow(dead_code)] #![allow(dead_code)]
pub mod database; pub mod database;
pub mod error; pub mod error;

View File

@ -1,6 +1,6 @@
#![allow(dead_code)] #![allow(dead_code)]
use serde::Serialize;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
use serde::Serialize;
pub mod models; pub mod models;
@ -31,4 +31,3 @@ pub trait GeoffreyDatabaseModel: Serialize + DeserializeOwned {
serde_json::from_slice(b) serde_json::from_slice(b)
} }
} }

View File

@ -9,7 +9,7 @@ pub struct Item {
impl Item { impl Item {
pub fn new(name: &str) -> Self { pub fn new(name: &str) -> Self {
Self { Self {
name: name.to_string() name: name.to_string(),
} }
} }
} }
@ -19,17 +19,18 @@ pub struct ItemListing {
pub item: Item, pub item: Item,
pub price: u32, pub price: u32,
pub count_per_price: u32, pub count_per_price: u32,
pub restocked_time: DateTime<Utc> pub restocked_time: DateTime<Utc>,
} }
impl ItemListing { impl ItemListing {
fn new(item: &str, price: u32, count_per_price: u32, restocked_time: DateTime<Utc>) -> Self { fn new(item: &str, price: u32, count_per_price: u32, restocked_time: DateTime<Utc>) -> Self {
Self { Self {
item: Item {name: item.to_string()}, item: Item {
name: item.to_string(),
},
price, price,
count_per_price, count_per_price,
restocked_time, restocked_time,
} }
} }
} }

View File

@ -1,12 +1,12 @@
use serde::{Serialize, Deserialize};
use crate::models::item::Item; use crate::models::item::Item;
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct FarmData { pub struct FarmData {
id: Option<u64>, id: Option<u64>,
pub items_produced: HashSet<Item> pub items_produced: HashSet<Item>,
} }
impl GeoffreyDatabaseModel for FarmData { impl GeoffreyDatabaseModel for FarmData {

View File

@ -1,11 +1,11 @@
use serde::{Serialize, Deserialize};
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Market { pub struct Market {
pub id: Option<u64>, pub id: Option<u64>,
pub shops: HashSet<u64> pub shops: HashSet<u64>,
} }
impl GeoffreyDatabaseModel for Market { impl GeoffreyDatabaseModel for Market {
@ -21,4 +21,3 @@ impl GeoffreyDatabaseModel for Market {
"market".to_string() "market".to_string()
} }
} }

View File

@ -1,15 +1,14 @@
use serde::{Serialize, Deserialize}; use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
use crate::GeoffreyDatabaseModel;
use crate::models::{Position, Tunnel}; use crate::models::{Position, Tunnel};
use crate::GeoffreyDatabaseModel;
pub mod farm; pub mod farm;
pub mod market; pub mod market;
pub mod shop; pub mod shop;
pub mod town; pub mod town;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub enum LocationType { pub enum LocationType {
Base, Base,
@ -27,11 +26,17 @@ pub struct Location {
pub position: Position, pub position: Position,
owners: HashSet<u64>, owners: HashSet<u64>,
pub tunnel: Option<Tunnel>, pub tunnel: Option<Tunnel>,
loc_type: LocationType loc_type: LocationType,
} }
impl Location { impl Location {
pub fn new (name: &str, position: Position, owner: u64, tunnel: Option<Tunnel>, loc_type: LocationType) -> Self { pub fn new(
name: &str,
position: Position,
owner: u64,
tunnel: Option<Tunnel>,
loc_type: LocationType,
) -> Self {
let mut owners = HashSet::new(); let mut owners = HashSet::new();
owners.insert(owner); owners.insert(owner);
@ -41,19 +46,19 @@ impl Location {
position, position,
owners, owners,
tunnel, tunnel,
loc_type loc_type,
} }
} }
fn owners(&self) -> Vec<u64> { pub fn owners(&self) -> Vec<u64> {
self.owners.iter().cloned().collect() self.owners.iter().cloned().collect()
} }
fn add_owner(&mut self, owner: u64) { pub fn add_owner(&mut self, owner: u64) {
self.owners.insert(owner); self.owners.insert(owner);
} }
fn remove_owner(&mut self, owner: u64) { pub fn remove_owner(&mut self, owner: u64) {
self.owners.remove(&owner); self.owners.remove(&owner);
} }
} }
@ -78,19 +83,43 @@ impl GeoffreyDatabaseModel for Location {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::GeoffreyDatabaseModel;
use crate::models::locations::{Location, LocationType}; use crate::models::locations::{Location, LocationType};
use crate::models::{Position, Dimension}; use crate::models::{Dimension, Position};
use crate::GeoffreyDatabaseModel;
#[test] #[test]
fn test_location_check_unique() { fn test_location_check_unique() {
let l1 = Location::new("Test", Position::new(0, 0, Dimension::Overworld), 0u64, None, LocationType::Base); let l1 = Location::new(
let l2 = Location::new("NotTest", Position::new(0, 0, Dimension::Overworld), 0u64, None, LocationType::Base); "Test",
Position::new(0, 0, Dimension::Overworld),
0u64,
None,
LocationType::Base,
);
let l2 = Location::new(
"NotTest",
Position::new(0, 0, Dimension::Overworld),
0u64,
None,
LocationType::Base,
);
assert!(l1.check_unique(&l2)); assert!(l1.check_unique(&l2));
let l1 = Location::new("Test", Position::new(0, 0, Dimension::Overworld), 0u64, None, LocationType::Base); let l1 = Location::new(
let l2 = Location::new("teSt", Position::new(0, 0, Dimension::Overworld), 0u64, None, LocationType::Base); "Test",
Position::new(0, 0, Dimension::Overworld),
0u64,
None,
LocationType::Base,
);
let l2 = Location::new(
"teSt",
Position::new(0, 0, Dimension::Overworld),
0u64,
None,
LocationType::Base,
);
assert!(!l1.check_unique(&l2)); assert!(!l1.check_unique(&l2));
} }

View File

@ -2,8 +2,8 @@ use std::collections::HashSet;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use crate::GeoffreyDatabaseModel;
use crate::models::item::ItemListing; use crate::models::item::ItemListing;
use crate::GeoffreyDatabaseModel;
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Shop { pub struct Shop {
@ -24,4 +24,3 @@ impl GeoffreyDatabaseModel for Shop {
"shop".to_string() "shop".to_string()
} }
} }

View File

@ -1,11 +1,11 @@
use serde::{Deserialize, Serialize};
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize};
use std::collections::HashSet; use std::collections::HashSet;
#[derive(Serialize, Deserialize, Debug, Clone, Default)] #[derive(Serialize, Deserialize, Debug, Clone, Default)]
pub struct Town { pub struct Town {
id: Option<u64>, id: Option<u64>,
pub residents: HashSet<u64> pub residents: HashSet<u64>,
} }
impl GeoffreyDatabaseModel for Town { impl GeoffreyDatabaseModel for Town {

View File

@ -1,5 +1,5 @@
use serde::{Deserialize, Serialize};
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
struct Meta { struct Meta {
@ -20,4 +20,4 @@ impl GeoffreyDatabaseModel for Meta {
fn tree() -> String { fn tree() -> String {
"meta".to_string() "meta".to_string()
} }
} }

View File

@ -1,16 +1,18 @@
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
pub mod player;
pub mod item; pub mod item;
pub mod token;
pub mod meta;
pub mod locations; pub mod locations;
pub mod meta;
pub mod parameters;
pub mod player;
pub mod response;
pub mod token;
#[derive(Serialize, Deserialize, Debug, Copy, Clone)] #[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub enum Dimension { pub enum Dimension {
Overworld, Overworld,
Nether, Nether,
TheEnd TheEnd,
} }
impl Default for Dimension { impl Default for Dimension {
@ -36,17 +38,19 @@ pub struct Position {
impl Position { impl Position {
pub fn new(x: i32, y: i32, dimension: Dimension) -> Self { pub fn new(x: i32, y: i32, dimension: Dimension) -> Self {
Self { Self { x, y, dimension }
x,
y,
dimension
}
} }
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Tunnel { pub struct Tunnel {
direction: Direction, direction: Direction,
number: i32 number: i32,
} }
#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Ord, PartialOrd, Eq, Hash)]
pub enum CommandLevel {
ALL = 0,
REGISTERED = 1,
ADMIN = 2,
}

View File

@ -0,0 +1,11 @@
use crate::models::locations::LocationType;
use crate::models::{Position, Tunnel};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AddLocationParams {
pub name: String,
pub position: Position,
pub loc_type: LocationType,
pub tunnel: Option<Tunnel>,
}

View File

@ -0,0 +1,6 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FindParams {
pub query: String,
}

View File

@ -0,0 +1,38 @@
pub mod add_location_params;
pub mod find_params;
pub mod register_params;
use crate::models::player::{Player, UserID};
use crate::models::token::{Permissions, Token};
use crate::models::CommandLevel;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct CommandRequest<T> {
pub user: Option<UserID>,
pub arguments: T,
pub token: u64,
}
impl<T> CommandRequest<T> {
fn has_user_id(&self) -> bool {
self.user.is_some()
}
fn check_permission(
&self,
player: &Player,
command_level: &CommandLevel,
token: &Token,
) -> bool {
if player.auth_level >= *command_level {
if *command_level == CommandLevel::ADMIN {
token.check_permission(Permissions::Admin)
} else {
true
}
} else {
false
}
}
}

View File

@ -0,0 +1,8 @@
use crate::models::player::UserID;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RegisterParameters {
pub username: String,
pub user_id: UserID,
}

View File

@ -1,17 +1,19 @@
use serde::{Deserialize, Serialize}; use crate::models::CommandLevel;
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
use serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
pub enum UserID { pub enum UserID {
DiscordUUID(u64), DiscordUUID { discord_uuid: u64 },
MinecraftUUID(String), MinecraftUUID { mc_uuid: String },
} }
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)] #[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
pub struct Player { pub struct Player {
pub id: Option<u64>, pub id: Option<u64>,
pub name: String, pub name: String,
pub user_ids: Vec<UserID> pub user_ids: Vec<UserID>,
pub auth_level: CommandLevel,
} }
impl Player { impl Player {
@ -19,9 +21,14 @@ impl Player {
Self { Self {
id: None, id: None,
name: name.to_string(), name: name.to_string(),
user_ids: vec![user_id] user_ids: vec![user_id],
auth_level: CommandLevel::REGISTERED,
} }
} }
pub fn has_user_id(&self, user_id: &UserID) -> bool {
self.user_ids.iter().any(|id| id == user_id)
}
} }
impl GeoffreyDatabaseModel for Player { impl GeoffreyDatabaseModel for Player {
@ -66,4 +73,4 @@ mod tests {
assert!(!p1.check_unique(&p2)); assert!(!p1.check_unique(&p2));
} }
} }

View File

@ -0,0 +1,9 @@
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize)]
pub enum GeoffreyAPIError {
PlayerNotFound,
LocationNotFound,
PermissionInsufficient,
DatabaseError(String),
}

View File

@ -0,0 +1,10 @@
use crate::models::response::api_error::GeoffreyAPIError;
use serde::{Deserialize, Serialize};
pub mod api_error;
#[derive(Debug, Serialize, Deserialize)]
pub enum APIResponse<T> {
Response(T),
Error(GeoffreyAPIError),
}

View File

@ -1,6 +1,6 @@
use serde::{Deserialize, Serialize};
use chrono::{DateTime, Utc};
use crate::GeoffreyDatabaseModel; use crate::GeoffreyDatabaseModel;
use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub enum Permissions { pub enum Permissions {
ModelGet = 0, ModelGet = 0,
@ -11,29 +11,20 @@ pub enum Permissions {
} }
#[derive(Serialize, Deserialize, Debug, Clone)] #[derive(Serialize, Deserialize, Debug, Clone)]
struct Token { pub struct Token {
pub id: Option<u64>, pub id: Option<u64>,
permission: u64, permission: u64,
pub created: DateTime<Utc>, pub created: DateTime<Utc>,
pub modified: DateTime<Utc> pub modified: DateTime<Utc>,
} }
impl Token { impl Token {
pub fn new() -> Self {
Self {
id: None,
permission: 0,
created: Utc::now(),
modified: Utc::now()
}
}
pub fn set_permission(&mut self, permission: Permissions) { pub fn set_permission(&mut self, permission: Permissions) {
self.permission = self.permission | (1u64 << permission as u32); self.permission |= 1u64 << permission as u32;
} }
pub fn clear_permission(&mut self, permission: Permissions) { pub fn clear_permission(&mut self, permission: Permissions) {
self.permission = self.permission & !(1u64 << permission as u32); self.permission &= !(1u64 << permission as u32);
} }
pub fn check_permission(&self, permission: Permissions) -> bool { pub fn check_permission(&self, permission: Permissions) -> bool {
@ -45,6 +36,17 @@ impl Token {
} }
} }
impl Default for Token {
fn default() -> Self {
Self {
id: None,
permission: 0,
created: Utc::now(),
modified: Utc::now(),
}
}
}
impl GeoffreyDatabaseModel for Token { impl GeoffreyDatabaseModel for Token {
fn id(&self) -> Option<u64> { fn id(&self) -> Option<u64> {
self.id self.id
@ -67,18 +69,16 @@ impl PartialEq for Token {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use crate::models::token::{Token, Permissions}; use crate::models::token::{Permissions, Token};
#[test] #[test]
fn test_token() { fn test_token() {
let mut token = Token::new(); let mut token = Token::default();
token.set_permission(Permissions::ModelGet); token.set_permission(Permissions::ModelGet);
assert_eq!(token.permission, 0x1u64); assert_eq!(token.permission, 0x1u64);
token.set_permission(Permissions::ModelPost); token.set_permission(Permissions::ModelPost);
assert_eq!(token.permission, 0x3u64); assert_eq!(token.permission, 0x3u64);
} }
} }