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