Added most relevant code for blog
parent
f98f0e2d4e
commit
07f25219d6
|
@ -0,0 +1,2 @@
|
||||||
|
# This file is used by diesel to find the development database
|
||||||
|
DATABASE_URL=postgres://rb:rb@localhost:5433/rb
|
|
@ -0,0 +1,21 @@
|
||||||
|
# ---> Rust
|
||||||
|
# Generated by Cargo
|
||||||
|
# will have compiled files and executables
|
||||||
|
debug/
|
||||||
|
out/
|
||||||
|
|
||||||
|
# Remove Cargo.lock from gitignore if creating an executable, leave it for libraries
|
||||||
|
# More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html
|
||||||
|
# Cargo.lock
|
||||||
|
|
||||||
|
# These are backup files generated by rustfmt
|
||||||
|
**/*.rs.bk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
# Added by cargo
|
||||||
|
|
||||||
|
/target
|
||||||
|
|
||||||
|
.vim/
|
||||||
|
vendor/
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,42 @@
|
||||||
|
[package]
|
||||||
|
name = "rb-blog"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
name = "rb_blog"
|
||||||
|
path = "src/lib.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rb-blog"
|
||||||
|
path = "src/main.rs"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
# Backend web framework
|
||||||
|
rocket = { version = "0.5.0-rc.1", features = [ "json", "uuid" ] }
|
||||||
|
# Used to provide Rocket routes with database connections
|
||||||
|
rocket_sync_db_pools = { version = "0.1.0-rc.1", default_features = false, features = [ "diesel_postgres_pool" ] }
|
||||||
|
# Used to (de)serialize JSON
|
||||||
|
serde = { version = "1.0.127", features = [ "derive" ] }
|
||||||
|
# ORM
|
||||||
|
diesel = { version = "1.4.7", features = ["postgres", "uuidv07", "chrono"] }
|
||||||
|
diesel_migrations = "1.4.0"
|
||||||
|
# To properly compile libpq statically
|
||||||
|
openssl = "0.10.36"
|
||||||
|
# For password hashing & verification
|
||||||
|
rust-argon2 = "0.8.3"
|
||||||
|
rand = "0.8.4"
|
||||||
|
uuid = { version = "0.8.2", features = ["serde"] }
|
||||||
|
# Authentification
|
||||||
|
jwt = "0.14.0"
|
||||||
|
hmac = "*"
|
||||||
|
sha2 = "*"
|
||||||
|
# Timestamps for JWT tokens
|
||||||
|
chrono = { version = "*", features = [ "serde" ] }
|
||||||
|
# Encoding of refresh tokens
|
||||||
|
base64 = "0.13.0"
|
||||||
|
# Reading in configuration files
|
||||||
|
figment = { version = "*", features = [ "yaml" ] }
|
||||||
|
mimalloc = { version = "0.1.26", default_features = false }
|
|
@ -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,0 +1,6 @@
|
||||||
|
-- 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.
|
||||||
|
|
||||||
|
DROP FUNCTION IF EXISTS diesel_manage_updated_at(_tbl regclass);
|
||||||
|
DROP FUNCTION IF EXISTS diesel_set_updated_at();
|
|
@ -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;
|
|
@ -0,0 +1,7 @@
|
||||||
|
-- This file should undo anything in `up.sql`
|
||||||
|
drop trigger insert_enforce_post_titles on posts;
|
||||||
|
drop trigger update_enforce_post_titles on posts;
|
||||||
|
drop function enforce_post_titles;
|
||||||
|
|
||||||
|
drop table posts cascade;
|
||||||
|
drop table sections cascade;
|
|
@ -0,0 +1,58 @@
|
||||||
|
-- Your SQL goes here
|
||||||
|
create table sections (
|
||||||
|
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||||
|
|
||||||
|
-- Title of the section
|
||||||
|
title varchar(255) UNIQUE NOT NULL,
|
||||||
|
-- Name to use when routing (this just makes for prettier URLs)
|
||||||
|
shortname varchar(32) UNIQUE NOT NULL,
|
||||||
|
-- Optional description of the section
|
||||||
|
description text,
|
||||||
|
-- Wether to show the section in the default list on the homepage
|
||||||
|
is_default boolean NOT NULL DEFAULT false,
|
||||||
|
-- Wether the posts should contain titles or not
|
||||||
|
has_titles boolean NOT NULL DEFAULT true
|
||||||
|
);
|
||||||
|
|
||||||
|
create table posts (
|
||||||
|
id uuid DEFAULT gen_random_uuid() PRIMARY KEY,
|
||||||
|
|
||||||
|
section_id uuid NOT NULL REFERENCES sections(id) ON DELETE CASCADE,
|
||||||
|
-- Title of the post
|
||||||
|
-- Wether this is NULL or not is enforced using the enforce_post_titles trigger
|
||||||
|
title varchar(255),
|
||||||
|
-- Post date, defaults to today
|
||||||
|
publish_date date NOT NULL DEFAULT now(),
|
||||||
|
-- Content of the post
|
||||||
|
content text NOT NULL
|
||||||
|
);
|
||||||
|
|
||||||
|
create function enforce_post_titles() returns trigger as $enforce_post_titles$
|
||||||
|
begin
|
||||||
|
-- Check for a wrongfully null title
|
||||||
|
if new.title is null and exists (
|
||||||
|
select 1 from sections where id = new.section_id and has_titles
|
||||||
|
) then
|
||||||
|
raise exception 'Expected a post title, but got null.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
if new.title is not null and exists (
|
||||||
|
select 1 from sections where id = new.section_id and not has_titles
|
||||||
|
) then
|
||||||
|
raise exception 'Expected an empty post title, but got a value.';
|
||||||
|
end if;
|
||||||
|
|
||||||
|
return new;
|
||||||
|
end;
|
||||||
|
$enforce_post_titles$ language plpgsql;
|
||||||
|
|
||||||
|
create trigger insert_enforce_post_titles
|
||||||
|
before insert on posts
|
||||||
|
for each row
|
||||||
|
execute function enforce_post_titles();
|
||||||
|
|
||||||
|
create trigger update_enforce_post_titles
|
||||||
|
before update of title on posts
|
||||||
|
for each row
|
||||||
|
when (old.title is distinct from new.title)
|
||||||
|
execute function enforce_post_titles();
|
|
@ -0,0 +1,69 @@
|
||||||
|
binop_separator = "Front"
|
||||||
|
blank_lines_lower_bound = 0
|
||||||
|
blank_lines_upper_bound = 1
|
||||||
|
# Trying something new
|
||||||
|
brace_style = "AlwaysNextLine"
|
||||||
|
color = "Auto"
|
||||||
|
combine_control_expr = false
|
||||||
|
comment_width = 80
|
||||||
|
condense_wildcard_suffixes = false
|
||||||
|
control_brace_style = "AlwaysSameLine"
|
||||||
|
disable_all_formatting = false
|
||||||
|
edition = "2018"
|
||||||
|
emit_mode = "Files"
|
||||||
|
empty_item_single_line = true
|
||||||
|
enum_discrim_align_threshold = 0
|
||||||
|
error_on_line_overflow = false
|
||||||
|
error_on_unformatted = false
|
||||||
|
fn_args_layout = "Tall"
|
||||||
|
fn_single_line = false
|
||||||
|
force_explicit_abi = true
|
||||||
|
force_multiline_blocks = false
|
||||||
|
format_code_in_doc_comments = false
|
||||||
|
format_macro_bodies = true
|
||||||
|
format_macro_matchers = false
|
||||||
|
format_strings = false
|
||||||
|
group_imports = "StdExternalCrate"
|
||||||
|
hard_tabs = false
|
||||||
|
hide_parse_errors = false
|
||||||
|
ignore = []
|
||||||
|
imports_granularity = "Crate"
|
||||||
|
imports_indent = "Block"
|
||||||
|
imports_layout = "Mixed"
|
||||||
|
indent_style = "Block"
|
||||||
|
inline_attribute_width = 0
|
||||||
|
license_template_path = ""
|
||||||
|
make_backup = false
|
||||||
|
match_arm_blocks = true
|
||||||
|
match_arm_leading_pipes = "Never"
|
||||||
|
match_block_trailing_comma = true
|
||||||
|
max_width = 100
|
||||||
|
merge_derives = true
|
||||||
|
newline_style = "Auto"
|
||||||
|
normalize_comments = false
|
||||||
|
normalize_doc_attributes = false
|
||||||
|
overflow_delimited_expr = false
|
||||||
|
remove_nested_parens = true
|
||||||
|
reorder_impl_items = false
|
||||||
|
reorder_imports = true
|
||||||
|
reorder_modules = true
|
||||||
|
report_fixme = "Always"
|
||||||
|
report_todo = "Always"
|
||||||
|
required_version = "1.4.37"
|
||||||
|
skip_children = false
|
||||||
|
space_after_colon = true
|
||||||
|
space_before_colon = false
|
||||||
|
spaces_around_ranges = false
|
||||||
|
struct_field_align_threshold = 0
|
||||||
|
struct_lit_single_line = true
|
||||||
|
tab_spaces = 4
|
||||||
|
trailing_comma = "Vertical"
|
||||||
|
trailing_semicolon = true
|
||||||
|
type_punctuation_density = "Wide"
|
||||||
|
unstable_features = false
|
||||||
|
use_field_init_shorthand = false
|
||||||
|
use_small_heuristics = "Default"
|
||||||
|
use_try_shorthand = false
|
||||||
|
version = "One"
|
||||||
|
where_single_line = false
|
||||||
|
wrap_comments = false
|
|
@ -0,0 +1,8 @@
|
||||||
|
//! The db module contains all Diesel-related logic. This is to prevent the various Diesel imports
|
||||||
|
//! from poluting other modules' namespaces.
|
||||||
|
|
||||||
|
pub mod posts;
|
||||||
|
pub mod sections;
|
||||||
|
|
||||||
|
pub use posts::{NewPost, PatchPost, Post};
|
||||||
|
pub use sections::{NewSection, Section};
|
|
@ -0,0 +1,85 @@
|
||||||
|
use chrono::NaiveDate;
|
||||||
|
use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
errors::{RbError, RbOption, RbResult},
|
||||||
|
schema::{posts, posts::dsl::*},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Queryable, Serialize)]
|
||||||
|
pub struct Post
|
||||||
|
{
|
||||||
|
pub id: Uuid,
|
||||||
|
pub section_id: Uuid,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub publish_date: NaiveDate,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Insertable)]
|
||||||
|
#[table_name = "posts"]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NewPost
|
||||||
|
{
|
||||||
|
pub section_id: Uuid,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub publish_date: NaiveDate,
|
||||||
|
pub content: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, AsChangeset)]
|
||||||
|
#[table_name = "posts"]
|
||||||
|
pub struct PatchPost
|
||||||
|
{
|
||||||
|
pub section_id: Option<Uuid>,
|
||||||
|
pub title: Option<String>,
|
||||||
|
pub publish_date: Option<NaiveDate>,
|
||||||
|
pub content: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(conn: &PgConnection, offset_: u32, limit_: u32) -> RbResult<Vec<Post>>
|
||||||
|
{
|
||||||
|
Ok(posts
|
||||||
|
.offset(offset_.into())
|
||||||
|
.limit(limit_.into())
|
||||||
|
.load(conn)
|
||||||
|
.map_err(|_| RbError::DbError("Couldn't query posts."))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn find(conn: &PgConnection, id_: &Uuid) -> RbOption<Post>
|
||||||
|
{
|
||||||
|
match posts.find(id_).first(conn) {
|
||||||
|
Ok(val) => Ok(Some(val)),
|
||||||
|
Err(diesel::NotFound) => Ok(None),
|
||||||
|
_ => Err(RbError::DbError("Couldn't find post.")),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(conn: &PgConnection, new_post: &NewPost) -> RbResult<Post>
|
||||||
|
{
|
||||||
|
Ok(insert_into(posts)
|
||||||
|
.values(new_post)
|
||||||
|
.get_result(conn)
|
||||||
|
.map_err(|_| RbError::DbError("Couldn't insert post."))?)
|
||||||
|
|
||||||
|
// TODO check for conflict?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(conn: &PgConnection, post_id: &Uuid, patch_post: &PatchPost) -> RbResult<Post>
|
||||||
|
{
|
||||||
|
Ok(diesel::update(posts.filter(id.eq(post_id)))
|
||||||
|
.set(patch_post)
|
||||||
|
.get_result(conn)
|
||||||
|
.map_err(|_| RbError::DbError("Couldn't update post."))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(conn: &PgConnection, post_id: &Uuid) -> RbResult<()>
|
||||||
|
{
|
||||||
|
diesel::delete(posts.filter(id.eq(post_id)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|_| RbError::DbError("Couldn't delete post."))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,79 @@
|
||||||
|
use diesel::{insert_into, prelude::*, Insertable, PgConnection, Queryable};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
errors::{RbError, RbResult},
|
||||||
|
schema::{sections, sections::dsl::*},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Queryable, Serialize)]
|
||||||
|
pub struct Section
|
||||||
|
{
|
||||||
|
pub id: Uuid,
|
||||||
|
pub title: String,
|
||||||
|
pub shortname: String,
|
||||||
|
pub description: Option<String>,
|
||||||
|
pub is_default: bool,
|
||||||
|
pub has_titles: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, Insertable)]
|
||||||
|
#[table_name = "sections"]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct NewSection
|
||||||
|
{
|
||||||
|
title: String,
|
||||||
|
pub shortname: String,
|
||||||
|
description: Option<String>,
|
||||||
|
is_default: Option<bool>,
|
||||||
|
has_titles: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize, AsChangeset)]
|
||||||
|
#[table_name = "sections"]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct PatchSection
|
||||||
|
{
|
||||||
|
title: Option<String>,
|
||||||
|
shortname: Option<String>,
|
||||||
|
description: Option<String>,
|
||||||
|
is_default: Option<bool>,
|
||||||
|
has_titles: Option<bool>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(conn: &PgConnection, offset_: u32, limit_: u32) -> RbResult<Vec<Section>>
|
||||||
|
{
|
||||||
|
Ok(sections
|
||||||
|
.offset(offset_.into())
|
||||||
|
.limit(limit_.into())
|
||||||
|
.load(conn)
|
||||||
|
.map_err(|_| RbError::DbError("Couldn't query sections."))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(conn: &PgConnection, new_post: &NewSection) -> RbResult<Section>
|
||||||
|
{
|
||||||
|
Ok(insert_into(sections)
|
||||||
|
.values(new_post)
|
||||||
|
.get_result(conn)
|
||||||
|
.map_err(|_| RbError::DbError("Couldn't insert section."))?)
|
||||||
|
|
||||||
|
// TODO check for conflict?
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(conn: &PgConnection, post_id: &Uuid, patch_post: &PatchSection) -> RbResult<Section>
|
||||||
|
{
|
||||||
|
Ok(diesel::update(sections.filter(id.eq(post_id)))
|
||||||
|
.set(patch_post)
|
||||||
|
.get_result(conn)
|
||||||
|
.map_err(|_| RbError::DbError("Couldn't update section."))?)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(conn: &PgConnection, post_id: &Uuid) -> RbResult<()>
|
||||||
|
{
|
||||||
|
diesel::delete(sections.filter(id.eq(post_id)))
|
||||||
|
.execute(conn)
|
||||||
|
.map_err(|_| RbError::DbError("Couldn't delete section."))?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
|
@ -0,0 +1,108 @@
|
||||||
|
// This needs to be explicitely included before diesel is imported to make sure
|
||||||
|
// compilation succeeds in the release Docker image.
|
||||||
|
extern crate openssl;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate diesel_migrations;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate diesel;
|
||||||
|
|
||||||
|
use figment::{
|
||||||
|
providers::{Env, Format, Yaml},
|
||||||
|
Figment,
|
||||||
|
};
|
||||||
|
use rocket::{
|
||||||
|
fairing::AdHoc,
|
||||||
|
http::Status,
|
||||||
|
serde::json::{json, Value},
|
||||||
|
Build, Request, Rocket,
|
||||||
|
};
|
||||||
|
use rocket_sync_db_pools::database;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
|
pub mod db;
|
||||||
|
pub mod errors;
|
||||||
|
pub mod guards;
|
||||||
|
pub mod posts;
|
||||||
|
pub(crate) mod schema;
|
||||||
|
pub mod sections;
|
||||||
|
|
||||||
|
#[global_allocator]
|
||||||
|
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
|
||||||
|
|
||||||
|
#[database("postgres_rb")]
|
||||||
|
pub struct RbDbConn(diesel::PgConnection);
|
||||||
|
|
||||||
|
#[catch(default)]
|
||||||
|
fn default_catcher(status: Status, _: &Request) -> Value
|
||||||
|
{
|
||||||
|
json!({"status": status.code, "message": ""})
|
||||||
|
}
|
||||||
|
|
||||||
|
embed_migrations!();
|
||||||
|
|
||||||
|
async fn run_db_migrations(rocket: Rocket<Build>) -> Result<Rocket<Build>, Rocket<Build>>
|
||||||
|
{
|
||||||
|
let conn = RbDbConn::get_one(&rocket)
|
||||||
|
.await
|
||||||
|
.expect("database connection");
|
||||||
|
conn.run(|c| match embedded_migrations::run(c) {
|
||||||
|
Ok(()) => Ok(rocket),
|
||||||
|
Err(_) => Err(rocket),
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
|
||||||
|
// async fn create_admin_user<'a>(rocket: &'a Rocket<Orbit>)
|
||||||
|
// {
|
||||||
|
// let config = rocket.state::<RbConfig>().expect("RbConfig instance");
|
||||||
|
// let admin_user = config.admin_user.clone();
|
||||||
|
// let admin_pass = config.admin_pass.clone();
|
||||||
|
|
||||||
|
// let conn = RbDbConn::get_one(&rocket)
|
||||||
|
// .await
|
||||||
|
// .expect("database connection");
|
||||||
|
// conn.run(move |c| {
|
||||||
|
// admin::create_admin_user(c, &admin_user, &admin_pass).expect("failed to create admin user")
|
||||||
|
// })
|
||||||
|
// .await;
|
||||||
|
// }
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize, Clone)]
|
||||||
|
pub struct RbJwtConf
|
||||||
|
{
|
||||||
|
key: String,
|
||||||
|
refresh_token_size: usize,
|
||||||
|
refresh_token_expire: i64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Serialize)]
|
||||||
|
pub struct RbConfig
|
||||||
|
{
|
||||||
|
admin_user: String,
|
||||||
|
admin_pass: String,
|
||||||
|
jwt: RbJwtConf,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[launch]
|
||||||
|
fn rocket() -> _
|
||||||
|
{
|
||||||
|
let figment = Figment::from(rocket::config::Config::default())
|
||||||
|
.merge(Yaml::file("Rb.yaml").nested())
|
||||||
|
.merge(Env::prefixed("RB_").global());
|
||||||
|
|
||||||
|
// This mut is necessary when the "docs" or "web" feature is enabled, as these further modify
|
||||||
|
// the instance variable
|
||||||
|
rocket::custom(figment)
|
||||||
|
.attach(RbDbConn::fairing())
|
||||||
|
.attach(AdHoc::try_on_ignite(
|
||||||
|
"Run database migrations",
|
||||||
|
run_db_migrations,
|
||||||
|
))
|
||||||
|
// .attach(AdHoc::try_on_ignite("Create admin user", create_admin_user))
|
||||||
|
.attach(AdHoc::config::<RbConfig>())
|
||||||
|
.register("/", catchers![default_catcher])
|
||||||
|
.mount("/sections", routes![sections::create_section])
|
||||||
|
.mount("/posts", routes![posts::get, posts::create])
|
||||||
|
}
|
|
@ -0,0 +1,58 @@
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
db,
|
||||||
|
errors::{RbOption, RbResult},
|
||||||
|
guards::Admin,
|
||||||
|
RbDbConn,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[get("/?<offset>&<limit>")]
|
||||||
|
pub async fn get(conn: RbDbConn, offset: u32, limit: u32) -> RbResult<Json<Vec<db::Post>>>
|
||||||
|
{
|
||||||
|
Ok(Json(
|
||||||
|
conn.run(move |c| db::posts::get(c, offset, limit)).await?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/", data = "<new_post>")]
|
||||||
|
pub async fn create(
|
||||||
|
_admin: Admin,
|
||||||
|
conn: RbDbConn,
|
||||||
|
new_post: Json<db::NewPost>,
|
||||||
|
) -> RbResult<Json<db::Post>>
|
||||||
|
{
|
||||||
|
Ok(Json(
|
||||||
|
conn.run(move |c| db::posts::create(c, &new_post.into_inner()))
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/<id>")]
|
||||||
|
pub async fn find(conn: RbDbConn, id: uuid::Uuid) -> RbOption<Json<db::Post>>
|
||||||
|
{
|
||||||
|
Ok(conn
|
||||||
|
.run(move |c| db::posts::find(c, &id))
|
||||||
|
.await?
|
||||||
|
.and_then(|p| Some(Json(p))))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[patch("/<id>", data = "<patch_post>")]
|
||||||
|
pub async fn patch(
|
||||||
|
_admin: Admin,
|
||||||
|
conn: RbDbConn,
|
||||||
|
id: uuid::Uuid,
|
||||||
|
patch_post: Json<db::PatchPost>,
|
||||||
|
) -> RbResult<Json<db::Post>>
|
||||||
|
{
|
||||||
|
Ok(Json(
|
||||||
|
conn.run(move |c| db::posts::update(c, &id, &patch_post.into_inner()))
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[delete("/<id>")]
|
||||||
|
pub async fn delete(_admin: Admin, conn: RbDbConn, id: uuid::Uuid) -> RbResult<()>
|
||||||
|
{
|
||||||
|
Ok(conn.run(move |c| db::posts::delete(c, &id)).await?)
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
//! This module handles management of site sections (aka blogs).
|
||||||
|
|
||||||
|
use rocket::serde::json::Json;
|
||||||
|
|
||||||
|
use crate::{db, errors::RbResult, guards::Admin, RbDbConn};
|
||||||
|
|
||||||
|
/// Route for creating a new section.
|
||||||
|
///
|
||||||
|
/// # Arguments
|
||||||
|
///
|
||||||
|
/// * `_admin` - guard ensuring user is admin
|
||||||
|
/// * `conn` - guard providing a connection to the database
|
||||||
|
/// * `new_section` - Json-encoded NewSection object
|
||||||
|
#[post("/", data = "<new_section>")]
|
||||||
|
pub async fn create_section(
|
||||||
|
_admin: Admin,
|
||||||
|
conn: RbDbConn,
|
||||||
|
new_section: Json<db::NewSection>,
|
||||||
|
) -> RbResult<Json<db::Section>>
|
||||||
|
{
|
||||||
|
Ok(Json(
|
||||||
|
conn.run(move |c| db::sections::create(c, &new_section.into_inner()))
|
||||||
|
.await?,
|
||||||
|
))
|
||||||
|
}
|
Reference in New Issue