Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fd255d9f74
|
||
|
|
e2738678c8
|
||
|
|
3bb79666b2 | ||
|
|
c148e839f6 | ||
|
|
e98b468b10 | ||
|
|
7c2ce180c6 |
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Generated
+733
-788
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,17 @@
|
|||||||
# Pslink a "Private Short Link page"
|
# 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.
|
So in general this is more a shared short url bookmark webpage than a short url service.
|
||||||
|
|
||||||

|
## 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.
|
||||||
|
|
||||||
|
[](https://pslink.teilgedanken.de/app/)
|
||||||
|
[](https://pslink.teilgedanken.de/app/)
|
||||||
|
|
||||||
## What users can do
|
## 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
|
* download qr-codes of the links
|
||||||
* modify own "profile" settings
|
* modify own "profile" settings
|
||||||
|
|
||||||
### Admins (priviledged account)
|
### Admins (privileged account)
|
||||||
|
|
||||||
* everything from users
|
* everything from users
|
||||||
* modify all links
|
* 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`.
|
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
|
### 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`
|
||||||
|
|
||||||
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`
|
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`
|
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`
|
4. `pslink runserver`
|
||||||
|
|
||||||
@@ -164,3 +174,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**.
|
||||||
|
|||||||
+4
-4
@@ -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()]),
|
||||||
|
|||||||
+19
-18
@@ -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"],
|
||||||
|
|||||||
+2
-2
@@ -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"
|
||||||
|
|
||||||
|
|||||||
+18
-15
@@ -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 = { git = "https://github.com/enaut/actix-web-static-files.git", branch="master" }
|
||||||
|
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 = { git = "https://github.com/enaut/actix-web-static-files.git", branch="master" }
|
||||||
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
-1
@@ -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();
|
||||||
|
|||||||
+101
-16
@@ -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,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -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,7 +95,7 @@ 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 command line parameters 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",
|
||||||
@@ -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
|
||||||
@@ -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();
|
||||||
@@ -234,7 +240,8 @@ 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!(
|
||||||
@@ -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 {}:",
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+3
-3
@@ -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"
|
||||||
|
|||||||
+10
-14
@@ -1,8 +1,8 @@
|
|||||||
//! 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 return type 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
|
||||||
@@ -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,
|
||||||
@@ -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.
|
/// 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user