Getting ready for 1.2.0 release: many smaller requests implemented

- Implements #7
- Implements #42 and therefore #64
- Improved #53
- Implements #59
- Implements #61
- Implements #63
- Implements #80
- Implements #84
- Added Info page
- Removed Help page
- Bumped version number to 1.2.0
- Fixed a bug where wide mode was still 720px wide
- Created FUNDING.yml
- Reorganised arguments in README.MD and documented new options
- Updated SECURITY.MD
- Added display of last read time and read count
- Increased default width to 800px to make UI less cluttered
- Reorganised index page
- New, better attach file button

I want to spend some time testing these changes and let everyone have a look at them before tagging and releasing new artifacts.
This commit is contained in:
Daniel Szabo 2022-10-29 14:11:55 +03:00
parent 769901c895
commit 44b55ae08e
22 changed files with 977 additions and 327 deletions

View file

@ -54,6 +54,9 @@ pub async fn create(
private: false,
editable: false,
created: timenow,
read_count: 0,
burn_after_reads: 0,
last_read: timenow,
pasta_type: String::from(""),
expiration: 0,
};
@ -78,9 +81,34 @@ pub async fn create(
"1hour" => timenow + 60 * 60,
"24hour" => timenow + 60 * 60 * 24,
"1week" => timenow + 60 * 60 * 24 * 7,
"never" => 0,
"never" => {
if ARGS.no_eternal_pasta {
timenow + 60 * 60 * 24 * 7
} else {
0
}
}
_ => {
log::error!("{}", "Unexpected expiration time!");
timenow + 60 * 60 * 24 * 7
}
};
}
continue;
}
"burn_after" => {
while let Some(chunk) = field.try_next().await? {
new_pasta.burn_after_reads = match std::str::from_utf8(&chunk).unwrap() {
// give an extra read because the user will be redirected to the pasta page automatically
"1" => 2,
"10" => 10,
"100" => 100,
"1000" => 1000,
"10000" => 10000,
"0" => 0,
_ => {
log::error!("{}", "Unexpected burn after value!");
0
}
};
@ -126,8 +154,11 @@ pub async fn create(
}
};
std::fs::create_dir_all(format!("./pasta_data/public/{}", &new_pasta.id_as_animals()))
.unwrap();
std::fs::create_dir_all(format!(
"./pasta_data/public/{}",
&new_pasta.id_as_animals()
))
.unwrap();
let filepath = format!(
"./pasta_data/public/{}/{}",
@ -158,6 +189,9 @@ pub async fn create(
save_to_file(&pastas);
Ok(HttpResponse::Found()
.append_header(("Location", format!("{}/pasta/{}", ARGS.public_path, to_animal_names(id))))
.append_header((
"Location",
format!("{}/pasta/{}", ARGS.public_path, to_animal_names(id)),
))
.finish())
}

View file

@ -1,23 +0,0 @@
use crate::args::{Args, ARGS};
use actix_web::{get, HttpResponse};
use askama::Template;
use std::marker::PhantomData;
#[derive(Template)]
#[template(path = "help.html")]
struct Help<'a> {
args: &'a Args,
_marker: PhantomData<&'a ()>,
}
#[get("/help")]
pub async fn help() -> HttpResponse {
HttpResponse::Ok().content_type("text/html").body(
Help {
args: &ARGS,
_marker: Default::default(),
}
.render()
.unwrap(),
)
}

42
src/endpoints/info.rs Normal file
View file

@ -0,0 +1,42 @@
use crate::args::{Args, ARGS};
use crate::pasta::Pasta;
use crate::AppState;
use actix_web::{get, web, HttpResponse};
use askama::Template;
#[derive(Template)]
#[template(path = "info.html")]
struct Info<'a> {
args: &'a Args,
pastas: &'a Vec<Pasta>,
status: &'a String,
version_string: &'a String,
message: &'a String,
}
#[get("/info")]
pub async fn info(data: web::Data<AppState>) -> HttpResponse {
// get access to the pasta collection
let mut pastas = data.pastas.lock().unwrap();
// todo status report more sophisticated
let mut status = "OK";
let mut message = "";
if ARGS.public_path.to_string() == "" {
status = "WARNING";
message = "Warning: No public URL set with --public-path parameter. QR code and URL Copying functions have been disabled"
}
HttpResponse::Ok().content_type("text/html").body(
Info {
args: &ARGS,
pastas: &pastas,
status: &String::from(status),
version_string: &String::from("1.2.0-20221029"),
message: &String::from(message),
}
.render()
.unwrap(),
)
}

View file

@ -1,12 +1,14 @@
use actix_web::{get, web, HttpResponse};
use askama::Template;
use crate::args::{Args, ARGS};
use crate::dbio::save_to_file;
use crate::endpoints::errors::ErrorTemplate;
use crate::pasta::Pasta;
use crate::util::animalnumbers::to_u64;
use crate::util::misc::remove_expired;
use crate::AppState;
use actix_web::rt::time;
use actix_web::{get, web, HttpResponse};
use askama::Template;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Template)]
#[template(path = "pasta.html", escape = "none")]
@ -17,27 +19,60 @@ struct PastaTemplate<'a> {
#[get("/pasta/{id}")]
pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
// get access to the pasta collection
let mut pastas = data.pastas.lock().unwrap();
// get the u64 id from the animal names in the path
let id = to_u64(&*id.into_inner()).unwrap_or(0);
println!("{}", id);
// remove expired pastas (including this one if needed)
remove_expired(&mut pastas);
for pasta in pastas.iter() {
// find the index of the pasta in the collection based on u64 id
let mut index: usize = 0;
let mut found: bool = false;
for (i, pasta) in pastas.iter().enumerate() {
if pasta.id == id {
return HttpResponse::Ok().content_type("text/html").body(
PastaTemplate {
pasta: &pasta,
args: &ARGS,
}
.render()
.unwrap(),
);
index = i;
found = true;
break;
}
}
if found {
// increment read count
pastas[index].read_count = pastas[index].read_count + 1;
// save the updated read count
save_to_file(&pastas);
// serve pasta in template
let response = HttpResponse::Ok().content_type("text/html").body(
PastaTemplate {
pasta: &pastas[index],
args: &ARGS,
}
.render()
.unwrap(),
);
// get current unix time in seconds
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => {
log::error!("SystemTime before UNIX EPOCH!");
0
}
} as i64;
// update last read time
pastas[index].last_read = timenow;
return response;
}
// otherwise
// send pasta not found error
HttpResponse::Ok()
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
@ -45,26 +80,62 @@ pub async fn getpasta(data: web::Data<AppState>, id: web::Path<String>) -> HttpR
#[get("/url/{id}")]
pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
// get access to the pasta collection
let mut pastas = data.pastas.lock().unwrap();
// get the u64 id from the animal names in the path
let id = to_u64(&*id.into_inner()).unwrap_or(0);
// remove expired pastas (including this one if needed)
remove_expired(&mut pastas);
for pasta in pastas.iter() {
// find the index of the pasta in the collection based on u64 id
let mut index: usize = 0;
let mut found: bool = false;
for (i, pasta) in pastas.iter().enumerate() {
if pasta.id == id {
if pasta.pasta_type == "url" {
return HttpResponse::Found()
.append_header(("Location", String::from(&pasta.content)))
.finish();
} else {
return HttpResponse::Ok()
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap());
}
index = i;
found = true;
break;
}
}
if found {
// increment read count
pastas[index].read_count = pastas[index].read_count + 1;
// save the updated read count
save_to_file(&pastas);
// send redirect if it's a url pasta
if pastas[index].pasta_type == "url" {
let response = HttpResponse::Found()
.append_header(("Location", String::from(&pastas[index].content)))
.finish();
// get current unix time in seconds
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => {
log::error!("SystemTime before UNIX EPOCH!");
0
}
} as i64;
// update last read time
pastas[index].last_read = timenow;
return response;
// send error if we're trying to open a non-url pasta as a redirect
} else {
HttpResponse::Ok()
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap());
}
}
// otherwise
// send pasta not found error
HttpResponse::Ok()
.content_type("text/html")
.body(ErrorTemplate { args: &ARGS }.render().unwrap())
@ -72,17 +143,50 @@ pub async fn redirecturl(data: web::Data<AppState>, id: web::Path<String>) -> Ht
#[get("/raw/{id}")]
pub async fn getrawpasta(data: web::Data<AppState>, id: web::Path<String>) -> String {
// get access to the pasta collection
let mut pastas = data.pastas.lock().unwrap();
// get the u64 id from the animal names in the path
let id = to_u64(&*id.into_inner()).unwrap_or(0);
// remove expired pastas (including this one if needed)
remove_expired(&mut pastas);
for pasta in pastas.iter() {
// find the index of the pasta in the collection based on u64 id
let mut index: usize = 0;
let mut found: bool = false;
for (i, pasta) in pastas.iter().enumerate() {
if pasta.id == id {
return pasta.content.to_owned();
index = i;
found = true;
break;
}
}
if found {
// increment read count
pastas[index].read_count = pastas[index].read_count + 1;
// get current unix time in seconds
let timenow: i64 = match SystemTime::now().duration_since(UNIX_EPOCH) {
Ok(n) => n.as_secs(),
Err(_) => {
log::error!("SystemTime before UNIX EPOCH!");
0
}
} as i64;
// update last read time
pastas[index].last_read = timenow;
// save the updated read count
save_to_file(&pastas);
// send raw content of pasta
return pastas[index].content.to_owned();
}
// otherwise
// send pasta not found error as raw text
String::from("Pasta not found! :-(")
}

34
src/endpoints/qr.rs Normal file
View file

@ -0,0 +1,34 @@
use crate::args::{Args, ARGS};
use crate::pasta::Pasta;
use crate::util::misc::{self, remove_expired};
use crate::AppState;
use actix_web::{get, web, HttpResponse};
use askama::Template;
use qrcode_generator::QrCodeEcc;
use std::time::{SystemTime, UNIX_EPOCH};
#[derive(Template)]
#[template(path = "qr.html", escape = "none")]
struct QRTemplate<'a> {
qr: &'a String,
args: &'a Args,
}
#[get("/qr/{id}")]
pub async fn getqr(data: web::Data<AppState>, id: web::Path<String>) -> HttpResponse {
// find the index of the pasta in the collection based on u64 id
let svg: String = misc::string_to_qr_svg(
format!("{}/pasta/{}", &ARGS.public_path, &*id.into_inner()).as_str(),
);
// serve qr code in template
HttpResponse::Ok().content_type("text/html").body(
QRTemplate {
qr: &svg,
args: &ARGS,
}
.render()
.unwrap(),
)
}