initial commit

This commit is contained in:
Peter Cai 2020-02-20 20:02:09 +08:00
commit 2178b1f833
No known key found for this signature in database
GPG Key ID: 71F5FB4E4F3FD54F
12 changed files with 1378 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
/target
.env
database.db

1147
Cargo.lock generated Normal file

File diff suppressed because it is too large Load Diff

14
Cargo.toml Normal file
View 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"] }

0
db/.keep Normal file
View File

5
diesel.toml Normal file
View 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
View File

View File

@ -0,0 +1 @@
DROP TABLE users

View 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
View 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
View 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
View 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
View 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
}
}
}