mirror of
https://github.com/mvvasilev/paste-eater.git
synced 2025-04-11 18:05:01 +03:00
Initial Commit
This commit is contained in:
commit
e748695f42
12 changed files with 2549 additions and 0 deletions
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
/target
|
1965
Cargo.lock
generated
Normal file
1965
Cargo.lock
generated
Normal file
File diff suppressed because it is too large
Load diff
18
Cargo.toml
Normal file
18
Cargo.toml
Normal file
|
@ -0,0 +1,18 @@
|
|||
[package]
|
||||
name = "paste-eater"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
toml = "0.8.4"
|
||||
serde = "1.0.189"
|
||||
serde_derive = "1.0.189"
|
||||
directories = "5.0.1"
|
||||
clap = { version = "4.4.7", features = ["derive"] }
|
||||
rocket = { version = "0.5.0-rc.3", features = ["tls", "json"] }
|
||||
rand = "0.8.5"
|
||||
chrono = "0.4.31"
|
||||
lz4_flex = "0.11.1"
|
||||
|
28
resources/Rocket.toml
Normal file
28
resources/Rocket.toml
Normal file
|
@ -0,0 +1,28 @@
|
|||
[default]
|
||||
address = "127.0.0.1"
|
||||
port = 8000
|
||||
workers = 16
|
||||
max_blocking = 512
|
||||
keep_alive = 5
|
||||
ident = "Rocket"
|
||||
ip_header = "X-Real-IP" # set to `false` to disable
|
||||
log_level = "normal"
|
||||
temp_dir = "/tmp"
|
||||
cli_colors = true
|
||||
secret_key = ""
|
||||
|
||||
[default.limits]
|
||||
form = "64 kB"
|
||||
json = "1 MiB"
|
||||
msgpack = "2 MiB"
|
||||
"file/jpg" = "5 MiB"
|
||||
|
||||
[default.tls]
|
||||
certs = "path/to/cert-chain.pem"
|
||||
key = "path/to/key.pem"
|
||||
|
||||
[default.shutdown]
|
||||
ctrlc = true
|
||||
signals = ["term", "hup"]
|
||||
grace = 5
|
||||
mercy = 5
|
25
src/args.rs
Normal file
25
src/args.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use clap::Parser;
|
||||
|
||||
#[derive(Parser, Debug)]
|
||||
#[command(author, version, about, long_about = None)]
|
||||
#[command(next_line_help = true)]
|
||||
pub struct Args {
|
||||
/// Where to store the pastes. By default this is a "data" sub-directory within the system-specific configuration directory for the application ( see https://github.com/dirs-dev/directories-rs#basedirs" ).
|
||||
#[arg(short, long)]
|
||||
pub location: Option<String>,
|
||||
|
||||
/// Max size of a single paste
|
||||
#[arg(short = 'f', long, default_value_t = 10000000)]
|
||||
pub max_file_size: u64,
|
||||
|
||||
// /// Max size of all pastes existing on the server. Once this limit is reached, pastes will be deleted, starting with oldest first.
|
||||
// #[arg(short = 's', long, default_value_t = 100000000)]
|
||||
// pub max_storage_size: u64,
|
||||
|
||||
/// Constant size of new paste identifiers being generated. Changing this does not alter existing pastes.
|
||||
#[arg(short, long, default_value_t = 12)]
|
||||
pub name_size: usize,
|
||||
|
||||
#[arg(short, long, default_value_t = true)]
|
||||
pub compress: bool
|
||||
}
|
177
src/config.rs
Normal file
177
src/config.rs
Normal file
|
@ -0,0 +1,177 @@
|
|||
use std::{path::{Path, PathBuf}, fs::File, io::Write};
|
||||
|
||||
use directories::ProjectDirs;
|
||||
use serde_derive::{Deserialize, Serialize};
|
||||
|
||||
use crate::{args::Args, error::PasteEaterError};
|
||||
|
||||
const PASTE_EATER_CONFIG_FILE: &str = "config.toml";
|
||||
const PASTE_EATER_QUALIFIER: &str = "dev.mvvasilev";
|
||||
const PASTE_EATER_ORGANIZATION: &str = "mvvasilev";
|
||||
const PASTE_EATER_APPLICATION: &str = "paste-eater";
|
||||
|
||||
type ConfigurationError = PasteEaterError;
|
||||
|
||||
#[derive(Default, Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PasteEaterConfig {
|
||||
/// Where to store the pastes
|
||||
#[serde(default = "default_files_location")]
|
||||
pub files_location: String,
|
||||
|
||||
/// Max size of a single paste
|
||||
#[serde(default = "default_files_max_single_file_size_in_bytes")]
|
||||
pub files_max_single_file_size_in_bytes: u64,
|
||||
|
||||
// /// Max size of all pastes existing on the server. Once this limit is reached, pastes will be deleted, starting with oldest first.
|
||||
// #[serde(default = "default_files_max_total_file_size_in_bytes")]
|
||||
// pub files_max_total_file_size_in_bytes: u64,
|
||||
|
||||
/// Constant size of new paste identifiers being generated. Changing this should not alter existing pastes.
|
||||
#[serde(default = "default_name_size")]
|
||||
pub name_size: usize,
|
||||
|
||||
#[serde(default = "default_compress")]
|
||||
pub compress: bool
|
||||
|
||||
// TODO: Encrypted pastes?
|
||||
}
|
||||
|
||||
fn default_files_location() -> String {
|
||||
if let Some(config_dirs) = ProjectDirs::from(PASTE_EATER_QUALIFIER, PASTE_EATER_ORGANIZATION, PASTE_EATER_APPLICATION) {
|
||||
let mut dir = config_dirs.config_dir().to_path_buf();
|
||||
|
||||
dir.push(Path::new("data"));
|
||||
|
||||
dir.display().to_string()
|
||||
} else {
|
||||
Path::new(".").to_path_buf().display().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
fn default_files_max_single_file_size_in_bytes() -> u64 {
|
||||
10_000_000
|
||||
}
|
||||
|
||||
fn default_files_max_total_file_size_in_bytes() -> u64 {
|
||||
100_000_000
|
||||
}
|
||||
|
||||
fn default_name_size() -> usize {
|
||||
12
|
||||
}
|
||||
|
||||
fn default_compress() -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
pub struct ConfigurationHandler {
|
||||
config_path: PathBuf
|
||||
}
|
||||
|
||||
impl ConfigurationHandler {
|
||||
pub fn new() -> Result<Self, ConfigurationError> {
|
||||
if let Some(config_dirs) = ProjectDirs::from(PASTE_EATER_QUALIFIER, PASTE_EATER_ORGANIZATION, PASTE_EATER_APPLICATION) {
|
||||
ConfigurationHandler::new_with_path(config_dirs.config_dir())
|
||||
} else {
|
||||
Err(ConfigurationError::new("Unable to determine configuration path"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_args(args: &Args) -> Result<Self, ConfigurationError> {
|
||||
let config = PasteEaterConfig {
|
||||
files_location: args.location.clone().unwrap_or(default_files_location()),
|
||||
files_max_single_file_size_in_bytes: args.max_file_size,
|
||||
// files_max_total_file_size_in_bytes: args.max_storage_size,
|
||||
name_size: args.name_size,
|
||||
compress: args.compress
|
||||
};
|
||||
|
||||
|
||||
|
||||
if let Some(config_dirs) = ProjectDirs::from(PASTE_EATER_QUALIFIER, PASTE_EATER_ORGANIZATION, PASTE_EATER_APPLICATION) {
|
||||
ConfigurationHandler::new_with_defaults(config_dirs.config_dir(), &config)
|
||||
} else {
|
||||
Err(ConfigurationError::new("Unable to determine configuration path"))
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_with_path(path: &Path) -> Result<Self, ConfigurationError> {
|
||||
ConfigurationHandler::new_with_defaults(path, &PasteEaterConfig {
|
||||
files_location: default_files_location(),
|
||||
files_max_single_file_size_in_bytes: default_files_max_single_file_size_in_bytes(),
|
||||
// files_max_total_file_size_in_bytes: default_files_max_total_file_size_in_bytes(),
|
||||
name_size: default_name_size(),
|
||||
compress: default_compress()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn new_with_defaults(path: &Path, default_config: &PasteEaterConfig) -> Result<Self, ConfigurationError> {
|
||||
if path.extension().is_some() {
|
||||
return Err(ConfigurationError::new(&format!("Provided configuration path '{}' is not a directory.", path.display())));
|
||||
}
|
||||
|
||||
let mut pathbuf = path.to_path_buf();
|
||||
|
||||
match pathbuf.try_exists() {
|
||||
Ok(exists) => {
|
||||
if !exists {
|
||||
match std::fs::create_dir_all(&pathbuf) {
|
||||
Ok(_) => {},
|
||||
Err(internal) => return Err(ConfigurationError::new_internal(&format!("Failed to create configuration directory '{}'.", path.display()), Box::new(internal))),
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => return Err(ConfigurationError::new_internal(&format!("Failed to check if configuration directory '{}' exists.", path.display()), Box::new(err))),
|
||||
}
|
||||
|
||||
pathbuf.push(Path::new(PASTE_EATER_CONFIG_FILE));
|
||||
|
||||
if !pathbuf.exists() {
|
||||
match File::create(&pathbuf) {
|
||||
Ok(mut f) => {
|
||||
|
||||
let config = PasteEaterConfig {
|
||||
files_location: if default_config.files_location.is_empty() { default_files_location() } else { default_config.files_location.clone() },
|
||||
files_max_single_file_size_in_bytes: default_config.files_max_single_file_size_in_bytes,
|
||||
// files_max_total_file_size_in_bytes: default_config.files_max_total_file_size_in_bytes,
|
||||
name_size: default_config.name_size,
|
||||
compress: default_config.compress
|
||||
};
|
||||
|
||||
let serialized_default_config = toml::to_string_pretty(&config);
|
||||
|
||||
let write_default_config = match serialized_default_config {
|
||||
Ok(ser) => f.write_all(ser.as_bytes()),
|
||||
Err(err) => return Err(ConfigurationError::new_internal("Failed to create default configuration.", Box::new(err))),
|
||||
};
|
||||
|
||||
match write_default_config {
|
||||
Ok(_) => {},
|
||||
Err(err) => return Err(ConfigurationError::new_internal("Failed to create default configuration.", Box::new(err))),
|
||||
}
|
||||
},
|
||||
Err(err) => return Err(ConfigurationError::new_internal(&format!("Failed to create configuration file '{}'", path.display()), Box::new(err))),
|
||||
}
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
config_path: pathbuf
|
||||
})
|
||||
}
|
||||
|
||||
pub fn fetch_config(&self) -> Result<PasteEaterConfig, ConfigurationError> {
|
||||
let config_as_string = std::fs::read_to_string(&self.config_path);
|
||||
|
||||
let config: PasteEaterConfig = match config_as_string {
|
||||
Ok(s) => {
|
||||
match toml::from_str(&s) {
|
||||
Ok(config) => config,
|
||||
Err(err) => return Err(ConfigurationError::new_internal(&format!("Failed to parse configuration file '{}'", self.config_path.display()), Box::new(err))),
|
||||
}
|
||||
},
|
||||
Err(err) => return Err(ConfigurationError::new_internal(&format!("Failed to read configuration file '{}'", self.config_path.display()), Box::new(err))),
|
||||
};
|
||||
|
||||
Ok(config)
|
||||
}
|
||||
}
|
37
src/error.rs
Normal file
37
src/error.rs
Normal file
|
@ -0,0 +1,37 @@
|
|||
use std::{error::Error, fmt::Display};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PasteEaterError {
|
||||
internal_error: Option<Box<dyn Error>>,
|
||||
message: String
|
||||
}
|
||||
|
||||
impl PasteEaterError {
|
||||
pub fn new(message: &str) -> Self {
|
||||
Self {
|
||||
message: message.to_string(),
|
||||
internal_error: None
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new_internal(message: &str, internal: Box<dyn Error>) -> Self {
|
||||
Self {
|
||||
message: message.to_string(),
|
||||
internal_error: Some(internal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for PasteEaterError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(
|
||||
f,
|
||||
"Paste eater error: {}, internal: {}",
|
||||
self.message,
|
||||
self.internal_error.as_ref().map_or("".to_string(), |e| format!("{}", e))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for PasteEaterError {
|
||||
}
|
12
src/main.rs
Normal file
12
src/main.rs
Normal file
|
@ -0,0 +1,12 @@
|
|||
mod server;
|
||||
mod config;
|
||||
mod args;
|
||||
mod paste;
|
||||
mod error;
|
||||
|
||||
#[rocket::main]
|
||||
async fn main() -> Result<(), rocket::Error> {
|
||||
server::app::start_paste_eater().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
221
src/paste.rs
Normal file
221
src/paste.rs
Normal file
|
@ -0,0 +1,221 @@
|
|||
use std::{path::{Path, PathBuf}, fs::File};
|
||||
|
||||
use chrono::{DateTime, Local};
|
||||
use lz4_flex::{compress_prepend_size, decompress_size_prepended};
|
||||
use rand::{Rng, distributions::Alphanumeric};
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
use crate::{config::ConfigurationHandler, error::PasteEaterError};
|
||||
|
||||
type PasteError = PasteEaterError;
|
||||
|
||||
type PasteUID = String;
|
||||
|
||||
#[repr(u8)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub enum PasteLanguage {
|
||||
None = 0u8,
|
||||
CSharp = 1u8,
|
||||
Java = 2u8,
|
||||
CPlusPlus = 3u8,
|
||||
C = 4u8,
|
||||
Python = 5u8,
|
||||
Rust = 6u8,
|
||||
Go = 7u8,
|
||||
}
|
||||
|
||||
fn convert_u8_to_language(val: u8) -> PasteLanguage {
|
||||
match val {
|
||||
0u8 => PasteLanguage::None,
|
||||
1u8 => PasteLanguage::CSharp,
|
||||
2u8 => PasteLanguage::Java,
|
||||
3u8 => PasteLanguage::CPlusPlus,
|
||||
4u8 => PasteLanguage::C,
|
||||
5u8 => PasteLanguage::Python,
|
||||
6u8 => PasteLanguage::Rust,
|
||||
7u8 => PasteLanguage::Go,
|
||||
_ => PasteLanguage::None
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct PasteOutput {
|
||||
pub encrypted: bool,
|
||||
pub created: String,
|
||||
pub last_accessed: String,
|
||||
pub language: PasteLanguage,
|
||||
pub data: String
|
||||
}
|
||||
|
||||
pub struct PasteHandler {
|
||||
config_handler: ConfigurationHandler,
|
||||
// storage_size_cache: u64, TODO: Optimize storage size tracking
|
||||
|
||||
}
|
||||
|
||||
impl PasteHandler {
|
||||
pub fn new(config_handler: ConfigurationHandler) -> Self {
|
||||
Self {
|
||||
config_handler,
|
||||
// storage_size_cache: 0
|
||||
}
|
||||
}
|
||||
|
||||
pub fn create_new_paste(&self, encrypt: bool, language: &PasteLanguage, paste_data: &String) -> Result<PasteUID, PasteError> {
|
||||
let config = self.config_handler.fetch_config()?;
|
||||
|
||||
let data_path = Path::new(&config.files_location);
|
||||
|
||||
match data_path.try_exists() {
|
||||
Ok(exists) => {
|
||||
if !exists {
|
||||
match std::fs::create_dir_all(data_path) {
|
||||
Ok(_) => {},
|
||||
Err(internal) => return Err(PasteError::new_internal(&format!("Failed to create data directory '{}'.", data_path.display()), Box::new(internal))),
|
||||
}
|
||||
}
|
||||
},
|
||||
Err(err) => return Err(PasteError::new_internal(&format!("Failed to check if data directory '{}' exists.", data_path.display()), Box::new(err))),
|
||||
}
|
||||
|
||||
// TODO: Encrypt (?)
|
||||
|
||||
if paste_data.len() as u64 > config.files_max_single_file_size_in_bytes {
|
||||
return Err(PasteError::new("Uploaded paste is larger than maximum allowed size."));
|
||||
}
|
||||
|
||||
|
||||
// TODO: Enforce max size for single file and max total sizes. Note: scrapped for now.
|
||||
// if self.determine_current_storage_size() + paste_data.len() as u64 > config.files_max_total_file_size_in_bytes {
|
||||
// if let Err(e) = std::fs::remove_file(self.find_oldest_paste_larger_than(paste_data.len() as u64)) {
|
||||
// return Err(PasteError::new_internal("Unable to save paste, overloads capacity and no additional storage could be acquired.", Box::new(e)));
|
||||
// }
|
||||
// }
|
||||
|
||||
let (file_path, uid) = self.create_new_paste_file(Path::new(&config.files_location), config.name_size)?;
|
||||
|
||||
// paste file format:
|
||||
// 0: is encrypted | is compressed | unused | unused | unused | unused | unused | unused
|
||||
// 1: language byte
|
||||
// 2-end: data bytes
|
||||
|
||||
let mut file_data: Vec<u8> = Vec::new();
|
||||
|
||||
let mut flags = 0b0000_0000;
|
||||
|
||||
if encrypt {
|
||||
flags |= 0b1000_0000;
|
||||
}
|
||||
|
||||
if config.compress {
|
||||
flags |= 0b0100_0000;
|
||||
}
|
||||
|
||||
file_data.push(flags);
|
||||
file_data.push(language.to_owned() as u8);
|
||||
|
||||
if config.compress {
|
||||
file_data.extend(compress_prepend_size(paste_data.as_bytes()));
|
||||
}
|
||||
|
||||
match std::fs::write(&file_path, file_data) {
|
||||
Ok(_) => Ok(uid),
|
||||
Err(e) => Err(PasteError::new_internal(&format!("Failed to write paste file '{}'.", file_path.display()), Box::new(e))),
|
||||
}
|
||||
}
|
||||
|
||||
// fn determine_current_storage_size(&self) -> u64 {
|
||||
// 0
|
||||
// }
|
||||
|
||||
// fn find_oldest_paste_larger_than(&self, size: u64) -> PathBuf {
|
||||
// Path::new(".").to_path_buf()
|
||||
// }
|
||||
|
||||
fn create_new_paste_file(&self, directory: &Path, name_size: usize) -> Result<(PathBuf, PasteUID), PasteError> {
|
||||
let mut uid: String;
|
||||
let mut file_path;
|
||||
|
||||
loop {
|
||||
uid = rand::thread_rng()
|
||||
.sample_iter(&Alphanumeric)
|
||||
.take(name_size)
|
||||
.map(char::from)
|
||||
.collect();
|
||||
|
||||
file_path = directory.to_path_buf();
|
||||
|
||||
file_path.push(Path::new(&format!("{}.paste", uid)));
|
||||
|
||||
if !file_path.exists() {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
match File::create(&file_path) {
|
||||
Ok(_) => Ok((file_path, uid)),
|
||||
Err(e) => Err(PasteError::new_internal(&format!("Failed to create paste file '{}'.", file_path.display()), Box::new(e))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fetch_raw_paste(&self, uid: PasteUID) -> Result<PasteOutput, PasteError> {
|
||||
let config = self.config_handler.fetch_config()?;
|
||||
|
||||
let mut file_path = Path::new(&config.files_location).to_path_buf();
|
||||
|
||||
file_path.push(Path::new(&format!("{}.paste", uid)));
|
||||
|
||||
if !file_path.exists() {
|
||||
return Err(PasteError::new(&format!("Requested paste '{}' does not exist.", uid)));
|
||||
}
|
||||
|
||||
let file_bytes = match std::fs::read(&file_path) {
|
||||
Ok(bytes) => bytes,
|
||||
Err(e) => return Err(PasteError::new_internal(&format!("Failed to read paste '{}'.", uid), Box::new(e))),
|
||||
};
|
||||
|
||||
let flags = file_bytes[0];
|
||||
|
||||
let encrypted = 0b1000_0000 & flags != 0;
|
||||
let compressed = 0b0100_0000 & flags != 0;
|
||||
|
||||
let language = convert_u8_to_language(file_bytes[1]);
|
||||
|
||||
let bytes = if compressed {
|
||||
match decompress_size_prepended(&file_bytes[2..]) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return Err(PasteError::new_internal(&format!("Failed to decompress paste '{}'.", uid), Box::new(e))),
|
||||
}
|
||||
} else {
|
||||
file_bytes[2..].to_vec()
|
||||
};
|
||||
|
||||
let data = match String::from_utf8(bytes) {
|
||||
Ok(d) => d,
|
||||
Err(e) => return Err(PasteError::new_internal(&format!("Failed to parse paste '{}'.", uid), Box::new(e))),
|
||||
};
|
||||
|
||||
let mut paste = PasteOutput {
|
||||
encrypted,
|
||||
last_accessed: DateTime::UNIX_EPOCH.to_rfc2822(),
|
||||
created: DateTime::UNIX_EPOCH.to_rfc2822(),
|
||||
language,
|
||||
data
|
||||
};
|
||||
|
||||
if let Ok(metadata) = std::fs::metadata(file_path) {
|
||||
paste.created = match metadata.created() {
|
||||
Ok(created) => DateTime::<Local>::from(created).to_rfc2822(),
|
||||
Err(_) => paste.created,
|
||||
};
|
||||
|
||||
paste.last_accessed = match metadata.accessed() {
|
||||
Ok(accessed) => DateTime::<Local>::from(accessed).to_rfc2822(),
|
||||
Err(_) => paste.last_accessed,
|
||||
};
|
||||
}
|
||||
|
||||
Ok(paste)
|
||||
}
|
||||
}
|
||||
|
29
src/server/app.rs
Normal file
29
src/server/app.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use clap::Parser;
|
||||
use rocket::routes;
|
||||
|
||||
use crate::{server::endpoints, args::Args, config::ConfigurationHandler, paste::PasteHandler, error::PasteEaterError};
|
||||
|
||||
pub async fn start_paste_eater() -> Result<(), rocket::Error> {
|
||||
let paste_handler = match create_paste_handler() {
|
||||
Ok(ph) => ph,
|
||||
Err(e) => {
|
||||
panic!("{}", e);
|
||||
},
|
||||
};
|
||||
|
||||
let _rocket = rocket::build()
|
||||
.manage(paste_handler)
|
||||
.mount("/api", routes![endpoints::create_paste, endpoints::get_paste])
|
||||
.launch()
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn create_paste_handler() -> Result<PasteHandler, PasteEaterError> {
|
||||
let args = Args::parse();
|
||||
|
||||
let config_handler = ConfigurationHandler::new_with_args(&args)?;
|
||||
|
||||
Ok(PasteHandler::new(config_handler))
|
||||
}
|
33
src/server/endpoints.rs
Normal file
33
src/server/endpoints.rs
Normal file
|
@ -0,0 +1,33 @@
|
|||
use rocket::{post, get, response::status::Custom, http::Status, State, serde::json::Json};
|
||||
use serde_derive::{Serialize, Deserialize};
|
||||
|
||||
use crate::paste::{PasteHandler, PasteOutput, PasteLanguage};
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PasteInput {
|
||||
encrypted: bool,
|
||||
language: PasteLanguage,
|
||||
data: String
|
||||
}
|
||||
|
||||
#[post("/paste", data = "<paste>")]
|
||||
pub fn create_paste(paste: Json<PasteInput>, paste_handler: &State<PasteHandler>) -> Custom<String> {
|
||||
match paste_handler.create_new_paste(paste.encrypted, &paste.language, &paste.data) {
|
||||
Ok(uid) => Custom(Status::Ok, uid),
|
||||
Err(e) => Custom(Status::InternalServerError, format!("{}", e)),
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize)]
|
||||
pub struct PasteResponse {
|
||||
pub paste: Option<PasteOutput>,
|
||||
pub error: Option<String>
|
||||
}
|
||||
|
||||
#[get("/paste/<uid>")]
|
||||
pub fn get_paste(uid: String, paste_handler: &State<PasteHandler>) -> Custom<Json<PasteResponse>> {
|
||||
match paste_handler.fetch_raw_paste(uid) {
|
||||
Ok(paste) => Custom(Status::Ok, Json(PasteResponse { paste: Some(paste), error: None })),
|
||||
Err(e) => Custom(Status::InternalServerError, Json(PasteResponse { paste: None, error: Some(format!("{}", e)) })),
|
||||
}
|
||||
}
|
3
src/server/mod.rs
Normal file
3
src/server/mod.rs
Normal file
|
@ -0,0 +1,3 @@
|
|||
mod endpoints;
|
||||
|
||||
pub mod app;
|
Loading…
Add table
Reference in a new issue