why does proc macro not work

This commit is contained in:
EvilMuffinHa 2021-08-29 14:59:11 -04:00
parent 9d6901c499
commit 5318c59880
23 changed files with 2288 additions and 582 deletions

2
.gitignore vendored
View File

@ -60,6 +60,8 @@ buck-out/
# Rust # Rust
/target/ /target/
/cms/target/
/cms/cms_macro/target/
# Environment # Environment
*.env *.env

1605
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -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
View 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" ]

View File

17
cms/cms_macro/Cargo.toml Normal file
View 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
View 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()
}

View File

@ -1,2 +1,3 @@
DROP TABLE events; DROP TABLE auth_val;
DROP TABLE events;

View File

@ -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,

View File

@ -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"),
)
} }

View File

@ -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)
}

View File

@ -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))
}
}
*/

View File

@ -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
}

View File

@ -1,12 +0,0 @@
table! {
use diesel::sql_types::*;
events (id) {
id -> Integer,
title -> Text,
text -> Text,
location -> Text,
event_date -> Date,
}
}

View File

@ -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(
.short("p") Arg::with_name("port")
.long("port") .short("p")
.default_value("8080")) .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(); .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
View 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)
}

View File

@ -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
View File

@ -0,0 +1,3 @@
body {
background-color: lightblue;
}

View 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>

View 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
View 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
View File

@ -0,0 +1,2 @@
imports_granularity="Crate"
reorder_imports = true

150
testfile.rs Normal file
View 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))
}
}