commit
2178b1f833
12 changed files with 1378 additions and 0 deletions
@ -0,0 +1,14 @@
|
||||
[package] |
||||
name = "sfrs" |
||||
version = "0.1.0" |
||||
authors = ["Peter Cai <[email protected]>"] |
||||
edition = "2018" |
||||
|
||||
[dependencies] |
||||
rocket = "0.4.2" |
||||
rocket_contrib = { version = "0.4.2", features = ["diesel_sqlite_pool"] } |
||||
jwt = "0.4.0" |
||||
diesel = { version = "1.4.3", features = ["sqlite"] } |
||||
diesel_migrations = "1.4.0" |
||||
dotenv = "0.9.0" |
||||
serde = { version = "1.0.104", features = ["derive"] } |
@ -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,0 +1 @@
|
||||
DROP TABLE users |
@ -0,0 +1,8 @@
|
||||
CREATE TABLE users ( |
||||
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, |
||||
email VARCHAR NOT NULL, |
||||
password VARCHAR NOT NULL, |
||||
pw_cost VARCHAR NOT NULL, |
||||
pw_nonce VARCHAR NOT NULL, |
||||
version VARCHAR NOT NULL |
||||
) |
@ -0,0 +1,49 @@
|
||||
use crate::DbConn; |
||||
use crate::user; |
||||
use rocket::http::Status; |
||||
use rocket::response::status::Custom; |
||||
use rocket_contrib::json::Json; |
||||
use serde::Serialize; |
||||
use std::vec::Vec; |
||||
|
||||
pub fn routes() -> impl Into<Vec<rocket::Route>> { |
||||
routes![auth] |
||||
} |
||||
|
||||
#[derive(Serialize)] |
||||
#[serde(untagged)] |
||||
enum Response<T: Serialize> { |
||||
Error { |
||||
errors: Vec<String> |
||||
}, |
||||
Success(T) |
||||
} |
||||
|
||||
// Some shorthands
|
||||
type JsonResp<T> = Json<Response<T>>; |
||||
|
||||
fn success_resp<T: Serialize>(resp: T) -> Custom<JsonResp<T>> { |
||||
Custom(Status::Ok, Json(Response::Success(resp))) |
||||
} |
||||
|
||||
fn error_resp<T: Serialize>(status: Status, errors: Vec<String>) -> Custom<JsonResp<T>> { |
||||
Custom(status, Json(Response::Error { |
||||
errors |
||||
})) |
||||
} |
||||
|
||||
#[derive(Serialize)] |
||||
struct AuthResult { |
||||
token: String |
||||
} |
||||
|
||||
#[post("/auth", format = "json", data = "<new_user>")] |
||||
fn auth(db: DbConn, new_user: Json<user::NewUser>) -> Custom<JsonResp<AuthResult>> { |
||||
match user::User::create(&db.0, &new_user) { |
||||
Ok(_) => success_resp(AuthResult { |
||||
token: "aaaa".to_string() |
||||
}), |
||||
Err(user::UserOpError(e)) => |
||||
error_resp(Status::InternalServerError, vec![e]) |
||||
} |
||||
} |
@ -0,0 +1,88 @@
|
||||
#![feature(proc_macro_hygiene, decl_macro)] |
||||
|
||||
#[macro_use] |
||||
extern crate rocket; |
||||
#[macro_use] |
||||
extern crate rocket_contrib; |
||||
#[macro_use] |
||||
extern crate diesel; |
||||
#[macro_use] |
||||
extern crate diesel_migrations; |
||||
extern crate dotenv; |
||||
#[macro_use] |
||||
extern crate serde; |
||||
|
||||
mod schema; |
||||
mod api; |
||||
mod user; |
||||
|
||||
use diesel::prelude::*; |
||||
use diesel::sqlite::SqliteConnection; |
||||
use dotenv::dotenv; |
||||
use rocket::Rocket; |
||||
use rocket::config::{Config, Environment, Value}; |
||||
use rocket::fairing::AdHoc; |
||||
use std::collections::HashMap; |
||||
use std::env; |
||||
|
||||
embed_migrations!(); |
||||
|
||||
#[database("db")] |
||||
pub struct DbConn(SqliteConnection); |
||||
|
||||
#[get("/")] |
||||
fn index() -> &'static str { |
||||
"Hello, world!" |
||||
} |
||||
|
||||
fn db_path() -> String { |
||||
env::var("DATABASE_URL") |
||||
.expect("DATABASE_URL must be set") |
||||
} |
||||
|
||||
fn db_config() -> HashMap<&'static str, Value> { |
||||
let mut database_config = HashMap::new(); |
||||
let mut databases = HashMap::new(); |
||||
|
||||
database_config.insert("url", Value::from(db_path())); |
||||
databases.insert("db", Value::from(database_config)); |
||||
|
||||
return databases; |
||||
} |
||||
|
||||
fn get_environment() -> Environment { |
||||
let v = env::var("SFRS_ENV").unwrap_or("development".to_string()); |
||||
|
||||
if v == "development" { |
||||
Environment::Development |
||||
} else { |
||||
Environment::Production |
||||
} |
||||
} |
||||
|
||||
fn build_config() -> Config { |
||||
Config::build(get_environment()) |
||||
.extra("databases", db_config()) |
||||
.finalize() |
||||
.unwrap() |
||||
} |
||||
|
||||
fn run_db_migrations(rocket: Rocket) -> Result<Rocket, Rocket> { |
||||
let db = DbConn::get_one(&rocket).expect("Could not connect to Database"); |
||||
match embedded_migrations::run(&*db) { |
||||
Ok(()) => Ok(rocket), |
||||
Err(e) => { |
||||
// We should not do anything if database failed to migrate
|
||||
panic!("Failed to run database migrations: {:?}", e); |
||||
} |
||||
} |
||||
} |
||||
|
||||
fn main() { |
||||
dotenv().ok(); |
||||
rocket::custom(build_config()) |
||||
.attach(DbConn::fairing()) |
||||
.attach(AdHoc::on_attach("Database Migrations", run_db_migrations)) |
||||
.mount("/", api::routes()) |
||||
.launch(); |
||||
} |
@ -0,0 +1,10 @@
|
||||
table! { |
||||
users (id) { |
||||
id -> Integer, |
||||
email -> Text, |
||||
password -> Text, |
||||
pw_cost -> Text, |
||||
pw_nonce -> Text, |
||||
version -> Text, |
||||
} |
||||
} |
@ -0,0 +1,53 @@
|
||||
use crate::schema::users; |
||||
use crate::schema::users::dsl::*; |
||||
use diesel::prelude::*; |
||||
use diesel::sqlite::SqliteConnection; |
||||
use serde::Deserialize; |
||||
|
||||
#[derive(Debug)] |
||||
pub struct UserOpError(pub String); |
||||
|
||||
#[derive(Queryable, Debug)] |
||||
pub struct User { |
||||
pub id: i32, |
||||
pub email: String, |
||||
pub password: String, |
||||
pub pw_cost: String, |
||||
pub pw_nonce: String, |
||||
pub version: String |
||||
} |
||||
|
||||
#[derive(Insertable, Deserialize)] |
||||
#[table_name="users"] |
||||
pub struct NewUser { |
||||
pub email: String, |
||||
pub password: String, |
||||
pub pw_cost: String, |
||||
pub pw_nonce: String, |
||||
pub version: String |
||||
} |
||||
|
||||
impl User { |
||||
pub fn create(db: &SqliteConnection, new_user: &NewUser) -> Result<(), UserOpError> { |
||||
match Self::find_user_by_email(db, &new_user.email) { |
||||
Ok(_) => Err(UserOpError("User already registered".to_string())), |
||||
Err(_) => diesel::insert_into(users::table) |
||||
.values(new_user) |
||||
.execute(db) |
||||
.map(|_| ()) |
||||
.map_err(|_| UserOpError("Database error".to_string())) |
||||
} |
||||
} |
||||
|
||||
pub fn find_user_by_email(db: &SqliteConnection, user_email: &str) -> Result<User, UserOpError> { |
||||
let mut results = users.filter(email.eq(user_email)) |
||||
.limit(1) |
||||
.load::<User>(db) |
||||
.map_err(|_| UserOpError("Database error".to_string()))?; |
||||
if results.is_empty() { |
||||
Result::Err(UserOpError("No matching user found".to_string())) |
||||
} else { |
||||
Result::Ok(results.remove(0)) // Take ownership, kill the stupid Vec
|
||||
} |
||||
} |
||||
} |
Loading…
Reference in new issue