aboutsummaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/db/mod.rs56
-rw-r--r--src/db/models.rs18
-rw-r--r--src/db/schema.rs9
-rw-r--r--src/main.rs44
-rw-r--r--src/routes/api.rs79
-rw-r--r--src/routes/mod.rs5
-rw-r--r--src/routes/ui.rs55
-rw-r--r--src/structure.rs21
8 files changed, 287 insertions, 0 deletions
diff --git a/src/db/mod.rs b/src/db/mod.rs
new file mode 100644
index 0000000..b0321cb
--- /dev/null
+++ b/src/db/mod.rs
@@ -0,0 +1,56 @@
+// Copyright (C) 2021-2021 Fuwn
+// SPDX-License-Identifier: GPL-3.0-only
+
+pub mod models;
+mod schema;
+
+use diesel::{insert_into, prelude::*, update};
+
+use crate::db::models::{Link, LinkForm};
+
+fn establish_connection() -> SqliteConnection {
+ let database_url = std::env::var("DATABASE_URL").unwrap_or_else(|_| "lime.sqlite3".to_string());
+ SqliteConnection::establish(&database_url)
+ .unwrap_or_else(|_| panic!("error connecting to {}", database_url))
+}
+
+pub fn insert_link(form: LinkForm) -> Result<(), Box<dyn std::error::Error>> {
+ use schema::links::dsl::*;
+
+ if find_link(form.short, false).is_err() {
+ insert_into(links)
+ .values((long.eq(form.long), short.eq(form.short), ip.eq(form.ip)))
+ .execute(&establish_connection())?;
+ } else {
+ bail!("short url already exists");
+ }
+
+ Ok(())
+}
+
+pub fn find_link(short_c: &str, tick: bool) -> Result<Link, Box<dyn std::error::Error>> {
+ use schema::links::dsl::*;
+
+ let results = links
+ .filter(short.eq(short_c))
+ .load::<Link>(&establish_connection())
+ .unwrap();
+
+ if results.is_empty() {
+ bail!("no entry found with the short url: /{}", short_c)
+ } else {
+ let long_c = results[0].clone();
+
+ if tick {
+ update(links.find(&long_c.short))
+ .set(uses.eq(long_c.uses + 1))
+ .execute(&establish_connection())?;
+ }
+
+ if long_c.disabled {
+ bail!("short url disabled")
+ }
+
+ Ok(long_c)
+ }
+}
diff --git a/src/db/models.rs b/src/db/models.rs
new file mode 100644
index 0000000..d77da1d
--- /dev/null
+++ b/src/db/models.rs
@@ -0,0 +1,18 @@
+// Copyright (C) 2021-2021 Fuwn
+// SPDX-License-Identifier: GPL-3.0-only
+
+#[derive(Queryable, Debug, Clone)]
+pub struct Link {
+ pub long: String,
+ pub short: String,
+ pub disabled: bool,
+ pub ip: String,
+ pub uses: i32,
+}
+
+#[derive(Deserialize, Debug)]
+pub struct LinkForm<'a> {
+ pub long: &'a str,
+ pub short: &'a str,
+ pub ip: &'a str,
+}
diff --git a/src/db/schema.rs b/src/db/schema.rs
new file mode 100644
index 0000000..8d4006b
--- /dev/null
+++ b/src/db/schema.rs
@@ -0,0 +1,9 @@
+table! {
+ links (short) {
+ long -> Text,
+ short -> Text,
+ disabled -> Bool,
+ ip -> Text,
+ uses -> Int4,
+ }
+}
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..aeb4906
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,44 @@
+// Copyright (C) 2021-2021 Fuwn
+// SPDX-License-Identifier: GPL-3.0-only
+
+#[macro_use]
+extern crate actix_web;
+#[macro_use]
+extern crate serde_derive;
+#[macro_use]
+extern crate diesel;
+#[macro_use]
+extern crate simple_error;
+
+pub mod routes;
+pub mod structure;
+
+pub mod db;
+
+use actix_web::web;
+use routes::*;
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+ dotenv::dotenv().ok();
+
+ actix_web::HttpServer::new(|| {
+ actix_web::App::new()
+ .wrap(actix_cors::Cors::default().allow_any_origin())
+ .service(ui::index)
+ .service(ui::handle)
+ .service(ui::statistics)
+ .service(
+ web::scope("/api/v1")
+ .service(api::create)
+ .service(api::statistics)
+ .service(api::index),
+ )
+ })
+ .bind(format!(
+ "0.0.0.0:{}",
+ std::env::var("PORT").expect("no port was provided.")
+ ))?
+ .run()
+ .await
+}
diff --git a/src/routes/api.rs b/src/routes/api.rs
new file mode 100644
index 0000000..2445d5d
--- /dev/null
+++ b/src/routes/api.rs
@@ -0,0 +1,79 @@
+// Copyright (C) 2021-2021 Fuwn
+// SPDX-License-Identifier: GPL-3.0-only
+
+use actix_web::{web, HttpRequest, HttpResponse};
+use askama::Template;
+
+use crate::{
+ db::{find_link, insert_link, models::LinkForm},
+ structure::{PostCreateShort, StatisticsApi, TextTemplate},
+};
+
+#[post("/create")]
+pub fn create(req: HttpRequest, params: web::Form<PostCreateShort>) -> HttpResponse {
+ if let Err(e) = insert_link(LinkForm {
+ long: &params.long,
+ short: &params.short,
+ ip: &req.peer_addr().unwrap().ip().to_string(),
+ }) {
+ HttpResponse::Ok().body(
+ TextTemplate {
+ text: e.to_string().as_str(),
+ }
+ .render()
+ .unwrap(),
+ )
+ } else {
+ HttpResponse::Ok().body(
+ TextTemplate {
+ text: &format!("short url created: /{}", params.short),
+ }
+ .render()
+ .unwrap(),
+ )
+ }
+}
+
+#[get("/statistics")]
+pub fn statistics(req: HttpRequest) -> HttpResponse {
+ let queries = qstring::QString::from(req.query_string());
+
+ let result = find_link(queries.get("short").unwrap_or(""), false);
+
+ HttpResponse::Ok().json(if let Err(e) = result {
+ StatisticsApi {
+ long: e.to_string(),
+ disabled: true,
+ uses: 0,
+ }
+ } else {
+ let usable = result.unwrap();
+ StatisticsApi {
+ long: usable.long,
+ disabled: usable.disabled,
+ uses: usable.uses,
+ }
+ })
+}
+
+#[get("/")]
+pub fn index() -> HttpResponse {
+ HttpResponse::Ok().body(
+ r#"# lime api
+## routes
+if a route requires a parameter, it will be notated like <this>.
+for example; if a route is notated as "/api/v1/route?<parameter>", you can
+access that route via the url
+"http://this.domain/api/v1/route?parameter=something"
+
+- /api/v1
+ - /: index page (you are here)
+ - /statistics?<short>: get information about a short url; long, disabled, uses
+ - /create: a post route which takes a form; long and short, creates a new
+ short url
+
+### license
+gnu general public license v3.0 (gpl-3.0-only)
+https://github.com/fuwn/lime/blob/main/license"#,
+ )
+}
diff --git a/src/routes/mod.rs b/src/routes/mod.rs
new file mode 100644
index 0000000..90851fc
--- /dev/null
+++ b/src/routes/mod.rs
@@ -0,0 +1,5 @@
+// Copyright (C) 2021-2021 Fuwn
+// SPDX-License-Identifier: GPL-3.0-only
+
+pub mod api;
+pub mod ui;
diff --git a/src/routes/ui.rs b/src/routes/ui.rs
new file mode 100644
index 0000000..f70b6f1
--- /dev/null
+++ b/src/routes/ui.rs
@@ -0,0 +1,55 @@
+// Copyright (C) 2021-2021 Fuwn
+// SPDX-License-Identifier: GPL-3.0-only
+
+use actix_web::{web, HttpResponse};
+use askama::Template;
+
+use crate::{db::find_link, structure::TextTemplate};
+
+#[get("/")]
+pub fn index() -> HttpResponse {
+ HttpResponse::Ok().body(include_str!("../../templates/index.html"))
+}
+
+#[get("/{short}")]
+pub async fn handle(info: web::Path<String>) -> HttpResponse {
+ let result = find_link(&info.0, true);
+
+ if let Err(ref e) = result {
+ HttpResponse::Ok().body(
+ TextTemplate {
+ text: e.to_string().as_str(),
+ }
+ .render()
+ .unwrap(),
+ )
+ } else {
+ HttpResponse::Ok().body(format!(
+ "<script>location.href=\"/{}\"</script>",
+ result.unwrap().long,
+ ))
+ }
+}
+
+#[get("/{short}/statistics")]
+pub async fn statistics(info: web::Path<String>) -> HttpResponse {
+ let result = find_link(&info.0, false);
+
+ if let Err(ref e) = result {
+ HttpResponse::Ok().body(
+ TextTemplate {
+ text: e.to_string().as_str(),
+ }
+ .render()
+ .unwrap(),
+ )
+ } else {
+ HttpResponse::Ok().body(
+ TextTemplate {
+ text: &format!("/{} has {} uses", info.0, result.unwrap().uses),
+ }
+ .render()
+ .unwrap(),
+ )
+ }
+}
diff --git a/src/structure.rs b/src/structure.rs
new file mode 100644
index 0000000..328a950
--- /dev/null
+++ b/src/structure.rs
@@ -0,0 +1,21 @@
+// Copyright (C) 2021-2021 Fuwn
+// SPDX-License-Identifier: GPL-3.0-only
+
+#[derive(Serialize, Deserialize, Debug)]
+pub struct PostCreateShort {
+ pub long: String,
+ pub short: String,
+}
+
+#[derive(askama::Template)]
+#[template(path = "text.html")]
+pub struct TextTemplate<'a> {
+ pub text: &'a str,
+}
+
+#[derive(Serialize, Deserialize)]
+pub struct StatisticsApi {
+ pub long: String,
+ pub disabled: bool,
+ pub uses: i32,
+}