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