From 6fe9b52d00400a4c7c2c01376a9e57722e251713 Mon Sep 17 00:00:00 2001 From: ntr Date: Wed, 14 Aug 2019 15:23:15 +1000 Subject: [PATCH] fk axios --- acp/acp.html | 2 - acp/acp.js | 3 + acp/package.json | 1 - acp/src/acp.game.list.jsx | 38 ++++++++ acp/src/acp.jsx | 5 +- acp/src/acp.main.jsx | 162 +++++++++++++++++++++++++++++++++ acp/src/acp.user.jsx | 44 +++++++++ acp/src/acp.users.jsx | 119 ------------------------ acp/src/actions.jsx | 1 + acp/src/reducers.jsx | 1 + client/assets/styles/menu.less | 7 +- client/src/utils.jsx | 17 ---- server/src/account.rs | 55 +++++------ server/src/acp.rs | 125 +++++++++++++++++++++++++ server/src/game.rs | 50 ++++++++++ server/src/http.rs | 116 +++++++---------------- server/src/main.rs | 1 + 17 files changed, 487 insertions(+), 260 deletions(-) create mode 100644 acp/src/acp.game.list.jsx create mode 100644 acp/src/acp.main.jsx create mode 100644 acp/src/acp.user.jsx delete mode 100644 acp/src/acp.users.jsx create mode 100644 server/src/acp.rs diff --git a/acp/acp.html b/acp/acp.html index 8760e330..25f1133c 100644 --- a/acp/acp.html +++ b/acp/acp.html @@ -10,8 +10,6 @@ - - diff --git a/acp/acp.js b/acp/acp.js index 6ad73d17..825f39d8 100644 --- a/acp/acp.js +++ b/acp/acp.js @@ -1,3 +1,6 @@ +require('./../client/assets/styles/normalize.css'); +require('./../client/assets/styles/skeleton.css'); + require('./../client/assets/styles/styles.less'); require('./../client/assets/styles/menu.less'); require('./../client/assets/styles/nav.less'); diff --git a/acp/package.json b/acp/package.json index 6222922b..a914b2e5 100644 --- a/acp/package.json +++ b/acp/package.json @@ -15,7 +15,6 @@ "anime": "^0.1.2", "animejs": "^3.0.1", "async": "^2.6.2", - "axios": "^0.19.0", "borc": "^2.0.3", "docco": "^0.7.0", "izitoast": "^1.4.0", diff --git a/acp/src/acp.game.list.jsx b/acp/src/acp.game.list.jsx new file mode 100644 index 00000000..e180aeca --- /dev/null +++ b/acp/src/acp.game.list.jsx @@ -0,0 +1,38 @@ +const preact = require('preact'); +const { Component } = require('preact'); +const { connect } = require('preact-redux'); +const linkState = require('linkstate').default; + +const axios = require('axios'); + +const actions = require('./actions'); + +const addState = connect( + function receiveState(state) { + const { + games, + } = state; + + return { + games + }; + }, +); + +function AcpGameList(args) { + const { + games, + } = args; + + if (!games) return false; + + return ( + + + {games.map((g, i) => )} + +
{JSON.stringify(g)}
+ ) +} + +module.exports = addState(AcpGameList); diff --git a/acp/src/acp.jsx b/acp/src/acp.jsx index 63b4f0d7..320236c3 100644 --- a/acp/src/acp.jsx +++ b/acp/src/acp.jsx @@ -7,7 +7,7 @@ const { createStore, combineReducers } = require('redux'); const reducers = require('./reducers'); const actions = require('./actions'); -const Users = require('./acp.users'); +const Main = require('./acp.main'); // Redux Store const store = createStore( @@ -22,9 +22,8 @@ document.fonts.load('16pt "Jura"').then(() => { - +
diff --git a/acp/src/acp.main.jsx b/acp/src/acp.main.jsx new file mode 100644 index 00000000..e078c2a2 --- /dev/null +++ b/acp/src/acp.main.jsx @@ -0,0 +1,162 @@ +const preact = require('preact'); +const { Component } = require('preact'); +const { connect } = require('preact-redux'); +const linkState = require('linkstate').default; + +const actions = require('./actions'); +const { postData, errorToast } = require('./../../client/src/utils'); + +const AcpGameList = require('./acp.game.list'); +const AcpUser = require('./acp.user'); + +const addState = connect( + function receiveState(state) { + const { + account, + user, + } = state; + + return { + account, user, + }; + }, + + function receiveDispatch(dispatch) { + function setUser(user) { + dispatch(actions.setUser(user)); + } + + function setGames(list) { + dispatch(actions.setGames(list)); + } + + return { + setUser, + setGames, + }; + } +); + + +class AcpMain extends Component { + constructor(props) { + super(props); + + this.state = { + account: {}, + name: null, + id: null, + msg: '', + user: null, + games: [], + }; + } + + render(args, state) { + const { + setGames, + setUser, + } = args; + + const { + msg, + name, + id, + } = state; + + const getUser = () => { + this.setState({ msg: null }); + postData('/acp/user', { id, name }) + .then(res => res.json()) + .then(data => { + if (data.error) return this.setState({ msg: data.error }); + setUser(data); + }) + .catch(error => errorToast(error)); + }; + + const gameList = () => { + this.setState({ msg: null }); + postData('/acp/game/list', { number: 20 }) + .then(res => res.json()) + .then(data => { + if (data.error) return this.setState({ msg: data.error }); + console.log(data); + setGames(data.data); + }) + .catch(error => errorToast(error)); + }; + + const gameOpen = () => { + this.setState({ msg: null }); + postData('/acp/game/open') + .then(res => res.json()) + .then(data => { + if (data.error) return this.setState({ msg: data.error }); + console.log(data); + setGames(data); + }) + .catch(error => errorToast(error)); + }; + + return ( +
+
+
{msg}
+ + +
+
+
+ + + + +
+
+ + + + + +
+
+
+ ); + } +} + +module.exports = addState(AcpMain); diff --git a/acp/src/acp.user.jsx b/acp/src/acp.user.jsx new file mode 100644 index 00000000..0f5b2a3b --- /dev/null +++ b/acp/src/acp.user.jsx @@ -0,0 +1,44 @@ +const preact = require('preact'); +const { Component } = require('preact'); +const { connect } = require('preact-redux'); +const linkState = require('linkstate').default; + +const axios = require('axios'); + +const actions = require('./actions'); + +const addState = connect( + function receiveState(state) { + const { + user, + } = state; + + return { + user + }; + }, +); + +function AcpGameList(args) { + const { + user, + } = args; + + if (!user) return false; + + return ( +
+

{user.name}

+
+
Id
+
{user.id}
+
Credits
+
{user.balance}
+
Subscribed
+
{user.subscribed.toString()}
+
+
+ ) +} + +module.exports = addState(AcpGameList); diff --git a/acp/src/acp.users.jsx b/acp/src/acp.users.jsx deleted file mode 100644 index 1c8c84b5..00000000 --- a/acp/src/acp.users.jsx +++ /dev/null @@ -1,119 +0,0 @@ -const preact = require('preact'); -const { Component } = require('preact'); -const { connect } = require('preact-redux'); -const linkState = require('linkstate').default; - -const axios = require('axios'); - -const actions = require('./actions'); - -const addState = connect( - function receiveState(state) { - const { - account, - user, - } = state; - - return { - account, user, - }; - }, - - function receiveDispatch(dispatch) { - function setUser(user) { - dispatch(actions.setUser(user)); - } - - return { setUser }; - } -); - - -class AccountStatus extends Component { - constructor(props) { - super(props); - - this.state = { - account: {}, - name: null, - id: null, - msg: '', - user: null, - }; - } - - render(args, state) { - const { - msg, - name, - id, - user, - } = state; - - console.log(user); - - const getUser = () => { - this.setState({ msg: null }); - axios.post('/api/acp/user', { id, name }) - .then(response => { - console.log(response); - this.setState({ user: JSON.parse(response.data.response) }); - }) - .catch(error => { - console.error(error); - this.setState({ msg: error.message }); - }); - }; - - const userEl = user - ? ( -
-

{user.name}

-
-
Id
-
{user.id}
-
Credits
-
{user.balance}
-
Subscribed
-
{user.subscribed.toString()}
-
-
- ) : null; - - return ( -
-
-
{msg}
- {userEl} -
-
-
- - - - -
-
-
- ); - } -} - -module.exports = addState(AccountStatus); diff --git a/acp/src/actions.jsx b/acp/src/actions.jsx index 4c7d20ff..bf8155be 100644 --- a/acp/src/actions.jsx +++ b/acp/src/actions.jsx @@ -1,2 +1,3 @@ export const setAccount = value => ({ type: 'SET_ACCOUNT', value }); export const setUser = value => ({ type: 'SET_USER', value }); +export const setGames = value => ({ type: 'SET_GAMES', value }); diff --git a/acp/src/reducers.jsx b/acp/src/reducers.jsx index 6bc56021..7522236e 100644 --- a/acp/src/reducers.jsx +++ b/acp/src/reducers.jsx @@ -13,4 +13,5 @@ function createReducer(defaultState, actionType) { module.exports = { account: createReducer(null, 'SET_ACCOUNT'), user: createReducer(null, 'SET_USER'), + games: createReducer([], 'SET_GAMES'), }; diff --git a/client/assets/styles/menu.less b/client/assets/styles/menu.less index f3f5248a..30e74a2a 100644 --- a/client/assets/styles/menu.less +++ b/client/assets/styles/menu.less @@ -89,12 +89,17 @@ } } -#mnml.acp, .acp { +#mnml.acp { user-select: text; -moz-user-select: text; -webkit-user-select: text; -ms-user-select: text; + .bottom { + display: grid; + grid-template-columns: repeat(4, 1fr); + } + input { display: block; } diff --git a/client/src/utils.jsx b/client/src/utils.jsx index 3816da45..a50ba095 100644 --- a/client/src/utils.jsx +++ b/client/src/utils.jsx @@ -204,23 +204,6 @@ function postData(url = '/', data = {}) { }); } -function getData(url = '/', data = {}) { - // Default options are marked with * - return fetch(`/api${url}`, { - method: 'GET', // *GET, POST, PUT, DELETE, etc. - // mode: 'no-cors', // no-cors, cors, *same-origin - cache: 'no-cache', // *default, no-cache, reload, force-cache, only-if-cached - credentials: 'include', // include, same-origin, *omit - headers: { - Accept: 'application/json', - 'content-type': 'application/json', - }, - redirect: 'error', // manual, *follow, error - // referrer: ', // no-referrer, *client - body: JSON.stringify(data), // body data type must match "Content-Type" header - }); -} - function errorToast(message) { toast.error({ position: 'topRight', diff --git a/server/src/account.rs b/server/src/account.rs index 350aae84..562cae11 100644 --- a/server/src/account.rs +++ b/server/src/account.rs @@ -29,6 +29,23 @@ pub struct Account { pub subscribed: bool, } +impl<'a> TryFrom> for Account { + type Error = Error; + + fn try_from(row: postgres::rows::Row) -> Result { + 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 { let query = " SELECT id, name, balance, subscribed @@ -42,13 +59,7 @@ pub fn select(db: &Db, id: Uuid) -> Result { 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 { @@ -64,14 +75,7 @@ pub fn select_name(db: &Db, name: &String) -> Result { 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 { @@ -88,15 +92,7 @@ pub fn from_token(db: &Db, token: String) -> Result { 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 { @@ -124,20 +120,13 @@ pub fn login(tx: &mut Transaction, name: &String, password: &String) -> Result Result { diff --git a/server/src/acp.rs b/server/src/acp.rs new file mode 100644 index 00000000..c428ed47 --- /dev/null +++ b/server/src/acp.rs @@ -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::() { + 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, + id: Option, +} + +fn acp_user(req: &mut Request) -> IronResult { + let state = req.get::>().unwrap(); + let params = match req.get::>() { + 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 { + let state = req.get::>().unwrap(); + let params = match req.get::>() { + 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 { + let state = req.get::>().unwrap(); + let params = match req.get::>() { + 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 { + let state = req.get::>().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 +} diff --git a/server/src/game.rs b/server/src/game.rs index 8cfdaf77..6cdeda4c 100644 --- a/server/src/game.rs +++ b/server/src/game.rs @@ -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 { return Ok(game); } +pub fn select(db: &Db, id: Uuid) -> Result { + 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 = returned.get("data"); + let game = from_slice::(&game_bytes)?; + + return Ok(game); +} + +pub fn list(db: &Db, number: u32) -> Result, 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 = row.get(0); + + match from_slice::(&bytes) { + Ok(i) => list.push(i), + Err(e) => { + warn!("{:?}", e); + } + }; + } + + return Ok(list); +} + pub fn games_need_upkeep(tx: &mut Transaction) -> Result, Error> { let query = " SELECT data, id diff --git a/server/src/http.rs b/server/src/http.rs index c05906fd..cc6f3d0a 100644 --- a/server/src/http.rs +++ b/server/src/http.rs @@ -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 for MnmlHttpError { } } +impl From for MnmlHttpError { + fn from(_err: r2d2::Error) -> Self { + MnmlHttpError::DbError + } +} + impl From 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, - success: bool, - error_message: Option +#[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::().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::().unwrap(); + return Response::with((content_type, status, object)); } impl From 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 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 { 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::() { - 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, - id: Option, -} - -fn acp_user(req: &mut Request) -> IronResult { - let state = req.get::>().unwrap(); - let params = match req.get::>() { - 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::::both(State { pool })); diff --git a/server/src/main.rs b/server/src/main.rs index 731ca154..44102168 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -30,6 +30,7 @@ extern crate ws; extern crate crossbeam_channel; mod account; +mod acp; mod construct; mod effect; mod game;