mirror of
https://github.com/Blair-SGA-Dev-Team/blazerapp.git
synced 2024-11-21 04:21: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
|
||||
/target/
|
||||
/cms/target/
|
||||
/cms/cms_macro/target/
|
||||
|
||||
# Environment
|
||||
*.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"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rocket = "0.5.0-rc.1"
|
||||
rocket = "0.4.10"
|
||||
dotenv = "0.15.0"
|
||||
oauth2 = { version = "4.1.0", default-features = false }
|
||||
clap = "2.33.3"
|
||||
ansi_term = "0.12.1"
|
||||
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 (
|
||||
id SERIAL PRIMARY KEY,
|
||||
lang VARCHAR,
|
||||
title VARCHAR NOT NULL,
|
||||
text VARCHAR,
|
||||
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;
|
||||
pub mod schema;
|
||||
pub mod models;
|
||||
#[macro_use]
|
||||
use cms_macro::api_route;
|
||||
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,
|
||||
}
|
||||
}
|
142
cms/src/main.rs
142
cms/src/main.rs
|
@ -1,53 +1,141 @@
|
|||
#[macro_use] extern crate diesel;
|
||||
#[macro_use] extern crate rocket;
|
||||
#![feature(proc_macro_hygiene, decl_macro)]
|
||||
#[macro_use]
|
||||
extern crate diesel;
|
||||
#[macro_use]
|
||||
extern crate diesel_migrations;
|
||||
#[macro_use]
|
||||
extern crate rocket;
|
||||
|
||||
mod utils;
|
||||
mod auth;
|
||||
mod data;
|
||||
mod secret;
|
||||
mod utils;
|
||||
|
||||
use auth::Settings;
|
||||
use clap::{App, Arg};
|
||||
use rocket::{Build, Rocket};
|
||||
use utils::{exit_with_error, db_conn};
|
||||
use diesel::prelude::*;
|
||||
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("/")]
|
||||
fn index() -> &'static str {
|
||||
"Hello, world!"
|
||||
fn index() -> Template {
|
||||
let context: HashMap<&str, &str> = [("oauth", "/oauth")].iter().cloned().collect();
|
||||
Template::render("index", &context)
|
||||
}
|
||||
|
||||
|
||||
fn rocket(port: u32) -> Rocket<Build> {
|
||||
let figment = rocket::Config::figment()
|
||||
.merge(("port", port));
|
||||
rocket::custom(figment)
|
||||
.mount("/", routes![index])
|
||||
|
||||
#[get("/static/<path..>")]
|
||||
fn static_files(path: PathBuf) -> Option<rocket::response::NamedFile> {
|
||||
rocket::response::NamedFile::open(Path::new("cms/static/").join(path)).ok()
|
||||
}
|
||||
|
||||
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]
|
||||
async fn main() {
|
||||
fn main() {
|
||||
dotenv().ok();
|
||||
|
||||
let postgres = db_conn("/postgres")
|
||||
.unwrap_or_else(|_| exit_with_error("Error connecting to database. "));
|
||||
let gcid = env::var("BLAZERCMS_CLIENT_ID")
|
||||
.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")
|
||||
.version("1.0")
|
||||
.arg(Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.default_value("8080"))
|
||||
.arg(
|
||||
Arg::with_name("port")
|
||||
.short("p")
|
||||
.long("port")
|
||||
.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();
|
||||
|
||||
let port = matches
|
||||
.value_of("port")
|
||||
.unwrap()
|
||||
.parse::<u32>()
|
||||
.parse::<u16>()
|
||||
.unwrap_or_else(|_| exit_with_error("Port must be an integer! "));
|
||||
|
||||
if let Err(_) = rocket(port).launch().await {
|
||||
exit_with_error(&format!("Error binding port {}. ", port));
|
||||
}
|
||||
}
|
||||
let addr = matches
|
||||
.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 diesel::prelude::*;
|
||||
use diesel::pg::{PgConnection};
|
||||
use std::{env};
|
||||
use diesel::{pg::PgConnection, prelude::*};
|
||||
use std::env;
|
||||
|
||||
pub fn exit_with_error(msg: &str) -> ! {
|
||||
eprintln!("{}", Red.paint(msg));
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
pub fn db_conn() -> PgConnection {
|
||||
embed_migrations!("migrations");
|
||||
|
||||
|
||||
pub fn db_conn(dbname: &str) -> Result<PgConnection, ConnectionError> {
|
||||
|
||||
let mut database_url = env::var("BLAZERCMS_DATABASE_URL")
|
||||
let database_url = env::var("BLAZERCMS_DATABASE_URL")
|
||||
.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