6 Commits
Author SHA1 Message Date
dietrich fd255d9f74 Update the readme 2021-10-17 10:29:28 +02:00
dietrich e2738678c8 use a git dependency instead of local 2021-10-11 13:25:47 +02:00
dietrich 3bb79666b2 Bumping versions using actix-web 4 2021-10-11 13:04:24 +02:00
dietrich c148e839f6 restructure an if 2021-10-04 13:46:19 +02:00
dietrich e98b468b10 Add demo mode + various fixes. 2021-10-04 13:46:19 +02:00
dietrich 7c2ce180c6 Added Help command to README 2021-10-04 13:46:19 +02:00
17 changed files with 992 additions and 924 deletions
+1 -1
View File
@@ -27,7 +27,7 @@ jobs:
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-cargo
- name: Install musl-tools
run: sudo apt-get install musl-tools
- name: Build
+1 -1
View File
@@ -35,7 +35,7 @@ jobs:
~/.cargo/registry/cache/
~/.cargo/git/db/
target/
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
key: ${{ runner.os }}-cargols
- name: Install musl-tools
run: sudo apt-get install musl-tools
- name: Build
Generated
+733 -788
View File
File diff suppressed because it is too large Load Diff
+40 -7
View File
@@ -1,11 +1,17 @@
# Pslink a "Private Short Link page"
The target audience of this tool are small entities that need a url shortener. The shortened urls can be publicly resolved but only registered users can create short urls. Every registered user can see all shorted urls but ownly modify its own. Admin users can invite other accounts and edit everything that can be edited (also urls created by other accounts).
The target audience of this tool are small entities that need a url shortener. The shortened urls can be publicly resolved but only registered users can create short urls. Every registered user can see all shorted urls but only modify its own. Admin users can invite other accounts and edit everything that can be edited (also urls created by other accounts).
So in general this is more a shared short url bookmark webpage than a short url service.
![Screenshot](./doc/img/screenshot.png)
![Screenshot](./doc/img/screenshot_edit.png)
## Demo
A demo instance is running under [https://pslink.teilgedanken.de/app/](https://pslink.teilgedanken.de/app/)
The Username and Password are both `demo`. Do not use this for any production usecase as the database is wiped every 15 minutes. If your created links/users are suddenly missing this is due to such a database wipe.
[![Screenshot](./doc/img/screenshot.png)](https://pslink.teilgedanken.de/app/)
[![Screenshot](./doc/img/screenshot_edit.png)](https://pslink.teilgedanken.de/app/)
## What users can do
@@ -22,7 +28,7 @@ So in general this is more a shared short url bookmark webpage than a shorturl s
* download qr-codes of the links
* modify own "profile" settings
### Admins (priviledged account)
### Admins (privileged account)
* everything from users
* modify all links
@@ -95,15 +101,19 @@ $ cargo make build_release
If pslink is built with `cargo make build_standalone` everything is embedded and it should be portable to any 64bit linux system. Otherwise the same or newer version of libc needs to be installed on the target linux system. Note that you need to install `musl-gcc` for this to work using: `sudo dnf install musl-libc musl-gcc` or `sudo apt-get install musl-tools`.
Templates and migrations are allways embedded in the binary so it should run standalone without anything extra.
Templates and migrations are always embedded in the binary so it should run standalone without anything extra.
### Setup
To read the help and documentation of additional options call:
```pslink help```
To get Pslink up and running use the commands in the following order:
1. `pslink generate-env`
this will generate a `.env` file in the curent directory with the default settings. Edit this file to your liking. You can however skip this step and provide all the parameters via commandline or environmentvariable. It is **not** recommended to provide PSLINK_SECRET with commandline parameters as they can be read by every user on the system.
this will generate a `.env` file in the current directory with the default settings. Edit this file to your liking. You can however skip this step and provide all the parameters via command line or environment variable. It is **not** recommended to provide PSLINK_SECRET with command line parameters as they can be read by every user on the system.
2. `pslink migrate-database`
@@ -111,7 +121,7 @@ To get Pslink up and running use the commands in the following order:
3. `pslink create-admin`
create an initial admin user. As the page has no "register" function this is required to do anything usefull. The command is interactive so you will be asked the username and password of the new admin user.
create an initial admin user. As the page has no "register" function this is required to do anything useful. The command is interactive so you will be asked the username and password of the new admin user.
4. `pslink runserver`
@@ -164,3 +174,26 @@ ExecStart=/var/pslink/pslink runserver
[Install]
WantedBy=multi-user.target
```
### Setup a demo container
First build the standalone binary:
```bash
$ cargo make build_standalone
```
Create a temporary directory and copy the binary from above:
```bash
$ mkdir /tmp/pslink-container/
$ cp target/x86_64-unknown-linux-musl/release/pslink /tmp/pslink-container/
```
Run the container (podman is used here but docker could be used exactly the same):
```bash
$ podman run --expose 8080 -p=8080:8080 -it pslink-container ./pslink demo -i 0.0.0.0
```
Note that this is **absolutely not for a production use** and only for demo purposes as the links are **deleted on every restart**.
+4 -4
View File
@@ -8,7 +8,7 @@ keywords = ["url", "link", "webpage", "actix", "web"]
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/enaut/pslink/"
version = "0.4.3"
version = "0.4.4"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@@ -16,12 +16,12 @@ version = "0.4.3"
crate-type = ["cdylib", "rlib"]
[dependencies]
fluent = "0.15"
fluent = "0.16"
seed = "0.8"
serde = {version="1.0", features = ["derive"]}
unic-langid = "0.9"
strum_macros = "0.21"
strum = "0.21"
strum_macros = "0.22"
strum = "0.22"
enum-map = "1"
qrcode = "0.12"
image = "0.23"
+1 -1
View File
@@ -16,7 +16,7 @@ use crate::Msg;
pub fn navigation(i18n: &I18n, base_url: &Url, user: &User) -> Node<Msg> {
// A shortcut for translating strings.
let t = move |key: &str| i18n.translate(key, None);
// Translate the wellcome message
// Translate the welcome message
let welcome = i18n.translate(
"welcome-user",
Some(&fluent_args![ "username" => user.username.clone()]),
+19 -18
View File
@@ -91,7 +91,7 @@ impl<T> Deref for Cached<T> {
}
}
/// There can allways be only one dialog.
/// There can always be only one dialog.
#[derive(Debug, Clone)]
enum Dialog {
EditLink {
@@ -150,7 +150,7 @@ pub enum Msg {
Query(QueryMsg), // Messages related to querying links
Edit(EditMsg), // Messages related to editing links
ClearAll, // Clear all messages
SetupObserver, // Make an observer for endles scroll
SetupObserver, // Make an observer for endless scroll
Observed(Vec<IntersectionObserverEntry>),
SetMessage(String), // Set a message to the user
}
@@ -244,7 +244,7 @@ pub fn update(msg: Msg, model: &mut Model, orders: &mut impl Orders<Msg>) {
log!("element not yet registered! ");
};
} else {
log!("Failed to get observer!")
log!("Failed to get observer!");
};
}
}
@@ -255,13 +255,13 @@ pub fn process_query_messages(msg: QueryMsg, model: &mut Model, orders: &mut imp
match msg {
QueryMsg::Fetch => {
orders.skip(); // No need to rerender
initial_load(model, orders)
initial_load(model, orders);
}
QueryMsg::FetchAdditional => {
orders.skip(); // No need to rerender
consecutive_load(model, orders)
consecutive_load(model, orders);
}
// Default to ascending ordering but if the links are already sorted according to this collumn toggle between ascending and descending ordering.
// Default to ascending ordering but if the links are already sorted according to this column toggle between ascending and descending ordering.
QueryMsg::OrderBy(column) => {
model.formconfig.order = model.formconfig.order.as_ref().map_or_else(
|| {
@@ -316,7 +316,7 @@ pub fn process_query_messages(msg: QueryMsg, model: &mut Model, orders: &mut imp
QueryMsg::ReceivedAdditional(response) => {
if response.len() < model.formconfig.amount {
log!("There are no more links! ");
model.everything_loaded = true
model.everything_loaded = true;
};
let mut new_links = response
.into_iter()
@@ -376,7 +376,7 @@ fn load_links(orders: &mut impl Orders<Msg>, data: LinkRequestForm) {
.json(&data),
Msg::SetMessage("Failed to parse data".to_string())
);
// send the request and recieve a response
// send the request and receive a response
let response = unwrap_or_return!(
fetch(request).await,
Msg::SetMessage("Failed to send data".to_string())
@@ -554,7 +554,7 @@ fn delete_link(link_delta: LinkDelta, orders: &mut impl Orders<Msg>) {
.json(&link_delta),
Msg::SetMessage("serialization failed".to_string())
);
// perform the request and recieve a respnse
// perform the request and receive a response
let response =
unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToDeleteLink));
@@ -746,7 +746,8 @@ fn view_link(l: &Cached<FullLink>, logged_in_user: &User) -> Node<Msg> {
C!["table_qr"],
a![
ev(Ev::Click, |event| event.stop_propagation()),
attrs![At::Href => format!["/admin/download/png/{}", &l.link.code], At::Download => true.as_at_value()],
attrs![At::Href => format!("/admin/download/png/{}", &l.link.code),
At::Download => true.as_at_value()],
raw!(&l.cache)
]
]
@@ -820,14 +821,7 @@ fn edit_or_create_link<F: Fn(&str) -> String>(
],
tr![
th![t("qr-code")],
if let Loadable::Data(Some(qr)) = qr {
td![a![
span![C!["qrdownload"], "Download", raw!(&qr.svg),],
attrs!(At::Href => qr.url, At::Download => "qr-code.png")
]]
} else {
td!["Loading..."]
}
qr.as_ref().map_or_else(|| td!["Loading..."], render_qr),
]
],
a![
@@ -841,6 +835,13 @@ fn edit_or_create_link<F: Fn(&str) -> String>(
]
}
fn render_qr(qr: &QrGuard) -> Node<Msg> {
td![a![
span![C!["qrdownload"], "Download", raw!(&qr.svg),],
attrs!(At::Href => qr.url, At::Download => "qr-code.png")
]]
}
/// generate a qr-code for a code
fn generate_qr_from_code(code: &str) -> String {
generate_qr_from_link(&format!("https://{}/{}", get_host(), code))
+4 -4
View File
@@ -62,7 +62,7 @@ struct FilterInput {
filter_input: ElRef<web_sys::HtmlInputElement>,
}
/// The message splits the contained message into messages related to querrying and messages related to editing.
/// The message splits the contained message into messages related to querying and messages related to editing.
#[derive(Clone)]
pub enum Msg {
Query(UserQueryMsg),
@@ -161,7 +161,7 @@ pub fn process_query_messages(msg: UserQueryMsg, model: &mut Model, orders: &mut
}
UserQueryMsg::EmailFilterChanged(s) => {
log!("Filter is: ", &s);
// FIXME: Sanitazion does not work for @
// FIXME: Sanitation does not work for @
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
model.formconfig.filter[UserOverviewColumns::Email].sieve = sanit;
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
@@ -238,7 +238,7 @@ pub fn process_user_edit_messages(
let data = model
.user_edit
.take()
.expect("A user should allways be there on save");
.expect("A user should always be there on save");
log!("Saving User: ", &data.username);
save_user(data, orders);
}
@@ -393,7 +393,7 @@ fn view_user_table_head<F: Fn(&str) -> String>(t: F) -> Node<Msg> {
]
}
/// Display the filterboxes below the headlines
/// Display the filter-boxes below the headlines
fn view_user_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> Node<Msg> {
tr![
C!["filters"],
+2 -2
View File
@@ -8,13 +8,13 @@ keywords = ["url", "link", "webpage", "actix", "web"]
license = "MIT OR Apache-2.0"
readme = "README.md"
repository = "https://github.com/enaut/pslink/"
version = "0.4.3"
version = "0.4.4"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
fluent = "0.15"
fluent = "0.16"
serde = {version="1.0", features = ["derive"]}
unic-langid = "0.9"
+18 -15
View File
@@ -9,33 +9,36 @@ license = "MIT OR Apache-2.0"
name = "pslink"
readme = "README.md"
repository = "https://github.com/enaut/pslink/"
version = "0.4.3"
version = "0.4.4"
[build-dependencies]
actix-web-static-files = "3.0"
actix-web-static-files = { git = "https://github.com/enaut/actix-web-static-files.git", branch="master" }
static-files = { version = "0.2", default-features = false }
[dependencies]
actix-identity = "0.3"
actix-rt = "1.1"
actix-web = "3"
actix-web-static-files = "3"
actix-files = "0.5"
actix-identity = "0.4.0-beta.2"
actix-rt = "2.2"
actix-web = "4.0.0-beta.9"
actix-web-static-files = { git = "https://github.com/enaut/actix-web-static-files.git", branch="master" }
actix-files = "0.6.0-beta.7"
argonautica = "0.2"
clap = "2.33"
dotenv = "0.15.0"
fluent-langneg = "0.13"
image = "0.23"
opentelemetry = "0.14"
opentelemetry-jaeger = "0.12"
opentelemetry = "0.16"
opentelemetry-jaeger = "0.15"
qrcode = "0.12"
rand = "0.8"
rpassword = "5.0"
serde = {version="1.0", features = ["derive"]}
static-files = { version = "0.2", default-features = false }
thiserror = "1.0"
tracing-actix-web = "0.2.1"
tracing-opentelemetry = "0.12"
tracing-actix-web = "0.4.0-beta.13"
tracing-opentelemetry = "0.15"
async-trait = "0.1"
enum-map = {version="1", features = ["serde"]}
indexmap = "~1.6.2"
pslink-shared = {version="0.4", path = "../shared" }
@@ -48,7 +51,7 @@ version = "0.6"
[dependencies.sqlx]
features = ["sqlite", "macros", "runtime-actix-rustls", "chrono", "migrate", "offline"]
version = "0.4"
version = "0.5"
[dependencies.tracing]
features = ["log"]
@@ -62,11 +65,11 @@ version = "0.2.17"
actix-server = "1.0.4"
tempdir = "0.3"
test_bin = "0.3"
tokio = "0.2.25"
assert_cmd = "1.0.7"
tokio = "1.12"
assert_cmd = "2.0"
predicates = "2.0.0"
[dev-dependencies.reqwest]
features = ["cookies", "json"]
version = "0.10.10"
version = "0.11"
+1 -1
View File
@@ -1,4 +1,4 @@
use actix_web_static_files::resource_dir;
use static_files::resource_dir;
fn main() {
resource_dir("./static/").build().unwrap();
+101 -16
View File
@@ -1,6 +1,6 @@
use clap::{
app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, Arg,
ArgMatches, SubCommand,
app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, AppSettings,
Arg, ArgMatches, SubCommand,
};
use dotenv::dotenv;
use pslink_shared::datatypes::{Secret, User};
@@ -12,7 +12,7 @@ use std::{
};
use pslink::{
models::{NewUser, UserDbOperations},
models::{NewLink, NewUser, UserDbOperations},
ServerConfig, ServerError,
};
@@ -47,7 +47,7 @@ fn generate_cli() -> App<'static, 'static> {
.short("u")
.help("The host url or the page that will be part of the short urls.")
.env("PSLINK_PUBLIC_URL")
.default_value("localhost:8080")
.default_value("127.0.0.1:8080")
.global(true),
)
.arg(
@@ -63,7 +63,7 @@ fn generate_cli() -> App<'static, 'static> {
Arg::with_name("brand_name")
.long("brand-name")
.short("b")
.help("The Brandname that will apper in various places.")
.help("The brand name that will appear in various places.")
.env("PSLINK_BRAND_NAME")
.default_value("Pslink")
.global(true),
@@ -74,7 +74,7 @@ fn generate_cli() -> App<'static, 'static> {
.short("i")
.help("The host (ip) that will run the pslink service")
.env("PSLINK_IP")
.default_value("localhost")
.default_value("127.0.0.1")
.global(true),
)
.arg(
@@ -95,7 +95,7 @@ fn generate_cli() -> App<'static, 'static> {
.long("secret")
.help(concat!(
"The secret that is used to encrypt the",
" password database keep this as inacessable as possible.",
" password database keep this as inaccessible as possible.",
" As command line parameters are visible",
" to all users",
" it is not wise to use this as",
@@ -125,6 +125,12 @@ fn generate_cli() -> App<'static, 'static> {
.about("Create an admin user.")
.display_order(2),
)
.subcommand(
SubCommand::with_name("demo")
.about("Create a database and demo user.")
.display_order(3)
.setting(AppSettings::Hidden),
)
}
/// parse the options to the [`ServerConfig`] struct
@@ -209,7 +215,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
/// Setup and launch the command
///
/// # Panics
/// This funcion panics if preconditions like the availability of the database are not met.
/// This function panics if preconditions like the availability of the database are not met.
pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
// load the environment .env file if available.
dotenv().ok();
@@ -234,7 +240,8 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
if !db.exists() {
trace!("No database file found {}", db.display());
if !(config.subcommand_matches("migrate-database").is_none()
| config.subcommand_matches("generate-env").is_none())
| config.subcommand_matches("generate-env").is_none()
| config.subcommand_matches("demo").is_none())
{
let msg = format!(
concat!(
@@ -268,12 +275,40 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
};
}
if let Some(_create_config) = config.subcommand_matches("create-admin") {
return match create_admin(&server_config).await {
return match request_admin_credentials(&server_config).await {
Ok(_) => Ok(None),
Err(e) => Err(e),
};
}
if let Some(_runserver_config) = config.subcommand_matches("demo") {
let num_users = User::count_admins(&server_config).await;
match num_users {
Err(_) => {
generate_env_file(&server_config).expect("Failed to generate env file.");
apply_migrations(&server_config)
.await
.expect("Failed to apply migrations.");
let new_admin = NewUser::new(
"demo".to_string(),
"demo@teilgedanken.de".to_string(),
"demo",
&server_config.secret,
)
.expect("Failed to generate new user credentials.");
create_admin(&new_admin, &server_config)
.await
.expect("Failed to create admin");
add_example_links(&server_config).await;
return Ok(Some(server_config));
}
_ => {
return Err(ServerError::User("The database is not empty aborting because this could mean that creating a demo instance would lead in data loss.".to_string()));
}
}
}
if let Some(_runserver_config) = config.subcommand_matches("runserver") {
let num_users = User::count_admins(&server_config).await?;
@@ -294,8 +329,54 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
}
}
async fn add_example_links(server_config: &ServerConfig) {
NewLink {
title: "Pslink Repository".to_owned(),
target: "https://github.com/enaut/pslink".to_owned(),
code: "pslink".to_owned(),
author: 1,
created_at: chrono::Local::now().naive_utc(),
}
.insert(server_config)
.await
.expect("Failed to insert example 1");
NewLink {
title: "Seed".to_owned(),
target: "https://seed-rs.org/".to_owned(),
code: "seed".to_owned(),
author: 1,
created_at: chrono::Local::now().naive_utc(),
}
.insert(server_config)
.await
.expect("Failed to insert example 1");
NewLink {
title: "actix".to_owned(),
target: "https://actix.rs/".to_owned(),
code: "actix".to_owned(),
author: 1,
created_at: chrono::Local::now().naive_utc(),
}
.insert(server_config)
.await
.expect("Failed to insert example 1");
NewLink {
title: "rust".to_owned(),
target: "https://www.rust-lang.org/".to_owned(),
code: "rust".to_owned(),
author: 1,
created_at: chrono::Local::now().naive_utc(),
}
.insert(server_config)
.await
.expect("Failed to insert example 1");
}
/// Interactively create a new admin user.
async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
async fn request_admin_credentials(config: &ServerConfig) -> Result<(), ServerError> {
info!("Creating an admin user.");
let sin = io::stdin();
@@ -306,7 +387,7 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
io::stdout().flush().unwrap();
let new_username = sin.lock().lines().next().unwrap().unwrap();
print!("Please enter the emailadress for {}: ", new_username);
print!("Please enter the email address for {}: ", new_username);
io::stdout().flush().unwrap();
let new_email = sin.lock().lines().next().unwrap().unwrap();
@@ -325,16 +406,20 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
&config.secret,
)?;
new_admin.insert_user(config).await?;
let created_user = User::get_user_by_name(&new_username, config).await?;
create_admin(&new_admin, config).await
}
async fn create_admin(new_user: &NewUser, config: &ServerConfig) -> Result<(), ServerError> {
new_user.insert_user(config).await?;
let created_user = User::get_user_by_name(&new_user.username, config).await?;
created_user.toggle_admin(config).await?;
info!("Admin user created: {}", new_username);
info!("Admin user created: {}", &new_user.username);
Ok(())
}
/// Apply any pending migrations to the database. The migrations are embedded in the binary and don't need any addidtional files.
/// Apply any pending migrations to the database. The migrations are embedded in the binary and don't need any additional files.
async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> {
info!(
"Creating a database file and running the migrations in the file {}:",
+7 -4
View File
@@ -5,6 +5,8 @@ mod views;
use actix_files::Files;
use actix_identity::{CookieIdentityPolicy, IdentityService};
use actix_web::middleware::Compat;
use actix_web::web::Data;
use actix_web::{web, App, HttpServer};
use pslink::ServerConfig;
@@ -32,7 +34,7 @@ pub fn get_subscriber(name: &str, env_filter: &str) -> impl Subscriber + Send +
let otel_layer = OpenTelemetryLayer::new(tracer);
// Use the tracing subscriber `Registry`, or any other subscriber
// that impls `LookupSpan`
// that implements `LookupSpan`
Registry::default()
.with(otel_layer)
.with(env_filter)
@@ -79,7 +81,7 @@ async fn main() -> std::result::Result<(), std::io::Error> {
// include the static files into the binary
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
/// Launch the pslink-webservice
/// Launch the pslink-web-service
///
/// # Errors
/// This produces a [`ServerError`] if:
@@ -105,9 +107,10 @@ pub async fn webservice(
let server = HttpServer::new(move || {
let generated = generate();
let logger = Compat::new(TracingLogger::default());
App::new()
.data(server_config.clone())
.wrap(TracingLogger)
.app_data(Data::new(server_config.clone()))
.wrap(logger)
.wrap(IdentityService::new(
CookieIdentityPolicy::new(&[0; 32])
.name("auth-cookie")
+30 -28
View File
@@ -30,13 +30,13 @@ use pslink::ServerError;
#[instrument]
fn redirect_builder(target: &str) -> HttpResponse {
HttpResponse::SeeOther()
.set(CacheControl(vec![
.insert_header(CacheControl(vec![
CacheDirective::NoCache,
CacheDirective::NoStore,
CacheDirective::MustRevalidate,
]))
.set(Expires(SystemTime::now().into()))
.set_header(actix_web::http::header::LOCATION, target)
.insert_header(Expires(SystemTime::now().into()))
.insert_header((actix_web::http::header::LOCATION, target))
.body(format!("Redirect to {}", target))
}
@@ -67,9 +67,9 @@ fn detect_language(request: &HttpRequest) -> Result<Lang, ServerError> {
);
info!("supported languages: {:?}", supported);
if let Some(languagecode) = supported.get(0) {
info!("Supported Language: {}", languagecode);
Ok(languagecode
if let Some(language_code) = supported.get(0) {
info!("Supported Language: {}", language_code);
Ok(language_code
.to_string()
.parse()
.expect("Failed to parse 2 language"))
@@ -112,7 +112,7 @@ pub async fn index_json(
) -> Result<HttpResponse, ServerError> {
info!("Listing Links to Json api");
match queries::list_all_allowed(&id, &config, form.0).await {
Ok(links) => Ok(HttpResponse::Ok().json2(&links.list)),
Ok(links) => Ok(HttpResponse::Ok().json(&links.list)),
Err(e) => {
error!("Failed to access database: {:?}", e);
warn!("Not logged in - redirecting to login page");
@@ -129,7 +129,7 @@ pub async fn index_users_json(
) -> Result<HttpResponse, ServerError> {
info!("Listing Users to Json api");
if let Ok(users) = queries::list_users(&id, &config, form.0).await {
Ok(HttpResponse::Ok().json2(&users.list))
Ok(HttpResponse::Ok().json(&users.list))
} else {
Ok(redirect_builder("/admin/login"))
}
@@ -145,7 +145,7 @@ pub async fn get_logged_user_json(
Ok(HttpResponse::Unauthorized().finish())
}
RoleGuard::Regular { user } | RoleGuard::Admin { user } => {
Ok(HttpResponse::Ok().json2(&user))
Ok(HttpResponse::Ok().json(&user))
}
}
}
@@ -156,7 +156,7 @@ pub async fn download_png(
config: web::Data<crate::ServerConfig>,
link_code: web::Path<String>,
) -> Result<HttpResponse, ServerError> {
match queries::get_link(&id, &link_code.0, &config).await {
match queries::get_link(&id, &link_code, &config).await {
Ok(query) => {
let qr = QrCode::with_error_correction_level(
&format!("http://{}/{}", config.public_url, &query.item.code),
@@ -169,7 +169,9 @@ pub async fn download_png(
.write_to(&mut temporary_data, ImageOutputFormat::Png)
.unwrap();
let image_data = temporary_data.into_inner();
Ok(HttpResponse::Ok().set(ContentType::png()).body(image_data))
Ok(HttpResponse::Ok()
.insert_header(ContentType::png())
.body(image_data))
}
Err(e) => Err(e),
}
@@ -183,7 +185,7 @@ pub async fn process_create_user_json(
) -> Result<HttpResponse, ServerError> {
info!("Listing Users to Json api");
match queries::create_user(&id, data.into_inner(), &config).await {
Ok(item) => Ok(HttpResponse::Ok().json2(&Status::Success(Message {
Ok(item) => Ok(HttpResponse::Ok().json(&Status::Success(Message {
message: format!("Successfully saved user: {}", item.item.username),
}))),
Err(e) => Err(e),
@@ -198,7 +200,7 @@ pub async fn process_update_user_json(
) -> Result<HttpResponse, ServerError> {
info!("Listing Users to Json api");
match queries::update_user(&id, &form, &config).await {
Ok(item) => Ok(HttpResponse::Ok().json2(&Status::Success(Message {
Ok(item) => Ok(HttpResponse::Ok().json(&Status::Success(Message {
message: format!("Successfully saved user: {}", item.item.username),
}))),
Err(e) => Err(e),
@@ -212,7 +214,7 @@ pub async fn toggle_admin(
id: Identity,
) -> Result<HttpResponse, ServerError> {
let update = queries::toggle_admin(&id, user.id, &config).await?;
Ok(HttpResponse::Ok().json2(&Status::Success(Message {
Ok(HttpResponse::Ok().json(&Status::Success(Message {
message: format!(
"Successfully changed privileges or user: {}",
update.item.username
@@ -230,14 +232,14 @@ pub async fn get_language(
let user = authenticate(&id, &config).await?;
match user {
RoleGuard::NotAuthenticated | RoleGuard::Disabled => {
Ok(HttpResponse::Ok().json2(&detect_language(&req)?))
Ok(HttpResponse::Ok().json(&detect_language(&req)?))
}
RoleGuard::Regular { user } | RoleGuard::Admin { user } => {
Ok(HttpResponse::Ok().json2(&user.language))
Ok(HttpResponse::Ok().json(&user.language))
}
}
} else {
Ok(HttpResponse::Ok().json2(&detect_language(&req)?))
Ok(HttpResponse::Ok().json(&detect_language(&req)?))
}
}
@@ -248,7 +250,7 @@ pub async fn set_language(
id: Identity,
) -> Result<HttpResponse, ServerError> {
queries::set_language(&id, data.0, &config).await?;
Ok(HttpResponse::Ok().json2(&data.0))
Ok(HttpResponse::Ok().json(&data.0))
}
#[instrument(skip(id))]
@@ -278,23 +280,23 @@ pub async fn process_login_json(
info!("Log-in of user: {}", &u.username);
let session_token = u.username.clone();
id.remember(session_token);
Ok(HttpResponse::Ok().json2(&u))
Ok(HttpResponse::Ok().json(&u))
} else {
info!("Invalid password for user: {}", &u.username);
Ok(HttpResponse::Unauthorized().json2(&Status::Error(Message {
Ok(HttpResponse::Unauthorized().json(&Status::Error(Message {
message: "Failed to Login".to_string(),
})))
}
} else {
// should fail earlier if secret is missing.
Ok(HttpResponse::Unauthorized().json2(&Status::Error(Message {
Ok(HttpResponse::Unauthorized().json(&Status::Error(Message {
message: "Failed to Login".to_string(),
})))
}
}
Err(e) => {
info!("Failed to login: {}", e);
Ok(HttpResponse::Unauthorized().json2(&Status::Error(Message {
Ok(HttpResponse::Unauthorized().json(&Status::Error(Message {
message: "Failed to Login".to_string(),
})))
}
@@ -311,7 +313,7 @@ pub async fn logout(id: Identity) -> Result<HttpResponse, ServerError> {
#[instrument()]
pub async fn to_admin() -> Result<HttpResponse, ServerError> {
let response = HttpResponse::PermanentRedirect()
.set_header(actix_web::http::header::LOCATION, "/app/")
.insert_header((actix_web::http::header::LOCATION, "/app/"))
.body(r#"The admin interface moved to <a href="/app/">/app/</a>"#);
Ok(response)
@@ -324,7 +326,7 @@ pub async fn redirect(
req: HttpRequest,
) -> Result<HttpResponse, ServerError> {
info!("Redirecting to {:?}", data);
let link = queries::get_link_simple(&data.0, &config).await;
let link = queries::get_link_simple(&data, &config).await;
info!("link: {:?}", link);
match link {
Ok(link) => {
@@ -334,7 +336,7 @@ pub async fn redirect(
Err(ServerError::Database(e)) => {
info!(
"Link was not found: http://{}/{} \n {}",
&config.public_url, &data.0, e
&config.public_url, &data, e
);
Ok(HttpResponse::NotFound().body(
r#"<!DOCTYPE html>
@@ -373,7 +375,7 @@ pub async fn process_create_link_json(
) -> Result<HttpResponse, ServerError> {
let new_link = queries::create_link(&id, data.into_inner(), &config).await;
match new_link {
Ok(item) => Ok(HttpResponse::Ok().json2(&Status::Success(Message {
Ok(item) => Ok(HttpResponse::Ok().json(&Status::Success(Message {
message: format!("Successfully saved link: {}", item.item.code),
}))),
Err(e) => Err(e),
@@ -388,7 +390,7 @@ pub async fn process_update_link_json(
) -> Result<HttpResponse, ServerError> {
let new_link = queries::update_link(&id, data.into_inner(), &config).await;
match new_link {
Ok(item) => Ok(HttpResponse::Ok().json2(&Status::Success(Message {
Ok(item) => Ok(HttpResponse::Ok().json(&Status::Success(Message {
message: format!("Successfully updated link: {}", item.item.code),
}))),
Err(e) => Err(e),
@@ -402,7 +404,7 @@ pub async fn process_delete_link_json(
data: web::Json<LinkDelta>,
) -> Result<HttpResponse, ServerError> {
queries::delete_link(&id, &data.code, &config).await?;
Ok(HttpResponse::Ok().json2(&Status::Success(Message {
Ok(HttpResponse::Ok().json(&Status::Success(Message {
message: format!("Successfully deleted link: {}", &data.code),
})))
}
+1 -1
View File
@@ -368,7 +368,7 @@ impl NewLink {
///
/// # Errors
/// fails with [`ServerError`] if the database cannot be acessed or constraints are not met.
pub(crate) async fn insert(self, server_config: &ServerConfig) -> Result<(), ServerError> {
pub async fn insert(self, server_config: &ServerConfig) -> Result<(), ServerError> {
sqlx::query!(
"Insert into links (
title,
+3 -3
View File
@@ -8,11 +8,11 @@ license = "MIT OR Apache-2.0"
name = "pslink-shared"
readme = "../pslink/README.md"
repository = "https://github.com/enaut/pslink/"
version = "0.4.3"
version = "0.4.4"
[dependencies]
serde = {version="1.0", features = ["derive"]}
chrono = {version = "0.4", features = ["serde"] }
enum-map = {version="1", features = ["serde"]}
strum_macros = "0.21"
strum = "0.21"
strum_macros = "0.22"
strum = "0.22"
+10 -14
View File
@@ -1,8 +1,8 @@
//! The more generic datatypes used in pslink
//! The more generic data-types used in pslink
use std::ops::Deref;
use serde::{Deserialize, Serialize, Serializer};
use strum_macros::{AsRefStr, EnumIter, EnumString, ToString};
use strum_macros::{AsRefStr, EnumIter, EnumString};
use crate::apirequests::users::Role;
/// A generic list return type containing the User and a Vec containing e.g. Links or Users
@@ -48,7 +48,7 @@ pub struct Count {
pub number: i32,
}
/// Everytime a shor url is clicked record it for statistical evaluation.
/// Every time a short url is clicked record it for statistical evaluation.
#[derive(Serialize, Debug)]
pub struct Click {
pub id: i64,
@@ -121,17 +121,7 @@ impl<T> Deref for Loadable<T> {
/// To add an additional language add it to this enum as well as an appropriate file into the locales folder.
#[allow(clippy::upper_case_acronyms)]
#[derive(
Debug,
Copy,
Clone,
EnumIter,
EnumString,
ToString,
AsRefStr,
Eq,
PartialEq,
Serialize,
Deserialize,
Debug, Copy, Clone, EnumIter, EnumString, AsRefStr, Eq, PartialEq, Serialize, Deserialize,
)]
pub enum Lang {
#[strum(serialize = "en-US", serialize = "en", serialize = "enUS")]
@@ -139,3 +129,9 @@ pub enum Lang {
#[strum(serialize = "de-DE", serialize = "de", serialize = "deDE")]
DeDE,
}
impl std::fmt::Display for Lang {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}