mirror of
				https://github.com/Blair-SGA-Dev-Team/blazerapp.git
				synced 2025-10-30 22:41:12 -04: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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user