master 76de5623d54f cached
32 files
38.4 KB
9.8k tokens
54 symbols
1 requests
Download .txt
Repository: svenstaro/rust-web-boilerplate
Branch: master
Commit: 76de5623d54f
Files: 32
Total size: 38.4 KB

Directory structure:
gitextract_gpb2a5y3/

├── .github/
│   └── dependabot.yml
├── .gitignore
├── .travis.yml
├── Cargo.toml
├── LICENSE
├── README.md
├── diesel.toml
├── migrations/
│   ├── .gitkeep
│   ├── 00000000000000_diesel_initial_setup/
│   │   ├── down.sql
│   │   └── up.sql
│   └── 20170211131857_create_initial_db/
│       ├── down.sql
│       └── up.sql
├── reset.sh
├── src/
│   ├── api/
│   │   ├── auth.rs
│   │   ├── hello.rs
│   │   └── mod.rs
│   ├── bin/
│   │   └── runner.rs
│   ├── config.rs
│   ├── database.rs
│   ├── handlers.rs
│   ├── lib.rs
│   ├── models/
│   │   ├── mod.rs
│   │   └── user.rs
│   ├── responses.rs
│   ├── schema.rs
│   └── validation/
│       ├── mod.rs
│       └── user.rs
├── tests/
│   ├── common/
│   │   └── mod.rs
│   ├── factories/
│   │   └── mod.rs
│   ├── test_api_auth.rs
│   └── test_api_hello.rs
└── watch.sh

================================================
FILE CONTENTS
================================================

================================================
FILE: .github/dependabot.yml
================================================
version: 2
updates:
- package-ecosystem: cargo
  directory: "/"
  schedule:
    interval: daily
    time: "04:00"
  open-pull-requests-limit: 10


================================================
FILE: .gitignore
================================================
target
Cargo.lock
.env
.idea

================================================
FILE: .travis.yml
================================================
language: rust

rust:
  - stable
  - beta
  - nightly

env:
  - DATABASE_NAME=boilerplateapp DATABASE_URL=postgres://localhost/boilerplateapp

services:
  - postgresql

matrix:
  allow_failures:
    - rust: stable
    - rust: beta

before_script:
  - cargo install --force diesel_cli
  - cp .env.example .env
  - ./reset.sh
  - |
    if [[ "$TRAVIS_RUST_VERSION" == nightly && "$TRAVIS_OS_NAME" == "linux" ]]; then
      RUSTFLAGS="--cfg procmacro2_semver_exempt" cargo install cargo-tarpaulin
    fi

script:
  - cargo build --verbose
  - cargo test --verbose

after_success: |
  if [[ "$TRAVIS_RUST_VERSION" == nightly && "$TRAVIS_OS_NAME" == "linux" ]]; then
    cargo tarpaulin --out Xml
    bash <(curl -s https://codecov.io/bash)
  fi


================================================
FILE: Cargo.toml
================================================
[package]
name = "rust-web-boilerplate"
version = "0.1.0"
authors = ["Sven-Hendrik Haase <svenstaro@gmail.com>"]
edition = "2018"

[lib]
name = "rust_web_boilerplate"
path = "src/lib.rs"

[dependencies]
uuid = { version = "0.8", features = ["serde", "v4"] }
chrono = { version = "0.4", features = ["serde"] }
argon2rs = "0.2"
rocket = "0.4"
diesel = { version = "1.4", features = ["postgres", "uuidv07", "chrono", "serde_json"] }
dotenv = "0.15"
serde = "1"
serde_json = "1"
serde_derive = "1"
validator = "0.16"
validator_derive = "0.16"
ring = "0.13"
rand = "0.7"

[dev-dependencies]
quickcheck = "0.9"
speculate = "0.1"
parking_lot = { version = "0.12", features = ["nightly"] }

[dependencies.rocket_contrib]
version = "0.4"
default-features = false
features = ["json", "diesel_postgres_pool"]

[features]
default = []


================================================
FILE: LICENSE
================================================
MIT License

Copyright (c) 2017 Sven-Hendrik Haase

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.


================================================
FILE: README.md
================================================
# Rust Web Boilerplate

[![Build Status](https://travis-ci.org/svenstaro/rust-web-boilerplate.svg?branch=master)](https://travis-ci.org/svenstaro/rust-web-boilerplate)
[![codecov](https://codecov.io/gh/svenstaro/rust-web-boilerplate/branch/master/graph/badge.svg)](https://codecov.io/gh/svenstaro/rust-web-boilerplate)
[![lines of code](https://tokei.rs/b1/github/svenstaro/rust-web-boilerplate)](https://github.com/svenstaro/rust-web-boilerplate)
[![license](http://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/svenstaro/rust-web-boilerplate/blob/master/LICENSE)


## About
This is a boilerplate project made using best practices for getting started quickly
in a new project. I made this for myself but maybe it will help someone else. Pull
requests and discussions on best practices welcome!

## Development setup

Install a few external dependencies and make sure `~/.cargo/bin` is in your `$PATH`:

    cargo install diesel_cli
    cargo install cargo-watch

Optionally if you want line coverage from your tests, install cargo-tarpaulin:

    cargo-tarpaulin

Copy `.env.example` to `.env` and update your application environment in this file.

Make sure you have a working local postgres setup. Your current user should be
admin in your development postgres installation and it should use the "peer" or
"trust" auth methods (see `pg_hba.conf`).

Now you can launch the `watch.sh` script which helps you quickly iterate. It
will remove and recreate the DB and run the migrations and then the tests on
all code changes.

    ./watch.sh

To get line coverage, do

    cargo tarpaulin --ignore-tests


================================================
FILE: diesel.toml
================================================
# For documentation on how to configure this file,
# see diesel.rs/guides/configuring-diesel-cli

[print_schema]
file = "src/schema.rs"
with_docs = true
import_types = ["diesel::sql_types::*"]


================================================
FILE: migrations/.gitkeep
================================================


================================================
FILE: migrations/00000000000000_diesel_initial_setup/down.sql
================================================
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
DROP FUNCTION IF EXISTS diesel_set_updated_at();


================================================
FILE: migrations/00000000000000_diesel_initial_setup/up.sql
================================================
-- 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;


================================================
FILE: migrations/20170211131857_create_initial_db/down.sql
================================================
DROP TABLE users


================================================
FILE: migrations/20170211131857_create_initial_db/up.sql
================================================
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";

CREATE TABLE users (
    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
    created_at TIMESTAMP DEFAULT current_timestamp NOT NULL,
    updated_at TIMESTAMP DEFAULT current_timestamp NOT NULL,
    email VARCHAR(120) UNIQUE NOT NULL,
    password_hash BYTEA NOT NULL,
    current_auth_token VARCHAR(32),
    last_action TIMESTAMP
);
SELECT diesel_manage_updated_at('users');

CREATE UNIQUE INDEX email_idx ON users(email);
CREATE UNIQUE INDEX current_auth_token_idx ON users(current_auth_token);


================================================
FILE: reset.sh
================================================
#!/bin/bash

dropdb --if-exists ${DATABASE_NAME}
diesel setup --database-url ${DATABASE_URL}


================================================
FILE: src/api/auth.rs
================================================
use diesel;
use diesel::prelude::*;
use rocket::{post, State};
use rocket_contrib::json;
use rocket_contrib::json::{Json, JsonValue};

use crate::config::AppConfig;
use crate::database::DbConn;
use crate::models::user::{NewUser, UserModel};
use crate::responses::{
    conflict, created, internal_server_error, ok, unauthorized, unprocessable_entity, APIResponse,
};
use crate::schema::users;
use crate::schema::users::dsl::*;
use crate::validation::user::UserLogin;

/// Log the user in and return a response with an auth token.
///
/// Return UNAUTHORIZED in case the user can't be found or if the password is incorrect.
#[post("/login", data = "<user_in>", format = "application/json")]
pub fn login(
    user_in: Json<UserLogin>,
    app_config: State<AppConfig>,
    db: DbConn,
) -> Result<APIResponse, APIResponse> {
    let user_q = users
        .filter(email.eq(&user_in.email))
        .first::<UserModel>(&*db)
        .optional()?;

    // For privacy reasons, we'll not provide the exact reason for failure here (although this
    // could probably be timing attacked to find out whether users exist or not.
    let mut user =
        user_q.ok_or_else(|| unauthorized().message("Username or password incorrect."))?;

    if !user.verify_password(user_in.password.as_str()) {
        return Err(unauthorized().message("Username or password incorrect."));
    }

    let token = if user.has_valid_auth_token(app_config.auth_token_timeout_days) {
        user.current_auth_token.ok_or_else(internal_server_error)?
    } else {
        user.generate_auth_token(&db)?
    };

    Ok(ok().data(json!({
        "user_id": user.id,
        "token": token,
    })))
}

/// Register a new user using email and password.
///
/// Return CONFLICT is a user with the same email already exists.
#[post("/register", data = "<user>", format = "application/json")]
pub fn register(
    user: Result<UserLogin, JsonValue>,
    db: DbConn,
) -> Result<APIResponse, APIResponse> {
    let user_data = user.map_err(unprocessable_entity)?;

    let new_password_hash = UserModel::make_password_hash(user_data.password.as_str());
    let new_user = NewUser {
        email: user_data.email.clone(),
        password_hash: new_password_hash,
    };

    let insert_result = diesel::insert_into(users::table)
        .values(&new_user)
        .get_result::<UserModel>(&*db);
    if let Err(diesel::result::Error::DatabaseError(
        diesel::result::DatabaseErrorKind::UniqueViolation,
        _,
    )) = insert_result
    {
        return Err(conflict().message("User already exists."));
    }

    let user = insert_result?;
    Ok(created().data(json!(&user)))
}


================================================
FILE: src/api/hello.rs
================================================
use rocket::get;
use rocket_contrib::json;

use crate::models::user::UserModel;
use crate::responses::{ok, APIResponse};

#[get("/whoami")]
pub fn whoami(current_user: UserModel) -> APIResponse {
    ok().data(json!(current_user.email))
}


================================================
FILE: src/api/mod.rs
================================================
pub mod hello;
pub mod auth;


================================================
FILE: src/bin/runner.rs
================================================
use dotenv::dotenv;
use std::env;

fn main() -> Result<(), String> {
    dotenv().ok();

    let config_name = env::var("CONFIG_ENV").expect("CONFIG must be set");
    let rocket = rust_web_boilerplate::rocket_factory(&config_name)?;
    rocket.launch();
    Ok(())
}


================================================
FILE: src/config.rs
================================================
use rocket::config::{Config, ConfigError, Environment, Value};
use std::env;
use std::collections::HashMap;
use chrono::Duration;


#[derive(Debug)]
pub struct AppConfig {
    pub auth_token_timeout_days: Duration,
    pub cors_allow_origin: String,
    pub cors_allow_methods: String,
    pub cors_allow_headers: String,
    pub environment_name: String,
}

impl Default for AppConfig {
    fn default() -> AppConfig {
        AppConfig {
            auth_token_timeout_days: Duration::days(30),
            cors_allow_origin: String::from("*"),
            cors_allow_methods: String::from("*"),
            cors_allow_headers: String::from("*"),
            environment_name: String::from("unconfigured"),
        }
    }
}


/// Return a tuple of an app-specific config and a Rocket config.
pub fn get_rocket_config(config_name: &str) -> Result<(AppConfig, Config), ConfigError> {
    fn production_config() -> Result<(AppConfig, Config), ConfigError> {
        let app_config = AppConfig {
            cors_allow_origin: String::from("https://example.com"),
            environment_name: String::from("production"),
            ..Default::default()
        };

        let mut database_config = HashMap::new();
        let mut databases = HashMap::new();
        database_config.insert("url", Value::from(env::var("DATABASE_URL").unwrap()));
        databases.insert("postgres_db", Value::from(database_config));

        let rocket_config = Config::build(Environment::Production)
            .address("0.0.0.0")
            .port(8080)
            .extra("databases", databases)
            .finalize()?;

        Ok((app_config, rocket_config))
    }

    fn staging_config() -> Result<(AppConfig, Config), ConfigError> {
        let app_config = AppConfig {
            cors_allow_origin: String::from("https://staging.example.com"),
            environment_name: String::from("staging"),
            ..Default::default()
        };

        let mut database_config = HashMap::new();
        let mut databases = HashMap::new();
        database_config.insert("url", Value::from(env::var("DATABASE_URL").unwrap()));
        databases.insert("postgres_db", Value::from(database_config));

        let rocket_config = Config::build(Environment::Staging)
            .address("0.0.0.0")
            .port(8080)
            .extra("databases", databases)
            .finalize()?;

        Ok((app_config, rocket_config))
    }

    fn develop_config() -> Result<(AppConfig, Config), ConfigError> {
        let app_config = AppConfig {
            cors_allow_origin: String::from("https://develop.example.com"),
            environment_name: String::from("develop"),
            ..Default::default()
        };

        let mut database_config = HashMap::new();
        let mut databases = HashMap::new();
        database_config.insert("url", Value::from(env::var("DATABASE_URL").unwrap()));
        databases.insert("postgres_db", Value::from(database_config));

        let rocket_config = Config::build(Environment::Staging)
            .address("0.0.0.0")
            .port(8080)
            .extra("databases", databases)
            .finalize()?;

        Ok((app_config, rocket_config))
    }

    fn testing_config() -> Result<(AppConfig, Config), ConfigError> {
        let app_config = AppConfig {
            environment_name: String::from("testing"),
            ..Default::default()
        };

        let mut database_config = HashMap::new();
        let mut databases = HashMap::new();
        database_config.insert("url", Value::from(env::var("DATABASE_URL").unwrap()));
        databases.insert("postgres_db", Value::from(database_config));

        let rocket_config = Config::build(Environment::Staging)
            .address("0.0.0.0")
            .port(5000)
            .extra("databases", databases)
            .finalize()?;

        Ok((app_config, rocket_config))
    }

    fn local_config() -> Result<(AppConfig, Config), ConfigError> {
        let app_config = AppConfig {
            environment_name: String::from("local"),
            ..Default::default()
        };

        let mut database_config = HashMap::new();
        let mut databases = HashMap::new();
        database_config.insert("url", Value::from(env::var("DATABASE_URL").unwrap()));
        databases.insert("postgres_db", Value::from(database_config));

        let rocket_config = Config::build(Environment::Staging)
            .address("0.0.0.0")
            .port(5000)
            .extra("databases", databases)
            .finalize()?;

        Ok((app_config, rocket_config))
    }

    match config_name {
        "production" => production_config(),
        "staging" => staging_config(),
        "develop" => develop_config(),
        "testing" => testing_config(),
        "local" => local_config(),
        _ => Err(ConfigError::BadEnv(format!(
            "No valid config chosen: {}",
            config_name
        ))),
    }
}


================================================
FILE: src/database.rs
================================================
use rocket_contrib::database;

#[database("postgres_db")]
pub struct DbConn(diesel::PgConnection);


================================================
FILE: src/handlers.rs
================================================
use rocket::http::Status;
use rocket::request::{self, FromRequest, Request};
use rocket::{catch, Outcome};

use crate::database::DbConn;

use crate::responses::{
    bad_request, forbidden, internal_server_error, not_found, service_unavailable, unauthorized,
    APIResponse,
};
use crate::models::user::UserModel;

#[catch(400)]
pub fn bad_request_handler() -> APIResponse {
    bad_request()
}

#[catch(401)]
pub fn unauthorized_handler() -> APIResponse {
    unauthorized()
}

#[catch(403)]
pub fn forbidden_handler() -> APIResponse {
    forbidden()
}

#[catch(404)]
pub fn not_found_handler() -> APIResponse {
    not_found()
}

#[catch(500)]
pub fn internal_server_error_handler() -> APIResponse {
    internal_server_error()
}

#[catch(503)]
pub fn service_unavailable_handler() -> APIResponse {
    service_unavailable()
}

impl<'a, 'r> FromRequest<'a, 'r> for UserModel {
    type Error = ();

    fn from_request(request: &'a Request<'r>) -> request::Outcome<UserModel, ()> {
        let db = <DbConn as FromRequest>::from_request(request)?;
        let keys: Vec<_> = request.headers().get("Authorization").collect();
        if keys.len() != 1 {
            return Outcome::Failure((Status::BadRequest, ()));
        };

        let token_header = keys[0];
        let token = token_header.replace("Bearer ", "");

        match UserModel::get_user_from_login_token(&token, &*db) {
            Some(user) => Outcome::Success(user),
            None => Outcome::Failure((Status::Unauthorized, ())),
        }
    }
}


================================================
FILE: src/lib.rs
================================================
#![feature(proc_macro_hygiene, decl_macro)]
#![recursion_limit = "128"]

// Keep the pre-2018 style [macro_use] for diesel because it's annoying otherwise:
// https://github.com/diesel-rs/diesel/issues/1764
#[macro_use]
extern crate diesel;

use rocket::{catchers, routes};

pub mod api;
pub mod config;
pub mod database;
pub mod handlers;
pub mod models;
pub mod responses;
pub mod schema;
pub mod validation;

/// Constructs a new Rocket instance.
///
/// This function takes care of attaching all routes and handlers of the application.
pub fn rocket_factory(config_name: &str) -> Result<rocket::Rocket, String> {
    let (app_config, rocket_config) =
        config::get_rocket_config(config_name).map_err(|x| format!("{}", x))?;
    let rocket = rocket::custom(rocket_config)
        .attach(database::DbConn::fairing())
        .manage(app_config)
        .mount("/hello/", routes![api::hello::whoami])
        .mount("/auth/", routes![api::auth::login, api::auth::register,])
        .register(catchers![
            handlers::bad_request_handler,
            handlers::unauthorized_handler,
            handlers::forbidden_handler,
            handlers::not_found_handler,
            handlers::internal_server_error_handler,
            handlers::service_unavailable_handler,
        ]);
    Ok(rocket)
}


================================================
FILE: src/models/mod.rs
================================================
pub mod user;


================================================
FILE: src/models/user.rs
================================================
// TODO: Silence this until diesel 1.4.
// See https://github.com/diesel-rs/diesel/issues/1785#issuecomment-422579609.
#![allow(proc_macro_derive_resolution_fallback)]

use std::fmt;

use argon2rs::argon2i_simple;
use chrono::{Duration, NaiveDateTime, Utc};
use diesel::pg::PgConnection;
use diesel::prelude::*;
use diesel::result::Error as DieselError;
use rand::distributions::Alphanumeric;
use rand::{Rng, thread_rng};
use ring::constant_time::verify_slices_are_equal;
use serde_derive::{Deserialize, Serialize};
use uuid::Uuid;

use crate::schema::users;

#[derive(Debug, Serialize, Deserialize, Queryable, Identifiable, AsChangeset)]
#[table_name = "users"]
pub struct UserModel {
    pub id: Uuid,
    pub created_at: NaiveDateTime,
    pub updated_at: NaiveDateTime,
    pub email: String,
    pub password_hash: Vec<u8>,
    pub current_auth_token: Option<String>,
    pub last_action: Option<NaiveDateTime>,
}

#[derive(Insertable)]
#[table_name = "users"]
pub struct NewUser {
    pub email: String,
    pub password_hash: Vec<u8>,
}

impl fmt::Display for UserModel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "<User {email}>", email = self.email)
    }
}

impl UserModel {
    /// Hash `password` using argon2 and return it.
    pub fn make_password_hash(password: &str) -> Vec<u8> {
        argon2i_simple(password, "loginsalt").to_vec()
    }

    /// Verify that `candidate_password` matches the stored password.
    pub fn verify_password(&self, candidate_password: &str) -> bool {
        let candidate_hash = argon2i_simple(candidate_password, "loginsalt").to_vec();
        self.password_hash == candidate_hash
    }

    /// Generate an auth token and save it to the `current_auth_token` column.
    pub fn generate_auth_token(&mut self, conn: &PgConnection) -> Result<String, DieselError> {
        let rng = thread_rng();
        let new_auth_token = rng
            .sample_iter(&Alphanumeric)
            .take(32)
            .collect::<String>();
        self.current_auth_token = Some(new_auth_token.clone());
        self.last_action = Some(Utc::now().naive_utc());
        self.save_changes::<UserModel>(conn)?;
        Ok(new_auth_token)
    }

    /// Return whether or not the user has a valid auth token.
    pub fn has_valid_auth_token(&self, auth_token_timeout: Duration) -> bool {
        let latest_valid_date = Utc::now() - auth_token_timeout;
        if let Some(last_action) = self.last_action {
            if self.current_auth_token.is_some() {
                last_action > latest_valid_date.naive_utc()
            } else {
                false
            }
        } else {
            false
        }
    }

    /// Get a `User` from a login token.
    ///
    /// A login token has this format:
    ///     <user uuid>:<auth token>
    pub fn get_user_from_login_token(token: &str, db: &PgConnection) -> Option<UserModel> {
        use crate::schema::users::dsl::*;

        let v: Vec<&str> = token.split(':').collect();
        let user_id = Uuid::parse_str(v.get(0).unwrap_or(&"")).unwrap_or_default();
        let auth_token = v.get(1).unwrap_or(&"").to_string();

        let user = users.find(user_id).first::<UserModel>(&*db).optional();
        if let Ok(Some(u)) = user {
            if let Some(token) = u.current_auth_token.clone() {
                if verify_slices_are_equal(token.as_bytes(), auth_token.as_bytes()).is_ok() {
                    return Some(u);
                }
            }
        }
        None
    }
}


================================================
FILE: src/responses.rs
================================================
use diesel::result::Error as DieselError;
use rocket::http::{ContentType, Status};
use rocket::request::Request;
use rocket::response::{Responder, Response};
use rocket_contrib::json;
use rocket_contrib::json::JsonValue;
use std::convert::From;
use std::io::Cursor;

#[derive(Debug)]
pub struct APIResponse {
    data: JsonValue,
    status: Status,
}

impl APIResponse {
    /// Set the data of the `Response` to `data`.
    pub fn data(mut self, data: JsonValue) -> APIResponse {
        self.data = data;
        self
    }

    /// Convenience method to set `self.data` to `{"message": message}`.
    pub fn message(mut self, message: &str) -> APIResponse {
        self.data = json!({ "message": message });
        self
    }
}

impl From<DieselError> for APIResponse {
    fn from(_: DieselError) -> Self {
        internal_server_error()
    }
}

impl<'r> Responder<'r> for APIResponse {
    fn respond_to(self, _req: &Request) -> Result<Response<'r>, Status> {
        let body = self.data;

        Response::build()
            .status(self.status)
            .sized_body(Cursor::new(body.to_string()))
            .header(ContentType::JSON)
            .ok()
    }
}

pub fn ok() -> APIResponse {
    APIResponse {
        data: json!(null),
        status: Status::Ok,
    }
}

pub fn created() -> APIResponse {
    APIResponse {
        data: json!(null),
        status: Status::Created,
    }
}

pub fn accepted() -> APIResponse {
    APIResponse {
        data: json!(null),
        status: Status::Accepted,
    }
}

pub fn no_content() -> APIResponse {
    APIResponse {
        data: json!(null),
        status: Status::NoContent,
    }
}

pub fn bad_request() -> APIResponse {
    APIResponse {
        data: json!({"message": "Bad Request"}),
        status: Status::BadRequest,
    }
}

pub fn unauthorized() -> APIResponse {
    APIResponse {
        data: json!({"message": "Unauthorized"}),
        status: Status::Unauthorized,
    }
}

pub fn forbidden() -> APIResponse {
    APIResponse {
        data: json!({"message": "Forbidden"}),
        status: Status::Forbidden,
    }
}

pub fn not_found() -> APIResponse {
    APIResponse {
        data: json!({"message": "Not Found"}),
        status: Status::NotFound,
    }
}

pub fn method_not_allowed() -> APIResponse {
    APIResponse {
        data: json!({"message": "Method Not Allowed"}),
        status: Status::MethodNotAllowed,
    }
}

pub fn conflict() -> APIResponse {
    APIResponse {
        data: json!({"message": "Conflict"}),
        status: Status::Conflict,
    }
}

pub fn unprocessable_entity(errors: JsonValue) -> APIResponse {
    APIResponse {
        data: json!({ "message": errors }),
        status: Status::UnprocessableEntity,
    }
}

pub fn internal_server_error() -> APIResponse {
    APIResponse {
        data: json!({"message": "Internal Server Error"}),
        status: Status::InternalServerError,
    }
}

pub fn service_unavailable() -> APIResponse {
    APIResponse {
        data: json!({"message": "Service Unavailable"}),
        status: Status::ServiceUnavailable,
    }
}


================================================
FILE: src/schema.rs
================================================
// TODO: Silence this until diesel 1.4.
// See https://github.com/diesel-rs/diesel/issues/1785#issuecomment-422579609.
#![allow(proc_macro_derive_resolution_fallback)]

table! {
    users (id) {
        id -> Uuid,
        created_at -> Timestamp,
        updated_at -> Timestamp,
        email -> Varchar,
        password_hash -> Bytea,
        current_auth_token -> Nullable<Varchar>,
        last_action -> Nullable<Timestamp>,
    }
}


================================================
FILE: src/validation/mod.rs
================================================
pub mod user;


================================================
FILE: src/validation/user.rs
================================================
use rocket::data::{self, FromData, FromDataSimple, Transform};
use rocket::http::Status;
use rocket::Outcome::*;
use rocket::{Data, Request};
use rocket_contrib::json;
use rocket_contrib::json::{Json, JsonValue};
use serde_derive::Deserialize;
use std::collections::HashMap;
use std::io::Read;
use uuid::Uuid;
use validator::Validate;
use validator_derive::Validate;

#[derive(Deserialize, Debug, Validate)]
pub struct UserLogin {
    #[serde(skip_deserializing)]
    pub id: Option<Uuid>,
    #[validate(email)]
    pub email: String,
    pub password: String,
}

impl FromDataSimple for UserLogin {
    type Error = JsonValue;

    fn from_data(req: &Request, data: Data) -> data::Outcome<Self, JsonValue> {
        let mut d = String::new();
        if data.open().read_to_string(&mut d).is_err() {
            return Failure((
                Status::InternalServerError,
                json!({"_schema": "Internal server error."}),
            ));
        }
        let user =
            Json::<UserLogin>::from_data(req, Transform::Borrowed(Success(&d))).map_failure(|_| {
                (
                    Status::UnprocessableEntity,
                    json!({"_schema": "Error while parsing user login."}),
                )
            })?;

        let mut errors = HashMap::new();
        if user.email == "" {
            errors
                .entry("email")
                .or_insert_with(|| vec![])
                .push("Must not be empty.");
        } else if !user.email.contains('@') || !user.email.contains('.') {
            errors
                .entry("email")
                .or_insert_with(|| vec![])
                .push("Invalid email.");
        }

        if user.password == "" {
            errors
                .entry("password")
                .or_insert_with(|| vec![])
                .push("Must not be empty.");
        }

        if !errors.is_empty() {
            return Failure((Status::UnprocessableEntity, json!(errors)));
        }

        Success(UserLogin {
            id: user.id,
            email: user.email.clone(),
            password: user.password.clone(),
        })
    }
}


================================================
FILE: tests/common/mod.rs
================================================
pub fn setup() {
    dotenv::dotenv().ok();
}


================================================
FILE: tests/factories/mod.rs
================================================
use uuid::Uuid;
use diesel;
use diesel::prelude::*;
use diesel::pg::PgConnection;

use rust_web_boilerplate::models::user::{UserModel, NewUser};
use rust_web_boilerplate::schema::users::dsl::*;

/// Create a new `User` and add it to the database.
///
/// The user's email will be set to '<uuid>@example.com'.
pub fn make_user(conn: &PgConnection) -> UserModel {
    let new_email = format!("{username}@example.com", username=Uuid::new_v4().to_hyphenated().to_string());
    let new_password_hash = UserModel::make_password_hash("testtest");
    let new_user = NewUser {
        email: new_email,
        password_hash: new_password_hash,
    };

    diesel::insert_into(users)
        .values(&new_user)
        .get_result::<UserModel>(conn)
        .expect("Error saving new post")
}


================================================
FILE: tests/test_api_auth.rs
================================================
#[allow(unused_imports)]
use diesel::prelude::*;
use parking_lot::Mutex;
use rocket::http::{ContentType, Status};
use rocket::local::Client;
use rocket_contrib::json;
use rocket_contrib::json::JsonValue;
use serde_derive::Deserialize;
use speculate::speculate;
use uuid::Uuid;

use rust_web_boilerplate::database::DbConn;
use rust_web_boilerplate::models::user::UserModel;
use rust_web_boilerplate::rocket_factory;
use rust_web_boilerplate::schema::users::dsl::*;

use crate::factories::make_user;

mod common;
mod factories;

static DB_LOCK: Mutex<()> = Mutex::new(());

#[derive(Deserialize)]
struct LoginData {
    user_id: Uuid,
    token: String,
}

speculate! {
    before {
        common::setup();
        let _lock = DB_LOCK.lock();
        let rocket = rocket_factory("testing").unwrap();
        let client = Client::new(rocket).unwrap();
        #[allow(unused_variables)]
        let conn = DbConn::get_one(client.rocket()).expect("Failed to get a database connection for testing!");
    }

    describe "login" {
        it "enables users to login and get back a valid auth token" {
            let user = make_user(&conn);
            let data = json!({
                "email": user.email,
                "password": "testtest",
            });
            let mut res = client.post("/auth/login")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: LoginData = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            let refreshed_user = users
                .find(user.id)
                .first::<UserModel>(&*conn).unwrap();
            assert_eq!(res.status(), Status::Ok);
            assert_eq!(body.user_id, refreshed_user.id);
            assert_eq!(body.token, refreshed_user.current_auth_token.unwrap());
        }

        it "can log in and get back the same auth token if there's already a valid one" {
            let user = make_user(&conn);
            let data = json!({
                "email": user.email,
                "password": "testtest",
            });

            // Login the first time and then retrieve and store the token.
            let first_login_token = {
                client.post("/auth/login")
                    .header(ContentType::JSON)
                    .body(data.to_string())
                    .dispatch();
                let user_after_first_login = users
                    .find(user.id)
                    .first::<UserModel>(&*conn).unwrap();
                user_after_first_login.current_auth_token.unwrap()
            };

            // Login the second time and then retrieve and store the token.
            let second_login_token = {
                client.post("/auth/login")
                    .header(ContentType::JSON)
                    .body(data.to_string())
                    .dispatch();
                let user_after_second_login = users
                    .find(user.id)
                    .first::<UserModel>(&*conn).unwrap();
                user_after_second_login.current_auth_token.unwrap()
            };

            assert_eq!(first_login_token, second_login_token);
        }

        it "fails with a wrong username" {
            make_user(&conn);
            let data = json!({
                    "email": "invalid@example.com",
                    "password": "testtest",
            });
            let mut res = client.post("/auth/login")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: JsonValue = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            assert_eq!(res.status(), Status::Unauthorized);
            assert_eq!(body["message"], "Username or password incorrect.");
        }

        it "fails with a wrong password" {
            let user = make_user(&conn);
            let data = json!({
                    "email": user.email,
                    "password": "invalid",
            });
            let mut res = client.post("/auth/login")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: JsonValue = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            assert_eq!(res.status(), Status::Unauthorized);
            assert_eq!(body["message"], "Username or password incorrect.");
        }
    }

    describe "register" {
        it "allows users to register a new account and then login with it" {
            let new_email = format!("{username}@example.com", username=Uuid::new_v4().to_hyphenated().to_string());
            let new_password = "mypassword";
            let data = json!({
                "email": new_email,
                "password": new_password,
            });
            let mut res = client.post("/auth/register")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: UserModel = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            assert_eq!(res.status(), Status::Created);
            assert_eq!(body.email, new_email);

            // Now try to log in using the new account.
            let data = json!({
                "email": new_email,
                "password": new_password,
            });
            let mut res = client.post("/auth/login")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: LoginData = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            let logged_in_user = users
                .filter(email.eq(new_email))
                .first::<UserModel>(&*conn).unwrap();
            assert_eq!(res.status(), Status::Ok);
            assert_eq!(body.token, logged_in_user.current_auth_token.unwrap());
        }

        it "can't register with an existing email" {
            let new_email = format!("{username}@example.com", username=Uuid::new_v4().to_hyphenated().to_string());
            let new_password = "mypassword";
            let data = json!({
                "email": new_email,
                "password": new_password,
            });
            client.post("/auth/register")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();

            let mut res = client.post("/auth/register")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: JsonValue = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            assert_eq!(res.status(), Status::Conflict);
            assert_eq!(body["message"], "User already exists.");
        }

        it "can't register with an invalid email" {
            let data = json!({
                "email": "invalid",
                "password": "somepw",
            });
            let mut res = client.post("/auth/register")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: JsonValue = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            assert_eq!(res.status(), Status::UnprocessableEntity);
            assert_eq!(body["message"]["email"], *json!(["Invalid email."]));
        }

        it "can't register with an empty email" {
            let data = json!({
                "email": "",
                "password": "somepw",
            });
            let mut res = client.post("/auth/register")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: JsonValue = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            assert_eq!(res.status(), Status::UnprocessableEntity);
            assert_eq!(body["message"]["email"], *json!(["Must not be empty."]));
        }

        it "can't register with an empty password" {
            let data = json!({
                "email": "something@example.com",
                "password": "",
            });
            let mut res = client.post("/auth/register")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: JsonValue = serde_json::from_str(&res.body_string().unwrap()).unwrap();

            assert_eq!(res.status(), Status::UnprocessableEntity);
            assert_eq!(body["message"]["password"], *json!(["Must not be empty."]));
        }
    }
}


================================================
FILE: tests/test_api_hello.rs
================================================
#[allow(unused_imports)]
use diesel::prelude::*;
use parking_lot::Mutex;
use rocket::http::{ContentType, Header, Status};
use rocket::local::Client;
use rocket_contrib::json;
use serde_derive::Deserialize;
use speculate::speculate;
use uuid::Uuid;

use rust_web_boilerplate::database::DbConn;
use rust_web_boilerplate::rocket_factory;

use crate::factories::make_user;

mod common;
mod factories;

static DB_LOCK: Mutex<()> = Mutex::new(());

#[derive(Deserialize)]
struct LoginData {
    token: String,
}

speculate! {
    before {
        common::setup();
        let _lock = DB_LOCK.lock();
        let rocket = rocket_factory("testing").unwrap();
        let client = Client::new(rocket).unwrap();
        #[allow(unused_variables)]
        let conn = DbConn::get_one(client.rocket()).expect("Failed to get a database connection for testing!");
    }

    describe "whoami" {
        it "echoes back the email" {
            let user = make_user(&conn);
            let data = json!({
                "email": user.email,
                "password": "testtest",
            });
            let mut res = client.post("/auth/login")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();
            let body: LoginData = serde_json::from_str(&res.body_string().unwrap()).unwrap();
            let token = body.token;

            let res = client.get("/hello/whoami")
                .header(ContentType::JSON)
                .header(Header::new("Authorization", format!("Bearer {}:{}", user.id, token)))
                .dispatch();

            assert_eq!(res.status(), Status::Ok);
        }

        it "returns BadRequest when sent no Authorization header" {
            let user = make_user(&conn);
            let data = json!({
                "email": user.email,
                "password": "testtest",
            });
            client.post("/auth/login")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();

            let res = client.get("/hello/whoami")
                .header(ContentType::JSON)
                .dispatch();

            assert_eq!(res.status(), Status::BadRequest);
        }

        it "returns Unauthorized when sent an invalid token" {
            let user = make_user(&conn);
            let data = json!({
                "email": user.email,
                "password": "testtest",
            });
            client.post("/auth/login")
                .header(ContentType::JSON)
                .body(data.to_string())
                .dispatch();

            let res = client.get("/hello/whoami")
                .header(ContentType::JSON)
                .header(Header::new("Authorization", format!("Bearer {}:{}", user.id, Uuid::nil())))
                .dispatch();

            assert_eq!(res.status(), Status::Unauthorized);
        }
    }
}


================================================
FILE: watch.sh
================================================
#!/bin/bash
cargo watch -s "./reset.sh && cargo clippy && cargo build && RUST_BACKTRACE=1 cargo test && RUST_BACKTRACE=1 cargo run"
Download .txt
gitextract_gpb2a5y3/

├── .github/
│   └── dependabot.yml
├── .gitignore
├── .travis.yml
├── Cargo.toml
├── LICENSE
├── README.md
├── diesel.toml
├── migrations/
│   ├── .gitkeep
│   ├── 00000000000000_diesel_initial_setup/
│   │   ├── down.sql
│   │   └── up.sql
│   └── 20170211131857_create_initial_db/
│       ├── down.sql
│       └── up.sql
├── reset.sh
├── src/
│   ├── api/
│   │   ├── auth.rs
│   │   ├── hello.rs
│   │   └── mod.rs
│   ├── bin/
│   │   └── runner.rs
│   ├── config.rs
│   ├── database.rs
│   ├── handlers.rs
│   ├── lib.rs
│   ├── models/
│   │   ├── mod.rs
│   │   └── user.rs
│   ├── responses.rs
│   ├── schema.rs
│   └── validation/
│       ├── mod.rs
│       └── user.rs
├── tests/
│   ├── common/
│   │   └── mod.rs
│   ├── factories/
│   │   └── mod.rs
│   ├── test_api_auth.rs
│   └── test_api_hello.rs
└── watch.sh
Download .txt
SYMBOL INDEX (54 symbols across 16 files)

FILE: migrations/00000000000000_diesel_initial_setup/up.sql
  function diesel_set_updated_at (line 26) | CREATE OR REPLACE FUNCTION diesel_set_updated_at() RETURNS trigger AS $$

FILE: migrations/20170211131857_create_initial_db/up.sql
  type users (line 3) | CREATE TABLE users (
  type email_idx (line 14) | CREATE UNIQUE INDEX email_idx ON users(email)
  type current_auth_token_idx (line 15) | CREATE UNIQUE INDEX current_auth_token_idx ON users(current_auth_token)

FILE: src/api/auth.rs
  function login (line 21) | pub fn login(
  function register (line 56) | pub fn register(

FILE: src/api/hello.rs
  function whoami (line 8) | pub fn whoami(current_user: UserModel) -> APIResponse {

FILE: src/bin/runner.rs
  function main (line 4) | fn main() -> Result<(), String> {

FILE: src/config.rs
  type AppConfig (line 8) | pub struct AppConfig {
  method default (line 17) | fn default() -> AppConfig {
  function get_rocket_config (line 30) | pub fn get_rocket_config(config_name: &str) -> Result<(AppConfig, Config...

FILE: src/database.rs
  type DbConn (line 4) | pub struct DbConn(diesel::PgConnection);

FILE: src/handlers.rs
  function bad_request_handler (line 14) | pub fn bad_request_handler() -> APIResponse {
  function unauthorized_handler (line 19) | pub fn unauthorized_handler() -> APIResponse {
  function forbidden_handler (line 24) | pub fn forbidden_handler() -> APIResponse {
  function not_found_handler (line 29) | pub fn not_found_handler() -> APIResponse {
  function internal_server_error_handler (line 34) | pub fn internal_server_error_handler() -> APIResponse {
  function service_unavailable_handler (line 39) | pub fn service_unavailable_handler() -> APIResponse {
  type Error (line 44) | type Error = ();
  method from_request (line 46) | fn from_request(request: &'a Request<'r>) -> request::Outcome<UserModel,...

FILE: src/lib.rs
  function rocket_factory (line 23) | pub fn rocket_factory(config_name: &str) -> Result<rocket::Rocket, Strin...

FILE: src/models/user.rs
  type UserModel (line 22) | pub struct UserModel {
    method fmt (line 40) | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
    method make_password_hash (line 47) | pub fn make_password_hash(password: &str) -> Vec<u8> {
    method verify_password (line 52) | pub fn verify_password(&self, candidate_password: &str) -> bool {
    method generate_auth_token (line 58) | pub fn generate_auth_token(&mut self, conn: &PgConnection) -> Result<S...
    method has_valid_auth_token (line 71) | pub fn has_valid_auth_token(&self, auth_token_timeout: Duration) -> bo...
    method get_user_from_login_token (line 88) | pub fn get_user_from_login_token(token: &str, db: &PgConnection) -> Op...
  type NewUser (line 34) | pub struct NewUser {

FILE: src/responses.rs
  type APIResponse (line 11) | pub struct APIResponse {
    method data (line 18) | pub fn data(mut self, data: JsonValue) -> APIResponse {
    method message (line 24) | pub fn message(mut self, message: &str) -> APIResponse {
    method from (line 31) | fn from(_: DieselError) -> Self {
    method respond_to (line 37) | fn respond_to(self, _req: &Request) -> Result<Response<'r>, Status> {
  function ok (line 48) | pub fn ok() -> APIResponse {
  function created (line 55) | pub fn created() -> APIResponse {
  function accepted (line 62) | pub fn accepted() -> APIResponse {
  function no_content (line 69) | pub fn no_content() -> APIResponse {
  function bad_request (line 76) | pub fn bad_request() -> APIResponse {
  function unauthorized (line 83) | pub fn unauthorized() -> APIResponse {
  function forbidden (line 90) | pub fn forbidden() -> APIResponse {
  function not_found (line 97) | pub fn not_found() -> APIResponse {
  function method_not_allowed (line 104) | pub fn method_not_allowed() -> APIResponse {
  function conflict (line 111) | pub fn conflict() -> APIResponse {
  function unprocessable_entity (line 118) | pub fn unprocessable_entity(errors: JsonValue) -> APIResponse {
  function internal_server_error (line 125) | pub fn internal_server_error() -> APIResponse {
  function service_unavailable (line 132) | pub fn service_unavailable() -> APIResponse {

FILE: src/validation/user.rs
  type UserLogin (line 15) | pub struct UserLogin {
  type Error (line 24) | type Error = JsonValue;
  method from_data (line 26) | fn from_data(req: &Request, data: Data) -> data::Outcome<Self, JsonValue> {

FILE: tests/common/mod.rs
  function setup (line 1) | pub fn setup() {

FILE: tests/factories/mod.rs
  function make_user (line 12) | pub fn make_user(conn: &PgConnection) -> UserModel {

FILE: tests/test_api_auth.rs
  type LoginData (line 25) | struct LoginData {

FILE: tests/test_api_hello.rs
  type LoginData (line 22) | struct LoginData {
Condensed preview — 32 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (43K chars).
[
  {
    "path": ".github/dependabot.yml",
    "chars": 145,
    "preview": "version: 2\nupdates:\n- package-ecosystem: cargo\n  directory: \"/\"\n  schedule:\n    interval: daily\n    time: \"04:00\"\n  open"
  },
  {
    "path": ".gitignore",
    "chars": 28,
    "preview": "target\nCargo.lock\n.env\n.idea"
  },
  {
    "path": ".travis.yml",
    "chars": 741,
    "preview": "language: rust\n\nrust:\n  - stable\n  - beta\n  - nightly\n\nenv:\n  - DATABASE_NAME=boilerplateapp DATABASE_URL=postgres://loc"
  },
  {
    "path": "Cargo.toml",
    "chars": 823,
    "preview": "[package]\nname = \"rust-web-boilerplate\"\nversion = \"0.1.0\"\nauthors = [\"Sven-Hendrik Haase <svenstaro@gmail.com>\"]\nedition"
  },
  {
    "path": "LICENSE",
    "chars": 1075,
    "preview": "MIT License\n\nCopyright (c) 2017 Sven-Hendrik Haase\n\nPermission is hereby granted, free of charge, to any person obtainin"
  },
  {
    "path": "README.md",
    "chars": 1619,
    "preview": "# Rust Web Boilerplate\n\n[![Build Status](https://travis-ci.org/svenstaro/rust-web-boilerplate.svg?branch=master)](https:"
  },
  {
    "path": "diesel.toml",
    "chars": 193,
    "preview": "# For documentation on how to configure this file,\n# see diesel.rs/guides/configuring-diesel-cli\n\n[print_schema]\nfile = "
  },
  {
    "path": "migrations/.gitkeep",
    "chars": 0,
    "preview": ""
  },
  {
    "path": "migrations/00000000000000_diesel_initial_setup/down.sql",
    "chars": 114,
    "preview": "DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);\nDROP FUNCTION IF EXISTS diesel_set_updated_at();\n"
  },
  {
    "path": "migrations/00000000000000_diesel_initial_setup/up.sql",
    "chars": 1145,
    "preview": "-- This file was automatically created by Diesel to setup helper functions\n-- and other internal bookkeeping. This file "
  },
  {
    "path": "migrations/20170211131857_create_initial_db/down.sql",
    "chars": 17,
    "preview": "DROP TABLE users\n"
  },
  {
    "path": "migrations/20170211131857_create_initial_db/up.sql",
    "chars": 542,
    "preview": "CREATE EXTENSION IF NOT EXISTS \"uuid-ossp\";\n\nCREATE TABLE users (\n    id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),\n  "
  },
  {
    "path": "reset.sh",
    "chars": 93,
    "preview": "#!/bin/bash\n\ndropdb --if-exists ${DATABASE_NAME}\ndiesel setup --database-url ${DATABASE_URL}\n"
  },
  {
    "path": "src/api/auth.rs",
    "chars": 2659,
    "preview": "use diesel;\nuse diesel::prelude::*;\nuse rocket::{post, State};\nuse rocket_contrib::json;\nuse rocket_contrib::json::{Json"
  },
  {
    "path": "src/api/hello.rs",
    "chars": 239,
    "preview": "use rocket::get;\nuse rocket_contrib::json;\n\nuse crate::models::user::UserModel;\nuse crate::responses::{ok, APIResponse};"
  },
  {
    "path": "src/api/mod.rs",
    "chars": 29,
    "preview": "pub mod hello;\npub mod auth;\n"
  },
  {
    "path": "src/bin/runner.rs",
    "chars": 268,
    "preview": "use dotenv::dotenv;\nuse std::env;\n\nfn main() -> Result<(), String> {\n    dotenv().ok();\n\n    let config_name = env::var("
  },
  {
    "path": "src/config.rs",
    "chars": 4949,
    "preview": "use rocket::config::{Config, ConfigError, Environment, Value};\nuse std::env;\nuse std::collections::HashMap;\nuse chrono::"
  },
  {
    "path": "src/database.rs",
    "chars": 99,
    "preview": "use rocket_contrib::database;\n\n#[database(\"postgres_db\")]\npub struct DbConn(diesel::PgConnection);\n"
  },
  {
    "path": "src/handlers.rs",
    "chars": 1528,
    "preview": "use rocket::http::Status;\nuse rocket::request::{self, FromRequest, Request};\nuse rocket::{catch, Outcome};\n\nuse crate::d"
  },
  {
    "path": "src/lib.rs",
    "chars": 1314,
    "preview": "#![feature(proc_macro_hygiene, decl_macro)]\n#![recursion_limit = \"128\"]\n\n// Keep the pre-2018 style [macro_use] for dies"
  },
  {
    "path": "src/models/mod.rs",
    "chars": 14,
    "preview": "pub mod user;\n"
  },
  {
    "path": "src/models/user.rs",
    "chars": 3522,
    "preview": "// TODO: Silence this until diesel 1.4.\n// See https://github.com/diesel-rs/diesel/issues/1785#issuecomment-422579609.\n#"
  },
  {
    "path": "src/responses.rs",
    "chars": 3099,
    "preview": "use diesel::result::Error as DieselError;\nuse rocket::http::{ContentType, Status};\nuse rocket::request::Request;\nuse roc"
  },
  {
    "path": "src/schema.rs",
    "chars": 440,
    "preview": "// TODO: Silence this until diesel 1.4.\n// See https://github.com/diesel-rs/diesel/issues/1785#issuecomment-422579609.\n#"
  },
  {
    "path": "src/validation/mod.rs",
    "chars": 14,
    "preview": "pub mod user;\n"
  },
  {
    "path": "src/validation/user.rs",
    "chars": 2149,
    "preview": "use rocket::data::{self, FromData, FromDataSimple, Transform};\nuse rocket::http::Status;\nuse rocket::Outcome::*;\nuse roc"
  },
  {
    "path": "tests/common/mod.rs",
    "chars": 46,
    "preview": "pub fn setup() {\n    dotenv::dotenv().ok();\n}\n"
  },
  {
    "path": "tests/factories/mod.rs",
    "chars": 786,
    "preview": "use uuid::Uuid;\nuse diesel;\nuse diesel::prelude::*;\nuse diesel::pg::PgConnection;\n\nuse rust_web_boilerplate::models::use"
  },
  {
    "path": "tests/test_api_auth.rs",
    "chars": 8601,
    "preview": "#[allow(unused_imports)]\nuse diesel::prelude::*;\nuse parking_lot::Mutex;\nuse rocket::http::{ContentType, Status};\nuse ro"
  },
  {
    "path": "tests/test_api_hello.rs",
    "chars": 2916,
    "preview": "#[allow(unused_imports)]\nuse diesel::prelude::*;\nuse parking_lot::Mutex;\nuse rocket::http::{ContentType, Header, Status}"
  },
  {
    "path": "watch.sh",
    "chars": 132,
    "preview": "#!/bin/bash\ncargo watch -s \"./reset.sh && cargo clippy && cargo build && RUST_BACKTRACE=1 cargo test && RUST_BACKTRACE=1"
  }
]

About this extraction

This page contains the full source code of the svenstaro/rust-web-boilerplate GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 32 files (38.4 KB), approximately 9.8k tokens, and a symbol index with 54 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!