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
/target/
/cms/target/
/cms/cms_macro/target/
# Environment
*.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"
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
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 (
id SERIAL PRIMARY KEY,
lang VARCHAR,
title VARCHAR NOT NULL,
text VARCHAR,
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;
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))
}
}
*/

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;
#[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")
.arg(
Arg::with_name("port")
.short("p")
.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();
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
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 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
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))
}
}