mirror of
https://github.com/13hannes11/ics-proxy.git
synced 2024-09-06 08:01:41 +02:00
315 lines
11 KiB
Rust
315 lines
11 KiB
Rust
use std::collections::HashMap;
|
|
use url::Url;
|
|
|
|
use actix_web::http::StatusCode;
|
|
use actix_web::{error, web, App, Error, HttpRequest, HttpResponse, HttpServer, Responder, Result};
|
|
use sqlx::{Pool, Sqlite, SqlitePool};
|
|
use tera::Tera;
|
|
use uuid::Uuid;
|
|
|
|
extern crate dotenv;
|
|
use dotenv::dotenv;
|
|
|
|
mod model;
|
|
use model::Link;
|
|
|
|
const REDIRECT_TIMEOUT_S: i32 = 2;
|
|
|
|
#[derive(Clone)]
|
|
struct Config {
|
|
root: String,
|
|
}
|
|
|
|
async fn make_ics_request(req: HttpRequest, db_pool: web::Data<Pool<Sqlite>>) -> impl Responder {
|
|
let id = req.match_info().get("id").unwrap_or("");
|
|
|
|
match Uuid::parse_str(id) {
|
|
Ok(uuid) => match Link::find_by_uuid(uuid.to_string(), db_pool).await {
|
|
Ok(link) => match reqwest::blocking::get(link.destination) {
|
|
Ok(r) => match r.text() {
|
|
Ok(res) => HttpResponse::Ok().content_type("text/calendar").body(res),
|
|
Err(_) => HttpResponse::Ok()
|
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
.finish(),
|
|
},
|
|
Err(_) => HttpResponse::Ok()
|
|
.status(StatusCode::INTERNAL_SERVER_ERROR)
|
|
.finish(),
|
|
},
|
|
Err(_) => HttpResponse::Ok().status(StatusCode::NOT_FOUND).finish(),
|
|
},
|
|
Err(_) => HttpResponse::Ok().status(StatusCode::BAD_REQUEST).finish(),
|
|
}
|
|
}
|
|
|
|
fn error_page(tmpl: web::Data<tera::Tera>, msg: String) -> Result<HttpResponse, Error> {
|
|
let mut ctx = tera::Context::new();
|
|
ctx.insert("message", &msg);
|
|
let s = tmpl
|
|
.render("error.html", &ctx)
|
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
|
|
|
Ok(HttpResponse::Ok().content_type("text/html").body(s))
|
|
}
|
|
|
|
// This is the new edit page:
|
|
async fn edit_page(
|
|
tmpl: web::Data<tera::Tera>,
|
|
query: web::Query<HashMap<String, String>>,
|
|
db_pool: web::Data<Pool<Sqlite>>,
|
|
conf: web::Data<Config>,
|
|
) -> Result<HttpResponse, Error> {
|
|
// one uuid: 9228c1a4-8956-4f1c-8b5f-53cc575bd78
|
|
if let Some(uuid_str) = query.get("uuid") {
|
|
match Uuid::parse_str(uuid_str) {
|
|
Ok(uuid) => match Link::find_by_uuid(uuid.to_string(), db_pool).await {
|
|
Ok(link) => {
|
|
let mut ctx = tera::Context::new();
|
|
ctx.insert("link", &link.destination);
|
|
ctx.insert("uuid", &link.uuid);
|
|
ctx.insert("root", &conf.root);
|
|
let s = tmpl
|
|
.render("edit.html", &ctx)
|
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
|
|
|
Ok(HttpResponse::Ok().content_type("text/html").body(s))
|
|
}
|
|
Err(err) => error_page(tmpl, format!("db error: {}", err.to_string())),
|
|
},
|
|
Err(err) => error_page(tmpl, format!("uuid parsing error: {}", err.to_string())),
|
|
}
|
|
} else {
|
|
error_page(tmpl, "uuid parameter missing".to_string())
|
|
}
|
|
}
|
|
|
|
fn redirect_to_page(
|
|
tmpl: web::Data<tera::Tera>,
|
|
message: String,
|
|
link: String,
|
|
time_s: i32,
|
|
) -> Result<HttpResponse, Error> {
|
|
let mut ctx = tera::Context::new();
|
|
ctx.insert("message", &message);
|
|
ctx.insert("link", &link);
|
|
ctx.insert("time", &time_s);
|
|
let s = tmpl
|
|
.render("redirect.html", &ctx)
|
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
|
|
|
Ok(HttpResponse::Ok().content_type("text/html").body(s))
|
|
}
|
|
|
|
fn redirect_to_edit_page(
|
|
tmpl: web::Data<tera::Tera>,
|
|
message: String,
|
|
uuid: Uuid,
|
|
time_s: i32,
|
|
) -> Result<HttpResponse, Error> {
|
|
let mut ctx = tera::Context::new();
|
|
ctx.insert("message", &message);
|
|
let link = format!("/edit?uuid={}", uuid.to_string());
|
|
ctx.insert("time", &time_s);
|
|
redirect_to_page(tmpl, message, link, time_s)
|
|
}
|
|
|
|
/*
|
|
fn redirect_to_index_page(
|
|
tmpl: web::Data<tera::Tera>,
|
|
query: web::Query<HashMap<String, String>>,
|
|
message: String,
|
|
uuid: Uuid,
|
|
time_s: i32,
|
|
) -> Result<HttpResponse, Error> {
|
|
// TODO: add option to prefill link
|
|
}
|
|
*/
|
|
|
|
async fn edit_process(
|
|
tmpl: web::Data<tera::Tera>,
|
|
query: web::Query<HashMap<String, String>>,
|
|
db_pool: web::Data<Pool<Sqlite>>,
|
|
config: web::Data<Config>,
|
|
) -> Result<HttpResponse, Error> {
|
|
// TODO: implement handling
|
|
if let Some(uuid_str) = query.get("uuid") {
|
|
if let Some(destination) = query.get("link") {
|
|
if destination.starts_with(&config.root) {
|
|
return error_page(tmpl, "url cannot contain url of ics-proxy".to_string());
|
|
};
|
|
|
|
if Url::parse(destination).is_err() {
|
|
return error_page(tmpl, "could not parse url".to_string());
|
|
}
|
|
|
|
match Uuid::parse_str(uuid_str) {
|
|
Ok(uuid) => {
|
|
let link = Link {
|
|
uuid: uuid.to_string(),
|
|
destination: destination.to_string(),
|
|
};
|
|
match Link::update(link, db_pool).await {
|
|
Ok(_) => redirect_to_edit_page(
|
|
tmpl,
|
|
"Edit successful!".to_string(),
|
|
uuid,
|
|
REDIRECT_TIMEOUT_S,
|
|
),
|
|
Err(err) => error_page(tmpl, format!("db error: {}", err.to_string())),
|
|
}
|
|
}
|
|
Err(err) => error_page(tmpl, format!("uuid parsing error: {}", err.to_string())),
|
|
}
|
|
} else {
|
|
error_page(tmpl, "link parameter missing".to_string())
|
|
}
|
|
} else {
|
|
error_page(tmpl, "uuid parameter missing".to_string())
|
|
}
|
|
}
|
|
|
|
async fn index_process(
|
|
tmpl: web::Data<tera::Tera>,
|
|
query: web::Query<HashMap<String, String>>,
|
|
db_pool: web::Data<Pool<Sqlite>>,
|
|
config: web::Data<Config>,
|
|
) -> Result<HttpResponse, Error> {
|
|
if query.get("create").is_some() {
|
|
let uuid = Uuid::new_v4();
|
|
// TODO: add actuall logic and use proper uuid
|
|
match query.get("link") {
|
|
// TODO: actually parse link to url to make sure its valid
|
|
Some(destination) => {
|
|
if destination.starts_with(&config.root) {
|
|
return error_page(tmpl, "url cannot contain url of ics-proxy".to_string());
|
|
};
|
|
|
|
if Url::parse(destination).is_err() {
|
|
return error_page(tmpl, "could not parse url".to_string());
|
|
}
|
|
|
|
let insert_link = Link {
|
|
uuid: uuid.to_string(),
|
|
destination: destination.to_string(),
|
|
};
|
|
|
|
match Link::create(insert_link, db_pool).await {
|
|
Ok(link) => match Uuid::parse_str(&link.uuid) {
|
|
Ok(uuid) => redirect_to_edit_page(
|
|
tmpl,
|
|
"Create was successful".to_string(),
|
|
uuid,
|
|
REDIRECT_TIMEOUT_S,
|
|
),
|
|
Err(e) => error_page(tmpl, format!("uuid parsing error {}", e.to_string())),
|
|
},
|
|
// TODO: actually redirect to index page to try again
|
|
Err(e) => error_page(tmpl, format!("db error: {}", e.to_string())),
|
|
}
|
|
}
|
|
None => {
|
|
// TODO: actually redirect back to index page
|
|
error_page(
|
|
tmpl,
|
|
"link attribute not set please enter a link".to_string(),
|
|
)
|
|
}
|
|
}
|
|
} else if query.get("edit").is_some() {
|
|
match query.get("link") {
|
|
Some(link) => {
|
|
// Splitting string and getting uuid, alternatively pretend whole string is uuid
|
|
let vec: Vec<&str> = link.split('/').collect();
|
|
|
|
let mut uuid_str = link.to_string();
|
|
if vec.len() > 1 {
|
|
uuid_str = match vec.get(vec.len() - 2) {
|
|
Some(s) => s.to_string(),
|
|
None => link.to_string(),
|
|
};
|
|
}
|
|
|
|
match Uuid::parse_str(&uuid_str) {
|
|
Ok(uuid) => redirect_to_edit_page(
|
|
tmpl,
|
|
"Got uuid from submission!".to_string(),
|
|
uuid,
|
|
REDIRECT_TIMEOUT_S,
|
|
),
|
|
// TODO: actually redirect back to index page
|
|
Err(e) => error_page(tmpl, format!("could not parse uuid: {}", e.to_string())),
|
|
}
|
|
}
|
|
None => {
|
|
// TODO: actually redirect back to index page
|
|
error_page(
|
|
tmpl,
|
|
"link attribute not set please enter a link".to_string(),
|
|
)
|
|
}
|
|
}
|
|
} else {
|
|
error_page(tmpl, "missing create or edit form submission!".to_string())
|
|
}
|
|
}
|
|
|
|
// store tera template in application state
|
|
async fn index(tmpl: web::Data<tera::Tera>) -> Result<HttpResponse, Error> {
|
|
// TODO: add option to prefill link with parameter
|
|
|
|
let s = tmpl
|
|
.render("index.html", &tera::Context::new())
|
|
.map_err(|_| error::ErrorInternalServerError("Template error"))?;
|
|
Ok(HttpResponse::Ok().content_type("text/html").body(s))
|
|
}
|
|
|
|
#[actix_web::main]
|
|
async fn main() -> std::io::Result<()> {
|
|
std::env::set_var("RUST_LOG", "actix_web=info");
|
|
|
|
dotenv().ok();
|
|
|
|
let database_url = match std::env::var("DATABASE_URL") {
|
|
Ok(var) => var,
|
|
Err(e) => panic!("{}", e.to_string()),
|
|
};
|
|
let protocol =
|
|
std::env::var("PROTOCOL").expect("PROTOCOL environemt variable error, make sure it is set");
|
|
let base_url =
|
|
std::env::var("BASE_URL").expect("BASE_URL environemt variable error, make sure it is set");
|
|
|
|
let host = match std::env::var("HOST") {
|
|
Ok(host) => host,
|
|
Err(_e) => "0.0.0.0:8080".to_string(),
|
|
};
|
|
|
|
let conf = Config {
|
|
root: format!("{}://{}", protocol, base_url),
|
|
};
|
|
|
|
let db_pool = SqlitePool::connect(&database_url)
|
|
.await
|
|
.expect("could not create db pool");
|
|
|
|
println!(
|
|
"Listening on: {}://{}, open browser and visit have a try!",
|
|
protocol, base_url
|
|
);
|
|
HttpServer::new(move || {
|
|
let tera = Tera::new("templates/**/*.html").unwrap();
|
|
|
|
App::new()
|
|
.data(db_pool.clone()) // pass database pool to application so we can access it inside handlers
|
|
.data(tera)
|
|
.data(conf.clone())
|
|
.route("/{id}/events.ics", web::get().to(make_ics_request))
|
|
.service(web::resource("/").route(web::get().to(index)))
|
|
.service(web::resource("/edit").route(web::get().to(edit_page)))
|
|
.service(web::resource("/index_process").route(web::get().to(index_process)))
|
|
.service(web::resource("/edit_process").route(web::get().to(edit_process)))
|
|
})
|
|
.bind(host)?
|
|
.run()
|
|
.await
|
|
}
|