diff --git a/backend/Cargo.lock b/backend/Cargo.lock index 57cf057..d71ffe8 100644 --- a/backend/Cargo.lock +++ b/backend/Cargo.lock @@ -245,6 +245,12 @@ dependencies = [ "libc", ] +[[package]] +name = "arrayvec" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8da52d66c7071e2e3fa2a1e5c6d088fec47b593032b254f5e980de8ea54454d6" + [[package]] name = "async-trait" version = "0.1.68" @@ -271,8 +277,13 @@ dependencies = [ "chrono", "dotenv", "dotenv_codegen", + "nanoid", + "pbkdf2", + "rust_decimal", + "rust_decimal_macros", "serde", "serde_json", + "sha2", "tokio", "tokio-postgres", ] @@ -289,6 +300,18 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitvec" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bc2832c24239b0141d5674bb9174f9d68a8b5b3f2753311927c172ca46f7e9c" +dependencies = [ + "funty", + "radium", + "tap", + "wyz", +] + [[package]] name = "block-buffer" version = "0.10.4" @@ -298,6 +321,51 @@ dependencies = [ "generic-array", ] +[[package]] +name = "borsh" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4114279215a005bc675e386011e594e1d9b800918cea18fcadadcce864a2046b" +dependencies = [ + "borsh-derive", + "hashbrown", +] + +[[package]] +name = "borsh-derive" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0754613691538d51f329cce9af41d7b7ca150bc973056f1156611489475f54f7" +dependencies = [ + "borsh-derive-internal", + "borsh-schema-derive-internal", + "proc-macro-crate", + "proc-macro2", + "syn 1.0.109", +] + +[[package]] +name = "borsh-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "afb438156919598d2c7bad7e1c0adf3d26ed3840dbc010db1a882a65583ca2fb" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "borsh-schema-derive-internal" +version = "0.10.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "634205cc43f74a1b9046ef87c4540ebda95696ec0f315024860cad7c5b0f5ccd" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "brotli" version = "3.3.4" @@ -325,6 +393,28 @@ version = "3.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d261e256854913907f67ed06efbc3338dfe6179796deefc1ff763fc1aee5535" +[[package]] +name = "bytecheck" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b6372023ac861f6e6dc89c8344a8f398fb42aaba2b5dbc649ca0c0e9dbcb627" +dependencies = [ + "bytecheck_derive", + "ptr_meta", + "simdutf8", +] + +[[package]] +name = "bytecheck_derive" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7ec4c6f261935ad534c0c22dbef2201b45918860eb1c574b972bd213a76af61" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "byteorder" version = "1.4.3" @@ -575,6 +665,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "funty" +version = "2.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6d5a32815ae3f33302d95fdcb2ce17862f8c65363dcfd29360480ba1001fc9c" + [[package]] name = "futures-channel" version = "0.3.28" @@ -674,6 +770,9 @@ name = "hashbrown" version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +dependencies = [ + "ahash 0.7.6", +] [[package]] name = "hermit-abi" @@ -884,6 +983,15 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "nanoid" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ffa00dec017b5b1a8b7cf5e2c008bfda1aa7e0697ac1508b491fdf2622fb4d8" +dependencies = [ + "rand", +] + [[package]] name = "num-integer" version = "0.1.45" @@ -948,6 +1056,16 @@ version = "1.0.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f746c4065a8fa3fe23974dd82f15431cc8d40779821001404d10d2e79ca7d79" +[[package]] +name = "pbkdf2" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0ca0b5a68607598bf3bad68f32227a8164f6254833f84eafaac409cd6746c31" +dependencies = [ + "digest", + "hmac", +] + [[package]] name = "percent-encoding" version = "2.2.0" @@ -990,6 +1108,20 @@ version = "0.3.26" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6ac9a59f73473f1b8d852421e59e64809f025994837ef743615c6d0c5b305160" +[[package]] +name = "postgres" +version = "0.19.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bed5017bc2ff49649c0075d0d7a9d676933c1292480c1d137776fb205b5cd18" +dependencies = [ + "bytes", + "fallible-iterator", + "futures-util", + "log", + "tokio", + "tokio-postgres", +] + [[package]] name = "postgres-protocol" version = "0.6.5" @@ -1026,6 +1158,15 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de" +[[package]] +name = "proc-macro-crate" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d6ea3c4595b96363c13943497db34af4460fb474a95c43f4446ad341b8c9785" +dependencies = [ + "toml", +] + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -1041,6 +1182,26 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "ptr_meta" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0738ccf7ea06b608c10564b31debd4f5bc5e197fc8bfe088f68ae5ce81e7a4f1" +dependencies = [ + "ptr_meta_derive", +] + +[[package]] +name = "ptr_meta_derive" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16b845dbfca988fa33db069c0e230574d15a3088f147a87b64c7589eb662c9ac" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + [[package]] name = "quote" version = "1.0.26" @@ -1050,6 +1211,12 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "radium" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" + [[package]] name = "rand" version = "0.8.5" @@ -1106,6 +1273,73 @@ version = "0.6.29" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" +[[package]] +name = "rend" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "581008d2099240d37fb08d77ad713bcaec2c4d89d50b5b21a8bb1996bbab68ab" +dependencies = [ + "bytecheck", +] + +[[package]] +name = "rkyv" +version = "0.7.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0200c8230b013893c0b2d6213d6ec64ed2b9be2e0e016682b7224ff82cff5c58" +dependencies = [ + "bitvec", + "bytecheck", + "hashbrown", + "ptr_meta", + "rend", + "rkyv_derive", + "seahash", + "tinyvec", + "uuid", +] + +[[package]] +name = "rkyv_derive" +version = "0.7.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2e06b915b5c230a17d7a736d1e2e63ee753c256a8614ef3f5147b13a4f5541d" +dependencies = [ + "proc-macro2", + "quote", + "syn 1.0.109", +] + +[[package]] +name = "rust_decimal" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26bd36b60561ee1fb5ec2817f198b6fd09fa571c897a5e86d1487cfc2b096dfc" +dependencies = [ + "arrayvec", + "borsh", + "bytecheck", + "byteorder", + "bytes", + "num-traits", + "postgres", + "rand", + "rkyv", + "serde", + "serde_json", + "tokio-postgres", +] + +[[package]] +name = "rust_decimal_macros" +version = "1.29.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0e773fd3da1ed42472fdf3cfdb4972948a555bc3d73f5e0bdb99d17e7b54c687" +dependencies = [ + "quote", + "rust_decimal", +] + [[package]] name = "rustc_version" version = "0.4.0" @@ -1133,6 +1367,12 @@ version = "1.0.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1792db035ce95be60c3f8853017b3999209281c24e2ba5bc8e59bf97a0c590c1" +[[package]] +name = "seahash" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c107b6f4780854c8b126e228ea8869f4d7b71260f962fefb57b996b8959ba6b" + [[package]] name = "semver" version = "1.0.17" @@ -1213,6 +1453,12 @@ dependencies = [ "libc", ] +[[package]] +name = "simdutf8" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27f6278552951f1f2b8cf9da965d10969b2efdea95a6ec47987ab46edfe263a" + [[package]] name = "siphasher" version = "0.3.10" @@ -1292,6 +1538,12 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tap" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55937e1799185b12863d447f42597ed69d9928686b8d88a1df17376a097d8369" + [[package]] name = "termcolor" version = "1.2.0" @@ -1422,6 +1674,15 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml" +version = "0.5.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f4f7f0dd8d50a853a531c426359045b1998f04219d88799810762cd4ad314234" +dependencies = [ + "serde", +] + [[package]] name = "tracing" version = "0.1.37" @@ -1487,6 +1748,12 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "uuid" +version = "1.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dad5567ad0cf5b760e5665964bec1b47dfd077ba8a2544b513f3556d3d239a2" + [[package]] name = "version_check" version = "0.9.4" @@ -1665,6 +1932,15 @@ version = "0.42.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" +[[package]] +name = "wyz" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05f360fc0b24296329c78fda852a1e9ae82de9cf7b27dae4b7f62f118f77b9ed" +dependencies = [ + "tap", +] + [[package]] name = "zstd" version = "0.12.3+zstd.1.5.2" diff --git a/backend/Cargo.toml b/backend/Cargo.toml index 3f2713e..6ac8bec 100644 --- a/backend/Cargo.toml +++ b/backend/Cargo.toml @@ -14,4 +14,9 @@ chrono = { version = "0.4.24", features = ["serde"] } serde_json = "1.0" serde = { version = "1.0.159", features = ["derive"] } async-trait = "0.1.68" -dotenv_codegen = "0.15.0" \ No newline at end of file +dotenv_codegen = "0.15.0" +pbkdf2 = "0.12.1" +nanoid = "0.4.0" +sha2 = "0.10.6" +rust_decimal = { version = "1.29", features = ["db-tokio-postgres"] } +rust_decimal_macros = "1.29" \ No newline at end of file diff --git a/backend/src/endpoints/auth.rs b/backend/src/endpoints/auth.rs new file mode 100644 index 0000000..6690521 --- /dev/null +++ b/backend/src/endpoints/auth.rs @@ -0,0 +1,29 @@ +use std::sync::Mutex; +use actix_web::{web, post, Responder, HttpResponse}; +use serde_json::json; +use crate::{State, models::administrator}; + +#[derive(serde::Deserialize)] +pub struct LoginInfo { + username: String, + password: String +} + +#[post("/login")] +pub async fn login(state: web::Data>, json: web::Json) -> impl Responder { + match state.lock() { + Ok(guard) => match guard.auth_service.login(guard.administrator_repository.as_ref(), &json.username, &json.password).await { + Ok((token, administrator_id, car_station_id)) => HttpResponse::Ok().json(json!({"token": token, "administrator_id": administrator_id, "car_station_id": car_station_id}) ), + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + }, + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + } +} + +#[post("/logout")] +pub async fn logout(state: web::Data>, json: web::Json) -> impl Responder { + match state.lock() { + Ok(guard) => { guard.auth_service.logout(&json.0); HttpResponse::Ok().body("Success") }, + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + } +} \ No newline at end of file diff --git a/backend/src/endpoints/benchmark.rs b/backend/src/endpoints/benchmark.rs new file mode 100644 index 0000000..39986a7 --- /dev/null +++ b/backend/src/endpoints/benchmark.rs @@ -0,0 +1,14 @@ +use std::sync::Mutex; +use actix_web::{web, get, post, patch, delete, Responder, HttpResponse, HttpRequest}; +use crate::State; + +#[get("/benchmark")] +async fn benchmark(state: web::Data>) -> impl Responder { + match state.lock() { + Ok(guard) => match guard.benchmark_repository.benchmark().await { + Ok(result) => HttpResponse::Ok().json(result), + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + }, + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + } +} \ No newline at end of file diff --git a/backend/src/endpoints/car.rs b/backend/src/endpoints/car.rs index 4b4c510..dece7c2 100644 --- a/backend/src/endpoints/car.rs +++ b/backend/src/endpoints/car.rs @@ -1,10 +1,22 @@ use std::sync::Mutex; use crate::models::car::*; -use actix_web::{web, get, post, patch, delete, Responder, HttpResponse}; +use actix_web::{web, get, post, patch, delete, Responder, HttpResponse, HttpRequest}; use crate::State; #[get("/")] -pub async fn get_cars(state: web::Data>) -> impl Responder { +pub async fn get_cars(state: web::Data>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_repository.read_all().await { Ok(result) => HttpResponse::Ok().json(result), @@ -15,7 +27,19 @@ pub async fn get_cars(state: web::Data>) -> impl Responder { } #[get("/{id}")] -pub async fn get_car(state: web::Data>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn get_car(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_repository.read(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -26,7 +50,19 @@ pub async fn get_car(state: web::Data>, path: web::Path<(u32, )>) - } #[post("/")] -pub async fn create_car(state: web::Data>, json: web::Json) -> impl Responder { +pub async fn create_car(state: web::Data>, json: web::Json, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_repository.create(json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -37,7 +73,19 @@ pub async fn create_car(state: web::Data>, json: web::Json>, json: web::Json, path: web::Path<(u32, )>) -> impl Responder { +pub async fn update_car(state: web::Data>, json: web::Json, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_repository.update(path.into_inner().0, json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -48,7 +96,19 @@ pub async fn update_car(state: web::Data>, json: web::Json>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn delete_car(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_repository.delete(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -56,4 +116,27 @@ pub async fn delete_car(state: web::Data>, path: web::Path<(u32, )> }, Err(error) => HttpResponse::InternalServerError().json(error.to_string()) } +} + +#[get("/report")] +pub async fn get_report(state: web::Data>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + + match state.lock() { + Ok(guard) => match guard.car_repository.get_report().await { + Ok(result) => HttpResponse::Ok().json(result), + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + }, + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + } } \ No newline at end of file diff --git a/backend/src/endpoints/car_station.rs b/backend/src/endpoints/car_station.rs index 36b3d8e..a9caa11 100644 --- a/backend/src/endpoints/car_station.rs +++ b/backend/src/endpoints/car_station.rs @@ -1,10 +1,22 @@ use std::sync::Mutex; use crate::models::car_station::*; -use actix_web::{web, get, post, patch, delete, Responder, HttpResponse}; +use actix_web::{web, get, post, patch, delete, Responder, HttpResponse, HttpRequest}; use crate::State; #[get("/")] -pub async fn get_car_stations(state: web::Data>) -> impl Responder { +pub async fn get_car_stations(state: web::Data>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_station_repository.read_all().await { Ok(result) => HttpResponse::Ok().json(result), @@ -15,7 +27,19 @@ pub async fn get_car_stations(state: web::Data>) -> impl Responder } #[get("/{id}")] -pub async fn get_car_station(state: web::Data>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn get_car_station(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_station_repository.read(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -26,7 +50,19 @@ pub async fn get_car_station(state: web::Data>, path: web::Path<(u3 } #[post("/")] -pub async fn create_car_station(state: web::Data>, json: web::Json) -> impl Responder { +pub async fn create_car_station(state: web::Data>, json: web::Json, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_station_repository.create(json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -37,7 +73,19 @@ pub async fn create_car_station(state: web::Data>, json: web::Json< } #[patch("/{id}")] -pub async fn update_car_station(state: web::Data>, json: web::Json, path: web::Path<(u32, )>) -> impl Responder { +pub async fn update_car_station(state: web::Data>, json: web::Json, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_station_repository.update(path.into_inner().0, json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -48,7 +96,19 @@ pub async fn update_car_station(state: web::Data>, json: web::Json< } #[delete("/{id}")] -pub async fn delete_car_station(state: web::Data>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn delete_car_station(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.car_station_repository.delete(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), diff --git a/backend/src/endpoints/client.rs b/backend/src/endpoints/client.rs index d85a862..d1cc189 100644 --- a/backend/src/endpoints/client.rs +++ b/backend/src/endpoints/client.rs @@ -1,10 +1,22 @@ use std::sync::Mutex; use crate::models::client::*; -use actix_web::{web, get, post, patch, delete, Responder, HttpResponse}; +use actix_web::{web, get, post, patch, delete, Responder, HttpResponse, HttpRequest}; use crate::State; #[get("/")] -pub async fn get_clients(state: web::Data>) -> impl Responder { +pub async fn get_clients(state: web::Data>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.client_repository.read_all().await { Ok(result) => HttpResponse::Ok().json(result), @@ -15,7 +27,19 @@ pub async fn get_clients(state: web::Data>) -> impl Responder { } #[get("/{id}")] -pub async fn get_client(state: web::Data>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn get_client(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.client_repository.read(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -26,7 +50,19 @@ pub async fn get_client(state: web::Data>, path: web::Path<(u32, )> } #[post("/")] -pub async fn create_client(state: web::Data>, json: web::Json) -> impl Responder { +pub async fn create_client(state: web::Data>, json: web::Json, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.client_repository.create(json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -37,7 +73,19 @@ pub async fn create_client(state: web::Data>, json: web::Json>, json: web::Json, path: web::Path<(u32, )>) -> impl Responder { +pub async fn update_client(state: web::Data>, json: web::Json, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.client_repository.update(path.into_inner().0, json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -48,7 +96,19 @@ pub async fn update_client(state: web::Data>, json: web::Json>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn delete_client(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.client_repository.delete(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), diff --git a/backend/src/endpoints/mod.rs b/backend/src/endpoints/mod.rs index 2c3aab9..69bb70f 100644 --- a/backend/src/endpoints/mod.rs +++ b/backend/src/endpoints/mod.rs @@ -2,4 +2,6 @@ pub mod client; pub mod car_station; pub mod car; pub mod rent; -pub mod owner; \ No newline at end of file +pub mod owner; +pub mod auth; +pub mod benchmark; \ No newline at end of file diff --git a/backend/src/endpoints/owner.rs b/backend/src/endpoints/owner.rs index 0c65251..7d70b28 100644 --- a/backend/src/endpoints/owner.rs +++ b/backend/src/endpoints/owner.rs @@ -1,10 +1,22 @@ use std::sync::Mutex; use crate::models::client::*; -use actix_web::{web, get, post, patch, delete, Responder, HttpResponse}; +use actix_web::{web, get, post, patch, delete, Responder, HttpResponse, HttpRequest}; use crate::State; #[get("/")] -pub async fn get_owners(state: web::Data>) -> impl Responder { +pub async fn get_owners(state: web::Data>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.owner_repository.read_all().await { Ok(result) => HttpResponse::Ok().json(result), @@ -15,7 +27,19 @@ pub async fn get_owners(state: web::Data>) -> impl Responder { } #[get("/{id}")] -pub async fn get_owner(state: web::Data>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn get_owner(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.owner_repository.read(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -26,7 +50,19 @@ pub async fn get_owner(state: web::Data>, path: web::Path<(u32, )>) } #[post("/")] -pub async fn create_owner(state: web::Data>, json: web::Json) -> impl Responder { +pub async fn create_owner(state: web::Data>, json: web::Json, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.owner_repository.create(json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -37,7 +73,19 @@ pub async fn create_owner(state: web::Data>, json: web::Json>, json: web::Json, path: web::Path<(u32, )>) -> impl Responder { +pub async fn update_owner(state: web::Data>, json: web::Json, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.owner_repository.update(path.into_inner().0, json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -48,7 +96,19 @@ pub async fn update_owner(state: web::Data>, json: web::Json>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn delete_owner(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.owner_repository.delete(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), diff --git a/backend/src/endpoints/rent.rs b/backend/src/endpoints/rent.rs index 005b843..d3acf38 100644 --- a/backend/src/endpoints/rent.rs +++ b/backend/src/endpoints/rent.rs @@ -1,10 +1,22 @@ use std::sync::Mutex; use crate::models::rent::*; -use actix_web::{web, get, post, patch, delete, Responder, HttpResponse}; +use actix_web::{web, get, post, patch, delete, Responder, HttpResponse, HttpRequest}; use crate::State; #[get("/")] -pub async fn get_rents(state: web::Data>) -> impl Responder { +pub async fn get_rents(state: web::Data>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.rent_repository.read_all().await { Ok(result) => HttpResponse::Ok().json(result), @@ -15,7 +27,19 @@ pub async fn get_rents(state: web::Data>) -> impl Responder { } #[get("/{id}")] -pub async fn get_rent(state: web::Data>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn get_rent(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.rent_repository.read(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -26,7 +50,19 @@ pub async fn get_rent(state: web::Data>, path: web::Path<(u32, )>) } #[post("/")] -pub async fn create_rent(state: web::Data>, json: web::Json) -> impl Responder { +pub async fn create_rent(state: web::Data>, json: web::Json, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.rent_repository.create(json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -37,7 +73,19 @@ pub async fn create_rent(state: web::Data>, json: web::Json>, json: web::Json, path: web::Path<(u32, )>) -> impl Responder { +pub async fn update_rent(state: web::Data>, json: web::Json, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.rent_repository.update(path.into_inner().0, json.0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -48,7 +96,19 @@ pub async fn update_rent(state: web::Data>, json: web::Json>, path: web::Path<(u32, )>) -> impl Responder { +pub async fn delete_rent(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + match state.lock() { Ok(guard) => match guard.rent_repository.delete(path.into_inner().0).await { Ok(result) => HttpResponse::Ok().json(result), @@ -56,4 +116,27 @@ pub async fn delete_rent(state: web::Data>, path: web::Path<(u32, ) }, Err(error) => HttpResponse::InternalServerError().json(error.to_string()) } +} + +#[get("/{id}/end")] +pub async fn end_rent(state: web::Data>, path: web::Path<(i32, )>, request: HttpRequest) -> impl Responder { + let token = request.headers().get("Authorization"); + if token.is_none() { + return HttpResponse::Unauthorized().body("There is no token"); + } + let token = token.unwrap(); + + if let Ok(guard) = state.lock() { + if guard.auth_service.administrator_by_token(guard.administrator_repository.as_ref(), token.to_str().unwrap()).await.is_err() { + return HttpResponse::Unauthorized().body("Invalid token"); + } + } + + match state.lock() { + Ok(guard) => match guard.rent_repository.end_rent(path.into_inner().0).await { + Ok(result) => HttpResponse::Ok().json(result), + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + }, + Err(error) => HttpResponse::InternalServerError().json(error.to_string()) + } } \ No newline at end of file diff --git a/backend/src/lib.rs b/backend/src/lib.rs index 7278f76..4cbd82c 100644 --- a/backend/src/lib.rs +++ b/backend/src/lib.rs @@ -1,15 +1,20 @@ -use storages::traits::OwnerRepository; +use services::auth::AuthService; +use storages::traits::{OwnerRepository, AdministratorRepository, BenchmarkRepository}; use crate::storages::traits::{CarRepository, CarStationRepository, ClientRepository, RentRepository}; pub mod endpoints; pub mod models; pub mod storages; +pub mod services; pub struct State { pub car_repository: Box, pub car_station_repository: Box, pub client_repository: Box, pub owner_repository: Box, - pub rent_repository: Box + pub rent_repository: Box, + pub administrator_repository: Box, + pub benchmark_repository: Box, + pub auth_service: AuthService } \ No newline at end of file diff --git a/backend/src/main.rs b/backend/src/main.rs index 7a37b36..181d294 100644 --- a/backend/src/main.rs +++ b/backend/src/main.rs @@ -1,14 +1,18 @@ use std::sync::{Arc, Mutex}; use actix_web::{App, HttpServer, web}; use actix_web::web::Data; +use backend::endpoints::benchmark::benchmark; +use backend::endpoints::{auth::*}; +use backend::storages::postgres::administrator::PostgresAdministratorRepository; use dotenv_codegen::dotenv; -use backend::State; +use backend::{State, services}; use tokio_postgres::NoTls; use backend::storages::postgres::car::PostgresCarRepository; use backend::storages::postgres::car_station::PostgresCarStationRepository; use backend::storages::postgres::client::PostgresClientRepository; use backend::storages::postgres::rent::PostgresRentRepository; use backend::storages::postgres::owner::PostgresOwnerRepository; +use backend::storages::postgres::benchmark::PostgresBenchmarkRepository; use backend::endpoints::car_station::*; use backend::endpoints::car::*; use backend::endpoints::client::*; @@ -18,7 +22,7 @@ use backend::endpoints::owner::*; #[actix_web::main] async fn main() -> std::io::Result<()> { let (client, connection) = tokio_postgres::connect( - &format!("host={} user={} password={} dbname={}", dotenv!("HOST"), dotenv!("USER"), dotenv!("PASSWORD"), dotenv!("DBNAME")), + &format!("host={} user={} password={} dbname={}", dotenv!("HOST"), dotenv!("USER"), dotenv!("PASSWORD"), dotenv!("DBNAMEE")), NoTls ).await.unwrap(); @@ -40,57 +44,72 @@ async fn main() -> std::io::Result<()> { let rent_repository = PostgresRentRepository { connection: Arc::clone(&client) }; + let administrator_repository = PostgresAdministratorRepository { connection: Arc::clone(&client) }; + + let benchmark_repository = PostgresBenchmarkRepository { connection: Arc::clone(&client) }; + + let auth_service = services::auth::AuthService::new(); + let state = Data::new(Mutex::new(State { car_repository: Box::new(car_repository), car_station_repository: Box::new(car_station_repository), client_repository: Box::new(client_repository), owner_repository: Box::new(owner_repository), - rent_repository: Box::new(rent_repository) + rent_repository: Box::new(rent_repository), + administrator_repository: Box::new(administrator_repository), + benchmark_repository: Box::new(benchmark_repository), + auth_service })); - + HttpServer::new(move || { App::new() .app_data(Data::clone(&state)) - .service( - web::scope("/cars") - .service(get_cars) - .service(get_car) - .service(create_car) - .service(update_car) - .service(delete_car) - ) - .service( - web::scope("/clients") - .service(get_clients) - .service(get_client) - .service(create_client) - .service(update_client) - .service(delete_client) - ) - .service( - web::scope("/owners") - .service(get_owners) - .service(get_owner) - .service(create_owner) - .service(update_owner) - .service(delete_owner) - ) - .service( - web::scope("/car_stations") - .service(get_car_stations) - .service(get_car_station) - .service(create_car_station) - .service(update_car_station) - .service(delete_car_station) - ) - .service( - web::scope("/rents") - .service(get_rents) - .service(get_rent) - .service(create_rent) - .service(update_rent) - .service(delete_rent) - ) + .service(web::scope("/api") + .service( + web::scope("/cars") + .service(get_cars) + .service(get_report) + .service(get_car) + .service(create_car) + .service(update_car) + .service(delete_car) + ) + .service( + web::scope("/clients") + .service(get_clients) + .service(get_client) + .service(create_client) + .service(update_client) + .service(delete_client) + ) + .service( + web::scope("/owners") + .service(get_owners) + .service(get_owner) + .service(create_owner) + .service(update_owner) + .service(delete_owner) + ) + .service( + web::scope("/car_stations") + .service(get_car_stations) + .service(get_car_station) + .service(create_car_station) + .service(update_car_station) + .service(delete_car_station) + ) + .service( + web::scope("/rents") + .service(get_rents) + .service(get_rent) + .service(create_rent) + .service(update_rent) + .service(delete_rent) + .service(end_rent) + ) + .service(login) + .service(logout) + ).service(benchmark) }) .bind("127.0.0.1:8080")? .run() diff --git a/backend/src/models/administrator.rs b/backend/src/models/administrator.rs new file mode 100644 index 0000000..3ba74cb --- /dev/null +++ b/backend/src/models/administrator.rs @@ -0,0 +1,12 @@ +use serde::{Serialize, Deserialize}; + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Administrator { + pub id: i32, + pub username: String, + pub password: String, + pub name: String, + pub surname: String, + pub middlename: String, + pub car_station_id: i32 +} \ No newline at end of file diff --git a/backend/src/models/car.rs b/backend/src/models/car.rs index ec2fa17..8f74de3 100644 --- a/backend/src/models/car.rs +++ b/backend/src/models/car.rs @@ -1,3 +1,4 @@ +use rust_decimal::Decimal; use serde::{Serialize, Deserialize}; #[derive(Debug, Clone, Serialize, Deserialize)] @@ -5,7 +6,7 @@ pub struct Car { pub id: i32, pub brand: String, pub model: String, - pub price: f64, + pub price: Decimal, pub owner_id: i32, pub car_station_id: i32 } @@ -14,7 +15,16 @@ pub struct Car { pub struct BindingCar { pub brand: String, pub model: String, - pub price: f64, - pub owner_id: u32, - pub car_station_id: u32 + pub price: Decimal, + pub owner_id: i32, + pub car_station_id: i32 +} + +#[derive(Debug, Clone, Serialize, Deserialize)] +pub struct Report { + pub car_id: i32, + pub brand: String, + pub model: String, + pub times: i64, + pub income: Decimal } \ No newline at end of file diff --git a/backend/src/models/mod.rs b/backend/src/models/mod.rs index 4783b7d..aaa6c7c 100644 --- a/backend/src/models/mod.rs +++ b/backend/src/models/mod.rs @@ -1,4 +1,5 @@ pub mod client; pub mod car_station; pub mod car; -pub mod rent; \ No newline at end of file +pub mod rent; +pub mod administrator; \ No newline at end of file diff --git a/backend/src/models/rent.rs b/backend/src/models/rent.rs index 72dec1c..1566902 100644 --- a/backend/src/models/rent.rs +++ b/backend/src/models/rent.rs @@ -3,7 +3,7 @@ use serde::{Serialize, Deserialize}; #[derive(Debug, Clone, Serialize, Deserialize)] pub struct Rent { pub id: i32, - pub start_time: chrono::NaiveTime, + pub start_time: chrono::NaiveDateTime, pub time_amount: Option, pub client_id: i32, pub car_id: i32 @@ -11,7 +11,7 @@ pub struct Rent { #[derive(Debug, Clone, Serialize, Deserialize)] pub struct BindingRent { - pub time_amount: Option, - pub client_id: u32, - pub car_id: u32 + pub time_amount: Option, + pub client_id: i32, + pub car_id: i32 } \ No newline at end of file diff --git a/backend/src/services/auth.rs b/backend/src/services/auth.rs new file mode 100644 index 0000000..184e818 --- /dev/null +++ b/backend/src/services/auth.rs @@ -0,0 +1,34 @@ +use crate::{storages::traits::AdministratorRepository, models::administrator::Administrator}; +use nanoid::nanoid; +use sha2::Sha256; +use std::cell::RefCell; + +pub struct AuthService { + authed: RefCell> +} + +impl AuthService { + pub fn new() -> Self { + AuthService { authed: RefCell::new(Vec::new()) } + } + + pub async fn login(&self, storage: &dyn AdministratorRepository, username: &str, password: &str) -> Result<(String, i32, i32), String> { + let administrator = storage.find_by_username(&username).await?; + if pbkdf2::pbkdf2_hmac_array::(password.as_bytes(), dotenv_codegen::dotenv!("SALT").as_bytes(), 4096).iter().map(|x| format!("{:x}", x)).collect::() != administrator.password { + Err("Invalid password or login!".to_owned()) + } else { + let token = nanoid!(32); + self.authed.borrow_mut().push((token.clone(), administrator.id)); + Ok((token, administrator.id, administrator.car_station_id)) + } + } + + pub fn logout(&self, token: &str) { + self.authed.borrow_mut().retain(|x| x.0 != token); + } + + pub async fn administrator_by_token(&self, storage: &dyn AdministratorRepository, token: &str) -> Result { + let id = self.authed.borrow().iter().find(|&x| x.0.starts_with(token)).ok_or("Invalid token")?.1; + storage.read(id).await + } +} \ No newline at end of file diff --git a/backend/src/services/mod.rs b/backend/src/services/mod.rs new file mode 100644 index 0000000..5696e21 --- /dev/null +++ b/backend/src/services/mod.rs @@ -0,0 +1 @@ +pub mod auth; \ No newline at end of file diff --git a/backend/src/storages/postgres/administrator.rs b/backend/src/storages/postgres/administrator.rs new file mode 100644 index 0000000..312adda --- /dev/null +++ b/backend/src/storages/postgres/administrator.rs @@ -0,0 +1,52 @@ +use tokio_postgres::Client as PgClient; +use std::sync::Arc; +use crate::models::administrator::Administrator; +use crate::storages::traits::AdministratorRepository; +use async_trait::async_trait; + +pub struct PostgresAdministratorRepository { + pub connection: Arc +} + +#[async_trait] +impl AdministratorRepository for PostgresAdministratorRepository { + async fn read(&self, id: i32) -> Result { + let result = self.connection.query( + "SELECT * FROM administrator WHERE id = $1", &[&id] + ).await; + + if let Ok(rows) = result { + let row = rows.get(0).ok_or("Administrator not found".to_owned())?; + Ok(Administrator { + id: row.get("id"), + username: row.get("username"), + password: row.get("password"), + name: row.get("name"), + surname: row.get("surname"), + middlename: row.get("middlename"), + car_station_id: row.get("car_station_id") + }) + } else { + Err(result.unwrap_err().to_string()) + } + } + + async fn find_by_username(&self, username: &str) -> Result { + let result = self.connection.query("SELECT * FROM administrator WHERE username LIKE $1", &[&username]).await; + + if let Ok(rows) = result { + let row = rows.get(0).ok_or("Administrator not found".to_owned())?; + Ok(Administrator { + id: row.get("id"), + username: row.get("username"), + password: row.get("password"), + name: row.get("name"), + surname: row.get("surname"), + middlename: row.get("middlename"), + car_station_id: row.get("car_station_id") + }) + } else { + Err(result.unwrap_err().to_string()) + } + } +} \ No newline at end of file diff --git a/backend/src/storages/postgres/benchmark.rs b/backend/src/storages/postgres/benchmark.rs new file mode 100644 index 0000000..1143190 --- /dev/null +++ b/backend/src/storages/postgres/benchmark.rs @@ -0,0 +1,49 @@ +use tokio_postgres::Client as PgClient; +use std::sync::Arc; +use crate::storages::traits::BenchmarkRepository; +use async_trait::async_trait; + +pub struct PostgresBenchmarkRepository { + pub connection: Arc +} + +#[async_trait] +impl BenchmarkRepository for PostgresBenchmarkRepository { + async fn benchmark(&self) -> Result { + self.connection.execute("DELETE FROM Rent", &[]).await.unwrap(); + self.connection.execute("DELETE FROM Car", &[]).await.unwrap(); + self.connection.execute("DELETE FROM Owner", &[]).await.unwrap(); + self.connection.execute("DELETE FROM Client", &[]).await.unwrap(); + + let start_time = chrono::Utc::now().naive_local(); + + for n in 1..201 { + let str = format!("INSERT INTO Owner(name, surname, middlename, phone) VALUES ('Иван{0}', 'Иванов{0}', 'Иванович{0}', {0}) RETURNING id", n); + let owner_id: i32 = self.connection.query( + &str, + &[] + ).await.unwrap().get(0).unwrap().get(0); + + let client_id: i32 = self.connection.query( + &str.replace("Owner", "Client").replace("Иван", "Петр"), + &[] + ).await.unwrap().get(0).unwrap().get(0); + + let str = format!("INSERT INTO Car(Brand, Model, Price, owner_id, car_station_id) VALUES ('Лада{0}', 'Гранта{0}', {0}.00, {1}, 2) RETURNING id", n, owner_id); + let car_id: i32 = self.connection.query( + &str, + &[] + ).await.unwrap().get(0).unwrap().get(0); + + let str = format!("INSERT INTO Rent(car_id, client_id, start_time, time_amount) VALUES ({1}, {2}, now()::timestamp, {0}) RETURNING id", n, car_id, client_id); + let rent_id: i32 = self.connection.query( + &str, + &[] + ).await.unwrap().get(0).unwrap().get(0); + } + + let elapsed = chrono::Utc::now().naive_local() - start_time; + + Ok(elapsed.num_microseconds().unwrap()) + } +} \ No newline at end of file diff --git a/backend/src/storages/postgres/car.rs b/backend/src/storages/postgres/car.rs index c878fd0..0fb80b8 100644 --- a/backend/src/storages/postgres/car.rs +++ b/backend/src/storages/postgres/car.rs @@ -1,6 +1,6 @@ use tokio_postgres::Client as PgClient; use std::sync::Arc; -use crate::models::car::{BindingCar, Car}; +use crate::models::car::{BindingCar, Car, Report}; use crate::storages::traits::CarRepository; use async_trait::async_trait; @@ -28,11 +28,11 @@ impl CarRepository for PostgresCarRepository { car_station_id: row.get("car_station_id") }) } else { - Err("Something gone wrong during creation of Car".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn read(&self, id: u32) -> Result { + async fn read(&self, id: i32) -> Result { let result = self.connection.query( "SELECT * FROM Car WHERE id = $1", &[&id] ).await; @@ -48,7 +48,7 @@ impl CarRepository for PostgresCarRepository { car_station_id: row.get("car_station_id") }) } else { - Err("Something gone wrong during reading of Car".to_owned()) + Err(result.unwrap_err().to_string()) } } @@ -69,11 +69,11 @@ impl CarRepository for PostgresCarRepository { }).collect() ) } else { - Err("Something gone wrong during reading CarStations".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn update(&self, id: u32, car: BindingCar) -> Result { + async fn update(&self, id: i32, car: BindingCar) -> Result { let result = self.connection.query( "UPDATE Car SET brand = $1, model = $2, price = $3, owner_id = $4, car_station_id = $5 WHERE id = $6 RETURNING *", &[&car.brand, &car.model, &car.price, &car.owner_id, &car.car_station_id, &id] @@ -90,11 +90,11 @@ impl CarRepository for PostgresCarRepository { car_station_id: row.get("car_station_id") }) } else { - Err("Something gone wrong during updating of Car".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn delete(&self, id: u32) -> Result<(), String> { + async fn delete(&self, id: i32) -> Result<(), String> { let result = self.connection.execute( "DELETE FROM Car WHERE id = $1", &[&id] @@ -107,7 +107,38 @@ impl CarRepository for PostgresCarRepository { Ok(()) } } else { - Err("Something gone wrong during deleting of Car".to_owned()) + Err(result.unwrap_err().to_string()) + } + } + + async fn get_report(&self) -> Result, String> { + let result = self.connection.query( + "SELECT + c.id AS \"car_id\", + c.brand AS \"brand\", + c.model AS \"model\", + COUNT(r.id) AS \"times\", + ROUND(SUM(c.price * r.time_amount), 2) AS \"income\" + FROM car c + JOIN rent r ON c.id = r.car_id + WHERE date_trunc('month', r.start_time) = date_trunc('month', CURRENT_DATE) + GROUP BY c.id;", &[] + ).await; + + if let Ok(rows) = result { + Ok( + rows.into_iter() + .map(|row| Report { + car_id: row.get("car_id"), + brand: row.get("brand"), + model: row.get("model"), + times: row.get("times"), + income: row.get("income") + }) + .collect() + ) + } else { + Err(result.unwrap_err().to_string()) } } } \ No newline at end of file diff --git a/backend/src/storages/postgres/car_station.rs b/backend/src/storages/postgres/car_station.rs index 5b57867..1fb7805 100644 --- a/backend/src/storages/postgres/car_station.rs +++ b/backend/src/storages/postgres/car_station.rs @@ -23,11 +23,11 @@ impl CarStationRepository for PostgresCarStationRepository { address: row.get("address") }) } else { - Err("Something gone wrong during creation of CarStation".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn read(&self, id: u32) -> Result { + async fn read(&self, id: i32) -> Result { let result = self.connection.query( "SELECT * FROM Car_Station WHERE id = $1", &[&id] ).await; @@ -39,7 +39,7 @@ impl CarStationRepository for PostgresCarStationRepository { address: row.get("address") }) } else { - Err("Something gone wrong during reading of CarStation".to_owned()) + Err(result.unwrap_err().to_string()) } } @@ -56,11 +56,11 @@ impl CarStationRepository for PostgresCarStationRepository { }).collect() ) } else { - Err("Something gone wrong during reading CarStations".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn update(&self, id: u32, car_station: BindingCarStation) -> Result { + async fn update(&self, id: i32, car_station: BindingCarStation) -> Result { let result = self.connection.query( "UPDATE Car_Station SET address = $1 WHERE id = $2 RETURNING *", &[&car_station.address, &id] @@ -73,11 +73,11 @@ impl CarStationRepository for PostgresCarStationRepository { address: row.get("address") }) } else { - Err("Something gone wrong during updating of CarStation".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn delete(&self, id: u32) -> Result<(), String> { + async fn delete(&self, id: i32) -> Result<(), String> { let result = self.connection.execute( "DELETE FROM Car_Station WHERE id = $1", &[&id] @@ -90,7 +90,7 @@ impl CarStationRepository for PostgresCarStationRepository { Ok(()) } } else { - Err("Something gone wrong during deleting of CarStation".to_owned()) + Err(result.unwrap_err().to_string()) } } } \ No newline at end of file diff --git a/backend/src/storages/postgres/client.rs b/backend/src/storages/postgres/client.rs index 88bfeb0..6ad6b23 100644 --- a/backend/src/storages/postgres/client.rs +++ b/backend/src/storages/postgres/client.rs @@ -27,11 +27,11 @@ impl ClientRepository for PostgresClientRepository { phone: row.get("phone") }) } else { - Err("Something gone wrong during creation of Client".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn read(&self, id: u32) -> Result { + async fn read(&self, id: i32) -> Result { let result= self.connection.query( "SELECT * FROM Client WHERE id = $1", &[&id] ).await; @@ -47,7 +47,7 @@ impl ClientRepository for PostgresClientRepository { phone: row.get("phone") }) } else { - Err("Something gone wrong during reading of Client".to_owned()) + Err(result.unwrap_err().to_string()) } } @@ -67,11 +67,11 @@ impl ClientRepository for PostgresClientRepository { }).collect() ) } else { - Err("Something gone wrong during reading Clients".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn update(&self, id: u32, client: BindingClient) -> Result { + async fn update(&self, id: i32, client: BindingClient) -> Result { let result = self.connection.query( "UPDATE Client SET name = $1, surname = $2, middlename = $3, phone = $4 \ WHERE id = $5 RETURNING *", @@ -88,11 +88,11 @@ impl ClientRepository for PostgresClientRepository { phone: row.get("phone") }) } else { - Err("Something gone wrong during updating of Client".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn delete(&self, id: u32) -> Result<(), String> { + async fn delete(&self, id: i32) -> Result<(), String> { let result = self.connection.execute( "DELETE FROM Client WHERE id = $1", &[&id] @@ -105,7 +105,7 @@ impl ClientRepository for PostgresClientRepository { Ok(()) } } else { - Err("Something gone wrong during deleting of Client".to_owned()) + Err(result.unwrap_err().to_string()) } } } diff --git a/backend/src/storages/postgres/mod.rs b/backend/src/storages/postgres/mod.rs index 2c3aab9..ec478c8 100644 --- a/backend/src/storages/postgres/mod.rs +++ b/backend/src/storages/postgres/mod.rs @@ -2,4 +2,6 @@ pub mod client; pub mod car_station; pub mod car; pub mod rent; -pub mod owner; \ No newline at end of file +pub mod owner; +pub mod administrator; +pub mod benchmark; \ No newline at end of file diff --git a/backend/src/storages/postgres/owner.rs b/backend/src/storages/postgres/owner.rs index 7f63f2d..6ae5f1c 100644 --- a/backend/src/storages/postgres/owner.rs +++ b/backend/src/storages/postgres/owner.rs @@ -27,11 +27,11 @@ impl OwnerRepository for PostgresOwnerRepository { phone: row.get("phone") }) } else { - Err("Something gone wrong during creation of Owner".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn read(&self, id: u32) -> Result { + async fn read(&self, id: i32) -> Result { let result= self.connection.query( "SELECT * FROM Owner WHERE id = $1", &[&id] ).await; @@ -47,7 +47,7 @@ impl OwnerRepository for PostgresOwnerRepository { phone: row.get("phone") }) } else { - Err("Something gone wrong during reading of Owner".to_owned()) + Err(result.unwrap_err().to_string()) } } @@ -67,11 +67,11 @@ impl OwnerRepository for PostgresOwnerRepository { }).collect() ) } else { - Err("Something gone wrong during reading Owner".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn update(&self, id: u32, client: BindingClient) -> Result { + async fn update(&self, id: i32, client: BindingClient) -> Result { let result = self.connection.query( "UPDATE Owner SET name = $1, surname = $2, middlename = $3, phone = $4 \ WHERE id = $5 RETURNING *", @@ -88,11 +88,11 @@ impl OwnerRepository for PostgresOwnerRepository { phone: row.get("phone") }) } else { - Err("Something gone wrong during updating of Owner".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn delete(&self, id: u32) -> Result<(), String> { + async fn delete(&self, id: i32) -> Result<(), String> { let result = self.connection.execute( "DELETE FROM Owner WHERE id = $1", &[&id] @@ -105,7 +105,7 @@ impl OwnerRepository for PostgresOwnerRepository { Ok(()) } } else { - Err("Something gone wrong during deleting of Owner".to_owned()) + Err(result.unwrap_err().to_string()) } } } diff --git a/backend/src/storages/postgres/rent.rs b/backend/src/storages/postgres/rent.rs index 585a1e1..84ed6a9 100644 --- a/backend/src/storages/postgres/rent.rs +++ b/backend/src/storages/postgres/rent.rs @@ -12,7 +12,7 @@ pub struct PostgresRentRepository { impl RentRepository for PostgresRentRepository { async fn create(&self, rent: BindingRent) -> Result { let query_result = self.connection.query( - "SELECT * FROM Rent WHERE car_id=$1 AND time_amount IS NOT NULL", + "SELECT * FROM Rent WHERE car_id=$1 AND time_amount IS NULL", &[&rent.car_id] ).await; @@ -21,7 +21,7 @@ impl RentRepository for PostgresRentRepository { return Err("The car is already occupied".to_owned()); } } else { - return Err("Something gone wrong during checking for existing rents".to_owned()); + return Err(query_result.unwrap_err().to_string()); } let result = self.connection.query( @@ -40,11 +40,11 @@ impl RentRepository for PostgresRentRepository { car_id: row.get("car_id") }) } else { - Err("Something gone wrong during creation of Rent".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn read(&self, id: u32) -> Result { + async fn read(&self, id: i32) -> Result { let result = self.connection.query( "SELECT * FROM Rent WHERE id = $1", &[&id] ).await; @@ -59,7 +59,7 @@ impl RentRepository for PostgresRentRepository { car_id: row.get("car_id") }) } else { - Err("Something gone wrong during reading of Rent".to_owned()) + Err(result.unwrap_err().to_string()) } } @@ -79,11 +79,11 @@ impl RentRepository for PostgresRentRepository { }).collect() ) } else { - Err("Something gone wrong during reading CarStations".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn update(&self, id: u32, rent: BindingRent) -> Result { + async fn update(&self, id: i32, rent: BindingRent) -> Result { let result = self.connection.query( "UPDATE Rent SET time_amount = $2, client_id = $3, car_id = $4 WHERE id = $5 RETURNING *", &[&rent.time_amount, &rent.client_id, &rent.car_id, &id] @@ -99,11 +99,11 @@ impl RentRepository for PostgresRentRepository { car_id: row.get("car_id") }) } else { - Err("Something gone wrong during updating of Rent".to_owned()) + Err(result.unwrap_err().to_string()) } } - async fn delete(&self, id: u32) -> Result<(), String> { + async fn delete(&self, id: i32) -> Result<(), String> { let result = self.connection.execute( "DELETE FROM Rent WHERE id = $1", &[&id] ).await; @@ -111,7 +111,22 @@ impl RentRepository for PostgresRentRepository { if let Ok(_) = result { Ok(()) } else { - Err("Something gone wrong during deletion of Rent".to_owned()) + Err(result.unwrap_err().to_string()) + } + } + + async fn end_rent(&self, id: i32) -> Result<(), String> { + let current_rent = self.read(id).await?; + + if current_rent.time_amount.is_some() { + return Err("Rent is already ended".to_owned()); + } + + let time_amount = (self.connection.query("SELECT now()::timestamp", &[]).await.unwrap().get(0).unwrap().get::(0) - current_rent.start_time).num_minutes() as i32; + + match self.connection.execute("UPDATE Rent SET time_amount = $2 WHERE id = $1", &[&id, &time_amount]).await { + Ok(_) => Ok(()), + Err(err) => Err(err.to_string()) } } } \ No newline at end of file diff --git a/backend/src/storages/traits.rs b/backend/src/storages/traits.rs index 5a38418..bf03e53 100644 --- a/backend/src/storages/traits.rs +++ b/backend/src/storages/traits.rs @@ -1,3 +1,4 @@ +use crate::models::administrator::Administrator; use crate::models::client::*; use crate::models::car_station::*; use crate::models::car::*; @@ -7,44 +8,57 @@ use async_trait::async_trait; #[async_trait] pub trait ClientRepository { async fn create(&self, client: BindingClient) -> Result; - async fn read(&self, id: u32) -> Result; + async fn read(&self, id: i32) -> Result; async fn read_all(&self) -> Result, String>; - async fn update(&self, id: u32, client: BindingClient) -> Result; - async fn delete(&self, id: u32) -> Result<(), String>; + async fn update(&self, id: i32, client: BindingClient) -> Result; + async fn delete(&self, id: i32) -> Result<(), String>; } #[async_trait] pub trait CarRepository { async fn create(&self, car: BindingCar) -> Result; - async fn read(&self, id: u32) -> Result; + async fn read(&self, id: i32) -> Result; async fn read_all(&self) -> Result, String>; - async fn update(&self, id: u32, car: BindingCar) -> Result; - async fn delete(&self, id: u32) -> Result<(), String>; + async fn update(&self, id: i32, car: BindingCar) -> Result; + async fn delete(&self, id: i32) -> Result<(), String>; + async fn get_report(&self) -> Result, String>; } #[async_trait] pub trait CarStationRepository { async fn create(&self, car_station: BindingCarStation) -> Result; - async fn read(&self, id: u32) -> Result; + async fn read(&self, id: i32) -> Result; async fn read_all(&self) -> Result, String>; - async fn update(&self, id: u32, car_station: BindingCarStation) -> Result; - async fn delete(&self, id: u32) -> Result<(), String>; + async fn update(&self, id: i32, car_station: BindingCarStation) -> Result; + async fn delete(&self, id: i32) -> Result<(), String>; } #[async_trait] pub trait RentRepository { async fn create(&self, rent: BindingRent) -> Result; - async fn read(&self, id: u32) -> Result; + async fn read(&self, id: i32) -> Result; async fn read_all(&self) -> Result, String>; - async fn update(&self, id: u32, rent: BindingRent) -> Result; - async fn delete(&self, id: u32) -> Result<(), String>; + async fn update(&self, id: i32, rent: BindingRent) -> Result; + async fn delete(&self, id: i32) -> Result<(), String>; + async fn end_rent(&self, id: i32) -> Result<(), String>; } #[async_trait] pub trait OwnerRepository { async fn create(&self, client: BindingClient) -> Result; - async fn read(&self, id: u32) -> Result; + async fn read(&self, id: i32) -> Result; async fn read_all(&self) -> Result, String>; - async fn update(&self, id: u32, client: BindingClient) -> Result; - async fn delete(&self, id: u32) -> Result<(), String>; + async fn update(&self, id: i32, client: BindingClient) -> Result; + async fn delete(&self, id: i32) -> Result<(), String>; +} + +#[async_trait] +pub trait AdministratorRepository { + async fn read(&self, id: i32) -> Result; + async fn find_by_username(&self, username: &str) -> Result; +} + +#[async_trait] +pub trait BenchmarkRepository { + async fn benchmark(&self) -> Result; } \ No newline at end of file