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 (
+
+ );
+ }
+}
+
+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 (
-
- );
- }
-}
-
-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;