1
.env
Normal file
1
.env
Normal file
@@ -0,0 +1 @@
|
||||
DATABASE_URL=postgresql://glyph:glyph@localhost:5432/glyph
|
@@ -16,6 +16,7 @@ cache: &global_cache
|
||||
|
||||
variables:
|
||||
RUSTFLAGS: "-Dwarnings"
|
||||
SQLX_OFFLINE: "true"
|
||||
|
||||
build:
|
||||
image: registry.karaolidis.com/karaolidis/glyph/rust
|
||||
|
22
.sqlx/query-090673660f991b66b0b5a7e2492e94011405a313f89943cff7e64e3ccc674822.json
generated
Normal file
22
.sqlx/query-090673660f991b66b0b5a7e2492e94011405a313f89943cff7e64e3ccc674822.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT COUNT(*) AS \"count!\"\n FROM users\n WHERE name = ANY($1)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "090673660f991b66b0b5a7e2492e94011405a313f89943cff7e64e3ccc674822"
|
||||
}
|
22
.sqlx/query-19d85e2094bcb4ac818975b9477f4cc3de4128ef0aa3383369092f2df56636d9.json
generated
Normal file
22
.sqlx/query-19d85e2094bcb4ac818975b9477f4cc3de4128ef0aa3383369092f2df56636d9.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT name\n FROM groups\n WHERE name = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false
|
||||
]
|
||||
},
|
||||
"hash": "19d85e2094bcb4ac818975b9477f4cc3de4128ef0aa3383369092f2df56636d9"
|
||||
}
|
14
.sqlx/query-275592cdd00626bcb0c5c3054952b6cd170d0692354100d0a1c25c2dba9e9e6b.json
generated
Normal file
14
.sqlx/query-275592cdd00626bcb0c5c3054952b6cd170d0692354100d0a1c25c2dba9e9e6b.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM groups\n WHERE name = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "275592cdd00626bcb0c5c3054952b6cd170d0692354100d0a1c25c2dba9e9e6b"
|
||||
}
|
14
.sqlx/query-282189b1fc3f70e5c2de3f19a3cc8b1fe7e32e4b9b501674ea138acf0cd759ff.json
generated
Normal file
14
.sqlx/query-282189b1fc3f70e5c2de3f19a3cc8b1fe7e32e4b9b501674ea138acf0cd759ff.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM users\n WHERE name = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "282189b1fc3f70e5c2de3f19a3cc8b1fe7e32e4b9b501674ea138acf0cd759ff"
|
||||
}
|
28
.sqlx/query-52bcae42b069a7665baeff903774e624f3e7ae6e2474d03c8619fa1816edefe0.json
generated
Normal file
28
.sqlx/query-52bcae42b069a7665baeff903774e624f3e7ae6e2474d03c8619fa1816edefe0.json
generated
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n g.name,\n COALESCE(array_agg(ug.user_name ORDER BY ug.user_name), ARRAY[]::TEXT[]) AS \"users!\"\n FROM groups g\n LEFT JOIN users_groups ug ON g.name = ug.group_name\n WHERE g.name = $1\n GROUP BY g.name\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "users!",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "52bcae42b069a7665baeff903774e624f3e7ae6e2474d03c8619fa1816edefe0"
|
||||
}
|
19
.sqlx/query-5dbde6bba584448a7be9fd6965aec52a8050d21c453d7ec221be44bd0d893fd1.json
generated
Normal file
19
.sqlx/query-5dbde6bba584448a7be9fd6965aec52a8050d21c453d7ec221be44bd0d893fd1.json
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO users (name, display_name, password, email, disabled, image)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (name) DO UPDATE\n SET display_name = EXCLUDED.display_name,\n password = EXCLUDED.password,\n email = EXCLUDED.email,\n disabled = EXCLUDED.disabled,\n image = EXCLUDED.image\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "5dbde6bba584448a7be9fd6965aec52a8050d21c453d7ec221be44bd0d893fd1"
|
||||
}
|
52
.sqlx/query-74d4ef98ee975bfe90418171dea43397316f8d57ac4d9b09248bb5b0f767b166.json
generated
Normal file
52
.sqlx/query-74d4ef98ee975bfe90418171dea43397316f8d57ac4d9b09248bb5b0f767b166.json
generated
Normal file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT name, display_name, password, email, disabled, image\n FROM users\n WHERE name = $1\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "display_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "disabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "image",
|
||||
"type_info": "Text"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true
|
||||
]
|
||||
},
|
||||
"hash": "74d4ef98ee975bfe90418171dea43397316f8d57ac4d9b09248bb5b0f767b166"
|
||||
}
|
15
.sqlx/query-91b332e6af78793ae53cfdbf8e5edccfe031a21ad1ca8240024adb7e0006570b.json
generated
Normal file
15
.sqlx/query-91b332e6af78793ae53cfdbf8e5edccfe031a21ad1ca8240024adb7e0006570b.json
generated
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n INSERT INTO users_groups (user_name, group_name)\n SELECT * FROM UNNEST($1::text[], $2::text[])\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray",
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "91b332e6af78793ae53cfdbf8e5edccfe031a21ad1ca8240024adb7e0006570b"
|
||||
}
|
58
.sqlx/query-9313aac97fa5191c47874e2e3834ca713d3a3b5556ac26c3cc51ee138f411982.json
generated
Normal file
58
.sqlx/query-9313aac97fa5191c47874e2e3834ca713d3a3b5556ac26c3cc51ee138f411982.json
generated
Normal file
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n u.name,\n u.display_name,\n u.password,\n u.email,\n u.disabled,\n u.image,\n COALESCE(array_agg(ug.group_name ORDER BY ug.group_name), ARRAY[]::TEXT[]) AS \"groups!\"\n FROM users u\n LEFT JOIN users_groups ug ON u.name = ug.user_name\n WHERE u.name = $1\n GROUP BY u.name, u.email, u.disabled, u.image\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "display_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "disabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "image",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "groups!",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "9313aac97fa5191c47874e2e3834ca713d3a3b5556ac26c3cc51ee138f411982"
|
||||
}
|
56
.sqlx/query-95bbd23a12bf44b1bc31859a1fd324c16d76ec2797f68da75fc6e526a3cd0bc4.json
generated
Normal file
56
.sqlx/query-95bbd23a12bf44b1bc31859a1fd324c16d76ec2797f68da75fc6e526a3cd0bc4.json
generated
Normal file
@@ -0,0 +1,56 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n u.name,\n u.display_name,\n u.password,\n u.email,\n u.disabled,\n u.image,\n COALESCE(array_agg(ug.group_name ORDER BY ug.group_name), ARRAY[]::TEXT[]) AS \"groups!\"\n FROM users u\n LEFT JOIN users_groups ug ON u.name = ug.user_name\n GROUP BY u.name, u.email, u.disabled, u.image\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "display_name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 2,
|
||||
"name": "password",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 3,
|
||||
"name": "email",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 4,
|
||||
"name": "disabled",
|
||||
"type_info": "Bool"
|
||||
},
|
||||
{
|
||||
"ordinal": 5,
|
||||
"name": "image",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 6,
|
||||
"name": "groups!",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "95bbd23a12bf44b1bc31859a1fd324c16d76ec2797f68da75fc6e526a3cd0bc4"
|
||||
}
|
22
.sqlx/query-9caa0dac7d2a5098a09278e2331e86d87b1e4a6916836ca0d1a0509a159affc8.json
generated
Normal file
22
.sqlx/query-9caa0dac7d2a5098a09278e2331e86d87b1e4a6916836ca0d1a0509a159affc8.json
generated
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT COUNT(*) AS \"count!\"\n FROM groups\n WHERE name = ANY($1)\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "count!",
|
||||
"type_info": "Int8"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"TextArray"
|
||||
]
|
||||
},
|
||||
"nullable": [
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "9caa0dac7d2a5098a09278e2331e86d87b1e4a6916836ca0d1a0509a159affc8"
|
||||
}
|
14
.sqlx/query-adb2455e26b1cddf90a54d08e79f57258db1212ef4120868581cd0a8a81eff8f.json
generated
Normal file
14
.sqlx/query-adb2455e26b1cddf90a54d08e79f57258db1212ef4120868581cd0a8a81eff8f.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM users_groups\n WHERE group_name = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "adb2455e26b1cddf90a54d08e79f57258db1212ef4120868581cd0a8a81eff8f"
|
||||
}
|
14
.sqlx/query-b1be2a377b5bfaf093618d049c0ed8b759f946580870558c699cce9490a0e0f2.json
generated
Normal file
14
.sqlx/query-b1be2a377b5bfaf093618d049c0ed8b759f946580870558c699cce9490a0e0f2.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO groups (name) VALUES ($1)",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "b1be2a377b5bfaf093618d049c0ed8b759f946580870558c699cce9490a0e0f2"
|
||||
}
|
19
.sqlx/query-ba1cb3d9ffd5dd2260815616abc0b93cd67767cf299f443023d8ab9f9a12c44c.json
generated
Normal file
19
.sqlx/query-ba1cb3d9ffd5dd2260815616abc0b93cd67767cf299f443023d8ab9f9a12c44c.json
generated
Normal file
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "INSERT INTO users (name, display_name, password, email, disabled, image)\n VALUES ($1, $2, $3, $4, $5, $6)\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Text",
|
||||
"Bool",
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "ba1cb3d9ffd5dd2260815616abc0b93cd67767cf299f443023d8ab9f9a12c44c"
|
||||
}
|
26
.sqlx/query-e52660da218cabe80565d95bf77add43558dc3a99c29246cf61d2431ddf34cf8.json
generated
Normal file
26
.sqlx/query-e52660da218cabe80565d95bf77add43558dc3a99c29246cf61d2431ddf34cf8.json
generated
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n SELECT\n g.name,\n COALESCE(array_agg(ug.user_name ORDER BY ug.user_name), ARRAY[]::TEXT[]) AS \"users!\"\n FROM groups g\n LEFT JOIN users_groups ug ON g.name = ug.group_name\n GROUP BY g.name\n ",
|
||||
"describe": {
|
||||
"columns": [
|
||||
{
|
||||
"ordinal": 0,
|
||||
"name": "name",
|
||||
"type_info": "Text"
|
||||
},
|
||||
{
|
||||
"ordinal": 1,
|
||||
"name": "users!",
|
||||
"type_info": "TextArray"
|
||||
}
|
||||
],
|
||||
"parameters": {
|
||||
"Left": []
|
||||
},
|
||||
"nullable": [
|
||||
false,
|
||||
null
|
||||
]
|
||||
},
|
||||
"hash": "e52660da218cabe80565d95bf77add43558dc3a99c29246cf61d2431ddf34cf8"
|
||||
}
|
14
.sqlx/query-e7258b575bc6d1d71f9c62a9c6b56f6103ab7caebc26886346e4ecec399bd86c.json
generated
Normal file
14
.sqlx/query-e7258b575bc6d1d71f9c62a9c6b56f6103ab7caebc26886346e4ecec399bd86c.json
generated
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"db_name": "PostgreSQL",
|
||||
"query": "\n DELETE FROM users_groups\n WHERE user_name = $1\n ",
|
||||
"describe": {
|
||||
"columns": [],
|
||||
"parameters": {
|
||||
"Left": [
|
||||
"Text"
|
||||
]
|
||||
},
|
||||
"nullable": []
|
||||
},
|
||||
"hash": "e7258b575bc6d1d71f9c62a9c6b56f6103ab7caebc26886346e4ecec399bd86c"
|
||||
}
|
560
Cargo.lock
generated
560
Cargo.lock
generated
@@ -17,6 +17,12 @@ version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "512761e0bb2578dd7380c6baaa0f4ce03e84f95e960231d1dec8bf4d7d6e2627"
|
||||
|
||||
[[package]]
|
||||
name = "allocator-api2"
|
||||
version = "0.2.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "683d7910e743518b0e34f1186f92494becacb047c7b6bf616c96772180fef923"
|
||||
|
||||
[[package]]
|
||||
name = "android-tzdata"
|
||||
version = "0.1.1"
|
||||
@@ -283,6 +289,15 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atoi"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f28d99ec8bfea296261ca1af174f24225171fea9664ba9003cbebee704810528"
|
||||
dependencies = [
|
||||
"num-traits",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "atomic-waker"
|
||||
version = "1.1.2"
|
||||
@@ -396,7 +411,7 @@ dependencies = [
|
||||
"miniz_oxide",
|
||||
"object",
|
||||
"rustc-demangle",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -443,6 +458,9 @@ name = "bitflags"
|
||||
version = "2.9.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1b8e56985ec62d17e9c1001dc89c88ecd7dc08e47eba5ec7c29c7b5eeecde967"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "blake2"
|
||||
@@ -655,6 +673,30 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc"
|
||||
version = "3.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9710d3b3739c2e349eb44fe848ad0b7c8cb1e42bd87ee49371df2f7acaf3e675"
|
||||
dependencies = [
|
||||
"crc-catalog",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crc-catalog"
|
||||
version = "2.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19d374276b40fb8bbdee95aef7c7fa6b5316ec764510eb64b8dd0e2ed0d7e7f5"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.3.12"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0f58bbc28f91df819d0aa2a2c00cd19754769c2fad90579b3592b1c9ba7a3115"
|
||||
dependencies = [
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.8.21"
|
||||
@@ -846,6 +888,12 @@ dependencies = [
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dotenvy"
|
||||
version = "0.15.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aaf95b3e5c8f23aa320147307562d361db0ae0d51242340f558153b4eb2439b"
|
||||
|
||||
[[package]]
|
||||
name = "dtoa"
|
||||
version = "0.4.8"
|
||||
@@ -901,6 +949,9 @@ name = "either"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "elliptic-curve"
|
||||
@@ -939,6 +990,17 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "etcetera"
|
||||
version = "0.8.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "136d1b5283a1ab77bd9257427ffd09d8667ced0570b6f938942bc7568ed5b943"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"home",
|
||||
"windows-sys 0.48.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "event-listener"
|
||||
version = "2.5.3"
|
||||
@@ -988,12 +1050,29 @@ version = "0.2.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "28dea519a9695b9977216879a3ebfddf92f1c08c05d984f8996aecd6ecdc811d"
|
||||
|
||||
[[package]]
|
||||
name = "flume"
|
||||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "da0e4dd2a88388a1f4ccc7c9ce104604dab68d9f408dc34cd45823d5a9069095"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
"spin",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "foldhash"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2"
|
||||
|
||||
[[package]]
|
||||
name = "form_urlencoded"
|
||||
version = "1.2.1"
|
||||
@@ -1003,6 +1082,22 @@ dependencies = [
|
||||
"percent-encoding",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fuser"
|
||||
version = "0.15.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53274f494609e77794b627b1a3cddfe45d675a6b2e9ba9c0fdc8d8eee2184369"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"log",
|
||||
"memchr",
|
||||
"nix",
|
||||
"page_size",
|
||||
"pkg-config",
|
||||
"smallvec",
|
||||
"zerocopy",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-channel"
|
||||
version = "0.3.31"
|
||||
@@ -1010,6 +1105,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2dff15bf788c671c1934e366d07e30c1814a8ef514e1af724a602e8a2fbe1b10"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-sink",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1018,6 +1114,28 @@ version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e"
|
||||
|
||||
[[package]]
|
||||
name = "futures-executor"
|
||||
version = "0.3.31"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e28d1d997f585e54aebc3f97d39e72338912123a67330d723fdbb564d646c9f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-task",
|
||||
"futures-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-intrusive"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d930c203dd0b6ff06e0201a4a2fe9149b43c684fd4420555b26d21b1a02956f"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"lock_api",
|
||||
"parking_lot",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "futures-io"
|
||||
version = "0.3.31"
|
||||
@@ -1056,8 +1174,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-sink",
|
||||
"futures-task",
|
||||
"memchr",
|
||||
"pin-project-lite",
|
||||
"pin-utils",
|
||||
"slab",
|
||||
@@ -1129,6 +1249,7 @@ dependencies = [
|
||||
"axum",
|
||||
"axum-extra",
|
||||
"clap",
|
||||
"fuser",
|
||||
"log",
|
||||
"log4rs",
|
||||
"non-empty-string",
|
||||
@@ -1139,6 +1260,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"serde_yaml",
|
||||
"sqlx",
|
||||
"time",
|
||||
"tokio",
|
||||
"uuid",
|
||||
@@ -1166,6 +1288,20 @@ name = "hashbrown"
|
||||
version = "0.15.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "84b26c544d002229e640969970a2e74021aadf6e2f96372b9c58eff97de08eb3"
|
||||
dependencies = [
|
||||
"allocator-api2",
|
||||
"equivalent",
|
||||
"foldhash",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hashlink"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7382cf6263419f2d8df38c55d7da83da5c18aef87fc7a7fc1fb1e344edfe14c1"
|
||||
dependencies = [
|
||||
"hashbrown 0.15.3",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "headers"
|
||||
@@ -1237,6 +1373,15 @@ dependencies = [
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "home"
|
||||
version = "0.5.11"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "589533453244b0995c858700322199b2becb13b627df2851f64a2775d024abcf"
|
||||
dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "http"
|
||||
version = "1.3.1"
|
||||
@@ -1592,6 +1737,16 @@ version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f9fbbcab51052fe104eb5e5d351cf728d30a5be1fe14d9be8a3b097481fb97de"
|
||||
|
||||
[[package]]
|
||||
name = "libsqlite3-sys"
|
||||
version = "0.30.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e99fb7a497b1e3339bc746195567ed8d3e24945ecd636e3619d20b9de9e9149"
|
||||
dependencies = [
|
||||
"pkg-config",
|
||||
"vcpkg",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "linux-raw-sys"
|
||||
version = "0.9.4"
|
||||
@@ -1670,6 +1825,16 @@ version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
|
||||
|
||||
[[package]]
|
||||
name = "md-5"
|
||||
version = "0.10.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d89e7ee0cfbedfc4da3340218492196241d89eefb6dab27de5df917a6d2e78cf"
|
||||
dependencies = [
|
||||
"cfg-if 1.0.0",
|
||||
"digest 0.10.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "memchr"
|
||||
version = "2.7.4"
|
||||
@@ -1702,6 +1867,18 @@ dependencies = [
|
||||
"windows-sys 0.59.0",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "nix"
|
||||
version = "0.29.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "71e2746dc3a24dd78b3cfcb7be93368c6de9963d30f43a6a73998a9cf4b17b46"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"cfg-if 1.0.0",
|
||||
"cfg_aliases",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "non-empty-string"
|
||||
version = "0.2.6"
|
||||
@@ -1886,6 +2063,16 @@ dependencies = [
|
||||
"sha2 0.10.9",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "page_size"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "30d5b2194ed13191c1999ae0704b7839fb18384fa22e49b57eeaa97d79ce40da"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"winapi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "parking"
|
||||
version = "2.2.1"
|
||||
@@ -1912,7 +2099,7 @@ dependencies = [
|
||||
"libc",
|
||||
"redox_syscall",
|
||||
"smallvec",
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1994,6 +2181,12 @@ dependencies = [
|
||||
"spki",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pkg-config"
|
||||
version = "0.3.32"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7edddbd0b52d732b21ad9a5fab5c704c14cd949e5e9a1ec5929a24fded1b904c"
|
||||
|
||||
[[package]]
|
||||
name = "polling"
|
||||
version = "3.8.0"
|
||||
@@ -2690,6 +2883,9 @@ name = "smallvec"
|
||||
version = "1.15.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8917285742e9f3e1683f0a9c4e6b57960b7314d0b08d30d1ecd426713ee2eee9"
|
||||
dependencies = [
|
||||
"serde",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "socket2"
|
||||
@@ -2706,6 +2902,9 @@ name = "spin"
|
||||
version = "0.9.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6980e8d7511241f8acf4aebddbb1ff938df5eebe98691418c4468d0b72a96a67"
|
||||
dependencies = [
|
||||
"lock_api",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "spki"
|
||||
@@ -2717,12 +2916,219 @@ dependencies = [
|
||||
"der",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fefb893899429669dcdd979aff487bd78f4064e5e7907e4269081e0ef7d97dc"
|
||||
dependencies = [
|
||||
"sqlx-core",
|
||||
"sqlx-macros",
|
||||
"sqlx-mysql",
|
||||
"sqlx-postgres",
|
||||
"sqlx-sqlite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ee6798b1838b6a0f69c007c133b8df5866302197e404e8b6ee8ed3e3a5e68dc6"
|
||||
dependencies = [
|
||||
"base64 0.22.1",
|
||||
"bytes",
|
||||
"crc",
|
||||
"crossbeam-queue",
|
||||
"either",
|
||||
"event-listener 5.4.0",
|
||||
"futures-core",
|
||||
"futures-intrusive",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"hashbrown 0.15.3",
|
||||
"hashlink",
|
||||
"indexmap 2.9.0",
|
||||
"log",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"smallvec",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tokio",
|
||||
"tokio-stream",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a2d452988ccaacfbf5e0bdbc348fb91d7c8af5bee192173ac3636b5fb6e6715d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"sqlx-core",
|
||||
"sqlx-macros-core",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-macros-core"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "19a9c1841124ac5a61741f96e1d9e2ec77424bf323962dd894bdb93f37d5219b"
|
||||
dependencies = [
|
||||
"dotenvy",
|
||||
"either",
|
||||
"heck",
|
||||
"hex",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"sqlx-core",
|
||||
"sqlx-mysql",
|
||||
"sqlx-postgres",
|
||||
"sqlx-sqlite",
|
||||
"syn 2.0.101",
|
||||
"tokio",
|
||||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-mysql"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aa003f0038df784eb8fecbbac13affe3da23b45194bd57dba231c8f48199c526"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"bytes",
|
||||
"crc",
|
||||
"digest 0.10.7",
|
||||
"dotenvy",
|
||||
"either",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-io",
|
||||
"futures-util",
|
||||
"generic-array",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac 0.12.1",
|
||||
"itoa 1.0.15",
|
||||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"percent-encoding",
|
||||
"rand 0.8.5",
|
||||
"rsa",
|
||||
"serde",
|
||||
"sha1 0.10.6",
|
||||
"sha2 0.10.9",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-postgres"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db58fcd5a53cf07c184b154801ff91347e4c30d17a3562a635ff028ad5deda46"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"base64 0.22.1",
|
||||
"bitflags",
|
||||
"byteorder",
|
||||
"crc",
|
||||
"dotenvy",
|
||||
"etcetera",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-util",
|
||||
"hex",
|
||||
"hkdf",
|
||||
"hmac 0.12.1",
|
||||
"home",
|
||||
"itoa 1.0.15",
|
||||
"log",
|
||||
"md-5",
|
||||
"memchr",
|
||||
"once_cell",
|
||||
"rand 0.8.5",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"sha2 0.10.9",
|
||||
"smallvec",
|
||||
"sqlx-core",
|
||||
"stringprep",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tracing",
|
||||
"uuid",
|
||||
"whoami",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sqlx-sqlite"
|
||||
version = "0.8.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2d12fe70b2c1b4401038055f90f151b78208de1f9f89a7dbfd41587a10c3eea"
|
||||
dependencies = [
|
||||
"atoi",
|
||||
"flume",
|
||||
"futures-channel",
|
||||
"futures-core",
|
||||
"futures-executor",
|
||||
"futures-intrusive",
|
||||
"futures-util",
|
||||
"libsqlite3-sys",
|
||||
"log",
|
||||
"percent-encoding",
|
||||
"serde",
|
||||
"serde_urlencoded",
|
||||
"sqlx-core",
|
||||
"thiserror 2.0.12",
|
||||
"time",
|
||||
"tracing",
|
||||
"url",
|
||||
"uuid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "stable_deref_trait"
|
||||
version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||
|
||||
[[package]]
|
||||
name = "stringprep"
|
||||
version = "0.1.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7b4df3d392d81bd458a8a621b8bffbd2302a12ffe288a9d931670948749463b1"
|
||||
dependencies = [
|
||||
"unicode-bidi",
|
||||
"unicode-normalization",
|
||||
"unicode-properties",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "strsim"
|
||||
version = "0.11.1"
|
||||
@@ -2921,6 +3327,17 @@ dependencies = [
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-stream"
|
||||
version = "0.1.17"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eca58d7bba4a75707817a2c44174253f9236b2d5fbd055602e9d5c07c139a047"
|
||||
dependencies = [
|
||||
"futures-core",
|
||||
"pin-project-lite",
|
||||
"tokio",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tokio-util"
|
||||
version = "0.6.10"
|
||||
@@ -3002,9 +3419,21 @@ checksum = "784e0ac535deb450455cbfa28a6f0df145ea1bb7ae51b821cf5e7927fdcfbdd0"
|
||||
dependencies = [
|
||||
"log",
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
"tracing-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-attributes"
|
||||
version = "0.1.28"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "395ae124c09f9e6918a2310af6038fba074bcf474ac352496d5910dd59a2226d"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.101",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.33"
|
||||
@@ -3035,12 +3464,33 @@ version = "1.18.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-bidi"
|
||||
version = "0.3.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5c1cb5db39152898a79168971543b1cb5020dff7fe43c8dc468b0885f5e29df5"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-ident"
|
||||
version = "1.0.18"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-normalization"
|
||||
version = "0.1.24"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5033c97c4262335cded6d6fc3e5c18ab755e1a3dc96376350f3d8e9f009ad956"
|
||||
dependencies = [
|
||||
"tinyvec",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-properties"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e70f2a8b45122e719eb623c01822704c4e0907e7e426a05927e1a1cfff5b75d0"
|
||||
|
||||
[[package]]
|
||||
name = "unsafe-any-ors"
|
||||
version = "1.0.0"
|
||||
@@ -3103,6 +3553,12 @@ version = "1.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "943ce29a8a743eb10d6082545d861b24f9d1b160b7d741e0f2cdf726bec909c5"
|
||||
|
||||
[[package]]
|
||||
name = "vcpkg"
|
||||
version = "0.2.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426"
|
||||
|
||||
[[package]]
|
||||
name = "version_check"
|
||||
version = "0.9.5"
|
||||
@@ -3133,6 +3589,12 @@ dependencies = [
|
||||
"wit-bindgen-rt",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "b8dad83b4f25e74f184f64c43b150b91efe7647395b42289f38e50566d82855b"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.100"
|
||||
@@ -3233,6 +3695,16 @@ dependencies = [
|
||||
"rustls-pki-types",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "whoami"
|
||||
version = "1.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6994d13118ab492c3c80c1f81928718159254c53c472bf9ce36f8dae4add02a7"
|
||||
dependencies = [
|
||||
"redox_syscall",
|
||||
"wasite",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "winapi"
|
||||
version = "0.3.9"
|
||||
@@ -3314,13 +3786,22 @@ dependencies = [
|
||||
"windows-link",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.48.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9"
|
||||
dependencies = [
|
||||
"windows-targets 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-sys"
|
||||
version = "0.52.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3329,7 +3810,22 @@ version = "0.59.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b"
|
||||
dependencies = [
|
||||
"windows-targets",
|
||||
"windows-targets 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows-targets"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm 0.48.5",
|
||||
"windows_aarch64_msvc 0.48.5",
|
||||
"windows_i686_gnu 0.48.5",
|
||||
"windows_i686_msvc 0.48.5",
|
||||
"windows_x86_64_gnu 0.48.5",
|
||||
"windows_x86_64_gnullvm 0.48.5",
|
||||
"windows_x86_64_msvc 0.48.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -3338,28 +3834,46 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973"
|
||||
dependencies = [
|
||||
"windows_aarch64_gnullvm",
|
||||
"windows_aarch64_msvc",
|
||||
"windows_i686_gnu",
|
||||
"windows_aarch64_gnullvm 0.52.6",
|
||||
"windows_aarch64_msvc 0.52.6",
|
||||
"windows_i686_gnu 0.52.6",
|
||||
"windows_i686_gnullvm",
|
||||
"windows_i686_msvc",
|
||||
"windows_x86_64_gnu",
|
||||
"windows_x86_64_gnullvm",
|
||||
"windows_x86_64_msvc",
|
||||
"windows_i686_msvc 0.52.6",
|
||||
"windows_x86_64_gnu 0.52.6",
|
||||
"windows_x86_64_gnullvm 0.52.6",
|
||||
"windows_x86_64_msvc 0.52.6",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_aarch64_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_gnu"
|
||||
version = "0.52.6"
|
||||
@@ -3372,24 +3886,48 @@ version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406"
|
||||
|
||||
[[package]]
|
||||
name = "windows_i686_msvc"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnu"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_gnullvm"
|
||||
version = "0.52.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.48.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538"
|
||||
|
||||
[[package]]
|
||||
name = "windows_x86_64_msvc"
|
||||
version = "0.52.6"
|
||||
|
@@ -20,6 +20,7 @@ async-session = "3.0.0"
|
||||
axum = { version = "0.8.4", features = ["macros"] }
|
||||
axum-extra = { version = "0.10.1", features = ["typed-header"] }
|
||||
clap = { version = "4.5.39", features = ["derive"] }
|
||||
fuser = "0.15.1"
|
||||
log = "0.4.27"
|
||||
log4rs = "1.3.0"
|
||||
non-empty-string = { version = "0.2.6", features = ["serde"] }
|
||||
@@ -30,6 +31,7 @@ redis-macros = "0.5.4"
|
||||
serde = "1.0.219"
|
||||
serde_json = "1.0.140"
|
||||
serde_yaml = "0.9.34"
|
||||
sqlx = { version = "0.8.6", features = ["runtime-tokio", "postgres", "time", "uuid"] }
|
||||
time = { version = "0.3.41", features = ["serde"] }
|
||||
tokio = { version = "1.45.1", features = ["rt-multi-thread", "process"] }
|
||||
uuid = { version = "1.17.0", features = ["serde"] }
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# glyph
|
||||
|
||||
*Glyph* is an Authelia user file database manager. Because files are light but unweildy, and LDAP is convenient but complex.
|
||||
*Glyph* is an Authelia user file database manager. Because files are light but unwieldy, and LDAP is convenient but complex.
|
||||
|
||||
## Development
|
||||
|
||||
|
84
flake.lock
generated
Normal file
84
flake.lock
generated
Normal file
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"ref": "main",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs": {
|
||||
"locked": {
|
||||
"lastModified": 1748939429,
|
||||
"narHash": "sha256-IrdLwKWucb9xj1dOpbXHuaV1GzHYx51ZGF4wbl5NPwU=",
|
||||
"owner": "karaolidis",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "7b041169050f5a7b6a15bacdb68a935cee995fe7",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "karaolidis",
|
||||
"ref": "integration",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748243702,
|
||||
"narHash": "sha256-9YzfeN8CB6SzNPyPm2XjRRqSixDopTapaRsnTpXUEY8=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "1f3f7b784643d488ba4bf315638b2b0a4c5fb007",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"ref": "main",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
52
flake.nix
Executable file
52
flake.nix
Executable file
@@ -0,0 +1,52 @@
|
||||
{
|
||||
inputs = {
|
||||
nixpkgs = {
|
||||
type = "github";
|
||||
owner = "karaolidis";
|
||||
repo = "nixpkgs";
|
||||
ref = "integration";
|
||||
};
|
||||
|
||||
flake-utils = {
|
||||
type = "github";
|
||||
owner = "numtide";
|
||||
repo = "flake-utils";
|
||||
ref = "main";
|
||||
};
|
||||
|
||||
treefmt-nix = {
|
||||
type = "github";
|
||||
owner = "numtide";
|
||||
repo = "treefmt-nix";
|
||||
ref = "main";
|
||||
|
||||
inputs.nixpkgs.follows = "nixpkgs";
|
||||
};
|
||||
};
|
||||
|
||||
outputs =
|
||||
{ self, nixpkgs, ... }@inputs:
|
||||
inputs.flake-utils.lib.eachDefaultSystem (
|
||||
system:
|
||||
let
|
||||
pkgs = nixpkgs.legacyPackages.${system};
|
||||
treefmt = inputs.treefmt-nix.lib.evalModule pkgs ./treefmt.nix;
|
||||
in
|
||||
{
|
||||
devShells.default = pkgs.mkShell {
|
||||
packages = with pkgs; [
|
||||
cargo
|
||||
rustc
|
||||
rustfmt
|
||||
clippy
|
||||
cargo-udeps
|
||||
cargo-outdated
|
||||
sqlx-cli
|
||||
];
|
||||
};
|
||||
|
||||
formatter = treefmt.config.build.wrapper;
|
||||
checks.formatting = treefmt.config.build.check self;
|
||||
}
|
||||
);
|
||||
}
|
56
migrations/20250605080246_init.sql
Normal file
56
migrations/20250605080246_init.sql
Normal file
@@ -0,0 +1,56 @@
|
||||
CREATE TABLE IF NOT EXISTS users (
|
||||
name TEXT PRIMARY KEY,
|
||||
display_name TEXT NOT NULL,
|
||||
password TEXT NOT NULL,
|
||||
email TEXT NOT NULL UNIQUE,
|
||||
disabled BOOLEAN NOT NULL,
|
||||
image TEXT,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS groups (
|
||||
name TEXT PRIMARY KEY,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS users_groups (
|
||||
user_name TEXT NOT NULL,
|
||||
group_name TEXT NOT NULL,
|
||||
PRIMARY KEY (user_name, group_name),
|
||||
FOREIGN KEY (user_name) REFERENCES users(name) ON DELETE CASCADE,
|
||||
FOREIGN KEY (group_name) REFERENCES groups(name) ON DELETE CASCADE
|
||||
);
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
NEW.updated_at = NOW();
|
||||
RETURN NEW;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER update_users_timestamp
|
||||
BEFORE UPDATE ON users
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_timestamp();
|
||||
|
||||
CREATE TRIGGER update_groups_timestamp
|
||||
BEFORE UPDATE ON groups
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_timestamp();
|
||||
|
||||
CREATE OR REPLACE FUNCTION update_users_groups_timestamp()
|
||||
RETURNS TRIGGER AS $$
|
||||
BEGIN
|
||||
UPDATE users SET updated_at = NOW() WHERE name = NEW.user_name;
|
||||
UPDATE groups SET updated_at = NOW() WHERE name = NEW.group_name;
|
||||
RETURN NULL;
|
||||
END;
|
||||
$$ LANGUAGE plpgsql;
|
||||
|
||||
CREATE TRIGGER update_users_groups_timestamp
|
||||
AFTER INSERT OR DELETE ON users_groups
|
||||
FOR EACH ROW
|
||||
EXECUTE FUNCTION update_users_groups_timestamp();
|
@@ -41,6 +41,15 @@ pub struct AutheliaConfig {
|
||||
pub user_database: PathBuf,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct PostgresqlConfig {
|
||||
pub user: String,
|
||||
pub password: String,
|
||||
pub host: String,
|
||||
pub port: u16,
|
||||
pub database: String,
|
||||
}
|
||||
|
||||
#[derive(Clone, Deserialize)]
|
||||
pub struct RedisConfig {
|
||||
pub host: String,
|
||||
@@ -54,6 +63,7 @@ pub struct Config {
|
||||
pub server: ServerConfig,
|
||||
pub oauth: OAuthConfig,
|
||||
pub authelia: AutheliaConfig,
|
||||
pub postgresql: PostgresqlConfig,
|
||||
pub redis: RedisConfig,
|
||||
}
|
||||
|
||||
|
13
src/main.rs
13
src/main.rs
@@ -11,7 +11,7 @@ use axum::serve;
|
||||
use clap::Parser;
|
||||
use log::info;
|
||||
use log4rs::config::Deserializers;
|
||||
use std::net::SocketAddr;
|
||||
use std::{error::Error, net::SocketAddr};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use config::{Args, Config};
|
||||
@@ -25,6 +25,8 @@ async fn main() {
|
||||
let config = Config::try_from(&args.config).unwrap();
|
||||
let state = State::from_config(config.clone()).await;
|
||||
|
||||
init(&state).await.unwrap();
|
||||
|
||||
let routes = routes::routes(state);
|
||||
let app = axum::Router::new().nest(&format!("{}/api", config.server.subpath), routes);
|
||||
|
||||
@@ -34,3 +36,12 @@ async fn main() {
|
||||
info!("Listening on {}", listener.local_addr().unwrap());
|
||||
serve(listener, app).await.unwrap();
|
||||
}
|
||||
|
||||
async fn init(state: &State) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
sqlx::migrate!("./migrations")
|
||||
.run(&state.pg_pool)
|
||||
.await
|
||||
.expect("Failed to run migrations");
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
@@ -1,4 +1,3 @@
|
||||
use non_empty_string::NonEmptyString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
|
||||
@@ -6,50 +5,20 @@ use std::collections::HashMap;
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UsersFile {
|
||||
pub users: HashMap<NonEmptyString, UserFile>,
|
||||
pub users: HashMap<String, UserFile>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Option<HashMap<NonEmptyString, Value>>,
|
||||
pub extra: Option<HashMap<String, Value>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct UserFile {
|
||||
pub displayname: NonEmptyString,
|
||||
pub password: NonEmptyString,
|
||||
pub displayname: String,
|
||||
pub password: String,
|
||||
pub email: Option<String>,
|
||||
pub disabled: Option<bool>,
|
||||
pub groups: Option<Vec<NonEmptyString>>,
|
||||
pub groups: Option<Vec<String>>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Option<HashMap<NonEmptyString, Value>>,
|
||||
}
|
||||
|
||||
impl From<super::users::User> for UserFile {
|
||||
fn from(user: super::users::User) -> Self {
|
||||
Self {
|
||||
displayname: user.displayname,
|
||||
email: user.email,
|
||||
password: user.password,
|
||||
disabled: if user.disabled { Some(true) } else { None },
|
||||
groups: if user.groups.is_empty() {
|
||||
None
|
||||
} else {
|
||||
Some(user.groups)
|
||||
},
|
||||
extra: user.extra,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::users::Users> for UsersFile {
|
||||
fn from(users: super::users::Users) -> Self {
|
||||
Self {
|
||||
users: users
|
||||
.users
|
||||
.into_iter()
|
||||
.map(|(key, user)| (key, UserFile::from(user)))
|
||||
.collect(),
|
||||
extra: users.extra,
|
||||
}
|
||||
}
|
||||
pub extra: Option<HashMap<String, Value>>,
|
||||
}
|
||||
|
@@ -1,49 +1,143 @@
|
||||
use non_empty_string::NonEmptyString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::error::Error;
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{PgPool, prelude::FromRow, query, query_as};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct Group {
|
||||
pub name: String,
|
||||
}
|
||||
|
||||
impl Group {
|
||||
pub async fn select_by_name(
|
||||
pool: &PgPool,
|
||||
name: &str,
|
||||
) -> Result<Option<Self>, Box<dyn Error + Send + Sync>> {
|
||||
let group = query_as!(
|
||||
Group,
|
||||
r#"
|
||||
SELECT name
|
||||
FROM groups
|
||||
WHERE name = $1
|
||||
"#,
|
||||
name
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
Ok(group)
|
||||
}
|
||||
|
||||
pub async fn delete_by_name(
|
||||
pool: &PgPool,
|
||||
name: &str,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
query!(
|
||||
r#"
|
||||
DELETE FROM groups
|
||||
WHERE name = $1
|
||||
"#,
|
||||
name
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn all_exist_by_names(
|
||||
pool: &PgPool,
|
||||
names: &[String],
|
||||
) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
||||
let row = query!(
|
||||
r#"
|
||||
SELECT COUNT(*) AS "count!"
|
||||
FROM groups
|
||||
WHERE name = ANY($1)
|
||||
"#,
|
||||
names
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(row.count == i64::try_from(names.len()).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Group {
|
||||
pub users: Vec<NonEmptyString>,
|
||||
pub struct GroupWithUsers {
|
||||
pub name: String,
|
||||
pub users: Vec<String>,
|
||||
}
|
||||
|
||||
pub struct Groups {
|
||||
pub groups: HashMap<NonEmptyString, Group>,
|
||||
}
|
||||
|
||||
impl Deref for Groups {
|
||||
type Target = HashMap<NonEmptyString, Group>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.groups
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Groups {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.groups
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::authelia::UsersFile> for Groups {
|
||||
fn from(users_file: super::authelia::UsersFile) -> Self {
|
||||
users_file.users.into_iter().fold(
|
||||
Self {
|
||||
groups: HashMap::new(),
|
||||
},
|
||||
|mut acc, (key, user)| {
|
||||
for group in user.groups.unwrap_or_default() {
|
||||
acc.entry(group)
|
||||
.or_insert_with(|| Group { users: Vec::new() })
|
||||
.users
|
||||
.push(key.clone());
|
||||
}
|
||||
acc
|
||||
},
|
||||
impl GroupWithUsers {
|
||||
pub async fn select(pool: &PgPool) -> Result<Vec<Self>, Box<dyn Error + Send + Sync>> {
|
||||
let groups = query_as!(
|
||||
GroupWithUsers,
|
||||
r#"
|
||||
SELECT
|
||||
g.name,
|
||||
COALESCE(array_agg(ug.user_name ORDER BY ug.user_name), ARRAY[]::TEXT[]) AS "users!"
|
||||
FROM groups g
|
||||
LEFT JOIN users_groups ug ON g.name = ug.group_name
|
||||
GROUP BY g.name
|
||||
"#
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
Ok(groups)
|
||||
}
|
||||
|
||||
pub async fn select_by_name(
|
||||
pool: &PgPool,
|
||||
name: &str,
|
||||
) -> Result<Option<Self>, Box<dyn Error + Send + Sync>> {
|
||||
let group = query_as!(
|
||||
GroupWithUsers,
|
||||
r#"
|
||||
SELECT
|
||||
g.name,
|
||||
COALESCE(array_agg(ug.user_name ORDER BY ug.user_name), ARRAY[]::TEXT[]) AS "users!"
|
||||
FROM groups g
|
||||
LEFT JOIN users_groups ug ON g.name = ug.group_name
|
||||
WHERE g.name = $1
|
||||
GROUP BY g.name
|
||||
"#,
|
||||
name
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
Ok(group)
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
pool: &PgPool,
|
||||
group_with_users: &Self,
|
||||
) -> Result<(), Box<dyn std::error::Error>> {
|
||||
let mut tx = pool.begin().await?;
|
||||
|
||||
query!(
|
||||
r#"INSERT INTO groups (name) VALUES ($1)"#,
|
||||
group_with_users.name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
query!(
|
||||
r#"
|
||||
INSERT INTO users_groups (user_name, group_name)
|
||||
SELECT * FROM UNNEST($1::text[], $2::text[])
|
||||
"#,
|
||||
&group_with_users.users,
|
||||
&vec![group_with_users.name.clone(); group_with_users.users.len()]
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
74
src/models/intersections.rs
Normal file
74
src/models/intersections.rs
Normal file
@@ -0,0 +1,74 @@
|
||||
use std::error::Error;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::{FromRow, PgPool, query};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct UsersGroups {
|
||||
pub user_name: String,
|
||||
pub group_name: String,
|
||||
}
|
||||
|
||||
impl UsersGroups {
|
||||
pub async fn set_users_for_group(
|
||||
pool: &PgPool,
|
||||
group_name: &str,
|
||||
users: &[String],
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut tx = pool.begin().await?;
|
||||
|
||||
query!(
|
||||
r#"
|
||||
DELETE FROM users_groups
|
||||
WHERE group_name = $1
|
||||
"#,
|
||||
group_name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
query!(
|
||||
r#"
|
||||
INSERT INTO users_groups (user_name, group_name)
|
||||
SELECT * FROM UNNEST($1::text[], $2::text[])
|
||||
"#,
|
||||
users,
|
||||
&vec![group_name.to_string(); users.len()]
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn set_groups_for_user(
|
||||
pool: &PgPool,
|
||||
user_name: &str,
|
||||
groups: &[String],
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut tx = pool.begin().await?;
|
||||
|
||||
query!(
|
||||
r#"
|
||||
DELETE FROM users_groups
|
||||
WHERE user_name = $1
|
||||
"#,
|
||||
user_name
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
query!(
|
||||
r#"
|
||||
INSERT INTO users_groups (user_name, group_name)
|
||||
SELECT * FROM UNNEST($1::text[], $2::text[])
|
||||
"#,
|
||||
&vec![user_name.to_string(); groups.len()],
|
||||
groups
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
@@ -1,9 +1,9 @@
|
||||
use redis_macros::{FromRedisValue, ToRedisArgs};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::FromRow;
|
||||
use time::UtcDateTime;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Serialize, Deserialize, FromRedisValue, ToRedisArgs)]
|
||||
#[derive(Serialize, Deserialize, FromRow)]
|
||||
struct Invite {
|
||||
id: Uuid,
|
||||
groups: Vec<String>,
|
||||
|
@@ -1,4 +1,5 @@
|
||||
pub mod authelia;
|
||||
pub mod groups;
|
||||
pub mod intersections;
|
||||
pub mod invites;
|
||||
pub mod users;
|
||||
|
@@ -1,68 +1,199 @@
|
||||
use non_empty_string::NonEmptyString;
|
||||
use std::error::Error;
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value;
|
||||
use sqlx::{FromRow, PgPool, query, query_as};
|
||||
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
ops::{Deref, DerefMut},
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, FromRow)]
|
||||
pub struct User {
|
||||
pub displayname: NonEmptyString,
|
||||
pub email: Option<String>,
|
||||
pub password: NonEmptyString,
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub password: String,
|
||||
pub email: String,
|
||||
#[serde(default)]
|
||||
pub disabled: bool,
|
||||
pub groups: Vec<NonEmptyString>,
|
||||
#[serde(default)]
|
||||
pub image: Option<String>,
|
||||
}
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Option<HashMap<NonEmptyString, Value>>,
|
||||
impl User {
|
||||
pub async fn select_by_name(
|
||||
pool: &PgPool,
|
||||
name: &str,
|
||||
) -> Result<Option<Self>, Box<dyn Error + Send + Sync>> {
|
||||
let user = query_as!(
|
||||
User,
|
||||
r#"
|
||||
SELECT name, display_name, password, email, disabled, image
|
||||
FROM users
|
||||
WHERE name = $1
|
||||
"#,
|
||||
name
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn upsert(pool: &PgPool, user: &Self) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
query!(
|
||||
r#"
|
||||
INSERT INTO users (name, display_name, password, email, disabled, image)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
ON CONFLICT (name) DO UPDATE
|
||||
SET display_name = EXCLUDED.display_name,
|
||||
password = EXCLUDED.password,
|
||||
email = EXCLUDED.email,
|
||||
disabled = EXCLUDED.disabled,
|
||||
image = EXCLUDED.image
|
||||
"#,
|
||||
user.name,
|
||||
user.display_name,
|
||||
user.password,
|
||||
user.email,
|
||||
user.disabled,
|
||||
user.image
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_by_name(
|
||||
pool: &PgPool,
|
||||
name: &str,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
query!(
|
||||
r#"
|
||||
DELETE FROM users
|
||||
WHERE name = $1
|
||||
"#,
|
||||
name
|
||||
)
|
||||
.execute(pool)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn all_exist_by_names(
|
||||
pool: &PgPool,
|
||||
names: &[String],
|
||||
) -> Result<bool, Box<dyn Error + Send + Sync>> {
|
||||
let row = query!(
|
||||
r#"
|
||||
SELECT COUNT(*) AS "count!"
|
||||
FROM users
|
||||
WHERE name = ANY($1)
|
||||
"#,
|
||||
names
|
||||
)
|
||||
.fetch_one(pool)
|
||||
.await?;
|
||||
|
||||
Ok(row.count == i64::try_from(names.len()).unwrap())
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize)]
|
||||
pub struct Users {
|
||||
pub users: HashMap<NonEmptyString, User>,
|
||||
|
||||
#[serde(flatten)]
|
||||
pub extra: Option<HashMap<NonEmptyString, Value>>,
|
||||
pub struct UserWithGroups {
|
||||
pub name: String,
|
||||
pub display_name: String,
|
||||
pub password: String,
|
||||
pub email: String,
|
||||
#[serde(default)]
|
||||
pub disabled: bool,
|
||||
#[serde(default)]
|
||||
pub image: Option<String>,
|
||||
pub groups: Vec<String>,
|
||||
}
|
||||
|
||||
impl Deref for Users {
|
||||
type Target = HashMap<NonEmptyString, User>;
|
||||
impl UserWithGroups {
|
||||
pub async fn select(pool: &PgPool) -> Result<Vec<Self>, Box<dyn Error + Send + Sync>> {
|
||||
let users = query_as!(
|
||||
UserWithGroups,
|
||||
r#"
|
||||
SELECT
|
||||
u.name,
|
||||
u.display_name,
|
||||
u.password,
|
||||
u.email,
|
||||
u.disabled,
|
||||
u.image,
|
||||
COALESCE(array_agg(ug.group_name ORDER BY ug.group_name), ARRAY[]::TEXT[]) AS "groups!"
|
||||
FROM users u
|
||||
LEFT JOIN users_groups ug ON u.name = ug.user_name
|
||||
GROUP BY u.name, u.email, u.disabled, u.image
|
||||
"#
|
||||
)
|
||||
.fetch_all(pool)
|
||||
.await?;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.users
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for Users {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.users
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::authelia::UserFile> for User {
|
||||
fn from(user_file: super::authelia::UserFile) -> Self {
|
||||
Self {
|
||||
displayname: user_file.displayname,
|
||||
email: user_file.email,
|
||||
password: user_file.password,
|
||||
disabled: user_file.disabled.unwrap_or(false),
|
||||
groups: user_file.groups.unwrap_or_default(),
|
||||
extra: user_file.extra,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<super::authelia::UsersFile> for Users {
|
||||
fn from(users_file: super::authelia::UsersFile) -> Self {
|
||||
Self {
|
||||
users: users_file
|
||||
.users
|
||||
.into_iter()
|
||||
.map(|(key, user)| (key, User::from(user)))
|
||||
.collect(),
|
||||
extra: users_file.extra,
|
||||
}
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
pub async fn select_by_name(
|
||||
pool: &PgPool,
|
||||
name: &str,
|
||||
) -> Result<Option<Self>, Box<dyn Error + Send + Sync>> {
|
||||
let user = query_as!(
|
||||
UserWithGroups,
|
||||
r#"
|
||||
SELECT
|
||||
u.name,
|
||||
u.display_name,
|
||||
u.password,
|
||||
u.email,
|
||||
u.disabled,
|
||||
u.image,
|
||||
COALESCE(array_agg(ug.group_name ORDER BY ug.group_name), ARRAY[]::TEXT[]) AS "groups!"
|
||||
FROM users u
|
||||
LEFT JOIN users_groups ug ON u.name = ug.user_name
|
||||
WHERE u.name = $1
|
||||
GROUP BY u.name, u.email, u.disabled, u.image
|
||||
"#,
|
||||
name
|
||||
)
|
||||
.fetch_optional(pool)
|
||||
.await?;
|
||||
|
||||
Ok(user)
|
||||
}
|
||||
|
||||
pub async fn insert(
|
||||
pool: &PgPool,
|
||||
user_with_groups: &Self,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let mut tx = pool.begin().await?;
|
||||
|
||||
query!(
|
||||
r#"INSERT INTO users (name, display_name, password, email, disabled, image)
|
||||
VALUES ($1, $2, $3, $4, $5, $6)
|
||||
"#,
|
||||
user_with_groups.name,
|
||||
user_with_groups.display_name,
|
||||
user_with_groups.password,
|
||||
user_with_groups.email,
|
||||
user_with_groups.disabled,
|
||||
user_with_groups.image
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
query!(
|
||||
r#"
|
||||
INSERT INTO users_groups (user_name, group_name)
|
||||
SELECT * FROM UNNEST($1::text[], $2::text[])
|
||||
"#,
|
||||
&user_with_groups.groups,
|
||||
&vec![user_with_groups.name.clone(); user_with_groups.groups.len()]
|
||||
)
|
||||
.execute(&mut *tx)
|
||||
.await?;
|
||||
|
||||
tx.commit().await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@@ -6,227 +6,173 @@ use axum::{
|
||||
response::{IntoResponse, Redirect},
|
||||
routing,
|
||||
};
|
||||
use log::error;
|
||||
|
||||
use non_empty_string::NonEmptyString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{models::groups, routes::auth, state::State};
|
||||
use crate::{
|
||||
config::Config,
|
||||
models::{self, groups::Group},
|
||||
routes::auth,
|
||||
state::State,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct GroupResponse {
|
||||
groupname: NonEmptyString,
|
||||
users: Vec<NonEmptyString>,
|
||||
users: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<(NonEmptyString, groups::Group)> for GroupResponse {
|
||||
fn from((groupname, group): (NonEmptyString, groups::Group)) -> Self {
|
||||
Self {
|
||||
groupname,
|
||||
users: group.users,
|
||||
}
|
||||
impl From<models::groups::GroupWithUsers> for GroupResponse {
|
||||
fn from(group: models::groups::GroupWithUsers) -> Self {
|
||||
Self { users: group.users }
|
||||
}
|
||||
}
|
||||
|
||||
type GroupsResponse = HashMap<NonEmptyString, GroupResponse>;
|
||||
|
||||
impl From<groups::Groups> for GroupsResponse {
|
||||
fn from(groups: groups::Groups) -> Self {
|
||||
groups
|
||||
.groups
|
||||
.into_iter()
|
||||
.map(|(key, group)| (key.clone(), GroupResponse::from((key, group))))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
type GroupsResponse = HashMap<String, GroupResponse>;
|
||||
|
||||
pub async fn get_all(
|
||||
_user: auth::User,
|
||||
extract::State(state): extract::State<State>,
|
||||
_: auth::User,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let groups = state.load_groups().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let groups_with_users = models::groups::GroupWithUsers::select(&pg_pool)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Ok(Json(GroupsResponse::from(groups)))
|
||||
let groups_response = groups_with_users
|
||||
.into_iter()
|
||||
.map(|group| (group.name.clone(), GroupResponse::from(group)))
|
||||
.collect::<GroupsResponse>();
|
||||
|
||||
Ok(Json(groups_response))
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
_user: auth::User,
|
||||
extract::Path(groupname): extract::Path<NonEmptyString>,
|
||||
extract::State(state): extract::State<State>,
|
||||
_: auth::User,
|
||||
extract::Path(name): extract::Path<NonEmptyString>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let groups = state.load_groups().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let group_with_users = models::groups::GroupWithUsers::select_by_name(&pg_pool, name.as_str())
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
groups.get(&groupname).cloned().map_or_else(
|
||||
|| Err(StatusCode::NOT_FOUND),
|
||||
|group| Ok(Json(GroupResponse::from((groupname, group))).into_response()),
|
||||
)
|
||||
Ok(Json(GroupResponse::from(group_with_users)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GroupCreate {
|
||||
groupname: NonEmptyString,
|
||||
name: NonEmptyString,
|
||||
users: Vec<NonEmptyString>,
|
||||
}
|
||||
|
||||
impl From<GroupCreate> for groups::Group {
|
||||
fn from(update: GroupCreate) -> Self {
|
||||
Self {
|
||||
users: update.users,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
_user: auth::User,
|
||||
extract::State(state): extract::State<State>,
|
||||
_: auth::User,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::Json(group_create): extract::Json<GroupCreate>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let (mut users, groups) = state.load_users_and_groups().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
let groupname = group_create.groupname.clone();
|
||||
if groups.contains_key(&groupname) {
|
||||
if models::groups::Group::select_by_name(&pg_pool, group_create.name.as_str())
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.is_some()
|
||||
{
|
||||
return Err(StatusCode::CONFLICT);
|
||||
}
|
||||
|
||||
let group_created = groups::Group::from(group_create);
|
||||
let users = group_create
|
||||
.users
|
||||
.into_iter()
|
||||
.map(|u| u.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
for username in &group_created.users {
|
||||
if !users.contains_key(username) {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
users
|
||||
.get_mut(username)
|
||||
.unwrap()
|
||||
.groups
|
||||
.push(groupname.clone());
|
||||
if !models::users::User::all_exist_by_names(&pg_pool, &users)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
{
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
state.save_users(users).map_err(|e| {
|
||||
error!("Failed to save users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let group_with_users = models::groups::GroupWithUsers {
|
||||
name: group_create.name.to_string(),
|
||||
users,
|
||||
};
|
||||
|
||||
Ok(Json(GroupResponse::from((groupname, group_created))).into_response())
|
||||
models::groups::GroupWithUsers::insert(&pg_pool, &group_with_users)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct GroupUpdate {
|
||||
groupname: Option<NonEmptyString>,
|
||||
users: Vec<NonEmptyString>,
|
||||
}
|
||||
|
||||
impl From<GroupUpdate> for groups::Group {
|
||||
fn from(update: GroupUpdate) -> Self {
|
||||
Self {
|
||||
users: update.users,
|
||||
}
|
||||
}
|
||||
users: Option<Vec<NonEmptyString>>,
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
user: auth::User,
|
||||
extract::Path(groupname): extract::Path<NonEmptyString>,
|
||||
extract::State(state): extract::State<State>,
|
||||
session_user: auth::User,
|
||||
extract::Path(name): extract::Path<NonEmptyString>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::State(config): extract::State<Config>,
|
||||
extract::Json(group_update): extract::Json<GroupUpdate>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let (mut users, groups) = state.load_users_and_groups().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let group = models::groups::Group::select_by_name(&pg_pool, name.as_str())
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
let new_groupname = group_update
|
||||
.groupname
|
||||
.clone()
|
||||
.unwrap_or_else(|| groupname.clone());
|
||||
let mut logout = false;
|
||||
|
||||
let group_existing = groups.get(&groupname).ok_or(StatusCode::NOT_FOUND)?;
|
||||
let group_updated = groups::Group::from(group_update);
|
||||
if let Some(users) = &group_update.users {
|
||||
let users = users.iter().map(ToString::to_string).collect::<Vec<_>>();
|
||||
|
||||
if groupname != new_groupname
|
||||
&& (groupname == state.config.oauth.admin_group
|
||||
|| new_groupname == state.config.oauth.admin_group)
|
||||
{
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
if groupname != new_groupname && groups.contains_key(&new_groupname) {
|
||||
return Err(StatusCode::CONFLICT);
|
||||
}
|
||||
|
||||
for user in &group_existing.users {
|
||||
let user = users.get_mut(user).unwrap();
|
||||
let pos = user.groups.iter().position(|g| g == &groupname).unwrap();
|
||||
user.groups.remove(pos);
|
||||
}
|
||||
|
||||
for username in &group_updated.users {
|
||||
if !users.contains_key(username) {
|
||||
if !models::users::User::all_exist_by_names(&pg_pool, &users)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
{
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
let user = users.get_mut(username).unwrap();
|
||||
if !user.groups.contains(&new_groupname) {
|
||||
user.groups.push(new_groupname.clone());
|
||||
models::intersections::UsersGroups::set_users_for_group(
|
||||
&pg_pool,
|
||||
group.name.as_str(),
|
||||
&users,
|
||||
)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
if name == config.oauth.admin_group && !users.contains(&session_user.username) {
|
||||
logout = true;
|
||||
}
|
||||
}
|
||||
|
||||
state.save_users(users).map_err(|e| {
|
||||
error!("Failed to save users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
if new_groupname == state.config.oauth.admin_group
|
||||
&& !group_updated
|
||||
.users
|
||||
.iter()
|
||||
.any(|group_user| *group_user == *user.username.to_string())
|
||||
{
|
||||
if logout {
|
||||
return Ok(Redirect::to("/api/auth/logout").into_response());
|
||||
}
|
||||
|
||||
Ok(Json(GroupResponse::from((new_groupname, group_updated))).into_response())
|
||||
Ok(().into_response())
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
_user: auth::User,
|
||||
extract::Path(groupname): extract::Path<String>,
|
||||
extract::State(state): extract::State<State>,
|
||||
_: auth::User,
|
||||
extract::Path(name): extract::Path<String>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::State(config): extract::State<Config>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let (mut users, groups) = state.load_users_and_groups().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
if groupname == state.config.oauth.admin_group {
|
||||
if name == config.oauth.admin_group {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
if let Some(old_group) = groups.get(&groupname) {
|
||||
for user in &old_group.users {
|
||||
let user = users.get_mut(user).unwrap();
|
||||
let pos = user.groups.iter().position(|g| g == &groupname).unwrap();
|
||||
user.groups.remove(pos);
|
||||
}
|
||||
} else {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
let group = models::groups::Group::select_by_name(&pg_pool, &name)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
state.save_users(users).map_err(|e| {
|
||||
error!("Failed to save users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
Group::delete_by_name(&pg_pool, &group.name)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT.into_response())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn routes(state: State) -> Router {
|
||||
|
@@ -6,205 +6,216 @@ use axum::{
|
||||
response::{IntoResponse, Redirect},
|
||||
routing,
|
||||
};
|
||||
use log::error;
|
||||
|
||||
use non_empty_string::NonEmptyString;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use sqlx::PgPool;
|
||||
|
||||
use crate::{
|
||||
models::users, routes::auth, state::State, utils::crypto::generate_random_password_hash,
|
||||
config::Config, models, routes::auth, state::State,
|
||||
utils::crypto::generate_random_password_hash,
|
||||
};
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
struct UserResponse {
|
||||
username: NonEmptyString,
|
||||
displayname: NonEmptyString,
|
||||
email: Option<String>,
|
||||
groups: Option<Vec<NonEmptyString>>,
|
||||
display_name: String,
|
||||
email: String,
|
||||
disabled: bool,
|
||||
image: Option<String>,
|
||||
groups: Vec<String>,
|
||||
}
|
||||
|
||||
impl From<(NonEmptyString, users::User)> for UserResponse {
|
||||
fn from((username, user): (NonEmptyString, users::User)) -> Self {
|
||||
impl From<models::users::UserWithGroups> for UserResponse {
|
||||
fn from(user: models::users::UserWithGroups) -> Self {
|
||||
Self {
|
||||
username,
|
||||
displayname: user.displayname,
|
||||
display_name: user.display_name,
|
||||
email: user.email,
|
||||
groups: Some(user.groups),
|
||||
disabled: user.disabled,
|
||||
image: user.image,
|
||||
groups: user.groups,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type UsersResponse = HashMap<NonEmptyString, UserResponse>;
|
||||
|
||||
impl From<users::Users> for UsersResponse {
|
||||
fn from(users: users::Users) -> Self {
|
||||
users
|
||||
.users
|
||||
.into_iter()
|
||||
.map(|(key, user)| (key.clone(), UserResponse::from((key, user))))
|
||||
.collect()
|
||||
}
|
||||
}
|
||||
type UsersResponse = HashMap<String, UserResponse>;
|
||||
|
||||
pub async fn get_all(
|
||||
_user: auth::User,
|
||||
extract::State(state): extract::State<State>,
|
||||
_: auth::User,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let users = state.load_users().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let users_with_groups = models::users::UserWithGroups::select(&pg_pool)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Ok(Json(UsersResponse::from(users)))
|
||||
let users_response = users_with_groups
|
||||
.into_iter()
|
||||
.map(|user| (user.name.clone(), UserResponse::from(user)))
|
||||
.collect::<UsersResponse>();
|
||||
|
||||
Ok(Json(users_response))
|
||||
}
|
||||
|
||||
pub async fn get(
|
||||
_user: auth::User,
|
||||
extract::Path(username): extract::Path<NonEmptyString>,
|
||||
extract::State(state): extract::State<State>,
|
||||
_: auth::User,
|
||||
extract::Path(name): extract::Path<NonEmptyString>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let users = state.load_users().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let user_with_groups = models::users::UserWithGroups::select_by_name(&pg_pool, name.as_str())
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
users.get(&username).cloned().map_or_else(
|
||||
|| Err(StatusCode::NOT_FOUND),
|
||||
|user| Ok(Json(UserResponse::from((username, user))).into_response()),
|
||||
)
|
||||
Ok(Json(UserResponse::from(user_with_groups)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UserCreate {
|
||||
username: NonEmptyString,
|
||||
name: NonEmptyString,
|
||||
displayname: NonEmptyString,
|
||||
email: NonEmptyString,
|
||||
disabled: Option<bool>,
|
||||
groups: Option<Vec<NonEmptyString>>,
|
||||
}
|
||||
|
||||
#[allow(clippy::fallible_impl_from)]
|
||||
impl From<UserCreate> for users::User {
|
||||
fn from(user_create: UserCreate) -> Self {
|
||||
Self {
|
||||
displayname: user_create.displayname,
|
||||
email: Some(user_create.email.to_string()),
|
||||
password: NonEmptyString::new(generate_random_password_hash()).unwrap(),
|
||||
disabled: user_create.disabled.unwrap_or(false),
|
||||
groups: user_create.groups.unwrap_or_default(),
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
disabled: bool,
|
||||
image: Option<NonEmptyString>,
|
||||
groups: Vec<NonEmptyString>,
|
||||
}
|
||||
|
||||
pub async fn create(
|
||||
_user: auth::User,
|
||||
extract::State(state): extract::State<State>,
|
||||
_: auth::User,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::Json(user_create): extract::Json<UserCreate>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let mut users = state.load_users().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
let username = user_create.username.clone();
|
||||
if users.contains_key(&username) {
|
||||
if models::users::User::select_by_name(&pg_pool, user_create.name.as_str())
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.is_some()
|
||||
{
|
||||
return Err(StatusCode::CONFLICT);
|
||||
}
|
||||
|
||||
let user_created = users::User::from(user_create);
|
||||
users.users.insert(username.clone(), user_created.clone());
|
||||
let groups = user_create
|
||||
.groups
|
||||
.into_iter()
|
||||
.map(|g| g.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
state.save_users(users).map_err(|e| {
|
||||
error!("Failed to save users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
if !models::groups::Group::all_exist_by_names(&pg_pool, &groups)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
{
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
Ok(Json(UserResponse::from((username, user_created))).into_response())
|
||||
let user_with_groups = models::users::UserWithGroups {
|
||||
name: user_create.name.to_string(),
|
||||
display_name: user_create.displayname.to_string(),
|
||||
password: generate_random_password_hash(),
|
||||
email: user_create.email.to_string(),
|
||||
disabled: user_create.disabled,
|
||||
image: user_create.image.map(|i| i.to_string()),
|
||||
groups,
|
||||
};
|
||||
|
||||
models::users::UserWithGroups::insert(&pg_pool, &user_with_groups)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
pub struct UserUpdate {
|
||||
username: Option<NonEmptyString>,
|
||||
displayname: NonEmptyString,
|
||||
email: NonEmptyString,
|
||||
display_name: Option<NonEmptyString>,
|
||||
email: Option<NonEmptyString>,
|
||||
disabled: Option<bool>,
|
||||
image: Option<NonEmptyString>,
|
||||
groups: Option<Vec<NonEmptyString>>,
|
||||
}
|
||||
|
||||
impl From<(Self, UserUpdate)> for users::User {
|
||||
fn from((user_existing, user_update): (Self, UserUpdate)) -> Self {
|
||||
Self {
|
||||
displayname: user_update.displayname,
|
||||
email: Some(user_update.email.to_string()),
|
||||
password: user_existing.password,
|
||||
disabled: user_update.disabled.unwrap_or(user_existing.disabled),
|
||||
groups: user_update.groups.unwrap_or(user_existing.groups),
|
||||
extra: user_existing.extra,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn update(
|
||||
user: auth::User,
|
||||
extract::Path(username): extract::Path<NonEmptyString>,
|
||||
extract::State(state): extract::State<State>,
|
||||
session_user: auth::User,
|
||||
extract::Path(name): extract::Path<NonEmptyString>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
extract::State(config): extract::State<Config>,
|
||||
extract::Json(user_update): extract::Json<UserUpdate>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let mut users = state.load_users().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let user = models::users::User::select_by_name(&pg_pool, name.as_str())
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
let new_username = user_update
|
||||
.username
|
||||
.clone()
|
||||
.unwrap_or_else(|| username.clone());
|
||||
let mut logout = false;
|
||||
|
||||
let user_existing = users.remove(&username).ok_or(StatusCode::NOT_FOUND)?;
|
||||
let user_updated = users::User::from((user_existing, user_update));
|
||||
if let Some(groups) = user_update.groups {
|
||||
let groups = groups
|
||||
.into_iter()
|
||||
.map(|g| g.to_string())
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
users
|
||||
.users
|
||||
.insert(new_username.clone(), user_updated.clone());
|
||||
if !models::groups::Group::all_exist_by_names(&pg_pool, &groups)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
{
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
state.save_users(users).map_err(|e| {
|
||||
error!("Failed to save users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
models::intersections::UsersGroups::set_groups_for_user(
|
||||
&pg_pool,
|
||||
user.name.as_str(),
|
||||
&groups,
|
||||
)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
if user.username.to_string() == username && (username != new_username || user_updated.disabled)
|
||||
{
|
||||
if name == session_user.username.to_string() && !groups.contains(&config.oauth.admin_group)
|
||||
{
|
||||
logout = true;
|
||||
}
|
||||
}
|
||||
|
||||
let user = models::users::User {
|
||||
name: user.name,
|
||||
display_name: user_update
|
||||
.display_name
|
||||
.map(|d| d.to_string())
|
||||
.unwrap_or(user.display_name),
|
||||
password: user.password,
|
||||
email: user_update
|
||||
.email
|
||||
.map(|e| e.to_string())
|
||||
.unwrap_or(user.email),
|
||||
disabled: user_update.disabled.unwrap_or(user.disabled),
|
||||
image: user_update.image.map(|i| i.to_string()).or(user.image),
|
||||
};
|
||||
|
||||
models::users::User::upsert(&pg_pool, &user)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
if logout {
|
||||
return Ok(Redirect::to("/api/auth/logout").into_response());
|
||||
}
|
||||
|
||||
Ok(Json(UserResponse::from((new_username, user_updated))).into_response())
|
||||
Ok(().into_response())
|
||||
}
|
||||
|
||||
pub async fn delete(
|
||||
user: auth::User,
|
||||
extract::Path(username): extract::Path<String>,
|
||||
extract::State(state): extract::State<State>,
|
||||
session_user: auth::User,
|
||||
extract::Path(name): extract::Path<String>,
|
||||
extract::State(pg_pool): extract::State<PgPool>,
|
||||
) -> Result<impl IntoResponse, StatusCode> {
|
||||
let mut users = state.load_users().map_err(|e| {
|
||||
error!("Failed to read users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
|
||||
if users.remove(&username).is_none() {
|
||||
return Err(StatusCode::NOT_FOUND);
|
||||
if name == session_user.username.to_string() {
|
||||
return Err(StatusCode::FORBIDDEN);
|
||||
}
|
||||
|
||||
state.save_users(users).map_err(|e| {
|
||||
error!("Failed to save users file: {e}");
|
||||
StatusCode::INTERNAL_SERVER_ERROR
|
||||
})?;
|
||||
let user = models::users::User::select_by_name(&pg_pool, &name)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?
|
||||
.ok_or(StatusCode::NOT_FOUND)?;
|
||||
|
||||
if user.username.to_string() == username {
|
||||
return Ok(Redirect::to("/api/auth/logout").into_response());
|
||||
}
|
||||
models::users::User::delete_by_name(&pg_pool, &user.name)
|
||||
.await
|
||||
.or(Err(StatusCode::INTERNAL_SERVER_ERROR))?;
|
||||
|
||||
Ok(StatusCode::NO_CONTENT.into_response())
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn routes(state: State) -> Router {
|
||||
|
30
src/state.rs
30
src/state.rs
@@ -11,6 +11,7 @@ use openidconnect::{
|
||||
reqwest,
|
||||
};
|
||||
use redis::{self, AsyncCommands};
|
||||
use sqlx::{PgPool, postgres::PgPoolOptions};
|
||||
use tokio::spawn;
|
||||
|
||||
use crate::config::Config;
|
||||
@@ -47,6 +48,7 @@ pub struct State {
|
||||
pub config: Config,
|
||||
pub oauth_http_client: reqwest::Client,
|
||||
pub oauth_client: OAuthClient,
|
||||
pub pg_pool: PgPool,
|
||||
pub redis_client: redis::aio::MultiplexedConnection,
|
||||
pub session_store: RedisSessionStore,
|
||||
}
|
||||
@@ -54,6 +56,7 @@ pub struct State {
|
||||
impl State {
|
||||
pub async fn from_config(config: Config) -> Self {
|
||||
let (oauth_http_client, oauth_client) = oauth_client(&config).await;
|
||||
let pg_pool = pg_pool(&config).await;
|
||||
let redis_client = redis_client(&config).await;
|
||||
let session_store = session_store(&config);
|
||||
|
||||
@@ -61,6 +64,7 @@ impl State {
|
||||
config,
|
||||
oauth_http_client,
|
||||
oauth_client,
|
||||
pg_pool,
|
||||
redis_client,
|
||||
session_store,
|
||||
}
|
||||
@@ -85,6 +89,12 @@ impl FromRef<State> for OAuthClient {
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<State> for PgPool {
|
||||
fn from_ref(state: &State) -> Self {
|
||||
state.pg_pool.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl FromRef<State> for redis::aio::MultiplexedConnection {
|
||||
fn from_ref(state: &State) -> Self {
|
||||
state.redis_client.clone()
|
||||
@@ -127,6 +137,21 @@ async fn oauth_client(config: &Config) -> (reqwest::Client, OAuthClient) {
|
||||
(oauth_http_client, oauth_client)
|
||||
}
|
||||
|
||||
async fn pg_pool(config: &Config) -> PgPool {
|
||||
PgPoolOptions::new()
|
||||
.max_connections(5)
|
||||
.connect(&format!(
|
||||
"postgres://{}:{}@{}:{}/{}",
|
||||
config.postgresql.user,
|
||||
config.postgresql.password,
|
||||
config.postgresql.host,
|
||||
config.postgresql.port,
|
||||
config.postgresql.database
|
||||
))
|
||||
.await
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
async fn redis_client(config: &Config) -> redis::aio::MultiplexedConnection {
|
||||
let url = format!(
|
||||
"redis://{}:{}/{}",
|
||||
@@ -153,7 +178,7 @@ async fn redis_client(config: &Config) -> redis::aio::MultiplexedConnection {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let channel = format!("__keyevent@{}__:expired", database);
|
||||
let channel = format!("__keyevent@{database}__:expired");
|
||||
connection.subscribe(&[channel]).await.unwrap();
|
||||
|
||||
while let Some(msg) = rx.recv().await {
|
||||
@@ -178,7 +203,6 @@ fn session_store(config: &Config) -> RedisSessionStore {
|
||||
"redis://{}:{}/{}",
|
||||
config.redis.host, config.redis.port, config.redis.database
|
||||
);
|
||||
let session_store = RedisSessionStore::new(url).unwrap().with_prefix("session:");
|
||||
|
||||
session_store
|
||||
RedisSessionStore::new(url).unwrap().with_prefix("session:")
|
||||
}
|
||||
|
@@ -1,39 +0,0 @@
|
||||
use std::error::Error;
|
||||
|
||||
use crate::{models, state::State};
|
||||
|
||||
impl State {
|
||||
pub fn load_users(&self) -> Result<models::users::Users, Box<dyn Error + Send + Sync>> {
|
||||
let file_contents = std::fs::read_to_string(&self.config.authelia.user_database)?;
|
||||
let users_file: models::authelia::UsersFile = serde_yaml::from_str(&file_contents)?;
|
||||
let users = models::users::Users::from(users_file);
|
||||
Ok(users)
|
||||
}
|
||||
|
||||
pub fn load_groups(&self) -> Result<models::groups::Groups, Box<dyn Error + Send + Sync>> {
|
||||
let file_contents = std::fs::read_to_string(&self.config.authelia.user_database)?;
|
||||
let users_file = serde_yaml::from_str::<models::authelia::UsersFile>(&file_contents)?;
|
||||
let groups = models::groups::Groups::from(users_file);
|
||||
Ok(groups)
|
||||
}
|
||||
|
||||
pub fn load_users_and_groups(
|
||||
&self,
|
||||
) -> Result<(models::users::Users, models::groups::Groups), Box<dyn Error + Send + Sync>> {
|
||||
let file_contents = std::fs::read_to_string(&self.config.authelia.user_database)?;
|
||||
let users_file = serde_yaml::from_str::<models::authelia::UsersFile>(&file_contents)?;
|
||||
let users = models::users::Users::from(users_file.clone());
|
||||
let groups = models::groups::Groups::from(users_file);
|
||||
Ok((users, groups))
|
||||
}
|
||||
|
||||
pub fn save_users(
|
||||
&self,
|
||||
users: models::users::Users,
|
||||
) -> Result<(), Box<dyn Error + Send + Sync>> {
|
||||
let users_file = models::authelia::UsersFile::from(users);
|
||||
let file_contents = serde_yaml::to_string(&users_file)?;
|
||||
std::fs::write(&self.config.authelia.user_database, file_contents)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
@@ -1,2 +1 @@
|
||||
pub mod authelia;
|
||||
pub mod crypto;
|
||||
|
@@ -2,8 +2,7 @@ FROM docker.io/library/rust AS builder
|
||||
|
||||
ARG BUILD_MODE=debug
|
||||
|
||||
RUN apt-get update && apt-get install -y musl-tools && apt-get clean
|
||||
RUN rustup target add x86_64-unknown-linux-musl
|
||||
RUN apt-get update && apt-get clean
|
||||
|
||||
WORKDIR /app
|
||||
|
||||
@@ -13,11 +12,13 @@ RUN cargo fetch
|
||||
RUN rm -rf src
|
||||
|
||||
COPY src ./src
|
||||
COPY migrations ./migrations
|
||||
COPY .sqlx ./.sqlx
|
||||
|
||||
RUN cargo build --target=x86_64-unknown-linux-musl $(if [ "$BUILD_MODE" = "release" ]; then echo "--release"; else echo ""; fi)
|
||||
RUN mkdir -p build && cp target/x86_64-unknown-linux-musl/$(if [ "$BUILD_MODE" = "release" ]; then echo "release"; else echo "debug"; fi)/glyph build/glyph
|
||||
RUN cargo build $(if [ "$BUILD_MODE" = "release" ]; then echo "--release"; else echo ""; fi)
|
||||
RUN mkdir -p build && cp target/$(if [ "$BUILD_MODE" = "release" ]; then echo "release"; else echo "debug"; fi)/glyph build/glyph
|
||||
|
||||
FROM docker.io/library/alpine
|
||||
FROM docker.io/library/debian:bookworm-slim
|
||||
|
||||
COPY --from=builder /app/build/glyph /usr/local/bin/glyph
|
||||
|
||||
|
@@ -20,6 +20,19 @@ spec:
|
||||
"/etc/glyph/log4rs.yml",
|
||||
]
|
||||
|
||||
- name: postgresql
|
||||
image: docker.io/library/postgres:latest
|
||||
env:
|
||||
- name: POSTGRES_DB
|
||||
value: glyph
|
||||
- name: POSTGRES_USER
|
||||
value: glyph
|
||||
- name: POSTGRES_PASSWORD
|
||||
value: glyph
|
||||
ports:
|
||||
- containerPort: 5432
|
||||
hostPort: 5432
|
||||
|
||||
- name: redis
|
||||
image: docker.io/library/redis:latest
|
||||
|
||||
|
17
treefmt.nix
Executable file
17
treefmt.nix
Executable file
@@ -0,0 +1,17 @@
|
||||
{ ... }:
|
||||
{
|
||||
projectRootFile = "flake.nix";
|
||||
|
||||
programs = {
|
||||
nixfmt = {
|
||||
enable = true;
|
||||
strict = true;
|
||||
};
|
||||
};
|
||||
|
||||
settings = {
|
||||
global = {
|
||||
excludes = [ ".envrc" ];
|
||||
};
|
||||
};
|
||||
}
|
Reference in New Issue
Block a user