mirror of
https://github.com/Blair-SGA-Dev-Team/blazerapp.git
synced 2024-11-21 12:31:16 -05:00
why does proc macro not work
This commit is contained in:
parent
9d6901c499
commit
5318c59880
2
.gitignore
vendored
2
.gitignore
vendored
|
@ -60,6 +60,8 @@ buck-out/
|
||||||
|
|
||||||
# Rust
|
# Rust
|
||||||
/target/
|
/target/
|
||||||
|
/cms/target/
|
||||||
|
/cms/cms_macro/target/
|
||||||
|
|
||||||
# Environment
|
# Environment
|
||||||
*.env
|
*.env
|
||||||
|
|
1605
Cargo.lock
generated
1605
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
|
@ -3,14 +3,39 @@ name = "cms"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2018"
|
edition = "2018"
|
||||||
|
|
||||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
rocket = "0.5.0-rc.1"
|
rocket = "0.4.10"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
oauth2 = { version = "4.1.0", default-features = false }
|
|
||||||
clap = "2.33.3"
|
clap = "2.33.3"
|
||||||
ansi_term = "0.12.1"
|
ansi_term = "0.12.1"
|
||||||
chrono = "0.4.19"
|
chrono = "0.4.19"
|
||||||
diesel = { version = "1.4.4", features = ["postgres", "extras"] }
|
diesel_migrations = "1.4.0"
|
||||||
|
url = "2.2.2"
|
||||||
|
oauth2 = "4.1.0"
|
||||||
|
serde_json = "1.0.66"
|
||||||
|
base64 = "0.13.0"
|
||||||
|
quote = "1.0.9"
|
||||||
|
|
||||||
|
[dependencies.reqwest]
|
||||||
|
version = "0.11.4"
|
||||||
|
features = ["blocking", "json"]
|
||||||
|
|
||||||
|
[dependencies.diesel]
|
||||||
|
version = "1.4.4"
|
||||||
|
features = ["postgres", "extras"]
|
||||||
|
|
||||||
|
[dependencies.rocket_contrib]
|
||||||
|
version = "0.4.10"
|
||||||
|
default-features = false
|
||||||
|
features = ["handlebars_templates", "diesel_postgres_pool", "json"]
|
||||||
|
|
||||||
|
[dependencies.serde]
|
||||||
|
version = "1.0.126"
|
||||||
|
features = ["derive"]
|
||||||
|
|
||||||
|
[dependencies.rand]
|
||||||
|
version = "0.8.4"
|
||||||
|
features = ["getrandom"]
|
||||||
|
|
||||||
|
[dependencies.cms_macro]
|
||||||
|
path = "cms_macro"
|
||||||
|
|
10
cms/Dockerfile
Normal file
10
cms/Dockerfile
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
FROM rustlang/rust:nightly AS build
|
||||||
|
COPY / /build
|
||||||
|
WORKDIR /build
|
||||||
|
RUN cargo build --release
|
||||||
|
|
||||||
|
FROM ubuntu:20.04 AS release
|
||||||
|
WORKDIR /root
|
||||||
|
COPY --from=build /build/target/release/cms ./cms
|
||||||
|
CMD [ "./cms" ]
|
||||||
|
|
17
cms/cms_macro/Cargo.toml
Normal file
17
cms/cms_macro/Cargo.toml
Normal file
|
@ -0,0 +1,17 @@
|
||||||
|
[package]
|
||||||
|
name = "cms_macro"
|
||||||
|
version = "0.1.0"
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
[lib]
|
||||||
|
proc-macro=true
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
quote = "1.0.9"
|
||||||
|
proc-macro2 = "1.0.28"
|
||||||
|
|
||||||
|
[dependencies.diesel]
|
||||||
|
version = "1.4.4"
|
||||||
|
features = ["postgres", "extras"]
|
311
cms/cms_macro/src/lib.rs
Normal file
311
cms/cms_macro/src/lib.rs
Normal file
|
@ -0,0 +1,311 @@
|
||||||
|
extern crate proc_macro;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate quote;
|
||||||
|
|
||||||
|
use proc_macro2::*;
|
||||||
|
use std::fmt::Debug;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Row(Ident, Ident, Ident, Ident);
|
||||||
|
|
||||||
|
fn api_route_inner(item: TokenStream) -> TokenStream {
|
||||||
|
let iterator = item.into_iter().clone().collect::<Vec<TokenTree>>();
|
||||||
|
let name = iterator[0].clone();
|
||||||
|
let name_literal = &iterator[0].clone().to_string();
|
||||||
|
let name_dir = format!("/{}", name_literal);
|
||||||
|
let name_add = format!("/{}/add", name_literal);
|
||||||
|
let name_upd = format!("/{}/upd", name_literal);
|
||||||
|
let name_del = format!("/{}/del", name_literal);
|
||||||
|
let name_api = format!("/<lang>/{}", name_literal);
|
||||||
|
let name_redir = format!("/ui/{}", name_literal);
|
||||||
|
|
||||||
|
let value_group = iterator[1].clone();
|
||||||
|
|
||||||
|
let (delim, stream) = match value_group {
|
||||||
|
TokenTree::Group(g) => (g.delimiter(), g.stream()),
|
||||||
|
n => panic!("Incorrect syntax at brace => {:?}", n),
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(delim, Delimiter::Brace);
|
||||||
|
let splitted = &mut stream.into_iter().clone().collect::<Vec<TokenTree>>()[..]
|
||||||
|
.split(|token| {
|
||||||
|
if let TokenTree::Punct(p) = token.clone() {
|
||||||
|
p.as_char() == ',' && p.spacing() == Spacing::Alone
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(Vec::from)
|
||||||
|
.collect::<Vec<Vec<TokenTree>>>();
|
||||||
|
|
||||||
|
splitted.pop();
|
||||||
|
|
||||||
|
let mut rows: Vec<Row> = vec![];
|
||||||
|
|
||||||
|
for vecs in splitted.into_iter() {
|
||||||
|
let mut vals_iter = vecs.split(|token| {
|
||||||
|
if let TokenTree::Punct(p) = token.clone() {
|
||||||
|
p.as_char() == ':' && p.spacing() == Spacing::Alone
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let name = vals_iter.next().unwrap().to_vec();
|
||||||
|
let vals = vals_iter.next().unwrap().to_vec();
|
||||||
|
let (brack, val_stream) = match vals.get(0).unwrap() {
|
||||||
|
TokenTree::Group(g) => (g.delimiter(), g.stream()),
|
||||||
|
n => panic!("Incorrect syntax at parenthesis => {:?}", n),
|
||||||
|
};
|
||||||
|
assert_eq!(brack, Delimiter::Parenthesis);
|
||||||
|
assert_eq!(name.len(), 1);
|
||||||
|
let name = match name.get(0).unwrap() {
|
||||||
|
TokenTree::Ident(g) => g,
|
||||||
|
n => panic!("Incorrect syntax at name => {:?}", n),
|
||||||
|
};
|
||||||
|
|
||||||
|
let vals = &val_stream.into_iter().clone().collect::<Vec<TokenTree>>()[..]
|
||||||
|
.split(|token| {
|
||||||
|
if let TokenTree::Punct(p) = token.clone() {
|
||||||
|
p.as_char() == ',' && p.spacing() == Spacing::Alone
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.map(|x| match x.to_vec().get(0).unwrap().clone() {
|
||||||
|
TokenTree::Ident(i) => i,
|
||||||
|
n => panic!("Incorrect syntax at group => {:?}", n),
|
||||||
|
})
|
||||||
|
.collect::<Vec<Ident>>();
|
||||||
|
rows.push(Row(
|
||||||
|
name.clone(),
|
||||||
|
vals.get(0).unwrap().clone(),
|
||||||
|
vals.get(1).unwrap().clone(),
|
||||||
|
vals.get(2).unwrap().clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
let names_vec = rows
|
||||||
|
.iter()
|
||||||
|
.map(|x| TokenTree::Ident(x.0.clone()))
|
||||||
|
.collect::<Vec<TokenTree>>();
|
||||||
|
|
||||||
|
let schema_value = rows
|
||||||
|
.iter()
|
||||||
|
.map(|x| TokenTree::Ident(x.1.clone()))
|
||||||
|
.collect::<Vec<TokenTree>>();
|
||||||
|
|
||||||
|
let get_value = rows
|
||||||
|
.iter()
|
||||||
|
.map(|x| TokenTree::Ident(x.2.clone()))
|
||||||
|
.collect::<Vec<TokenTree>>();
|
||||||
|
|
||||||
|
let put_value = rows
|
||||||
|
.iter()
|
||||||
|
.map(|x| TokenTree::Ident(x.3.clone()))
|
||||||
|
.collect::<Vec<TokenTree>>();
|
||||||
|
|
||||||
|
let impl_value = rows
|
||||||
|
.iter()
|
||||||
|
.map(|x| {
|
||||||
|
if x.2.to_string() == x.3.to_string() {
|
||||||
|
let s = x.0.clone();
|
||||||
|
quote! {
|
||||||
|
#s: self.#s,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let s = x.0.clone();
|
||||||
|
quote! {
|
||||||
|
#s: *self.#s,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.collect::<Vec<TokenStream>>()
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| x.into_iter().collect::<Vec<TokenTree>>())
|
||||||
|
.flatten()
|
||||||
|
.collect::<Vec<TokenTree>>();
|
||||||
|
|
||||||
|
let impls = quote! {
|
||||||
|
|
||||||
|
impl Post {
|
||||||
|
fn convert(self) -> Create {
|
||||||
|
Create {
|
||||||
|
lang: self.lang,
|
||||||
|
#(#impl_value)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Update {
|
||||||
|
fn convert(self) -> Create {
|
||||||
|
Create {
|
||||||
|
lang: self.lang,
|
||||||
|
#(#impl_value)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let schema = quote! {
|
||||||
|
|
||||||
|
pub mod schema {
|
||||||
|
table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
|
||||||
|
#name (id) {
|
||||||
|
id -> Integer,
|
||||||
|
lang -> Text,
|
||||||
|
#(#names_vec -> #schema_value,)
|
||||||
|
*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let structs = quote! {
|
||||||
|
|
||||||
|
use schema::#name;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Queryable, Serialize)]
|
||||||
|
pub struct Get {
|
||||||
|
pub id: i32,
|
||||||
|
pub lang: String,
|
||||||
|
#(pub #names_vec: #get_value),
|
||||||
|
*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, AsChangeset, Insertable)]
|
||||||
|
#[table_name = #name_literal]
|
||||||
|
pub struct Create {
|
||||||
|
pub lang: String,
|
||||||
|
#(pub #names_vec: #get_value),
|
||||||
|
*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Post {
|
||||||
|
pub lang: String,
|
||||||
|
#(pub #names_vec: #put_value),
|
||||||
|
*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Update {
|
||||||
|
pub id: i32,
|
||||||
|
pub lang: String,
|
||||||
|
#(pub #names_vec: #put_value),
|
||||||
|
*
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Delete {
|
||||||
|
pub id: i32,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let imports = quote! {
|
||||||
|
use crate::data::{defs::*, Lang};
|
||||||
|
use crate::auth::Token;
|
||||||
|
use ::chrono::naive::*;
|
||||||
|
use ::diesel::{prelude::*, Insertable, Queryable};
|
||||||
|
use ::rocket::{http::Status, request::Form, response::Redirect, State};
|
||||||
|
use ::rocket_contrib::{json::Json, templates::Template};
|
||||||
|
use ::serde::Serialize;
|
||||||
|
use ::std::{collections::*, sync::Mutex};
|
||||||
|
};
|
||||||
|
|
||||||
|
let endpoints = quote! {
|
||||||
|
|
||||||
|
pub fn create(conn: &PgConnection, create: Create) -> Result<Get, diesel::result::Error> {
|
||||||
|
diesel::insert_into(#name::table)
|
||||||
|
.values(&create)
|
||||||
|
.get_result(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(conn: &PgConnection, lg: Lang) -> Result<Vec<Get>, diesel::result::Error> {
|
||||||
|
use schema::#name::dsl::*;
|
||||||
|
|
||||||
|
#name.filter(lang.eq(lg.0)).load::<Get>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all(conn: &PgConnection) -> Result<Vec<Get>, diesel::result::Error> {
|
||||||
|
use schema::#name::dsl::*;
|
||||||
|
#name.load::<Get>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
conn: &PgConnection,
|
||||||
|
idn: i32,
|
||||||
|
create: Create,
|
||||||
|
) -> Result<Get, diesel::result::Error> {
|
||||||
|
use schema::#name::dsl::*;
|
||||||
|
diesel::update(#name.find(idn))
|
||||||
|
.set(&create)
|
||||||
|
.get_result::<Get>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(conn: &PgConnection, idn: i32) -> Result<usize, diesel::result::Error> {
|
||||||
|
use schema::#name::dsl::*;
|
||||||
|
diesel::delete(#name.find(idn)).execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get(#name_api)]
|
||||||
|
pub fn api(pg: State<Mutex<PgConnection>>, lang: Lang) -> Result<Json<Vec<Get>>, Status> {
|
||||||
|
Ok(Json(
|
||||||
|
get(&*(pg.lock().unwrap()), lang).map_err(|_| Status::InternalServerError)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post(#name_add, data = "<form>")]
|
||||||
|
pub fn add(_token: Token, pg: State<Mutex<PgConnection>>, form: Form<Post>) -> Result<Redirect, Status> {
|
||||||
|
match create(&*(pg.lock().unwrap()), form.into_inner().convert()) {
|
||||||
|
Ok(_) => Ok(Redirect::to(#name_redir)),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post(#name_del, data = "<form>")]
|
||||||
|
pub fn del(_token: Token, pg: State<Mutex<PgConnection>>, form: Form<Delete>) -> Result<Redirect, Status> {
|
||||||
|
match delete(&*(pg.lock().unwrap()), form.id) {
|
||||||
|
Ok(_) => Ok(Redirect::to(#name_redir)),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post(#name_upd, data = "<form>")]
|
||||||
|
pub fn upd(_token: Token, pg: State<Mutex<PgConnection>>, form: Form<Update>) -> Result<Redirect, Status> {
|
||||||
|
match update(&*(pg.lock().unwrap()), form.id, form.into_inner().convert()) {
|
||||||
|
Ok(_) => Ok(Redirect::to(#name_redir)),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get(#name_dir)]
|
||||||
|
pub fn ui(_token: Token, pg: State<Mutex<PgConnection>>) -> Result<Template, Status> {
|
||||||
|
let ctx = get_all(&*(pg.lock().unwrap()))
|
||||||
|
.map_err(|_| Status::InternalServerError)?
|
||||||
|
.iter()
|
||||||
|
.map(|x| (x.id, x.clone()))
|
||||||
|
.collect::<HashMap<i32, Get>>();
|
||||||
|
Ok(Template::render(#name_literal, &ctx))
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let tok = quote! {
|
||||||
|
pub mod #name {
|
||||||
|
#imports
|
||||||
|
#schema
|
||||||
|
#structs
|
||||||
|
#impls
|
||||||
|
#endpoints
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tok.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[proc_macro]
|
||||||
|
pub fn api_route(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
|
api_route_inner(TokenStream::from(item)).into()
|
||||||
|
}
|
|
@ -1,2 +1,3 @@
|
||||||
DROP TABLE events;
|
DROP TABLE auth_val;
|
||||||
|
|
||||||
|
DROP TABLE events;
|
||||||
|
|
|
@ -1,5 +1,13 @@
|
||||||
|
CREATE TABLE auth_val (
|
||||||
|
id SERIAL PRIMARY KEY,
|
||||||
|
email VARCHAR
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO auth_val(email) VALUES('hkailadka88@gmail.com');
|
||||||
|
|
||||||
CREATE TABLE events (
|
CREATE TABLE events (
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
|
lang VARCHAR,
|
||||||
title VARCHAR NOT NULL,
|
title VARCHAR NOT NULL,
|
||||||
text VARCHAR,
|
text VARCHAR,
|
||||||
location VARCHAR NOT NULL,
|
location VARCHAR NOT NULL,
|
||||||
|
|
208
cms/src/auth.rs
208
cms/src/auth.rs
|
@ -1,3 +1,209 @@
|
||||||
pub fn auth() {
|
use diesel::{prelude::*, Queryable};
|
||||||
|
use oauth2::{
|
||||||
|
basic::BasicClient, reqwest::http_client, AuthUrl, AuthorizationCode, ClientId, ClientSecret,
|
||||||
|
CsrfToken, RedirectUrl, RevocationUrl, Scope, TokenResponse, TokenUrl,
|
||||||
|
};
|
||||||
|
use reqwest::blocking::Client;
|
||||||
|
use rocket::{
|
||||||
|
http::{Cookie, Cookies, SameSite, Status},
|
||||||
|
request,
|
||||||
|
request::FromRequest,
|
||||||
|
response::Redirect,
|
||||||
|
Outcome, Request, State,
|
||||||
|
};
|
||||||
|
use serde::Serialize;
|
||||||
|
use serde_json::Value;
|
||||||
|
use std::{fmt::Debug, sync::Mutex};
|
||||||
|
|
||||||
|
mod schema {
|
||||||
|
table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
|
||||||
|
auth_val (id) {
|
||||||
|
id -> Integer,
|
||||||
|
email -> Text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Host(String);
|
||||||
|
pub struct Token(String);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Settings {
|
||||||
|
pub id: String,
|
||||||
|
pub secret: String,
|
||||||
|
pub auth_url: AuthUrl,
|
||||||
|
pub token_url: TokenUrl,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Queryable, Serialize)]
|
||||||
|
struct Auth {
|
||||||
|
pub id: i32,
|
||||||
|
pub email: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_auth(conn: &PgConnection) -> Result<Vec<Auth>, diesel::result::Error> {
|
||||||
|
use schema::auth_val::dsl::*;
|
||||||
|
auth_val.load::<Auth>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'r> FromRequest<'a, 'r> for Host {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
let host = request.headers().get_one("Host");
|
||||||
|
match host {
|
||||||
|
Some(host) => Outcome::Success(Host(host.to_string())),
|
||||||
|
None => Outcome::Failure((Status::Unauthorized, ())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, 'r> FromRequest<'a, 'r> for Token {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn from_request(request: &'a Request<'r>) -> request::Outcome<Self, Self::Error> {
|
||||||
|
match request.cookies().get("token") {
|
||||||
|
Some(token) => {
|
||||||
|
let resp: Value = Client::new()
|
||||||
|
.get("https://www.googleapis.com/userinfo/v2/me")
|
||||||
|
.bearer_auth(token.name_value().1)
|
||||||
|
.send()
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
if resp["error"] != Value::Null {
|
||||||
|
return Outcome::Failure((Status::Forbidden, ()));
|
||||||
|
} else {
|
||||||
|
let email = resp["email"].clone();
|
||||||
|
let pg = request.guard::<State<Mutex<PgConnection>>>()?;
|
||||||
|
let diesel_op = get_auth(&*(pg.lock().unwrap()));
|
||||||
|
let auths: Vec<String> = match diesel_op {
|
||||||
|
Ok(n) => n.into_iter().map(|x| x.email).collect::<Vec<String>>(),
|
||||||
|
Err(_) => vec![],
|
||||||
|
};
|
||||||
|
|
||||||
|
if auths.into_iter().any(|x| x == email.as_str().unwrap_or("")) {
|
||||||
|
return Outcome::Success(Token(String::from(email.as_str().unwrap_or(""))));
|
||||||
|
} else {
|
||||||
|
return Outcome::Failure((Status::Forbidden, ()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Outcome::Failure((Status::Unauthorized, ())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/oauth")]
|
||||||
|
pub fn oauth(
|
||||||
|
mut cookies: Cookies,
|
||||||
|
settings: State<Settings>,
|
||||||
|
host: Host,
|
||||||
|
) -> Result<Redirect, Status> {
|
||||||
|
let client = get_client(settings.inner().clone(), host);
|
||||||
|
let csrf_token = CsrfToken::new_random();
|
||||||
|
let csrf: String = csrf_token.secret().into();
|
||||||
|
cookies.add(Cookie::new("state", csrf));
|
||||||
|
let (authorize_url, _csrf_state) = client
|
||||||
|
.authorize_url(|| csrf_token.clone())
|
||||||
|
.add_scope(Scope::new(
|
||||||
|
"https://www.googleapis.com/auth/userinfo.email".to_owned(),
|
||||||
|
))
|
||||||
|
.url();
|
||||||
|
let auth = authorize_url.to_string();
|
||||||
|
Ok(Redirect::to(auth))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/logout")]
|
||||||
|
pub fn logout(mut cookies: Cookies) -> Redirect {
|
||||||
|
match cookies.get("token") {
|
||||||
|
Some(_) => {
|
||||||
|
cookies.remove(Cookie::named("token"));
|
||||||
|
Redirect::to("/")
|
||||||
|
}
|
||||||
|
None => Redirect::to("/"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/callback?<state>&<code>")]
|
||||||
|
pub fn callback(
|
||||||
|
state: String,
|
||||||
|
code: String,
|
||||||
|
pg: State<Mutex<PgConnection>>,
|
||||||
|
mut cookies: Cookies,
|
||||||
|
host: Host,
|
||||||
|
sa: State<Settings>,
|
||||||
|
) -> Result<Redirect, Status> {
|
||||||
|
let sc = cookies.get("state");
|
||||||
|
match sc {
|
||||||
|
Some(c) => {
|
||||||
|
if state != c.value() {
|
||||||
|
return Err(Status::Forbidden);
|
||||||
|
} else {
|
||||||
|
cookies.remove(Cookie::named("state"));
|
||||||
|
let client = get_client(sa.inner().clone(), host);
|
||||||
|
let token_result = client
|
||||||
|
.exchange_code(AuthorizationCode::new(code))
|
||||||
|
.request(http_client);
|
||||||
|
match token_result {
|
||||||
|
Ok(n) => {
|
||||||
|
let secret = n.access_token().secret();
|
||||||
|
|
||||||
|
let resp: Value = Client::new()
|
||||||
|
.get("https://www.googleapis.com/userinfo/v2/me")
|
||||||
|
.bearer_auth(secret)
|
||||||
|
.send()
|
||||||
|
.unwrap()
|
||||||
|
.json()
|
||||||
|
.unwrap();
|
||||||
|
if resp["error"] != Value::Null {
|
||||||
|
return Err(Status::BadRequest);
|
||||||
|
} else {
|
||||||
|
let email = resp["email"].clone();
|
||||||
|
let diesel_op = get_auth(&*(pg.lock().unwrap()));
|
||||||
|
let auths: Vec<String> = match diesel_op {
|
||||||
|
Ok(n) => n.into_iter().map(|x| x.email).collect::<Vec<String>>(),
|
||||||
|
Err(_) => vec![],
|
||||||
|
};
|
||||||
|
if auths.into_iter().any(|x| x == email.as_str().unwrap_or("")) {
|
||||||
|
let mut cook = Cookie::new("token", secret.to_string());
|
||||||
|
cook.set_same_site(SameSite::Strict);
|
||||||
|
cook.set_http_only(true);
|
||||||
|
cook.set_secure(true);
|
||||||
|
cookies.add(cook);
|
||||||
|
return Ok(Redirect::to("/"));
|
||||||
|
} else {
|
||||||
|
return Err(Status::Forbidden);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(_) => return Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => Err(Status::BadRequest),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_client(settings: Settings, host: Host) -> BasicClient {
|
||||||
|
let gcid = ClientId::new(settings.id);
|
||||||
|
|
||||||
|
let gcs = ClientSecret::new(settings.secret);
|
||||||
|
|
||||||
|
let auth_url = settings.auth_url;
|
||||||
|
let token_url = settings.token_url;
|
||||||
|
|
||||||
|
let base: String = host.0.to_owned();
|
||||||
|
|
||||||
|
BasicClient::new(gcid, Some(gcs), auth_url, Some(token_url))
|
||||||
|
.set_redirect_uri(
|
||||||
|
RedirectUrl::new(format!("http://{}/callback", base)).expect("Invalid redirect URL"),
|
||||||
|
)
|
||||||
|
.set_revocation_uri(
|
||||||
|
RevocationUrl::new("https://oauth2.googleapis.com/revoke".to_owned())
|
||||||
|
.expect("Invalid revocation endpoint URL"),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,20 +0,0 @@
|
||||||
use diesel::prelude::*;
|
|
||||||
use super::models::{Event, NewEvent};
|
|
||||||
use super::super::utils::{exit_with_error};
|
|
||||||
|
|
||||||
pub fn create_event(conn: &PgConnection, event: NewEvent) -> Event {
|
|
||||||
|
|
||||||
use super::schema::events;
|
|
||||||
diesel::insert_into(events::table)
|
|
||||||
.values(&event)
|
|
||||||
.get_result(conn)
|
|
||||||
.unwrap_or_else(|_| exit_with_error("Error saving new post"))
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_all(conn: &PgConnection) -> Result<Vec<Event>, diesel::result::Error> {
|
|
||||||
|
|
||||||
use super::schema::events::dsl::*;
|
|
||||||
|
|
||||||
events.load::<Event>(conn)
|
|
||||||
}
|
|
|
@ -1,3 +1,238 @@
|
||||||
pub mod events;
|
#[macro_use]
|
||||||
pub mod schema;
|
use cms_macro::api_route;
|
||||||
pub mod models;
|
use rocket::{
|
||||||
|
http::{RawStr, Status},
|
||||||
|
request::FromParam,
|
||||||
|
};
|
||||||
|
use std::borrow::Cow;
|
||||||
|
|
||||||
|
pub struct Lang<'a>(Cow<'a, str>);
|
||||||
|
|
||||||
|
fn valid_lang(lang: &str) -> bool {
|
||||||
|
lang.chars().all(|c| (c >= 'a' && c <= 'z')) && lang.chars().count() == 2
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> FromParam<'a> for Lang<'a> {
|
||||||
|
type Error = Status;
|
||||||
|
fn from_param(param: &'a RawStr) -> Result<Lang<'a>, Status> {
|
||||||
|
match valid_lang(param) {
|
||||||
|
true => Ok(Lang(Cow::Borrowed(param))),
|
||||||
|
false => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub mod defs {
|
||||||
|
use chrono::naive::NaiveDate;
|
||||||
|
use rocket::{http::RawStr, request::FromFormValue};
|
||||||
|
use std::ops::Deref;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct DateForm(NaiveDate);
|
||||||
|
|
||||||
|
impl Deref for DateForm {
|
||||||
|
type Target = NaiveDate;
|
||||||
|
|
||||||
|
fn deref(&self) -> &NaiveDate {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'v> FromFormValue<'v> for DateForm {
|
||||||
|
type Error = ();
|
||||||
|
|
||||||
|
fn from_form_value(value: &'v RawStr) -> Result<DateForm, ()> {
|
||||||
|
let value_uri = match value.url_decode() {
|
||||||
|
Ok(n) => n,
|
||||||
|
Err(_) => return Err(()),
|
||||||
|
};
|
||||||
|
let naivedate = NaiveDate::parse_from_str(&value_uri[..], "%m/%d/%Y");
|
||||||
|
match naivedate {
|
||||||
|
Ok(n) => Ok(DateForm(n)),
|
||||||
|
Err(_) => Err(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
api_route! {
|
||||||
|
events {
|
||||||
|
title: (Text, String, String),
|
||||||
|
location: (Text, String, String),
|
||||||
|
text: (Text, String, String),
|
||||||
|
event_date: (Text, NaiveDate, DateForm),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
pub mod events {
|
||||||
|
|
||||||
|
use crate::data::{defs::*, Lang};
|
||||||
|
use crate::auth::Token;
|
||||||
|
use ::chrono::naive::*;
|
||||||
|
use ::diesel::{prelude::*, Insertable, Queryable};
|
||||||
|
use ::rocket::{http::Status, request::Form, response::Redirect, State};
|
||||||
|
use ::rocket_contrib::{json::Json, templates::Template};
|
||||||
|
use ::serde::Serialize;
|
||||||
|
use ::std::{collections::*, sync::Mutex};
|
||||||
|
|
||||||
|
pub mod schema {
|
||||||
|
table! {
|
||||||
|
use diesel::sql_types::*;
|
||||||
|
|
||||||
|
events (id) {
|
||||||
|
id -> Integer,
|
||||||
|
lang -> Text,
|
||||||
|
title -> Text,
|
||||||
|
location -> Text,
|
||||||
|
text -> Text,
|
||||||
|
event_date -> Date,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use schema::events;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Queryable, Serialize)]
|
||||||
|
pub struct Get {
|
||||||
|
pub id: i32,
|
||||||
|
pub lang: String,
|
||||||
|
pub title: String,
|
||||||
|
pub location: String,
|
||||||
|
pub text: String,
|
||||||
|
pub event_date: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, AsChangeset, Insertable)]
|
||||||
|
#[table_name = "events"]
|
||||||
|
pub struct Create {
|
||||||
|
pub lang: String,
|
||||||
|
pub title: String,
|
||||||
|
pub location: String,
|
||||||
|
pub text: String,
|
||||||
|
pub event_date: NaiveDate,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Post {
|
||||||
|
pub lang: String,
|
||||||
|
pub title: String,
|
||||||
|
pub location: String,
|
||||||
|
pub text: String,
|
||||||
|
pub event_date: DateForm,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Update {
|
||||||
|
pub id: i32,
|
||||||
|
pub lang: String,
|
||||||
|
pub title: String,
|
||||||
|
pub location: String,
|
||||||
|
pub text: String,
|
||||||
|
pub event_date: DateForm,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Delete {
|
||||||
|
pub id: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Post {
|
||||||
|
fn convert(self) -> Create {
|
||||||
|
Create {
|
||||||
|
lang: self.lang,
|
||||||
|
title: self.title,
|
||||||
|
location: self.location,
|
||||||
|
text: self.text,
|
||||||
|
event_date: *self.event_date,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Update {
|
||||||
|
fn convert(self) -> Create {
|
||||||
|
Create {
|
||||||
|
lang: self.lang,
|
||||||
|
title: self.title,
|
||||||
|
location: self.location,
|
||||||
|
text: self.text,
|
||||||
|
event_date: *self.event_date,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(conn: &PgConnection, event: Create) -> Result<Get, diesel::result::Error> {
|
||||||
|
diesel::insert_into(events::table)
|
||||||
|
.values(&event)
|
||||||
|
.get_result(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(conn: &PgConnection, lg: Lang) -> Result<Vec<Get>, diesel::result::Error> {
|
||||||
|
use schema::events::dsl::*;
|
||||||
|
|
||||||
|
events.filter(lang.eq(lg.0)).load::<Get>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_all(conn: &PgConnection) -> Result<Vec<Get>, diesel::result::Error> {
|
||||||
|
use schema::events::dsl::*;
|
||||||
|
events.load::<Get>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
conn: &PgConnection,
|
||||||
|
idn: i32,
|
||||||
|
event: Create,
|
||||||
|
) -> Result<Get, diesel::result::Error> {
|
||||||
|
use schema::events::dsl::*;
|
||||||
|
diesel::update(events.find(idn))
|
||||||
|
.set(&event)
|
||||||
|
.get_result::<Get>(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn delete(conn: &PgConnection, idn: i32) -> Result<usize, diesel::result::Error> {
|
||||||
|
use schema::events::dsl::*;
|
||||||
|
diesel::delete(events.find(idn)).execute(conn)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/<lang>/events")]
|
||||||
|
pub fn api(pg: State<Mutex<PgConnection>>, lang: Lang) -> Result<Json<Vec<Get>>, Status> {
|
||||||
|
Ok(Json(
|
||||||
|
get(&*(pg.lock().unwrap()), lang).map_err(|_| Status::InternalServerError)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/events/add", data = "<form>")]
|
||||||
|
pub fn add(pg: State<Mutex<PgConnection>>, form: Form<Post>) -> Result<Redirect, Status> {
|
||||||
|
match create(&*(pg.lock().unwrap()), form.into_inner().convert()) {
|
||||||
|
Ok(_) => Ok(Redirect::to("/ui/events")),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/events/del", data = "<form>")]
|
||||||
|
pub fn del(pg: State<Mutex<PgConnection>>, form: Form<Delete>) -> Result<Redirect, Status> {
|
||||||
|
match delete(&*(pg.lock().unwrap()), form.id) {
|
||||||
|
Ok(_) => Ok(Redirect::to("/ui/events")),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[post("/events/upd", data = "<form>")]
|
||||||
|
pub fn upd(pg: State<Mutex<PgConnection>>, form: Form<Update>) -> Result<Redirect, Status> {
|
||||||
|
match update(&*(pg.lock().unwrap()), form.id, form.into_inner().convert()) {
|
||||||
|
Ok(_) => Ok(Redirect::to("/ui/events")),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[get("/events")]
|
||||||
|
pub fn ui(_token: Token, pg: State<Mutex<PgConnection>>) -> Result<Template, Status> {
|
||||||
|
let ctx = get_all(&*(pg.lock().unwrap()))
|
||||||
|
.map_err(|_| Status::InternalServerError)?
|
||||||
|
.iter()
|
||||||
|
.map(|x| (x.id, x.clone()))
|
||||||
|
.collect::<HashMap<i32, Get>>();
|
||||||
|
Ok(Template::render("events", &ctx))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
|
|
@ -1,22 +0,0 @@
|
||||||
use super::schema::events;
|
|
||||||
use diesel::Insertable;
|
|
||||||
use diesel::Queryable;
|
|
||||||
use chrono::naive::NaiveDate;
|
|
||||||
|
|
||||||
#[derive(Queryable)]
|
|
||||||
pub struct Event {
|
|
||||||
pub id: i32,
|
|
||||||
pub title: String,
|
|
||||||
pub location: String,
|
|
||||||
pub text: String,
|
|
||||||
pub event_date: NaiveDate,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Insertable)]
|
|
||||||
#[table_name="events"]
|
|
||||||
pub struct NewEvent<'a> {
|
|
||||||
pub title: &'a str,
|
|
||||||
pub location: &'a str,
|
|
||||||
pub text: &'a str,
|
|
||||||
pub event_date: &'a NaiveDate
|
|
||||||
}
|
|
|
@ -1,12 +0,0 @@
|
||||||
|
|
||||||
table! {
|
|
||||||
use diesel::sql_types::*;
|
|
||||||
|
|
||||||
events (id) {
|
|
||||||
id -> Integer,
|
|
||||||
title -> Text,
|
|
||||||
text -> Text,
|
|
||||||
location -> Text,
|
|
||||||
event_date -> Date,
|
|
||||||
}
|
|
||||||
}
|
|
136
cms/src/main.rs
136
cms/src/main.rs
|
@ -1,53 +1,141 @@
|
||||||
#[macro_use] extern crate diesel;
|
#![feature(proc_macro_hygiene, decl_macro)]
|
||||||
#[macro_use] extern crate rocket;
|
#[macro_use]
|
||||||
|
extern crate diesel;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate diesel_migrations;
|
||||||
|
#[macro_use]
|
||||||
|
extern crate rocket;
|
||||||
|
|
||||||
mod utils;
|
mod auth;
|
||||||
mod data;
|
mod data;
|
||||||
|
mod secret;
|
||||||
|
mod utils;
|
||||||
|
|
||||||
|
use auth::Settings;
|
||||||
use clap::{App, Arg};
|
use clap::{App, Arg};
|
||||||
use rocket::{Build, Rocket};
|
use diesel::prelude::*;
|
||||||
use utils::{exit_with_error, db_conn};
|
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
|
use oauth2::{AuthUrl, TokenUrl};
|
||||||
|
use rocket::{
|
||||||
|
config::{Config, Environment},
|
||||||
|
Rocket,
|
||||||
|
};
|
||||||
|
use rocket_contrib::templates::Template;
|
||||||
|
use std::{
|
||||||
|
collections::*,
|
||||||
|
env,
|
||||||
|
net::IpAddr,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
sync::Mutex,
|
||||||
|
};
|
||||||
|
use utils::{db_conn, exit_with_error};
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
fn index() -> &'static str {
|
fn index() -> Template {
|
||||||
"Hello, world!"
|
let context: HashMap<&str, &str> = [("oauth", "/oauth")].iter().cloned().collect();
|
||||||
|
Template::render("index", &context)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[get("/static/<path..>")]
|
||||||
fn rocket(port: u32) -> Rocket<Build> {
|
fn static_files(path: PathBuf) -> Option<rocket::response::NamedFile> {
|
||||||
let figment = rocket::Config::figment()
|
rocket::response::NamedFile::open(Path::new("cms/static/").join(path)).ok()
|
||||||
.merge(("port", port));
|
|
||||||
rocket::custom(figment)
|
|
||||||
.mount("/", routes![index])
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rocket(port: u16, address: String, env: Environment, pg: PgConnection, sa: Settings) -> Rocket {
|
||||||
|
let mut config = Config::build(env)
|
||||||
|
.port(port)
|
||||||
|
.address(address)
|
||||||
|
.secret_key(secret::create_secret())
|
||||||
|
.unwrap();
|
||||||
|
let mut extras = HashMap::new();
|
||||||
|
extras.insert("template_dir".to_string(), "cms/templates/".into());
|
||||||
|
config.set_extras(extras);
|
||||||
|
rocket::custom(config)
|
||||||
|
.attach(Template::fairing())
|
||||||
|
.manage(Mutex::new(pg))
|
||||||
|
.manage(sa)
|
||||||
|
.mount(
|
||||||
|
"/",
|
||||||
|
routes![index, auth::callback, auth::oauth, static_files],
|
||||||
|
)
|
||||||
|
.mount("/api", routes![data::events::api])
|
||||||
|
.mount(
|
||||||
|
"/ui",
|
||||||
|
routes![
|
||||||
|
data::events::ui,
|
||||||
|
data::events::add,
|
||||||
|
data::events::del,
|
||||||
|
data::events::upd
|
||||||
|
],
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
#[rocket::main]
|
fn main() {
|
||||||
async fn main() {
|
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
|
|
||||||
let postgres = db_conn("/postgres")
|
let gcid = env::var("BLAZERCMS_CLIENT_ID")
|
||||||
.unwrap_or_else(|_| exit_with_error("Error connecting to database. "));
|
.unwrap_or_else(|_| exit_with_error("BLAZERCMS_CLIENT_ID must be set"));
|
||||||
|
|
||||||
|
let gcs = env::var("BLAZERCMS_SECRET")
|
||||||
|
.unwrap_or_else(|_| exit_with_error("BLAZERCMS_SECRET must be set"));
|
||||||
|
|
||||||
|
let auth_url = AuthUrl::new("https://accounts.google.com/o/oauth2/v2/auth".to_owned())
|
||||||
|
.expect("Invalid authorization endpoint.");
|
||||||
|
|
||||||
|
let token_url = TokenUrl::new("https://www.googleapis.com/oauth2/v3/token".to_owned())
|
||||||
|
.expect("Invalid token endpoint. ");
|
||||||
|
|
||||||
|
let postgres = db_conn();
|
||||||
|
|
||||||
let matches = App::new("blazercms")
|
let matches = App::new("blazercms")
|
||||||
.version("1.0")
|
.version("1.0")
|
||||||
.arg(Arg::with_name("port")
|
.arg(
|
||||||
|
Arg::with_name("port")
|
||||||
.short("p")
|
.short("p")
|
||||||
.long("port")
|
.long("port")
|
||||||
.default_value("8080"))
|
.default_value("8080"),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("prod")
|
||||||
|
.short("r")
|
||||||
|
.long("prod")
|
||||||
|
.takes_value(false),
|
||||||
|
)
|
||||||
|
.arg(
|
||||||
|
Arg::with_name("addr")
|
||||||
|
.short("a")
|
||||||
|
.long("addr")
|
||||||
|
.default_value("127.0.0.1"),
|
||||||
|
)
|
||||||
.get_matches();
|
.get_matches();
|
||||||
|
|
||||||
let port = matches
|
let port = matches
|
||||||
.value_of("port")
|
.value_of("port")
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.parse::<u32>()
|
.parse::<u16>()
|
||||||
.unwrap_or_else(|_| exit_with_error("Port must be an integer! "));
|
.unwrap_or_else(|_| exit_with_error("Port must be an integer! "));
|
||||||
|
|
||||||
if let Err(_) = rocket(port).launch().await {
|
let addr = matches
|
||||||
exit_with_error(&format!("Error binding port {}. ", port));
|
.value_of("addr")
|
||||||
}
|
.unwrap()
|
||||||
|
.parse::<IpAddr>()
|
||||||
|
.unwrap_or_else(|_| exit_with_error("Address must be a valid IP Address! "))
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let env;
|
||||||
|
if matches.is_present("prod") {
|
||||||
|
env = Environment::Production;
|
||||||
|
} else {
|
||||||
|
env = Environment::Development;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let auth_settings = Settings {
|
||||||
|
id: gcid,
|
||||||
|
secret: gcs,
|
||||||
|
auth_url: auth_url,
|
||||||
|
token_url: token_url,
|
||||||
|
};
|
||||||
|
|
||||||
|
let err = rocket(port, addr, env, postgres, auth_settings).launch();
|
||||||
|
exit_with_error(&format!("Error: {}", err));
|
||||||
|
}
|
||||||
|
|
7
cms/src/secret.rs
Normal file
7
cms/src/secret.rs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
use rand::{rngs::OsRng, RngCore};
|
||||||
|
|
||||||
|
pub fn create_secret() -> String {
|
||||||
|
let mut secret = [0u8; 32];
|
||||||
|
OsRng.fill_bytes(&mut secret);
|
||||||
|
base64::encode(&secret)
|
||||||
|
}
|
|
@ -1,21 +1,21 @@
|
||||||
use ansi_term::Colour::Red;
|
use ansi_term::Colour::Red;
|
||||||
use diesel::prelude::*;
|
use diesel::{pg::PgConnection, prelude::*};
|
||||||
use diesel::pg::{PgConnection};
|
use std::env;
|
||||||
use std::{env};
|
|
||||||
|
|
||||||
pub fn exit_with_error(msg: &str) -> ! {
|
pub fn exit_with_error(msg: &str) -> ! {
|
||||||
eprintln!("{}", Red.paint(msg));
|
eprintln!("{}", Red.paint(msg));
|
||||||
std::process::exit(1);
|
std::process::exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn db_conn() -> PgConnection {
|
||||||
|
embed_migrations!("migrations");
|
||||||
|
|
||||||
|
let database_url = env::var("BLAZERCMS_DATABASE_URL")
|
||||||
pub fn db_conn(dbname: &str) -> Result<PgConnection, ConnectionError> {
|
|
||||||
|
|
||||||
let mut database_url = env::var("BLAZERCMS_DATABASE_URL")
|
|
||||||
.unwrap_or_else(|_| exit_with_error("BLAZERCMS_DATABASE_URL must be set"));
|
.unwrap_or_else(|_| exit_with_error("BLAZERCMS_DATABASE_URL must be set"));
|
||||||
|
|
||||||
database_url.push_str(dbname);
|
let db = PgConnection::establish(&database_url)
|
||||||
|
.unwrap_or_else(|_| exit_with_error("Error connecting to database. "));
|
||||||
|
|
||||||
PgConnection::establish(&database_url)
|
embedded_migrations::run(&db).unwrap_or_else(|_| exit_with_error("Error migrating database. "));
|
||||||
|
db
|
||||||
}
|
}
|
||||||
|
|
3
cms/static/style.css
Normal file
3
cms/static/style.css
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
body {
|
||||||
|
background-color: lightblue;
|
||||||
|
}
|
25
cms/templates/events.html.hbs
Normal file
25
cms/templates/events.html.hbs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
<!-- vim: set ft=html: -->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link rel="stylesheet" href="/static/style.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<p>{{6.title}}</p>
|
||||||
|
<br>
|
||||||
|
<form action="events/add" method="post">
|
||||||
|
<input type="text", id="lang", name="lang">
|
||||||
|
<input type="text", id="title", name="title">
|
||||||
|
<input type="text", id="location", name="location">
|
||||||
|
<input type="text", id="text", name="text">
|
||||||
|
<input type="text", id="event_date", name="event_date">
|
||||||
|
<input type="submit", value="Submit">
|
||||||
|
</form>
|
||||||
|
<form action="events/del" method="post">
|
||||||
|
<input type="number", id="id", name="id">
|
||||||
|
<input type="submit", value="Submit">
|
||||||
|
</form>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</head>
|
||||||
|
</html>
|
10
cms/templates/index.html.hbs
Normal file
10
cms/templates/index.html.hbs
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
<!-- vim: set ft=html: -->
|
||||||
|
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<a href={{oauth}}>Login</a>
|
||||||
|
</body>
|
||||||
|
</head>
|
||||||
|
</html>
|
19
docker-compose.yml
Normal file
19
docker-compose.yml
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
version: "3.9"
|
||||||
|
services:
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: "postgres:alpine"
|
||||||
|
environment:
|
||||||
|
POSTGRES_PASSWORD: ${BLAZERCMS_POSTGRES}
|
||||||
|
ports:
|
||||||
|
- "5432:5432"
|
||||||
|
web:
|
||||||
|
build: cms/
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
depends_on:
|
||||||
|
- postgres
|
||||||
|
environment:
|
||||||
|
BLAZERCMS_DATABASE_URL: ${BLAZERCMS_DATABASE_URL}
|
||||||
|
BLAZERCMS_CLIENT_ID: ${BLAZERCMS_CLIENT_ID}
|
||||||
|
BLAZERCMS_SECRET: ${BLAZERCMS_SECRET}
|
2
rustfmt.toml
Normal file
2
rustfmt.toml
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
imports_granularity="Crate"
|
||||||
|
reorder_imports = true
|
150
testfile.rs
Normal file
150
testfile.rs
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
pub mod events {
|
||||||
|
use super::{defs::DateForm, Lang};
|
||||||
|
use crate::auth::Token;
|
||||||
|
use chrono::naive::NaiveDate;
|
||||||
|
use diesel::{prelude::*, Insertable, Queryable};
|
||||||
|
use rocket::{http::Status, request::Form, response::Redirect, State};
|
||||||
|
use rocket_contrib::{json::Json, templates::Template};
|
||||||
|
use serde::Serialize;
|
||||||
|
use std::{collections::*, sync::Mutex};
|
||||||
|
pub mod schema {
|
||||||
|
table! { use diesel :: sql_types :: * ; events (id) { id -> Integer , lang -> Text , title -> Text , location -> Text , text -> Text , event_date -> Text , } }
|
||||||
|
}
|
||||||
|
use schema::events;
|
||||||
|
#[derive(Debug, Clone, Queryable, Serialize)]
|
||||||
|
pub struct Get {
|
||||||
|
pub id: i32,
|
||||||
|
pub lang: String,
|
||||||
|
pub title: String,
|
||||||
|
pub location: String,
|
||||||
|
pub text: String,
|
||||||
|
pub event_date: NaiveDate,
|
||||||
|
}
|
||||||
|
#[derive(Debug, AsChangeset, Insertable)]
|
||||||
|
#[table_name = "events"]
|
||||||
|
pub struct Create {
|
||||||
|
pub lang: String,
|
||||||
|
pub title: String,
|
||||||
|
pub location: String,
|
||||||
|
pub text: String,
|
||||||
|
pub event_date: NaiveDate,
|
||||||
|
}
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Post {
|
||||||
|
pub lang: String,
|
||||||
|
pub title: String,
|
||||||
|
pub location: String,
|
||||||
|
pub text: String,
|
||||||
|
pub event_date: FormDate,
|
||||||
|
}
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Update {
|
||||||
|
pub id: i32,
|
||||||
|
pub lang: String,
|
||||||
|
pub title: String,
|
||||||
|
pub location: String,
|
||||||
|
pub text: String,
|
||||||
|
pub event_date: FormDate,
|
||||||
|
}
|
||||||
|
#[derive(Debug, FromForm)]
|
||||||
|
pub struct Delete {
|
||||||
|
pub id: i32,
|
||||||
|
}
|
||||||
|
impl Post {
|
||||||
|
fn convert(self) -> Create {
|
||||||
|
Create {
|
||||||
|
lang: self.lang,
|
||||||
|
title: self.title,
|
||||||
|
location: self.location,
|
||||||
|
text: self.text,
|
||||||
|
event_date: *self.event_date,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Update {
|
||||||
|
fn convert(self) -> Create {
|
||||||
|
Create {
|
||||||
|
lang: self.lang,
|
||||||
|
title: self.title,
|
||||||
|
location: self.location,
|
||||||
|
text: self.text,
|
||||||
|
event_date: *self.event_date,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub fn create(conn: &PgConnection, create: Create) -> Result<Get, diesel::result::Error> {
|
||||||
|
diesel::insert_into(events::table)
|
||||||
|
.values(&create)
|
||||||
|
.get_result(conn)
|
||||||
|
}
|
||||||
|
pub fn get(conn: &PgConnection, lg: Lang) -> Result<Vec<Get>, diesel::result::Error> {
|
||||||
|
use schema::events::dsl::*;
|
||||||
|
events.filter(lang.eq(lg.0)).load::<Get>(conn)
|
||||||
|
}
|
||||||
|
pub fn get_all(conn: &PgConnection) -> Result<Vec<Get>, diesel::result::Error> {
|
||||||
|
use schema::events::dsl::*;
|
||||||
|
events.load::<Get>(conn)
|
||||||
|
}
|
||||||
|
pub fn update(
|
||||||
|
conn: &PgConnection,
|
||||||
|
idn: i32,
|
||||||
|
create: Create,
|
||||||
|
) -> Result<Get, diesel::result::Error> {
|
||||||
|
use schema::events::dsl::*;
|
||||||
|
diesel::update(events.find(idn))
|
||||||
|
.set(&create)
|
||||||
|
.get_result::<Get>(conn)
|
||||||
|
}
|
||||||
|
pub fn delete(conn: &PgConnection, idn: i32) -> Result<usize, diesel::result::Error> {
|
||||||
|
use schema::events::dsl::*;
|
||||||
|
diesel::delete(events.find(idn)).execute(conn)
|
||||||
|
}
|
||||||
|
#[get("/<lang>/events")]
|
||||||
|
pub fn api(pg: State<Mutex<PgConnection>>, lang: Lang) -> Result<Json<Vec<Get>>, Status> {
|
||||||
|
Ok(Json(
|
||||||
|
get(&*(pg.lock().unwrap()), lang).map_err(|_| Status::InternalServerError)?,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
#[post("/events/add", data = "<form>")]
|
||||||
|
pub fn add(
|
||||||
|
_token: Token,
|
||||||
|
pg: State<Mutex<PgConnection>>,
|
||||||
|
form: Form<Post>,
|
||||||
|
) -> Result<Redirect, Status> {
|
||||||
|
match create(&*(pg.lock().unwrap()), form.into_inner().convert()) {
|
||||||
|
Ok(_) => Ok(Redirect::to("/ui/events")),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[post("/events/del", data = "<form>")]
|
||||||
|
pub fn del(
|
||||||
|
_token: Token,
|
||||||
|
pg: State<Mutex<PgConnection>>,
|
||||||
|
form: Form<Delete>,
|
||||||
|
) -> Result<Redirect, Status> {
|
||||||
|
match delete(&*(pg.lock().unwrap()), form.id) {
|
||||||
|
Ok(_) => Ok(Redirect::to("/ui/events")),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[post("/events/upd", data = "<form>")]
|
||||||
|
pub fn upd(
|
||||||
|
_token: Token,
|
||||||
|
pg: State<Mutex<PgConnection>>,
|
||||||
|
form: Form<Update>,
|
||||||
|
) -> Result<Redirect, Status> {
|
||||||
|
match update(&*(pg.lock().unwrap()), form.id, form.into_inner().convert()) {
|
||||||
|
Ok(_) => Ok(Redirect::to("/ui/events")),
|
||||||
|
Err(_) => Err(Status::InternalServerError),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[get("/events")]
|
||||||
|
pub fn ui(_token: Token, pg: State<Mutex<PgConnection>>) -> Result<Template, Status> {
|
||||||
|
let ctx = get_all(&*(pg.lock().unwrap()))
|
||||||
|
.map_err(|_| Status::InternalServerError)?
|
||||||
|
.iter()
|
||||||
|
.map(|x| (x.id, x.clone()))
|
||||||
|
.collect::<HashMap<i32, Get>>();
|
||||||
|
Ok(Template::render("events", &ctx))
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user