Compare commits
20
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
0ed010b78f
|
||
|
|
a6598ba5db | ||
|
|
2fba9080d6 | ||
|
|
053992e30c | ||
|
|
fdcc89ec1f
|
||
|
|
dd759923f0
|
||
|
|
0b9afb1fa3
|
||
|
|
c6c56a5ea2
|
||
|
|
aac653f6cb
|
||
|
|
3cb6784448
|
||
|
|
cada51f768
|
||
|
|
59ad7089df
|
||
|
|
20506d1bbe
|
||
|
|
218840432f
|
||
|
|
7fd29d4903
|
||
|
|
77f362368c
|
||
|
|
453cccde8b
|
||
|
|
81c636de56
|
||
|
|
c646dcf263 | ||
|
|
5218efe551 |
+2
-1
@@ -1,6 +1,7 @@
|
||||
/target
|
||||
.env
|
||||
links.db
|
||||
links.db*
|
||||
launch.json
|
||||
settings.json
|
||||
links.session.sql
|
||||
sqltemplates
|
||||
Generated
+304
-1
@@ -1,5 +1,15 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "Inflector"
|
||||
version = "0.11.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
|
||||
dependencies = [
|
||||
"lazy_static",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "actix-codec"
|
||||
version = "0.3.0"
|
||||
@@ -412,6 +422,12 @@ dependencies = [
|
||||
"winapi 0.3.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "arc-swap"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d4d7d63395147b81a9e570bcc6243aaf71c017bd666d4909cfef0085bdda8d73"
|
||||
|
||||
[[package]]
|
||||
name = "argonautica"
|
||||
version = "0.2.0"
|
||||
@@ -1037,6 +1053,12 @@ version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "212d0f5754cb6769937f4501cc0e67f4f4483c8d2c3e1e922ee9edbe4ab4c7c0"
|
||||
|
||||
[[package]]
|
||||
name = "doc-comment"
|
||||
version = "0.3.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fea41bba32d969b513997752735605054bc0dfa92b4c56bf1189f2e174be7a10"
|
||||
|
||||
[[package]]
|
||||
name = "dotenv"
|
||||
version = "0.15.0"
|
||||
@@ -1048,6 +1070,9 @@ name = "either"
|
||||
version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "encoding_rs"
|
||||
@@ -1123,6 +1148,99 @@ dependencies = [
|
||||
"miniz_oxide 0.4.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluent"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "960ac6317b829b94c67f9a774e8b56db388405e174855a5a84d4b461ff85b281"
|
||||
dependencies = [
|
||||
"fluent-bundle",
|
||||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluent-bundle"
|
||||
version = "0.14.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ba3cc2d1c59a0daaa93bb346db97e1ebad1067c5ffedc1af8b937a9d8caa6a77"
|
||||
dependencies = [
|
||||
"fluent-langneg",
|
||||
"fluent-syntax",
|
||||
"intl-memoizer",
|
||||
"intl_pluralrules",
|
||||
"ouroboros",
|
||||
"rustc-hash",
|
||||
"smallvec",
|
||||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluent-langneg"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2c4ad0989667548f06ccd0e306ed56b61bd4d35458d54df5ec7587c0e8ed5e94"
|
||||
dependencies = [
|
||||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluent-syntax"
|
||||
version = "0.10.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "784f660373ea898f712a7e67b43f35bf79608d46112747c29767d087611d716b"
|
||||
|
||||
[[package]]
|
||||
name = "fluent-template-macros"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3051f88dd918b30a994177acc8f3b8b0b399c98ca5aa2ab985e79070a8841592"
|
||||
dependencies = [
|
||||
"flume",
|
||||
"ignore",
|
||||
"once_cell",
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.8",
|
||||
"syn",
|
||||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fluent-templates"
|
||||
version = "0.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fa6b2186b959236019d67fb839f036c83a799edd4389c505678ca1b8d41e9ed"
|
||||
dependencies = [
|
||||
"arc-swap",
|
||||
"fluent",
|
||||
"fluent-bundle",
|
||||
"fluent-langneg",
|
||||
"fluent-syntax",
|
||||
"fluent-template-macros",
|
||||
"flume",
|
||||
"heck",
|
||||
"ignore",
|
||||
"lazy_static",
|
||||
"log",
|
||||
"once_cell",
|
||||
"serde_json",
|
||||
"snafu",
|
||||
"tera",
|
||||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.10.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "531a685ab99b8f60a271b44d5dd1a76e55124a8c9fa0407b7a8e9cd172d5b588"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"nanorand",
|
||||
"pin-project 1.0.4",
|
||||
"spinning_top",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
@@ -1324,8 +1442,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"wasi 0.10.2+wasi-snapshot-preview1",
|
||||
"wasm-bindgen",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1586,6 +1706,26 @@ dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intl-memoizer"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c310433e4a310918d6ed9243542a6b83ec1183df95dff8f23f87bb88a264a66f"
|
||||
dependencies = [
|
||||
"type-map",
|
||||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "intl_pluralrules"
|
||||
version = "7.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b18f988384267d7066cc2be425e6faf352900652c046b6971d2e228d3b1c5ecf"
|
||||
dependencies = [
|
||||
"tinystr",
|
||||
"unic-langid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "iovec"
|
||||
version = "0.1.4"
|
||||
@@ -1845,6 +1985,15 @@ dependencies = [
|
||||
"ws2_32-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nanorand"
|
||||
version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac1378b66f7c93a1c0f8464a19bf47df8795083842e5090f4b7305973d5a22d0"
|
||||
dependencies = [
|
||||
"getrandom 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "net2"
|
||||
version = "0.2.37"
|
||||
@@ -1954,6 +2103,29 @@ version = "0.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5f6d5c203fe8d786d9d7bec8203cbbff3eb2cf8410c0d70cfd05b3d5f5d545da"
|
||||
dependencies = [
|
||||
"ouroboros_macro",
|
||||
"stable_deref_trait",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ouroboros_macro"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "129943a960e6a08c7e70ca5a09f113c273fe7f10ae8420992c78293e3dffdf65"
|
||||
dependencies = [
|
||||
"Inflector",
|
||||
"proc-macro-error",
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.8",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking_lot"
|
||||
version = "0.11.1"
|
||||
@@ -2142,6 +2314,30 @@ version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
|
||||
dependencies = [
|
||||
"proc-macro-error-attr",
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.8",
|
||||
"syn",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error-attr"
|
||||
version = "1.0.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.8",
|
||||
"version_check 0.9.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-hack"
|
||||
version = "0.5.19"
|
||||
@@ -2174,7 +2370,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "pslink"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
dependencies = [
|
||||
"actix-identity",
|
||||
"actix-slog",
|
||||
@@ -2184,6 +2380,8 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"dotenv",
|
||||
"fluent-langneg",
|
||||
"fluent-templates",
|
||||
"image",
|
||||
"qrcode",
|
||||
"rand 0.8.3",
|
||||
@@ -2563,6 +2761,12 @@ version = "0.1.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e3bad0ee36814ca07d7968269dd4b7ec89ec2da10c4bb613928d3077083c232"
|
||||
|
||||
[[package]]
|
||||
name = "rustc-hash"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2"
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
@@ -2694,6 +2898,7 @@ version = "1.0.61"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4fceb2595057b6891a4ee808f70054bd2d12f0e97f1cbb78689b59f676df325a"
|
||||
dependencies = [
|
||||
"indexmap",
|
||||
"itoa",
|
||||
"ryu",
|
||||
"serde",
|
||||
@@ -2816,6 +3021,27 @@ version = "1.6.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fe0f37c9e8f3c5a4a66ad655a93c74daac4ad00c441533bf5c6e7990bb42604e"
|
||||
|
||||
[[package]]
|
||||
name = "snafu"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eab12d3c261b2308b0d80c26fffb58d17eba81a4be97890101f416b478c79ca7"
|
||||
dependencies = [
|
||||
"doc-comment",
|
||||
"snafu-derive",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "snafu-derive"
|
||||
version = "0.6.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1508efa03c362e23817f96cde18abed596a25219a8b2c66e8db33c03543d315b"
|
||||
dependencies = [
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.8",
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
version = "0.3.19"
|
||||
@@ -2833,6 +3059,15 @@ version = "0.5.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
|
||||
|
||||
[[package]]
|
||||
name = "spinning_top"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7e529d73e80d64b5f2631f9035113347c578a1c9c7774b83a2b880788459ab36"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlformat"
|
||||
version = "0.1.6"
|
||||
@@ -2887,6 +3122,7 @@ dependencies = [
|
||||
"parking_lot",
|
||||
"percent-encoding",
|
||||
"rustls",
|
||||
"serde",
|
||||
"sha2",
|
||||
"smallvec",
|
||||
"sqlformat",
|
||||
@@ -2910,9 +3146,12 @@ dependencies = [
|
||||
"either",
|
||||
"futures 0.3.12",
|
||||
"heck",
|
||||
"hex",
|
||||
"lazy_static",
|
||||
"proc-macro2 1.0.24",
|
||||
"quote 1.0.8",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2",
|
||||
"sqlx-core",
|
||||
"sqlx-rt",
|
||||
@@ -2933,6 +3172,12 @@ dependencies = [
|
||||
"tokio-rustls",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "standback"
|
||||
version = "0.2.14"
|
||||
@@ -3212,6 +3457,12 @@ dependencies = [
|
||||
"syn",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tinystr"
|
||||
version = "0.3.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29738eedb4388d9ea620eeab9384884fc3f06f586a2eddb56bedc5885126c7c1"
|
||||
|
||||
[[package]]
|
||||
name = "tinyvec"
|
||||
version = "1.1.1"
|
||||
@@ -3357,6 +3608,15 @@ dependencies = [
|
||||
"trust-dns-proto",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "type-map"
|
||||
version = "0.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b6d3364c5e96cb2ad1603037ab253ddd34d7fb72a58bdddf4b7350760fc69a46"
|
||||
dependencies = [
|
||||
"rustc-hash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "typenum"
|
||||
version = "1.12.0"
|
||||
@@ -3390,6 +3650,49 @@ version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "80d7ff825a6a654ee85a63e80f92f054f904f21e7d12da4e22f9834a4aaa35bc"
|
||||
|
||||
[[package]]
|
||||
name = "unic-langid"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73328fcd730a030bdb19ddf23e192187a6b01cd98be6d3140622a89129459ce5"
|
||||
dependencies = [
|
||||
"unic-langid-impl",
|
||||
"unic-langid-macros",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-langid-impl"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1a4a8eeaf0494862c1404c95ec2f4c33a2acff5076f64314b465e3ddae1b934d"
|
||||
dependencies = [
|
||||
"tinystr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-langid-macros"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "18f980d6d87e8805f2836d64b4138cc95aa7986fa63b1f51f67d5fbff64dd6e5"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"tinystr",
|
||||
"unic-langid-impl",
|
||||
"unic-langid-macros-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-langid-macros-impl"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "29396ffd97e27574c3e01368b1a64267d3064969e4848e2e130ff668be9daa9f"
|
||||
dependencies = [
|
||||
"proc-macro-hack",
|
||||
"quote 1.0.8",
|
||||
"syn",
|
||||
"unic-langid-impl",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unic-segment"
|
||||
version = "0.9.0"
|
||||
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "pslink"
|
||||
version = "0.3.0"
|
||||
version = "0.3.1"
|
||||
description = "A simple webservice that allows registered users to create short links including qr-codes.\nAnyone can visit the shortened links. This is an ideal setup for small busines or for publishing papers."
|
||||
authors = ["Dietrich <dietrich@teilgedanken.de>"]
|
||||
edition = "2018"
|
||||
@@ -8,7 +8,7 @@ license = "MIT OR Apache-2.0"
|
||||
keywords = ["url", "link", "webpage", "actix", "web"]
|
||||
categories = ["web-programming", "network-programming", "web-programming::http-server", "command-line-utilities"]
|
||||
readme = "README.md"
|
||||
repository = "https://git.teilgedanken.de/dietrich/Pslink"
|
||||
repository = "https://github.com/enaut/pslink/"
|
||||
build = "build.rs"
|
||||
|
||||
|
||||
@@ -20,7 +20,7 @@ actix-web-static-files = "3.0"
|
||||
actix-slog = "0.2"
|
||||
tera = "1.6"
|
||||
serde = "1.0"
|
||||
sqlx={version="0.4", features = [ "sqlite", "macros", "runtime-actix-rustls", "chrono", "migrate" ]}
|
||||
sqlx={version="0.4", features = [ "sqlite", "macros", "runtime-actix-rustls", "chrono", "migrate", "offline" ]}
|
||||
dotenv = "0.15.0"
|
||||
actix-identity = "0.3"
|
||||
chrono = { version = "0.4", features = ["serde"] }
|
||||
@@ -33,6 +33,8 @@ image = "0.23"
|
||||
rand="0.8"
|
||||
rpassword = "5.0"
|
||||
clap = "2.33"
|
||||
fluent-templates = { version = "0.6", features = ["tera"] }
|
||||
fluent-langneg ="0.13"
|
||||
|
||||
[build-dependencies]
|
||||
actix-web-static-files = "3.0"
|
||||
|
||||
@@ -2,41 +2,91 @@
|
||||
|
||||
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).
|
||||
|
||||
So in general this is more a shared short url bookmark webpage than a shorturl service.
|
||||
|
||||

|
||||
|
||||
The Page comes with a basic commandline interface to setup the environment. If it is built with `cargo build release --target=x86_64-unknown-linux-musl` everything is embedded and it should be portable to any 64bit linux system.
|
||||
Templates and migrations are embedded in the binary. So it should run standalone without anything extra.
|
||||
The Page comes with a basic commandline interface to setup the environment.
|
||||
|
||||
## usage
|
||||
## Usage
|
||||
|
||||
### setup
|
||||
### install binary
|
||||
|
||||
The pslink binary can be downloaded from the latest release at: https://github.com/enaut/pslink/releases
|
||||
|
||||
These binaries are selfcontained and should run on any linux 64bit system. Just put them where you like them to be and make them executable. A sample install might be:
|
||||
|
||||
```bash
|
||||
# mkdir -p /opt/pslink
|
||||
# wget -o /opt/pslink/pslink https://github.com/enaut/pslink/releases/latest/download/pslink.linux.64bit
|
||||
# chmod +x /opt/pslink/pslink
|
||||
```
|
||||
|
||||
You could now adjust your `PATH` or setup an alias or just call the binary with the full path `/opt/pslink/pslink`
|
||||
|
||||
### Install with cargo
|
||||
|
||||
Pslink can be compiled and installed with cargo. Setup cargo as guided here: https://doc.rust-lang.org/cargo/getting-started/installation.html
|
||||
|
||||
After that install pslink using:
|
||||
|
||||
```bash
|
||||
$ cargo install pslink
|
||||
```
|
||||
|
||||
If that succeeds you should now be able to call pslink.
|
||||
|
||||
### Build from source
|
||||
|
||||
When building manually with cargo you have to have a sqlite database present or build it in offline mode. So on your first build you will most likely need to call:
|
||||
|
||||
```bash
|
||||
SQLX_OFFLINE=1 cargo run
|
||||
# or
|
||||
$ export SQLX_OFFLINE=1
|
||||
$ cargo run
|
||||
```
|
||||
|
||||
If pslink is built with `cargo build release --target=x86_64-unknown-linux-musl` everything is embedded and it should be portable to any 64bit linux system.
|
||||
Templates and migrations are embedded in the binary so it should run standalone without anything extra.
|
||||
|
||||
### Setup
|
||||
|
||||
To get Pslink up and running use the commands in the following order:
|
||||
|
||||
1. `pslink generate-env` this will generate a `.env` file in the curent directory with the default settings. Edit this file to your liking. You can however skip this step and provide all the parameters via commandline or environmentvariable. It is **not** recommended to provide PSLINK_SECRET with commandline parameters as they can be read by every user on the system.
|
||||
2. `pslink migrate-database` will create a sqlite database in the location specified.
|
||||
3. `pslink create-admin` create an initial admin user. As the page has no "register" function this is required to do anything usefull.
|
||||
4. `pslink runserver` If everything is set up correctly this command will start the service.
|
||||
1. `pslink generate-env`
|
||||
|
||||
### run the service
|
||||
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.
|
||||
2. `pslink migrate-database`
|
||||
|
||||
will create a sqlite database in the location specified.
|
||||
3. `pslink create-admin`
|
||||
|
||||
create an initial admin user. As the page has no "register" function this is required to do anything usefull.
|
||||
4. `pslink runserver`
|
||||
|
||||
If everything is set up correctly this command will start the service.
|
||||
|
||||
### Run the service
|
||||
|
||||
If everything is correctly set up just do `pslink runserver`.
|
||||
|
||||
### update
|
||||
### Update
|
||||
|
||||
To update to a newer version execute the commands in the following order
|
||||
|
||||
1. stop the service
|
||||
2. run `pslink migrate-database`
|
||||
3. run the server again `pslink runserver`
|
||||
2. download and install the new binary
|
||||
3. run `pslink migrate-database`
|
||||
4. run the server again `pslink runserver`
|
||||
|
||||
### help
|
||||
### Help
|
||||
|
||||
For a list of options use `pslink help`.
|
||||
For a list of options use `pslink help`. If the help does not provide enough clues please file an issue at: https://github.com/enaut/pslink/issues/new
|
||||
|
||||
### systemd service file
|
||||
### Systemd service file
|
||||
|
||||
If you want to automatically start this with systemd you can adjust the following template unit to your system. In this case a dedicated `pslink` user and group are used with the users home directory at `/var/pslink`. Some additional settings are in place to protect the system a little should anything go wrong.
|
||||
If you want to automatically start this with systemd you can adjust the following template unit to your system. In this case a dedicated `pslink` user and group is used with the users home directory at `/var/pslink`. Some additional settings are in place to protect the system a little should anything go wrong.
|
||||
|
||||
```systemd
|
||||
# /etc/systemd/system/pslink.service
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
Guide to release:
|
||||
|
||||
- [ ] Verify everything is committed
|
||||
- [ ] update the sqlx cache: cargo sqlx prepare
|
||||
- [ ] commit the update
|
||||
- [ ] push to github and teilgedanken
|
||||
- [ ] create release draft tag: https://github.com/enaut/pslink/releases
|
||||
- [ ] check `git log --pretty=format:'* %s' --abbrev-commit` for changes and selectively include into changelist.
|
||||
- [ ] verify everything is ready for publishing using:
|
||||
|
||||
```
|
||||
SQLX_OFFLINE=1 cargo publish --dry-run
|
||||
```
|
||||
|
||||
- [ ] make draft a release
|
||||
|
||||
- [ ] publish
|
||||
|
||||
```SQLX_OFFLINE=1 cargo publish```
|
||||
@@ -0,0 +1,36 @@
|
||||
list-links = Link Liste
|
||||
add-link = Link hinzufügen
|
||||
invite-user = Benutzer einladen
|
||||
list-users = Liste der Benutzer
|
||||
welcome-user = Herzlich willkommen {$username}
|
||||
logout = Abmelden
|
||||
login = Login
|
||||
|
||||
not-found = Dieser Link existiert nicht, oder wurde gelöscht.
|
||||
|
||||
edit-link-headline = Zu editierender Link: {$linktitle}
|
||||
edit-link = Link Editieren
|
||||
link-description = Beschreibung
|
||||
link-target = Link Ziel
|
||||
link-code = Link Code
|
||||
shortlink = Shortlink
|
||||
qr-code = QR-code
|
||||
|
||||
danger-zone = Achtung!
|
||||
danger-zone-text = Verändern Sie den Code von bereits veröffentlichten Links nicht. Sollte es dennoch geschehen werden veröffentlichte links unbenutzbar. Wird das Linkziel verändert, so zeigen auch die bereits veröffentlichten Links auf das neue Ziel.
|
||||
|
||||
save-edits = Speichere die Veränderungen
|
||||
delete-link = Diesen Link löschen
|
||||
|
||||
edit-user-headline = Benutzereinstellungen von: {$username}
|
||||
username = Benutzername
|
||||
email = Email
|
||||
password = Passwort
|
||||
password-placeholder = Leer lassen um das Passwort nicht zu ändern
|
||||
save-user = Benutzer speichern
|
||||
edit-user = Benutzer editieren
|
||||
make-user-admin = Zum Administrator befördern
|
||||
make-user-regular = Zurückstufen zum normalen Nutzer
|
||||
|
||||
userid = Benutzernummer
|
||||
statistics = Statistik
|
||||
@@ -0,0 +1,36 @@
|
||||
list-links = List of existing links
|
||||
add-link = Add a new link
|
||||
invite-user = Invite a new user
|
||||
list-users = List of existing users
|
||||
welcome-user = Welcome {$username}
|
||||
logout = Logout
|
||||
login = Login
|
||||
|
||||
not-found = This Link has not been found or has been deleted
|
||||
|
||||
edit-link-headline = Edit link: {$linktitle}
|
||||
edit-link = Edit link
|
||||
link-description = Description
|
||||
link-target = Link target
|
||||
link-code = Link code
|
||||
shortlink = Shortlink
|
||||
|
||||
danger-zone = Danger Zone!
|
||||
danger-zone-text = Do not change the code of links that are published. If you do so the published links will become invalid! If you change the target the published links will point to the new target.
|
||||
|
||||
save-edits = Save edits
|
||||
delete-link = Delete this link
|
||||
|
||||
user-headline = User Settings of: {$username}
|
||||
edit-user-headline = Change Settings of: {$username}
|
||||
username = Username
|
||||
email = Email
|
||||
password = Password
|
||||
password-placeholder = Leave this empty to keep the current password
|
||||
save-user = Save this user
|
||||
edit-user = Edit this user
|
||||
make-user-admin = Promote to admin
|
||||
make-user-regular = Demote to regular
|
||||
|
||||
userid = User ID
|
||||
statistics = Statistics
|
||||
@@ -0,0 +1,4 @@
|
||||
-- Add migration script here
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN language Text NOT NULL DEFAULT "en";
|
||||
@@ -0,0 +1,24 @@
|
||||
-- Add migration script here
|
||||
PRAGMA foreign_keys = off;
|
||||
|
||||
|
||||
CREATE TABLE new_clicks (
|
||||
id INTEGER PRIMARY KEY NOT NULL,
|
||||
link INT NOT NULL,
|
||||
created_at TIMESTAMP NOT NULL,
|
||||
FOREIGN KEY (link) REFERENCES links (id) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
INSERT INTO
|
||||
new_clicks
|
||||
SELECT
|
||||
*
|
||||
FROM
|
||||
clicks;
|
||||
|
||||
|
||||
DROP TABLE clicks;
|
||||
ALTER TABLE
|
||||
new_clicks RENAME TO clicks;
|
||||
|
||||
PRAGMA foreign_keys = on;
|
||||
+265
@@ -0,0 +1,265 @@
|
||||
{
|
||||
"db": "SQLite",
|
||||
"01e68928ea67ef301d8ea72a320fe747dafbfaa398a22731effb93d23ae16a77": {
|
||||
"query": "UPDATE links SET\n title = ?,\n target = ?,\n code = ?,\n author = ?,\n created_at = ? where id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 6
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"04640e79c590ae8b845b6281a786ebe72060d02ee98eaec4839d1525bde3b0b5": {
|
||||
"query": "Select * from links where code = ? ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "title",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "target",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "code",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "author",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "created_at",
|
||||
"ordinal": 5,
|
||||
"type_info": "Datetime"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"0d5cc1ab073e15c4306ef2bfc89aeefd6daa409766741c21f9eb0115b0f24eb1": {
|
||||
"query": "UPDATE users SET\n username = ?,\n email = ?,\n password = ?,\n role = ? where id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"109ddc9fa55a36541ed1a866d362ff666bb39c3672f72000e786f86b514dc239": {
|
||||
"query": "Select * from users where id = ? ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"193ebdfd8bdb96da45f5054f83a6a5e23eaa311e3e5c4139095a3455f4764c64": {
|
||||
"query": "Select * from users",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"3ad5584fcb0c2685339e471320e8d0c091b684ffe86912a1f2540eee1444889d": {
|
||||
"query": "Insert into clicks (\n link,\n created_at) VALUES (?,?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"5919b8824209c31a76178f30b3d52f385931ee0f3aa17e65f8647ad15a3595d8": {
|
||||
"query": "Insert into links (\n title,\n target,\n code,\n author,\n created_at) VALUES (?,?,?,?,?)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 5
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"5d892d21ed6b4ccc3ddedb3a7469385f001f6ff9f41b19faa67b754ad8f7fc4b": {
|
||||
"query": "Insert into users (\n username,\n email,\n password,\n role) VALUES (?,?,?,1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 3
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"77a21769284cb3df457d806d6e04088ae4f99d92c535fd15c79828e46ee3ae6f": {
|
||||
"query": "UPDATE users SET role = ? where id = ?",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 2
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"7a52eae6cb72e4daac95a99d15113ab09571329733ea121fecc55d18dfdb1c45": {
|
||||
"query": "DELETE from links where code = ? ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": []
|
||||
}
|
||||
},
|
||||
"8670a2dacd013de68831d0a45d927ea9f473d86100387fc85c9b9802668c3de4": {
|
||||
"query": "Select * from users where username = ? ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "id",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int64"
|
||||
},
|
||||
{
|
||||
"name": "username",
|
||||
"ordinal": 1,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "email",
|
||||
"ordinal": 2,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "password",
|
||||
"ordinal": 3,
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"name": "role",
|
||||
"ordinal": 4,
|
||||
"type_info": "Int64"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 1
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false
|
||||
]
|
||||
}
|
||||
},
|
||||
"9cb2f491eab760ba60ede5b3e730b84d061fc09627579ff05bc63d3e27fe8fb7": {
|
||||
"query": "select count(*) as number from users where role = 2",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"name": "number",
|
||||
"ordinal": 0,
|
||||
"type_info": "Int"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Right": 0
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
+18
-2
@@ -25,6 +25,7 @@ use std::{fmt::Display, path::PathBuf, str::FromStr};
|
||||
use actix_identity::{CookieIdentityPolicy, IdentityService};
|
||||
use actix_web::{web, App, HttpResponse, HttpServer};
|
||||
|
||||
use fluent_templates::{static_loader, FluentLoader};
|
||||
use qrcode::types::QrError;
|
||||
use sqlx::{Pool, Sqlite};
|
||||
use tera::Tera;
|
||||
@@ -63,7 +64,7 @@ impl actix_web::error::ResponseError for ServerError {
|
||||
match self {
|
||||
Self::Argonautic => HttpResponse::InternalServerError().json("Argonautica Error"),
|
||||
Self::Database(e) => {
|
||||
HttpResponse::InternalServerError().json(format!("Diesel Error: {:?}", e))
|
||||
HttpResponse::InternalServerError().json(format!("Database Error: {:?}", e))
|
||||
}
|
||||
Self::DatabaseMigration(_) => {
|
||||
unimplemented!("A migration error should never be rendered")
|
||||
@@ -188,9 +189,20 @@ impl ServerConfig {
|
||||
}
|
||||
|
||||
include!(concat!(env!("OUT_DIR"), "/generated.rs"));
|
||||
|
||||
static_loader! {
|
||||
static LOCALES = {
|
||||
locales: "./locales",
|
||||
fallback_language: "en",
|
||||
};
|
||||
}
|
||||
|
||||
fn build_tera() -> Tera {
|
||||
let mut tera = Tera::default();
|
||||
|
||||
// Add translation support
|
||||
tera.register_function("fluent", FluentLoader::new(&*LOCALES));
|
||||
|
||||
tera.add_raw_templates(vec![
|
||||
("admin.html", include_str!("../templates/admin.html")),
|
||||
("base.html", include_str!("../templates/base.html")),
|
||||
@@ -311,7 +323,11 @@ async fn webservice(server_config: ServerConfig) -> std::io::Result<()> {
|
||||
web::post().to(views::process_edit_profile),
|
||||
),
|
||||
)
|
||||
.route("/set_admin/{user_id}", web::get().to(views::toggle_admin)),
|
||||
.route("/set_admin/{user_id}", web::get().to(views::toggle_admin))
|
||||
.route(
|
||||
"/set_language/{language}",
|
||||
web::get().to(views::set_language),
|
||||
),
|
||||
)
|
||||
.service(
|
||||
web::scope("/delete").service(
|
||||
|
||||
@@ -11,6 +11,7 @@ pub struct User {
|
||||
pub email: String,
|
||||
pub password: String,
|
||||
pub role: i64,
|
||||
pub language: String,
|
||||
}
|
||||
|
||||
impl User {
|
||||
@@ -62,6 +63,7 @@ impl User {
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn toggle_admin(
|
||||
self,
|
||||
server_config: &ServerConfig,
|
||||
@@ -73,6 +75,21 @@ impl User {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn set_language(
|
||||
self,
|
||||
server_config: &ServerConfig,
|
||||
new_language: &str,
|
||||
) -> Result<(), ServerError> {
|
||||
sqlx::query!(
|
||||
"UPDATE users SET language = ? where id = ?",
|
||||
new_language,
|
||||
self.id
|
||||
)
|
||||
.execute(&server_config.db_pool)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) async fn count_admins(server_config: &ServerConfig) -> Result<Count, ServerError> {
|
||||
let num = sqlx::query_as!(Count, "select count(*) as number from users where role = 2")
|
||||
.fetch_one(&server_config.db_pool)
|
||||
|
||||
+23
-1
@@ -81,6 +81,7 @@ pub(crate) async fn list_all_allowed(
|
||||
users.username as usern,
|
||||
users.email as uemail,
|
||||
users.role as urole,
|
||||
users.language as ulang,
|
||||
count(clicks.id) as counter
|
||||
from
|
||||
links
|
||||
@@ -107,6 +108,7 @@ pub(crate) async fn list_all_allowed(
|
||||
email: v.get("uemail"),
|
||||
password: "invalid".to_owned(),
|
||||
role: v.get("urole"),
|
||||
language: v.get("ulang"),
|
||||
},
|
||||
clicks: Count {
|
||||
number: v.get("counter"), /* count is never None */
|
||||
@@ -157,7 +159,6 @@ pub(crate) async fn get_user(
|
||||
if let Ok(uid) = user_id.parse::<i64>() {
|
||||
slog_info!(server_config.log, "Getting user {}", uid);
|
||||
let auth = authenticate(id, server_config).await?;
|
||||
slog_info!(server_config.log, "{:?}", &auth);
|
||||
if auth.admin_or_self(uid) {
|
||||
match auth {
|
||||
Role::Admin { user } | Role::Regular { user } => {
|
||||
@@ -245,6 +246,7 @@ pub(crate) async fn update_user(
|
||||
email: data.email.clone(),
|
||||
password,
|
||||
role: unmodified_user.role,
|
||||
language: unmodified_user.language,
|
||||
};
|
||||
new_user.update_user(server_config).await?;
|
||||
let changed_user = User::get_user(uid, server_config).await?;
|
||||
@@ -303,6 +305,26 @@ pub(crate) async fn toggle_admin(
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn set_language(
|
||||
id: &Identity,
|
||||
lang_code: &str,
|
||||
server_config: &ServerConfig,
|
||||
) -> Result<(), ServerError> {
|
||||
match lang_code {
|
||||
"de" | "en" => match authenticate(id, server_config).await? {
|
||||
Role::Admin { user } | Role::Regular { user } => {
|
||||
user.set_language(server_config, lang_code).await
|
||||
}
|
||||
Role::Disabled | Role::NotAuthenticated => {
|
||||
Err(ServerError::User("Not Allowed".to_owned()))
|
||||
}
|
||||
},
|
||||
_ => Err(ServerError::User(
|
||||
"This language is not supported!".to_owned(),
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get one link if permissions are accordingly.
|
||||
pub(crate) async fn get_link(
|
||||
id: &Identity,
|
||||
|
||||
+52
-1
@@ -3,9 +3,14 @@ use std::time::SystemTime;
|
||||
use actix_identity::Identity;
|
||||
use actix_web::{
|
||||
http::header::{CacheControl, CacheDirective, ContentType, Expires},
|
||||
web, HttpResponse,
|
||||
web, HttpRequest, HttpResponse,
|
||||
};
|
||||
use argonautica::Verifier;
|
||||
use fluent_langneg::{
|
||||
convert_vec_str_to_langids_lossy, negotiate_languages, parse_accepted_languages,
|
||||
NegotiationStrategy,
|
||||
};
|
||||
use fluent_templates::LanguageIdentifier;
|
||||
use image::{DynamicImage, ImageOutputFormat, Luma};
|
||||
use qrcode::{render::svg, QrCode};
|
||||
use tera::{Context, Tera};
|
||||
@@ -27,6 +32,35 @@ fn redirect_builder(target: &str) -> HttpResponse {
|
||||
.body(format!("Redirect to {}", target))
|
||||
}
|
||||
|
||||
fn detect_language(request: &HttpRequest) -> Result<String, ServerError> {
|
||||
let requested = parse_accepted_languages(
|
||||
request
|
||||
.headers()
|
||||
.get(actix_web::http::header::ACCEPT_LANGUAGE)
|
||||
.ok_or_else(|| ServerError::User("Failed to get Accept_Language".to_owned()))?
|
||||
.to_str()
|
||||
.map_err(|_| {
|
||||
ServerError::User("Failed to convert Accept_language to str".to_owned())
|
||||
})?,
|
||||
);
|
||||
let available = convert_vec_str_to_langids_lossy(&["de", "en"]);
|
||||
let default: LanguageIdentifier = "en"
|
||||
.parse()
|
||||
.map_err(|_| ServerError::User("Failed to parse a langid.".to_owned()))?;
|
||||
|
||||
let supported = negotiate_languages(
|
||||
&requested,
|
||||
&available,
|
||||
Some(&default),
|
||||
NegotiationStrategy::Filtering,
|
||||
);
|
||||
let languagecode = supported
|
||||
.get(0)
|
||||
.map_or("en".to_string(), std::string::ToString::to_string);
|
||||
println!("Detected the language: {}", &languagecode);
|
||||
Ok(languagecode)
|
||||
}
|
||||
|
||||
/// Show the list of all available links if a user is authenticated
|
||||
pub(crate) async fn index(
|
||||
tera: web::Data<Tera>,
|
||||
@@ -248,12 +282,26 @@ pub(crate) async fn toggle_admin(
|
||||
)))
|
||||
}
|
||||
|
||||
pub(crate) async fn set_language(
|
||||
data: web::Path<String>,
|
||||
config: web::Data<crate::ServerConfig>,
|
||||
id: Identity,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
queries::set_language(&id, &data.0, &config).await?;
|
||||
Ok(redirect_builder("/admin/index/"))
|
||||
}
|
||||
|
||||
pub(crate) async fn login(
|
||||
tera: web::Data<Tera>,
|
||||
id: Identity,
|
||||
config: web::Data<crate::ServerConfig>,
|
||||
req: HttpRequest,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
let language_code = detect_language(&req)?;
|
||||
slog_info!(config.log, "Detected languagecode: {}", &language_code);
|
||||
let mut data = Context::new();
|
||||
data.insert("title", "Login");
|
||||
data.insert("language", &language_code);
|
||||
|
||||
if let Some(_id) = id.identity() {
|
||||
return Ok(redirect_builder("/admin/index/"));
|
||||
@@ -304,6 +352,7 @@ pub(crate) async fn redirect(
|
||||
tera: web::Data<Tera>,
|
||||
config: web::Data<crate::ServerConfig>,
|
||||
data: web::Path<String>,
|
||||
req: HttpRequest,
|
||||
) -> Result<HttpResponse, ServerError> {
|
||||
slog_info!(config.log, "Redirecting to {:?}", data);
|
||||
let link = queries::get_link_simple(&data.0, &config).await;
|
||||
@@ -323,6 +372,8 @@ pub(crate) async fn redirect(
|
||||
);
|
||||
let mut data = Context::new();
|
||||
data.insert("title", "Wurde gel\u{f6}scht");
|
||||
let language = detect_language(&req)?;
|
||||
data.insert("language", &language);
|
||||
let rendered = tera.render("not_found.html", &data)?;
|
||||
Ok(HttpResponse::NotFound().body(rendered))
|
||||
}
|
||||
|
||||
+12
-7
@@ -11,14 +11,19 @@
|
||||
<div class="admin">
|
||||
<nav>
|
||||
<ol>
|
||||
<li><a href="/admin/index/">Link Liste</a></li>
|
||||
<li><a href="/admin/submit/">Link Hinzufügen</a></li>
|
||||
{% if user.role == 2 %}<li><a href="/admin/signup/">Einladen</a></li>
|
||||
<li><a href="/admin/view/users/">Benutzer</a></li>{% endif %}
|
||||
<li style="float:right"><a href="/admin/logout/">Abmelden</a></li>
|
||||
<li style="float:right">
|
||||
<div class="willkommen">Herzlich willkommen {{ user.username }}</div>
|
||||
<li><a href="/admin/index/">{{ fluent(key="list-links", lang=user.language) }}</a>
|
||||
</li>
|
||||
<li><a href="/admin/submit/">{{ fluent(key="add-link", lang=user.language) }}</a></li>
|
||||
{% if user.role == 2 %}<li><a href="/admin/signup/">{{ fluent(key="invite-user", lang=user.language) }}</a>
|
||||
</li>
|
||||
<li><a href="/admin/view/users/">{{ fluent(key="list-users", lang=user.language) }}</a></li>{% endif %}
|
||||
<li style="float:right"><a href="/admin/logout/">{{ fluent(key="logout", lang=user.language) }}</a></li>
|
||||
<li style="float:right">
|
||||
<div class="willkommen">{{ fluent(key="welcome-user", lang=user.language, username=user.username) }}
|
||||
</div>
|
||||
</li>
|
||||
<li style="float:right"><a href="/admin/edit/set_language/en">en</a></li>
|
||||
<li style="float:right"><a href="/admin/edit/set_language/de">de</a></li>
|
||||
</ol>
|
||||
</nav>
|
||||
{% block admin %}
|
||||
|
||||
@@ -2,26 +2,26 @@
|
||||
|
||||
{% block admin %}
|
||||
<div class="center">
|
||||
<h1>Link Editieren: {{ link.title }}</h1>
|
||||
<h1>{{ fluent(key="edit-link-headline", lang=user.language, linktitle=link.title) }}</h1>
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="title">Beschreibung:</label>
|
||||
<label for="title">{{ fluent(key="link-description", lang=user.language) }}:</label>
|
||||
<input type="text" name="title" value="{{ link.title }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="target">Ziel:</label>
|
||||
<label for="target">{{ fluent(key="link-target", lang=user.language) }}:</label>
|
||||
<input type="text" name="target" value="{{link.target}}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="code">Code:</label>
|
||||
<label for="code">{{ fluent(key="link-code", lang=user.language) }}:</label>
|
||||
<input type="text" name="code" value="{{link.code}}">
|
||||
</div>
|
||||
<div class="actions danger">
|
||||
<h2>Achtung!</h2>
|
||||
<h3>Werden schon veröffentlichte Links gelöscht oder editiert sind die Links z.B. aus einem Buch
|
||||
nicht mehr gültig! UNBEDINGT VERMEIDEN!</h3>
|
||||
<input type="submit" value="Speichern">
|
||||
<a class="button" href="/admin/delete/link/{{link.code}}">Delete</a>
|
||||
<h2>{{ fluent(key="danger-zone", lang=user.language) }}</h2>
|
||||
<h3>{{ fluent(key="danger-zone-text", lang=user.language) }}</h3>
|
||||
<input type="submit" value='{{ fluent(key="save-edits", lang=user.language) }}'>
|
||||
<a class="button" href="/admin/delete/link/{{link.code}}">{{ fluent(key="delete-link", lang=user.language)
|
||||
}}</a>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,21 +2,27 @@
|
||||
|
||||
{% block admin %}
|
||||
<div class="center">
|
||||
<h1>Profil von {{user.username}}</h1>
|
||||
<h1>{{ fluent(key="edit-user-headline", lang=user.language, username=user.username) }}
|
||||
</h1>
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="username">Benutzername:</label>
|
||||
<label for="username">{{ fluent(key="username", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="text" name="username" value="{{ user.username }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="email">E-mail:</label>
|
||||
<label for="email">{{ fluent(key="email", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="email" name="email" value="{{ user.email }}">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Passwort:</label>
|
||||
<input type="password" name="password" placeholder="Leer lassen um nichts zu ändern">
|
||||
<label for="password">{{ fluent(key="password", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="password" name="password" placeholder='{{ fluent(key="password-placeholder", lang=user.language)
|
||||
}}'>
|
||||
</div>
|
||||
<input type="submit" value="Speichern">
|
||||
<input type="submit" value='{{ fluent(key="save-user", lang=user.language)
|
||||
}}'>
|
||||
</form>
|
||||
<h2> </h2>
|
||||
</div>
|
||||
|
||||
@@ -11,16 +11,20 @@
|
||||
|
||||
<tr>
|
||||
<th>
|
||||
Kürzel
|
||||
{{ fluent(key="link-code", lang=user.language)
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
Ziellink
|
||||
{{ fluent(key="link-target", lang=user.language)
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
Benutzername
|
||||
{{ fluent(key="username", lang=user.language)
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
Statistik
|
||||
{{ fluent(key="statistics", lang=user.language)
|
||||
}}
|
||||
</th>
|
||||
</tr>
|
||||
{% for links_user in links_per_users %}
|
||||
|
||||
@@ -11,13 +11,16 @@
|
||||
|
||||
<tr>
|
||||
<th>
|
||||
Kürzel
|
||||
{{ fluent(key="userid", lang=user.language)
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
Emailadresse
|
||||
{{ fluent(key="email", lang=user.language)
|
||||
}}
|
||||
</th>
|
||||
<th>
|
||||
Benutzername
|
||||
{{ fluent(key="username", lang=user.language)
|
||||
}}
|
||||
</th>
|
||||
</tr>
|
||||
{% for user in users %}
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
<div class="center">
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="username">Benutzername:</label>
|
||||
<label for="username">{{ fluent(key="username", lang=language)
|
||||
}}:</label>
|
||||
<input type="text" name="username">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Passwort:</label>
|
||||
<label for="password">{{ fluent(key="password", lang=language)
|
||||
}}:</label>
|
||||
<input type="password" name="password">
|
||||
</div>
|
||||
<input type="submit" value="Login">
|
||||
<input type="submit" value='{{ fluent(key="login", lang=language) }}'>
|
||||
</form>
|
||||
<h2> </h2>
|
||||
</div>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="center">
|
||||
<h3>This Link has not been found or has been deleted</h3>
|
||||
<h3>{{ fluent(key="not-found", lang=language) }}</h3>
|
||||
<h2> </h2>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -4,18 +4,22 @@
|
||||
<div class="center">
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="username">Benutzername:</label>
|
||||
<label for="username">{{ fluent(key="username", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="text" name="username">
|
||||
</div>
|
||||
<div>
|
||||
<label for="email">E-mail:</label>
|
||||
<label for="email">{{ fluent(key="email", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="email" name="email">
|
||||
</div>
|
||||
<div>
|
||||
<label for="password">Passwort:</label>
|
||||
<label for="password">{{ fluent(key="password", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="password" name="password">
|
||||
</div>
|
||||
<input type="submit" value="Einladen">
|
||||
<input type="submit" value='{{ fluent(key="invite-user", lang=user.language)
|
||||
}}'>
|
||||
</form>
|
||||
<h2> </h2>
|
||||
</div>
|
||||
|
||||
@@ -4,15 +4,18 @@
|
||||
<div class="center">
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="title">Beschreibung:</label>
|
||||
<label for="title">{{ fluent(key="link-description", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="text" name="title">
|
||||
</div>
|
||||
<div>
|
||||
<label for="target">Ziel:</label>
|
||||
<label for="target">{{ fluent(key="link-target", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="text" name="target">
|
||||
</div>
|
||||
<div>
|
||||
<label for="code">Code:</label>
|
||||
<label for="code">{{ fluent(key="link-code", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="text" name="code">
|
||||
</div>
|
||||
<input type="submit" value="Submit">
|
||||
|
||||
@@ -5,24 +5,29 @@
|
||||
<h1>{{ link.title }}</h1>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Beschreibung:</td>
|
||||
<td>{{ fluent(key="link-description", lang=user.language)
|
||||
}}:</td>
|
||||
<td>{{ link.title }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Code:</td>
|
||||
<td>{{ fluent(key="link-code", lang=user.language)
|
||||
}}:</td>
|
||||
<td>{{ link.code }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Kurzlink:</td>
|
||||
<td>{{ fluent(key="shortlink", lang=user.language)
|
||||
}}:</td>
|
||||
<td><a href="{{ protocol }}://{{ host }}/{{ link.code }}">{{ protocol }}://{{ host }}/{{ link.code }}</a>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Ziel:</td>
|
||||
<td>{{ fluent(key="link-target", lang=user.language)
|
||||
}}:</td>
|
||||
<td>{{ link.target }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>QR-Code</td>
|
||||
<td>{{ fluent(key="qr-code", lang=user.language)
|
||||
}}</td>
|
||||
<td><a href="/admin/download/png/{{ link.code }}" download="{{ link.title | slugify }}.png">
|
||||
{{ qr | trim_start_matches(pat=
|
||||
'.*?>')
|
||||
@@ -33,7 +38,8 @@
|
||||
</table>
|
||||
{% if user.role == 2 or user.id == link.author %}
|
||||
<div class="actions">
|
||||
<a class="button" href="/admin/edit/link/{{ link.code }}">Editieren</a>
|
||||
<a class="button" href="/admin/edit/link/{{ link.code }}">{{ fluent(key="edit-link", lang=user.language)
|
||||
}}</a>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
@@ -2,31 +2,39 @@
|
||||
|
||||
{% block admin %}
|
||||
<div class="center">
|
||||
<h1>Profil von {{viewed_user.username}}</h1>
|
||||
<h1>{{ fluent(key="user-headline", lang=user.language, username=user.username) }}</h1>
|
||||
<form action="" method="POST">
|
||||
<div>
|
||||
<label for="username">Benutzername:</label>
|
||||
<label for="username">{{ fluent(key="username", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="text" name="username" value="{{ viewed_user.username }}" readonly>
|
||||
</div>
|
||||
<div>
|
||||
<label for="email">E-mail:</label>
|
||||
<label for="email">{{ fluent(key="email", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="email" name="email" value="{{ viewed_user.email }}" readonly>
|
||||
</div>
|
||||
{% if user.role == 2 or user.id == viewed_user.id %}
|
||||
<div>
|
||||
<label for="password">Passwort:</label>
|
||||
<label for="password">{{ fluent(key="password", lang=user.language)
|
||||
}}:</label>
|
||||
<input type="password" name="password" value="verschlüsselt" readonly>
|
||||
</div>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% if user.role == 2 or user.id == viewed_user.id %}
|
||||
<div class="actions">
|
||||
<a class="button" href="/admin/edit/profile/{{ viewed_user.id }}">Editieren</a>
|
||||
<a class="button" href="/admin/edit/profile/{{ viewed_user.id }}">{{ fluent(key="edit-user", lang=user.language)
|
||||
}}</a>
|
||||
{% if user.role == 2 and viewed_user.role == 1 %}
|
||||
<a class="button" href="/admin/edit/set_admin/{{ viewed_user.id }}">Zum Admin machen</a>
|
||||
<a class="button" href="/admin/edit/set_admin/{{ viewed_user.id }}">{{ fluent(key="make-user-admin",
|
||||
lang=user.language)
|
||||
}}</a>
|
||||
{% endif %}
|
||||
{% if user.role == 2 and viewed_user.role == 2 %}
|
||||
<a class="button" href="/admin/edit/set_admin/{{ viewed_user.id }}">Zum Normalo machen</a>
|
||||
{% if user.role == 2 and viewed_user.role == 2 and not user.id == viewed_user.id %}
|
||||
<a class="button" href="/admin/edit/set_admin/{{ viewed_user.id }}">{{ fluent(key="make-user-regular",
|
||||
lang=user.language)
|
||||
}}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user