Compare commits
4 Commits
4c405b8a3c
...
3bb79666b2
Author | SHA1 | Date | |
---|---|---|---|
3bb79666b2 | |||
c148e839f6 | |||
e98b468b10 | |||
7c2ce180c6 |
2
.github/workflows/release.yml
vendored
2
.github/workflows/release.yml
vendored
@ -27,7 +27,7 @@ jobs:
|
|||||||
~/.cargo/registry/cache/
|
~/.cargo/registry/cache/
|
||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargo
|
||||||
- name: Install musl-tools
|
- name: Install musl-tools
|
||||||
run: sudo apt-get install musl-tools
|
run: sudo apt-get install musl-tools
|
||||||
- name: Build
|
- name: Build
|
||||||
|
2
.github/workflows/rust.yml
vendored
2
.github/workflows/rust.yml
vendored
@ -35,7 +35,7 @@ jobs:
|
|||||||
~/.cargo/registry/cache/
|
~/.cargo/registry/cache/
|
||||||
~/.cargo/git/db/
|
~/.cargo/git/db/
|
||||||
target/
|
target/
|
||||||
key: ${{ runner.os }}-cargo-${{ hashFiles('**/Cargo.lock') }}
|
key: ${{ runner.os }}-cargols
|
||||||
- name: Install musl-tools
|
- name: Install musl-tools
|
||||||
run: sudo apt-get install musl-tools
|
run: sudo apt-get install musl-tools
|
||||||
- name: Build
|
- name: Build
|
||||||
|
1520
Cargo.lock
generated
1520
Cargo.lock
generated
File diff suppressed because it is too large
Load Diff
27
README.md
27
README.md
@ -99,6 +99,10 @@ Templates and migrations are allways embedded in the binary so it should run sta
|
|||||||
|
|
||||||
### Setup
|
### 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:
|
To get Pslink up and running use the commands in the following order:
|
||||||
|
|
||||||
1. `pslink generate-env`
|
1. `pslink generate-env`
|
||||||
@ -164,3 +168,26 @@ ExecStart=/var/pslink/pslink runserver
|
|||||||
[Install]
|
[Install]
|
||||||
WantedBy=multi-user.target
|
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**.
|
||||||
|
@ -8,7 +8,7 @@ keywords = ["url", "link", "webpage", "actix", "web"]
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/enaut/pslink/"
|
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
|
# 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"]
|
crate-type = ["cdylib", "rlib"]
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fluent = "0.15"
|
fluent = "0.16"
|
||||||
seed = "0.8"
|
seed = "0.8"
|
||||||
serde = {version="1.0", features = ["derive"]}
|
serde = {version="1.0", features = ["derive"]}
|
||||||
unic-langid = "0.9"
|
unic-langid = "0.9"
|
||||||
strum_macros = "0.21"
|
strum_macros = "0.22"
|
||||||
strum = "0.21"
|
strum = "0.22"
|
||||||
enum-map = "1"
|
enum-map = "1"
|
||||||
qrcode = "0.12"
|
qrcode = "0.12"
|
||||||
image = "0.23"
|
image = "0.23"
|
||||||
|
@ -16,7 +16,7 @@ use crate::Msg;
|
|||||||
pub fn navigation(i18n: &I18n, base_url: &Url, user: &User) -> Node<Msg> {
|
pub fn navigation(i18n: &I18n, base_url: &Url, user: &User) -> Node<Msg> {
|
||||||
// A shortcut for translating strings.
|
// A shortcut for translating strings.
|
||||||
let t = move |key: &str| i18n.translate(key, None);
|
let t = move |key: &str| i18n.translate(key, None);
|
||||||
// Translate the wellcome message
|
// Translate the welcome message
|
||||||
let welcome = i18n.translate(
|
let welcome = i18n.translate(
|
||||||
"welcome-user",
|
"welcome-user",
|
||||||
Some(&fluent_args![ "username" => user.username.clone()]),
|
Some(&fluent_args![ "username" => user.username.clone()]),
|
||||||
|
@ -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)]
|
#[derive(Debug, Clone)]
|
||||||
enum Dialog {
|
enum Dialog {
|
||||||
EditLink {
|
EditLink {
|
||||||
@ -150,7 +150,7 @@ pub enum Msg {
|
|||||||
Query(QueryMsg), // Messages related to querying links
|
Query(QueryMsg), // Messages related to querying links
|
||||||
Edit(EditMsg), // Messages related to editing links
|
Edit(EditMsg), // Messages related to editing links
|
||||||
ClearAll, // Clear all messages
|
ClearAll, // Clear all messages
|
||||||
SetupObserver, // Make an observer for endles scroll
|
SetupObserver, // Make an observer for endless scroll
|
||||||
Observed(Vec<IntersectionObserverEntry>),
|
Observed(Vec<IntersectionObserverEntry>),
|
||||||
SetMessage(String), // Set a message to the user
|
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! ");
|
log!("element not yet registered! ");
|
||||||
};
|
};
|
||||||
} else {
|
} 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 {
|
match msg {
|
||||||
QueryMsg::Fetch => {
|
QueryMsg::Fetch => {
|
||||||
orders.skip(); // No need to rerender
|
orders.skip(); // No need to rerender
|
||||||
initial_load(model, orders)
|
initial_load(model, orders);
|
||||||
}
|
}
|
||||||
QueryMsg::FetchAdditional => {
|
QueryMsg::FetchAdditional => {
|
||||||
orders.skip(); // No need to rerender
|
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) => {
|
QueryMsg::OrderBy(column) => {
|
||||||
model.formconfig.order = model.formconfig.order.as_ref().map_or_else(
|
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) => {
|
QueryMsg::ReceivedAdditional(response) => {
|
||||||
if response.len() < model.formconfig.amount {
|
if response.len() < model.formconfig.amount {
|
||||||
log!("There are no more links! ");
|
log!("There are no more links! ");
|
||||||
model.everything_loaded = true
|
model.everything_loaded = true;
|
||||||
};
|
};
|
||||||
let mut new_links = response
|
let mut new_links = response
|
||||||
.into_iter()
|
.into_iter()
|
||||||
@ -376,7 +376,7 @@ fn load_links(orders: &mut impl Orders<Msg>, data: LinkRequestForm) {
|
|||||||
.json(&data),
|
.json(&data),
|
||||||
Msg::SetMessage("Failed to parse data".to_string())
|
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!(
|
let response = unwrap_or_return!(
|
||||||
fetch(request).await,
|
fetch(request).await,
|
||||||
Msg::SetMessage("Failed to send data".to_string())
|
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),
|
.json(&link_delta),
|
||||||
Msg::SetMessage("serialization failed".to_string())
|
Msg::SetMessage("serialization failed".to_string())
|
||||||
);
|
);
|
||||||
// perform the request and recieve a respnse
|
// perform the request and receive a response
|
||||||
let response =
|
let response =
|
||||||
unwrap_or_return!(fetch(request).await, Msg::Edit(EditMsg::FailedToDeleteLink));
|
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"],
|
C!["table_qr"],
|
||||||
a![
|
a![
|
||||||
ev(Ev::Click, |event| event.stop_propagation()),
|
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)
|
raw!(&l.cache)
|
||||||
]
|
]
|
||||||
]
|
]
|
||||||
@ -820,14 +821,7 @@ fn edit_or_create_link<F: Fn(&str) -> String>(
|
|||||||
],
|
],
|
||||||
tr![
|
tr![
|
||||||
th![t("qr-code")],
|
th![t("qr-code")],
|
||||||
if let Loadable::Data(Some(qr)) = qr {
|
qr.as_ref().map_or_else(|| td!["Loading..."], render_qr),
|
||||||
td![a![
|
|
||||||
span![C!["qrdownload"], "Download", raw!(&qr.svg),],
|
|
||||||
attrs!(At::Href => qr.url, At::Download => "qr-code.png")
|
|
||||||
]]
|
|
||||||
} else {
|
|
||||||
td!["Loading..."]
|
|
||||||
}
|
|
||||||
]
|
]
|
||||||
],
|
],
|
||||||
a![
|
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
|
/// generate a qr-code for a code
|
||||||
fn generate_qr_from_code(code: &str) -> String {
|
fn generate_qr_from_code(code: &str) -> String {
|
||||||
generate_qr_from_link(&format!("https://{}/{}", get_host(), code))
|
generate_qr_from_link(&format!("https://{}/{}", get_host(), code))
|
||||||
|
@ -62,7 +62,7 @@ struct FilterInput {
|
|||||||
filter_input: ElRef<web_sys::HtmlInputElement>,
|
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)]
|
#[derive(Clone)]
|
||||||
pub enum Msg {
|
pub enum Msg {
|
||||||
Query(UserQueryMsg),
|
Query(UserQueryMsg),
|
||||||
@ -161,7 +161,7 @@ pub fn process_query_messages(msg: UserQueryMsg, model: &mut Model, orders: &mut
|
|||||||
}
|
}
|
||||||
UserQueryMsg::EmailFilterChanged(s) => {
|
UserQueryMsg::EmailFilterChanged(s) => {
|
||||||
log!("Filter is: ", &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();
|
let sanit = s.chars().filter(|x| x.is_alphanumeric()).collect();
|
||||||
model.formconfig.filter[UserOverviewColumns::Email].sieve = sanit;
|
model.formconfig.filter[UserOverviewColumns::Email].sieve = sanit;
|
||||||
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
|
orders.send_msg(Msg::Query(UserQueryMsg::Fetch));
|
||||||
@ -238,7 +238,7 @@ pub fn process_user_edit_messages(
|
|||||||
let data = model
|
let data = model
|
||||||
.user_edit
|
.user_edit
|
||||||
.take()
|
.take()
|
||||||
.expect("A user should allways be there on save");
|
.expect("A user should always be there on save");
|
||||||
log!("Saving User: ", &data.username);
|
log!("Saving User: ", &data.username);
|
||||||
save_user(data, orders);
|
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> {
|
fn view_user_table_filter_input<F: Fn(&str) -> String>(model: &Model, t: F) -> Node<Msg> {
|
||||||
tr![
|
tr![
|
||||||
C!["filters"],
|
C!["filters"],
|
||||||
|
@ -8,13 +8,13 @@ keywords = ["url", "link", "webpage", "actix", "web"]
|
|||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/enaut/pslink/"
|
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
|
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||||
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
fluent = "0.15"
|
fluent = "0.16"
|
||||||
serde = {version="1.0", features = ["derive"]}
|
serde = {version="1.0", features = ["derive"]}
|
||||||
unic-langid = "0.9"
|
unic-langid = "0.9"
|
||||||
|
|
||||||
|
@ -9,33 +9,36 @@ license = "MIT OR Apache-2.0"
|
|||||||
name = "pslink"
|
name = "pslink"
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
repository = "https://github.com/enaut/pslink/"
|
repository = "https://github.com/enaut/pslink/"
|
||||||
version = "0.4.3"
|
version = "0.4.4"
|
||||||
|
|
||||||
[build-dependencies]
|
[build-dependencies]
|
||||||
actix-web-static-files = "3.0"
|
actix-web-static-files = { path = "../../actix-web-static-files" }
|
||||||
|
static-files = { version = "0.2", default-features = false }
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
actix-identity = "0.3"
|
actix-identity = "0.4.0-beta.2"
|
||||||
actix-rt = "1.1"
|
actix-rt = "2.2"
|
||||||
actix-web = "3"
|
actix-web = "4.0.0-beta.9"
|
||||||
actix-web-static-files = "3"
|
actix-web-static-files = { path = "../../actix-web-static-files" }
|
||||||
actix-files = "0.5"
|
actix-files = "0.6.0-beta.7"
|
||||||
argonautica = "0.2"
|
argonautica = "0.2"
|
||||||
clap = "2.33"
|
clap = "2.33"
|
||||||
dotenv = "0.15.0"
|
dotenv = "0.15.0"
|
||||||
fluent-langneg = "0.13"
|
fluent-langneg = "0.13"
|
||||||
image = "0.23"
|
image = "0.23"
|
||||||
opentelemetry = "0.14"
|
opentelemetry = "0.16"
|
||||||
opentelemetry-jaeger = "0.12"
|
opentelemetry-jaeger = "0.15"
|
||||||
qrcode = "0.12"
|
qrcode = "0.12"
|
||||||
rand = "0.8"
|
rand = "0.8"
|
||||||
rpassword = "5.0"
|
rpassword = "5.0"
|
||||||
serde = {version="1.0", features = ["derive"]}
|
serde = {version="1.0", features = ["derive"]}
|
||||||
|
static-files = { version = "0.2", default-features = false }
|
||||||
thiserror = "1.0"
|
thiserror = "1.0"
|
||||||
tracing-actix-web = "0.2.1"
|
tracing-actix-web = "0.4.0-beta.13"
|
||||||
tracing-opentelemetry = "0.12"
|
tracing-opentelemetry = "0.15"
|
||||||
async-trait = "0.1"
|
async-trait = "0.1"
|
||||||
enum-map = {version="1", features = ["serde"]}
|
enum-map = {version="1", features = ["serde"]}
|
||||||
|
indexmap = "~1.6.2"
|
||||||
|
|
||||||
pslink-shared = {version="0.4", path = "../shared" }
|
pslink-shared = {version="0.4", path = "../shared" }
|
||||||
|
|
||||||
@ -48,7 +51,7 @@ version = "0.6"
|
|||||||
|
|
||||||
[dependencies.sqlx]
|
[dependencies.sqlx]
|
||||||
features = ["sqlite", "macros", "runtime-actix-rustls", "chrono", "migrate", "offline"]
|
features = ["sqlite", "macros", "runtime-actix-rustls", "chrono", "migrate", "offline"]
|
||||||
version = "0.4"
|
version = "0.5"
|
||||||
|
|
||||||
[dependencies.tracing]
|
[dependencies.tracing]
|
||||||
features = ["log"]
|
features = ["log"]
|
||||||
@ -62,11 +65,11 @@ version = "0.2.17"
|
|||||||
actix-server = "1.0.4"
|
actix-server = "1.0.4"
|
||||||
tempdir = "0.3"
|
tempdir = "0.3"
|
||||||
test_bin = "0.3"
|
test_bin = "0.3"
|
||||||
tokio = "0.2.25"
|
tokio = "1.12"
|
||||||
assert_cmd = "1.0.7"
|
assert_cmd = "2.0"
|
||||||
predicates = "2.0.0"
|
predicates = "2.0.0"
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies.reqwest]
|
[dev-dependencies.reqwest]
|
||||||
features = ["cookies", "json"]
|
features = ["cookies", "json"]
|
||||||
version = "0.10.10"
|
version = "0.11"
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
use actix_web_static_files::resource_dir;
|
use static_files::resource_dir;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
resource_dir("./static/").build().unwrap();
|
resource_dir("./static/").build().unwrap();
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
use clap::{
|
use clap::{
|
||||||
app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, Arg,
|
app_from_crate, crate_authors, crate_description, crate_name, crate_version, App, AppSettings,
|
||||||
ArgMatches, SubCommand,
|
Arg, ArgMatches, SubCommand,
|
||||||
};
|
};
|
||||||
use dotenv::dotenv;
|
use dotenv::dotenv;
|
||||||
use pslink_shared::datatypes::{Secret, User};
|
use pslink_shared::datatypes::{Secret, User};
|
||||||
@ -12,7 +12,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use pslink::{
|
use pslink::{
|
||||||
models::{NewUser, UserDbOperations},
|
models::{NewLink, NewUser, UserDbOperations},
|
||||||
ServerConfig, ServerError,
|
ServerConfig, ServerError,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -20,7 +20,7 @@ use tracing::{error, info, trace, warn};
|
|||||||
|
|
||||||
static MIGRATOR: Migrator = sqlx::migrate!();
|
static MIGRATOR: Migrator = sqlx::migrate!();
|
||||||
|
|
||||||
/// generate the commandline options available
|
/// generate the command line options available
|
||||||
#[allow(clippy::too_many_lines)]
|
#[allow(clippy::too_many_lines)]
|
||||||
fn generate_cli() -> App<'static, 'static> {
|
fn generate_cli() -> App<'static, 'static> {
|
||||||
app_from_crate!()
|
app_from_crate!()
|
||||||
@ -47,7 +47,7 @@ fn generate_cli() -> App<'static, 'static> {
|
|||||||
.short("u")
|
.short("u")
|
||||||
.help("The host url or the page that will be part of the short urls.")
|
.help("The host url or the page that will be part of the short urls.")
|
||||||
.env("PSLINK_PUBLIC_URL")
|
.env("PSLINK_PUBLIC_URL")
|
||||||
.default_value("localhost:8080")
|
.default_value("127.0.0.1:8080")
|
||||||
.global(true),
|
.global(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@ -63,7 +63,7 @@ fn generate_cli() -> App<'static, 'static> {
|
|||||||
Arg::with_name("brand_name")
|
Arg::with_name("brand_name")
|
||||||
.long("brand-name")
|
.long("brand-name")
|
||||||
.short("b")
|
.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")
|
.env("PSLINK_BRAND_NAME")
|
||||||
.default_value("Pslink")
|
.default_value("Pslink")
|
||||||
.global(true),
|
.global(true),
|
||||||
@ -74,7 +74,7 @@ fn generate_cli() -> App<'static, 'static> {
|
|||||||
.short("i")
|
.short("i")
|
||||||
.help("The host (ip) that will run the pslink service")
|
.help("The host (ip) that will run the pslink service")
|
||||||
.env("PSLINK_IP")
|
.env("PSLINK_IP")
|
||||||
.default_value("localhost")
|
.default_value("127.0.0.1")
|
||||||
.global(true),
|
.global(true),
|
||||||
)
|
)
|
||||||
.arg(
|
.arg(
|
||||||
@ -95,11 +95,11 @@ fn generate_cli() -> App<'static, 'static> {
|
|||||||
.long("secret")
|
.long("secret")
|
||||||
.help(concat!(
|
.help(concat!(
|
||||||
"The secret that is used to encrypt the",
|
"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 commandlineparameters are visible",
|
" As command line parameters are visible",
|
||||||
" to all users",
|
" to all users",
|
||||||
" it is not wise to use this as",
|
" it is not wise to use this as",
|
||||||
" a commandline parameter but rather as an environment variable.",
|
" a command line parameter but rather as an environment variable.",
|
||||||
))
|
))
|
||||||
.env("PSLINK_SECRET")
|
.env("PSLINK_SECRET")
|
||||||
.default_value("")
|
.default_value("")
|
||||||
@ -125,6 +125,12 @@ fn generate_cli() -> App<'static, 'static> {
|
|||||||
.about("Create an admin user.")
|
.about("Create an admin user.")
|
||||||
.display_order(2),
|
.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
|
/// parse the options to the [`ServerConfig`] struct
|
||||||
@ -142,7 +148,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
|||||||
warn!("If you change the secret all passwords will be invalid");
|
warn!("If you change the secret all passwords will be invalid");
|
||||||
warn!("Using an auto generated one for this run.");
|
warn!("Using an auto generated one for this run.");
|
||||||
} else {
|
} else {
|
||||||
warn!("The provided secret was too short. Using an autogenerated one.");
|
warn!("The provided secret was too short. Using an auto generated one.");
|
||||||
}
|
}
|
||||||
|
|
||||||
thread_rng()
|
thread_rng()
|
||||||
@ -158,7 +164,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
|||||||
.value_of("database")
|
.value_of("database")
|
||||||
.expect(concat!(
|
.expect(concat!(
|
||||||
"Neither the DATABASE_URL environment variable",
|
"Neither the DATABASE_URL environment variable",
|
||||||
" nor the commandline parameters",
|
" nor the command line parameters",
|
||||||
" contain a valid database location."
|
" contain a valid database location."
|
||||||
))
|
))
|
||||||
.parse::<PathBuf>()
|
.parse::<PathBuf>()
|
||||||
@ -186,7 +192,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
|||||||
.value_of("port")
|
.value_of("port")
|
||||||
.expect("Failed to read the port value")
|
.expect("Failed to read the port value")
|
||||||
.parse::<u32>()
|
.parse::<u32>()
|
||||||
.expect("Failed to parse the portnumber");
|
.expect("Failed to parse the port number");
|
||||||
let protocol = config
|
let protocol = config
|
||||||
.value_of("protocol")
|
.value_of("protocol")
|
||||||
.expect("Failed to read the protocol value")
|
.expect("Failed to read the protocol value")
|
||||||
@ -209,7 +215,7 @@ async fn parse_args_to_config(config: ArgMatches<'_>) -> ServerConfig {
|
|||||||
/// Setup and launch the command
|
/// Setup and launch the command
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # 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> {
|
pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
||||||
// load the environment .env file if available.
|
// load the environment .env file if available.
|
||||||
dotenv().ok();
|
dotenv().ok();
|
||||||
@ -225,7 +231,7 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
|||||||
.value_of("database")
|
.value_of("database")
|
||||||
.expect(concat!(
|
.expect(concat!(
|
||||||
"Neither the DATABASE_URL environment variable",
|
"Neither the DATABASE_URL environment variable",
|
||||||
" nor the commandline parameters",
|
" nor the command line parameters",
|
||||||
" contain a valid database location."
|
" contain a valid database location."
|
||||||
))
|
))
|
||||||
.parse::<PathBuf>()
|
.parse::<PathBuf>()
|
||||||
@ -234,13 +240,14 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
|||||||
if !db.exists() {
|
if !db.exists() {
|
||||||
trace!("No database file found {}", db.display());
|
trace!("No database file found {}", db.display());
|
||||||
if !(config.subcommand_matches("migrate-database").is_none()
|
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!(
|
let msg = format!(
|
||||||
concat!(
|
concat!(
|
||||||
"Database not found at {}!",
|
"Database not found at {}!",
|
||||||
" Create a new database with: `pslink migrate-database`",
|
"Create a new database with: `pslink migrate-database`",
|
||||||
"or adjust the databasepath."
|
"or adjust the database path."
|
||||||
),
|
),
|
||||||
db.display()
|
db.display()
|
||||||
);
|
);
|
||||||
@ -268,12 +275,40 @@ pub async fn setup() -> Result<Option<crate::ServerConfig>, ServerError> {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
if let Some(_create_config) = config.subcommand_matches("create-admin") {
|
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),
|
Ok(_) => Ok(None),
|
||||||
Err(e) => Err(e),
|
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") {
|
if let Some(_runserver_config) = config.subcommand_matches("runserver") {
|
||||||
let num_users = User::count_admins(&server_config).await?;
|
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.
|
/// 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.");
|
info!("Creating an admin user.");
|
||||||
let sin = io::stdin();
|
let sin = io::stdin();
|
||||||
|
|
||||||
@ -306,7 +387,7 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
|
|||||||
io::stdout().flush().unwrap();
|
io::stdout().flush().unwrap();
|
||||||
let new_username = sin.lock().lines().next().unwrap().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();
|
io::stdout().flush().unwrap();
|
||||||
let new_email = sin.lock().lines().next().unwrap().unwrap();
|
let new_email = sin.lock().lines().next().unwrap().unwrap();
|
||||||
|
|
||||||
@ -325,16 +406,20 @@ async fn create_admin(config: &ServerConfig) -> Result<(), ServerError> {
|
|||||||
&config.secret,
|
&config.secret,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
new_admin.insert_user(config).await?;
|
create_admin(&new_admin, config).await
|
||||||
let created_user = User::get_user_by_name(&new_username, 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?;
|
created_user.toggle_admin(config).await?;
|
||||||
|
|
||||||
info!("Admin user created: {}", new_username);
|
info!("Admin user created: {}", &new_user.username);
|
||||||
|
|
||||||
Ok(())
|
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> {
|
async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> {
|
||||||
info!(
|
info!(
|
||||||
"Creating a database file and running the migrations in the file {}:",
|
"Creating a database file and running the migrations in the file {}:",
|
||||||
@ -344,7 +429,7 @@ async fn apply_migrations(config: &ServerConfig) -> Result<(), ServerError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The commandline parameters provided or if missing the default parameters can be converted and written to a .env file. That way the configuration is saved and automatically reused for subsequent launches.
|
/// The command line parameters provided or if missing the default parameters can be converted and written to a .env file. That way the configuration is saved and automatically reused for subsequent launches.
|
||||||
fn generate_env_file(server_config: &ServerConfig) -> Result<(), ServerError> {
|
fn generate_env_file(server_config: &ServerConfig) -> Result<(), ServerError> {
|
||||||
if std::path::Path::new(".env").exists() {
|
if std::path::Path::new(".env").exists() {
|
||||||
return Err(ServerError::User(
|
return Err(ServerError::User(
|
||||||
|
@ -5,6 +5,8 @@ mod views;
|
|||||||
|
|
||||||
use actix_files::Files;
|
use actix_files::Files;
|
||||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||||
|
use actix_web::middleware::Compat;
|
||||||
|
use actix_web::web::Data;
|
||||||
use actix_web::{web, App, HttpServer};
|
use actix_web::{web, App, HttpServer};
|
||||||
use pslink::ServerConfig;
|
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);
|
let otel_layer = OpenTelemetryLayer::new(tracer);
|
||||||
|
|
||||||
// Use the tracing subscriber `Registry`, or any other subscriber
|
// Use the tracing subscriber `Registry`, or any other subscriber
|
||||||
// that impls `LookupSpan`
|
// that implements `LookupSpan`
|
||||||
Registry::default()
|
Registry::default()
|
||||||
.with(otel_layer)
|
.with(otel_layer)
|
||||||
.with(env_filter)
|
.with(env_filter)
|
||||||
@ -79,7 +81,7 @@ async fn main() -> std::result::Result<(), std::io::Error> {
|
|||||||
// include the static files into the binary
|
// include the static files into the binary
|
||||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||||
|
|
||||||
/// Launch the pslink-webservice
|
/// Launch the pslink-web-service
|
||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// This produces a [`ServerError`] if:
|
/// This produces a [`ServerError`] if:
|
||||||
@ -105,9 +107,10 @@ pub async fn webservice(
|
|||||||
|
|
||||||
let server = HttpServer::new(move || {
|
let server = HttpServer::new(move || {
|
||||||
let generated = generate();
|
let generated = generate();
|
||||||
|
let logger = Compat::new(TracingLogger::default());
|
||||||
App::new()
|
App::new()
|
||||||
.data(server_config.clone())
|
.app_data(Data::new(server_config.clone()))
|
||||||
.wrap(TracingLogger)
|
.wrap(logger)
|
||||||
.wrap(IdentityService::new(
|
.wrap(IdentityService::new(
|
||||||
CookieIdentityPolicy::new(&[0; 32])
|
CookieIdentityPolicy::new(&[0; 32])
|
||||||
.name("auth-cookie")
|
.name("auth-cookie")
|
||||||
|
@ -30,13 +30,13 @@ use pslink::ServerError;
|
|||||||
#[instrument]
|
#[instrument]
|
||||||
fn redirect_builder(target: &str) -> HttpResponse {
|
fn redirect_builder(target: &str) -> HttpResponse {
|
||||||
HttpResponse::SeeOther()
|
HttpResponse::SeeOther()
|
||||||
.set(CacheControl(vec![
|
.insert_header(CacheControl(vec![
|
||||||
CacheDirective::NoCache,
|
CacheDirective::NoCache,
|
||||||
CacheDirective::NoStore,
|
CacheDirective::NoStore,
|
||||||
CacheDirective::MustRevalidate,
|
CacheDirective::MustRevalidate,
|
||||||
]))
|
]))
|
||||||
.set(Expires(SystemTime::now().into()))
|
.insert_header(Expires(SystemTime::now().into()))
|
||||||
.set_header(actix_web::http::header::LOCATION, target)
|
.insert_header((actix_web::http::header::LOCATION, target))
|
||||||
.body(format!("Redirect to {}", target))
|
.body(format!("Redirect to {}", target))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -67,9 +67,9 @@ fn detect_language(request: &HttpRequest) -> Result<Lang, ServerError> {
|
|||||||
);
|
);
|
||||||
info!("supported languages: {:?}", supported);
|
info!("supported languages: {:?}", supported);
|
||||||
|
|
||||||
if let Some(languagecode) = supported.get(0) {
|
if let Some(language_code) = supported.get(0) {
|
||||||
info!("Supported Language: {}", languagecode);
|
info!("Supported Language: {}", language_code);
|
||||||
Ok(languagecode
|
Ok(language_code
|
||||||
.to_string()
|
.to_string()
|
||||||
.parse()
|
.parse()
|
||||||
.expect("Failed to parse 2 language"))
|
.expect("Failed to parse 2 language"))
|
||||||
@ -112,7 +112,7 @@ pub async fn index_json(
|
|||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
info!("Listing Links to Json api");
|
info!("Listing Links to Json api");
|
||||||
match queries::list_all_allowed(&id, &config, form.0).await {
|
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) => {
|
Err(e) => {
|
||||||
error!("Failed to access database: {:?}", e);
|
error!("Failed to access database: {:?}", e);
|
||||||
warn!("Not logged in - redirecting to login page");
|
warn!("Not logged in - redirecting to login page");
|
||||||
@ -129,7 +129,7 @@ pub async fn index_users_json(
|
|||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
info!("Listing Users to Json api");
|
info!("Listing Users to Json api");
|
||||||
if let Ok(users) = queries::list_users(&id, &config, form.0).await {
|
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 {
|
} else {
|
||||||
Ok(redirect_builder("/admin/login"))
|
Ok(redirect_builder("/admin/login"))
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ pub async fn get_logged_user_json(
|
|||||||
Ok(HttpResponse::Unauthorized().finish())
|
Ok(HttpResponse::Unauthorized().finish())
|
||||||
}
|
}
|
||||||
RoleGuard::Regular { user } | RoleGuard::Admin { user } => {
|
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>,
|
config: web::Data<crate::ServerConfig>,
|
||||||
link_code: web::Path<String>,
|
link_code: web::Path<String>,
|
||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
match queries::get_link(&id, &link_code.0, &config).await {
|
match queries::get_link(&id, &link_code, &config).await {
|
||||||
Ok(query) => {
|
Ok(query) => {
|
||||||
let qr = QrCode::with_error_correction_level(
|
let qr = QrCode::with_error_correction_level(
|
||||||
&format!("http://{}/{}", config.public_url, &query.item.code),
|
&format!("http://{}/{}", config.public_url, &query.item.code),
|
||||||
@ -169,7 +169,9 @@ pub async fn download_png(
|
|||||||
.write_to(&mut temporary_data, ImageOutputFormat::Png)
|
.write_to(&mut temporary_data, ImageOutputFormat::Png)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let image_data = temporary_data.into_inner();
|
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),
|
Err(e) => Err(e),
|
||||||
}
|
}
|
||||||
@ -183,7 +185,7 @@ pub async fn process_create_user_json(
|
|||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
info!("Listing Users to Json api");
|
info!("Listing Users to Json api");
|
||||||
match queries::create_user(&id, data.into_inner(), &config).await {
|
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),
|
message: format!("Successfully saved user: {}", item.item.username),
|
||||||
}))),
|
}))),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
@ -198,7 +200,7 @@ pub async fn process_update_user_json(
|
|||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
info!("Listing Users to Json api");
|
info!("Listing Users to Json api");
|
||||||
match queries::update_user(&id, &form, &config).await {
|
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),
|
message: format!("Successfully saved user: {}", item.item.username),
|
||||||
}))),
|
}))),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
@ -212,7 +214,7 @@ pub async fn toggle_admin(
|
|||||||
id: Identity,
|
id: Identity,
|
||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
let update = queries::toggle_admin(&id, user.id, &config).await?;
|
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!(
|
message: format!(
|
||||||
"Successfully changed privileges or user: {}",
|
"Successfully changed privileges or user: {}",
|
||||||
update.item.username
|
update.item.username
|
||||||
@ -230,14 +232,14 @@ pub async fn get_language(
|
|||||||
let user = authenticate(&id, &config).await?;
|
let user = authenticate(&id, &config).await?;
|
||||||
match user {
|
match user {
|
||||||
RoleGuard::NotAuthenticated | RoleGuard::Disabled => {
|
RoleGuard::NotAuthenticated | RoleGuard::Disabled => {
|
||||||
Ok(HttpResponse::Ok().json2(&detect_language(&req)?))
|
Ok(HttpResponse::Ok().json(&detect_language(&req)?))
|
||||||
}
|
}
|
||||||
RoleGuard::Regular { user } | RoleGuard::Admin { user } => {
|
RoleGuard::Regular { user } | RoleGuard::Admin { user } => {
|
||||||
Ok(HttpResponse::Ok().json2(&user.language))
|
Ok(HttpResponse::Ok().json(&user.language))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} 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,
|
id: Identity,
|
||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
queries::set_language(&id, data.0, &config).await?;
|
queries::set_language(&id, data.0, &config).await?;
|
||||||
Ok(HttpResponse::Ok().json2(&data.0))
|
Ok(HttpResponse::Ok().json(&data.0))
|
||||||
}
|
}
|
||||||
|
|
||||||
#[instrument(skip(id))]
|
#[instrument(skip(id))]
|
||||||
@ -278,23 +280,23 @@ pub async fn process_login_json(
|
|||||||
info!("Log-in of user: {}", &u.username);
|
info!("Log-in of user: {}", &u.username);
|
||||||
let session_token = u.username.clone();
|
let session_token = u.username.clone();
|
||||||
id.remember(session_token);
|
id.remember(session_token);
|
||||||
Ok(HttpResponse::Ok().json2(&u))
|
Ok(HttpResponse::Ok().json(&u))
|
||||||
} else {
|
} else {
|
||||||
info!("Invalid password for user: {}", &u.username);
|
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(),
|
message: "Failed to Login".to_string(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// should fail earlier if secret is missing.
|
// 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(),
|
message: "Failed to Login".to_string(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
info!("Failed to login: {}", 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(),
|
message: "Failed to Login".to_string(),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
@ -311,7 +313,7 @@ pub async fn logout(id: Identity) -> Result<HttpResponse, ServerError> {
|
|||||||
#[instrument()]
|
#[instrument()]
|
||||||
pub async fn to_admin() -> Result<HttpResponse, ServerError> {
|
pub async fn to_admin() -> Result<HttpResponse, ServerError> {
|
||||||
let response = HttpResponse::PermanentRedirect()
|
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>"#);
|
.body(r#"The admin interface moved to <a href="/app/">/app/</a>"#);
|
||||||
|
|
||||||
Ok(response)
|
Ok(response)
|
||||||
@ -324,7 +326,7 @@ pub async fn redirect(
|
|||||||
req: HttpRequest,
|
req: HttpRequest,
|
||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
info!("Redirecting to {:?}", data);
|
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);
|
info!("link: {:?}", link);
|
||||||
match link {
|
match link {
|
||||||
Ok(link) => {
|
Ok(link) => {
|
||||||
@ -334,7 +336,7 @@ pub async fn redirect(
|
|||||||
Err(ServerError::Database(e)) => {
|
Err(ServerError::Database(e)) => {
|
||||||
info!(
|
info!(
|
||||||
"Link was not found: http://{}/{} \n {}",
|
"Link was not found: http://{}/{} \n {}",
|
||||||
&config.public_url, &data.0, e
|
&config.public_url, &data, e
|
||||||
);
|
);
|
||||||
Ok(HttpResponse::NotFound().body(
|
Ok(HttpResponse::NotFound().body(
|
||||||
r#"<!DOCTYPE html>
|
r#"<!DOCTYPE html>
|
||||||
@ -373,7 +375,7 @@ pub async fn process_create_link_json(
|
|||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
let new_link = queries::create_link(&id, data.into_inner(), &config).await;
|
let new_link = queries::create_link(&id, data.into_inner(), &config).await;
|
||||||
match new_link {
|
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),
|
message: format!("Successfully saved link: {}", item.item.code),
|
||||||
}))),
|
}))),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
@ -388,7 +390,7 @@ pub async fn process_update_link_json(
|
|||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
let new_link = queries::update_link(&id, data.into_inner(), &config).await;
|
let new_link = queries::update_link(&id, data.into_inner(), &config).await;
|
||||||
match new_link {
|
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),
|
message: format!("Successfully updated link: {}", item.item.code),
|
||||||
}))),
|
}))),
|
||||||
Err(e) => Err(e),
|
Err(e) => Err(e),
|
||||||
@ -402,7 +404,7 @@ pub async fn process_delete_link_json(
|
|||||||
data: web::Json<LinkDelta>,
|
data: web::Json<LinkDelta>,
|
||||||
) -> Result<HttpResponse, ServerError> {
|
) -> Result<HttpResponse, ServerError> {
|
||||||
queries::delete_link(&id, &data.code, &config).await?;
|
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),
|
message: format!("Successfully deleted link: {}", &data.code),
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
@ -368,7 +368,7 @@ impl NewLink {
|
|||||||
///
|
///
|
||||||
/// # Errors
|
/// # Errors
|
||||||
/// fails with [`ServerError`] if the database cannot be acessed or constraints are not met.
|
/// 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!(
|
sqlx::query!(
|
||||||
"Insert into links (
|
"Insert into links (
|
||||||
title,
|
title,
|
||||||
|
@ -8,11 +8,11 @@ license = "MIT OR Apache-2.0"
|
|||||||
name = "pslink-shared"
|
name = "pslink-shared"
|
||||||
readme = "../pslink/README.md"
|
readme = "../pslink/README.md"
|
||||||
repository = "https://github.com/enaut/pslink/"
|
repository = "https://github.com/enaut/pslink/"
|
||||||
version = "0.4.3"
|
version = "0.4.4"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
serde = {version="1.0", features = ["derive"]}
|
serde = {version="1.0", features = ["derive"]}
|
||||||
chrono = {version = "0.4", features = ["serde"] }
|
chrono = {version = "0.4", features = ["serde"] }
|
||||||
enum-map = {version="1", features = ["serde"]}
|
enum-map = {version="1", features = ["serde"]}
|
||||||
strum_macros = "0.21"
|
strum_macros = "0.22"
|
||||||
strum = "0.21"
|
strum = "0.22"
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
//! The more generic datatypes used in pslink
|
//! The more generic data-types used in pslink
|
||||||
use std::ops::Deref;
|
use std::ops::Deref;
|
||||||
|
|
||||||
use serde::{Deserialize, Serialize, Serializer};
|
use serde::{Deserialize, Serialize, Serializer};
|
||||||
use strum_macros::{AsRefStr, EnumIter, EnumString, ToString};
|
use strum_macros::{AsRefStr, EnumIter, EnumString};
|
||||||
|
|
||||||
use crate::apirequests::users::Role;
|
use crate::apirequests::users::Role;
|
||||||
/// A generic list returntype containing the User and a Vec containing e.g. Links or Users
|
/// A generic list return type containing the User and a Vec containing e.g. Links or Users
|
||||||
#[derive(Clone, Deserialize, Serialize)]
|
#[derive(Clone, Deserialize, Serialize)]
|
||||||
pub struct ListWithOwner<T> {
|
pub struct ListWithOwner<T> {
|
||||||
pub user: User,
|
pub user: User,
|
||||||
@ -48,7 +48,7 @@ pub struct Count {
|
|||||||
pub number: i32,
|
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)]
|
#[derive(Serialize, Debug)]
|
||||||
pub struct Click {
|
pub struct Click {
|
||||||
pub id: i64,
|
pub id: i64,
|
||||||
@ -118,20 +118,10 @@ impl<T> Deref for Loadable<T> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// An `enum` containing the available languages.
|
/// An `enum` containing the available languages.
|
||||||
/// To add an additional language add it to this enum aswell as an appropriate file into the locales folder.
|
/// 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)]
|
#[allow(clippy::upper_case_acronyms)]
|
||||||
#[derive(
|
#[derive(
|
||||||
Debug,
|
Debug, Copy, Clone, EnumIter, EnumString, AsRefStr, Eq, PartialEq, Serialize, Deserialize,
|
||||||
Copy,
|
|
||||||
Clone,
|
|
||||||
EnumIter,
|
|
||||||
EnumString,
|
|
||||||
ToString,
|
|
||||||
AsRefStr,
|
|
||||||
Eq,
|
|
||||||
PartialEq,
|
|
||||||
Serialize,
|
|
||||||
Deserialize,
|
|
||||||
)]
|
)]
|
||||||
pub enum Lang {
|
pub enum Lang {
|
||||||
#[strum(serialize = "en-US", serialize = "en", serialize = "enUS")]
|
#[strum(serialize = "en-US", serialize = "en", serialize = "enUS")]
|
||||||
@ -139,3 +129,9 @@ pub enum Lang {
|
|||||||
#[strum(serialize = "de-DE", serialize = "de", serialize = "deDE")]
|
#[strum(serialize = "de-DE", serialize = "de", serialize = "deDE")]
|
||||||
DeDE,
|
DeDE,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Lang {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "{:?}", self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user