Compare commits

...

2 Commits

Author SHA1 Message Date
Joey Hines 3711e7011c
Refactored parameter handling + added delete command
+ CommandRequest is now a struct that contains a generic parameter for the type
+ This streamlines adding new command parameters and reduces duplicate code
+ Added delete commands
2021-12-17 20:03:15 -07:00
Joey Hines 4c1d1cd6d0
Added a remove method to GeoffreyDB
+ clippy + fmt
2021-12-17 17:24:51 -07:00
23 changed files with 186 additions and 236 deletions

View File

@ -36,7 +36,7 @@ impl Command for AddItem {
}
}
fn run_command(ctx: Arc<Context>, req: Self::Req, user: Option<Player>) -> Result<Self::Resp> {
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
let user = user.unwrap();
let shops: Vec<LocationDb> = ctx

View File

@ -26,7 +26,7 @@ impl Command for AddLocation {
CommandLevel::REGISTERED
}
fn run_command(ctx: Arc<Context>, req: Self::Req, user: Option<Player>) -> Result<Self::Resp> {
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
let user = user.unwrap();
let location = LocationDb::new(

View File

@ -28,7 +28,7 @@ impl Command for AddToken {
CommandLevel::ADMIN
}
fn run_command(ctx: Arc<Context>, req: Self::Req, _: Option<Player>) -> Result<Self::Resp> {
fn run_command(ctx: Arc<Context>, req: &Self::Req, _: Option<Player>) -> Result<Self::Resp> {
let mut token = Token::default();
let secret: String = rand::thread_rng()
@ -39,8 +39,8 @@ impl Command for AddToken {
token.secret = secret;
for permission in req.permissions {
token.set_permission(permission);
for permission in &req.permissions {
token.set_permission(*permission);
}
ctx.db.insert(token).map_err(GeoffreyAPIError::from)

View File

@ -0,0 +1,48 @@
use crate::commands::{Command, RequestType};
use crate::context::Context;
use crate::Result;
use geoffrey_db::helper::load_location;
use geoffrey_models::models::locations::{Location, LocationDb};
use geoffrey_models::models::parameters::delete_params::DeleteParams;
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 Delete {}
impl Command for Delete {
type Req = DeleteParams;
type Resp = Location;
fn command_name() -> String {
"delete".to_string()
}
fn request_type() -> RequestType {
RequestType::POST
}
fn command_level() -> CommandLevel {
CommandLevel::REGISTERED
}
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
let user = user.unwrap();
let filter = regex::Regex::new(format!(r"(?i)^{}$", req.location).as_str()).unwrap();
let location: LocationDb = ctx
.db
.filter(|_, loc: &LocationDb| {
filter.is_match(&loc.name) && loc.owners().contains(&user.id.unwrap())
})?
.next()
.ok_or(GeoffreyAPIError::EntryNotFound)?;
let location = load_location(&ctx.db, &location).map_err(GeoffreyAPIError::from)?;
ctx.db.remove::<LocationDb>(location.id)?;
Ok(location)
}
}

View File

@ -27,7 +27,7 @@ impl Command for FindCommand {
CommandLevel::ALL
}
fn run_command(ctx: Arc<Context>, req: Self::Req, _: Option<Player>) -> Result<Self::Resp> {
fn run_command(ctx: Arc<Context>, req: &Self::Req, _: Option<Player>) -> Result<Self::Resp> {
let query = req.query.to_lowercase();
let players: Vec<u64> = ctx
.db

View File

@ -1,5 +1,6 @@
use crate::commands::add_item::AddItem;
use crate::commands::add_location::AddLocation;
use crate::commands::delete::Delete;
use crate::commands::find::FindCommand;
use crate::commands::register::Register;
use crate::commands::selling::Selling;
@ -7,7 +8,7 @@ use crate::commands::set_portal::SetPortal;
use crate::context::Context;
use crate::helper::{get_player_from_req, get_token_from_req};
use crate::Result;
use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::response::APIResponse;
@ -23,6 +24,7 @@ use warp::Filter;
pub mod add_item;
pub mod add_location;
pub mod add_token;
pub mod delete;
pub mod find;
pub mod register;
pub mod selling;
@ -36,13 +38,13 @@ pub enum RequestType {
}
pub trait Command {
type Req: CommandRequest;
type Resp: Serialize + DeserializeOwned + Send;
type Req: GeoffreyParam + 'static;
type Resp: Serialize + DeserializeOwned + Send + Debug;
fn command_name() -> String;
fn request_type() -> RequestType;
fn command_level() -> CommandLevel;
fn run_command(ctx: Arc<Context>, req: Self::Req, user: Option<Player>) -> Result<Self::Resp>;
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp>;
fn validate_parameters(_: &Self::Req) -> Result<()> {
Ok(())
@ -75,17 +77,20 @@ pub trait Command {
}
}
pub fn handle_command<T: Command>(ctx: Arc<Context>, req: T::Req) -> Result<T::Resp> {
pub fn handle_command<T: Command>(
ctx: Arc<Context>,
req: CommandRequest<T::Req>,
) -> Result<T::Resp> {
log::info!("Running command {}", T::command_name());
log::debug!("Request: {:?}", req);
let user = get_player_from_req(&ctx.db, &req)?;
let token = get_token_from_req(&ctx.db, &req)?;
let user = get_player_from_req::<T::Req>(&ctx.db, &req)?;
let token = get_token_from_req::<T::Req>(&ctx.db, &req)?;
match T::user_is_authorized(&token, &user) {
Ok(_) => {
T::validate_parameters(&req)?;
T::run_command(ctx, req, user)
T::validate_parameters(&req.params)?;
T::run_command(ctx, &req.params, user)
}
Err(e) => Err(e),
}
@ -96,7 +101,7 @@ pub fn create_command_filter<T: Command>(ctx: Arc<Context>) -> BoxedFilter<(impl
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| {
.map(|ctx: Arc<Context>, req: CommandRequest<T::Req>| {
let reply = handle_command::<T>(ctx, req);
if let Ok(reply) = reply {
log::debug!("Successfully processed command");
@ -125,6 +130,7 @@ pub fn command_filter(
.or(create_command_filter::<Register>(ctx.clone()))
.or(create_command_filter::<Selling>(ctx.clone()))
.or(create_command_filter::<AddItem>(ctx.clone()))
.or(create_command_filter::<Delete>(ctx.clone()))
.or(create_command_filter::<SetPortal>(ctx)),
)
}

View File

@ -26,10 +26,10 @@ impl Command for Register {
fn run_command(
ctx: Arc<Context>,
req: Self::Req,
req: &Self::Req,
_: Option<Player>,
) -> crate::Result<Self::Resp> {
let player = Player::new(req.username.as_str(), req.new_user_id);
let player = Player::new(req.username.as_str(), req.user_id.clone());
ctx.db.insert(player).map_err(GeoffreyAPIError::from)
}

View File

@ -27,7 +27,7 @@ impl Command for Selling {
CommandLevel::ALL
}
fn run_command(ctx: Arc<Context>, req: Self::Req, _: Option<Player>) -> Result<Self::Resp> {
fn run_command(ctx: Arc<Context>, req: &Self::Req, _: Option<Player>) -> Result<Self::Resp> {
let mut listings: Vec<SellingListing> = ctx
.db
.filter(|_, loc: &LocationDb| {
@ -69,7 +69,7 @@ impl Command for Selling {
.flatten()
.collect();
let sort = req.sort.unwrap_or(ItemSort::Restock);
let sort = req.sort.as_ref().unwrap_or(&ItemSort::Restock);
match sort {
ItemSort::Price => {
@ -80,16 +80,16 @@ impl Command for Selling {
}
}
let ordering = if let Some(order) = req.order {
let ordering = if let Some(order) = &req.order {
order
} else {
match sort {
ItemSort::Price => Order::Low,
ItemSort::Restock => Order::High,
ItemSort::Price => &Order::Low,
ItemSort::Restock => &Order::High,
}
};
if ordering == Order::High {
if *ordering == Order::High {
listings.reverse();
}

View File

@ -27,7 +27,7 @@ impl Command for SetPortal {
CommandLevel::REGISTERED
}
fn run_command(ctx: Arc<Context>, req: Self::Req, user: Option<Player>) -> Result<Self::Resp> {
fn run_command(ctx: Arc<Context>, req: &Self::Req, user: Option<Player>) -> Result<Self::Resp> {
let user = user.unwrap();
let filter = regex::Regex::new(format!(r"(?i)^{}$", req.loc_name).as_str()).unwrap();
@ -39,7 +39,7 @@ impl Command for SetPortal {
.next()
.ok_or(GeoffreyAPIError::EntryNotFound)?;
location.portal = Some(req.portal);
location.portal = Some(req.portal.clone());
let location = ctx.db.insert(location)?;

View File

@ -1,21 +1,27 @@
use crate::Result;
use geoffrey_db::database::Database;
use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam};
use geoffrey_models::models::player::Player;
use geoffrey_models::models::token::Token;
pub fn get_player_from_req<T: CommandRequest>(db: &Database, req: &T) -> Result<Option<Player>> {
if let Some(user_id) = req.user_id() {
pub fn get_player_from_req<T: GeoffreyParam>(
db: &Database,
req: &CommandRequest<T>,
) -> Result<Option<Player>> {
if let Some(user_id) = &req.user_id {
Ok(db
.filter(|_, player: &Player| player.has_user_id(&user_id))?
.filter(|_, player: &Player| player.has_user_id(user_id))?
.next())
} else {
Ok(None)
}
}
pub fn get_token_from_req<T: CommandRequest>(db: &Database, req: &T) -> Result<Option<Token>> {
pub fn get_token_from_req<T: GeoffreyParam>(
db: &Database,
req: &CommandRequest<T>,
) -> Result<Option<Token>> {
Ok(db
.filter(|_, token: &Token| token.secret == req.token())?
.filter(|_, token: &Token| token.secret == req.token)?
.next())
}

View File

@ -70,14 +70,7 @@ async fn run_server(ctx: Arc<Context>) {
}
fn create_token(ctx: Arc<Context>, perms: Vec<Permissions>) {
match AddToken::run_command(
ctx,
AddTokenParams {
token: "".to_string(),
permissions: perms,
},
None,
) {
match AddToken::run_command(ctx, &AddTokenParams { permissions: perms }, None) {
Ok(token) => {
// Don't log this to keep tokens out of the log
println!("Added admin token with secret: {}", token.secret)

View File

@ -1,4 +1,4 @@
use std::fmt::{Display, Formatter};
use std::fmt::{Debug, Display, Formatter};
use async_trait::async_trait;
use reqwest::Error;
@ -7,7 +7,7 @@ use serde::Serialize;
use serenity::model::interactions::application_command::ApplicationCommandInteraction;
use serenity::Error as SerenityError;
use geoffrey_models::models::parameters::CommandRequest;
use geoffrey_models::models::parameters::{CommandRequest, GeoffreyParam};
use geoffrey_models::models::player::UserID;
use geoffrey_models::models::response::api_error::GeoffreyAPIError;
use geoffrey_models::models::response::APIResponse;
@ -74,8 +74,8 @@ impl From<reqwest::Error> for CommandError {
#[async_trait]
pub trait BotCommand: Send + 'static {
type ApiParams: CommandRequest;
type ApiResp: Serialize + DeserializeOwned + Send;
type ApiParams: GeoffreyParam;
type ApiResp: Serialize + DeserializeOwned + Send + Debug;
fn command_name() -> String;
@ -89,7 +89,7 @@ pub trait BotCommand: Send + 'static {
async fn run_api_query(
ctx: GeoffreyContext,
params: Self::ApiParams,
params: CommandRequest<Self::ApiParams>,
) -> Result<APIResponse<Self::ApiResp>, CommandError> {
let command_url = Self::command_url(&ctx.cfg.api.base_url);
let resp: APIResponse<Self::ApiResp> = ctx
@ -154,7 +154,7 @@ pub trait BotCommand: Send + 'static {
user_id: UserID,
command_interact: ApplicationCommandInteraction,
) -> Result<String, CommandError> {
let mut args = Self::process_arguments(command_interact).await?;
let args = Self::process_arguments(command_interact).await?;
log::info!(
"Running command {}, with args {:?}",
@ -162,10 +162,13 @@ pub trait BotCommand: Send + 'static {
args
);
args.set_token(ctx.cfg.api.token.clone());
args.set_user_id(user_id);
let request = CommandRequest {
token: ctx.cfg.api.token.clone(),
user_id: Some(user_id),
params: args,
};
let resp = Self::run_api_query(ctx, args).await?;
let resp = Self::run_api_query(ctx, request).await?;
match resp {
APIResponse::Response(resp) => Ok(Self::build_response(resp)),

View File

@ -2,12 +2,22 @@ use crate::error::{GeoffreyDBError, Result};
use crate::migration::do_migration;
use geoffrey_models::models::db_metadata::DBMetadata;
use geoffrey_models::GeoffreyDatabaseModel;
use sled::IVec;
use std::convert::TryInto;
use std::path::Path;
const DB_VERSION: u64 = 3;
const DB_METADATA_ID: u64 = 1;
fn option_bytes_to_model<T: GeoffreyDatabaseModel>(bytes: Option<IVec>, id: u64) -> Result<T> {
if let Some(bytes) = bytes {
Ok(T::try_from_bytes(&bytes)?)
} else {
log::debug!("{} of id {} was not found in the database", T::tree(), id);
Err(GeoffreyDBError::NotFound)
}
}
pub struct Database {
pub(crate) db: sled::Db,
}
@ -66,14 +76,7 @@ impl Database {
T: GeoffreyDatabaseModel,
{
let tree = self.get_tree::<T>()?;
let id_bytes = id.to_be_bytes();
if let Some(bytes) = tree.get(id_bytes)? {
Ok(T::try_from_bytes(&bytes)?)
} else {
log::debug!("{} of id {} was not found in the database", T::tree(), id);
Err(GeoffreyDBError::NotFound)
}
option_bytes_to_model(tree.get(id.to_be_bytes())?, id)
}
pub fn clear_tree<T>(&self) -> Result<()>
@ -110,6 +113,14 @@ impl Database {
}))
}
pub fn remove<T>(&self, id: u64) -> Result<T>
where
T: GeoffreyDatabaseModel,
{
let tree = self.db.open_tree(T::tree())?;
option_bytes_to_model(tree.remove(id.to_be_bytes())?, id)
}
pub fn tree_iter<T>(&self) -> Result<sled::Iter>
where
T: GeoffreyDatabaseModel,
@ -228,6 +239,21 @@ mod tests {
cleanup();
}
#[test]
fn test_remove() {
let _lock = LOCK.lock().unwrap();
cleanup();
let player = Player::new("CoolZero123", UserID::DiscordUUID { discord_uuid: 0u64 });
let p2 = DB.insert::<Player>(player.clone()).unwrap();
let p3 = DB.remove::<Player>(p2.id.unwrap()).unwrap();
assert!(DB.get::<Player>(p3.id.unwrap()).is_err());
assert_eq!(p3.id.unwrap(), p2.id.unwrap());
cleanup();
}
#[test]
fn test_insert_speed() {
let _lock = LOCK.lock().unwrap();

View File

@ -1,11 +1,8 @@
use crate::models::parameters::CommandRequest;
use crate::models::player::UserID;
use crate::models::parameters::GeoffreyParam;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AddItemParams {
pub token: String,
pub user_id: UserID,
pub item_name: String,
pub price: u32,
pub quantity: u32,
@ -15,8 +12,6 @@ pub struct AddItemParams {
impl AddItemParams {
pub fn new(item_name: String, price: u32, quantity: u32, shop: String) -> Self {
Self {
token: Default::default(),
user_id: Default::default(),
item_name,
price,
quantity,
@ -25,20 +20,4 @@ impl AddItemParams {
}
}
impl CommandRequest for AddItemParams {
fn token(&self) -> String {
self.token.clone()
}
fn user_id(&self) -> Option<UserID> {
Some(self.user_id.clone())
}
fn set_token(&mut self, token: String) {
self.token = token;
}
fn set_user_id(&mut self, user_id: UserID) {
self.user_id = user_id;
}
}
impl GeoffreyParam for AddItemParams {}

View File

@ -1,13 +1,10 @@
use crate::models::locations::LocationType;
use crate::models::parameters::CommandRequest;
use crate::models::player::UserID;
use crate::models::parameters::GeoffreyParam;
use crate::models::{Portal, Position};
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AddLocationParams {
pub token: String,
pub user_id: UserID,
pub name: String,
pub position: Position,
pub loc_type: LocationType,
@ -22,8 +19,6 @@ impl AddLocationParams {
portal: Option<Portal>,
) -> Self {
Self {
token: Default::default(),
user_id: Default::default(),
name,
position,
loc_type,
@ -32,20 +27,4 @@ impl AddLocationParams {
}
}
impl CommandRequest for AddLocationParams {
fn token(&self) -> String {
self.token.clone()
}
fn user_id(&self) -> Option<UserID> {
Some(self.user_id.clone())
}
fn set_user_id(&mut self, user_id: UserID) {
self.user_id = user_id;
}
fn set_token(&mut self, token: String) {
self.token = token;
}
}
impl GeoffreyParam for AddLocationParams {}

View File

@ -1,28 +1,16 @@
use crate::models::parameters::CommandRequest;
use crate::models::parameters::GeoffreyParam;
use crate::models::token::Permissions;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct AddTokenParams {
pub token: String,
pub permissions: Vec<Permissions>,
}
impl AddTokenParams {
fn new(permissions: Vec<Permissions>) -> Self {
Self {
token: Default::default(),
permissions,
}
Self { permissions }
}
}
impl CommandRequest for AddTokenParams {
fn token(&self) -> String {
self.token.clone()
}
fn set_token(&mut self, token: String) {
self.token = token;
}
}
impl GeoffreyParam for AddTokenParams {}

View File

@ -0,0 +1,15 @@
use crate::models::parameters::GeoffreyParam;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct DeleteParams {
pub location: String,
}
impl DeleteParams {
pub fn new(location: String) -> Self {
Self { location }
}
}
impl GeoffreyParam for DeleteParams {}

View File

@ -1,27 +1,15 @@
use crate::models::parameters::CommandRequest;
use crate::models::parameters::GeoffreyParam;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct FindParams {
pub token: String,
pub query: String,
}
impl FindParams {
pub fn new(query: String) -> Self {
Self {
token: Default::default(),
query,
}
Self { query }
}
}
impl CommandRequest for FindParams {
fn token(&self) -> String {
self.token.clone()
}
fn set_token(&mut self, token: String) {
self.token = token;
}
}
impl GeoffreyParam for FindParams {}

View File

@ -1,43 +1,22 @@
pub mod add_item_params;
pub mod add_location_params;
pub mod add_token_params;
pub mod delete_params;
pub mod find_params;
pub mod register_params;
pub mod selling_params;
pub mod set_portal_params;
use crate::models::player::{Player, UserID};
use crate::models::token::{Permissions, Token};
use crate::models::CommandLevel;
use crate::models::player::UserID;
use serde::de::DeserializeOwned;
use serde::Serialize;
use serde::{Deserialize, Serialize};
use std::fmt::Debug;
pub trait CommandRequest:
Serialize + DeserializeOwned + Debug + Clone + Send + 'static + Sync
{
fn token(&self) -> String;
fn user_id(&self) -> Option<UserID> {
None
}
pub trait GeoffreyParam: Serialize + DeserializeOwned + Debug + Clone + Send + Sync {}
fn set_token(&mut self, token: String);
fn set_user_id(&mut self, _: UserID) {}
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
}
}
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct CommandRequest<T> {
pub token: String,
pub user_id: Option<UserID>,
pub params: T,
}

View File

@ -1,34 +1,17 @@
use crate::models::parameters::CommandRequest;
use crate::models::parameters::GeoffreyParam;
use crate::models::player::UserID;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct RegisterParameters {
pub token: String,
pub new_user_id: UserID,
pub username: String,
pub user_id: UserID,
}
impl RegisterParameters {
pub fn new(username: String) -> Self {
RegisterParameters {
token: Default::default(),
new_user_id: Default::default(),
username,
}
pub fn new(username: String, user_id: UserID) -> Self {
RegisterParameters { username, user_id }
}
}
impl CommandRequest for RegisterParameters {
fn token(&self) -> String {
self.token.clone()
}
fn set_user_id(&mut self, user_id: UserID) {
self.new_user_id = user_id;
}
fn set_token(&mut self, token: String) {
self.token = token;
}
}
impl GeoffreyParam for RegisterParameters {}

View File

@ -1,5 +1,4 @@
use crate::models::parameters::CommandRequest;
use crate::models::player::UserID;
use crate::models::parameters::GeoffreyParam;
use serde::{Deserialize, Serialize};
use std::fmt::{Display, Formatter};
use std::str::FromStr;
@ -35,7 +34,7 @@ impl FromStr for ItemSort {
}
}
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
#[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Copy)]
pub enum Order {
High,
Low,
@ -68,7 +67,6 @@ impl FromStr for Order {
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SellingParams {
pub token: String,
pub query: String,
pub sort: Option<ItemSort>,
pub order: Option<Order>,
@ -76,25 +74,8 @@ pub struct SellingParams {
impl SellingParams {
pub fn new(query: String, sort: Option<ItemSort>, order: Option<Order>) -> Self {
Self {
token: Default::default(),
query,
sort,
order,
}
Self { query, sort, order }
}
}
impl CommandRequest for SellingParams {
fn token(&self) -> String {
self.token.clone()
}
fn user_id(&self) -> Option<UserID> {
None
}
fn set_token(&mut self, token: String) {
self.token = token;
}
}
impl GeoffreyParam for SellingParams {}

View File

@ -1,41 +1,17 @@
use crate::models::parameters::CommandRequest;
use crate::models::player::UserID;
use crate::models::parameters::GeoffreyParam;
use crate::models::Portal;
use serde::{Deserialize, Serialize};
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct SetPortalParams {
pub token: String,
pub user_id: UserID,
pub loc_name: String,
pub portal: Portal,
}
impl SetPortalParams {
pub fn new(loc_name: String, portal: Portal) -> Self {
Self {
token: Default::default(),
user_id: Default::default(),
loc_name,
portal,
}
Self { loc_name, portal }
}
}
impl CommandRequest for SetPortalParams {
fn token(&self) -> String {
self.token.clone()
}
fn user_id(&self) -> Option<UserID> {
Some(self.user_id.clone())
}
fn set_user_id(&mut self, user_id: UserID) {
self.user_id = user_id;
}
fn set_token(&mut self, token: String) {
self.token = token;
}
}
impl GeoffreyParam for SetPortalParams {}

View File

@ -3,7 +3,7 @@ use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::convert::TryFrom;
#[derive(Serialize, Deserialize, Debug, Clone, PartialOrd, PartialEq)]
#[derive(Serialize, Deserialize, Debug, Clone, PartialOrd, PartialEq, Copy)]
pub enum Permissions {
ModelGet = 0,
ModelPost,