Initial commit
Basic POST and GET callback have been implemented
This commit is contained in:
1
.env
Normal file
1
.env
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DATABASE_URL=postgres://postgres:mypassword@localhost/callmeback
|
||||||
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
/target
|
||||||
|
**/*.rs.bk
|
||||||
2008
Cargo.lock
generated
Normal file
2008
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
24
Cargo.toml
Normal file
24
Cargo.toml
Normal 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
5
diesel.toml
Normal 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
0
migrations/.gitkeep
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal file
6
migrations/00000000000000_diesel_initial_setup/down.sql
Normal 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();
|
||||||
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal file
36
migrations/00000000000000_diesel_initial_setup/up.sql
Normal 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;
|
||||||
1
migrations/2019-02-10-163625_callback/down.sql
Normal file
1
migrations/2019-02-10-163625_callback/down.sql
Normal file
@@ -0,0 +1 @@
|
|||||||
|
DROP TABLE callbacks
|
||||||
5
migrations/2019-02-10-163625_callback/up.sql
Normal file
5
migrations/2019-02-10-163625_callback/up.sql
Normal 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
39
src/bin/callmeback.rs
Normal 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
50
src/controller.rs
Normal 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
76
src/db.rs
Normal 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
41
src/lib.rs
Normal 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
16
src/models.rs
Normal 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
7
src/schema.rs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
table! {
|
||||||
|
callbacks (id) {
|
||||||
|
id -> Int4,
|
||||||
|
url -> Varchar,
|
||||||
|
scheduled_date -> Timestamptz,
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user