Compare commits
12 Commits
7fd29d4903
...
c18098cb8a
Author | SHA1 | Date | |
---|---|---|---|
c18098cb8a | |||
2bad759547 | |||
fdcc89ec1f | |||
dd759923f0 | |||
0b9afb1fa3 | |||
c6c56a5ea2 | |||
aac653f6cb | |||
3cb6784448 | |||
cada51f768 | |||
59ad7089df | |||
20506d1bbe | |||
218840432f |
2
.gitignore
vendored
2
.gitignore
vendored
@ -1,6 +1,6 @@
|
||||
/target
|
||||
.env
|
||||
links.db
|
||||
links.db*
|
||||
launch.json
|
||||
settings.json
|
||||
links.session.sql
|
||||
|
295
Cargo.lock
generated
295
Cargo.lock
generated
@ -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"
|
||||
@ -1126,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"
|
||||
@ -1327,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]]
|
||||
@ -1589,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"
|
||||
@ -1848,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"
|
||||
@ -1957,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"
|
||||
@ -2145,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"
|
||||
@ -2187,6 +2380,8 @@ dependencies = [
|
||||
"chrono",
|
||||
"clap",
|
||||
"dotenv",
|
||||
"fluent-langneg",
|
||||
"fluent-templates",
|
||||
"image",
|
||||
"qrcode",
|
||||
"rand 0.8.3",
|
||||
@ -2566,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"
|
||||
@ -2820,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"
|
||||
@ -2837,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"
|
||||
@ -2941,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"
|
||||
@ -3220,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"
|
||||
@ -3365,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"
|
||||
@ -3398,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"
|
||||
|
@ -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"
|
||||
|
36
locales/de/main.ftl
Normal file
36
locales/de/main.ftl
Normal file
@ -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
|
36
locales/en/main.ftl
Normal file
36
locales/en/main.ftl
Normal file
@ -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
|
4
migrations/20210330103013_addinglanguage.sql
Normal file
4
migrations/20210330103013_addinglanguage.sql
Normal file
@ -0,0 +1,4 @@
|
||||
-- Add migration script here
|
||||
|
||||
ALTER TABLE users
|
||||
ADD COLUMN language Text NOT NULL DEFAULT "en";
|
24
migrations/20210401070030_on_delete_cascade.sql
Normal file
24
migrations/20210401070030_on_delete_cascade.sql
Normal file
@ -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;
|
18
src/main.rs
18
src/main.rs
@ -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;
|
||||
@ -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)
|
||||
|
@ -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 */
|
||||
@ -244,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?;
|
||||
@ -302,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,
|
||||
|
53
src/views.rs
53
src/views.rs
@ -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))
|
||||
}
|
||||
|
@ -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 %}
|
||||
|
Loading…
Reference in New Issue
Block a user