Merge pull request #24 from IMPHNEN/chore/normalize-code
chore: normalize code
This commit is contained in:
Generated
+143
-35
@@ -115,6 +115,56 @@ dependencies = [
|
|||||||
"libc",
|
"libc",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstream"
|
||||||
|
version = "0.6.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8acc5369981196006228e28809f761875c0327210a891e941f4c683b3a99529b"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"anstyle-parse",
|
||||||
|
"anstyle-query",
|
||||||
|
"anstyle-wincon",
|
||||||
|
"colorchoice",
|
||||||
|
"is_terminal_polyfill",
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle"
|
||||||
|
version = "1.0.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "55cc3b69f167a1ef2e161439aa98aed94e6028e5f9a59be9a6ffb47aef1651f9"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-parse"
|
||||||
|
version = "0.2.6"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3b2d16507662817a6a20a9ea92df6652ee4f94f914589377d69f3b21bc5798a9"
|
||||||
|
dependencies = [
|
||||||
|
"utf8parse",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-query"
|
||||||
|
version = "1.1.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "79947af37f4177cfead1110013d678905c37501914fba0efea834c3fe9a8d60c"
|
||||||
|
dependencies = [
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "anstyle-wincon"
|
||||||
|
version = "3.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "ca3534e77181a9cc07539ad51f2141fe32f6c3ffd4df76db8ad92346b003ae4e"
|
||||||
|
dependencies = [
|
||||||
|
"anstyle",
|
||||||
|
"once_cell",
|
||||||
|
"windows-sys 0.59.0",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "any_ascii"
|
name = "any_ascii"
|
||||||
version = "0.3.2"
|
version = "0.3.2"
|
||||||
@@ -796,6 +846,12 @@ dependencies = [
|
|||||||
"inout",
|
"inout",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "colorchoice"
|
||||||
|
version = "1.0.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5b63caa9aa9397e2d9480a9b13673856c78d8ac123288526c37d7839f2a86990"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "concurrent-queue"
|
name = "concurrent-queue"
|
||||||
version = "2.5.0"
|
version = "2.5.0"
|
||||||
@@ -1097,6 +1153,29 @@ version = "0.1.2"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_filter"
|
||||||
|
version = "0.1.3"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "186e05a59d4c50738528153b83b0b0194d3a29507dfec16eccd4b342903397d0"
|
||||||
|
dependencies = [
|
||||||
|
"log",
|
||||||
|
"regex",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "env_logger"
|
||||||
|
version = "0.11.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "13c863f0904021b108aa8b2f55046443e6b1ebde8fd4a15c399893aae4fa069f"
|
||||||
|
dependencies = [
|
||||||
|
"anstream",
|
||||||
|
"anstyle",
|
||||||
|
"env_filter",
|
||||||
|
"jiff",
|
||||||
|
"log",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "equivalent"
|
name = "equivalent"
|
||||||
version = "1.0.2"
|
version = "1.0.2"
|
||||||
@@ -1847,15 +1926,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
checksum = "cb56e1aa765b4b4f3aadfab769793b7087bb03a4ea4920644a6d238e2df5b9ed"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imphnen-backend-service"
|
name = "imphnen-backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"axum",
|
"axum",
|
||||||
"axum-test",
|
"axum-test",
|
||||||
"chrono",
|
"chrono",
|
||||||
|
"env_logger",
|
||||||
"imphnen-entities",
|
"imphnen-entities",
|
||||||
"imphnen-gateway-service",
|
"imphnen-gateway",
|
||||||
|
"imphnen-iam",
|
||||||
"imphnen-libs",
|
"imphnen-libs",
|
||||||
"imphnen-utils",
|
"imphnen-utils",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -1872,31 +1953,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imphnen-cms-service"
|
name = "imphnen-dimentorin"
|
||||||
version = "0.1.0"
|
|
||||||
dependencies = [
|
|
||||||
"anyhow",
|
|
||||||
"axum",
|
|
||||||
"axum-test",
|
|
||||||
"chrono",
|
|
||||||
"imphnen-entities",
|
|
||||||
"imphnen-libs",
|
|
||||||
"imphnen-utils",
|
|
||||||
"lazy_static",
|
|
||||||
"rand 0.9.0",
|
|
||||||
"regex",
|
|
||||||
"serde",
|
|
||||||
"serde_json",
|
|
||||||
"surrealdb",
|
|
||||||
"tokio",
|
|
||||||
"tower-http",
|
|
||||||
"utoipa",
|
|
||||||
"utoipa-swagger-ui",
|
|
||||||
"validator",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "imphnen-dimentorin-service"
|
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -1931,7 +1988,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imphnen-gacha-service"
|
name = "imphnen-gacha"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -1955,7 +2012,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imphnen-gateway-service"
|
name = "imphnen-gateway"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -1963,9 +2020,9 @@ dependencies = [
|
|||||||
"axum-test",
|
"axum-test",
|
||||||
"chrono",
|
"chrono",
|
||||||
"imphnen-entities",
|
"imphnen-entities",
|
||||||
"imphnen-iam-service",
|
"imphnen-iam",
|
||||||
"imphnen-libs",
|
"imphnen-libs",
|
||||||
"imphnen-middleware-service",
|
"imphnen-middleware",
|
||||||
"imphnen-utils",
|
"imphnen-utils",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
"rand 0.9.0",
|
"rand 0.9.0",
|
||||||
@@ -1981,7 +2038,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imphnen-iam-service"
|
name = "imphnen-iam"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -2020,7 +2077,7 @@ dependencies = [
|
|||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "imphnen-middleware-service"
|
name = "imphnen-middleware"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
@@ -2029,7 +2086,7 @@ dependencies = [
|
|||||||
"chrono",
|
"chrono",
|
||||||
"futures",
|
"futures",
|
||||||
"imphnen-entities",
|
"imphnen-entities",
|
||||||
"imphnen-iam-service",
|
"imphnen-iam",
|
||||||
"imphnen-libs",
|
"imphnen-libs",
|
||||||
"imphnen-utils",
|
"imphnen-utils",
|
||||||
"lazy_static",
|
"lazy_static",
|
||||||
@@ -2100,6 +2157,12 @@ version = "2.11.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
checksum = "469fb0b9cefa57e3ef31275ee7cacb78f2fdca44e4765491884a2b119d4eb130"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "is_terminal_polyfill"
|
||||||
|
version = "1.70.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7943c866cc5cd64cbc25b2e01621d07fa8eb2a1a23160ee81ce38704e97b8ecf"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "itertools"
|
name = "itertools"
|
||||||
version = "0.10.5"
|
version = "0.10.5"
|
||||||
@@ -2133,6 +2196,30 @@ version = "1.0.15"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f02000660d30638906021176af16b17498bd0d12813dbfe7b276d8bc7f3c0806"
|
||||||
|
dependencies = [
|
||||||
|
"jiff-static",
|
||||||
|
"log",
|
||||||
|
"portable-atomic",
|
||||||
|
"portable-atomic-util",
|
||||||
|
"serde",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "jiff-static"
|
||||||
|
version = "0.2.13"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f3c30758ddd7188629c6713fc45d1188af4f44c90582311d0c8d8c9907f60c48"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn 2.0.100",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "js-sys"
|
name = "js-sys"
|
||||||
version = "0.3.77"
|
version = "0.3.77"
|
||||||
@@ -2869,6 +2956,21 @@ version = "0.3.32"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic"
|
||||||
|
version = "1.11.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "350e9b48cbc6b0e028b0473b114454c6316e57336ee184ceab6e53f72c178b3e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "portable-atomic-util"
|
||||||
|
version = "0.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d8a2f0d8d040d7848a709caf78912debcc3f33ee4b3cac47d73d1e1069e83507"
|
||||||
|
dependencies = [
|
||||||
|
"portable-atomic",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "powerfmt"
|
name = "powerfmt"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
@@ -4317,7 +4419,7 @@ dependencies = [
|
|||||||
"axum-test",
|
"axum-test",
|
||||||
"chrono",
|
"chrono",
|
||||||
"imphnen-entities",
|
"imphnen-entities",
|
||||||
"imphnen-iam-service",
|
"imphnen-iam",
|
||||||
"imphnen-libs",
|
"imphnen-libs",
|
||||||
"imphnen-utils",
|
"imphnen-utils",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
@@ -4765,6 +4867,12 @@ version = "1.0.4"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "utf8parse"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "utoipa"
|
name = "utoipa"
|
||||||
version = "5.3.1"
|
version = "5.3.1"
|
||||||
|
|||||||
+12
-11
@@ -1,18 +1,16 @@
|
|||||||
[workspace]
|
[workspace]
|
||||||
resolver = "2"
|
resolver = "2"
|
||||||
members = [
|
members = [
|
||||||
# "imphnen-*",
|
|
||||||
"imphnen-utils",
|
|
||||||
"imphnen-libs",
|
|
||||||
"imphnen-entities",
|
|
||||||
"imphnen-cms-service",
|
|
||||||
"imphnen-iam-service",
|
|
||||||
"imphnen-gacha-service",
|
|
||||||
"imphnen-gateway-service",
|
|
||||||
"imphnen-dimentorin-service",
|
|
||||||
"imphnen-middleware-service",
|
|
||||||
"imphnen-core-service",
|
|
||||||
"tests",
|
"tests",
|
||||||
|
"imphnen-iam",
|
||||||
|
"imphnen-libs",
|
||||||
|
"imphnen-utils",
|
||||||
|
"imphnen-gacha",
|
||||||
|
"imphnen-gateway",
|
||||||
|
"imphnen-backend",
|
||||||
|
"imphnen-entities",
|
||||||
|
"imphnen-dimentorin",
|
||||||
|
"imphnen-middleware",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -38,6 +36,9 @@ lazy_static = "1.4.0"
|
|||||||
regex = "1.11.1"
|
regex = "1.11.1"
|
||||||
axum-test = "17.2.0"
|
axum-test = "17.2.0"
|
||||||
fancy-regex = "0.14.0"
|
fancy-regex = "0.14.0"
|
||||||
|
futures = "0.3.31"
|
||||||
|
tower = "0.5.2"
|
||||||
|
env_logger = "0.11.8"
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
lto = "fat"
|
lto = "fat"
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "imphnen-dimentorin-service"
|
name = "imphnen-backend"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2021"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
|
||||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||||
|
imphnen-gateway = { version = "0.1.0", path = "../imphnen-gateway" }
|
||||||
|
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||||
|
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@@ -21,4 +23,5 @@ tokio.workspace = true
|
|||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
utoipa-swagger-ui.workspace = true
|
utoipa-swagger-ui.workspace = true
|
||||||
|
env_logger.workspace = true
|
||||||
@@ -1,8 +1,10 @@
|
|||||||
use imphnen_gateway_service::gateway_service;
|
use env_logger;
|
||||||
|
use imphnen_gateway::gateway_service;
|
||||||
use imphnen_libs::axum_init;
|
use imphnen_libs::axum_init;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() {
|
||||||
|
env_logger::init();
|
||||||
axum_init(|surrealdb_ws, surrealdb_mem| async {
|
axum_init(|surrealdb_ws, surrealdb_mem| async {
|
||||||
gateway_service(surrealdb_ws, surrealdb_mem).await
|
gateway_service(surrealdb_ws, surrealdb_mem).await
|
||||||
})
|
})
|
||||||
+13
-13
@@ -25,8 +25,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
(
|
(
|
||||||
"0269ed71-0ae0-4c43-ad29-e3d861d8f9a0",
|
"0269ed71-0ae0-4c43-ad29-e3d861d8f9a0",
|
||||||
"Create Permissions",
|
"Create Permissions",
|
||||||
None,
|
Some("2025-01-29T05:11:01.265+00"),
|
||||||
None,
|
Some("2025-01-29T05:11:01.265001+00"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b",
|
"299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b",
|
||||||
@@ -37,20 +37,20 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
(
|
(
|
||||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a2",
|
"319ee593-ff0a-4f29-bbaf-9feb3174a3a2",
|
||||||
"Create Roles",
|
"Create Roles",
|
||||||
None,
|
Some("2025-01-29T05:11:01.265+00"),
|
||||||
None,
|
Some("2025-01-29T05:11:01.265001+00"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6",
|
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6",
|
||||||
"Read Detail Users",
|
"Read Detail Users",
|
||||||
None,
|
Some("2025-01-29T05:11:01.265+00"),
|
||||||
None,
|
Some("2025-01-29T05:11:01.265001+00"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"35b0d992-65c8-4b62-b030-e6e0320e4048",
|
"35b0d992-65c8-4b62-b030-e6e0320e4048",
|
||||||
"Delete Roles",
|
"Delete Roles",
|
||||||
None,
|
Some("2025-01-29T05:34:40.621554+00"),
|
||||||
None,
|
Some("2025-01-29T05:34:40.621555+00"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"4da8b434-89f9-4d91-85ae-eebd63cdbeda",
|
"4da8b434-89f9-4d91-85ae-eebd63cdbeda",
|
||||||
@@ -91,8 +91,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
(
|
(
|
||||||
"9164ca6e-c7e3-4238-a15f-f36ab9577e7e",
|
"9164ca6e-c7e3-4238-a15f-f36ab9577e7e",
|
||||||
"Read List Roles",
|
"Read List Roles",
|
||||||
None,
|
Some("2025-01-29T05:34:40.621554+00"),
|
||||||
None,
|
Some("2025-01-29T05:34:40.621555+00"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"96df0689-2ae9-4894-bf00-837c19415e5c",
|
"96df0689-2ae9-4894-bf00-837c19415e5c",
|
||||||
@@ -109,8 +109,8 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
(
|
(
|
||||||
"a00d5608-4c48-4542-845c-dfe004687022",
|
"a00d5608-4c48-4542-845c-dfe004687022",
|
||||||
"Update Roles",
|
"Update Roles",
|
||||||
None,
|
Some("2025-01-29T05:34:40.621554+00"),
|
||||||
None,
|
Some("2025-01-29T05:34:40.621555+00"),
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
"b2dc3928-86ba-4c59-a03d-0b57d5183ebc",
|
"b2dc3928-86ba-4c59-a03d-0b57d5183ebc",
|
||||||
@@ -140,6 +140,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
.await?;
|
.await?;
|
||||||
println!("✅ Inserted: {}", name);
|
println!("✅ Inserted: {}", name);
|
||||||
}
|
}
|
||||||
println!("✅ Semua permissions berhasil disimpan ke SurrealDB!");
|
println!("✅ All Permissions seeded");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -65,6 +65,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
.await?;
|
.await?;
|
||||||
println!("✅ Inserted role: {}", name);
|
println!("✅ Inserted role: {}", name);
|
||||||
}
|
}
|
||||||
println!("✅ Semua role berhasil disimpan ke SurrealDB!");
|
println!("✅ All Roles seeded");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,57 @@
|
|||||||
|
use imphnen_iam::{get_iso_date, make_thing, Env};
|
||||||
|
use std::error::Error;
|
||||||
|
use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal};
|
||||||
|
|
||||||
|
#[tokio::main]
|
||||||
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
|
let env = Env::new();
|
||||||
|
let db = Surreal::new::<Ws>(env.surrealdb_url).await?;
|
||||||
|
|
||||||
|
db.signin(Root {
|
||||||
|
username: &env.surrealdb_username,
|
||||||
|
password: &env.surrealdb_password,
|
||||||
|
})
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
db.use_ns(env.surrealdb_namespace)
|
||||||
|
.use_db(env.surrealdb_dbname)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let admin_permissions = vec![
|
||||||
|
"023e2dfe-93c3-4008-94a8-b5dff403f73b",
|
||||||
|
"0269ed71-0ae0-4c43-ad29-e3d861d8f9a0",
|
||||||
|
"299cb4d5-6556-4cc9-b6c1-32e6d31e0f9b",
|
||||||
|
"319ee593-ff0a-4f29-bbaf-9feb3174a3a2",
|
||||||
|
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6",
|
||||||
|
"35b0d992-65c8-4b62-b030-e6e0320e4048",
|
||||||
|
"4da8b434-89f9-4d91-85ae-eebd63cdbeda",
|
||||||
|
"73888d18-b3e9-4f62-95a5-ba2c0d69fccb",
|
||||||
|
"7c15e31d-36e2-49f9-97db-138c03fb0cf6",
|
||||||
|
"7d4b1379-4960-416a-b045-98cd82c0cac9",
|
||||||
|
"8195eeb8-e64f-4172-aa57-596492c84a72",
|
||||||
|
"81eba91d-b8ab-44b9-bbfe-4e6da2f98952",
|
||||||
|
"9164ca6e-c7e3-4238-a15f-f36ab9577e7e",
|
||||||
|
"96df0689-2ae9-4894-bf00-837c19415e5c",
|
||||||
|
"98b3dc4c-0124-461f-afcd-166637c5e6e8",
|
||||||
|
"a00d5608-4c48-4542-845c-dfe004687022",
|
||||||
|
"b2dc3928-86ba-4c59-a03d-0b57d5183ebc",
|
||||||
|
"dad435cf-042c-41bd-a946-cea61ed2ffbc",
|
||||||
|
];
|
||||||
|
|
||||||
|
let admin_role_id = "f6b03f25-e416-4893-ac88-caaa690afb07";
|
||||||
|
|
||||||
|
let permission_refs_admin: Vec<_> = admin_permissions
|
||||||
|
.into_iter()
|
||||||
|
.map(|perm_id| make_thing("app_permissions", perm_id))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
db.query("UPDATE type::thing('app_roles', $role_id) SET permissions = $permissions, updated_at = $updated_at WHERE is_deleted = false")
|
||||||
|
.bind(("role_id", admin_role_id))
|
||||||
|
.bind(("permissions", permission_refs_admin))
|
||||||
|
.bind(("updated_at", get_iso_date()))
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
println!("✅ All permissions successfully added to each role");
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,31 +1,8 @@
|
|||||||
|
use imphnen_iam::UsersSchema;
|
||||||
use imphnen_utils::{get_iso_date, hash_password, Env};
|
use imphnen_utils::{get_iso_date, hash_password, Env};
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, sql::Thing, Surreal};
|
use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, sql::Thing, Surreal};
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct UsersSchema {
|
|
||||||
pub id: Thing,
|
|
||||||
pub fullname: String,
|
|
||||||
pub email: String,
|
|
||||||
pub password: String,
|
|
||||||
pub avatar: Option<String>,
|
|
||||||
pub phone_number: String,
|
|
||||||
pub referral_code: Option<String>,
|
|
||||||
pub referred_by: Option<String>,
|
|
||||||
pub identity_number: Option<String>,
|
|
||||||
pub is_active: bool,
|
|
||||||
pub is_deleted: bool,
|
|
||||||
pub student_type: String,
|
|
||||||
pub religion: Option<String>,
|
|
||||||
pub gender: Option<String>,
|
|
||||||
pub birthdate: Option<String>,
|
|
||||||
pub is_profile_completed: bool,
|
|
||||||
pub role: Thing,
|
|
||||||
pub created_at: String,
|
|
||||||
pub updated_at: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
async fn main() -> Result<(), Box<dyn Error>> {
|
||||||
let env = Env::new();
|
let env = Env::new();
|
||||||
@@ -68,16 +45,10 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
password: hash_password("password").unwrap(),
|
password: hash_password("password").unwrap(),
|
||||||
avatar: None,
|
avatar: None,
|
||||||
phone_number: "081234567890".into(),
|
phone_number: "081234567890".into(),
|
||||||
referral_code: None,
|
|
||||||
referred_by: None,
|
|
||||||
identity_number: None,
|
|
||||||
is_active: true,
|
is_active: true,
|
||||||
is_deleted: false,
|
is_deleted: false,
|
||||||
student_type: "TNI".into(),
|
|
||||||
religion: None,
|
|
||||||
gender: None,
|
gender: None,
|
||||||
birthdate: None,
|
birthdate: None,
|
||||||
is_profile_completed: false,
|
|
||||||
role: Thing::from(("app_roles", role_id)),
|
role: Thing::from(("app_roles", role_id)),
|
||||||
created_at: get_iso_date(),
|
created_at: get_iso_date(),
|
||||||
updated_at: get_iso_date(),
|
updated_at: get_iso_date(),
|
||||||
@@ -90,6 +61,6 @@ async fn main() -> Result<(), Box<dyn Error>> {
|
|||||||
println!("✅ Inserted user: {} ({})", fullname, email);
|
println!("✅ Inserted user: {} ({})", fullname, email);
|
||||||
}
|
}
|
||||||
|
|
||||||
println!("✅ Semua users berhasil disimpan ke SurrealDB!");
|
println!("✅ All Users seeded");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use imphnen_gateway_service::gateway_service;
|
use imphnen_gateway::gateway_service;
|
||||||
use imphnen_libs::axum_init;
|
use imphnen_libs::axum_init;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
@@ -1,59 +0,0 @@
|
|||||||
use imphnen_utils::{get_iso_date, make_thing, Env};
|
|
||||||
use std::error::Error;
|
|
||||||
use surrealdb::{engine::remote::ws::Ws, opt::auth::Root, Surreal};
|
|
||||||
|
|
||||||
#[tokio::main]
|
|
||||||
async fn main() -> Result<(), Box<dyn Error>> {
|
|
||||||
let env = Env::new();
|
|
||||||
let db = Surreal::new::<Ws>(env.surrealdb_url).await?;
|
|
||||||
|
|
||||||
db.signin(Root {
|
|
||||||
username: &env.surrealdb_username,
|
|
||||||
password: &env.surrealdb_password,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
db.use_ns(env.surrealdb_namespace)
|
|
||||||
.use_db(env.surrealdb_dbname)
|
|
||||||
.await?;
|
|
||||||
|
|
||||||
let role_permissions = vec![
|
|
||||||
(
|
|
||||||
"50133429-f4b1-4249-9f97-7b86e6ee9d86", // Staf
|
|
||||||
vec![
|
|
||||||
"7c15e31d-36e2-49f9-97db-138c03fb0cf6", // Read List Users
|
|
||||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6", // Read Detail Users
|
|
||||||
],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"f6b03f25-e416-4893-ac88-caaa690afb07", // Admin
|
|
||||||
vec![
|
|
||||||
"023e2dfe-93c3-4008-94a8-b5dff403f73b", // Create Users
|
|
||||||
"96df0689-2ae9-4894-bf00-837c19415e5c", // Delete Users
|
|
||||||
"98b3dc4c-0124-461f-afcd-166637c5e6e8", // Update Users
|
|
||||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a6", // Read Detail Users
|
|
||||||
"7c15e31d-36e2-49f9-97db-138c03fb0cf6", // Read List Users
|
|
||||||
"9164ca6e-c7e3-4238-a15f-f36ab9577e7e", // Read List Roles
|
|
||||||
"319ee593-ff0a-4f29-bbaf-9feb3174a3a2", // Create Roles
|
|
||||||
"a00d5608-4c48-4542-845c-dfe004687022", // Update Roles
|
|
||||||
"35b0d992-65c8-4b62-b030-e6e0320e4048", // Delete Roles
|
|
||||||
],
|
|
||||||
),
|
|
||||||
];
|
|
||||||
for (role_id, permission_ids) in role_permissions {
|
|
||||||
let permission_refs: Vec<_> = permission_ids
|
|
||||||
.into_iter()
|
|
||||||
.map(|perm_id| make_thing("app_permissions", perm_id))
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
db.query("UPDATE type::thing('app_roles', $role_id) SET permissions = $permissions, updated_at = $updated_at")
|
|
||||||
.bind(("role_id", role_id))
|
|
||||||
.bind(("permissions", permission_refs))
|
|
||||||
.bind(("updated_at", get_iso_date()))
|
|
||||||
.await?;
|
|
||||||
println!("✅ Updated permissions for role ID: {}", role_id);
|
|
||||||
}
|
|
||||||
println!("✅ Semua roles telah diperbarui dengan permissions di SurrealDB!");
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
pub fn add(left: u64, right: u64) -> u64 {
|
|
||||||
left + right
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn it_works() {
|
|
||||||
let result = add(2, 2);
|
|
||||||
assert_eq!(result, 4);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "imphnen-cms-service"
|
name = "imphnen-dimentorin"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
|
||||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||||
|
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@@ -21,4 +21,4 @@ tokio.workspace = true
|
|||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
utoipa-swagger-ui.workspace = true
|
utoipa-swagger-ui.workspace = true
|
||||||
@@ -6,6 +6,6 @@ edition = "2024"
|
|||||||
[dependencies]
|
[dependencies]
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
surrealdb = { workspace = true, features = ["kv-mem"] }
|
|
||||||
thiserror.workspace = true
|
|
||||||
utoipa.workspace = true
|
utoipa.workspace = true
|
||||||
|
surrealdb.workspace = true
|
||||||
|
thiserror.workspace = true
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "imphnen-iam-service"
|
name = "imphnen-gacha"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
|
||||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||||
|
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@@ -21,4 +21,4 @@ tokio.workspace = true
|
|||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
utoipa-swagger-ui.workspace = true
|
utoipa-swagger-ui.workspace = true
|
||||||
+3
@@ -28,6 +28,9 @@ impl<'a> GachaItemRepository<'a> {
|
|||||||
&meta,
|
&meta,
|
||||||
conditions,
|
conditions,
|
||||||
None,
|
None,
|
||||||
|
"name",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
}
|
}
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
[package]
|
|
||||||
name = "imphnen-gateway-service"
|
|
||||||
version = "0.1.0"
|
|
||||||
edition = "2024"
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" }
|
|
||||||
imphnen-middleware-service = { version = "0.1.0", path = "../imphnen-middleware-service" }
|
|
||||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
|
||||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
|
||||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
|
||||||
axum.workspace = true
|
|
||||||
serde.workspace = true
|
|
||||||
serde_json.workspace = true
|
|
||||||
utoipa.workspace = true
|
|
||||||
lazy_static.workspace = true
|
|
||||||
regex.workspace = true
|
|
||||||
validator.workspace = true
|
|
||||||
axum-test.workspace = true
|
|
||||||
surrealdb.workspace = true
|
|
||||||
rand.workspace = true
|
|
||||||
tokio.workspace = true
|
|
||||||
chrono.workspace = true
|
|
||||||
anyhow.workspace = true
|
|
||||||
tower-http.workspace = true
|
|
||||||
utoipa-swagger-ui.workspace = true
|
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "imphnen-backend-service"
|
name = "imphnen-gateway"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||||
imphnen-gateway-service = { version = "0.1.0", path = "../imphnen-gateway-service" }
|
imphnen-middleware = { version = "0.1.0", path = "../imphnen-middleware" }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
use imphnen_iam_service::{
|
use imphnen_iam::{
|
||||||
auth, permissions, roles, users, AuthLoginRequestDto, AuthLoginResponsetDto, AuthResendOtpRequestDto, AuthVerifyEmailRequestDto,
|
auth, permissions, roles, users, AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, AuthResendOtpRequestDto, AuthVerifyEmailRequestDto, MessageResponseDto, MetaRequestDto, MetaResponseDto, PermissionsItemDto, PermissionsRequestDto, ResponseListSuccessDto, ResponseSuccessDto, RolesDetailItemDto, RolesListItemDto, RolesRequestCreateDto, RolesRequestUpdateDto, TokenDto, UsersCreateRequestDto, UsersDetailItemDto, UsersListItemDto, UsersUpdateRequestDto
|
||||||
AuthNewPasswordRequestDto, AuthRefreshTokenRequestDto, MessageResponseDto, MetaRequestDto, MetaResponseDto, PermissionsItemDto, PermissionsRequestDto, ResponseListSuccessDto, ResponseSuccessDto, RolesItemDto, RolesRequestCreateDto, RolesRequestUpdateDto, TokenDto, UsersCreateRequestDto, UsersDetailItemDto, UsersItemDto, UsersListItemDto, UsersUpdateRequestDto
|
|
||||||
};
|
};
|
||||||
use utoipa::{
|
use utoipa::{
|
||||||
openapi::security::{Http, HttpAuthScheme, SecurityScheme},
|
openapi::security::{Http, HttpAuthScheme, SecurityScheme},
|
||||||
@@ -48,18 +47,18 @@ use utoipa::{
|
|||||||
AuthNewPasswordRequestDto,
|
AuthNewPasswordRequestDto,
|
||||||
AuthRefreshTokenRequestDto,
|
AuthRefreshTokenRequestDto,
|
||||||
ResponseSuccessDto<TokenDto>,
|
ResponseSuccessDto<TokenDto>,
|
||||||
RolesItemDto,
|
RolesListItemDto,
|
||||||
RolesRequestCreateDto,
|
RolesRequestCreateDto,
|
||||||
RolesRequestUpdateDto,
|
RolesRequestUpdateDto,
|
||||||
PermissionsRequestDto,
|
PermissionsRequestDto,
|
||||||
PermissionsItemDto,
|
PermissionsItemDto,
|
||||||
UsersItemDto,
|
UsersDetailItemDto,
|
||||||
UsersListItemDto,
|
UsersListItemDto,
|
||||||
UsersUpdateRequestDto,
|
UsersUpdateRequestDto,
|
||||||
UsersCreateRequestDto,
|
UsersCreateRequestDto,
|
||||||
ResponseSuccessDto<AuthLoginResponsetDto>,
|
ResponseSuccessDto<AuthLoginResponsetDto>,
|
||||||
ResponseListSuccessDto<Vec<RolesItemDto>>,
|
ResponseListSuccessDto<Vec<RolesListItemDto>>,
|
||||||
ResponseSuccessDto<RolesItemDto>,
|
ResponseSuccessDto<RolesDetailItemDto>,
|
||||||
ResponseListSuccessDto<Vec<UsersListItemDto>>,
|
ResponseListSuccessDto<Vec<UsersListItemDto>>,
|
||||||
ResponseSuccessDto<UsersDetailItemDto>,
|
ResponseSuccessDto<UsersDetailItemDto>,
|
||||||
ResponseListSuccessDto<Vec<PermissionsItemDto>>,
|
ResponseListSuccessDto<Vec<PermissionsItemDto>>,
|
||||||
@@ -102,4 +101,4 @@ impl Modify for SecurityAddon {
|
|||||||
|
|
||||||
pub fn docs_router() -> utoipa::openapi::OpenApi {
|
pub fn docs_router() -> utoipa::openapi::OpenApi {
|
||||||
ApiDoc::openapi()
|
ApiDoc::openapi()
|
||||||
}
|
}
|
||||||
@@ -2,12 +2,12 @@ use axum::{
|
|||||||
Extension, Router, middleware::from_fn, response::Redirect, routing::get,
|
Extension, Router, middleware::from_fn, response::Redirect, routing::get,
|
||||||
};
|
};
|
||||||
use imphnen_entities::{AppState, SurrealMemClient, SurrealWsClient};
|
use imphnen_entities::{AppState, SurrealMemClient, SurrealWsClient};
|
||||||
use imphnen_iam_service::{iam_protected_routes, iam_public_routes};
|
use imphnen_iam::{iam_protected_routes, iam_public_routes};
|
||||||
|
use imphnen_middleware::{auth_middleware, cors_middleware};
|
||||||
|
use utoipa_swagger_ui::SwaggerUi;
|
||||||
|
|
||||||
pub mod docs;
|
pub mod docs;
|
||||||
pub use docs::*;
|
pub use docs::*;
|
||||||
use imphnen_middleware_service::{auth_middleware, cors_middleware};
|
|
||||||
use utoipa_swagger_ui::SwaggerUi;
|
|
||||||
|
|
||||||
pub async fn gateway_service(
|
pub async fn gateway_service(
|
||||||
surrealdb_ws: SurrealWsClient,
|
surrealdb_ws: SurrealWsClient,
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use surrealdb::{sql::Thing, Uuid};
|
|
||||||
|
|
||||||
use crate::{make_thing, ResourceEnum};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct RolesSchema {
|
|
||||||
pub id: Thing,
|
|
||||||
pub name: String,
|
|
||||||
pub is_deleted: bool,
|
|
||||||
pub permissions: Vec<Thing>,
|
|
||||||
pub created_at: Option<String>,
|
|
||||||
pub updated_at: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for RolesSchema {
|
|
||||||
fn default() -> Self {
|
|
||||||
RolesSchema {
|
|
||||||
id: make_thing(
|
|
||||||
&ResourceEnum::Roles.to_string(),
|
|
||||||
&Uuid::new_v4().to_string(),
|
|
||||||
),
|
|
||||||
permissions: vec![make_thing(
|
|
||||||
&ResourceEnum::Permissions.to_string(),
|
|
||||||
&Uuid::new_v4().to_string(),
|
|
||||||
)],
|
|
||||||
name: String::new(),
|
|
||||||
is_deleted: false,
|
|
||||||
created_at: None,
|
|
||||||
updated_at: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,317 +0,0 @@
|
|||||||
use super::{
|
|
||||||
UsersActiveInactiveSchema, UsersItemDto, UsersItemDtoRaw, UsersListItemDto,
|
|
||||||
UsersListItemDtoRaw, UsersSchema, UsersSetNewPasswordSchema,
|
|
||||||
};
|
|
||||||
use crate::{
|
|
||||||
extract_id, get_id, make_thing, query_list_with_meta, AppState, MetaRequestDto,
|
|
||||||
PermissionsItemDto, PermissionsItemDtoRaw, ResourceEnum, ResponseListSuccessDto,
|
|
||||||
RolesItemDto, RolesItemDtoRaw,
|
|
||||||
};
|
|
||||||
use anyhow::{anyhow, bail, Result};
|
|
||||||
|
|
||||||
pub struct UsersRepository<'a> {
|
|
||||||
state: &'a AppState,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> UsersRepository<'a> {
|
|
||||||
pub fn new(state: &'a AppState) -> Self {
|
|
||||||
Self { state }
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_user_list(
|
|
||||||
&self,
|
|
||||||
meta: MetaRequestDto,
|
|
||||||
) -> Result<ResponseListSuccessDto<Vec<UsersListItemDto>>> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let mut conditions = vec!["is_deleted = false".to_string()];
|
|
||||||
if let Some(search) = meta.search.as_deref() {
|
|
||||||
if !search.is_empty() {
|
|
||||||
conditions.push("string::contains(fullname ?? '', $search)".to_string());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if let (Some(filter_by), Some(_filter)) =
|
|
||||||
(meta.filter_by.as_ref(), meta.filter.as_ref())
|
|
||||||
{
|
|
||||||
conditions.push(format!("{} = $filter", filter_by));
|
|
||||||
}
|
|
||||||
let where_clause = if !conditions.is_empty() {
|
|
||||||
format!("WHERE {}", conditions.join(" AND "))
|
|
||||||
} else {
|
|
||||||
String::new()
|
|
||||||
};
|
|
||||||
let limit = meta.per_page.unwrap_or(10);
|
|
||||||
let page = meta.page.unwrap_or(1);
|
|
||||||
if page < 1 || limit < 1 {
|
|
||||||
return Err(anyhow!("Invalid pagination parameters"));
|
|
||||||
}
|
|
||||||
let start = (page - 1) * limit;
|
|
||||||
let select_query = format!(
|
|
||||||
"
|
|
||||||
SELECT
|
|
||||||
id,
|
|
||||||
role.name AS role,
|
|
||||||
fullname,
|
|
||||||
email,
|
|
||||||
avatar,
|
|
||||||
phone_number,
|
|
||||||
is_active,
|
|
||||||
gender,
|
|
||||||
birthdate,
|
|
||||||
created_at,
|
|
||||||
updated_at
|
|
||||||
FROM {}
|
|
||||||
{}
|
|
||||||
LIMIT {} START {}
|
|
||||||
FETCH role
|
|
||||||
",
|
|
||||||
ResourceEnum::Users.to_string(),
|
|
||||||
where_clause,
|
|
||||||
limit,
|
|
||||||
start
|
|
||||||
);
|
|
||||||
let raw_result = query_list_with_meta::<UsersListItemDtoRaw>(
|
|
||||||
db,
|
|
||||||
&ResourceEnum::Users.to_string(),
|
|
||||||
&meta,
|
|
||||||
vec![],
|
|
||||||
Some(select_query),
|
|
||||||
)
|
|
||||||
.await?;
|
|
||||||
let transformed_data = raw_result
|
|
||||||
.data
|
|
||||||
.into_iter()
|
|
||||||
.map(|user| UsersListItemDto {
|
|
||||||
id: extract_id(&user.id),
|
|
||||||
fullname: user.fullname,
|
|
||||||
email: user.email,
|
|
||||||
avatar: user.avatar,
|
|
||||||
phone_number: user.phone_number,
|
|
||||||
is_active: user.is_active,
|
|
||||||
role: user.role.unwrap_or_else(|| "-".into()),
|
|
||||||
created_at: user.created_at,
|
|
||||||
updated_at: user.updated_at,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
Ok(ResponseListSuccessDto {
|
|
||||||
data: transformed_data,
|
|
||||||
meta: raw_result.meta,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_user_by_email(&self, email: String) -> Result<UsersItemDtoRaw> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let sql = format!(
|
|
||||||
"SELECT *, role AS role FROM {} WHERE email = $email AND is_deleted = false LIMIT 1 FETCH role, role.permissions",
|
|
||||||
ResourceEnum::Users.to_string()
|
|
||||||
);
|
|
||||||
|
|
||||||
let response: Option<UsersItemDtoRaw> = db
|
|
||||||
.query(sql)
|
|
||||||
.bind(("email", email.clone()))
|
|
||||||
.await?
|
|
||||||
.take(0)?;
|
|
||||||
|
|
||||||
match response {
|
|
||||||
Some(user) if !user.role.is_deleted => {
|
|
||||||
let permissions = user
|
|
||||||
.role
|
|
||||||
.permissions
|
|
||||||
.into_iter()
|
|
||||||
.map(|perm| PermissionsItemDtoRaw {
|
|
||||||
id: perm.id,
|
|
||||||
name: perm.name,
|
|
||||||
created_at: perm.created_at,
|
|
||||||
updated_at: perm.updated_at,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
Ok(UsersItemDtoRaw {
|
|
||||||
id: user.id,
|
|
||||||
fullname: user.fullname,
|
|
||||||
email: user.email,
|
|
||||||
avatar: user.avatar,
|
|
||||||
phone_number: user.phone_number,
|
|
||||||
is_active: user.is_active,
|
|
||||||
is_deleted: user.is_deleted,
|
|
||||||
gender: user.gender,
|
|
||||||
birthdate: user.birthdate,
|
|
||||||
password: user.password,
|
|
||||||
created_at: user.created_at,
|
|
||||||
updated_at: user.updated_at,
|
|
||||||
role: RolesItemDtoRaw {
|
|
||||||
id: user.role.id,
|
|
||||||
name: user.role.name,
|
|
||||||
permissions,
|
|
||||||
created_at: user.role.created_at,
|
|
||||||
updated_at: user.role.updated_at,
|
|
||||||
is_deleted: user.role.is_deleted,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => bail!("User not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_user_by_id(&self, id: String) -> Result<UsersItemDto> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
|
|
||||||
let query = format!(
|
|
||||||
"SELECT *, role AS role FROM app_users:⟨{}⟩ FETCH role, role.permissions",
|
|
||||||
id
|
|
||||||
);
|
|
||||||
|
|
||||||
let mut result = db.query(query).await?;
|
|
||||||
|
|
||||||
let response: Option<UsersItemDtoRaw> = result.take(0)?;
|
|
||||||
|
|
||||||
match response {
|
|
||||||
Some(user) if !user.role.is_deleted => {
|
|
||||||
let permissions = user
|
|
||||||
.role
|
|
||||||
.permissions
|
|
||||||
.into_iter()
|
|
||||||
.map(|perm| PermissionsItemDto {
|
|
||||||
id: extract_id(&perm.id),
|
|
||||||
name: perm.name,
|
|
||||||
created_at: perm.created_at,
|
|
||||||
updated_at: perm.updated_at,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
Ok(UsersItemDto {
|
|
||||||
id: extract_id(&user.id),
|
|
||||||
fullname: user.fullname,
|
|
||||||
email: user.email,
|
|
||||||
avatar: user.avatar,
|
|
||||||
phone_number: user.phone_number,
|
|
||||||
is_active: user.is_active,
|
|
||||||
is_deleted: user.is_deleted,
|
|
||||||
gender: user.gender,
|
|
||||||
birthdate: user.birthdate,
|
|
||||||
password: user.password,
|
|
||||||
role: RolesItemDto {
|
|
||||||
id: extract_id(&user.role.id),
|
|
||||||
name: user.role.name,
|
|
||||||
permissions,
|
|
||||||
created_at: user.role.created_at,
|
|
||||||
updated_at: user.role.updated_at,
|
|
||||||
},
|
|
||||||
created_at: user.created_at,
|
|
||||||
updated_at: user.updated_at,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
_ => bail!("User not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_create_user(&self, data: UsersSchema) -> Result<String> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let record: Option<UsersSchema> = db
|
|
||||||
.create(ResourceEnum::Users.to_string())
|
|
||||||
.content(data)
|
|
||||||
.await?;
|
|
||||||
match record {
|
|
||||||
Some(_) => Ok("Success create user".into()),
|
|
||||||
None => bail!("Failed to create user"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_update_user(&self, data: UsersSchema) -> Result<String> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let record_key = get_id(&data.id)?;
|
|
||||||
let existing = self.query_user_by_id(data.id.id.to_raw()).await?;
|
|
||||||
if existing.is_deleted {
|
|
||||||
bail!("User already deleted");
|
|
||||||
}
|
|
||||||
let merged = UsersSchema {
|
|
||||||
password: existing.password,
|
|
||||||
created_at: existing.created_at,
|
|
||||||
role: make_thing("app_roles", &existing.role.id),
|
|
||||||
..data.clone()
|
|
||||||
};
|
|
||||||
let record: Option<UsersSchema> = db.update(record_key).merge(merged).await?;
|
|
||||||
match record {
|
|
||||||
Some(_) => Ok("Success update user".into()),
|
|
||||||
None => bail!("Failed to update user"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_active_inactive_user(
|
|
||||||
&self,
|
|
||||||
email: String,
|
|
||||||
data: UsersActiveInactiveSchema,
|
|
||||||
) -> Result<String> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let user = self.query_user_by_email(email.clone()).await?;
|
|
||||||
if user.is_deleted {
|
|
||||||
bail!("User already deleted");
|
|
||||||
}
|
|
||||||
let record_key = get_id(&user.id)?;
|
|
||||||
let record: Option<UsersSchema> = db
|
|
||||||
.update(record_key)
|
|
||||||
.merge(UsersActiveInactiveSchema {
|
|
||||||
is_active: data.is_active,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
match record {
|
|
||||||
Some(_) => Ok("Success update user".into()),
|
|
||||||
None => bail!("Failed to update user"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_active_inactive_user_by_id(
|
|
||||||
&self,
|
|
||||||
id: String,
|
|
||||||
data: UsersActiveInactiveSchema,
|
|
||||||
) -> Result<String> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let record: Option<UsersSchema> = db
|
|
||||||
.update((ResourceEnum::Users.to_string(), id))
|
|
||||||
.merge(UsersActiveInactiveSchema {
|
|
||||||
is_active: data.is_active,
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
match record {
|
|
||||||
Some(_) => Ok("Success update user".into()),
|
|
||||||
None => bail!("Failed to update user"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_update_password_user(
|
|
||||||
&self,
|
|
||||||
email: String,
|
|
||||||
data: UsersSetNewPasswordSchema,
|
|
||||||
) -> Result<String> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let user = self.query_user_by_email(email).await?;
|
|
||||||
let record: Option<UsersSetNewPasswordSchema> = db
|
|
||||||
.update((ResourceEnum::Users.to_string(), user.id.id.to_raw()))
|
|
||||||
.merge(UsersSetNewPasswordSchema {
|
|
||||||
password: data.password.clone(),
|
|
||||||
})
|
|
||||||
.await?;
|
|
||||||
dbg!(record.clone());
|
|
||||||
match record {
|
|
||||||
Some(_) => Ok("Success update password user".into()),
|
|
||||||
None => bail!("Failed to update password user"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_delete_user(&self, id: String) -> Result<String> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let user_id = make_thing(&ResourceEnum::Users.to_string(), &id);
|
|
||||||
let user = self.query_user_by_id(user_id.id.to_raw()).await?;
|
|
||||||
if user.is_deleted {
|
|
||||||
bail!("User already deleted");
|
|
||||||
}
|
|
||||||
let id = make_thing(&ResourceEnum::Users.to_string(), &user.id);
|
|
||||||
let record_key = get_id(&id)?;
|
|
||||||
let record: Option<UsersSchema> = db
|
|
||||||
.update(record_key)
|
|
||||||
.merge(serde_json::json!({ "is_deleted": true }))
|
|
||||||
.await?;
|
|
||||||
match record {
|
|
||||||
Some(_) => Ok("Success delete user".into()),
|
|
||||||
None => bail!("Failed to delete user"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
use crate::{get_iso_date, make_thing, ResourceEnum};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use surrealdb::{sql::Thing, Uuid};
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct UsersSchema {
|
|
||||||
pub id: Thing,
|
|
||||||
pub fullname: String,
|
|
||||||
pub email: String,
|
|
||||||
pub password: String,
|
|
||||||
pub avatar: Option<String>,
|
|
||||||
pub phone_number: String,
|
|
||||||
pub is_active: bool,
|
|
||||||
pub is_deleted: bool,
|
|
||||||
pub gender: Option<String>,
|
|
||||||
pub birthdate: Option<String>,
|
|
||||||
pub role: Thing,
|
|
||||||
pub created_at: String,
|
|
||||||
pub updated_at: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Default for UsersSchema {
|
|
||||||
fn default() -> Self {
|
|
||||||
UsersSchema {
|
|
||||||
id: make_thing(
|
|
||||||
&ResourceEnum::Users.to_string(),
|
|
||||||
&Uuid::new_v4().to_string(),
|
|
||||||
),
|
|
||||||
fullname: String::new(),
|
|
||||||
email: String::new(),
|
|
||||||
password: String::new(),
|
|
||||||
avatar: None,
|
|
||||||
phone_number: String::new(),
|
|
||||||
is_active: false,
|
|
||||||
is_deleted: false,
|
|
||||||
gender: None,
|
|
||||||
birthdate: None,
|
|
||||||
role: make_thing(
|
|
||||||
&ResourceEnum::Roles.to_string(),
|
|
||||||
&Uuid::new_v4().to_string(),
|
|
||||||
),
|
|
||||||
created_at: get_iso_date(),
|
|
||||||
updated_at: get_iso_date(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct UsersSetNewPasswordSchema {
|
|
||||||
pub password: String,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
|
||||||
pub struct UsersActiveInactiveSchema {
|
|
||||||
pub is_active: bool,
|
|
||||||
}
|
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "imphnen-gacha-service"
|
name = "imphnen-iam"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
|
||||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||||
|
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@@ -21,4 +21,4 @@ tokio.workspace = true
|
|||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
utoipa-swagger-ui.workspace = true
|
utoipa-swagger-ui.workspace = true
|
||||||
@@ -7,6 +7,7 @@ pub mod v1;
|
|||||||
|
|
||||||
pub use imphnen_entities::*;
|
pub use imphnen_entities::*;
|
||||||
pub use imphnen_libs::*;
|
pub use imphnen_libs::*;
|
||||||
|
pub use imphnen_utils::*;
|
||||||
pub use v1::*;
|
pub use v1::*;
|
||||||
|
|
||||||
pub fn create_test_user(
|
pub fn create_test_user(
|
||||||
@@ -27,6 +28,7 @@ pub fn create_test_user(
|
|||||||
gender: None,
|
gender: None,
|
||||||
birthdate: None,
|
birthdate: None,
|
||||||
role: make_thing("app_roles", role_id),
|
role: make_thing("app_roles", role_id),
|
||||||
..Default::default()
|
created_at: get_iso_date(),
|
||||||
|
updated_at: get_iso_date(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1,25 +1,29 @@
|
|||||||
use crate::RolesItemDto;
|
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
use validator::{Validate, ValidationError};
|
use validator::{Validate, ValidationError};
|
||||||
|
|
||||||
|
use crate::UsersDetailItemDto;
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PASSWORD_REGEX: Regex = Regex::new(r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$").unwrap();
|
static ref PASSWORD_REGEX: Regex = Regex::new(
|
||||||
|
r"^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]{8,}$"
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_password_complexity(password: &str) -> Result<(), ValidationError> {
|
fn validate_password_complexity(password: &str) -> Result<(), ValidationError> {
|
||||||
let has_uppercase = password.chars().any(|c| c.is_ascii_uppercase());
|
let has_uppercase = password.chars().any(|c| c.is_ascii_uppercase());
|
||||||
let has_lowercase = password.chars().any(|c| c.is_ascii_lowercase());
|
let has_lowercase = password.chars().any(|c| c.is_ascii_lowercase());
|
||||||
let has_digit = password.chars().any(|c| c.is_ascii_digit());
|
let has_digit = password.chars().any(|c| c.is_ascii_digit());
|
||||||
let has_special = password.chars().any(|c| "@$!%*?&".contains(c));
|
let has_special = password.chars().any(|c| "@$!%*?&".contains(c));
|
||||||
|
|
||||||
if has_uppercase && has_lowercase && has_digit && has_special {
|
if has_uppercase && has_lowercase && has_digit && has_special {
|
||||||
Ok(())
|
Ok(())
|
||||||
} else {
|
} else {
|
||||||
Err(ValidationError::new("complexity"))
|
Err(ValidationError::new("complexity"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
@@ -33,23 +37,10 @@ pub struct AuthLoginRequestDto {
|
|||||||
pub password: String,
|
pub password: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
|
||||||
pub struct AuthUserItemDto {
|
|
||||||
pub id: String,
|
|
||||||
pub role: RolesItemDto,
|
|
||||||
pub fullname: String,
|
|
||||||
pub email: String,
|
|
||||||
pub avatar: Option<String>,
|
|
||||||
pub phone_number: String,
|
|
||||||
pub is_active: bool,
|
|
||||||
pub gender: Option<String>,
|
|
||||||
pub birthdate: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct AuthLoginResponsetDto {
|
pub struct AuthLoginResponsetDto {
|
||||||
pub token: TokenDto,
|
pub token: TokenDto,
|
||||||
pub user: AuthUserItemDto,
|
pub user: UsersDetailItemDto,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
@@ -77,28 +68,7 @@ pub struct AuthRegisterRequestDto {
|
|||||||
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
||||||
pub fullname: String,
|
pub fullname: String,
|
||||||
#[validate(length(min = 1, message = "Student type is required"))]
|
#[validate(length(min = 1, message = "Student type is required"))]
|
||||||
pub student_type: String,
|
|
||||||
#[validate(length(
|
|
||||||
min = 10,
|
|
||||||
message = "Phone number at least have 10 character"
|
|
||||||
))]
|
|
||||||
pub phone_number: String,
|
pub phone_number: String,
|
||||||
#[validate(length(
|
|
||||||
max = 4,
|
|
||||||
message = "Referal code cannot be more than 4 character"
|
|
||||||
))]
|
|
||||||
pub referral_code: Option<String>,
|
|
||||||
pub referred_by: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
|
||||||
pub struct AuthActiveInactiveRequestDto {
|
|
||||||
pub is_active: bool,
|
|
||||||
#[validate(
|
|
||||||
length(min = 1, message = "Email cannot be empty"),
|
|
||||||
email(message = "Email not valid")
|
|
||||||
)]
|
|
||||||
pub email: String,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
+8
-8
@@ -1,6 +1,6 @@
|
|||||||
use super::AuthOtpSchema;
|
use super::AuthOtpSchema;
|
||||||
use crate::{make_thing, AppState, ResourceEnum, UsersItemDtoRaw};
|
use crate::{AppState, ResourceEnum, UsersDetailQueryDto, make_thing};
|
||||||
use anyhow::{anyhow, bail, Result};
|
use anyhow::{Result, anyhow, bail};
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
|
|
||||||
pub struct AuthRepository<'a> {
|
pub struct AuthRepository<'a> {
|
||||||
@@ -12,7 +12,7 @@ impl<'a> AuthRepository<'a> {
|
|||||||
Self { state }
|
Self { state }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn query_store_user(&self, user: UsersItemDtoRaw) -> Result<String> {
|
pub async fn query_store_user(&self, user: UsersDetailQueryDto) -> Result<String> {
|
||||||
if user.email.trim().is_empty() {
|
if user.email.trim().is_empty() {
|
||||||
bail!("Email is required");
|
bail!("Email is required");
|
||||||
}
|
}
|
||||||
@@ -22,11 +22,11 @@ impl<'a> AuthRepository<'a> {
|
|||||||
let _ = self
|
let _ = self
|
||||||
.state
|
.state
|
||||||
.surrealdb_mem
|
.surrealdb_mem
|
||||||
.delete::<Option<UsersItemDtoRaw>>((table.clone(), user_id.clone()))
|
.delete::<Option<UsersDetailQueryDto>>((table.clone(), user_id.clone()))
|
||||||
.await?;
|
.await?;
|
||||||
let mut user_to_store = user.clone();
|
let mut user_to_store = user.clone();
|
||||||
user_to_store.id = id.clone();
|
user_to_store.id = id.clone();
|
||||||
let record: Option<UsersItemDtoRaw> = self
|
let record: Option<UsersDetailQueryDto> = self
|
||||||
.state
|
.state
|
||||||
.surrealdb_mem
|
.surrealdb_mem
|
||||||
.create((table, user_id))
|
.create((table, user_id))
|
||||||
@@ -41,8 +41,8 @@ impl<'a> AuthRepository<'a> {
|
|||||||
pub async fn query_get_stored_user(
|
pub async fn query_get_stored_user(
|
||||||
&self,
|
&self,
|
||||||
email: String,
|
email: String,
|
||||||
) -> Result<UsersItemDtoRaw> {
|
) -> Result<UsersDetailQueryDto> {
|
||||||
let user: Option<UsersItemDtoRaw> = self
|
let user: Option<UsersDetailQueryDto> = self
|
||||||
.state
|
.state
|
||||||
.surrealdb_mem
|
.surrealdb_mem
|
||||||
.select((ResourceEnum::UsersCache.to_string(), email))
|
.select((ResourceEnum::UsersCache.to_string(), email))
|
||||||
@@ -54,7 +54,7 @@ impl<'a> AuthRepository<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub async fn query_delete_stored_user(&self, email: String) -> Result<String> {
|
pub async fn query_delete_stored_user(&self, email: String) -> Result<String> {
|
||||||
let record: Option<UsersItemDtoRaw> = self
|
let record: Option<UsersDetailQueryDto> = self
|
||||||
.state
|
.state
|
||||||
.surrealdb_mem
|
.surrealdb_mem
|
||||||
.delete((ResourceEnum::UsersCache.to_string(), email))
|
.delete((ResourceEnum::UsersCache.to_string(), email))
|
||||||
+48
-52
@@ -1,15 +1,15 @@
|
|||||||
use super::{
|
use super::{
|
||||||
AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto,
|
AuthLoginRequestDto, AuthLoginResponsetDto, AuthNewPasswordRequestDto,
|
||||||
AuthRefreshTokenRequestDto, AuthRegisterRequestDto, AuthRepository,
|
AuthRefreshTokenRequestDto, AuthRegisterRequestDto, AuthRepository,
|
||||||
AuthResendOtpRequestDto, AuthUserItemDto, AuthVerifyEmailRequestDto, TokenDto,
|
AuthResendOtpRequestDto, AuthVerifyEmailRequestDto, TokenDto,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
common_response, decode_refresh_token, encode_access_token, encode_refresh_token,
|
AppState, Env, ResourceEnum, ResponseSuccessDto, RolesEnum, RolesRepository,
|
||||||
|
UsersDetailItemDto, UsersRepository, UsersSchema, common_response,
|
||||||
|
decode_refresh_token, encode_access_token, encode_refresh_token,
|
||||||
encode_reset_password_token, extract_email_token, generate_otp, get_iso_date,
|
encode_reset_password_token, extract_email_token, generate_otp, get_iso_date,
|
||||||
hash_password, make_thing, send_email, success_response, validate_request,
|
hash_password, make_thing, send_email, success_response, validate_request,
|
||||||
verify_password, AppState, Env, ResourceEnum, ResponseSuccessDto, RolesEnum,
|
verify_password,
|
||||||
RolesItemDto, RolesRepository, UsersActiveInactiveSchema, UsersRepository,
|
|
||||||
UsersSchema, UsersSetNewPasswordSchema,
|
|
||||||
};
|
};
|
||||||
use axum::{http::StatusCode, response::Response};
|
use axum::{http::StatusCode, response::Response};
|
||||||
use surrealdb::Uuid;
|
use surrealdb::Uuid;
|
||||||
@@ -27,7 +27,6 @@ impl AuthService {
|
|||||||
|
|
||||||
let user_repo = UsersRepository::new(state);
|
let user_repo = UsersRepository::new(state);
|
||||||
let auth_repo = AuthRepository::new(state);
|
let auth_repo = AuthRepository::new(state);
|
||||||
let role_repo = RolesRepository::new(state);
|
|
||||||
|
|
||||||
match user_repo.query_user_by_email(payload.email.clone()).await {
|
match user_repo.query_user_by_email(payload.email.clone()).await {
|
||||||
Ok(user) => {
|
Ok(user) => {
|
||||||
@@ -54,7 +53,7 @@ impl AuthService {
|
|||||||
return common_response(
|
return common_response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
"Failed to generate access token",
|
"Failed to generate access token",
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -64,34 +63,13 @@ impl AuthService {
|
|||||||
return common_response(
|
return common_response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
"Failed to generate refresh token",
|
"Failed to generate refresh token",
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let role_response = role_repo
|
|
||||||
.query_role_by_id(user.role.id.id.to_raw())
|
|
||||||
.await
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let response = ResponseSuccessDto {
|
let response = ResponseSuccessDto {
|
||||||
data: AuthLoginResponsetDto {
|
data: AuthLoginResponsetDto {
|
||||||
user: AuthUserItemDto {
|
user: UsersDetailItemDto::from(&user),
|
||||||
id: user.id.id.to_raw(),
|
|
||||||
fullname: user.fullname.clone(),
|
|
||||||
email: user.email.clone(),
|
|
||||||
is_active: user.is_active.clone(),
|
|
||||||
avatar: user.avatar.clone(),
|
|
||||||
phone_number: user.phone_number.clone(),
|
|
||||||
gender: user.gender.clone(),
|
|
||||||
birthdate: user.birthdate.clone(),
|
|
||||||
role: RolesItemDto {
|
|
||||||
id: role_response.id,
|
|
||||||
name: role_response.name,
|
|
||||||
permissions: role_response.permissions,
|
|
||||||
created_at: role_response.created_at,
|
|
||||||
updated_at: role_response.updated_at,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
token: TokenDto {
|
token: TokenDto {
|
||||||
access_token,
|
access_token,
|
||||||
refresh_token,
|
refresh_token,
|
||||||
@@ -116,6 +94,7 @@ impl AuthService {
|
|||||||
if let Err((status, message)) = validate_request(&payload) {
|
if let Err((status, message)) = validate_request(&payload) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_repo = UsersRepository::new(state);
|
let user_repo = UsersRepository::new(state);
|
||||||
let auth_repo = AuthRepository::new(state);
|
let auth_repo = AuthRepository::new(state);
|
||||||
let role_repo = RolesRepository::new(state);
|
let role_repo = RolesRepository::new(state);
|
||||||
@@ -126,6 +105,7 @@ impl AuthService {
|
|||||||
Ok(role) => role,
|
Ok(role) => role,
|
||||||
Err(_) => return common_response(StatusCode::BAD_REQUEST, "Role Not Found"),
|
Err(_) => return common_response(StatusCode::BAD_REQUEST, "Role Not Found"),
|
||||||
};
|
};
|
||||||
|
|
||||||
if user_repo
|
if user_repo
|
||||||
.query_user_by_email(payload.email.clone())
|
.query_user_by_email(payload.email.clone())
|
||||||
.await
|
.await
|
||||||
@@ -133,6 +113,7 @@ impl AuthService {
|
|||||||
{
|
{
|
||||||
return common_response(StatusCode::BAD_REQUEST, "User already exists");
|
return common_response(StatusCode::BAD_REQUEST, "User already exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
let hashed_password = match hash_password(&payload.password) {
|
let hashed_password = match hash_password(&payload.password) {
|
||||||
Ok(hash) => hash,
|
Ok(hash) => hash,
|
||||||
Err(_) => {
|
Err(_) => {
|
||||||
@@ -142,16 +123,16 @@ impl AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_user = AuthRegisterRequestDto {
|
let new_user = AuthRegisterRequestDto {
|
||||||
email: payload.email,
|
email: payload.email,
|
||||||
password: hashed_password,
|
password: hashed_password,
|
||||||
fullname: payload.fullname,
|
fullname: payload.fullname,
|
||||||
student_type: payload.student_type,
|
|
||||||
phone_number: payload.phone_number,
|
phone_number: payload.phone_number,
|
||||||
referral_code: payload.referral_code,
|
|
||||||
referred_by: payload.referred_by,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let otp = generate_otp::OtpManager::generate_otp();
|
let otp = generate_otp::OtpManager::generate_otp();
|
||||||
|
|
||||||
match auth_repo
|
match auth_repo
|
||||||
.query_store_otp(new_user.email.clone(), otp.clone())
|
.query_store_otp(new_user.email.clone(), otp.clone())
|
||||||
.await
|
.await
|
||||||
@@ -169,11 +150,13 @@ impl AuthService {
|
|||||||
return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string());
|
return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let role_thing = make_thing(&ResourceEnum::Roles.to_string(), &role.id);
|
let role_thing = make_thing(&ResourceEnum::Roles.to_string(), &role.id);
|
||||||
let user_thing = make_thing(
|
let user_thing = make_thing(
|
||||||
&ResourceEnum::Users.to_string(),
|
&ResourceEnum::Users.to_string(),
|
||||||
&Uuid::new_v4().to_string(),
|
&Uuid::new_v4().to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
match user_repo
|
match user_repo
|
||||||
.query_create_user(UsersSchema {
|
.query_create_user(UsersSchema {
|
||||||
id: user_thing,
|
id: user_thing,
|
||||||
@@ -202,14 +185,18 @@ impl AuthService {
|
|||||||
if let Err((status, message)) = validate_request(&payload) {
|
if let Err((status, message)) = validate_request(&payload) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
|
|
||||||
let user_repo = UsersRepository::new(state);
|
let user_repo = UsersRepository::new(state);
|
||||||
if user_repo.query_user_by_email(payload.email.clone()).await.is_err() {
|
if user_repo
|
||||||
|
.query_user_by_email(payload.email.clone())
|
||||||
|
.await
|
||||||
|
.is_err()
|
||||||
|
{
|
||||||
return common_response(StatusCode::BAD_REQUEST, "User not found");
|
return common_response(StatusCode::BAD_REQUEST, "User not found");
|
||||||
}
|
}
|
||||||
|
|
||||||
let auth_repo = AuthRepository::new(state);
|
let auth_repo = AuthRepository::new(state);
|
||||||
let _ = auth_repo.query_get_stored_otp(payload.email.clone()).await;
|
let _ = auth_repo.query_get_stored_otp(payload.email.clone()).await;
|
||||||
|
|
||||||
let otp = generate_otp::OtpManager::generate_otp();
|
let otp = generate_otp::OtpManager::generate_otp();
|
||||||
let message = format!("Your OTP code is {}", otp);
|
let message = format!("Your OTP code is {}", otp);
|
||||||
@@ -273,10 +260,10 @@ impl AuthService {
|
|||||||
let user = match user_result {
|
let user = match user_result {
|
||||||
Ok(user) => user,
|
Ok(user) => user,
|
||||||
Err(err) if err.to_string().contains("User not found") => {
|
Err(err) if err.to_string().contains("User not found") => {
|
||||||
return common_response(StatusCode::BAD_REQUEST, "User not found")
|
return common_response(StatusCode::BAD_REQUEST, "User not found");
|
||||||
}
|
}
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string())
|
return common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string());
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let token = match encode_reset_password_token(user.email) {
|
let token = match encode_reset_password_token(user.email) {
|
||||||
@@ -285,7 +272,7 @@ impl AuthService {
|
|||||||
return common_response(
|
return common_response(
|
||||||
StatusCode::INTERNAL_SERVER_ERROR,
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
"Failed to generate access token",
|
"Failed to generate access token",
|
||||||
)
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
let env = Env::new();
|
let env = Env::new();
|
||||||
@@ -310,15 +297,18 @@ impl AuthService {
|
|||||||
let user_repo = UsersRepository::new(state);
|
let user_repo = UsersRepository::new(state);
|
||||||
let auth_repo = AuthRepository::new(state);
|
let auth_repo = AuthRepository::new(state);
|
||||||
let email = payload.email.clone();
|
let email = payload.email.clone();
|
||||||
|
let user = match user_repo.query_user_by_email(email.clone()).await {
|
||||||
|
Ok(user) if !user.is_deleted => user,
|
||||||
|
_ => return common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
|
};
|
||||||
|
let patch = UsersSchema {
|
||||||
|
id: user.id.clone(),
|
||||||
|
is_active: true,
|
||||||
|
..UsersSchema::from(user)
|
||||||
|
};
|
||||||
match auth_repo.query_get_stored_otp(email.clone()).await {
|
match auth_repo.query_get_stored_otp(email.clone()).await {
|
||||||
Ok(stored_otp) => match stored_otp == payload.otp {
|
Ok(stored_otp) => match stored_otp == payload.otp {
|
||||||
true => match user_repo
|
true => match user_repo.query_update_user(patch).await {
|
||||||
.query_active_inactive_user(
|
|
||||||
email.clone(),
|
|
||||||
UsersActiveInactiveSchema { is_active: true },
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(_) => match auth_repo.query_delete_stored_otp(email).await {
|
Ok(_) => match auth_repo.query_delete_stored_otp(email).await {
|
||||||
Ok(_) => common_response(StatusCode::OK, "Email verified successfully"),
|
Ok(_) => common_response(StatusCode::OK, "Email verified successfully"),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
@@ -346,7 +336,7 @@ impl AuthService {
|
|||||||
if let Err((status, message)) = validate_request(&payload) {
|
if let Err((status, message)) = validate_request(&payload) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
let user_repo = UsersRepository::new(state);
|
let repo = UsersRepository::new(state);
|
||||||
let email = match extract_email_token(payload.token.clone()) {
|
let email = match extract_email_token(payload.token.clone()) {
|
||||||
Some(email) => email,
|
Some(email) => email,
|
||||||
None => {
|
None => {
|
||||||
@@ -362,12 +352,18 @@ impl AuthService {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
match user_repo
|
let user = match repo.query_user_by_email(email.clone()).await {
|
||||||
.query_update_password_user(email, UsersSetNewPasswordSchema { password })
|
Ok(user) if !user.is_deleted => user,
|
||||||
.await
|
_ => return common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
{
|
};
|
||||||
|
let patch = UsersSchema {
|
||||||
|
id: user.id.clone(),
|
||||||
|
password,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
match repo.query_update_user(patch).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+7
-4
@@ -1,23 +1,26 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
|
Extension, Json,
|
||||||
extract::{Path, Query},
|
extract::{Path, Query},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
Extension, Json,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
|
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
||||||
|
ResponseSuccessDto,
|
||||||
v1::{
|
v1::{
|
||||||
permissions_dto::{PermissionsItemDto, PermissionsRequestDto},
|
permissions_dto::{PermissionsItemDto, PermissionsRequestDto},
|
||||||
permissions_service::PermissionsService,
|
permissions_service::PermissionsService,
|
||||||
},
|
},
|
||||||
AppState, MessageResponseDto, MetaRequestDto, ResponseListSuccessDto,
|
|
||||||
ResponseSuccessDto,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{permissions_guard, PermissionsEnum};
|
use super::{PermissionsEnum, permissions_guard};
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
path = "/v1/permissions",
|
path = "/v1/permissions",
|
||||||
|
security(
|
||||||
|
("Bearer" = [])
|
||||||
|
),
|
||||||
params(
|
params(
|
||||||
("page" = Option<i64>, Query, description = "Page number"),
|
("page" = Option<i64>, Query, description = "Page number"),
|
||||||
("per_page" = Option<i64>, Query, description = "Items per page"),
|
("per_page" = Option<i64>, Query, description = "Items per page"),
|
||||||
+12
-1
@@ -17,8 +17,19 @@ pub struct PermissionsItemDto {
|
|||||||
pub updated_at: Option<String>,
|
pub updated_at: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PermissionsItemDto {
|
||||||
|
pub fn from(dto: &PermissionsQueryDto) -> Self {
|
||||||
|
Self {
|
||||||
|
id: dto.id.id.to_raw(),
|
||||||
|
name: dto.name.clone(),
|
||||||
|
created_at: dto.created_at.clone(),
|
||||||
|
updated_at: dto.updated_at.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct PermissionsItemDtoRaw {
|
pub struct PermissionsQueryDto {
|
||||||
pub id: Thing,
|
pub id: Thing,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub created_at: Option<String>,
|
pub created_at: Option<String>,
|
||||||
+2
@@ -7,6 +7,7 @@ pub enum PermissionsEnum {
|
|||||||
CreateUsers,
|
CreateUsers,
|
||||||
DeleteUsers,
|
DeleteUsers,
|
||||||
UpdateUsers,
|
UpdateUsers,
|
||||||
|
ActivateUsers,
|
||||||
ReadListRoles,
|
ReadListRoles,
|
||||||
ReadDetailRoles,
|
ReadDetailRoles,
|
||||||
CreateRoles,
|
CreateRoles,
|
||||||
@@ -27,6 +28,7 @@ impl fmt::Display for PermissionsEnum {
|
|||||||
PermissionsEnum::CreateUsers => "Create Users",
|
PermissionsEnum::CreateUsers => "Create Users",
|
||||||
PermissionsEnum::DeleteUsers => "Delete Users",
|
PermissionsEnum::DeleteUsers => "Delete Users",
|
||||||
PermissionsEnum::UpdateUsers => "Update Users",
|
PermissionsEnum::UpdateUsers => "Update Users",
|
||||||
|
PermissionsEnum::ActivateUsers => "Activate Users",
|
||||||
PermissionsEnum::ReadListRoles => "Read List Roles",
|
PermissionsEnum::ReadListRoles => "Read List Roles",
|
||||||
PermissionsEnum::ReadDetailRoles => "Read Detail Roles",
|
PermissionsEnum::ReadDetailRoles => "Read Detail Roles",
|
||||||
PermissionsEnum::CreateRoles => "Create Roles",
|
PermissionsEnum::CreateRoles => "Create Roles",
|
||||||
+18
-22
@@ -1,9 +1,9 @@
|
|||||||
use super::{PermissionsItemDto, PermissionsItemDtoRaw, PermissionsSchema};
|
use super::{PermissionsItemDto, PermissionsSchema};
|
||||||
use crate::{
|
use crate::{
|
||||||
get_id, make_thing, query_list_with_meta, AppState, MetaRequestDto, ResourceEnum,
|
AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id,
|
||||||
ResponseListSuccessDto,
|
make_thing, query_list_with_meta,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use imphnen_utils::extract_id;
|
use imphnen_utils::extract_id;
|
||||||
|
|
||||||
pub struct PermissionsRepository<'a> {
|
pub struct PermissionsRepository<'a> {
|
||||||
@@ -27,23 +27,22 @@ impl<'a> PermissionsRepository<'a> {
|
|||||||
let filter_by = meta.filter_by.as_ref().unwrap();
|
let filter_by = meta.filter_by.as_ref().unwrap();
|
||||||
conditions.push(format!("{} = $filter", filter_by));
|
conditions.push(format!("{} = $filter", filter_by));
|
||||||
}
|
}
|
||||||
let raw_result: ResponseListSuccessDto<Vec<PermissionsItemDtoRaw>> = query_list_with_meta(
|
let raw_result: ResponseListSuccessDto<Vec<PermissionsSchema>> =
|
||||||
&self.state.surrealdb_ws,
|
query_list_with_meta(
|
||||||
&ResourceEnum::Permissions.to_string(),
|
&self.state.surrealdb_ws,
|
||||||
&meta,
|
&ResourceEnum::Permissions.to_string(),
|
||||||
conditions,
|
&meta,
|
||||||
None,
|
conditions,
|
||||||
)
|
None,
|
||||||
.await?;
|
"name",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
let transformed_data = raw_result
|
let transformed_data = raw_result
|
||||||
.data
|
.data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|permission| PermissionsItemDto {
|
.map(|permission| PermissionsSchema::list(&permission))
|
||||||
id: extract_id(&permission.id),
|
|
||||||
name: permission.name,
|
|
||||||
created_at: permission.created_at,
|
|
||||||
updated_at: permission.updated_at,
|
|
||||||
})
|
|
||||||
.collect();
|
.collect();
|
||||||
Ok(ResponseListSuccessDto {
|
Ok(ResponseListSuccessDto {
|
||||||
data: transformed_data,
|
data: transformed_data,
|
||||||
@@ -69,9 +68,7 @@ impl<'a> PermissionsRepository<'a> {
|
|||||||
&self,
|
&self,
|
||||||
id: String,
|
id: String,
|
||||||
) -> Result<PermissionsItemDto> {
|
) -> Result<PermissionsItemDto> {
|
||||||
let raw_result = self
|
let raw_result = self.query_permission_by_id(id.clone()).await?;
|
||||||
.query_permission_by_id(id.clone())
|
|
||||||
.await?;
|
|
||||||
let transformed_data = PermissionsItemDto {
|
let transformed_data = PermissionsItemDto {
|
||||||
id: extract_id(&raw_result.id),
|
id: extract_id(&raw_result.id),
|
||||||
name: raw_result.name,
|
name: raw_result.name,
|
||||||
@@ -81,7 +78,6 @@ impl<'a> PermissionsRepository<'a> {
|
|||||||
Ok(transformed_data)
|
Ok(transformed_data)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
pub async fn query_permission_by_name(
|
pub async fn query_permission_by_name(
|
||||||
&self,
|
&self,
|
||||||
name: String,
|
name: String,
|
||||||
+15
-2
@@ -1,6 +1,8 @@
|
|||||||
use crate::{make_thing, ResourceEnum};
|
use crate::{ResourceEnum, make_thing};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use surrealdb::{sql::Thing, Uuid};
|
use surrealdb::{Uuid, sql::Thing};
|
||||||
|
|
||||||
|
use super::PermissionsItemDto;
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct PermissionsSchema {
|
pub struct PermissionsSchema {
|
||||||
@@ -25,3 +27,14 @@ impl Default for PermissionsSchema {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PermissionsSchema {
|
||||||
|
pub fn list(&self) -> PermissionsItemDto {
|
||||||
|
PermissionsItemDto {
|
||||||
|
id: self.id.id.to_raw(),
|
||||||
|
name: self.name.clone(),
|
||||||
|
created_at: self.created_at.clone(),
|
||||||
|
updated_at: self.updated_at.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+9
-6
@@ -1,13 +1,16 @@
|
|||||||
use axum::{
|
use axum::{
|
||||||
|
Extension, Json,
|
||||||
extract::{Path, Query},
|
extract::{Path, Query},
|
||||||
response::IntoResponse,
|
response::IntoResponse,
|
||||||
Extension, Json,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{RolesItemDto, RolesRequestCreateDto, RolesRequestUpdateDto};
|
use super::{
|
||||||
|
RolesDetailItemDto, RolesListItemDto, RolesRequestCreateDto, RolesRequestUpdateDto,
|
||||||
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
permissions_guard, v1::roles_service::RolesService, AppState, MessageResponseDto,
|
AppState, MessageResponseDto, MetaRequestDto, PermissionsEnum,
|
||||||
MetaRequestDto, PermissionsEnum, ResponseListSuccessDto, ResponseSuccessDto,
|
ResponseListSuccessDto, ResponseSuccessDto, permissions_guard,
|
||||||
|
v1::roles_service::RolesService,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
@@ -26,7 +29,7 @@ use crate::{
|
|||||||
("filter_by" = Option<String>, Query, description = "Field to filter by"),
|
("filter_by" = Option<String>, Query, description = "Field to filter by"),
|
||||||
),
|
),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Get role list", body = ResponseListSuccessDto<Vec<RolesItemDto>>)
|
(status = 200, description = "Get role list", body = ResponseListSuccessDto<Vec<RolesListItemDto>>)
|
||||||
),
|
),
|
||||||
tag = "Roles"
|
tag = "Roles"
|
||||||
)]
|
)]
|
||||||
@@ -55,7 +58,7 @@ pub async fn get_role_list(
|
|||||||
path = "/v1/roles/detail/{id}",
|
path = "/v1/roles/detail/{id}",
|
||||||
params(("id" = String, Path, description = "Role ID")),
|
params(("id" = String, Path, description = "Role ID")),
|
||||||
responses(
|
responses(
|
||||||
(status = 200, description = "Get role by ID", body = ResponseSuccessDto<RolesItemDto>)
|
(status = 200, description = "Get role by ID", body = ResponseSuccessDto<RolesDetailItemDto>)
|
||||||
),
|
),
|
||||||
tag = "Roles"
|
tag = "Roles"
|
||||||
)]
|
)]
|
||||||
+22
-22
@@ -1,4 +1,4 @@
|
|||||||
use crate::{PermissionsItemDto, PermissionsItemDtoRaw};
|
use crate::{PermissionsItemDto, PermissionsQueryDto};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
use surrealdb::sql::Thing;
|
use surrealdb::sql::Thing;
|
||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
@@ -9,6 +9,7 @@ pub struct RolesRequestUpdateDto {
|
|||||||
#[validate(length(min = 1, message = "Role name must not be empty"))]
|
#[validate(length(min = 1, message = "Role name must not be empty"))]
|
||||||
pub name: Option<String>,
|
pub name: Option<String>,
|
||||||
pub permissions: Option<Vec<String>>,
|
pub permissions: Option<Vec<String>>,
|
||||||
|
pub overwrite: Option<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
@@ -19,24 +20,16 @@ pub struct RolesRequestCreateDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct RolesItemListDto {
|
pub struct RolesListItemDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
|
pub permissions_count: usize,
|
||||||
pub created_at: Option<String>,
|
pub created_at: Option<String>,
|
||||||
pub updated_at: Option<String>,
|
pub updated_at: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct RolesItemDto {
|
pub struct RolesDetailItemDto {
|
||||||
pub id: String,
|
|
||||||
pub name: String,
|
|
||||||
pub permissions: Vec<PermissionsItemDto>,
|
|
||||||
pub created_at: Option<String>,
|
|
||||||
pub updated_at: Option<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
|
||||||
pub struct RolesItemByIdDto {
|
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub is_deleted: bool,
|
pub is_deleted: bool,
|
||||||
@@ -45,21 +38,28 @@ pub struct RolesItemByIdDto {
|
|||||||
pub updated_at: Option<String>,
|
pub updated_at: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
impl RolesDetailItemDto {
|
||||||
pub struct RolesItemByIdDtoRaw {
|
pub fn from(dto: &RolesDetailQueryDto) -> Self {
|
||||||
pub id: Thing,
|
Self {
|
||||||
pub name: String,
|
id: dto.id.id.to_raw(),
|
||||||
pub permissions: Vec<PermissionsItemDtoRaw>,
|
name: dto.name.clone(),
|
||||||
pub is_deleted: bool,
|
is_deleted: dto.is_deleted,
|
||||||
pub created_at: Option<String>,
|
permissions: dto
|
||||||
pub updated_at: Option<String>,
|
.permissions
|
||||||
|
.iter()
|
||||||
|
.map(PermissionsItemDto::from)
|
||||||
|
.collect(),
|
||||||
|
created_at: dto.created_at.clone(),
|
||||||
|
updated_at: dto.updated_at.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct RolesItemDtoRaw {
|
pub struct RolesDetailQueryDto {
|
||||||
pub id: Thing,
|
pub id: Thing,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub permissions: Vec<PermissionsItemDtoRaw>,
|
pub permissions: Vec<PermissionsQueryDto>,
|
||||||
pub is_deleted: bool,
|
pub is_deleted: bool,
|
||||||
pub created_at: Option<String>,
|
pub created_at: Option<String>,
|
||||||
pub updated_at: Option<String>,
|
pub updated_at: Option<String>,
|
||||||
+2
-4
@@ -4,8 +4,7 @@ use std::fmt;
|
|||||||
pub enum RolesEnum {
|
pub enum RolesEnum {
|
||||||
Admin,
|
Admin,
|
||||||
User,
|
User,
|
||||||
Student,
|
Staff,
|
||||||
Staf,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl fmt::Display for RolesEnum {
|
impl fmt::Display for RolesEnum {
|
||||||
@@ -13,8 +12,7 @@ impl fmt::Display for RolesEnum {
|
|||||||
let roles_str = match self {
|
let roles_str = match self {
|
||||||
RolesEnum::Admin => "Admin",
|
RolesEnum::Admin => "Admin",
|
||||||
RolesEnum::User => "User",
|
RolesEnum::User => "User",
|
||||||
RolesEnum::Student => "Student",
|
RolesEnum::Staff => "Staff",
|
||||||
RolesEnum::Staf => "Staf",
|
|
||||||
};
|
};
|
||||||
write!(f, "{}", roles_str)
|
write!(f, "{}", roles_str)
|
||||||
}
|
}
|
||||||
+70
-92
@@ -1,13 +1,15 @@
|
|||||||
use super::{
|
use super::{
|
||||||
RolesItemByIdDto, RolesItemByIdDtoRaw, RolesItemDto, RolesItemDtoRaw, RolesRequestCreateDto, RolesRequestUpdateDto, RolesSchema
|
RolesDetailItemDto, RolesDetailQueryDto, RolesListItemDto, RolesRequestCreateDto,
|
||||||
|
RolesRequestUpdateDto, RolesSchema,
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
extract_id, get_id, make_thing, query_list_with_meta, AppState, MetaRequestDto,
|
AppState, MetaRequestDto, PermissionsItemDto, ResourceEnum,
|
||||||
PermissionsItemDto, ResourceEnum, ResponseListSuccessDto,
|
ResponseListSuccessDto, extract_id, get_id, make_thing, query_list_with_meta,
|
||||||
};
|
};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::{Result, bail};
|
||||||
use surrealdb::sql::Thing;
|
use imphnen_utils::DetailQueryBuilder;
|
||||||
use surrealdb::Uuid;
|
use surrealdb::Uuid;
|
||||||
|
use surrealdb::sql::Thing;
|
||||||
|
|
||||||
pub struct RolesRepository<'a> {
|
pub struct RolesRepository<'a> {
|
||||||
state: &'a AppState,
|
state: &'a AppState,
|
||||||
@@ -18,74 +20,67 @@ impl<'a> RolesRepository<'a> {
|
|||||||
Self { state }
|
Self { state }
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn query_raw_role_by_id(&self, id: &str) -> Result<RolesSchema> {
|
|
||||||
let db = &self.state.surrealdb_ws;
|
|
||||||
let role: Option<RolesSchema> =
|
|
||||||
db.select((ResourceEnum::Roles.to_string(), id)).await?;
|
|
||||||
match role {
|
|
||||||
Some(r) if !r.is_deleted => Ok(r),
|
|
||||||
_ => bail!("Role not found"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub async fn query_role_list(
|
pub async fn query_role_list(
|
||||||
&self,
|
&self,
|
||||||
meta: MetaRequestDto,
|
meta: MetaRequestDto,
|
||||||
) -> Result<ResponseListSuccessDto<Vec<RolesItemDto>>> {
|
) -> Result<ResponseListSuccessDto<Vec<RolesListItemDto>>> {
|
||||||
let mut conditions = vec!["is_deleted = false".into()];
|
let mut conditions = vec!["is_deleted = false".into()];
|
||||||
if meta.search.is_some() {
|
if let Some(_search) = meta.search.as_deref().filter(|s| !s.is_empty()) {
|
||||||
conditions.push("string::contains(name, $search)".into());
|
conditions.push("string::contains(name ?? '', $search)".into());
|
||||||
}
|
}
|
||||||
if meta.filter_by.is_some() && meta.filter.is_some() {
|
if let (Some(filter_by), Some(filter_val)) =
|
||||||
let filter_by = meta.filter_by.as_ref().unwrap();
|
(meta.filter_by.as_ref(), meta.filter.as_ref())
|
||||||
conditions.push(format!("{} = $filter", filter_by));
|
{
|
||||||
|
if !filter_val.is_empty() {
|
||||||
|
conditions.push(format!("{} = $filter", filter_by));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
let raw_result: ResponseListSuccessDto<Vec<RolesItemDtoRaw>> = query_list_with_meta(
|
let raw_result: ResponseListSuccessDto<Vec<RolesSchema>> = query_list_with_meta(
|
||||||
&self.state.surrealdb_ws,
|
&self.state.surrealdb_ws,
|
||||||
&ResourceEnum::Roles.to_string(),
|
&ResourceEnum::Roles.to_string(),
|
||||||
&meta,
|
&meta,
|
||||||
conditions,
|
conditions,
|
||||||
None,
|
None,
|
||||||
|
"name",
|
||||||
|
None,
|
||||||
|
None,
|
||||||
)
|
)
|
||||||
.await?;
|
.await?;
|
||||||
let transformed_data = raw_result
|
let data = raw_result
|
||||||
.data
|
.data
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.map(|role| {
|
.map(|role| RolesSchema::list(&role))
|
||||||
RolesItemDto {
|
.collect();
|
||||||
name: role.name,
|
|
||||||
created_at: role.created_at,
|
|
||||||
updated_at: role.updated_at,
|
|
||||||
permissions: role.permissions
|
|
||||||
.into_iter()
|
|
||||||
.map(|perm| PermissionsItemDto {
|
|
||||||
id: extract_id(&perm.id),
|
|
||||||
name: perm.name,
|
|
||||||
created_at: perm.created_at,
|
|
||||||
updated_at: perm.updated_at,
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>(),
|
|
||||||
id: extract_id(&role.id),
|
|
||||||
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.collect::<Vec<RolesItemDto>>();
|
|
||||||
let transformed_meta = raw_result.meta;
|
|
||||||
Ok(ResponseListSuccessDto {
|
Ok(ResponseListSuccessDto {
|
||||||
data: transformed_data,
|
data,
|
||||||
meta: transformed_meta,
|
meta: raw_result.meta,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn query_role_by_name(&self, name: String) -> Result<RolesItemByIdDto> {
|
pub async fn query_role_by_name(
|
||||||
|
&self,
|
||||||
|
name: String,
|
||||||
|
) -> Result<RolesDetailItemDto> {
|
||||||
let db = &self.state.surrealdb_ws;
|
let db = &self.state.surrealdb_ws;
|
||||||
let sql = format!(
|
let builder = DetailQueryBuilder::new(ResourceEnum::Roles.to_string())
|
||||||
"SELECT *, permissions FROM {} WHERE name = $name AND is_deleted = false LIMIT 1 FETCH permissions",
|
.with_where("name")
|
||||||
ResourceEnum::Roles.to_string()
|
.where_value(name.clone())
|
||||||
);
|
.with_select_fields(vec![
|
||||||
let mut result = db.query(sql).bind(("name", name.clone())).await?;
|
"id",
|
||||||
let role: Option<RolesItemByIdDtoRaw> = result.take(0)?;
|
"name",
|
||||||
let role = match role {
|
"permissions",
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"is_deleted",
|
||||||
|
])
|
||||||
|
.with_fetch("permissions");
|
||||||
|
|
||||||
|
let sql = builder.build();
|
||||||
|
let result: Option<RolesDetailQueryDto> = builder
|
||||||
|
.apply_bindings(db.query(sql).bind(("name", name)))
|
||||||
|
.await?
|
||||||
|
.take(0)?;
|
||||||
|
let role = match result {
|
||||||
Some(r) if !r.is_deleted => r,
|
Some(r) if !r.is_deleted => r,
|
||||||
_ => bail!("Role not found"),
|
_ => bail!("Role not found"),
|
||||||
};
|
};
|
||||||
@@ -98,8 +93,8 @@ impl<'a> RolesRepository<'a> {
|
|||||||
created_at: perm.created_at,
|
created_at: perm.created_at,
|
||||||
updated_at: perm.updated_at,
|
updated_at: perm.updated_at,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect();
|
||||||
Ok(RolesItemByIdDto {
|
Ok(RolesDetailItemDto {
|
||||||
id: extract_id(&role.id),
|
id: extract_id(&role.id),
|
||||||
name: role.name,
|
name: role.name,
|
||||||
is_deleted: role.is_deleted,
|
is_deleted: role.is_deleted,
|
||||||
@@ -109,16 +104,23 @@ impl<'a> RolesRepository<'a> {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn query_role_by_id(&self, id: String) -> Result<RolesItemByIdDto> {
|
pub async fn query_role_by_id(&self, id: String) -> Result<RolesDetailItemDto> {
|
||||||
let db = &self.state.surrealdb_ws;
|
let db = &self.state.surrealdb_ws;
|
||||||
let query = format!(
|
let builder = DetailQueryBuilder::new(ResourceEnum::Roles.to_string())
|
||||||
"SELECT *, permissions.* AS permissions
|
.with_id(&id)
|
||||||
FROM app_roles:⟨{}⟩ WHERE is_deleted = false FETCH permissions",
|
.with_select_fields(vec![
|
||||||
id
|
"id",
|
||||||
);
|
"name",
|
||||||
let mut result = db.query(query).await?;
|
"is_deleted",
|
||||||
let role: Option<RolesItemByIdDtoRaw> = result.take(0)?;
|
"permissions",
|
||||||
let role = match role {
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
])
|
||||||
|
.with_fetch("permissions");
|
||||||
|
let sql = builder.build();
|
||||||
|
let result: Option<RolesDetailQueryDto> =
|
||||||
|
builder.apply_bindings(db.query(sql)).await?.take(0)?;
|
||||||
|
let role = match result {
|
||||||
Some(r) if !r.is_deleted => r,
|
Some(r) if !r.is_deleted => r,
|
||||||
_ => bail!("Role not found"),
|
_ => bail!("Role not found"),
|
||||||
};
|
};
|
||||||
@@ -131,8 +133,8 @@ impl<'a> RolesRepository<'a> {
|
|||||||
created_at: perm.created_at,
|
created_at: perm.created_at,
|
||||||
updated_at: perm.updated_at,
|
updated_at: perm.updated_at,
|
||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect();
|
||||||
Ok(RolesItemByIdDto {
|
Ok(RolesDetailItemDto {
|
||||||
id: extract_id(&role.id),
|
id: extract_id(&role.id),
|
||||||
name: role.name,
|
name: role.name,
|
||||||
is_deleted: role.is_deleted,
|
is_deleted: role.is_deleted,
|
||||||
@@ -147,14 +149,12 @@ impl<'a> RolesRepository<'a> {
|
|||||||
payload: RolesRequestCreateDto,
|
payload: RolesRequestCreateDto,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let db = &self.state.surrealdb_ws;
|
let db = &self.state.surrealdb_ws;
|
||||||
|
|
||||||
let role_id = Uuid::new_v4().to_string();
|
let role_id = Uuid::new_v4().to_string();
|
||||||
let permission_things: Vec<Thing> = payload
|
let permission_things: Vec<Thing> = payload
|
||||||
.permissions
|
.permissions
|
||||||
.iter()
|
.iter()
|
||||||
.map(|id| make_thing(&ResourceEnum::Permissions.to_string(), id))
|
.map(|id| make_thing(&ResourceEnum::Permissions.to_string(), id))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let role = RolesSchema {
|
let role = RolesSchema {
|
||||||
id: make_thing(&ResourceEnum::Roles.to_string(), &role_id),
|
id: make_thing(&ResourceEnum::Roles.to_string(), &role_id),
|
||||||
name: payload.name,
|
name: payload.name,
|
||||||
@@ -163,12 +163,10 @@ impl<'a> RolesRepository<'a> {
|
|||||||
created_at: Some(crate::get_iso_date()),
|
created_at: Some(crate::get_iso_date()),
|
||||||
updated_at: Some(crate::get_iso_date()),
|
updated_at: Some(crate::get_iso_date()),
|
||||||
};
|
};
|
||||||
|
|
||||||
let _: Option<RolesSchema> = db
|
let _: Option<RolesSchema> = db
|
||||||
.create((&ResourceEnum::Roles.to_string(), role_id))
|
.create((&ResourceEnum::Roles.to_string(), role_id))
|
||||||
.content(role)
|
.content(role)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
Ok("Role with permissions created successfully".into())
|
Ok("Role with permissions created successfully".into())
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -178,31 +176,11 @@ impl<'a> RolesRepository<'a> {
|
|||||||
data: RolesRequestUpdateDto,
|
data: RolesRequestUpdateDto,
|
||||||
) -> Result<String> {
|
) -> Result<String> {
|
||||||
let db = &self.state.surrealdb_ws;
|
let db = &self.state.surrealdb_ws;
|
||||||
let thing_id = make_thing(&ResourceEnum::Roles.to_string(), &id);
|
let existing = self.query_role_by_id(id.clone()).await?;
|
||||||
let existing = self.query_raw_role_by_id(&id).await?;
|
|
||||||
if existing.is_deleted {
|
if existing.is_deleted {
|
||||||
bail!("Role already deleted");
|
bail!("Role already deleted");
|
||||||
}
|
}
|
||||||
let permissions: Vec<Thing> = if let Some(permission_ids) = &data.permissions {
|
let merged = RolesSchema::update(data, id.clone(), existing);
|
||||||
permission_ids
|
|
||||||
.iter()
|
|
||||||
.map(|id| make_thing(&ResourceEnum::Permissions.to_string(), id))
|
|
||||||
.collect()
|
|
||||||
} else {
|
|
||||||
existing
|
|
||||||
.permissions
|
|
||||||
.iter()
|
|
||||||
.map(|p| make_thing(&ResourceEnum::Permissions.to_string(), &p.id.to_raw()))
|
|
||||||
.collect()
|
|
||||||
};
|
|
||||||
let merged = RolesSchema {
|
|
||||||
id: thing_id,
|
|
||||||
name: data.name.unwrap_or(existing.name),
|
|
||||||
permissions,
|
|
||||||
is_deleted: existing.is_deleted,
|
|
||||||
created_at: existing.created_at,
|
|
||||||
updated_at: Some(crate::get_iso_date()),
|
|
||||||
};
|
|
||||||
let record: Option<RolesSchema> =
|
let record: Option<RolesSchema> =
|
||||||
db.update(get_id(&merged.id)?).content(merged).await?;
|
db.update(get_id(&merged.id)?).content(merged).await?;
|
||||||
match record {
|
match record {
|
||||||
@@ -0,0 +1,125 @@
|
|||||||
|
use super::{
|
||||||
|
RolesDetailItemDto, RolesDetailQueryDto, RolesListItemDto, RolesRequestCreateDto,
|
||||||
|
RolesRequestUpdateDto,
|
||||||
|
};
|
||||||
|
use crate::{ResourceEnum, make_thing};
|
||||||
|
use imphnen_utils::get_iso_date;
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use surrealdb::{Uuid, sql::Thing};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct RolesSchema {
|
||||||
|
pub id: Thing,
|
||||||
|
pub name: String,
|
||||||
|
pub is_deleted: bool,
|
||||||
|
pub permissions: Vec<Thing>,
|
||||||
|
pub created_at: Option<String>,
|
||||||
|
pub updated_at: Option<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for RolesSchema {
|
||||||
|
fn default() -> Self {
|
||||||
|
RolesSchema {
|
||||||
|
id: make_thing(
|
||||||
|
&ResourceEnum::Roles.to_string(),
|
||||||
|
&Uuid::new_v4().to_string(),
|
||||||
|
),
|
||||||
|
permissions: vec![make_thing(
|
||||||
|
&ResourceEnum::Permissions.to_string(),
|
||||||
|
&Uuid::new_v4().to_string(),
|
||||||
|
)],
|
||||||
|
name: String::new(),
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: None,
|
||||||
|
updated_at: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RolesSchema {
|
||||||
|
pub fn from(dto: RolesDetailQueryDto) -> Self {
|
||||||
|
Self {
|
||||||
|
id: dto.id,
|
||||||
|
name: dto.name,
|
||||||
|
permissions: dto
|
||||||
|
.permissions
|
||||||
|
.into_iter()
|
||||||
|
.map(|perm| {
|
||||||
|
make_thing(&ResourceEnum::Permissions.to_string(), &perm.id.to_raw())
|
||||||
|
})
|
||||||
|
.collect(),
|
||||||
|
is_deleted: dto.is_deleted,
|
||||||
|
created_at: dto.created_at,
|
||||||
|
updated_at: dto.updated_at,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(dto: RolesRequestCreateDto) -> Self {
|
||||||
|
let permissions: Vec<Thing> = dto
|
||||||
|
.permissions
|
||||||
|
.into_iter()
|
||||||
|
.map(|id| make_thing(&ResourceEnum::Permissions.to_string(), &id))
|
||||||
|
.collect();
|
||||||
|
Self {
|
||||||
|
id: make_thing(
|
||||||
|
&ResourceEnum::Roles.to_string(),
|
||||||
|
&Uuid::new_v4().to_string(),
|
||||||
|
),
|
||||||
|
name: dto.name,
|
||||||
|
permissions,
|
||||||
|
is_deleted: false,
|
||||||
|
created_at: Some(get_iso_date()),
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(
|
||||||
|
dto: RolesRequestUpdateDto,
|
||||||
|
id: String,
|
||||||
|
existing: RolesDetailItemDto,
|
||||||
|
) -> Self {
|
||||||
|
let name = dto.name.unwrap_or(existing.name);
|
||||||
|
let permissions: Vec<Thing> =
|
||||||
|
match (dto.permissions, dto.overwrite.unwrap_or(false)) {
|
||||||
|
(Some(new_ids), true) => new_ids
|
||||||
|
.iter()
|
||||||
|
.map(|id| make_thing(&ResourceEnum::Permissions.to_string(), id))
|
||||||
|
.collect(),
|
||||||
|
(Some(new_ids), false) => {
|
||||||
|
let mut all_ids: HashSet<String> =
|
||||||
|
existing.permissions.iter().map(|p| p.id.clone()).collect();
|
||||||
|
for id in new_ids {
|
||||||
|
all_ids.insert(id);
|
||||||
|
}
|
||||||
|
all_ids
|
||||||
|
.into_iter()
|
||||||
|
.map(|id| make_thing(&ResourceEnum::Permissions.to_string(), &id))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
(None, _) => existing
|
||||||
|
.permissions
|
||||||
|
.iter()
|
||||||
|
.map(|p| make_thing(&ResourceEnum::Permissions.to_string(), &p.id))
|
||||||
|
.collect(),
|
||||||
|
};
|
||||||
|
Self {
|
||||||
|
id: make_thing(&ResourceEnum::Roles.to_string(), &id),
|
||||||
|
name,
|
||||||
|
permissions,
|
||||||
|
is_deleted: existing.is_deleted,
|
||||||
|
created_at: existing.created_at,
|
||||||
|
updated_at: Some(get_iso_date()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn list(&self) -> RolesListItemDto {
|
||||||
|
RolesListItemDto {
|
||||||
|
id: self.id.id.to_raw(),
|
||||||
|
name: self.name.clone(),
|
||||||
|
permissions_count: self.permissions.len(),
|
||||||
|
created_at: self.created_at.clone(),
|
||||||
|
updated_at: self.updated_at.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+1
-1
@@ -1,7 +1,7 @@
|
|||||||
use super::{RolesRepository, RolesRequestCreateDto, RolesRequestUpdateDto};
|
use super::{RolesRepository, RolesRequestCreateDto, RolesRequestUpdateDto};
|
||||||
use crate::{
|
use crate::{
|
||||||
common_response, success_list_response, success_response, validate_request,
|
|
||||||
AppState, MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto,
|
AppState, MetaRequestDto, ResponseListSuccessDto, ResponseSuccessDto,
|
||||||
|
common_response, success_list_response, success_response, validate_request,
|
||||||
};
|
};
|
||||||
use axum::{http::StatusCode, response::Response};
|
use axum::{http::StatusCode, response::Response};
|
||||||
|
|
||||||
+8
-8
@@ -1,16 +1,16 @@
|
|||||||
|
use crate::{AppState, MetaRequestDto, v1::users_service::UsersService};
|
||||||
|
use crate::{
|
||||||
|
MessageResponseDto, PermissionsEnum, ResponseListSuccessDto, ResponseSuccessDto,
|
||||||
|
UsersCreateRequestDto, UsersDetailItemDto, permissions_guard,
|
||||||
|
};
|
||||||
use axum::extract::{Path, Query};
|
use axum::extract::{Path, Query};
|
||||||
use axum::http::HeaderMap;
|
use axum::http::HeaderMap;
|
||||||
use axum::response::IntoResponse;
|
use axum::response::IntoResponse;
|
||||||
use axum::{Extension, Json};
|
use axum::{Extension, Json};
|
||||||
|
|
||||||
use crate::{
|
use super::{
|
||||||
permissions_guard, MessageResponseDto, PermissionsEnum, ResponseListSuccessDto,
|
UsersActiveInactiveRequestDto, UsersListItemDto, UsersUpdateRequestDto,
|
||||||
ResponseSuccessDto, UsersActiveInactiveRequestDto, UsersCreateRequestDto,
|
|
||||||
UsersDetailItemDto,
|
|
||||||
};
|
};
|
||||||
use crate::{v1::users_service::UsersService, AppState, MetaRequestDto};
|
|
||||||
|
|
||||||
use super::{UsersListItemDto, UsersUpdateRequestDto};
|
|
||||||
|
|
||||||
#[utoipa::path(
|
#[utoipa::path(
|
||||||
get,
|
get,
|
||||||
@@ -204,7 +204,7 @@ pub async fn patch_user_active_status(
|
|||||||
match permissions_guard(
|
match permissions_guard(
|
||||||
&headers,
|
&headers,
|
||||||
state.clone(),
|
state.clone(),
|
||||||
vec![PermissionsEnum::UpdateUsers],
|
vec![PermissionsEnum::ActivateUsers],
|
||||||
)
|
)
|
||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
+68
-21
@@ -1,3 +1,4 @@
|
|||||||
|
use crate::{RolesDetailItemDto, RolesDetailQueryDto};
|
||||||
use lazy_static::lazy_static;
|
use lazy_static::lazy_static;
|
||||||
use regex::Regex;
|
use regex::Regex;
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
@@ -5,8 +6,6 @@ use surrealdb::sql::Thing;
|
|||||||
use utoipa::ToSchema;
|
use utoipa::ToSchema;
|
||||||
use validator::Validate;
|
use validator::Validate;
|
||||||
|
|
||||||
use crate::{RolesItemDto, RolesItemDtoRaw};
|
|
||||||
|
|
||||||
lazy_static! {
|
lazy_static! {
|
||||||
static ref PASSWORD_REGEX: Regex = Regex::new(r"^[A-Za-z\d@$!%*?&]{8,}$").unwrap();
|
static ref PASSWORD_REGEX: Regex = Regex::new(r"^[A-Za-z\d@$!%*?&]{8,}$").unwrap();
|
||||||
}
|
}
|
||||||
@@ -16,6 +15,12 @@ pub struct UsersActiveInactiveRequestDto {
|
|||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
|
pub struct UsersSetNewPasswordRequestDto {
|
||||||
|
pub password: String,
|
||||||
|
pub old_password: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema, Validate)]
|
||||||
pub struct UsersCreateRequestDto {
|
pub struct UsersCreateRequestDto {
|
||||||
#[validate(
|
#[validate(
|
||||||
@@ -23,6 +28,7 @@ pub struct UsersCreateRequestDto {
|
|||||||
email(message = "Email not valid")
|
email(message = "Email not valid")
|
||||||
)]
|
)]
|
||||||
pub email: String,
|
pub email: String,
|
||||||
|
|
||||||
#[validate(length(
|
#[validate(length(
|
||||||
min = 8,
|
min = 8,
|
||||||
message = "Password must have at least 8 characters"
|
message = "Password must have at least 8 characters"
|
||||||
@@ -32,8 +38,10 @@ pub struct UsersCreateRequestDto {
|
|||||||
message = "Password must include uppercase, lowercase, number, and special character"
|
message = "Password must include uppercase, lowercase, number, and special character"
|
||||||
))]
|
))]
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
|
||||||
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
#[validate(length(min = 2, message = "Fullname at least have 2 character"))]
|
||||||
pub fullname: String,
|
pub fullname: String,
|
||||||
|
|
||||||
#[validate(length(
|
#[validate(length(
|
||||||
min = 10,
|
min = 10,
|
||||||
message = "Phone number at least have 10 character"
|
message = "Phone number at least have 10 character"
|
||||||
@@ -72,33 +80,36 @@ pub struct UsersUpdateRequestDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
pub struct UsersItemDto {
|
pub struct UsersDetailItemDto {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub role: RolesItemDto,
|
pub role: RolesDetailItemDto,
|
||||||
pub fullname: String,
|
pub fullname: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
pub phone_number: String,
|
pub phone_number: String,
|
||||||
pub is_active: bool,
|
pub is_active: bool,
|
||||||
pub is_deleted: bool,
|
|
||||||
pub gender: Option<String>,
|
pub gender: Option<String>,
|
||||||
pub birthdate: Option<String>,
|
pub birthdate: Option<String>,
|
||||||
pub password: String,
|
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
impl UsersDetailItemDto {
|
||||||
pub struct UsersDetailItemDto {
|
pub fn from(dto: &UsersDetailQueryDto) -> Self {
|
||||||
pub id: String,
|
Self {
|
||||||
pub role: RolesItemDto,
|
id: dto.id.id.to_raw().clone(),
|
||||||
pub fullname: String,
|
role: RolesDetailItemDto::from(&dto.role),
|
||||||
pub email: String,
|
fullname: dto.fullname.clone(),
|
||||||
pub avatar: Option<String>,
|
email: dto.email.clone(),
|
||||||
pub phone_number: String,
|
avatar: dto.avatar.clone(),
|
||||||
pub is_active: bool,
|
phone_number: dto.phone_number.clone(),
|
||||||
pub gender: Option<String>,
|
is_active: dto.is_active.clone(),
|
||||||
pub birthdate: Option<String>,
|
gender: dto.gender.clone(),
|
||||||
|
birthdate: dto.birthdate.clone(),
|
||||||
|
created_at: dto.created_at.clone(),
|
||||||
|
updated_at: dto.updated_at.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
#[derive(Clone, Debug, Serialize, Deserialize, ToSchema)]
|
||||||
@@ -115,9 +126,9 @@ pub struct UsersListItemDto {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct UsersListItemDtoRaw {
|
pub struct UsersListQueryDto {
|
||||||
pub id: Thing,
|
pub id: Thing,
|
||||||
pub role: Option<String>,
|
pub role: RolesDetailQueryDto,
|
||||||
pub fullname: String,
|
pub fullname: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
pub avatar: Option<String>,
|
pub avatar: Option<String>,
|
||||||
@@ -127,8 +138,24 @@ pub struct UsersListItemDtoRaw {
|
|||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UsersListQueryDto {
|
||||||
|
pub fn from(&self) -> UsersListItemDto {
|
||||||
|
UsersListItemDto {
|
||||||
|
id: self.id.id.to_raw(),
|
||||||
|
role: self.role.name.clone(),
|
||||||
|
fullname: self.fullname.clone(),
|
||||||
|
email: self.email.clone(),
|
||||||
|
avatar: self.avatar.clone(),
|
||||||
|
phone_number: self.phone_number.clone(),
|
||||||
|
is_active: self.is_active,
|
||||||
|
created_at: self.created_at.clone(),
|
||||||
|
updated_at: self.updated_at.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Serialize, Deserialize)]
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
pub struct UsersItemDtoRaw {
|
pub struct UsersDetailQueryDto {
|
||||||
pub id: Thing,
|
pub id: Thing,
|
||||||
pub fullname: String,
|
pub fullname: String,
|
||||||
pub email: String,
|
pub email: String,
|
||||||
@@ -138,8 +165,28 @@ pub struct UsersItemDtoRaw {
|
|||||||
pub is_deleted: bool,
|
pub is_deleted: bool,
|
||||||
pub gender: Option<String>,
|
pub gender: Option<String>,
|
||||||
pub birthdate: Option<String>,
|
pub birthdate: Option<String>,
|
||||||
pub role: RolesItemDtoRaw,
|
|
||||||
pub password: String,
|
pub password: String,
|
||||||
|
pub role: RolesDetailQueryDto,
|
||||||
pub created_at: String,
|
pub created_at: String,
|
||||||
pub updated_at: String,
|
pub updated_at: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl UsersDetailQueryDto {
|
||||||
|
pub fn from(&self) -> Self {
|
||||||
|
Self {
|
||||||
|
id: self.id.clone(),
|
||||||
|
role: RolesDetailQueryDto::from(self.role.clone()),
|
||||||
|
fullname: self.fullname.clone(),
|
||||||
|
email: self.email.clone(),
|
||||||
|
avatar: self.avatar.clone(),
|
||||||
|
phone_number: self.phone_number.clone(),
|
||||||
|
is_active: self.is_active,
|
||||||
|
gender: self.gender.clone(),
|
||||||
|
is_deleted: self.is_deleted,
|
||||||
|
password: self.password.clone(),
|
||||||
|
birthdate: self.birthdate.clone(),
|
||||||
|
created_at: self.created_at.clone(),
|
||||||
|
updated_at: self.updated_at.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,171 @@
|
|||||||
|
use super::{UsersDetailQueryDto, UsersListItemDto, UsersListQueryDto, UsersSchema};
|
||||||
|
use crate::{
|
||||||
|
AppState, MetaRequestDto, ResourceEnum, ResponseListSuccessDto, get_id,
|
||||||
|
make_thing, query_list_with_meta,
|
||||||
|
};
|
||||||
|
use anyhow::{Result, bail};
|
||||||
|
use imphnen_utils::DetailQueryBuilder;
|
||||||
|
use surrealdb::{Surreal, engine::remote::ws::Client};
|
||||||
|
|
||||||
|
pub struct UsersRepository<'a> {
|
||||||
|
state: &'a AppState,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn update_partial_schema(
|
||||||
|
db: &Surreal<Client>,
|
||||||
|
table: &str,
|
||||||
|
id: &str,
|
||||||
|
patch: UsersSchema,
|
||||||
|
) -> Result<String> {
|
||||||
|
let thing = make_thing(table, id);
|
||||||
|
let record_key = get_id(&thing)?;
|
||||||
|
let result: Option<UsersSchema> = db.update(record_key).merge(patch).await?;
|
||||||
|
match result {
|
||||||
|
Some(_) => Ok("Success update".into()),
|
||||||
|
None => bail!("Failed to update"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> UsersRepository<'a> {
|
||||||
|
pub fn new(state: &'a AppState) -> Self {
|
||||||
|
Self { state }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_user_list(
|
||||||
|
&self,
|
||||||
|
meta: MetaRequestDto,
|
||||||
|
) -> Result<ResponseListSuccessDto<Vec<UsersListItemDto>>> {
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
|
||||||
|
let raw_result = query_list_with_meta::<UsersListQueryDto>(
|
||||||
|
db,
|
||||||
|
&ResourceEnum::Users.to_string(),
|
||||||
|
&meta,
|
||||||
|
vec!["is_deleted = false".into()],
|
||||||
|
None,
|
||||||
|
"fullname",
|
||||||
|
Some(vec!["*"]),
|
||||||
|
Some(vec!["role", "role.permissions"]),
|
||||||
|
)
|
||||||
|
.await?;
|
||||||
|
|
||||||
|
let data = raw_result
|
||||||
|
.data
|
||||||
|
.into_iter()
|
||||||
|
.map(|schema| UsersListQueryDto::from(&schema))
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
Ok(ResponseListSuccessDto {
|
||||||
|
data,
|
||||||
|
meta: raw_result.meta,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_user_by_email(
|
||||||
|
&self,
|
||||||
|
email: String,
|
||||||
|
) -> Result<UsersDetailQueryDto> {
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
|
||||||
|
let builder = DetailQueryBuilder::new(ResourceEnum::Users.to_string())
|
||||||
|
.with_where("email")
|
||||||
|
.where_value(email.clone())
|
||||||
|
.with_select_fields(vec!["*"])
|
||||||
|
.with_fetch("role")
|
||||||
|
.with_fetch("role.permissions");
|
||||||
|
|
||||||
|
let sql = builder.build();
|
||||||
|
|
||||||
|
let user_opt: Option<UsersDetailQueryDto> =
|
||||||
|
builder.apply_bindings(db.query(sql)).await?.take(0)?;
|
||||||
|
|
||||||
|
let Some(user) = user_opt else {
|
||||||
|
bail!("User not found");
|
||||||
|
};
|
||||||
|
|
||||||
|
if user.role.is_deleted {
|
||||||
|
bail!("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(UsersDetailQueryDto::from(&user))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_user_by_id(&self, id: String) -> Result<UsersDetailQueryDto> {
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
|
||||||
|
let builder = DetailQueryBuilder::new(ResourceEnum::Users.to_string())
|
||||||
|
.with_id(&id)
|
||||||
|
.with_select_fields(vec!["*"])
|
||||||
|
.with_fetch("role")
|
||||||
|
.with_fetch("role.permissions");
|
||||||
|
|
||||||
|
let sql = builder.build();
|
||||||
|
|
||||||
|
let result: Option<UsersDetailQueryDto> =
|
||||||
|
builder.apply_bindings(db.query(sql)).await?.take(0)?;
|
||||||
|
|
||||||
|
let Some(user) = result else {
|
||||||
|
bail!("User not found");
|
||||||
|
};
|
||||||
|
|
||||||
|
if user.role.is_deleted {
|
||||||
|
bail!("User not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(UsersDetailQueryDto::from(&user))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_create_user(&self, data: UsersSchema) -> Result<String> {
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
let record: Option<UsersSchema> = db
|
||||||
|
.create(ResourceEnum::Users.to_string())
|
||||||
|
.content(data)
|
||||||
|
.await?;
|
||||||
|
match record {
|
||||||
|
Some(_) => Ok("Success create user".into()),
|
||||||
|
None => bail!("Failed to create user"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_update_user(&self, data: UsersSchema) -> Result<String> {
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
let record_key = get_id(&data.id)?;
|
||||||
|
let existing = self.query_user_by_id(data.id.id.to_raw()).await?;
|
||||||
|
if existing.is_deleted {
|
||||||
|
bail!("User already deleted");
|
||||||
|
}
|
||||||
|
let role_thing = if data.role == existing.role.id {
|
||||||
|
existing.role.id
|
||||||
|
} else {
|
||||||
|
data.clone().role
|
||||||
|
};
|
||||||
|
let merged = UsersSchema {
|
||||||
|
password: existing.password,
|
||||||
|
created_at: existing.created_at,
|
||||||
|
role: role_thing,
|
||||||
|
..data.clone()
|
||||||
|
};
|
||||||
|
let record: Option<UsersSchema> = db.update(record_key).merge(merged).await?;
|
||||||
|
match record {
|
||||||
|
Some(_) => Ok("Success update user".into()),
|
||||||
|
None => bail!("Failed to update user"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn query_delete_user(&self, id: String) -> Result<String> {
|
||||||
|
let db = &self.state.surrealdb_ws;
|
||||||
|
let user = self.query_user_by_id(id).await?;
|
||||||
|
if user.is_deleted {
|
||||||
|
bail!("User already deleted");
|
||||||
|
}
|
||||||
|
let record_key = get_id(&user.id)?;
|
||||||
|
let record: Option<UsersSchema> = db
|
||||||
|
.update(record_key)
|
||||||
|
.merge(serde_json::json!({ "is_deleted": true }))
|
||||||
|
.await?;
|
||||||
|
match record {
|
||||||
|
Some(_) => Ok("Success delete user".into()),
|
||||||
|
None => bail!("Failed to delete user"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,109 @@
|
|||||||
|
use super::{UsersCreateRequestDto, UsersDetailQueryDto, UsersUpdateRequestDto};
|
||||||
|
use imphnen_libs::{ResourceEnum, hash_password};
|
||||||
|
use imphnen_utils::{get_iso_date, make_thing};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
use surrealdb::{Uuid, sql::Thing};
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Serialize, Deserialize)]
|
||||||
|
pub struct UsersSchema {
|
||||||
|
pub id: Thing,
|
||||||
|
pub fullname: String,
|
||||||
|
pub email: String,
|
||||||
|
pub password: String,
|
||||||
|
pub avatar: Option<String>,
|
||||||
|
pub phone_number: String,
|
||||||
|
pub is_active: bool,
|
||||||
|
pub is_deleted: bool,
|
||||||
|
pub gender: Option<String>,
|
||||||
|
pub birthdate: Option<String>,
|
||||||
|
pub role: Thing,
|
||||||
|
pub created_at: String,
|
||||||
|
pub updated_at: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for UsersSchema {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
id: Thing::from(("app_users", "dummy")),
|
||||||
|
fullname: "".into(),
|
||||||
|
email: "".into(),
|
||||||
|
password: "".into(),
|
||||||
|
avatar: None,
|
||||||
|
phone_number: "".into(),
|
||||||
|
is_active: false,
|
||||||
|
is_deleted: false,
|
||||||
|
gender: None,
|
||||||
|
birthdate: None,
|
||||||
|
role: Thing::from(("app_roles", "dummy")),
|
||||||
|
created_at: "".into(),
|
||||||
|
updated_at: "".into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UsersSchema {
|
||||||
|
pub fn from(dto: UsersDetailQueryDto) -> Self {
|
||||||
|
Self {
|
||||||
|
id: dto.id,
|
||||||
|
fullname: dto.fullname,
|
||||||
|
email: dto.email,
|
||||||
|
avatar: dto.avatar,
|
||||||
|
phone_number: dto.phone_number,
|
||||||
|
is_active: dto.is_active,
|
||||||
|
is_deleted: dto.is_deleted,
|
||||||
|
gender: dto.gender,
|
||||||
|
birthdate: dto.birthdate,
|
||||||
|
password: dto.password,
|
||||||
|
created_at: dto.created_at,
|
||||||
|
updated_at: dto.updated_at,
|
||||||
|
role: make_thing(&ResourceEnum::Roles.to_string(), &dto.role.id.to_string()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update(user: UsersUpdateRequestDto, id: String) -> Self {
|
||||||
|
Self {
|
||||||
|
id: make_thing(&ResourceEnum::Users.to_string(), &id),
|
||||||
|
fullname: user.fullname,
|
||||||
|
email: user.email,
|
||||||
|
phone_number: user.phone_number,
|
||||||
|
is_active: user.is_active,
|
||||||
|
gender: user.gender,
|
||||||
|
birthdate: user.birthdate,
|
||||||
|
avatar: user.avatar,
|
||||||
|
is_deleted: false,
|
||||||
|
role: make_thing(&ResourceEnum::Roles.to_string(), &user.role_id),
|
||||||
|
updated_at: get_iso_date(),
|
||||||
|
..Default::default()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create(user: UsersCreateRequestDto) -> Self {
|
||||||
|
let password = hash_password(&user.password).unwrap();
|
||||||
|
Self {
|
||||||
|
id: make_thing(
|
||||||
|
&ResourceEnum::Users.to_string(),
|
||||||
|
&Uuid::new_v4().to_string(),
|
||||||
|
),
|
||||||
|
fullname: user.fullname,
|
||||||
|
email: user.email,
|
||||||
|
password,
|
||||||
|
phone_number: user.phone_number,
|
||||||
|
is_active: false,
|
||||||
|
gender: None,
|
||||||
|
birthdate: None,
|
||||||
|
avatar: None,
|
||||||
|
is_deleted: false,
|
||||||
|
role: make_thing(&ResourceEnum::Roles.to_string(), &user.role_id),
|
||||||
|
created_at: get_iso_date(),
|
||||||
|
updated_at: get_iso_date(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn patch_password(dto: UsersDetailQueryDto, password: String) -> Self {
|
||||||
|
Self {
|
||||||
|
password,
|
||||||
|
id: dto.id.clone(),
|
||||||
|
..Self::from(dto)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
+75
-105
@@ -1,19 +1,17 @@
|
|||||||
use crate::{
|
use super::{
|
||||||
common_response, extract_email, get_iso_date, hash_password, make_thing,
|
UsersActiveInactiveRequestDto, UsersCreateRequestDto, UsersDetailItemDto,
|
||||||
success_list_response, success_response, validate_request, ResourceEnum,
|
UsersSetNewPasswordRequestDto, UsersUpdateRequestDto,
|
||||||
ResponseSuccessDto,
|
|
||||||
};
|
};
|
||||||
use crate::{
|
use crate::{
|
||||||
AppState, MetaRequestDto, ResponseListSuccessDto, UsersActiveInactiveSchema,
|
AppState, MetaRequestDto, ResponseListSuccessDto, UsersRepository, UsersSchema,
|
||||||
UsersRepository, UsersSchema, UsersSetNewPasswordSchema,
|
};
|
||||||
|
use crate::{
|
||||||
|
ResourceEnum, ResponseSuccessDto, common_response, extract_email, make_thing,
|
||||||
|
success_list_response, success_response, validate_request,
|
||||||
};
|
};
|
||||||
use axum::http::HeaderMap;
|
use axum::http::HeaderMap;
|
||||||
use axum::{http::StatusCode, response::Response};
|
use axum::{http::StatusCode, response::Response};
|
||||||
|
use imphnen_libs::{hash_password, verify_password};
|
||||||
use super::{
|
|
||||||
UsersActiveInactiveRequestDto, UsersCreateRequestDto, UsersDetailItemDto,
|
|
||||||
UsersUpdateRequestDto,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub struct UsersService;
|
pub struct UsersService;
|
||||||
|
|
||||||
@@ -36,17 +34,7 @@ impl UsersService {
|
|||||||
let repo = UsersRepository::new(state);
|
let repo = UsersRepository::new(state);
|
||||||
match repo.query_user_by_id(id).await {
|
match repo.query_user_by_id(id).await {
|
||||||
Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto {
|
Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto {
|
||||||
data: UsersDetailItemDto {
|
data: UsersDetailItemDto::from(&user),
|
||||||
id: user.id,
|
|
||||||
role: user.role,
|
|
||||||
fullname: user.fullname,
|
|
||||||
email: user.email,
|
|
||||||
avatar: user.avatar,
|
|
||||||
phone_number: user.phone_number,
|
|
||||||
is_active: user.is_active,
|
|
||||||
gender: user.gender,
|
|
||||||
birthdate: user.birthdate,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"),
|
Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
|
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
|
||||||
@@ -55,22 +43,15 @@ impl UsersService {
|
|||||||
|
|
||||||
pub async fn get_user_me(headers: HeaderMap, state: &AppState) -> Response {
|
pub async fn get_user_me(headers: HeaderMap, state: &AppState) -> Response {
|
||||||
let repo = UsersRepository::new(state);
|
let repo = UsersRepository::new(state);
|
||||||
let email = extract_email(&headers).unwrap();
|
let email = match extract_email(&headers) {
|
||||||
let user = repo.query_user_by_email(email).await.unwrap();
|
Some(email) => email,
|
||||||
match repo.query_user_by_id(user.id.id.to_raw()).await {
|
None => return common_response(StatusCode::UNAUTHORIZED, "Invalid token"),
|
||||||
Ok(user) => success_response(ResponseSuccessDto {
|
};
|
||||||
data: UsersDetailItemDto {
|
match repo.query_user_by_email(email).await {
|
||||||
id: user.id,
|
Ok(user) if !user.is_deleted => success_response(ResponseSuccessDto {
|
||||||
role: user.role,
|
data: UsersDetailItemDto::from(&user),
|
||||||
fullname: user.fullname,
|
|
||||||
email: user.email,
|
|
||||||
avatar: user.avatar,
|
|
||||||
phone_number: user.phone_number,
|
|
||||||
is_active: user.is_active,
|
|
||||||
gender: user.gender,
|
|
||||||
birthdate: user.birthdate,
|
|
||||||
},
|
|
||||||
}),
|
}),
|
||||||
|
Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
|
Err(e) => common_response(StatusCode::NOT_FOUND, &e.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -90,19 +71,7 @@ impl UsersService {
|
|||||||
{
|
{
|
||||||
return common_response(StatusCode::BAD_REQUEST, "User already exists");
|
return common_response(StatusCode::BAD_REQUEST, "User already exists");
|
||||||
}
|
}
|
||||||
let role_thing = make_thing(&ResourceEnum::Roles.to_string(), &new_user.role_id);
|
match repo.query_create_user(UsersSchema::create(new_user)).await {
|
||||||
match repo
|
|
||||||
.query_create_user(UsersSchema {
|
|
||||||
email: new_user.email.clone(),
|
|
||||||
fullname: new_user.fullname.clone(),
|
|
||||||
password: hash_password(&new_user.password).unwrap(),
|
|
||||||
phone_number: new_user.phone_number.clone(),
|
|
||||||
is_active: new_user.is_active.clone(),
|
|
||||||
role: role_thing,
|
|
||||||
..Default::default()
|
|
||||||
})
|
|
||||||
.await
|
|
||||||
{
|
|
||||||
Ok(msg) => common_response(StatusCode::CREATED, &msg),
|
Ok(msg) => common_response(StatusCode::CREATED, &msg),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string())
|
common_response(StatusCode::INTERNAL_SERVER_ERROR, &err.to_string())
|
||||||
@@ -116,28 +85,10 @@ impl UsersService {
|
|||||||
user: UsersUpdateRequestDto,
|
user: UsersUpdateRequestDto,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let repo = UsersRepository::new(state);
|
let repo = UsersRepository::new(state);
|
||||||
|
|
||||||
if let Err((status, message)) = validate_request(&user) {
|
if let Err((status, message)) = validate_request(&user) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
|
let updated_user = UsersSchema::update(user, id);
|
||||||
let user_id = make_thing(&ResourceEnum::Users.to_string(), &id);
|
|
||||||
let role_id = make_thing(&ResourceEnum::Roles.to_string(), "");
|
|
||||||
|
|
||||||
let updated_user = UsersSchema {
|
|
||||||
id: user_id,
|
|
||||||
fullname: user.fullname,
|
|
||||||
email: user.email,
|
|
||||||
phone_number: user.phone_number,
|
|
||||||
is_active: user.is_active,
|
|
||||||
gender: user.gender,
|
|
||||||
birthdate: user.birthdate,
|
|
||||||
avatar: user.avatar,
|
|
||||||
role: role_id,
|
|
||||||
updated_at: get_iso_date(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
match repo.query_update_user(updated_user).await {
|
match repo.query_update_user(updated_user).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||||
@@ -150,30 +101,18 @@ impl UsersService {
|
|||||||
user: UsersUpdateRequestDto,
|
user: UsersUpdateRequestDto,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let repo = UsersRepository::new(state);
|
let repo = UsersRepository::new(state);
|
||||||
let email = extract_email(&headers).unwrap();
|
let email = match extract_email(&headers) {
|
||||||
let user_data = repo.query_user_by_email(email).await.unwrap();
|
Some(email) => email,
|
||||||
|
None => return common_response(StatusCode::UNAUTHORIZED, "Unauthorized"),
|
||||||
|
};
|
||||||
|
let user_data = match repo.query_user_by_email(email.clone()).await {
|
||||||
|
Ok(user) => user,
|
||||||
|
Err(_) => return common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
|
};
|
||||||
if let Err((status, message)) = validate_request(&user) {
|
if let Err((status, message)) = validate_request(&user) {
|
||||||
return common_response(status, &message);
|
return common_response(status, &message);
|
||||||
}
|
}
|
||||||
let user_id =
|
let updated_user = UsersSchema::update(user, user_data.id.id.to_raw());
|
||||||
make_thing(&ResourceEnum::Users.to_string(), &user_data.id.id.to_raw());
|
|
||||||
let role_id = make_thing(&ResourceEnum::Roles.to_string(), "");
|
|
||||||
let updated_user = UsersSchema {
|
|
||||||
id: user_id,
|
|
||||||
fullname: user.fullname,
|
|
||||||
email: user.email,
|
|
||||||
phone_number: user.phone_number,
|
|
||||||
|
|
||||||
is_active: user.is_active,
|
|
||||||
|
|
||||||
gender: user.gender,
|
|
||||||
birthdate: user.birthdate,
|
|
||||||
avatar: user.avatar,
|
|
||||||
|
|
||||||
role: role_id,
|
|
||||||
updated_at: get_iso_date(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
match repo.query_update_user(updated_user).await {
|
match repo.query_update_user(updated_user).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||||
@@ -183,23 +122,23 @@ impl UsersService {
|
|||||||
pub async fn set_user_active_status(
|
pub async fn set_user_active_status(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
id: String,
|
id: String,
|
||||||
status: UsersActiveInactiveRequestDto,
|
payload: UsersActiveInactiveRequestDto,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let repo = UsersRepository::new(state);
|
let repo = UsersRepository::new(state);
|
||||||
let thing_id = make_thing(&ResourceEnum::Users.to_string(), &id);
|
let thing_id = make_thing(&ResourceEnum::Users.to_string(), &id);
|
||||||
match repo.query_user_by_id(thing_id.id.to_raw()).await {
|
match repo.query_user_by_id(thing_id.id.to_raw()).await {
|
||||||
Ok(_) => match repo
|
Ok(user) if !user.is_deleted => {
|
||||||
.query_active_inactive_user_by_id(
|
let patch = UsersSchema {
|
||||||
id,
|
id: user.id.clone(),
|
||||||
UsersActiveInactiveSchema {
|
is_active: payload.is_active,
|
||||||
is_active: status.is_active,
|
..UsersSchema::from(user)
|
||||||
},
|
};
|
||||||
)
|
match repo.query_update_user(patch).await {
|
||||||
.await
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
{
|
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
}
|
||||||
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
}
|
||||||
},
|
Ok(_) => common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
Err(err) => common_response(StatusCode::BAD_REQUEST, &err.to_string()),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -207,10 +146,41 @@ impl UsersService {
|
|||||||
pub async fn update_user_password(
|
pub async fn update_user_password(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
email: String,
|
email: String,
|
||||||
new_password: UsersSetNewPasswordSchema,
|
payload: UsersSetNewPasswordRequestDto,
|
||||||
) -> Response {
|
) -> Response {
|
||||||
let repo = UsersRepository::new(state);
|
let repo = UsersRepository::new(state);
|
||||||
match repo.query_update_password_user(email, new_password).await {
|
let user = match repo.query_user_by_email(email.clone()).await {
|
||||||
|
Ok(user) if !user.is_deleted => user,
|
||||||
|
_ => return common_response(StatusCode::NOT_FOUND, "User not found"),
|
||||||
|
};
|
||||||
|
let verify_result = match verify_password(&payload.old_password, &user.password)
|
||||||
|
{
|
||||||
|
Ok(result) => result,
|
||||||
|
Err(_) => {
|
||||||
|
return common_response(
|
||||||
|
StatusCode::BAD_REQUEST,
|
||||||
|
"Old password is incorrect",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if !verify_result {
|
||||||
|
return common_response(StatusCode::BAD_REQUEST, "Old password is incorrect");
|
||||||
|
}
|
||||||
|
let new_password = match hash_password(&payload.password) {
|
||||||
|
Ok(pw) => pw,
|
||||||
|
Err(_) => {
|
||||||
|
return common_response(
|
||||||
|
StatusCode::INTERNAL_SERVER_ERROR,
|
||||||
|
"Failed to hash password",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let patch = UsersSchema {
|
||||||
|
id: user.id.clone(),
|
||||||
|
password: new_password,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
match repo.query_update_user(patch).await {
|
||||||
Ok(msg) => common_response(StatusCode::OK, &msg),
|
Ok(msg) => common_response(StatusCode::OK, &msg),
|
||||||
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
Err(e) => common_response(StatusCode::BAD_REQUEST, &e.to_string()),
|
||||||
}
|
}
|
||||||
@@ -5,11 +5,11 @@ edition = "2024"
|
|||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
imphnen-entities = { path = "../imphnen-entities" }
|
imphnen-entities = { path = "../imphnen-entities" }
|
||||||
argon2.workspace = true
|
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
tokio.workspace = true
|
tokio.workspace = true
|
||||||
chrono.workspace = true
|
|
||||||
jsonwebtoken.workspace = true
|
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
|
argon2.workspace = true
|
||||||
lettre.workspace = true
|
lettre.workspace = true
|
||||||
surrealdb = { workspace = true, features = ["kv-mem"] }
|
chrono.workspace = true
|
||||||
|
surrealdb.workspace = true
|
||||||
|
jsonwebtoken.workspace = true
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "imphnen-middleware-service"
|
name = "imphnen-middleware"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" }
|
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
|
||||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||||
|
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
@@ -22,6 +22,6 @@ tokio.workspace = true
|
|||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
anyhow.workspace = true
|
anyhow.workspace = true
|
||||||
tower-http.workspace = true
|
tower-http.workspace = true
|
||||||
|
futures.workspace = true
|
||||||
|
tower.workspace = true
|
||||||
utoipa-swagger-ui.workspace = true
|
utoipa-swagger-ui.workspace = true
|
||||||
futures = "0.3.31"
|
|
||||||
tower = "0.5.2"
|
|
||||||
+2
-2
@@ -2,7 +2,7 @@ use axum::{
|
|||||||
Extension, extract::Request, http::StatusCode, middleware::Next,
|
Extension, extract::Request, http::StatusCode, middleware::Next,
|
||||||
response::Response,
|
response::Response,
|
||||||
};
|
};
|
||||||
use imphnen_iam_service::{UsersItemDtoRaw, UsersRepository};
|
use imphnen_iam::{UsersDetailQueryDto, UsersRepository};
|
||||||
use imphnen_libs::AppState;
|
use imphnen_libs::AppState;
|
||||||
use imphnen_utils::{common_response, extract_email};
|
use imphnen_utils::{common_response, extract_email};
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
@@ -23,7 +23,7 @@ pub async fn auth_middleware(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
let repository = UsersRepository::new(&state);
|
let repository = UsersRepository::new(&state);
|
||||||
let user: Option<UsersItemDtoRaw> =
|
let user: Option<UsersDetailQueryDto> =
|
||||||
match repository.query_user_by_email(email).await {
|
match repository.query_user_by_email(email).await {
|
||||||
Ok(user) => Some(user),
|
Ok(user) => Some(user),
|
||||||
Err(err) => {
|
Err(err) => {
|
||||||
+1
-1
@@ -3,7 +3,7 @@ use axum::{
|
|||||||
http::{Request, Response, StatusCode},
|
http::{Request, Response, StatusCode},
|
||||||
};
|
};
|
||||||
use futures::future::BoxFuture;
|
use futures::future::BoxFuture;
|
||||||
use imphnen_iam_service::{AuthRepository, PermissionsEnum};
|
use imphnen_iam::{AuthRepository, PermissionsEnum};
|
||||||
use imphnen_libs::AppState;
|
use imphnen_libs::AppState;
|
||||||
use imphnen_utils::{common_response, extract_email};
|
use imphnen_utils::{common_response, extract_email};
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
@@ -14,4 +14,4 @@ anyhow.workspace = true
|
|||||||
axum-test.workspace = true
|
axum-test.workspace = true
|
||||||
serde.workspace = true
|
serde.workspace = true
|
||||||
serde_json.workspace = true
|
serde_json.workspace = true
|
||||||
validator.workspace = true
|
validator.workspace = true
|
||||||
|
|||||||
@@ -4,11 +4,5 @@ pub fn bind_filter_value(
|
|||||||
query: Query<'_, Client>,
|
query: Query<'_, Client>,
|
||||||
val: String,
|
val: String,
|
||||||
) -> Query<'_, Client> {
|
) -> Query<'_, Client> {
|
||||||
if let Ok(b) = val.parse::<bool>() {
|
query.bind(("filter", val)) // langsung string aja, udah cukup
|
||||||
query.bind(("filter", b))
|
|
||||||
} else if let Ok(i) = val.parse::<i64>() {
|
|
||||||
query.bind(("filter", i))
|
|
||||||
} else {
|
|
||||||
query.bind(("filter", val))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ pub mod generate_otp;
|
|||||||
pub mod get_id;
|
pub mod get_id;
|
||||||
pub mod make_thing;
|
pub mod make_thing;
|
||||||
pub mod mock_test;
|
pub mod mock_test;
|
||||||
|
pub mod query_builder;
|
||||||
pub mod query_list;
|
pub mod query_list;
|
||||||
pub mod response_format;
|
pub mod response_format;
|
||||||
pub mod validator;
|
pub mod validator;
|
||||||
@@ -21,6 +22,7 @@ pub use imphnen_entities::*;
|
|||||||
pub use imphnen_libs::*;
|
pub use imphnen_libs::*;
|
||||||
pub use make_thing::*;
|
pub use make_thing::*;
|
||||||
pub use mock_test::*;
|
pub use mock_test::*;
|
||||||
|
pub use query_builder::*;
|
||||||
pub use query_list::*;
|
pub use query_list::*;
|
||||||
pub use response_format::*;
|
pub use response_format::*;
|
||||||
pub use validator::*;
|
pub use validator::*;
|
||||||
|
|||||||
@@ -3,3 +3,7 @@ use surrealdb::sql::Thing;
|
|||||||
pub fn make_thing(table: &str, id: &str) -> Thing {
|
pub fn make_thing(table: &str, id: &str) -> Thing {
|
||||||
Thing::from((table, id))
|
Thing::from((table, id))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn make_thing_str(table: &str, id: &str) -> String {
|
||||||
|
format!("{}:⟨{}⟩", table, id)
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
use imphnen_libs::MetaRequestDto;
|
||||||
|
use surrealdb::engine::remote::ws::Client;
|
||||||
|
use surrealdb::method::Query;
|
||||||
|
use surrealdb::sql::Thing;
|
||||||
|
|
||||||
|
pub struct ListQueryBuilder {
|
||||||
|
resource: String,
|
||||||
|
conditions: Vec<String>,
|
||||||
|
limit: usize,
|
||||||
|
start: usize,
|
||||||
|
order_by: Option<String>,
|
||||||
|
order: Option<String>,
|
||||||
|
fetch: Vec<String>,
|
||||||
|
select_fields: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ListQueryBuilder {
|
||||||
|
pub fn from_meta(
|
||||||
|
resource: impl Into<String>,
|
||||||
|
meta: &MetaRequestDto,
|
||||||
|
search_field: impl Into<String>,
|
||||||
|
select_fields: Option<Vec<&str>>,
|
||||||
|
fetch_fields: Option<Vec<&str>>,
|
||||||
|
) -> Self {
|
||||||
|
let mut builder = Self::new(resource)
|
||||||
|
.with_search(meta.search.as_deref(), &search_field.into())
|
||||||
|
.with_filter(meta.filter_by.as_deref(), meta.filter.as_deref())
|
||||||
|
.with_sorting(meta.sort_by.as_deref(), meta.order.as_deref())
|
||||||
|
.with_fetch(fetch_fields)
|
||||||
|
.with_pagination(meta.page, meta.per_page);
|
||||||
|
|
||||||
|
if let Some(fields) = select_fields {
|
||||||
|
builder = builder.with_select_fields(fields);
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new(resource: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
resource: resource.into(),
|
||||||
|
conditions: vec!["is_deleted = false".into()],
|
||||||
|
limit: 10,
|
||||||
|
start: 0,
|
||||||
|
order_by: None,
|
||||||
|
order: None,
|
||||||
|
fetch: vec![],
|
||||||
|
select_fields: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_select_fields(mut self, fields: Vec<&str>) -> Self {
|
||||||
|
self.select_fields = fields.into_iter().map(String::from).collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_search(mut self, search: Option<&str>, field: &str) -> Self {
|
||||||
|
if let Some(search) = search {
|
||||||
|
if !search.is_empty() {
|
||||||
|
self.conditions.push(format!(
|
||||||
|
"string::contains(string::lowercase({} ?? ''), string::lowercase($search))",
|
||||||
|
field
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_filter(mut self, field: Option<&str>, value: Option<&str>) -> Self {
|
||||||
|
if let (Some(f), Some(v)) = (field, value) {
|
||||||
|
if !v.is_empty() {
|
||||||
|
self.conditions.push(format!(
|
||||||
|
"string::contains(string::join('', [{}]), $filter)",
|
||||||
|
f
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_pagination(
|
||||||
|
mut self,
|
||||||
|
page: Option<u64>,
|
||||||
|
per_page: Option<u64>,
|
||||||
|
) -> Self {
|
||||||
|
let limit = per_page.unwrap_or(10).max(1);
|
||||||
|
let page = page.unwrap_or(1).max(1);
|
||||||
|
self.limit = limit as usize;
|
||||||
|
self.start = ((page - 1) * limit) as usize;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_sorting(mut self, sort_by: Option<&str>, order: Option<&str>) -> Self {
|
||||||
|
self.order_by = sort_by.map(|s| s.to_string());
|
||||||
|
self.order = order.map(|o| o.to_uppercase());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fetch(mut self, fetches: Option<Vec<&str>>) -> Self {
|
||||||
|
if let Some(items) = fetches {
|
||||||
|
self.fetch.extend(items.into_iter().map(String::from));
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(self) -> String {
|
||||||
|
let where_clause = if !self.conditions.is_empty() {
|
||||||
|
format!("WHERE {}", self.conditions.join(" AND "))
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let order_clause = if let Some(field) = self.order_by {
|
||||||
|
let ord = self.order.unwrap_or_else(|| "ASC".into());
|
||||||
|
format!("ORDER BY {} {}", field, ord)
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let fetch_clause = if !self.fetch.is_empty() {
|
||||||
|
format!("FETCH {}", self.fetch.join(", "))
|
||||||
|
} else {
|
||||||
|
String::new()
|
||||||
|
};
|
||||||
|
|
||||||
|
let select_clause = if self.select_fields.is_empty() {
|
||||||
|
"*".to_string()
|
||||||
|
} else {
|
||||||
|
self.select_fields.join(", ")
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
r#"
|
||||||
|
SELECT {} FROM {}
|
||||||
|
{}
|
||||||
|
{}
|
||||||
|
LIMIT {} START {}
|
||||||
|
{}
|
||||||
|
"#,
|
||||||
|
select_clause,
|
||||||
|
self.resource,
|
||||||
|
where_clause,
|
||||||
|
order_clause,
|
||||||
|
self.limit,
|
||||||
|
self.start,
|
||||||
|
fetch_clause
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DetailQueryBuilder {
|
||||||
|
resource: String,
|
||||||
|
id: Option<String>,
|
||||||
|
thing: Option<String>,
|
||||||
|
where_field: Option<String>,
|
||||||
|
where_value: Option<String>,
|
||||||
|
select_fields: Vec<String>,
|
||||||
|
fetch_fields: Vec<String>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DetailQueryBuilder {
|
||||||
|
pub fn new(resource: impl Into<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
resource: resource.into(),
|
||||||
|
id: None,
|
||||||
|
thing: None,
|
||||||
|
where_field: None,
|
||||||
|
where_value: None,
|
||||||
|
select_fields: vec![],
|
||||||
|
fetch_fields: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_id(mut self, id: impl Into<String>) -> Self {
|
||||||
|
if self.where_field.is_some() || self.thing.is_some() {
|
||||||
|
panic!("Cannot use with_id() after with_where() or with_thing()");
|
||||||
|
}
|
||||||
|
self.id = Some(id.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_thing(mut self, thing: &Thing) -> Self {
|
||||||
|
if self.id.is_some() || self.where_field.is_some() {
|
||||||
|
panic!("Cannot use with_thing() after with_id() or with_where()");
|
||||||
|
}
|
||||||
|
self.thing = Some(thing.to_string()); // app_users:uuid
|
||||||
|
self.resource = thing.tb.clone(); // update resource dari thing
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_where(mut self, field: impl Into<String>) -> Self {
|
||||||
|
if self.id.is_some() || self.thing.is_some() {
|
||||||
|
panic!("Cannot use with_where() after with_id() or with_thing()");
|
||||||
|
}
|
||||||
|
self.where_field = Some(field.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn where_value(mut self, value: impl Into<String>) -> Self {
|
||||||
|
self.where_value = Some(value.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_select_fields(mut self, fields: Vec<&str>) -> Self {
|
||||||
|
self.select_fields = fields.into_iter().map(String::from).collect();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn with_fetch(mut self, field: impl Into<String>) -> Self {
|
||||||
|
self.fetch_fields.push(field.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn build(&self) -> String {
|
||||||
|
let select_clause = if self.select_fields.is_empty() {
|
||||||
|
"*".to_string()
|
||||||
|
} else {
|
||||||
|
self.select_fields.join(", ")
|
||||||
|
};
|
||||||
|
|
||||||
|
let fetch_clause = if self.fetch_fields.is_empty() {
|
||||||
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!("FETCH {}", self.fetch_fields.join(", "))
|
||||||
|
};
|
||||||
|
|
||||||
|
let from_clause = if let Some(thing) = &self.thing {
|
||||||
|
thing.to_string()
|
||||||
|
} else if let Some(id) = &self.id {
|
||||||
|
format!("{}:⟨{}⟩", self.resource, id)
|
||||||
|
} else if let (Some(field), Some(_)) = (&self.where_field, &self.where_value) {
|
||||||
|
format!("{} WHERE {} = $value", self.resource, field)
|
||||||
|
} else {
|
||||||
|
panic!(
|
||||||
|
"You must set one of with_id(), with_thing(), or with_where()+where_value()"
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
format!(
|
||||||
|
"SELECT {} FROM {} {}",
|
||||||
|
select_clause, from_clause, fetch_clause
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn apply_bindings<'q>(&self, query: Query<'q, Client>) -> Query<'q, Client> {
|
||||||
|
if let (Some(_), Some(value)) = (&self.where_field, &self.where_value) {
|
||||||
|
query.bind(("value", value.clone()))
|
||||||
|
} else {
|
||||||
|
query
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
use super::bind_filter_value;
|
use super::{ListQueryBuilder, bind_filter_value};
|
||||||
use crate::{CountResult, MetaRequestDto, MetaResponseDto, ResponseListSuccessDto};
|
use crate::{CountResult, MetaRequestDto, MetaResponseDto, ResponseListSuccessDto};
|
||||||
use anyhow::{bail, Result};
|
use anyhow::Result;
|
||||||
use serde::{de::DeserializeOwned, Serialize};
|
use serde::{Serialize, de::DeserializeOwned};
|
||||||
use surrealdb::{engine::remote::ws::Client, Surreal};
|
use surrealdb::{Surreal, engine::remote::ws::Client};
|
||||||
|
|
||||||
pub async fn query_list_with_meta<T>(
|
pub async fn query_list_with_meta<T>(
|
||||||
db: &Surreal<Client>,
|
db: &Surreal<Client>,
|
||||||
@@ -10,75 +10,69 @@ pub async fn query_list_with_meta<T>(
|
|||||||
meta: &MetaRequestDto,
|
meta: &MetaRequestDto,
|
||||||
conditions: Vec<String>,
|
conditions: Vec<String>,
|
||||||
custom_select: Option<String>,
|
custom_select: Option<String>,
|
||||||
|
search_field: &str,
|
||||||
|
select_fields: Option<Vec<&str>>,
|
||||||
|
fetch_fields: Option<Vec<&str>>,
|
||||||
) -> Result<ResponseListSuccessDto<Vec<T>>>
|
) -> Result<ResponseListSuccessDto<Vec<T>>>
|
||||||
where
|
where
|
||||||
T: DeserializeOwned + Serialize,
|
T: DeserializeOwned + Serialize,
|
||||||
{
|
{
|
||||||
let page = meta.page.unwrap_or(1);
|
let page = meta.page.unwrap_or(1).max(1);
|
||||||
let per_page = meta.per_page.unwrap_or(10);
|
let per_page = meta.per_page.unwrap_or(10).max(1);
|
||||||
if page < 1 || per_page < 1 {
|
|
||||||
bail!("Invalid pagination: page and per_page must be greater than 0");
|
|
||||||
}
|
|
||||||
let start = (page - 1) * per_page;
|
let start = (page - 1) * per_page;
|
||||||
|
|
||||||
let sql = custom_select.unwrap_or_else(|| {
|
let sql = custom_select.unwrap_or_else(|| {
|
||||||
let mut s = format!("SELECT * FROM {}", table);
|
ListQueryBuilder::from_meta(
|
||||||
if !conditions.is_empty() {
|
table,
|
||||||
s.push_str(" WHERE ");
|
meta,
|
||||||
s.push_str(&conditions.join(" AND "));
|
search_field,
|
||||||
}
|
select_fields,
|
||||||
if let Some(sort_by) = &meta.sort_by {
|
fetch_fields,
|
||||||
let order = match meta
|
)
|
||||||
.order
|
.build()
|
||||||
.clone()
|
|
||||||
.unwrap_or_default()
|
|
||||||
.to_uppercase()
|
|
||||||
.as_str()
|
|
||||||
{
|
|
||||||
"DESC" => "DESC",
|
|
||||||
_ => "ASC",
|
|
||||||
};
|
|
||||||
s.push_str(&format!(" ORDER BY {} {}", sort_by, order));
|
|
||||||
}
|
|
||||||
s.push_str(" LIMIT $per_page START $start");
|
|
||||||
s
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut query_exec = db.query(sql);
|
let mut query_exec = db.query(sql);
|
||||||
if let Some(search) = &meta.search {
|
if let Some(search) = &meta.search {
|
||||||
if !search.is_empty() {
|
if !search.is_empty() {
|
||||||
query_exec = query_exec.bind(("search", search.clone()));
|
query_exec = query_exec.bind(("search", search.to_lowercase().clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(filter_val) = meta.filter.clone() {
|
if let Some(filter_val) = &meta.filter {
|
||||||
query_exec = bind_filter_value(query_exec, filter_val);
|
query_exec = bind_filter_value(query_exec, filter_val.clone());
|
||||||
}
|
}
|
||||||
query_exec = query_exec
|
query_exec = query_exec
|
||||||
.bind(("per_page", per_page))
|
.bind(("per_page", per_page))
|
||||||
.bind(("start", start));
|
.bind(("start", start));
|
||||||
|
|
||||||
let raw: Vec<T> = query_exec.await?.take(0)?;
|
let raw: Vec<T> = query_exec.await?.take(0)?;
|
||||||
let mut count_sql = format!("SELECT count() FROM {}", table);
|
|
||||||
if !conditions.is_empty() {
|
let mut count_query = db.query(format!(
|
||||||
count_sql.push_str(" WHERE ");
|
"SELECT count() FROM {} {}",
|
||||||
count_sql.push_str(&conditions.join(" AND "));
|
table,
|
||||||
}
|
if conditions.is_empty() {
|
||||||
let mut count_query = db.query(count_sql);
|
String::new()
|
||||||
|
} else {
|
||||||
|
format!("WHERE {}", conditions.join(" AND "))
|
||||||
|
}
|
||||||
|
));
|
||||||
if let Some(search) = &meta.search {
|
if let Some(search) = &meta.search {
|
||||||
if !search.is_empty() {
|
if !search.is_empty() {
|
||||||
count_query = count_query.bind(("search", search.clone()));
|
count_query = count_query.bind(("search", search.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if let Some(filter_val) = meta.filter.clone() {
|
if let Some(filter_val) = &meta.filter {
|
||||||
count_query = bind_filter_value(count_query, filter_val);
|
count_query = bind_filter_value(count_query, filter_val.clone());
|
||||||
}
|
}
|
||||||
let count_result: Vec<CountResult> = count_query.await?.take(0)?;
|
let count_result: Vec<CountResult> = count_query.await?.take(0)?;
|
||||||
let total = count_result.first().map(|c| c.count);
|
let total = count_result.first().map(|c| c.count);
|
||||||
|
|
||||||
let meta = MetaResponseDto {
|
|
||||||
page: Some(page),
|
|
||||||
per_page: Some(per_page),
|
|
||||||
total,
|
|
||||||
};
|
|
||||||
Ok(ResponseListSuccessDto {
|
Ok(ResponseListSuccessDto {
|
||||||
data: raw,
|
data: raw,
|
||||||
meta: Some(meta),
|
meta: Some(MetaResponseDto {
|
||||||
|
page: Some(page),
|
||||||
|
per_page: Some(per_page),
|
||||||
|
total,
|
||||||
|
}),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
+1
-1
@@ -4,10 +4,10 @@ version = "0.1.0"
|
|||||||
edition = "2024"
|
edition = "2024"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
|
imphnen-iam = { version = "0.1.0", path = "../imphnen-iam" }
|
||||||
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
imphnen-libs = { version = "0.1.0", path = "../imphnen-libs" }
|
||||||
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
imphnen-utils = { version = "0.1.0", path = "../imphnen-utils" }
|
||||||
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
imphnen-entities = { version = "0.1.0", path = "../imphnen-entities" }
|
||||||
imphnen-iam-service = { version = "0.1.0", path = "../imphnen-iam-service" }
|
|
||||||
axum.workspace = true
|
axum.workspace = true
|
||||||
axum-test.workspace = true
|
axum-test.workspace = true
|
||||||
chrono.workspace = true
|
chrono.workspace = true
|
||||||
|
|||||||
+6
-5
@@ -1,10 +1,11 @@
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod auth_repository_test {
|
mod auth_repository_test {
|
||||||
use crate::{
|
use crate::{
|
||||||
create_mock_app_state, get_iso_date, make_thing, AuthOtpSchema, AuthRepository,
|
AuthOtpSchema, AuthRepository, ResourceEnum, UsersRepository, UsersSchema,
|
||||||
ResourceEnum, RolesItemDtoRaw, UsersItemDtoRaw, UsersRepository, UsersSchema,
|
create_mock_app_state, get_iso_date, make_thing,
|
||||||
};
|
};
|
||||||
use chrono::{Duration, Utc};
|
use chrono::{Duration, Utc};
|
||||||
|
use imphnen_iam::{RolesDetailQueryDto, UsersDetailQueryDto};
|
||||||
use surrealdb::Uuid;
|
use surrealdb::Uuid;
|
||||||
|
|
||||||
fn create_mock_user(email: &str) -> UsersSchema {
|
fn create_mock_user(email: &str) -> UsersSchema {
|
||||||
@@ -47,7 +48,7 @@ mod auth_repository_test {
|
|||||||
let state = create_mock_app_state().await;
|
let state = create_mock_app_state().await;
|
||||||
let auth_repo = AuthRepository::new(&state);
|
let auth_repo = AuthRepository::new(&state);
|
||||||
let email = "delete_me@example.com".to_string();
|
let email = "delete_me@example.com".to_string();
|
||||||
let mock_user = UsersItemDtoRaw {
|
let mock_user = UsersDetailQueryDto {
|
||||||
id: make_thing(&ResourceEnum::UsersCache.to_string(), &email),
|
id: make_thing(&ResourceEnum::UsersCache.to_string(), &email),
|
||||||
fullname: "Test User".into(),
|
fullname: "Test User".into(),
|
||||||
email: email.clone(),
|
email: email.clone(),
|
||||||
@@ -56,7 +57,7 @@ mod auth_repository_test {
|
|||||||
is_active: true,
|
is_active: true,
|
||||||
gender: None,
|
gender: None,
|
||||||
birthdate: None,
|
birthdate: None,
|
||||||
role: RolesItemDtoRaw {
|
role: RolesDetailQueryDto {
|
||||||
id: make_thing("app_roles", &Uuid::new_v4().to_string()),
|
id: make_thing("app_roles", &Uuid::new_v4().to_string()),
|
||||||
name: "Dummy Role".into(),
|
name: "Dummy Role".into(),
|
||||||
permissions: vec![],
|
permissions: vec![],
|
||||||
@@ -69,7 +70,7 @@ mod auth_repository_test {
|
|||||||
created_at: get_iso_date(),
|
created_at: get_iso_date(),
|
||||||
updated_at: get_iso_date(),
|
updated_at: get_iso_date(),
|
||||||
};
|
};
|
||||||
let _: Option<UsersItemDtoRaw> = state
|
let _: Option<UsersDetailQueryDto> = state
|
||||||
.surrealdb_mem
|
.surrealdb_mem
|
||||||
.create((ResourceEnum::UsersCache.to_string(), email.clone()))
|
.create((ResourceEnum::UsersCache.to_string(), email.clone()))
|
||||||
.content(mock_user)
|
.content(mock_user)
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user