Initial commit

Basic POST and GET callback have been implemented
This commit is contained in:
2019-03-03 14:05:50 +01:00
commit d3bac73679
16 changed files with 2317 additions and 0 deletions

1
.env Normal file
View File

@@ -0,0 +1 @@
DATABASE_URL=postgres://postgres:mypassword@localhost/callmeback

2
.gitignore vendored Normal file
View File

@@ -0,0 +1,2 @@
/target
**/*.rs.bk

2008
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

24
Cargo.toml Normal file
View File

@@ -0,0 +1,24 @@
[package]
name = "callmeback"
version = "0.1.0"
authors = ["David Rousselie <david@rousselie.name>"]
edition = "2018"
[[bin]]
name = "callmeback"
doc = false
[dependencies]
actix = "0.7"
actix-web = "0.7"
listenfd = "0.3" # Run app using `systemfd --no-pid -s http::3000 -- cargo watch -x run`
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
actix-web-requestid = "0.1"
env_logger = "0.6"
diesel = { version = "1.4", features = ["postgres", "chrono", "r2d2"] }
dotenv = "0.13"
chrono = { version = "0.4", features = ["serde"] }
futures = "0.1"
r2d2 = "0.8"

5
diesel.toml Normal file
View File

@@ -0,0 +1,5 @@
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli
[print_schema]
file = "src/schema.rs"

0
migrations/.gitkeep Normal file
View File

View File

@@ -0,0 +1,6 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();

View File

@@ -0,0 +1,36 @@
-- This file was automatically created by Diesel to setup helper functions
-- and other internal bookkeeping. This file is safe to edit, any future
-- changes will be added to existing projects as new migrations.
-- Sets up a trigger for the given table to automatically set a column called
-- `updated_at` whenever the row is modified (unless `updated_at` was included
-- in the modified columns)
--
-- # Example
--
-- ```sql
-- CREATE TABLE users (id SERIAL PRIMARY KEY, updated_at TIMESTAMP NOT NULL DEFAULT NOW());
--
-- SELECT diesel_manage_updated_at('users');
-- ```
CREATE OR REPLACE FUNCTION diesel_manage_updated_at(_tbl regclass) RETURNS VOID AS $$
BEGIN
EXECUTE format('CREATE TRIGGER set_updated_at BEFORE UPDATE ON %s
FOR EACH ROW EXECUTE PROCEDURE diesel_set_updated_at()', _tbl);
END;
$$ LANGUAGE plpgsql;
CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$
BEGIN
IF (
NEW IS DISTINCT FROM OLD AND
NEW.updated_at IS NOT DISTINCT FROM OLD.updated_at
) THEN
NEW.updated_at := current_timestamp;
END IF;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;

View File

@@ -0,0 +1 @@
DROP TABLE callbacks

View File

@@ -0,0 +1,5 @@
CREATE TABLE callbacks (
id SERIAL PRIMARY KEY,
url VARCHAR NOT NULL,
scheduled_date TIMESTAMP WITH TIME ZONE NOT NULL
)

39
src/bin/callmeback.rs Normal file
View File

@@ -0,0 +1,39 @@
//extern crate callmeback;
extern crate dotenv;
extern crate env_logger;
extern crate listenfd;
use actix::prelude::*;
use actix_web::{server::HttpServer, App};
use callmeback::db::DbExecutor;
use dotenv::dotenv;
use listenfd::ListenFd;
use std::env;
pub fn main() {
::std::env::set_var("RUST_LOG", "actix_web=info");
env_logger::init();
dotenv().ok();
let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set");
let mut listenfd = ListenFd::from_env();
let sys = actix::System::new("callmeback");
let addr = SyncArbiter::start(3, move || DbExecutor::new(&database_url));
let mut server = HttpServer::new(move || {
callmeback::configure_app(App::with_state(callmeback::AppState { db: addr.clone() }))
})
.keep_alive(60)
.shutdown_timeout(60);
server = if let Some(l) = listenfd.take_tcp_listener(0).unwrap() {
server.listen(l)
} else {
server.bind("127.0.0.1:3000").unwrap()
};
server.start();
let _ = sys.run();
}

50
src/controller.rs Normal file
View File

@@ -0,0 +1,50 @@
pub mod callback {
use super::super::db::callback::{CreateCallback, GetCallback};
use super::super::AppState;
use actix_web::{
error, AsyncResponder, Error, FutureResponse, HttpResponse, Json, Path, State,
};
use chrono::prelude::*;
use futures::Future;
#[derive(Deserialize)]
pub struct CallbackCall {
pub url: String,
}
pub fn create(
(params, callback, state): (Path<(String,)>, Json<CallbackCall>, State<AppState>),
) -> impl Future<Item = HttpResponse, Error = Error> {
let date_param = params
.0
.parse::<DateTime<Utc>>()
.map_err(|_e| error::ErrorBadRequest("Bad date"))
.unwrap();
state
.db
.send(CreateCallback {
url: callback.url.to_owned(),
scheduled_date: date_param,
})
.from_err()
.and_then(|res| match res {
Ok(callback) => Ok(HttpResponse::Ok().json(callback)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
}
pub fn get((id, state): (Path<i32>, State<AppState>)) -> FutureResponse<HttpResponse> {
state
.db
.send(GetCallback {
id: id.into_inner(),
})
.from_err()
.and_then(|res| match res {
Ok(callback) => Ok(HttpResponse::Ok().json(callback)),
Err(_) => Ok(HttpResponse::InternalServerError().into()),
})
.responder()
}
}

76
src/db.rs Normal file
View File

@@ -0,0 +1,76 @@
use actix::prelude::*;
use diesel::pg::PgConnection;
use diesel::prelude::*;
pub struct DbExecutor(PgConnection);
impl Actor for DbExecutor {
type Context = SyncContext<Self>;
}
impl DbExecutor {
pub fn new(database_url: &str) -> Self {
DbExecutor(
PgConnection::establish(database_url)
.unwrap_or_else(|_| panic!("Error connecting to {}", database_url)),
)
}
}
pub mod callback {
use super::super::db::DbExecutor;
use super::super::models;
use super::super::schema::*;
use actix::prelude::*;
use chrono::prelude::*;
use diesel::prelude::*;
use actix_web::Error;
pub struct CreateCallback {
pub url: String,
pub scheduled_date: DateTime<Utc>,
}
impl Message for CreateCallback {
type Result = Result<models::Callback, Error>;
}
impl Handler<CreateCallback> for DbExecutor {
type Result = Result<models::Callback, Error>;
fn handle(&mut self, msg: CreateCallback, _: &mut Self::Context) -> Self::Result {
let new_callback = models::NewCallback {
url: &msg.url,
scheduled_date: &msg.scheduled_date,
};
Ok(diesel::insert_into(callbacks::table)
.values(&new_callback)
.get_result(&self.0)
.expect("Error inserting callback"))
}
}
pub struct GetCallback {
pub id: i32,
}
impl Message for GetCallback {
type Result = Result<models::Callback, Error>;
}
impl Handler<GetCallback> for DbExecutor {
type Result = Result<models::Callback, Error>;
fn handle(&mut self, msg: GetCallback, _: &mut Self::Context) -> Self::Result {
Ok(callbacks::table
.filter(callbacks::id.eq(msg.id))
.limit(1)
.load::<models::Callback>(&self.0)
.expect("Error loading callbacks")
.pop()
.unwrap())
}
}
}

41
src/lib.rs Normal file
View File

@@ -0,0 +1,41 @@
#[macro_use]
extern crate diesel;
extern crate actix;
extern crate actix_web;
extern crate actix_web_requestid;
extern crate serde;
extern crate serde_json;
#[macro_use]
extern crate serde_derive;
extern crate chrono;
mod controller;
pub mod db;
mod models;
mod schema;
use self::db::DbExecutor;
use actix::prelude::*;
use actix_web::middleware::Logger;
use actix_web::{http, App};
use actix_web_requestid::RequestIDHeader;
pub struct AppState {
pub db: Addr<DbExecutor>,
}
pub fn configure_app(app: App<AppState>) -> App<AppState> {
app.middleware(RequestIDHeader)
.middleware(Logger::default())
.resource("/{datetime}", |r| {
r.method(http::Method::POST).with_async_config(
controller::callback::create,
|(json_cfg,)| {
json_cfg.1.limit(4096);
},
);
})
.resource("/callback/{id}", |r| {
r.method(http::Method::GET).with(controller::callback::get)
})
}

16
src/models.rs Normal file
View File

@@ -0,0 +1,16 @@
use super::schema::callbacks;
use chrono::prelude::*;
#[derive(Queryable, Serialize)]
pub struct Callback {
id: i32,
url: String,
scheduled_date: DateTime<Utc>,
}
#[derive(Insertable)]
#[table_name = "callbacks"]
pub struct NewCallback<'a> {
pub url: &'a str,
pub scheduled_date: &'a DateTime<Utc>,
}

7
src/schema.rs Normal file
View File

@@ -0,0 +1,7 @@
table! {
callbacks (id) {
id -> Int4,
url -> Varchar,
scheduled_date -> Timestamptz,
}
}