initial commit
This commit is contained in:
commit
2178b1f833
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
/target
|
||||
.env
|
||||
database.db
|
1147
Cargo.lock
generated
Normal file
1147
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
14
Cargo.toml
Normal file
14
Cargo.toml
Normal file
|
@ -0,0 +1,14 @@
|
|||
[package]
|
||||
name = "sfrs"
|
||||
version = "0.1.0"
|
||||
authors = ["Peter Cai <peter@typeblog.net>"]
|
||||
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"] }
|
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
1
migrations/2020-02-20-085623_create_user/down.sql
Normal file
1
migrations/2020-02-20-085623_create_user/down.sql
Normal file
|
@ -0,0 +1 @@
|
|||
DROP TABLE users
|
8
migrations/2020-02-20-085623_create_user/up.sql
Normal file
8
migrations/2020-02-20-085623_create_user/up.sql
Normal file
|
@ -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
|
||||
)
|
49
src/api.rs
Normal file
49
src/api.rs
Normal file
|
@ -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])
|
||||
}
|
||||
}
|
88
src/main.rs
Normal file
88
src/main.rs
Normal file
|
@ -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();
|
||||
}
|
10
src/schema.rs
Normal file
10
src/schema.rs
Normal file
|
@ -0,0 +1,10 @@
|
|||
table! {
|
||||
users (id) {
|
||||
id -> Integer,
|
||||
email -> Text,
|
||||
password -> Text,
|
||||
pw_cost -> Text,
|
||||
pw_nonce -> Text,
|
||||
version -> Text,
|
||||
}
|
||||
}
|
53
src/user.rs
Normal file
53
src/user.rs
Normal file
|
@ -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 a new issue