diff --git a/VERSION b/VERSION index 9325c3cc..afaf360d 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.3.0 \ No newline at end of file +1.0.0 \ No newline at end of file diff --git a/WORKLOG.md b/WORKLOG.md index bb9934a4..bc56173d 100644 --- a/WORKLOG.md +++ b/WORKLOG.md @@ -3,8 +3,9 @@ *PRODUCTION* * ACP * essential - * error log - * account lookup w/ pw reset + * serde serialize privatise + * DO postgres + * mobile styles * treats * constructs jiggle when clicked @@ -12,15 +13,8 @@ * bot game grind -* serde serialize privatise -* stripe prod -* mobile styles * change score to enum * pct based translates for combat animation -* account page -* graphs n shit -* acp init -* DO postgres * make our own toasts / msg pane * send account_instances on players update @@ -28,6 +22,8 @@ * clear skill (if currently targetted) * increase power to speed up early rounds +* only login / logout / register http + ## SOON *SERVER* * modules @@ -38,12 +34,15 @@ * empower on ko * rework vecs into sets +* remove names so games/instances are copy *$$$* * chatwheel * eth adapter * illusions * vaporwave +* crop circles +* insects * sacred geometry * skulls / day of the dead * Aztec diff --git a/acp/package.json b/acp/package.json index ef344ba8..9ad1a0b1 100644 --- a/acp/package.json +++ b/acp/package.json @@ -1,6 +1,6 @@ { "name": "mnml-client", - "version": "0.3.0", + "version": "1.0.0", "description": "", "main": "index.js", "scripts": { diff --git a/client/assets/styles/account.less b/client/assets/styles/account.less index a73faaae..d6f43a30 100644 --- a/client/assets/styles/account.less +++ b/client/assets/styles/account.less @@ -20,15 +20,28 @@ display: block; } + .unsub { + &:hover { + color: @red; + border-color: @red; + }; + + &:active, &.confirming { + background: @red; + color: black; + border: 1px solid black; + } + } .list { letter-spacing: 0.25em; text-transform: uppercase; figure { - font-size: 125%; + width: 75%; display: flex; flex-flow: column; + margin-bottom: 1em; button { width: 100%; diff --git a/client/assets/styles/controls.less b/client/assets/styles/controls.less index 09713fc6..d036debc 100644 --- a/client/assets/styles/controls.less +++ b/client/assets/styles/controls.less @@ -76,3 +76,13 @@ aside { transition-duration: 0.25s; transition-timing-function: ease; } + +.team-page-ctrl { + display: flex; + height: 3em; + align-items: center; +} + +.team-page-ctrl h2 { + padding: 0 0.75em 0 0.75em; +} diff --git a/client/assets/styles/styles.less b/client/assets/styles/styles.less index f234fcd0..30b6d8e1 100644 --- a/client/assets/styles/styles.less +++ b/client/assets/styles/styles.less @@ -78,6 +78,10 @@ p { margin-bottom: 1em; } +dl { + margin: 1em 0; +} + #mnml { display: grid; grid-template-columns: minmax(min-content, 1fr) 8fr 1fr; diff --git a/client/package.json b/client/package.json index 96196e8e..da1b56da 100644 --- a/client/package.json +++ b/client/package.json @@ -1,6 +1,6 @@ { "name": "mnml-client", - "version": "0.3.0", + "version": "1.0.0", "description": "", "main": "index.js", "scripts": { diff --git a/client/src/actions.jsx b/client/src/actions.jsx index 1fde403a..d4242eb1 100644 --- a/client/src/actions.jsx +++ b/client/src/actions.jsx @@ -31,8 +31,10 @@ export const setShowLog = value => ({ type: 'SET_SHOW_LOG', value }); export const setShowNav = value => ({ type: 'SET_SHOW_NAV', value }); export const setSkip = value => ({ type: 'SET_SKIP', value }); export const setShop = value => ({ type: 'SET_SHOP', value }); +export const setSubscription = value => ({ type: 'SET_SUBSCRIPTION', value }); export const setTeam = value => ({ type: 'SET_TEAM', value: Array.from(value) }); +export const setTeamPage = value => ({ type: 'SET_TEAM_PAGE', value }); export const setTeamSelect = value => ({ type: 'SET_TEAM_SELECT', value: Array.from(value) }); export const setVboxHighlight = value => ({ type: 'SET_VBOX_HIGHLIGHT', value }); diff --git a/client/src/app.jsx b/client/src/app.jsx index 2e64724f..ddd7a1ea 100644 --- a/client/src/app.jsx +++ b/client/src/app.jsx @@ -30,7 +30,7 @@ document.fonts.load('16pt "Jura"').then(() => { const App = () => ( - + diff --git a/client/src/components/account.management.jsx b/client/src/components/account.management.jsx index 05a8b04b..b2799138 100644 --- a/client/src/components/account.management.jsx +++ b/client/src/components/account.management.jsx @@ -12,13 +12,14 @@ const addState = connect( function receiveState(state) { const { account, + subscription, email, ping, ws, } = state; - function setPassword(current, password) { + function sendSetPassword(current, password) { postData('/account/password', { current, password }) .then(res => res.json()) .then(data => { @@ -29,7 +30,7 @@ const addState = connect( .catch(error => errorToast(error)); } - function setEmail(email) { + function sendSetEmail(email) { postData('/account/email', { email }) .then(res => res.json()) .then(data => { @@ -49,14 +50,20 @@ const addState = connect( return ws.sendMtxConstructSpawn(name); } + function sendSubscriptionEnding(enabled) { + return ws.sendSubscriptionEnding(enabled); + } + return { account, + subscription, ping, email, logout, - setPassword, - setEmail, + sendSetPassword, + sendSetEmail, sendConstructSpawn, + sendSubscriptionEnding, }; }, ); @@ -67,42 +74,96 @@ class AccountStatus extends Component { super(props); this.state = { - setPassword: { current: '', password: '', confirm: ''}, - email: null, + passwordState: { current: '', password: '', confirm: ''}, + emailState: null, + unsubState: false, }; } - render(args) { + render(args, state) { const { account, + subscription, ping, email, logout, - setPassword, - setEmail, + sendSetEmail, + sendSetPassword, sendConstructSpawn, + sendSubscriptionEnding, } = args; + const { + passwordState, + emailState, + unsubState, + } = state; + if (!account) return null; const passwordsEqual = () => - this.state.setPassword.password === this.state.setPassword.confirm; + passwordState.password === passwordState.confirm; const setPasswordDisabled = () => { - const { current, password, confirm } = this.state.setPassword; + const { current, password, confirm } = passwordState; return !(passwordsEqual() && password && current && confirm); } + const tlClick = e => { + e.stopPropagation(); + return this.setState({ unsubState: false }); + } + + const subInfo = () => { + const unsubBtn = () => { + if (!subscription) return false; + + // resub button + if (subscription.cancel_at_period_end) { + return + } + + const classes = `unsub ${unsubState ? 'confirming' : ''}`; + const text = unsubState ? 'Confirm' : 'Unsubscribe' + return + } + + const unsub = e => { + if (unsubState) { + return sendSubscriptionEnding(true); + } + + e.stopPropagation(); + return this.setState({ unsubState: true }); + } + + if (!subscription) return false; + return
+

Subscription

+
+
Period End
+
{new Date(subscription.current_period_end * 1000).toString()}
+
Status
+
{subscription.cancel_at_period_end ? 'Disabled' : 'Active'}
+
+ {unsubBtn()} +
; + } + return ( -
+

{account.name}

-
-
Subscription
-
{account.subscribed ? 'some date' : 'unsubscribed'}
-
- +
+
+
spawn new construct
+ +
+
+
@@ -116,11 +177,11 @@ class AccountStatus extends Component { class="login-input" type="email" name="email" - value={this.state.email} - onInput={linkState(this, 'email')} + value={emailState} + onInput={linkState(this, 'emailState')} placeholder="recovery@email.tld" /> - +
@@ -128,39 +189,34 @@ class AccountStatus extends Component { class="login-input" type="password" name="current" - value={this.state.setPassword.current} - onInput={linkState(this, 'setPassword.current')} + value={passwordState.current} + onInput={linkState(this, 'passwordState.current')} placeholder="current" />
-
-
-
spawn new construct
- -
+
+ {subInfo()}
); diff --git a/client/src/components/account.status.jsx b/client/src/components/account.status.jsx index fce012cf..422d2233 100644 --- a/client/src/components/account.status.jsx +++ b/client/src/components/account.status.jsx @@ -13,11 +13,18 @@ function pingColour(ping) { const addState = connect( function receiveState(state) { const { + ws, account, ping, } = state; + function sendAccountStates() { + ws.sendEmailState(); + ws.sendSubscriptionState(); + } + return { + sendAccountStates, account, ping, }; @@ -49,10 +56,16 @@ function AccountStatus(args) { account, ping, accountPage, + sendAccountStates, } = args; if (!account) return null; + function accountClick() { + sendAccountStates(); + accountPage(); + } + return (
- +
); } diff --git a/client/src/components/stripe.buttons.jsx b/client/src/components/stripe.buttons.jsx index e660e4a9..7d15770e 100644 --- a/client/src/components/stripe.buttons.jsx +++ b/client/src/components/stripe.buttons.jsx @@ -8,7 +8,7 @@ function BitsBtn(args) { } = args; function subscribeClick() { stripe.redirectToCheckout({ - items: [{ plan: 'plan_FGmRwawcOJJ7Nv', quantity: 1 }], + items: [{ plan: 'plan_Fhl9r7UdMadjGi', quantity: 1 }], successUrl: 'http://localhost', cancelUrl: 'http://localhost', clientReferenceId: account.id, diff --git a/client/src/components/team.ctrl.jsx b/client/src/components/team.ctrl.jsx index fb45db9e..ba13edf1 100644 --- a/client/src/components/team.ctrl.jsx +++ b/client/src/components/team.ctrl.jsx @@ -6,6 +6,8 @@ const actions = require('./../actions'); const addState = connect( function receiveState(state) { const { + constructs, + teamPage, teamSelect, ws, } = state; @@ -15,18 +17,34 @@ const addState = connect( } return { + constructLength: constructs.length, sendAccountSetTeam, + teamPage, teamSelect, }; }, + + function receiveDispatch(dispatch) { + function setTeamPage(value) { + return dispatch(actions.setTeamPage(value)); + } + return { + setTeamPage, + }; + } ); function TeamCtrl(args) { const { - teamSelect, + constructLength, sendAccountSetTeam, + setTeamPage, + teamPage, + teamSelect, } = args; + const disableDecrement = teamPage === 0; + const disableIncrement = (teamPage + 1) * 6 >= constructLength; return ( ); } diff --git a/client/src/components/team.jsx b/client/src/components/team.jsx index af48f6ca..cb88b526 100644 --- a/client/src/components/team.jsx +++ b/client/src/components/team.jsx @@ -7,7 +7,7 @@ const { ConstructAvatar } = require('./construct'); const addState = connect( function receiveState(state) { - const { ws, constructs, teamSelect } = state; + const { ws, constructs, teamPage, teamSelect } = state; function sendConstructSpawn(name) { return ws.sendMtxConstructSpawn(name); @@ -15,6 +15,7 @@ const addState = connect( return { constructs, + teamPage, teamSelect, }; }, @@ -32,6 +33,7 @@ const addState = connect( function Team(args) { const { constructs, + teamPage, teamSelect, setTeam, } = args; @@ -54,8 +56,12 @@ function Team(args) { teamSelect[insert] = id; return setTeam(teamSelect); } + console.log(constructs.length); + const dispConstructs = constructs.length >= ((teamPage + 1) * 6) + ? constructs.slice(teamPage * 6, (teamPage + 1) * 6) + : constructs.slice(teamPage * 6, constructs.length); - const constructPanels = constructs.map(construct => { + const constructPanels = dispConstructs.map(construct => { const colour = teamSelect.indexOf(construct.id); const selected = colour > -1; diff --git a/client/src/events.jsx b/client/src/events.jsx index 15f9f35b..876d184b 100644 --- a/client/src/events.jsx +++ b/client/src/events.jsx @@ -3,8 +3,17 @@ const eachSeries = require('async/eachSeries'); const actions = require('./actions'); const { TIMES } = require('./constants'); const animations = require('./animations.utils'); +const { infoToast, errorToast } = require('./utils'); function registerEvents(store) { + function notify(msg) { + return infoToast(msg); + } + + function error(msg) { + return errorToast(msg); + } + function setPing(ping) { store.dispatch(actions.setPing(ping)); } @@ -18,6 +27,14 @@ function registerEvents(store) { setNav('play'); } + function setSubscription(sub) { + const { subscription } = store.getState(); + if (subscription && sub.cancel_at_period_end) { + notify('Your subscription has been cancelled. Thank you for your support.'); + } + store.dispatch(actions.setSubscription(sub)); + } + function setConstructList(constructs) { store.dispatch(actions.setConstructs(constructs)); } @@ -222,7 +239,10 @@ function registerEvents(store) { setPing, setShop, setTeam, + setSubscription, setWs, + + notify, }; } diff --git a/client/src/reducers.jsx b/client/src/reducers.jsx index 8c9beb5b..35c45a64 100644 --- a/client/src/reducers.jsx +++ b/client/src/reducers.jsx @@ -44,7 +44,10 @@ module.exports = { skip: createReducer(false, 'SET_SKIP'), shop: createReducer(false, 'SET_SHOP'), + subscription: createReducer(null, 'SET_SUBSCRIPTION'), + team: createReducer([], 'SET_TEAM'), + teamPage: createReducer(0, 'SET_TEAM_PAGE'), teamSelect: createReducer([null, null, null], 'SET_TEAM_SELECT'), vboxHighlight: createReducer([], 'SET_VBOX_HIGHLIGHT'), diff --git a/client/src/socket.jsx b/client/src/socket.jsx index 4fe78845..b8e919b9 100644 --- a/client/src/socket.jsx +++ b/client/src/socket.jsx @@ -54,6 +54,10 @@ function createSocket(events) { send(['AccountSetTeam', { ids }]); } + function sendSubscriptionEnding(ending) { + send(['SubscriptionEnding', { ending }]); + } + function sendGameState(id) { send(['GameState', { id }]); } @@ -139,6 +143,15 @@ function createSocket(events) { function sendMtxConstructSpawn() { send(['MtxConstructSpawn', {}]); } + + function sendEmailState() { + send(['EmailState', {}]); + } + + function sendSubscriptionState() { + send(['SubscriptionState', {}]); + } + // ------------- // Incoming // ------------- @@ -166,6 +179,11 @@ function createSocket(events) { events.setTeam(constructs); } + function onSubscriptionState(sub) { + // events.subscriptionState(`Subscription cancelled. Your subscription will remain active until ${exp}. Thank you for your support.`); + events.setSubscription(sub); + } + function onConstructSpawn(construct) { events.setNewConstruct(construct); } @@ -200,6 +218,7 @@ function createSocket(events) { AccountTeam: onAccountTeam, AccountInstances: onAccountInstances, AccountShop: onAccountShop, + SubscriptionState: onSubscriptionState, ConstructSpawn: onConstructSpawn, GameState: onGameState, EmailState: onEmailState, @@ -296,6 +315,7 @@ function createSocket(events) { sendAccountInstances, sendAccountSetTeam, + sendGameState, sendGameReady, sendGameSkill, @@ -315,6 +335,10 @@ function createSocket(events) { sendItemInfo, + sendEmailState, + sendSubscriptionState, + sendSubscriptionEnding, + sendMtxApply, sendMtxBuy, sendMtxConstructSpawn, diff --git a/etc/mnml.SAMPLE.env b/etc/mnml.SAMPLE.env deleted file mode 100644 index c2ee78ae..00000000 --- a/etc/mnml.SAMPLE.env +++ /dev/null @@ -1,6 +0,0 @@ -#!/bin/bash - -export MNML_USER=$(whoami) -export MNML_PG_PASSWORD=$(openssl rand -base64 16) -export MNML_PG_HOST="localhost" - diff --git a/etc/mnml/gs.SAMPLE.conf b/etc/mnml/gs.conf similarity index 72% rename from etc/mnml/gs.SAMPLE.conf rename to etc/mnml/gs.conf index 20435945..1597df41 100644 --- a/etc/mnml/gs.SAMPLE.conf +++ b/etc/mnml/gs.conf @@ -3,3 +3,6 @@ DATABASE_URL=postgres://mnml:password@somewhere/mnml MAIL_ADDRESS=machines@mnml.gg MAIL_DOMAIN=vinyl.mnml.gg MAIL_PASSWORD=mmmmmmmmmmmmmmmm + +STRIPE_SECRET=shhhhhhhhhh +STRIPE_WH_SECRET=SHHHHHHH diff --git a/ops/package.json b/ops/package.json index 2b7cd67a..1eb603d9 100755 --- a/ops/package.json +++ b/ops/package.json @@ -1,6 +1,6 @@ { "name": "mnml-ops", - "version": "0.3.0", + "version": "1.0.0", "description": "", "main": "index.js", "scripts": { diff --git a/server/Cargo.toml b/server/Cargo.toml index f66c0f87..caea8b7e 100644 --- a/server/Cargo.toml +++ b/server/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "mnml" -version = "0.3.0" +version = "1.0.0" authors = ["ntr "] [dependencies] @@ -38,4 +38,5 @@ ws = "0.8" lettre = "0.9" lettre_email = "0.9" -stripe-rust = { version = "0.10.4", features = ["webhooks"] } +stripe-rust = "0.10" +# stripe-rust = { path = "/home/ntr/code/stripe-rs" } diff --git a/server/src/events.rs b/server/src/events.rs index eb5e12d5..f37b97bd 100644 --- a/server/src/events.rs +++ b/server/src/events.rs @@ -132,7 +132,7 @@ impl Events { match self.clients.get_mut(&id) { Some(client) => { client.subs.insert(obj); - info!("client subscriptions={:?}", client.subs.len()); + info!("client={:?} subscriptions={:?}", id, client.subs.len()); Ok(()) }, None => return Err(format_err!("unknown client {:?}", id)) diff --git a/server/src/main.rs b/server/src/main.rs index b452f137..cad0c1f7 100644 --- a/server/src/main.rs +++ b/server/src/main.rs @@ -146,6 +146,8 @@ fn main() { let pg_pool = pool.clone(); let mailer = mail::listen(mail_rx); + let stripe = payments::stripe_client(); + spawn(move || http::start(http_pool, mailer)); spawn(move || warden.listen()); spawn(move || warden::upkeep_tick(warden_tick_tx)); @@ -154,5 +156,5 @@ fn main() { // the main thread becomes this ws listener let rpc_pool = pool.clone(); - rpc::start(rpc_pool, rpc_events_tx); + rpc::start(rpc_pool, rpc_events_tx, stripe); } diff --git a/server/src/payments.rs b/server/src/payments.rs index 7f02388c..f5dbd6f8 100644 --- a/server/src/payments.rs +++ b/server/src/payments.rs @@ -1,8 +1,9 @@ +use std::env; use std::io::Read; use http::State; use iron::prelude::*; -use iron::response::HttpResponse; + use iron::status; use persistent::Read as Readable; @@ -12,15 +13,16 @@ use postgres::transaction::Transaction; use failure::Error; use failure::err_msg; -use stripe::{Event, EventObject, CheckoutSession, SubscriptionStatus}; +use stripe::{Client, Event, EventObject, CheckoutSession, SubscriptionStatus, Subscription}; use http::{MnmlHttpError}; -use pg::{PgPool}; +use pg::{Db, PgPool}; use account; +use account::Account; pub fn subscription_account(tx: &mut Transaction, sub: String) -> Result { let query = " - SELECT account + SELECT account, customer, checkout, subscription FROM stripe_subscriptions WHERE subscription = $1; "; @@ -34,6 +36,69 @@ pub fn subscription_account(tx: &mut Transaction, sub: String) -> Result Result, Error> { + let query = " + SELECT account, customer, checkout, subscription + FROM stripe_subscriptions + WHERE account = $1; + "; + + let result = tx + .query(query, &[&account.id])?; + + let row = result.iter().next() + .ok_or(err_msg("user not subscribed"))?; + + let _customer: String = row.get(1); + let _checkout: String = row.get(2); + let subscription: String = row.get(3); + + let id = subscription.parse()?; + let mut params = stripe::UpdateSubscription::new(); + params.cancel_at_period_end = Some(ending); + + match stripe::Subscription::update(client, &id, params) { + Ok(s) => { + info!("subscription changed ending={:?} account={:?} subscription={:?}", ending, account, s); + Ok(Some(s)) + }, + Err(e) => { + warn!("{:?}", e); + Err(err_msg("unable to update subscription")) + } + } +} + +pub fn account_subscription(db: &Db, client: &Client, account: &Account) -> Result, Error> { + let query = " + SELECT account, customer, checkout, subscription + FROM stripe_subscriptions + WHERE account = $1; + "; + + let result = db + .query(query, &[&account.id])?; + + let row = match result.iter().next() { + Some(r) => r, + None => return Ok(None), + }; + + let _customer: String = row.get(1); + let _checkout: String = row.get(2); + let subscription: String = row.get(3); + + let id = subscription.parse()?; + + match stripe::Subscription::retrieve(client, &id, &[]) { + Ok(s) => Ok(Some(s)), + Err(e) => { + warn!("{:?}", e); + Err(err_msg("unable to retrieve subscription")) + } + } +} + // we use i64 because it is converted to BIGINT for pg // and we can losslessly pull it into u32 which is big // enough for the ballers @@ -45,7 +110,7 @@ const CREDITS_SUB_BONUS: i64 = 40; // we ensure that we store each object in pg with a link to the object // and to the account id in case of refunds #[derive(Debug,Clone,Serialize,Deserialize)] -enum StripeData { +pub enum StripeData { Customer { account: Uuid, customer: String, checkout: String }, Subscription { account: Uuid, customer: String, checkout: String, subscription: String, }, @@ -193,10 +258,31 @@ fn process_stripe_event(event: Event, pool: &PgPool) -> Result { } pub fn stripe(req: &mut Request) -> IronResult { - let state = req.get::>().unwrap(); - let event = match req.get::>() { - Ok(Some(b)) => b, - _ => return Err(IronError::from(MnmlHttpError::BadRequest)), + let webhook_secret = env::var("STRIPE_WH_SECRET").ok(); + let state= req.get::>().unwrap(); + + let event = match webhook_secret { + // prod mode + Some(s) => { + let mut payload = String::new(); + req.body.read_to_string(&mut payload) + .or(Err(IronError::from(MnmlHttpError::BadRequest)))?; + + let sig = match req.headers.get_raw("Stripe-Signature") { + Some(s) => String::from_utf8(s[0].clone()) + .or(Err(IronError::from(MnmlHttpError::BadRequest)))?, + None => return Err(IronError::from(MnmlHttpError::BadRequest)), + }; + + stripe::Webhook::construct_event(payload, sig, s) + .or(Err(IronError::from(MnmlHttpError::BadRequest)))? + }, + + // dev server + None => match req.get::>() { + Ok(Some(b)) => b, + _ => return Err(IronError::from(MnmlHttpError::BadRequest)), + }, }; match process_stripe_event(event, &state.pool) { @@ -211,6 +297,13 @@ pub fn stripe(req: &mut Request) -> IronResult { } } +pub fn stripe_client() -> Client { + let secret = env::var("STRIPE_SECRET") + .expect("STRIPE_SECRET must be set"); + + stripe::Client::new(secret) +} + #[cfg(test)] mod tests { use super::*; diff --git a/server/src/rpc.rs b/server/src/rpc.rs index eb97be9a..df7a503c 100644 --- a/server/src/rpc.rs +++ b/server/src/rpc.rs @@ -11,6 +11,8 @@ use failure::err_msg; use serde_cbor::{from_slice, to_vec}; use cookie::Cookie; +use stripe::{Client as StripeClient, Subscription}; + use crossbeam_channel::{unbounded, Sender as CbSender}; use ws::{listen, CloseCode, Message, Handler, Request, Response}; @@ -23,6 +25,7 @@ use instance::{Instance, instance_state, instance_practice, instance_ready}; use item::{Item, ItemInfoCtr, item_info}; use mtx; use mail; +use payments; use mail::Email; use pg::{Db}; use pg::{PgPool}; @@ -37,13 +40,16 @@ pub enum RpcMessage { AccountTeam(Vec), AccountInstances(Vec), AccountShop(mtx::Shop), + ConstructSpawn(Construct), - EmailState(Email), GameState(Game), ItemInfo(ItemInfoCtr), InstanceState(Instance), + EmailState(Option), + SubscriptionState(Option), + Pong(()), DevResolutions(Resolutions), @@ -75,6 +81,10 @@ enum RpcRequest { AccountConstructs {}, AccountSetTeam { ids: Vec }, + SubscriptionEnding { ending: bool }, + SubscriptionState {}, + EmailState {}, + InstanceQueue {}, InstancePractice {}, InstanceReady { instance_id: Uuid }, @@ -92,6 +102,7 @@ struct Connection { pub id: usize, pub ws: CbSender, pool: PgPool, + stripe: StripeClient, account: Option, events: CbSender, } @@ -135,13 +146,19 @@ impl Connection { let response = match v { RpcRequest::AccountState {} => - return Ok(RpcMessage::AccountState(account.clone())), + Ok(RpcMessage::AccountState(account.clone())), RpcRequest::AccountConstructs {} => Ok(RpcMessage::AccountConstructs(account::constructs(&mut tx, &account)?)), RpcRequest::AccountSetTeam { ids } => Ok(RpcMessage::AccountTeam(account::set_team(&mut tx, &account, ids)?)), + RpcRequest::EmailState {} => + Ok(RpcMessage::EmailState(mail::select_account(&db, account.id)?)), + + RpcRequest::SubscriptionState {} => + Ok(RpcMessage::SubscriptionState(payments::account_subscription(&db, &self.stripe, &account)?)), + // RpcRequest::AccountShop {} => // Ok(RpcMessage::AccountShop(mtx::account_shop(&mut tx, &account)?)), @@ -192,6 +209,9 @@ impl Connection { RpcRequest::MtxBuy { mtx } => Ok(RpcMessage::AccountShop(mtx::buy(&mut tx, account, mtx)?)), + RpcRequest::SubscriptionEnding { ending } => + Ok(RpcMessage::SubscriptionState(payments::subscription_ending(&mut tx, &self.stripe, account, ending)?)), + _ => Err(format_err!("unknown request request={:?}", request)), }; @@ -228,15 +248,6 @@ impl Handler for Connection { let db = self.pool.get().unwrap(); let mut tx = db.transaction().unwrap(); - // email state - match mail::select_account(&db, a.id).unwrap() { - Some(e) => { - self.ws.send(RpcMessage::EmailState(e.clone())).unwrap(); - self.events.send(Event::Subscribe(self.id, e.id)).unwrap(); - }, - None => (), - }; - // send account constructs let account_constructs = account::constructs(&mut tx, a).unwrap(); self.ws.send(RpcMessage::AccountConstructs(account_constructs)).unwrap(); @@ -270,8 +281,10 @@ impl Handler for Connection { // if the user queries the state of something // we tell events to push updates to them match reply { - RpcMessage::AccountState(ref v) => - self.events.send(Event::Subscribe(self.id, v.id)).unwrap(), + RpcMessage::AccountState(ref v) => { + self.account = Some(v.clone()); + self.events.send(Event::Subscribe(self.id, v.id)).unwrap() + }, RpcMessage::GameState(ref v) => self.events.send(Event::Subscribe(self.id, v.id)).unwrap(), RpcMessage::InstanceState(ref v) => @@ -332,7 +345,7 @@ impl Handler for Connection { } } -pub fn start(pool: PgPool, events_tx: CbSender) { +pub fn start(pool: PgPool, events_tx: CbSender, stripe: StripeClient) { let mut rng = thread_rng(); listen("127.0.0.1:40055", move |out| { @@ -364,6 +377,7 @@ pub fn start(pool: PgPool, events_tx: CbSender) { account: None, ws: tx, pool: pool.clone(), + stripe: stripe.clone(), events: events_tx.clone(), } }).unwrap();