feat: progress on general portal

This commit is contained in:
Max Hohlfeld 2024-01-31 22:07:10 +01:00
parent 6b34a41b7d
commit ba4e4af609
28 changed files with 12593 additions and 213 deletions

2
.env
View File

@ -1,5 +1,5 @@
# Postgres
# DATABASE_URL=postgres://postgres@localhost/my_database
# SQLite
DATABASE_URL=sqlite:brass.db
DATABASE_URL=postgresql://max@localhost/brass

352
Cargo.lock generated
View File

@ -8,7 +8,7 @@ version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617a8268e3537fe1d8c9ead925fca49ef6400927ee7bc26750e90ecee14ce4b8"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"bytes",
"futures-core",
"futures-sink",
@ -19,6 +19,29 @@ dependencies = [
"tracing",
]
[[package]]
name = "actix-files"
version = "0.6.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf0bdd6ff79de7c9a021f5d9ea79ce23e108d8bfc9b49b5b4a2cf6fad5a35212"
dependencies = [
"actix-http",
"actix-service",
"actix-utils",
"actix-web",
"bitflags 2.4.2",
"bytes",
"derive_more",
"futures-core",
"http-range",
"log",
"mime",
"mime_guess",
"percent-encoding",
"pin-project-lite",
"v_htmlescape",
]
[[package]]
name = "actix-http"
version = "3.3.1"
@ -31,7 +54,7 @@ dependencies = [
"actix-utils",
"ahash 0.8.3",
"base64 0.21.2",
"bitflags",
"bitflags 1.3.2",
"brotli",
"bytes",
"bytestring",
@ -304,6 +327,21 @@ dependencies = [
"alloc-no-stdlib",
]
[[package]]
name = "android-tzdata"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0"
[[package]]
name = "android_system_properties"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311"
dependencies = [
"libc",
]
[[package]]
name = "anyhow"
version = "1.0.71"
@ -508,6 +546,12 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e1b586273c5702936fe7b7d6896644d8be71e6314cfe09d3167c95f712589e8"
[[package]]
name = "base64"
version = "0.20.0"
@ -541,6 +585,12 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed570934406eb16438a4e976b1b4500774099c13b8cb96eec99f620f05090ddf"
[[package]]
name = "blake2"
version = "0.10.6"
@ -578,12 +628,14 @@ dependencies = [
name = "brass"
version = "0.1.0"
dependencies = [
"actix-files",
"actix-identity",
"actix-session",
"actix-web",
"anyhow",
"argon2",
"askama",
"chrono",
"dotenv",
"serde",
"sqlx",
@ -652,6 +704,20 @@ version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chrono"
version = "0.4.33"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9f13690e35a5e4ace198e7beea2895d29f3a9cc55015fcebe6336bd2010af9eb"
dependencies = [
"android-tzdata",
"iana-time-zone",
"js-sys",
"num-traits",
"wasm-bindgen",
"windows-targets 0.52.0",
]
[[package]]
name = "cipher"
version = "0.4.4"
@ -695,6 +761,12 @@ dependencies = [
"version_check",
]
[[package]]
name = "core-foundation-sys"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06ea2b9bc92be3c2baa9334a323ebca2d6f074ff852cd1d7b11064035cd3868f"
[[package]]
name = "cpufeatures"
version = "0.2.7"
@ -791,6 +863,26 @@ dependencies = [
"subtle",
]
[[package]]
name = "dirs"
version = "4.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca3aa72a6f96ea37bbc5aa912f6788242832f75369bdfdadcb0e38423f100059"
dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-sys"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1b1d1d91c932ef41c0f2663aa8b0ca0342d444d842c06914aa0a7e352d0bada6"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "dotenv"
version = "0.15.0"
@ -864,18 +956,6 @@ dependencies = [
"miniz_oxide",
]
[[package]]
name = "flume"
version = "0.10.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1657b4441c3403d9f7b3409e47575237dac27b1b5726df654a6ecbf92f0f7577"
dependencies = [
"futures-core",
"futures-sink",
"pin-project",
"spin 0.9.8",
]
[[package]]
name = "fnv"
version = "1.0.7"
@ -907,17 +987,6 @@ version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4bca583b7e26f571124fe5b7561d49cb2868d79116cfa0eefce955557c6fee8c"
[[package]]
name = "futures-executor"
version = "0.3.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ccecee823288125bd88b4d7f565c9e58e41858e47ab72e8ea2d64e93624386e0"
dependencies = [
"futures-core",
"futures-task",
"futures-util",
]
[[package]]
name = "futures-intrusive"
version = "0.4.2"
@ -984,7 +1053,6 @@ dependencies = [
"futures-task",
"pin-project-lite",
"pin-utils",
"slab",
]
[[package]]
@ -1132,6 +1200,12 @@ dependencies = [
"itoa",
]
[[package]]
name = "http-range"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "21dec9db110f5f872ed9699c3ecf50cf16f423502706ba5c72462e28d3157573"
[[package]]
name = "httparse"
version = "1.8.0"
@ -1153,6 +1227,29 @@ dependencies = [
"libm",
]
[[package]]
name = "iana-time-zone"
version = "0.1.59"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6a67363e2aa4443928ce15e57ebae94fd8949958fd1223c4cfc0cd473ad7539"
dependencies = [
"android_system_properties",
"core-foundation-sys",
"iana-time-zone-haiku",
"js-sys",
"wasm-bindgen",
"windows-core",
]
[[package]]
name = "iana-time-zone-haiku"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f"
dependencies = [
"cc",
]
[[package]]
name = "idna"
version = "0.4.0"
@ -1263,14 +1360,14 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4"
[[package]]
name = "libsqlite3-sys"
version = "0.24.2"
name = "libredox"
version = "0.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "898745e570c7d0453cc1fbc4a701eb6c662ed54e8fec8b7d14be137ebeeb9d14"
checksum = "85c833ca1e66078851dba29046874e38f08b2c883700aa29a03ddd3b23814ee8"
dependencies = [
"cc",
"pkg-config",
"vcpkg",
"bitflags 2.4.2",
"libc",
"redox_syscall 0.4.1",
]
[[package]]
@ -1316,6 +1413,16 @@ dependencies = [
"value-bag",
]
[[package]]
name = "md-5"
version = "0.10.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
dependencies = [
"cfg-if",
"digest",
]
[[package]]
name = "memchr"
version = "2.5.0"
@ -1457,7 +1564,7 @@ dependencies = [
"libc",
"redox_syscall 0.3.5",
"smallvec",
"windows-targets",
"windows-targets 0.48.0",
]
[[package]]
@ -1483,26 +1590,6 @@ version = "2.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b2a4787296e9989611394c33f193f676704af1686e70b8f8033ab5ba9a35a94"
[[package]]
name = "pin-project"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c95a7476719eab1e366eaf73d0260af3021184f18177925b07f54b30089ceead"
dependencies = [
"pin-project-internal",
]
[[package]]
name = "pin-project-internal"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39407670928234ebc5e6e580247dd567ad73a3578460c5990f9503df207e8f07"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.18",
]
[[package]]
name = "pin-project-lite"
version = "0.2.9"
@ -1528,7 +1615,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4b2d323e8ca7996b3e23126511a523f7e62924d93ecd5ae73b333815b0eb3dce"
dependencies = [
"autocfg",
"bitflags",
"bitflags 1.3.2",
"cfg-if",
"concurrent-queue",
"libc",
@ -1609,7 +1696,7 @@ version = "0.2.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb5a58c1855b4b6819d59012155603f0b22ad30cad752600aadfcb695265519a"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
@ -1618,7 +1705,27 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [
"bitflags",
"bitflags 1.3.2",
]
[[package]]
name = "redox_syscall"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4722d768eff46b75989dd134e5c353f0d6296e5aaa3132e776cbdb56be7731aa"
dependencies = [
"bitflags 1.3.2",
]
[[package]]
name = "redox_users"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a18479200779601e498ada4e8c1e1f50e3ee19deb0259c25825a98b5603b2cb4"
dependencies = [
"getrandom",
"libredox",
"thiserror",
]
[[package]]
@ -1647,7 +1754,7 @@ dependencies = [
"cc",
"libc",
"once_cell",
"spin 0.5.2",
"spin",
"untrusted",
"web-sys",
"winapi",
@ -1668,7 +1775,7 @@ version = "0.37.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "acf8729d8542766f1b2cf77eb034d52f40d375bb8b615d0b147089946e16613d"
dependencies = [
"bitflags",
"bitflags 1.3.2",
"errno",
"io-lifetimes",
"libc",
@ -1840,15 +1947,6 @@ version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6e63cff320ae2c57904679ba7cb63280a3dc4613885beafb148ee7bf9aa9042d"
[[package]]
name = "spin"
version = "0.9.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
dependencies = [
"lock_api",
]
[[package]]
name = "sqlformat"
version = "0.2.1"
@ -1878,33 +1976,40 @@ checksum = "fa8241483a83a3f33aa5fff7e7d9def398ff9990b2752b6c6112b83c6d246029"
dependencies = [
"ahash 0.7.6",
"atoi",
"bitflags",
"base64 0.13.1",
"bitflags 1.3.2",
"byteorder",
"bytes",
"chrono",
"crc",
"crossbeam-queue",
"dirs",
"dotenvy",
"either",
"event-listener",
"flume",
"futures-channel",
"futures-core",
"futures-executor",
"futures-intrusive",
"futures-util",
"hashlink",
"hex",
"hkdf",
"hmac",
"indexmap",
"itoa",
"libc",
"libsqlite3-sys",
"log",
"md-5",
"memchr",
"once_cell",
"paste",
"percent-encoding",
"rand",
"rustls",
"rustls-pemfile",
"serde",
"serde_json",
"sha1",
"sha2",
"smallvec",
"sqlformat",
@ -1913,6 +2018,7 @@ dependencies = [
"thiserror",
"url",
"webpki-roots",
"whoami",
]
[[package]]
@ -2171,18 +2277,18 @@ dependencies = [
"percent-encoding",
]
[[package]]
name = "v_htmlescape"
version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4e8257fbc510f0a46eb602c10215901938b5c2a7d5e70fc11483b1d3c9b5b18c"
[[package]]
name = "value-bag"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4d330786735ea358f3bc09eea4caa098569c1c93f342d9aca0514915022fe7e"
[[package]]
name = "vcpkg"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
[[package]]
name = "version_check"
version = "0.9.4"
@ -2296,6 +2402,16 @@ dependencies = [
"webpki",
]
[[package]]
name = "whoami"
version = "1.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22fc3756b8a9133049b26c7f61ab35416c130e8c09b660f5b3958b446f52cc50"
dependencies = [
"wasm-bindgen",
"web-sys",
]
[[package]]
name = "winapi"
version = "0.3.9"
@ -2318,13 +2434,22 @@ version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-core"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9"
dependencies = [
"windows-targets 0.52.0",
]
[[package]]
name = "windows-sys"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
dependencies = [
"windows-targets",
"windows-targets 0.48.0",
]
[[package]]
@ -2333,13 +2458,28 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7b1eb6f0cd7c80c79759c929114ef071b87354ce476d9d94271031c0497adfd5"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
"windows_aarch64_gnullvm 0.48.0",
"windows_aarch64_msvc 0.48.0",
"windows_i686_gnu 0.48.0",
"windows_i686_msvc 0.48.0",
"windows_x86_64_gnu 0.48.0",
"windows_x86_64_gnullvm 0.48.0",
"windows_x86_64_msvc 0.48.0",
]
[[package]]
name = "windows-targets"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8a18201040b24831fbb9e4eb208f8892e1f50a37feb53cc7ff887feb8f50e7cd"
dependencies = [
"windows_aarch64_gnullvm 0.52.0",
"windows_aarch64_msvc 0.52.0",
"windows_i686_gnu 0.52.0",
"windows_i686_msvc 0.52.0",
"windows_x86_64_gnu 0.52.0",
"windows_x86_64_gnullvm 0.52.0",
"windows_x86_64_msvc 0.52.0",
]
[[package]]
@ -2348,42 +2488,84 @@ version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "91ae572e1b79dba883e0d315474df7305d12f569b400fcf90581b06062f7e1bc"
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb7764e35d4db8a7921e09562a0304bf2f93e0a51bfccee0bd0bb0b666b015ea"
[[package]]
name = "windows_aarch64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b2ef27e0d7bdfcfc7b868b317c1d32c641a6fe4629c171b8928c7b08d98d7cf3"
[[package]]
name = "windows_aarch64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bbaa0368d4f1d2aaefc55b6fcfee13f41544ddf36801e793edbbfd7d7df075ef"
[[package]]
name = "windows_i686_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "622a1962a7db830d6fd0a69683c80a18fda201879f0f447f065a3b7467daa241"
[[package]]
name = "windows_i686_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a28637cb1fa3560a16915793afb20081aba2c92ee8af57b4d5f28e4b3e7df313"
[[package]]
name = "windows_i686_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4542c6e364ce21bf45d69fdd2a8e455fa38d316158cfd43b3ac1c5b1b19f8e00"
[[package]]
name = "windows_i686_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffe5e8e31046ce6230cc7215707b816e339ff4d4d67c65dffa206fd0f7aa7b9a"
[[package]]
name = "windows_x86_64_gnu"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ca2b8a661f7628cbd23440e50b05d705db3686f894fc9580820623656af974b1"
[[package]]
name = "windows_x86_64_gnu"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d6fa32db2bc4a2f5abeacf2b69f7992cd09dca97498da74a151a3132c26befd"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7896dbc1f41e08872e9d5e8f8baa8fdd2677f29468c4e156210174edc7f7b953"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a657e1e9d3f514745a572a6846d3c7aa7dbe1658c056ed9c3344c4109a6949e"
[[package]]
name = "windows_x86_64_msvc"
version = "0.48.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1a515f5799fe4961cb532f983ce2b23082366b898e52ffbce459c86f67c8378a"
[[package]]
name = "windows_x86_64_msvc"
version = "0.52.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dff9641d1cd4be8d1a070daf9e3773c5f67e78b4d9d42263020c057706765c04"
[[package]]
name = "zstd"
version = "0.12.3+zstd.1.5.2"

View File

@ -6,7 +6,7 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
sqlx = { version = "0.6", features = [ "runtime-async-std-rustls", "sqlite" ] }
sqlx = { version = "0.6", features = ["runtime-async-std-rustls", "postgres", "chrono"] }
actix-web = { version = "4" }
askama = "0.12.0"
serde = { version = "1.0.164", features = [ "derive"]}
@ -15,3 +15,5 @@ anyhow = "1.0.71"
dotenv = "0.15.0"
actix-session = { version = "0.7.2", features = ["cookie-session"] }
actix-identity = "0.5.2"
chrono = "0.4.33"
actix-files = "0.6.5"

View File

@ -1,18 +1,79 @@
CREATE TABLE IF NOT EXISTS roles
CREATE TYPE role AS ENUM ('staff', 'area manager', 'admin');
CREATE TYPE function AS ENUM ('posten', 'wachhabender');
CREATE TABLE area
(
id INTEGER PRIMARY KEY NOT NULL,
definition TEXT NOT NULL
id SERIAL PRIMARY KEY,
name TEXT NOT NULL
);
INSERT OR REPLACE INTO roles(id, definition) values(1, "Default");
INSERT OR REPLACE INTO roles(id, definition) values(2, "Administrator");
INSERT INTO area (name) VALUES ('Leipzig Ost');
CREATE TABLE IF NOT EXISTS users
CREATE TABLE location
(
id INTEGER PRIMARY KEY NOT NULL,
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
password_hash TEXT NOT NULL,
salt TEXT NOT NULL,
role_id INTEGER NOT NULL,
FOREIGN KEY(role_id) REFERENCES roles(id)
areaId INTEGER NOT NULL REFERENCES area (id)
);
CREATE TABLE user_
(
id SERIAL PRIMARY KEY,
name TEXT NOT NULL,
email TEXT NOT NULL,
password TEXT NOT NULL,
salt TEXT NOT NULL,
role role NOT NULL,
function function NOT NULL,
areaId INTEGER NOT NULL REFERENCES area (id),
locked BOOLEAN NOT NULL DEFAULT false,
lastLogin TIMESTAMP WITH TIME ZONE,
receiveNotifications BOOLEAN NOT NULL DEFAULT true
);
CREATE TABLE availabillity
(
id SERIAL PRIMARY KEY,
userId INTEGER NOT NULL REFERENCES user_ (id),
date DATE NOT NULL,
startTime TIME,
endTime TIME
);
CREATE TABLE event
(
id SERIAL PRIMARY KEY,
date DATE NOT NULL,
startTime TIME NOT NULL,
endTime TIME NOT NULL,
name TEXT NOT NULL,
locationId INTEGER NOT NULL REFERENCES location (id),
voluntaryWachhabender BOOLEAN NOT NULL,
amountOfPosten SMALLINT NOT NULL CHECK (amountOfPosten >= 0),
clothing TEXT NOT NULL,
canceled BOOLEAN NOT NULL DEFAULT false
);
CREATE TABLE assignement
(
eventId INTEGER REFERENCES event (id),
availabillityId INTEGER REFERENCES availabillity (id),
function function NOT NULL,
startTime TIME,
endTime TIME,
PRIMARY KEY (eventId, availabillityId)
);
CREATE TABLE vehicle
(
id SERIAL PRIMARY KEY,
radioCallName TEXT NOT NULL,
station TEXT NOT NULL
);
CREATE TABLE vehicleassignement
(
eventId INTEGER REFERENCES event (id),
vehicleId INTEGER REFERENCES vehicle (id),
PRIMARY KEY (eventId, vehicleId)
);

View File

@ -1,4 +1,4 @@
pub mod routes;
mod utils;
pub mod utils;
pub use routes::init;

View File

@ -1,23 +1,18 @@
use actix_web::{web, HttpResponse, Responder};
use askama::Template;
use sqlx::SqlitePool;
use crate::models::Role;
use sqlx::PgPool;
#[derive(Template)]
#[template(path = "register.html")]
struct RegisterTemplate {
roles: Vec<Role>,
// roles: Vec<Role>,
}
#[actix_web::get("/register")]
async fn route(pool: web::Data<SqlitePool>) -> impl Responder {
let roles = match Role::GetAll(pool.get_ref()).await {
Ok(value) => value,
Err(error) => return HttpResponse::InternalServerError().body(error.to_string()),
};
async fn route(pool: web::Data<PgPool>) -> impl Responder {
let bla = RegisterTemplate { roles };
let bla = RegisterTemplate { };
HttpResponse::Ok().body(bla.render().unwrap())
}

View File

@ -1,13 +1,13 @@
use actix_identity::Identity;
use actix_web::{web, HttpMessage, HttpRequest, HttpResponse, Responder};
use serde::Deserialize;
use sqlx::SqlitePool;
use sqlx::PgPool;
use crate::{auth::utils::hash_plain_password_with_salt, models::user::User};
#[derive(Deserialize)]
struct LoginForm {
name: String,
email: String,
password: String,
}
@ -15,20 +15,23 @@ struct LoginForm {
async fn route(
web::Form(form): web::Form<LoginForm>,
request: HttpRequest,
pool: web::Data<SqlitePool>,
pool: web::Data<PgPool>,
) -> impl Responder {
match User::read_by_name(pool.get_ref(), &form.name).await {
Some(potential_user) => {
let hash = hash_plain_password_with_salt(&form.password, &potential_user.salt).unwrap();
if hash == potential_user.password_hash {
Identity::login(&request.extensions(), potential_user.id.to_string());
if let Ok(result) = User::read_for_login(pool.get_ref(), &form.email).await {
if let Some(user) = result {
let hash = hash_plain_password_with_salt(&form.password, &user.salt).unwrap();
if hash == user.password {
Identity::login(&request.extensions(), user.id.to_string());
return HttpResponse::Ok().body("Angemeldet!");
} else {
return HttpResponse::Unauthorized().body("Nutzername oder Passwort falsch.");
}
} else {
return HttpResponse::Unauthorized().body("Nutzername oder Passwort falsch.");
}
None => return HttpResponse::Unauthorized().body("Nutzername oder Passwort falsch."),
} else {
return HttpResponse::Unauthorized().body("Nutzername oder Passwort falsch.");
}
}

View File

@ -1,24 +1,47 @@
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use serde::Deserialize;
use sqlx::SqlitePool;
use sqlx::PgPool;
use crate::{auth::utils::generate_salt_and_hash_plain_password, models::user::User};
#[derive(Deserialize)]
struct RegisterForm {
name: String,
password: String,
role: i64,
pub name: String,
pub email: String,
pub password: String,
pub role_id: u8,
pub function_id: u8,
pub area_id: i32,
}
#[actix_web::post("/register")]
async fn route(
web::Form(form): web::Form<RegisterForm>,
pool: web::Data<SqlitePool>,
pool: web::Data<PgPool>,
) -> impl Responder {
let (hash, salt) = generate_salt_and_hash_plain_password(&form.password).unwrap();
let result = User::create(pool.get_ref(), form.name, hash, salt, form.role).await;
let role = match form.role_id.try_into() {
Ok(role) => role,
Err(_) => return HttpResponse::BadRequest().body("fsdf"),
};
let function = match form.function_id.try_into() {
Ok(function) => function,
Err(_) => return HttpResponse::BadRequest().body("fsdf"),
};
let result = User::create(
pool.get_ref(),
form.name,
form.email,
hash,
salt,
role,
function,
form.area_id,
)
.await;
match result {
Ok(_) => {

View File

@ -1,12 +1,31 @@
use actix_identity::Identity;
use actix_web::{Responder, web, HttpResponse};
use actix_web::{http::header::LOCATION, web, HttpResponse, Responder};
use askama::Template;
use sqlx::PgPool;
use crate::models::{role::Role, user::User};
pub fn init(cfg: &mut web::ServiceConfig) {
cfg.service(get_login);
cfg.service(get_index);
}
#[derive(Template)]
#[template(path = "index.html")]
struct CalendarTemplate {
user_role: Role,
}
#[actix_web::get("/")]
async fn get_login(user: Identity) -> impl Responder {
async fn get_index(user: Option<Identity>, pool: web::Data<PgPool>) -> impl Responder {
if let Some(user) = user {
let current_user = User::read_by_id(pool.get_ref(), user.id().unwrap().parse().unwrap()).await.unwrap();
HttpResponse::Ok().body("Hierfür muss man angemeldet sein!")
let template = CalendarTemplate{ user_role: current_user.role };
HttpResponse::Ok().body(template.render().unwrap())
} else {
HttpResponse::PermanentRedirect()
.insert_header((LOCATION, "/login"))
.finish()
}
}

View File

@ -5,7 +5,7 @@ use actix_session::{storage::CookieSessionStore, SessionMiddleware};
use actix_web::cookie::Key;
use actix_web::{web, App, HttpServer};
use dotenv::dotenv;
use sqlx::sqlite::SqlitePool;
use sqlx::postgres::PgPool;
mod auth;
mod calendar;
@ -14,7 +14,7 @@ mod models;
#[actix_web::main]
async fn main() -> anyhow::Result<()> {
dotenv()?;
let pool = SqlitePool::connect(&env::var("DATABASE_URL")?).await?;
let pool = PgPool::connect(&env::var("DATABASE_URL")?).await?;
let secret_key = Key::generate();
println!("Starting server on http://localhost:8080.");
@ -29,6 +29,7 @@ async fn main() -> anyhow::Result<()> {
CookieSessionStore::default(),
secret_key.clone(),
))
.service(actix_files::Files::new("", "./static").show_files_listing())
})
.bind(("127.0.0.1", 8080))?
.run()

26
src/models/area.rs Normal file
View File

@ -0,0 +1,26 @@
use sqlx::{query, query_as, PgPool};
pub struct Area {
pub id: i32,
pub name: String
}
impl Area {
pub async fn create(pool: &PgPool, name: &str) -> anyhow::Result<i32> {
let result = query!("INSERT INTO area (name) VALUES ($1) RETURNING id;", name).fetch_one(pool).await?;
Ok(result.id)
}
pub async fn read_by_id(pool: &PgPool, id: i32) -> anyhow::Result<Option<Area>> {
let record = query_as!(Area, "SELECT * FROM area WHERE id = $1", id).fetch_optional(pool).await?;
Ok(record)
}
pub async fn read_all(pool: &PgPool) -> anyhow::Result<Vec<Area>> {
let records = query_as!(Area, "SELECT * FROM area").fetch_all(pool).await?;
Ok(records)
}
}

View File

View File

57
src/models/event.rs Normal file
View File

@ -0,0 +1,57 @@
use chrono::{NaiveDate, NaiveTime};
use sqlx::{query, query_as, PgPool};
pub struct Event {
pub id: i32,
pub date: NaiveDate,
pub start_time: NaiveTime,
pub end_time: NaiveTime,
pub name: String,
pub location_id: i32,
pub voluntary_wachhabender: bool,
pub amount_of_posten: i16,
pub clothing: String,
pub canceled: bool,
}
impl Event {
pub async fn create(
pool: &PgPool,
date: NaiveDate,
start_time: NaiveTime,
end_time: NaiveTime,
name: String,
location_id: i32,
voluntary_wachhabender: bool,
amount_of_posten: i16,
clothing: String,
) -> anyhow::Result<i32> {
let result = query!("INSERT INTO event (date, startTime, endTime, name, locationId, voluntaryWachhabender, amountOfPosten, clothing) VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id;", date, start_time, end_time, name, location_id, voluntary_wachhabender, amount_of_posten, clothing).fetch_one(pool).await?;
Ok(result.id)
}
pub async fn read_by_date(pool: &PgPool, date: NaiveDate) -> anyhow::Result<Vec<Event>> {
let records = query!("SELECT * FROM event WHERE date = $1;", date)
.fetch_all(pool)
.await?;
let events = records
.iter()
.map(|record| Event {
id: record.id,
date: record.date,
start_time: record.starttime,
end_time: record.endtime,
name: record.name.to_string(),
location_id: record.locationid,
voluntary_wachhabender: record.voluntarywachhabender,
amount_of_posten: record.amountofposten,
clothing: record.clothing.to_string(),
canceled: record.canceled,
})
.collect();
Ok(events)
}
}

18
src/models/function.rs Normal file
View File

@ -0,0 +1,18 @@
#[derive(sqlx::Type, Debug)]
#[sqlx(type_name = "function", rename_all = "lowercase")]
pub enum Function {
Posten = 1,
Wachhabender = 10,
}
impl TryFrom<u8> for Function {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Function::Posten),
10 => Ok(Function::Wachhabender),
_ => Err(()),
}
}
}

9
src/models/location.rs Normal file
View File

@ -0,0 +1,9 @@
pub struct Location {
pub id: i32,
pub name: String,
pub areaId: i32
}
impl Location {
}

View File

@ -1,34 +1,9 @@
pub mod role;
pub mod function;
pub mod user;
use sqlx::sqlite::SqlitePool;
pub struct Role {
pub id: u8,
pub definition: String
}
impl Role {
pub async fn GetAll(pool: &SqlitePool) -> anyhow::Result<Vec<Role>> {
let records = sqlx::query!(
r#"
SELECT id, definition
FROM roles
ORDER BY id
"#
)
.fetch_all(pool)
.await?;
let mut result = Vec::new();
for record in records {
let role = Role {
id: record.id as u8,
definition: record.definition,
};
result.push(role);
}
Ok(result)
}
}
pub mod event;
pub mod area;
pub mod vehicle;
pub mod availabillity;
pub mod assignement;
pub mod location;

20
src/models/role.rs Normal file
View File

@ -0,0 +1,20 @@
#[derive(sqlx::Type, Debug)]
#[sqlx(type_name = "role", rename_all = "lowercase")]
pub enum Role {
Staff = 1,
AreaManager = 10,
Admin = 100,
}
impl TryFrom<u8> for Role {
type Error = ();
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
1 => Ok(Role::Staff),
10 => Ok(Role::AreaManager),
100 => Ok(Role::Admin),
_ => Err(()),
}
}
}

View File

@ -1,47 +1,72 @@
use sqlx::sqlite::SqlitePool;
use chrono::{DateTime, Utc};
use sqlx::PgPool;
use super::{function::Function, role::Role};
pub struct User {
pub id: i64,
pub id: i32,
pub name: String,
pub password_hash: String,
pub email: String,
pub password: String,
pub salt: String,
pub role_id: i64,
pub role: Role,
pub function: Function,
pub area_id: i32,
pub locked: bool,
pub last_login: Option<DateTime<Utc>>,
pub receive_notifications: bool,
}
impl User {
pub async fn create(
pool: &SqlitePool,
pool: &PgPool,
name: String,
password_hash: String,
email: String,
password: String,
salt: String,
role_id: i64,
) -> anyhow::Result<i64> {
role: Role,
function: Function,
area_id: i32,
) -> anyhow::Result<i32> {
let created = sqlx::query!(
r#"
INSERT INTO users (name, password_hash, salt, role_id)
VALUES (?1, ?2, ?3, ?4);
INSERT INTO user_ (name, email, password, salt, role, function, areaId)
VALUES ($1, $2, $3, $4, $5, $6, $7)
RETURNING id;
"#,
name,
password_hash,
email,
password,
salt,
role_id
role as Role,
function as Function,
area_id
)
.execute(pool)
.fetch_one(pool)
.await;
match created {
Ok(result) => Ok(result.last_insert_rowid()),
Ok(result) => Ok(result.id),
Err(err) => Err(err.into()),
}
}
pub async fn read_by_id(pool: &SqlitePool, id: i64) -> Option<User> {
let record = sqlx::query_as!(
User,
pub async fn read_by_id(pool: &PgPool, id: i32) -> Option<User> {
let record = sqlx::query!(
r#"
SELECT *
FROM users
WHERE id = ?1;
SELECT id,
name,
email,
password,
salt,
role AS "role: Role",
function AS "function: Function",
areaId,
locked,
lastLogin,
receiveNotifications
FROM user_
WHERE id = $1;
"#,
id,
)
@ -49,7 +74,19 @@ impl User {
.await;
match record {
Ok(record) => Some(record),
Ok(record) => Some(User {
id: record.id,
name: record.name,
email: record.email,
password: record.password,
salt: record.salt,
role: record.role,
function: record.function,
area_id: record.areaid,
locked: record.locked,
last_login: record.lastlogin,
receive_notifications: record.receivenotifications,
}),
Err(err) => {
println!("User.read({id}): {err}");
None
@ -57,25 +94,57 @@ impl User {
}
}
pub async fn read_by_name(pool: &SqlitePool, name: &str) -> Option<User> {
let record = sqlx::query_as!(
User,
pub async fn read_for_login(pool: &PgPool, email: &str) -> anyhow::Result<Option<User>> {
let record = sqlx::query!(
r#"
SELECT *
FROM users
WHERE name = ?1;
SELECT id,
name,
email,
password,
salt,
role AS "role: Role",
function AS "function: Function",
areaId,
locked,
lastLogin,
receiveNotifications
FROM user_
WHERE email = $1;
"#,
name
email,
)
.fetch_one(pool)
.await;
.fetch_optional(pool)
.await?;
match record {
Ok(record) => Some(record),
Err(err) => {
println!("User.read({name}): {err}");
None
}
}
let result = match record {
Some(record) => Some(User {
id: record.id,
name: record.name,
email: record.email,
password: record.password,
salt: record.salt,
role: record.role,
function: record.function,
area_id: record.areaid,
locked: record.locked,
last_login: record.lastlogin,
receive_notifications: record.receivenotifications,
}),
None => None
};
Ok(result)
}
pub async fn read_all(pool: &PgPool) -> Option<Vec<User>> {
todo!()
}
pub async fn update(pool: &PgPool, id: i32, updated_user: User) -> Option<User> {
todo!()
}
pub async fn delete(pool: &PgPool, id: i32) -> anyhow::Result<bool> {
todo!()
}
}

0
src/models/vehicle.rs Normal file
View File

11851
static/bulma.css vendored Normal file

File diff suppressed because it is too large Load Diff

1
static/bulma.css.map Normal file

File diff suppressed because one or more lines are too long

1
static/bulma.min.css vendored Normal file

File diff suppressed because one or more lines are too long

16
templates/base.html Normal file
View File

@ -0,0 +1,16 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Brass - Brasiwa Leipzig</title>
<link rel="stylesheet" href="bulma.css">
</head>
<body>
{% block body %}
{% endblock %}
</body>
</html>

7
templates/index.html Normal file
View File

@ -0,0 +1,7 @@
{% extends "nav.html" %}
{% block content %}
<p>
Content
</p>
{% endblock %}

View File

@ -1,18 +1,15 @@
<html>
<head>
<title>Brass - Login</title>
</head>
<body>
{% extends "base.html" %}
{% block body %}
<h1>Brass - Anmeldung</h1>
<p>Gib dein Nutzernamen und das Passwort ein:</p>
<form>
<label for="name">Nutzername:</label>
<input name="name" type="text">
<label for="email">E-Mail:</label>
<input name="email" type="text">
<label for="password">Passwort:</label>
<input name="password" type="password">
<input type="submit" formmethod="post">
</form>
</body>
</html>
{% endblock %}

47
templates/nav.html Normal file
View File

@ -0,0 +1,47 @@
{% extends "base.html" %}
{% block body %}
<nav class="navbar" role="navigation" aria-label="main navigation">
<div class="navbar-brand">
<a class="navbar-item" href="/">
BRASS
</a>
<a role="button" class="navbar-burger" aria-label="menu" aria-expanded="false" data-target="navbarBasicExample">
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
<span aria-hidden="true"></span>
</a>
</div>
<div class="navbar-menu">
<div class="navbar-start">
<a class="navbar-item">
Kalender
</a>
{% match user_role %}
{% when AreaManager %}
<a class="navbar-item">
Planung
</a>
{% endmatch %}
</div>
<div class="navbar-end">
<div class="navbar-item">
<div class="buttons">
<a class="button is-primary">
Profil
</a>
<a class="button is-light">
Abmelden
</a>
</div>
</div>
</div>
</div>
</nav>
{% block content %}
{% endblock %}
{% endblock %}

View File

@ -14,9 +14,9 @@
<label for="role">Rolle:</label>
<select name="role">
{% for role in roles %}
<option value="{{ role.id }}">{{ role.definition }}</option>
{% endfor %}
<option value="1">Staff</option>
<option value="10">AreaManager</option>
<option value="100">Admin</option>
</select>
<input type="submit" formmethod="post">