1
0
Fork 0
mirror of https://github.com/SinTan1729/chhoto-url synced 2025-02-06 06:12:31 -06:00
chhoto-url/actix/src/services.rs

254 lines
8.1 KiB
Rust
Raw Normal View History

// SPDX-FileCopyrightText: 2023 Sayantan Santra <sayantan.santra689@gmail.com>
// SPDX-License-Identifier: MIT
use actix_files::NamedFile;
use actix_session::Session;
2024-12-31 00:19:20 -06:00
use actix_web::{delete, get, http::StatusCode, post, web::{self, Redirect}, Either, HttpRequest, HttpResponse, Responder};
2024-12-31 04:30:55 -06:00
use std::env;
2024-12-31 00:19:20 -06:00
// Serialize JSON data
use serde::Serialize;
use crate::auth;
use crate::database;
use crate::utils;
use crate::AppState;
// Store the version number
const VERSION: &str = env!("CARGO_PKG_VERSION");
2024-12-31 00:19:20 -06:00
// Define JSON struct for returning JSON data
#[derive(Serialize)]
struct Response {
success: bool,
error: bool,
reason: String,
}
// Needs to return the short URL to make it easier for programs leveraging the API
#[derive(Serialize)]
struct CreatedURL {
success: bool,
error: bool,
shorturl: String,
}
// Define the routes
// Add new links
#[post("/api/new")]
2024-12-31 00:19:20 -06:00
pub async fn add_link(req: String, data: web::Data<AppState>, session: Session, http: HttpRequest) -> HttpResponse {
// Call is_api_ok() function, pass HttpRequest
let result = utils::is_api_ok(http);
// If success, add new link
if result.success {
let out = utils::add_link(req, &data.db);
if out.0 {
let port = env::var("port")
.unwrap_or(String::from("4567"))
.parse::<u16>()
.expect("Supplied port is not an integer");
let url = format!("{}:{}", env::var("site_url").unwrap_or(String::from("http://localhost")), port);
let response = CreatedURL {
success: true,
error: false,
shorturl: format!("{}/{}", url, out.1)
};
HttpResponse::Created().json(response)
} else {
let response = Response {
success: false,
error: true,
reason: out.1
};
HttpResponse::Conflict().json(response)
}
2024-12-31 00:19:20 -06:00
} else if result.error {
HttpResponse::Unauthorized().json(result)
// If "pass" is true - keeps backwards compatibility
} else {
2024-12-31 00:19:20 -06:00
if env::var("public_mode") == Ok(String::from("Enable")) || auth::validate(session) {
let out = utils::add_link(req, &data.db);
if out.0 {
HttpResponse::Created().body(out.1)
} else {
HttpResponse::Conflict().body(out.1)
}
} else {
HttpResponse::Unauthorized().body("Not logged in!")
}
}
}
// Return all active links
#[get("/api/all")]
2024-12-31 00:19:20 -06:00
pub async fn getall(data: web::Data<AppState>, session: Session, http: HttpRequest) -> HttpResponse {
// Call is_api_ok() function, pass HttpRequest
let result = utils::is_api_ok(http);
// If success, return all links
if result.success {
HttpResponse::Ok().body(utils::getall(&data.db))
2024-12-31 00:19:20 -06:00
} else if result.error {
HttpResponse::Unauthorized().json(result)
// If "pass" is true - keeps backwards compatibility
} else {
2024-12-31 00:19:20 -06:00
if auth::validate(session){
HttpResponse::Ok().body(utils::getall(&data.db))
} else {
2024-12-31 00:19:20 -06:00
let body = if env::var("public_mode") == Ok(String::from("Enable")) {
"Using public mode."
} else {
"Not logged in!"
};
HttpResponse::Unauthorized().body(body)
}
}
}
// Get the site URL
#[get("/api/siteurl")]
pub async fn siteurl() -> HttpResponse {
let site_url = env::var("site_url")
.ok()
.filter(|s| !s.trim().is_empty())
.unwrap_or(String::from("unset"));
HttpResponse::Ok().body(site_url)
}
// Get the version number
#[get("/api/version")]
pub async fn version() -> HttpResponse {
HttpResponse::Ok().body(VERSION)
}
// 404 error page
pub async fn error404() -> impl Responder {
NamedFile::open_async("./resources/static/404.html")
.await
.customize()
.with_status(StatusCode::NOT_FOUND)
}
// Handle a given shortlink
#[get("/{shortlink}")]
pub async fn link_handler(
shortlink: web::Path<String>,
data: web::Data<AppState>,
) -> impl Responder {
let shortlink_str = shortlink.to_string();
if let Some(longlink) = utils::get_longurl(shortlink_str, &data.db) {
let redirect_method = env::var("redirect_method").unwrap_or(String::from("PERMANENT"));
database::add_hit(shortlink.as_str(), &data.db);
if redirect_method == "TEMPORARY" {
Either::Left(Redirect::to(longlink))
} else {
// Defaults to permanent redirection
Either::Left(Redirect::to(longlink).permanent())
}
} else {
Either::Right(
NamedFile::open_async("./resources/static/404.html")
.await
.customize()
.with_status(StatusCode::NOT_FOUND),
)
}
}
// Handle login
#[post("/api/login")]
pub async fn login(req: String, session: Session) -> HttpResponse {
2025-01-01 01:34:09 -06:00
// Keep this function backwards compatible
if env::var("api_key").is_ok() {
2024-12-31 00:19:20 -06:00
if let Ok(password) = env::var("password") {
if password != req {
eprintln!("Failed login attempt!");
let response = Response {
success: false,
error: true,
reason: "Wrong password!".to_string()
};
return HttpResponse::Unauthorized().json(response);
}
}
2024-12-31 00:19:20 -06:00
// Return Ok if no password was set on the server side
session
.insert("chhoto-url-auth", auth::gen_token())
.expect("Error inserting auth token.");
let response = Response {
success: true,
error: false,
reason: "Correct password!".to_string()
};
HttpResponse::Ok().json(response)
} else {
if let Ok(password) = env::var("password") {
if password != req {
eprintln!("Failed login attempt!");
return HttpResponse::Unauthorized().body("Wrong password!");
}
}
// Return Ok if no password was set on the server side
session
.insert("chhoto-url-auth", auth::gen_token())
.expect("Error inserting auth token.");
HttpResponse::Ok().body("Correct password!")
}
}
// Handle logout
2024-12-31 00:19:20 -06:00
// There's no reason to be calling this route with an API key, so it is not necessary to check if the api_key env variable is set.
#[delete("/api/logout")]
pub async fn logout(session: Session) -> HttpResponse {
if session.remove("chhoto-url-auth").is_some() {
HttpResponse::Ok().body("Logged out!")
} else {
HttpResponse::Unauthorized().body("You don't seem to be logged in.")
}
}
// Delete a given shortlink
#[delete("/api/del/{shortlink}")]
pub async fn delete_link(
shortlink: web::Path<String>,
data: web::Data<AppState>,
session: Session,
2024-12-31 00:19:20 -06:00
http: HttpRequest,
) -> HttpResponse {
2024-12-31 00:19:20 -06:00
// Call is_api_ok() function, pass HttpRequest
let result = utils::is_api_ok(http);
// If success, delete shortlink
if result.success {
if utils::delete_link(shortlink.to_string(), &data.db) {
2024-12-31 00:19:20 -06:00
let response = Response {
success: true,
error: false,
reason: format!("Deleted {}", shortlink)
};
HttpResponse::Ok().json(response)
} else {
2024-12-31 00:19:20 -06:00
let response = Response {
success: false,
error: true,
reason: "The short link was not found, and could not be deleted.".to_string()
};
HttpResponse::NotFound().json(response)
}
2024-12-31 00:19:20 -06:00
} else if result.error {
HttpResponse::Unauthorized().json(result)
// If "pass" is true - keeps backwards compatibility
} else {
2024-12-31 00:19:20 -06:00
if auth::validate(session) {
if utils::delete_link(shortlink.to_string(), &data.db) {
HttpResponse::Ok().body(format!("Deleted {shortlink}"))
} else {
HttpResponse::NotFound().body("Not found!")
}
} else {
HttpResponse::Unauthorized().body("Not logged in!")
}
}
}