mtx init
This commit is contained in:
parent
6f7c2cf571
commit
e74c820ce4
@ -463,6 +463,7 @@ header {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.stripe-btn {
|
.stripe-btn {
|
||||||
|
width: 100%;
|
||||||
padding: 0 0.5em;
|
padding: 0 0.5em;
|
||||||
background: whitesmoke;
|
background: whitesmoke;
|
||||||
color: black;
|
color: black;
|
||||||
|
|||||||
@ -11,12 +11,16 @@ function pingColour(ping) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function BitsBtn(args) {
|
function BitsBtn(args) {
|
||||||
const { stripe } = args;
|
const {
|
||||||
|
stripe,
|
||||||
|
account,
|
||||||
|
} = args;
|
||||||
function onClick(e) {
|
function onClick(e) {
|
||||||
stripe.redirectToCheckout({
|
stripe.redirectToCheckout({
|
||||||
items: [{sku: 'sku_FGjPl1YKoT241P', quantity: 1}],
|
items: [{plan: 'plan_FGmRwawcOJJ7Nv', quantity: 1}],
|
||||||
successUrl: 'http://localhost:40080/payments/success',
|
successUrl: 'http://localhost:40080/payments/success',
|
||||||
cancelUrl: 'http://localhost:40080/payments/cancel',
|
cancelUrl: 'http://localhost:40080/payments/cancel',
|
||||||
|
clientReferenceId: account.id
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@ -27,7 +31,7 @@ function BitsBtn(args) {
|
|||||||
class="stripe-btn"
|
class="stripe-btn"
|
||||||
id="checkout-button-sku_FGjPl1YKoT241P"
|
id="checkout-button-sku_FGjPl1YKoT241P"
|
||||||
role="link">
|
role="link">
|
||||||
Buy Bits
|
Subscribe
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
@ -72,7 +76,7 @@ function AccountStatus(args) {
|
|||||||
<div class="ping-text">{ping}ms</div>
|
<div class="ping-text">{ping}ms</div>
|
||||||
</div>
|
</div>
|
||||||
<Elements>
|
<Elements>
|
||||||
<StripeBitsBtn />
|
<StripeBitsBtn account={account} />
|
||||||
</Elements>
|
</Elements>
|
||||||
<button onClick={() => logout()}>Logout</button>
|
<button onClick={() => logout()}>Logout</button>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
const toast = require('izitoast');
|
const toast = require('izitoast');
|
||||||
const cbor = require('borc');
|
const cbor = require('borc');
|
||||||
|
|
||||||
const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://mnml.gg/ws' : 'ws://localhost:40000/ws/';
|
const SOCKET_URL = process.env.NODE_ENV === 'production' ? 'wss://mnml.gg/ws' : 'ws://localhost:40000/api/ws';
|
||||||
|
|
||||||
function errorToast(err) {
|
function errorToast(err) {
|
||||||
console.error(err);
|
console.error(err);
|
||||||
|
|||||||
49
ops/mnml.gg.DEV.nginx.conf
Normal file
49
ops/mnml.gg.DEV.nginx.conf
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
upstream mnml_dev {
|
||||||
|
server 0.0.0.0:41337;
|
||||||
|
}
|
||||||
|
|
||||||
|
map $http_upgrade $connection_upgrade {
|
||||||
|
default upgrade;
|
||||||
|
'' close;
|
||||||
|
}
|
||||||
|
|
||||||
|
# DEV
|
||||||
|
server {
|
||||||
|
root /home/git/mnml/client/dist/;
|
||||||
|
index index.html;
|
||||||
|
|
||||||
|
server_name dev.mnml.gg; # managed by Certbot
|
||||||
|
|
||||||
|
location / {
|
||||||
|
try_files $uri $uri/ =404;
|
||||||
|
}
|
||||||
|
|
||||||
|
listen [::]:443;
|
||||||
|
ssl on;
|
||||||
|
listen 443 ssl; # managed by Certbot
|
||||||
|
ssl_certificate /etc/letsencrypt/live/dev.mnml.gg/fullchain.pem; # managed by Certbot
|
||||||
|
ssl_certificate_key /etc/letsencrypt/live/dev.mnml.gg/privkey.pem; # managed by Certbot
|
||||||
|
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
|
||||||
|
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
|
||||||
|
|
||||||
|
location /api/ws {
|
||||||
|
proxy_pass http://mnml_dev;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Upgrade $http_upgrade;
|
||||||
|
proxy_set_header Connection $connection_upgrade;
|
||||||
|
proxy_read_timeout 600s;
|
||||||
|
}
|
||||||
|
|
||||||
|
location /api/ {
|
||||||
|
proxy_pass http://mnml_dev;
|
||||||
|
proxy_read_timeout 600s;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
# http -> https
|
||||||
|
server {
|
||||||
|
server_name dev.mnml.gg;
|
||||||
|
return 301 https://$host$request_uri;
|
||||||
|
}
|
||||||
|
|
||||||
@ -7,6 +7,7 @@ map $http_upgrade $connection_upgrade {
|
|||||||
'' close;
|
'' close;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# PRODUCTION
|
||||||
server {
|
server {
|
||||||
root /home/git/mnml/client/dist/;
|
root /home/git/mnml/client/dist/;
|
||||||
index index.html;
|
index index.html;
|
||||||
@ -1,2 +1 @@
|
|||||||
DATABASE_URL=postgres://mnml:craftbeer@localhost/mnml
|
DATABASE_URL=postgres://mnml:craftbeer@localhost/mnml
|
||||||
DEV_CORS=true
|
|
||||||
|
|||||||
@ -31,3 +31,4 @@ actix-web-actors = "1.0.0"
|
|||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
bytes = "0.4"
|
bytes = "0.4"
|
||||||
|
|
||||||
|
stripe-rust = "0.10.2"
|
||||||
|
|||||||
@ -21,23 +21,27 @@ extern crate serde_cbor;
|
|||||||
extern crate fern;
|
extern crate fern;
|
||||||
#[macro_use] extern crate log;
|
#[macro_use] extern crate log;
|
||||||
|
|
||||||
|
extern crate stripe;
|
||||||
|
|
||||||
mod account;
|
mod account;
|
||||||
mod construct;
|
mod construct;
|
||||||
|
mod effect;
|
||||||
mod game;
|
mod game;
|
||||||
mod instance;
|
mod instance;
|
||||||
mod item;
|
mod item;
|
||||||
mod mob;
|
mod mob;
|
||||||
|
mod mtx;
|
||||||
mod names;
|
mod names;
|
||||||
mod net;
|
mod net;
|
||||||
mod player;
|
mod player;
|
||||||
mod rpc;
|
|
||||||
mod pubsub;
|
mod pubsub;
|
||||||
|
mod rpc;
|
||||||
mod skill;
|
mod skill;
|
||||||
mod effect;
|
|
||||||
mod spec;
|
mod spec;
|
||||||
mod util;
|
mod util;
|
||||||
mod vbox;
|
mod vbox;
|
||||||
mod warden;
|
mod warden;
|
||||||
|
mod ws;
|
||||||
|
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use net::{start};
|
use net::{start};
|
||||||
|
|||||||
15
server/src/mtx.rs
Normal file
15
server/src/mtx.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use actix_web::{web, HttpResponse};
|
||||||
|
use stripe::{CheckoutSession};
|
||||||
|
|
||||||
|
use net::{State, MnmlError};
|
||||||
|
|
||||||
|
pub fn stripe_payment(state: web::Data<State>, body: web::Json::<CheckoutSession>) -> Result<HttpResponse, MnmlError> {
|
||||||
|
let db = state.pool.get().or(Err(MnmlError::ServerError))?;
|
||||||
|
let mut tx = db.transaction().or(Err(MnmlError::ServerError))?;
|
||||||
|
|
||||||
|
let session = body.into_inner();
|
||||||
|
|
||||||
|
info!("{:?}", session);
|
||||||
|
|
||||||
|
Ok(HttpResponse::Ok().finish())
|
||||||
|
}
|
||||||
@ -1,129 +1,31 @@
|
|||||||
use std::time::{Instant, Duration};
|
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use serde_cbor::{to_vec};
|
use actix_web::{middleware, web, App, HttpMessage, HttpRequest, HttpResponse, HttpServer};
|
||||||
|
|
||||||
use actix_web::{middleware, web, App, Error, HttpMessage, HttpRequest, HttpResponse, HttpServer};
|
|
||||||
use actix_web::middleware::cors::Cors;
|
use actix_web::middleware::cors::Cors;
|
||||||
use actix_web::error::ResponseError;
|
use actix_web::error::ResponseError;
|
||||||
use actix_web::http::{Cookie};
|
use actix_web::http::{Cookie};
|
||||||
use actix_web::cookie::{SameSite};
|
use actix_web::cookie::{SameSite};
|
||||||
use actix_web_actors::ws;
|
|
||||||
use actix::prelude::*;
|
use actix::prelude::*;
|
||||||
|
|
||||||
use r2d2::{Pool};
|
use r2d2::{Pool};
|
||||||
use r2d2::{PooledConnection};
|
use r2d2::{PooledConnection};
|
||||||
use r2d2_postgres::{TlsMode, PostgresConnectionManager};
|
use r2d2_postgres::{TlsMode, PostgresConnectionManager};
|
||||||
|
|
||||||
use rpc::{receive, RpcResult, RpcErrorResponse, AccountLoginParams, AccountCreateParams};
|
use rpc::{RpcErrorResponse, AccountLoginParams, AccountCreateParams};
|
||||||
use warden::{Warden};
|
use warden::{Warden};
|
||||||
use pubsub::PubSub;
|
use pubsub::PubSub;
|
||||||
use account::{Account, account_login, account_create, account_from_token, account_set_token};
|
use ws::{connect};
|
||||||
|
use account::{account_login, account_create, account_from_token, account_set_token};
|
||||||
|
use mtx::{stripe_payment};
|
||||||
|
|
||||||
pub type Db = PooledConnection<PostgresConnectionManager>;
|
pub type Db = PooledConnection<PostgresConnectionManager>;
|
||||||
pub type PgPool = Pool<PostgresConnectionManager>;
|
pub type PgPool = Pool<PostgresConnectionManager>;
|
||||||
|
|
||||||
const DB_POOL_SIZE: u32 = 20;
|
const DB_POOL_SIZE: u32 = 20;
|
||||||
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
|
||||||
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
|
|
||||||
|
|
||||||
pub struct MnmlSocket {
|
|
||||||
hb: Instant,
|
|
||||||
pool: PgPool,
|
|
||||||
account: Option<Account>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Actor for MnmlSocket {
|
|
||||||
type Context = ws::WebsocketContext<Self>;
|
|
||||||
|
|
||||||
// once the actor has been started this fn runs
|
|
||||||
// it starts the heartbeat interval and keepalive
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
self.hb(ctx);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handler for `ws::Message`
|
|
||||||
impl StreamHandler<ws::Message, ws::ProtocolError> for MnmlSocket {
|
|
||||||
fn started(&mut self, ctx: &mut Self::Context) {
|
|
||||||
match self.account.as_ref() {
|
|
||||||
Some(a) => {
|
|
||||||
info!("user connected {:?}", a);
|
|
||||||
let account_state = to_vec(&RpcResult::AccountState(a.clone()))
|
|
||||||
.expect("could not serialize account state");
|
|
||||||
ctx.binary(account_state)
|
|
||||||
},
|
|
||||||
None => info!("new connection"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
|
||||||
// process websocket messages
|
|
||||||
let begin = Instant::now();
|
|
||||||
debug!("msg: {:?}", msg);
|
|
||||||
match msg {
|
|
||||||
ws::Message::Ping(msg) => {
|
|
||||||
self.hb = Instant::now();
|
|
||||||
ctx.pong(&msg);
|
|
||||||
}
|
|
||||||
ws::Message::Pong(_) => {
|
|
||||||
self.hb = Instant::now();
|
|
||||||
}
|
|
||||||
ws::Message::Text(_text) => (),
|
|
||||||
ws::Message::Close(_) => {
|
|
||||||
match self.account.as_ref() {
|
|
||||||
Some(a) => info!("disconnected {:?}", a),
|
|
||||||
None => info!("disconnected"),
|
|
||||||
}
|
|
||||||
ctx.stop();
|
|
||||||
}
|
|
||||||
ws::Message::Nop => (),
|
|
||||||
ws::Message::Binary(bin) => {
|
|
||||||
let db_connection = self.pool.get().expect("unable to get db connection");
|
|
||||||
match receive(bin.to_vec(), &db_connection, ctx, begin, self.account.as_ref()) {
|
|
||||||
Ok(reply) => {
|
|
||||||
let response = to_vec(&reply)
|
|
||||||
.expect("failed to serialize response");
|
|
||||||
ctx.binary(response);
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
let response = to_vec(&RpcErrorResponse { err: e.to_string() })
|
|
||||||
.expect("failed to serialize error response");
|
|
||||||
ctx.binary(response);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl MnmlSocket {
|
|
||||||
fn new(state: web::Data<State>, account: Option<Account>) -> MnmlSocket {
|
|
||||||
// idk why this has to be cloned again
|
|
||||||
// i guess because each socket is added as a new thread?
|
|
||||||
MnmlSocket { hb: Instant::now(), pool: state.pool.clone(), account }
|
|
||||||
}
|
|
||||||
|
|
||||||
// starts the keepalive interval once actor started
|
|
||||||
fn hb(&self, ctx: &mut <MnmlSocket as Actor>::Context) {
|
|
||||||
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
|
||||||
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
|
|
||||||
info!("idle connection terminated");
|
|
||||||
|
|
||||||
// stop actor
|
|
||||||
ctx.stop();
|
|
||||||
|
|
||||||
// don't try to send a ping
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx.ping("");
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Fail, Debug)]
|
#[derive(Fail, Debug)]
|
||||||
enum MnmlError {
|
pub enum MnmlError {
|
||||||
#[fail(display="internal server error")]
|
#[fail(display="internal server error")]
|
||||||
ServerError,
|
ServerError,
|
||||||
#[fail(display="unauthorized")]
|
#[fail(display="unauthorized")]
|
||||||
@ -153,28 +55,6 @@ impl ResponseError for MnmlError {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// idk how this stuff works
|
|
||||||
// but the args extract what you need from the incoming requests
|
|
||||||
// this grabs
|
|
||||||
// the req obj itself which we need for cookies
|
|
||||||
// the application state
|
|
||||||
// and the websocket stream
|
|
||||||
fn connect(r: HttpRequest, state: web::Data<State>, stream: web::Payload) -> Result<HttpResponse, Error> {
|
|
||||||
let account: Option<Account> = match r.cookie("x-auth-token") {
|
|
||||||
Some(t) => {
|
|
||||||
let db = state.pool.get().or(Err(MnmlError::ServerError))?;
|
|
||||||
let mut tx = db.transaction().or(Err(MnmlError::ServerError))?;
|
|
||||||
match account_from_token(t.value().to_string(), &mut tx) {
|
|
||||||
Ok(a) => Some(a),
|
|
||||||
Err(_) => None,
|
|
||||||
}
|
|
||||||
},
|
|
||||||
None => None,
|
|
||||||
};
|
|
||||||
|
|
||||||
ws::start(MnmlSocket::new(state, account), &r, stream)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn login_res(token: String, secure: bool) -> HttpResponse {
|
fn login_res(token: String, secure: bool) -> HttpResponse {
|
||||||
HttpResponse::Ok()
|
HttpResponse::Ok()
|
||||||
.cookie(Cookie::build("x-auth-token", token)
|
.cookie(Cookie::build("x-auth-token", token)
|
||||||
@ -257,8 +137,8 @@ fn create_pool(url: String) -> Pool<PostgresConnectionManager> {
|
|||||||
.expect("Failed to create pool.")
|
.expect("Failed to create pool.")
|
||||||
}
|
}
|
||||||
|
|
||||||
struct State {
|
pub struct State {
|
||||||
pool: PgPool,
|
pub pool: PgPool,
|
||||||
secure: bool,
|
secure: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -274,29 +154,18 @@ pub fn start() {
|
|||||||
let pubsub_conn = pool.get().expect("could not get pubsub pg connection");
|
let pubsub_conn = pool.get().expect("could not get pubsub pg connection");
|
||||||
let pubsub_addr = Supervisor::start(move |_| PubSub::new(pubsub_conn));
|
let pubsub_addr = Supervisor::start(move |_| PubSub::new(pubsub_conn));
|
||||||
|
|
||||||
match env::var("DEV_CORS") {
|
HttpServer::new(move || App::new()
|
||||||
Ok(_) => {
|
.data(State { pool: pool.clone(), secure: false })
|
||||||
warn!("enabling dev CORS middleware");
|
.wrap(middleware::Logger::default())
|
||||||
HttpServer::new(move || App::new()
|
.wrap(Cors::new().supports_credentials())
|
||||||
.data(State { pool: pool.clone(), secure: false })
|
.service(web::resource("/api/login").route(web::post().to(login)))
|
||||||
.wrap(middleware::Logger::default())
|
.service(web::resource("/api/logout").route(web::post().to(logout)))
|
||||||
.wrap(Cors::new().supports_credentials())
|
.service(web::resource("/api/register").route(web::post().to(register)))
|
||||||
.service(web::resource("/login").route(web::post().to(login)))
|
|
||||||
.service(web::resource("/logout").route(web::post().to(logout)))
|
.service(web::resource("/api/payments/stripe").route(web::post().to(stripe_payment)))
|
||||||
.service(web::resource("/register").route(web::post().to(register)))
|
.service(web::resource("/api/payments/crypto").route(web::post().to(stripe_payment)))
|
||||||
.service(web::resource("/ws/").route(web::get().to(connect))))
|
|
||||||
.bind("127.0.0.1:40000").expect("could not bind to port")
|
.service(web::resource("/api/ws/").route(web::get().to(connect))))
|
||||||
.run().expect("could not start http server")
|
.bind("127.0.0.1:40000").expect("could not bind to port")
|
||||||
},
|
.run().expect("could not start http server");
|
||||||
Err(_) =>
|
|
||||||
HttpServer::new(move || App::new()
|
|
||||||
.data(State { pool: pool.clone(), secure: true })
|
|
||||||
.wrap(middleware::Logger::default())
|
|
||||||
.service(web::resource("/login").route(web::post().to(login)))
|
|
||||||
.service(web::resource("/logout").route(web::post().to(logout)))
|
|
||||||
.service(web::resource("/register").route(web::post().to(register)))
|
|
||||||
.service(web::resource("/ws/").route(web::get().to(connect))))
|
|
||||||
.bind("127.0.0.1:40000").expect("could not bind to port")
|
|
||||||
.run().expect("could not start http server"),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,8 @@ use uuid::Uuid;
|
|||||||
use failure::Error;
|
use failure::Error;
|
||||||
use failure::err_msg;
|
use failure::err_msg;
|
||||||
|
|
||||||
use net::{Db, MnmlSocket};
|
use net::{Db};
|
||||||
|
use ws::{MnmlSocket};
|
||||||
use construct::{Construct, construct_spawn, construct_delete};
|
use construct::{Construct, construct_spawn, construct_delete};
|
||||||
use game::{Game, game_state, game_skill, game_ready};
|
use game::{Game, game_state, game_skill, game_ready};
|
||||||
use account::{Account, account_constructs, account_instances};
|
use account::{Account, account_constructs, account_instances};
|
||||||
|
|||||||
131
server/src/ws.rs
Normal file
131
server/src/ws.rs
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
use std::time::{Instant, Duration};
|
||||||
|
|
||||||
|
use actix_web::{web, Error, HttpMessage, HttpRequest, HttpResponse};
|
||||||
|
use actix_web_actors::ws;
|
||||||
|
use actix::prelude::*;
|
||||||
|
|
||||||
|
use account::{Account, account_from_token};
|
||||||
|
use serde_cbor::{to_vec};
|
||||||
|
use net::{PgPool, MnmlError, State};
|
||||||
|
|
||||||
|
use rpc::{receive, RpcResult, RpcErrorResponse};
|
||||||
|
|
||||||
|
const HEARTBEAT_INTERVAL: Duration = Duration::from_secs(5);
|
||||||
|
const CLIENT_TIMEOUT: Duration = Duration::from_secs(10);
|
||||||
|
|
||||||
|
pub struct MnmlSocket {
|
||||||
|
hb: Instant,
|
||||||
|
pool: PgPool,
|
||||||
|
account: Option<Account>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Actor for MnmlSocket {
|
||||||
|
type Context = ws::WebsocketContext<Self>;
|
||||||
|
|
||||||
|
// once the actor has been started this fn runs
|
||||||
|
// it starts the heartbeat interval and keepalive
|
||||||
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
|
self.hb(ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Handler for `ws::Message`
|
||||||
|
impl StreamHandler<ws::Message, ws::ProtocolError> for MnmlSocket {
|
||||||
|
fn started(&mut self, ctx: &mut Self::Context) {
|
||||||
|
match self.account.as_ref() {
|
||||||
|
Some(a) => {
|
||||||
|
info!("user connected {:?}", a);
|
||||||
|
let account_state = to_vec(&RpcResult::AccountState(a.clone()))
|
||||||
|
.expect("could not serialize account state");
|
||||||
|
ctx.binary(account_state)
|
||||||
|
},
|
||||||
|
None => info!("new connection"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle(&mut self, msg: ws::Message, ctx: &mut Self::Context) {
|
||||||
|
// process websocket messages
|
||||||
|
let begin = Instant::now();
|
||||||
|
debug!("msg: {:?}", msg);
|
||||||
|
match msg {
|
||||||
|
ws::Message::Ping(msg) => {
|
||||||
|
self.hb = Instant::now();
|
||||||
|
ctx.pong(&msg);
|
||||||
|
}
|
||||||
|
ws::Message::Pong(_) => {
|
||||||
|
self.hb = Instant::now();
|
||||||
|
}
|
||||||
|
ws::Message::Text(_text) => (),
|
||||||
|
ws::Message::Close(_) => {
|
||||||
|
match self.account.as_ref() {
|
||||||
|
Some(a) => info!("disconnected {:?}", a),
|
||||||
|
None => info!("disconnected"),
|
||||||
|
}
|
||||||
|
ctx.stop();
|
||||||
|
}
|
||||||
|
ws::Message::Nop => (),
|
||||||
|
ws::Message::Binary(bin) => {
|
||||||
|
let db_connection = self.pool.get().expect("unable to get db connection");
|
||||||
|
match receive(bin.to_vec(), &db_connection, ctx, begin, self.account.as_ref()) {
|
||||||
|
Ok(reply) => {
|
||||||
|
let response = to_vec(&reply)
|
||||||
|
.expect("failed to serialize response");
|
||||||
|
ctx.binary(response);
|
||||||
|
},
|
||||||
|
Err(e) => {
|
||||||
|
let response = to_vec(&RpcErrorResponse { err: e.to_string() })
|
||||||
|
.expect("failed to serialize error response");
|
||||||
|
ctx.binary(response);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MnmlSocket {
|
||||||
|
fn new(state: web::Data<State>, account: Option<Account>) -> MnmlSocket {
|
||||||
|
// idk why this has to be cloned again
|
||||||
|
// i guess because each socket is added as a new thread?
|
||||||
|
MnmlSocket { hb: Instant::now(), pool: state.pool.clone(), account }
|
||||||
|
}
|
||||||
|
|
||||||
|
// starts the keepalive interval once actor started
|
||||||
|
fn hb(&self, ctx: &mut <MnmlSocket as Actor>::Context) {
|
||||||
|
ctx.run_interval(HEARTBEAT_INTERVAL, |act, ctx| {
|
||||||
|
if Instant::now().duration_since(act.hb) > CLIENT_TIMEOUT {
|
||||||
|
info!("idle connection terminated");
|
||||||
|
|
||||||
|
// stop actor
|
||||||
|
ctx.stop();
|
||||||
|
|
||||||
|
// don't try to send a ping
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ctx.ping("");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// idk how this stuff works
|
||||||
|
// but the args extract what you need from the incoming requests
|
||||||
|
// this grabs
|
||||||
|
// the req obj itself which we need for cookies
|
||||||
|
// the application state
|
||||||
|
// and the websocket stream
|
||||||
|
pub fn connect(r: HttpRequest, state: web::Data<State>, stream: web::Payload) -> Result<HttpResponse, Error> {
|
||||||
|
let account: Option<Account> = match r.cookie("x-auth-token") {
|
||||||
|
Some(t) => {
|
||||||
|
let db = state.pool.get().or(Err(MnmlError::ServerError))?;
|
||||||
|
let mut tx = db.transaction().or(Err(MnmlError::ServerError))?;
|
||||||
|
match account_from_token(t.value().to_string(), &mut tx) {
|
||||||
|
Ok(a) => Some(a),
|
||||||
|
Err(_) => None,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
None => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
ws::start(MnmlSocket::new(state, account), &r, stream)
|
||||||
|
}
|
||||||
101
server/test/subscription.json
Normal file
101
server/test/subscription.json
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"object": {
|
||||||
|
"id": "sub_FGmgBxTVX2xM7H",
|
||||||
|
"object": "subscription",
|
||||||
|
"application_fee_percent": null,
|
||||||
|
"billing": "charge_automatically",
|
||||||
|
"billing_cycle_anchor": 1560755931,
|
||||||
|
"billing_thresholds": null,
|
||||||
|
"cancel_at": null,
|
||||||
|
"cancel_at_period_end": false,
|
||||||
|
"canceled_at": null,
|
||||||
|
"collection_method": "charge_automatically",
|
||||||
|
"created": 1560755931,
|
||||||
|
"current_period_end": 1563347931,
|
||||||
|
"current_period_start": 1560755931,
|
||||||
|
"customer": "cus_FGmgIHDimD5Tei",
|
||||||
|
"days_until_due": null,
|
||||||
|
"default_payment_method": "pm_1EmF78Gf8y65MteUQBHQU1po",
|
||||||
|
"default_source": null,
|
||||||
|
"default_tax_rates": [
|
||||||
|
],
|
||||||
|
"discount": null,
|
||||||
|
"ended_at": null,
|
||||||
|
"items": {
|
||||||
|
"object": "list",
|
||||||
|
"data": [
|
||||||
|
{
|
||||||
|
"id": "si_FGmg6F7DZAyU2Y",
|
||||||
|
"object": "subscription_item",
|
||||||
|
"billing_thresholds": null,
|
||||||
|
"created": 1560755932,
|
||||||
|
"metadata": {
|
||||||
|
},
|
||||||
|
"plan": {
|
||||||
|
"id": "plan_FGmRwawcOJJ7Nv",
|
||||||
|
"object": "plan",
|
||||||
|
"active": true,
|
||||||
|
"aggregate_usage": null,
|
||||||
|
"amount": 1000,
|
||||||
|
"billing_scheme": "per_unit",
|
||||||
|
"created": 1560755040,
|
||||||
|
"currency": "aud",
|
||||||
|
"interval": "month",
|
||||||
|
"interval_count": 1,
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
},
|
||||||
|
"nickname": "basic",
|
||||||
|
"product": "prod_FGmRFYmB700pM5",
|
||||||
|
"tiers": null,
|
||||||
|
"tiers_mode": null,
|
||||||
|
"transform_usage": null,
|
||||||
|
"trial_period_days": null,
|
||||||
|
"usage_type": "licensed"
|
||||||
|
},
|
||||||
|
"quantity": 1,
|
||||||
|
"subscription": "sub_FGmgBxTVX2xM7H",
|
||||||
|
"tax_rates": [
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"has_more": false,
|
||||||
|
"total_count": 1,
|
||||||
|
"url": "/v1/subscription_items?subscription=sub_FGmgBxTVX2xM7H"
|
||||||
|
},
|
||||||
|
"latest_invoice": "in_1EmF7AGf8y65MteUvH8MCela",
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
},
|
||||||
|
"plan": {
|
||||||
|
"id": "plan_FGmRwawcOJJ7Nv",
|
||||||
|
"object": "plan",
|
||||||
|
"active": true,
|
||||||
|
"aggregate_usage": null,
|
||||||
|
"amount": 1000,
|
||||||
|
"billing_scheme": "per_unit",
|
||||||
|
"created": 1560755040,
|
||||||
|
"currency": "aud",
|
||||||
|
"interval": "month",
|
||||||
|
"interval_count": 1,
|
||||||
|
"livemode": false,
|
||||||
|
"metadata": {
|
||||||
|
},
|
||||||
|
"nickname": "basic",
|
||||||
|
"product": "prod_FGmRFYmB700pM5",
|
||||||
|
"tiers": null,
|
||||||
|
"tiers_mode": null,
|
||||||
|
"transform_usage": null,
|
||||||
|
"trial_period_days": null,
|
||||||
|
"usage_type": "licensed"
|
||||||
|
},
|
||||||
|
"quantity": 1,
|
||||||
|
"schedule": null,
|
||||||
|
"start": 1560755931,
|
||||||
|
"start_date": 1560755931,
|
||||||
|
"status": "active",
|
||||||
|
"tax_percent": null,
|
||||||
|
"trial_end": null,
|
||||||
|
"trial_start": null
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
x
Reference in New Issue
Block a user