This commit is contained in:
ntr
2019-08-14 15:23:15 +10:00
parent f68a12eab8
commit 6fe9b52d00
17 changed files with 487 additions and 260 deletions
+22 -33
View File
@@ -29,6 +29,23 @@ pub struct Account {
pub subscribed: bool,
}
impl<'a> TryFrom<postgres::rows::Row<'a>> for Account {
type Error = Error;
fn try_from(row: postgres::rows::Row) -> Result<Self, Error> {
let id: Uuid = row.get("id");
let db_balance: i64 = row.get("balance");
let balance = u32::try_from(db_balance)
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?;
let subscribed: bool = row.get("subscribed");
let name: String = row.get("name");
Ok(Account { id, name, balance, subscribed })
}
}
pub fn select(db: &Db, id: Uuid) -> Result<Account, Error> {
let query = "
SELECT id, name, balance, subscribed
@@ -42,13 +59,7 @@ pub fn select(db: &Db, id: Uuid) -> Result<Account, Error> {
let row = result.iter().next()
.ok_or(format_err!("account not found {:?}", id))?;
let db_balance: i64 = row.get(2);
let balance = u32::try_from(db_balance)
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?;
let subscribed: bool = row.get(3);
Ok(Account { id, name: row.get(1), balance, subscribed })
Account::try_from(row)
}
pub fn select_name(db: &Db, name: &String) -> Result<Account, Error> {
@@ -64,14 +75,7 @@ pub fn select_name(db: &Db, name: &String) -> Result<Account, Error> {
let row = result.iter().next()
.ok_or(format_err!("account not found name={:?}", name))?;
let id: Uuid = row.get(0);
let db_balance: i64 = row.get(2);
let balance = u32::try_from(db_balance)
.or(Err(format_err!("user {:?} has unparsable balance {:?}", name, db_balance)))?;
let subscribed: bool = row.get(3);
Ok(Account { id, name: row.get(1), balance, subscribed })
Account::try_from(row)
}
pub fn from_token(db: &Db, token: String) -> Result<Account, Error> {
@@ -88,15 +92,7 @@ pub fn from_token(db: &Db, token: String) -> Result<Account, Error> {
let row = result.iter().next()
.ok_or(err_msg("invalid token"))?;
let id: Uuid = row.get(0);
let name: String = row.get(1);
let subscribed: bool = row.get(2);
let db_balance: i64 = row.get(3);
let balance = u32::try_from(db_balance)
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?;
Ok(Account { id, name, balance, subscribed })
Account::try_from(row)
}
pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result<Account, MnmlHttpError> {
@@ -124,20 +120,13 @@ pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result<A
},
};
let id: Uuid = row.get(0);
let hash: String = row.get(1);
let name: String = row.get(2);
let db_balance: i64 = row.get(3);
let subscribed: bool = row.get(4);
if !verify(password, &hash)? {
return Err(MnmlHttpError::PasswordNotMatch);
}
let balance = u32::try_from(db_balance)
.or(Err(format_err!("user {:?} has unparsable balance {:?}", id, db_balance)))?;
Ok(Account { id, name, balance, subscribed })
Account::try_from(row)
.or(Err(MnmlHttpError::ServerError))
}
pub fn new_token(tx: &mut Transaction, id: Uuid) -> Result<String, MnmlHttpError> {
+125
View File
@@ -0,0 +1,125 @@
use iron::prelude::*;
use iron::status;
use iron::{BeforeMiddleware};
use persistent::Read;
use router::Router;
use serde::{Deserialize};
use uuid::Uuid;
use account;
use game;
use http::{State, MnmlHttpError, json_object};
struct AcpMiddleware;
impl BeforeMiddleware for AcpMiddleware {
fn before(&self, req: &mut Request) -> IronResult<()> {
match req.extensions.get::<account::Account>() {
Some(a) => {
if ["ntr", "mashy"].contains(&a.name.to_ascii_lowercase().as_ref()) {
return Ok(());
}
return Err(MnmlHttpError::Unauthorized.into());
},
None => Err(MnmlHttpError::Unauthorized.into()),
}
}
}
#[derive(Debug,Clone,Deserialize)]
struct GetUser {
name: Option<String>,
id: Option<Uuid>,
}
fn acp_user(req: &mut Request) -> IronResult<Response> {
let state = req.get::<Read<State>>().unwrap();
let params = match req.get::<bodyparser::Struct<GetUser>>() {
Ok(Some(b)) => b,
_ => return Err(MnmlHttpError::BadRequest.into()),
};
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
let user = match params.id {
Some(id) => account::select(&db, id)
.or(Err(MnmlHttpError::NotFound))?,
None => match params.name {
Some(n) => account::select_name(&db, &n)
.or(Err(MnmlHttpError::NotFound))?,
None => return Err(MnmlHttpError::BadRequest.into()),
}
};
Ok(json_object(status::Ok, serde_json::to_string(&user).unwrap()))
}
#[derive(Debug,Clone,Deserialize)]
struct GetGame {
id: Uuid,
}
fn acp_game(req: &mut Request) -> IronResult<Response> {
let state = req.get::<Read<State>>().unwrap();
let params = match req.get::<bodyparser::Struct<GetGame>>() {
Ok(Some(b)) => b,
_ => return Err(MnmlHttpError::BadRequest.into()),
};
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
let game = game::select(&db, params.id)
.or(Err(MnmlHttpError::NotFound))?;
Ok(json_object(status::Ok, serde_json::to_string(&game).unwrap()))
}
#[derive(Debug,Clone,Deserialize)]
struct GameList {
number: u32,
}
fn game_list(req: &mut Request) -> IronResult<Response> {
let state = req.get::<Read<State>>().unwrap();
let params = match req.get::<bodyparser::Struct<GameList>>() {
Ok(Some(b)) => b,
_ => return Err(MnmlHttpError::BadRequest.into()),
};
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
let list = game::list(&db, params.number)
.or(Err(MnmlHttpError::ServerError))?;
Ok(json_object(status::Ok, serde_json::to_string(&list).unwrap()))
}
fn game_open(req: &mut Request) -> IronResult<Response> {
let state = req.get::<Read<State>>().unwrap();
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
let mut tx = db.transaction().or(Err(MnmlHttpError::DbError))?;
let list = game::games_need_upkeep(&mut tx)
.or(Err(MnmlHttpError::ServerError))?;
tx.commit()
.or(Err(MnmlHttpError::ServerError))?;
Ok(json_object(status::Ok, serde_json::to_string(&list).unwrap()))
}
pub fn acp_mount() -> Chain {
let mut router = Router::new();
router.post("user", acp_user, "acp_user");
router.post("game", acp_game, "acp_game");
router.post("game/list", game_list, "acp_game_list");
router.post("game/open", game_open, "acp_game_open");
let mut chain = Chain::new(router);
chain.link_before(AcpMiddleware);
chain
}
+50
View File
@@ -12,6 +12,7 @@ use failure::Error;
use failure::err_msg;
use account::Account;
use pg::Db;
use construct::{Construct};
use skill::{Skill, Cast, Resolution, Event, resolution_steps};
@@ -655,6 +656,55 @@ pub fn game_get(tx: &mut Transaction, id: Uuid) -> Result<Game, Error> {
return Ok(game);
}
pub fn select(db: &Db, id: Uuid) -> Result<Game, Error> {
let query = "
SELECT *
FROM games
WHERE id = $1;
";
let result = db
.query(query, &[&id])?;
let returned = match result.iter().next() {
Some(row) => row,
None => return Err(err_msg("game not found")),
};
// tells from_slice to cast into a construct
let game_bytes: Vec<u8> = returned.get("data");
let game = from_slice::<Game>(&game_bytes)?;
return Ok(game);
}
pub fn list(db: &Db, number: u32) -> Result<Vec<Game>, Error> {
let query = "
SELECT data
FROM games
ORDER BY created_at
LIMIT $1;
";
let result = db
.query(query, &[&number])?;
let mut list = vec![];
for row in result.into_iter() {
let bytes: Vec<u8> = row.get(0);
match from_slice::<Game>(&bytes) {
Ok(i) => list.push(i),
Err(e) => {
warn!("{:?}", e);
}
};
}
return Ok(list);
}
pub fn games_need_upkeep(tx: &mut Transaction) -> Result<Vec<Game>, Error> {
let query = "
SELECT data, id
+32 -84
View File
@@ -11,8 +11,8 @@ use persistent::Read;
use router::Router;
use mount::{Mount};
use serde::{Serialize, Deserialize};
use uuid::Uuid;
use acp;
use account;
use pg::PgPool;
use payments::{stripe};
@@ -62,42 +62,40 @@ impl From<postgres::Error> for MnmlHttpError {
}
}
impl From<r2d2::Error> for MnmlHttpError {
fn from(_err: r2d2::Error) -> Self {
MnmlHttpError::DbError
}
}
impl From<failure::Error> for MnmlHttpError {
fn from(_err: failure::Error) -> Self {
fn from(err: failure::Error) -> Self {
warn!("{:?}", err);
MnmlHttpError::ServerError
}
}
#[derive(Serialize, Deserialize)]
struct JsonResponse {
response: Option<String>,
success: bool,
error_message: Option<String>
#[serde(rename_all(serialize = "lowercase"))]
pub enum Json {
Error(String),
Message(String),
}
impl JsonResponse {
fn success(response: String) -> Self {
JsonResponse { response: Some(response), success: true, error_message: None }
}
fn error(msg: String) -> Self {
JsonResponse { response: None, success: false, error_message: Some(msg) }
}
}
fn iron_response(status: status::Status, message: String) -> Response {
pub fn json_response(status: status::Status, response: Json) -> Response {
let content_type = "application/json".parse::<Mime>().unwrap();
let msg = match status {
status::Ok => JsonResponse::success(message),
_ => JsonResponse::error(message)
};
let msg_out = serde_json::to_string(&msg).unwrap();
return Response::with((content_type, status, msg_out));
let json = serde_json::to_string(&response).unwrap();
return Response::with((content_type, status, json));
}
pub fn json_object(status: status::Status, object: String) -> Response {
let content_type = "application/json".parse::<Mime>().unwrap();
return Response::with((content_type, status, object));
}
impl From<MnmlHttpError> for IronError {
fn from(m_err: MnmlHttpError) -> Self {
let (err, res) = match m_err {
let (err, status) = match m_err {
MnmlHttpError::ServerError |
MnmlHttpError::DbError => (m_err.compat(), status::InternalServerError),
@@ -114,7 +112,9 @@ impl From<MnmlHttpError> for IronError {
MnmlHttpError::NotFound => (m_err.compat(), status::NotFound),
};
IronError { error: Box::new(err), response: iron_response(res, m_err.to_string()) }
let response = json_response(status, Json::Error(m_err.to_string()));
IronError { error: Box::new(err), response }
}
}
@@ -173,7 +173,11 @@ fn token_res(token: String) -> Response {
.max_age(Duration::weeks(1)) // 1 week aligns with db set
.finish();
let mut res = iron_response(status::Ok, "token_res".to_string());
let mut res = json_response(
status::Ok,
Json::Message("authenticated".to_string())
);
res.headers.set(SetCookie(vec![v.to_string()]));
return res;
@@ -249,7 +253,7 @@ fn logout(req: &mut Request) -> IronResult<Response> {
tx.commit().or(Err(MnmlHttpError::ServerError))?;
let mut res = iron_response(status::Ok, "logout".to_string());
let mut res = json_response(status::Ok, Json::Message("logged out".to_string()));
res.headers.set(SetCookie(vec![AUTH_CLEAR.to_string()]));
Ok(res)
@@ -314,68 +318,12 @@ fn payment_mount() -> Router {
router
}
struct AcpMiddleware;
impl BeforeMiddleware for AcpMiddleware {
fn before(&self, req: &mut Request) -> IronResult<()> {
match req.extensions.get::<account::Account>() {
Some(a) => {
if ["ntr", "mashy"].contains(&a.name.to_ascii_lowercase().as_ref()) {
return Ok(());
}
return Err(IronError::from(MnmlHttpError::Unauthorized));
},
None => Err(IronError::from(MnmlHttpError::Unauthorized)),
}
}
}
#[derive(Debug,Clone,Deserialize)]
struct GetUser {
name: Option<String>,
id: Option<Uuid>,
}
fn acp_user(req: &mut Request) -> IronResult<Response> {
let state = req.get::<Read<State>>().unwrap();
let params = match req.get::<bodyparser::Struct<GetUser>>() {
Ok(Some(b)) => b,
_ => return Err(IronError::from(MnmlHttpError::BadRequest)),
};
let db = state.pool.get().or(Err(MnmlHttpError::DbError))?;
println!("{:?}", params);
let user = match params.id {
Some(id) => account::select(&db, id)
.or(Err(MnmlHttpError::NotFound))?,
None => match params.name {
Some(n) => account::select_name(&db, &n)
.or(Err(MnmlHttpError::NotFound))?,
None => return Err(IronError::from(MnmlHttpError::BadRequest)),
}
};
Ok(iron_response(status::Ok, serde_json::to_string(&user).unwrap()))
}
fn acp_mount() -> Chain {
let mut router = Router::new();
router.post("user", acp_user, "acp_user");
let mut chain = Chain::new(router);
chain.link_before(AcpMiddleware);
chain
}
pub fn start(pool: PgPool) {
let mut mounts = Mount::new();
mounts.mount("/api/account/", account_mount());
mounts.mount("/api/payments/", payment_mount());
mounts.mount("/api/acp/", acp_mount());
mounts.mount("/api/acp/", acp::acp_mount());
let mut chain = Chain::new(mounts);
chain.link(Read::<State>::both(State { pool }));
+1
View File
@@ -30,6 +30,7 @@ extern crate ws;
extern crate crossbeam_channel;
mod account;
mod acp;
mod construct;
mod effect;
mod game;