-
Notifications
You must be signed in to change notification settings - Fork 45
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #17 from pwoolcoc/add-rocket-and-diesel
Rust backend for fedibook
- Loading branch information
Showing
35 changed files
with
2,004 additions
and
28 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,51 @@ | ||
[package] | ||
name = "fedibook" | ||
version = "0.0.0" | ||
authors = ["Eric Chadbourne <sillystring@protonmail.com>", "Peter Alexander <drbanjofox@protonmail.com>", "Elijah Mark Anderson <kd0bpv@gmail.com>"] | ||
version = "0.1.0" | ||
authors = ["Eric Chadbourne <sillystring@protonmail.com>", "Peter Alexander <drbanjofox@protonmail.com>", "Elijah Mark Anderson <kd0bpv@gmail.com>", "Paul Woolcock <paul@woolcock.us>"] | ||
|
||
[lib] | ||
name = "_fedibook" | ||
path = "src/fedibook/lib.rs" | ||
|
||
[[bin]] | ||
name = "fedibook-server" | ||
path = "src/bin/main.rs" | ||
|
||
[dependencies] | ||
base64 = "0.7" | ||
bcrypt = "0.1" | ||
collection_macros = "0.2.0" | ||
derive_builder = "0.5.0" | ||
failure = "0.1.0" | ||
failure_derive = "0.1.1" | ||
libxml = "0.0.7481" | ||
r2d2 = "0.7" | ||
r2d2-diesel = "0.16" | ||
ring = "0.11" | ||
rocket = "0.3.3" | ||
rocket_codegen = "0.3.3" | ||
serde = "1.0.21" | ||
serde_derive = "1.0.21" | ||
|
||
[dependencies.chrono] | ||
version = "0.4" | ||
features = ["serde"] | ||
|
||
[dependencies.uuid] | ||
version = "0.5" | ||
features = ["v4"] | ||
|
||
[dependencies.diesel] | ||
version = "0.16" | ||
default-features = false | ||
features = ["postgres", "uuid", "chrono"] | ||
|
||
[dependencies.diesel_codegen] | ||
version = "0.16" | ||
default-features = false | ||
features = ["postgres"] | ||
|
||
[dependencies.rocket_contrib] | ||
version = "0.3.3" | ||
default-features = false | ||
features = ["handlebars_templates", "json"] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
[development] | ||
port = 7878 # https://twitter.com/ag_dubs/status/852559264510070784 | ||
database_url = "postgres://localhost/fedibook_development" | ||
|
||
[staging] | ||
|
||
[production] | ||
|
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass); | ||
DROP FUNCTION IF EXISTS diesel_set_updated_at(); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,36 @@ | ||
-- 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; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
DROP EXTENSION IF EXISTS "pgcrypto"; | ||
DROP SCHEMA IF EXISTS fedibook; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
CREATE SCHEMA IF NOT EXISTS fedibook; | ||
|
||
CREATE EXTENSION IF NOT EXISTS "pgcrypto"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
DROP INDEX IF EXISTS id_idx; | ||
DROP INDEX IF EXISTS username_domain_unique_idx; | ||
DROP TABLE IF EXISTS fedibook.accounts; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
CREATE TABLE IF NOT EXISTS fedibook.accounts ( | ||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | ||
username VARCHAR NOT NULL DEFAULT '', | ||
domain VARCHAR, | ||
display_name VARCHAR NOT NULL DEFAULT '', | ||
|
||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() at time zone 'utc'), | ||
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() at time zone 'utc') | ||
); | ||
|
||
CREATE UNIQUE INDEX IF NOT EXISTS id_idx ON fedibook.accounts (id); | ||
CREATE UNIQUE INDEX IF NOT EXISTS username_domain_unique_idx ON fedibook.accounts (username, domain); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
DROP INDEX IF EXISTS email_idx; | ||
DROP TABLE IF EXISTS fedibook.users; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
CREATE TABLE IF NOT EXISTS fedibook.users ( | ||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), | ||
email VARCHAR NOT NULL DEFAULT '', | ||
encrypted_password VARCHAR NOT NULL, | ||
account_id UUID NOT NULL, | ||
|
||
-- flags | ||
admin BOOLEAN NOT NULL DEFAULT false, | ||
disabled BOOLEAN NOT NULL DEFAULT false, | ||
|
||
-- confirmation stuff | ||
unconfirmed_email VARCHAR NOT NULL DEFAULT '', | ||
confirmation_token BYTEA NOT NULL, | ||
confirmed_at TIMESTAMP WITHOUT TIME ZONE, | ||
confirmation_sent_at TIMESTAMP WITHOUT TIME ZONE, | ||
|
||
-- timestamps | ||
created_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() at time zone 'utc'), | ||
updated_at TIMESTAMP WITHOUT TIME ZONE NOT NULL DEFAULT (now() at time zone 'utc'), | ||
|
||
FOREIGN KEY (account_id) REFERENCES fedibook.accounts (id) | ||
); | ||
|
||
CREATE INDEX IF NOT EXISTS email_idx ON fedibook.users (email); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,104 @@ | ||
# Rust backend | ||
|
||
Since the initial PR for this is rather large, I'm including a write-up | ||
here to hopefully aid in the code review process. | ||
|
||
The rust app here uses primarily [Rocket.rs](https://rocket.rs) and | ||
[Diesel](https://diesel.rs), along with a number of smaller crates to | ||
fill in any gaps. There are two primary entry points into the code, | ||
`src/bin/main.rs` and `src/fedibook/lib.rs`. This is a pretty standard | ||
setup for rust projects, in which the bulk of the code is in a | ||
library-style crate (`src/fedibook/`) and the main runnable binary | ||
is a smaller crate that sets up a few things and then calls into the | ||
library crate. Keeping this separation is useful for adding other | ||
binaries down the line, and it doesn't really cost us anything now. | ||
|
||
## src/bin/main.rs | ||
|
||
This is the file that, when compiled, implements the actual runnable | ||
binary. It will be where we set up all the necessary data structures | ||
that the webapp needs to run, gathers config from the user (right now | ||
it's just using the standard `Rocket.toml` file that rocket.rs expects), | ||
and sets up the various routes that are actually defined in the library | ||
crate. | ||
|
||
Currently we are mounting routes under 2 prefixes: `"/"` and | ||
`"/api/v1"`, though there is nothing actually implemented under | ||
`"/api/v1"`, other than a placeholder example route. All the URLs | ||
mounted under `"/"` are routes that you would expect a user to request | ||
in their browser. The routes that are currently written implement a | ||
barebones user login/registration system, though there are still many | ||
things to do to improve that system. | ||
|
||
Routes under the `"/api/v1"` prefix are routes that should not be | ||
directly browsed to, but will use OAuth for authentication, accept and | ||
return JSON, and would primarily be used via XMLHttpRequests from the | ||
browser webapp (or via cURL during development). | ||
|
||
## src/fedibook/ | ||
|
||
This is where the real meat of the application is. The main entry point | ||
for the library crate is `src/fedibook/lib.rs`, that has all the `mod` | ||
statements and such. There are a few directories from here that should | ||
be pretty self-explanatory. `controllers`, `forms`, `models`, and | ||
`routes`. The `routes` module contain the definitions for the actual | ||
endpoints, though I've tried to keep those small and move the bulk of | ||
the logic into the controllers. | ||
|
||
There is a special file, `src/fedibook/schema.rs`, that holds the | ||
generated database schema. This is generated by: | ||
|
||
1. Installing `diesel_cli`: `$ cargo install diesel_cli --no-default-features --features "postgres"` | ||
2. Running `diesel print-schema --with-docs -s fedibook > src/fedibook/schema.rs` | ||
|
||
The version of `diesel` that was used to develop this has a small bug in | ||
which the `joinable!()` invocation at the bottom of the schema.rs file | ||
had to be slightly changed, but the next version of `diesel` does not | ||
have that bug and I am working on upgrading `fedibook` to that version. | ||
|
||
## Migrations | ||
|
||
In the `migrations/` directory, you will find all the database | ||
migrations that `diesel` will need to properly set up your database. | ||
|
||
## Templates | ||
|
||
In the `templates/` directory, you will find the templates that `rocket` | ||
uses to serve the login, logout, register, and home pages (barely more | ||
than placeholders right now) | ||
|
||
## In the browser | ||
|
||
To go with the user registration/login system, I have a handful of | ||
(very) barebones templates that can be used to register, login, and | ||
logout in a browser. Assuming that rust, postgres, and diesel are | ||
already installed, the workflow goes like this: | ||
|
||
* start postgres. make sure you can successfully connect to it | ||
* cd into the fedibook directory and set up the database (this is only | ||
necessary the first time): | ||
* make sure diesel can connect to your database: | ||
`export DATABASE_URL="postgres://user:pass@host/dbname"` | ||
* `diesel setup` should create the database | ||
* `diesel migration run` should run all the migrations | ||
* start the app: `cargo run` (or `cargo run --bin fedibook-server` if | ||
you want to be explicit) | ||
* When you see "Rocket has launched from http://...." you should be | ||
able to connect to the running app | ||
* Assuming you run the app on `localhost` and use the `7878` port, you | ||
can browse to `http://localhost:7878/auth/sign_up` and register a | ||
user account | ||
* If the registration succeeds, it will redirect you to | ||
`http://localhost:7878/auth/sign_in`. You won't actually be able to | ||
sign in yet, you have to confirm your account. | ||
* Go back to the command line, you should see a message that has a | ||
path at the end with the token to confirm your account. | ||
* Go to `http://localhost:7878<the path from the console>` in your | ||
browser | ||
* If the confirmation succeeded, it should redirect you back to the | ||
`/auth/sign_in` page, where you should be able to log in now. | ||
* If the log in succeeds, it should redirect you to `/web` and show | ||
you a message, and a "logout" button | ||
|
||
Going to `/web` without logging in should redirect you to the login page | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
#![feature(plugin)] | ||
#![feature(custom_derive)] | ||
#![plugin(rocket_codegen)] | ||
|
||
extern crate rocket; | ||
extern crate failure; | ||
extern crate rocket_contrib; | ||
extern crate serde; | ||
extern crate r2d2; | ||
extern crate r2d2_diesel; | ||
extern crate ring; | ||
extern crate diesel; | ||
|
||
extern crate _fedibook as fedibook; | ||
|
||
use ring::rand::SystemRandom; | ||
use rocket::Rocket; | ||
use rocket_contrib::Template; | ||
use diesel::pg::PgConnection; | ||
use r2d2_diesel::ConnectionManager; | ||
|
||
type Pool = r2d2::Pool<ConnectionManager<PgConnection>>; | ||
|
||
fn db_pool(rocket: &Rocket) -> Pool { | ||
let database_url = rocket.config().get_str("database_url").expect("Must set DATABASE_URL"); | ||
let config = r2d2::Config::default(); | ||
let manager = ConnectionManager::<PgConnection>::new(database_url); | ||
r2d2::Pool::new(config, manager).expect("Could not get DB connection pool") | ||
} | ||
|
||
fn app() -> Rocket { | ||
let r = rocket::ignite() | ||
.mount("/api/v1", routes![ | ||
fedibook::routes::applications::register_application | ||
]) | ||
.mount("/", routes![ | ||
fedibook::routes::auth::sign_up_form, | ||
fedibook::routes::auth::sign_in_form, | ||
fedibook::routes::auth::sign_up, | ||
fedibook::routes::auth::sign_in, | ||
fedibook::routes::auth::confirm, | ||
fedibook::routes::auth::sign_out, | ||
|
||
fedibook::routes::app::home, | ||
fedibook::routes::app::home_redirect, | ||
]) | ||
.attach(Template::fairing()) | ||
.manage(SystemRandom::new()); | ||
|
||
// we need an instance of the app to access the config values in Rocket.toml, | ||
// so we pass it to the db_pool function, get the pool, and _then_ return the instance | ||
let pool = db_pool(&r); | ||
r.manage(pool) | ||
} | ||
|
||
fn main() { | ||
app().launch(); | ||
} | ||
|
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
use models::apps::{App, AppId, AppIdBuilder}; | ||
|
||
#[derive(Fail, Debug)] | ||
#[fail(display = "Failed to create app.")] | ||
pub(crate) struct CreateAppError; | ||
|
||
pub(crate) fn create(app: App) -> Result<AppId, CreateAppError> { | ||
// store the app somewhere | ||
Ok(AppIdBuilder::default() | ||
.id("foo") | ||
.client_id("bar") | ||
.client_secret("baz") | ||
.build() | ||
.unwrap()) | ||
} |
Oops, something went wrong.