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
/test_database
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 error;
mod context;
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::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)]
#[structopt(name = "GeoffreyAPI", about = "Geoffrey Central API")]
@ -23,7 +25,7 @@ struct Args {
async fn main() {
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();
@ -32,4 +34,4 @@ async fn main() {
warp::serve(api)
.run(SocketAddr::from_str(ctx.cfg.host.as_str()).unwrap())
.await;
}
}

View File

@ -1,7 +1,7 @@
use crate::error::{Result, GeoffreyDBError};
use std::path::Path;
use crate::error::{GeoffreyDBError, Result};
use geoffrey_models::GeoffreyDatabaseModel;
use std::convert::TryInto;
use std::path::Path;
pub struct Database {
db: sled::Db,
@ -11,16 +11,20 @@ impl Database {
pub fn new(db_path: &Path) -> Result<Self> {
let db = sled::open(db_path)?;
Ok(Self {
db,
})
Ok(Self { 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())?)
}
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() {
Some(id) => id,
None => {
@ -30,12 +34,10 @@ impl Database {
}
};
let match_count = self.filter(|_, o: &T| {
!o.check_unique(&model)
})?.count();
let match_count = self.filter(|_, o: &T| !o.check_unique(&model))?.count();
if match_count > 0 {
return Err(GeoffreyDBError::NotUnique)
return Err(GeoffreyDBError::NotUnique);
}
let tree = self.get_tree::<T>()?;
@ -46,25 +48,34 @@ impl Database {
Ok(model)
}
pub fn get<T>(&self, id: u64) -> Result<Option<T>> where T: GeoffreyDatabaseModel {
pub fn get<T>(&self, id: u64) -> Result<Option<T>>
where
T: GeoffreyDatabaseModel,
{
let tree = self.get_tree::<T>()?;
let id_bytes = id.to_be_bytes();
if let Some(bytes) = tree.get(id_bytes)? {
Ok(Some(T::try_from_bytes(&bytes)?))
}
else {
} else {
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()?)
}
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())?;
Ok(tree.iter().filter_map(move |e| {
@ -74,31 +85,32 @@ impl Database {
if f(id, &data) {
Some(data)
}
else {
} else {
None
}
}
else {
} else {
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())?)
}
}
#[cfg(test)]
mod tests {
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 crate::database::Database;
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;
lazy_static! {
@ -120,7 +132,6 @@ mod tests {
assert!(p2.id().is_some());
assert_eq!(player.name, p2.name);
cleanup();
}
@ -152,13 +163,22 @@ mod tests {
fn test_filter() {
let player = Player::new("CoolZero123", UserID::DiscordUUID(0u64));
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 count = DB.filter(|id: u64, l: &Location| {
assert_eq!(id, l.id().unwrap());
loc.id().unwrap() == id
}).unwrap().count();
let count = DB
.filter(|id: u64, l: &Location| {
assert_eq!(id, l.id().unwrap());
loc.id().unwrap() == id
})
.unwrap()
.count();
assert_eq!(count, 1);
DB.db.flush().unwrap();
@ -178,9 +198,12 @@ mod tests {
DB.db.flush().unwrap();
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()
}
}

View File

@ -14,7 +14,7 @@ impl std::fmt::Display for GeoffreyDBError {
match self {
GeoffreyDBError::SledError(e) => write!(f, "Sled 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)]
pub mod database;
pub mod error;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,18 @@
use serde::{Deserialize, Serialize};
pub mod player;
pub mod item;
pub mod token;
pub mod meta;
pub mod locations;
pub mod meta;
pub mod parameters;
pub mod player;
pub mod response;
pub mod token;
#[derive(Serialize, Deserialize, Debug, Copy, Clone)]
pub enum Dimension {
Overworld,
Nether,
TheEnd
TheEnd,
}
impl Default for Dimension {
@ -36,17 +38,19 @@ pub struct Position {
impl Position {
pub fn new(x: i32, y: i32, dimension: Dimension) -> Self {
Self {
x,
y,
dimension
}
Self { x, y, dimension }
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct Tunnel {
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 serde::{Deserialize, Serialize};
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
pub enum UserID {
DiscordUUID(u64),
MinecraftUUID(String),
DiscordUUID { discord_uuid: u64 },
MinecraftUUID { mc_uuid: String },
}
#[derive(Serialize, Deserialize, Debug, Clone, Hash, PartialEq, Eq)]
pub struct Player {
pub id: Option<u64>,
pub name: String,
pub user_ids: Vec<UserID>
pub user_ids: Vec<UserID>,
pub auth_level: CommandLevel,
}
impl Player {
@ -19,9 +21,14 @@ impl Player {
Self {
id: None,
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 {
@ -66,4 +73,4 @@ mod tests {
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 chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
pub enum Permissions {
ModelGet = 0,
@ -11,29 +11,20 @@ pub enum Permissions {
}
#[derive(Serialize, Deserialize, Debug, Clone)]
struct Token {
pub struct Token {
pub id: Option<u64>,
permission: u64,
pub created: DateTime<Utc>,
pub modified: DateTime<Utc>
pub modified: DateTime<Utc>,
}
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) {
self.permission = self.permission | (1u64 << permission as u32);
self.permission |= 1u64 << permission as u32;
}
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 {
@ -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 {
fn id(&self) -> Option<u64> {
self.id
@ -67,18 +69,16 @@ impl PartialEq for Token {
#[cfg(test)]
mod tests {
use crate::models::token::{Token, Permissions};
use crate::models::token::{Permissions, Token};
#[test]
fn test_token() {
let mut token = Token::new();
let mut token = Token::default();
token.set_permission(Permissions::ModelGet);
assert_eq!(token.permission, 0x1u64);
token.set_permission(Permissions::ModelPost);
assert_eq!(token.permission, 0x3u64);
}
}