diff --git a/.env b/.env new file mode 100644 index 0000000000000000000000000000000000000000..6b77cf254053d6bbf2e113be23f20444e9706801 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +DATABASE_URL=sqlite://./database.sqlite \ No newline at end of file diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000000000000000000000000000000000000..35410cacdc5e87f985c93a96520f5e11a5c822e4 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# 默认忽略的文件 +/shelf/ +/workspace.xml +# 基于编辑器的 HTTP 客户端请求 +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/apiserver.iml b/.idea/apiserver.iml new file mode 100644 index 0000000000000000000000000000000000000000..cf84ae4a69877a117dad3f555c9d8ebf05a4fc20 --- /dev/null +++ b/.idea/apiserver.iml @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml new file mode 100644 index 0000000000000000000000000000000000000000..ad765240e80a7d422b5c5a8401142fe971290e11 --- /dev/null +++ b/.idea/dataSources.xml @@ -0,0 +1,20 @@ + + + + + sqlite.xerial + true + org.sqlite.JDBC + jdbc:sqlite:$PROJECT_DIR$/database.sqlite + $ProjectFileDir$ + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/xerial/sqlite-jdbc/3.45.1.0/sqlite-jdbc-3.45.1.0.jar + + + file://$APPLICATION_CONFIG_DIR$/jdbc-drivers/Xerial SQLiteJDBC/3.45.1/org/slf4j/slf4j-api/1.7.36/slf4j-api-1.7.36.jar + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000000000000000000000000000000000000..3eff73054ab6112beb4f341301aaced00bac7222 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000000000000000000000000000000000000..35eb1ddfbbc029bcab630581847471d7f238ec53 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index a312dd98ff5c654cb61fddc50bf1bf83701c94d2..4c2b76fbbca11cf4e3971c0cd7778f94a60e60b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -238,6 +238,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anstream" version = "0.6.15" @@ -444,8 +459,13 @@ version = "0.4.38" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" dependencies = [ + "android-tzdata", + "iana-time-zone", + "js-sys", "num-traits", "serde", + "wasm-bindgen", + "windows-targets", ] [[package]] @@ -471,6 +491,12 @@ dependencies = [ "version_check", ] +[[package]] +name = "core-foundation-sys" +version = "0.8.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" + [[package]] name = "cpufeatures" version = "0.2.14" @@ -499,6 +525,41 @@ dependencies = [ "typenum", ] +[[package]] +name = "darling" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "strsim", + "syn", +] + +[[package]] +name = "darling_macro" +version = "0.20.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +dependencies = [ + "darling_core", + "quote", + "syn", +] + [[package]] name = "deranged" version = "0.3.11" @@ -521,6 +582,55 @@ dependencies = [ "syn", ] +[[package]] +name = "diesel" +version = "2.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "158fe8e2e68695bd615d7e4f3227c0727b151330d3e253b525086c348d055d5e" +dependencies = [ + "bitflags", + "byteorder", + "diesel_derives", + "itoa", + "libsqlite3-sys", + "pq-sys", + "r2d2", + "time", +] + +[[package]] +name = "diesel_derives" +version = "2.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f2c3de51e2ba6bf2a648285696137aaf0f5f487bcbea93972fe8a364e131a4" +dependencies = [ + "diesel_table_macro_syntax", + "dsl_auto_type", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "diesel_migrations" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a73ce704bad4231f001bff3314d91dce4aba0770cee8b233991859abc15c1f6" +dependencies = [ + "diesel", + "migrations_internals", + "migrations_macros", +] + +[[package]] +name = "diesel_table_macro_syntax" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "209c735641a413bc68c4923a9d6ad4bcb3ca306b794edaa7eb0b3228a99ffb25" +dependencies = [ + "syn", +] + [[package]] name = "digest" version = "0.10.7" @@ -542,12 +652,38 @@ dependencies = [ "syn", ] +[[package]] +name = "dotenv" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77c90badedccf4105eca100756a0b1289e191f6fcbdadd3cee1d2f614f97da8f" + +[[package]] +name = "dsl_auto_type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5d9abe6314103864cc2d8901b7ae224e0ab1a103a0a416661b4097b0779b607" +dependencies = [ + "darling", + "either", + "heck", + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "dyn-clone" version = "1.0.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0d6ef0072f8a535281e4876be788938b528e9a1d43900b82c2569af7da799125" +[[package]] +name = "either" +version = "1.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60b1af1c220855b6ceac025d3f6ecdd2b7c4894bfe9cd9bda4fbb4bc7c0d4cf0" + [[package]] name = "email_address" version = "0.2.9" @@ -754,6 +890,12 @@ version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1e087f84d4f86bf4b218b927129862374b72199ae7d8657835f1e89000eea4fb" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -861,6 +1003,29 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.61" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "235e081f3925a06703c2d0117ea8b91f042756fd6e7a6e5d901e8ca1a996b220" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + [[package]] name = "icu_collections" version = "1.5.0" @@ -979,6 +1144,12 @@ dependencies = [ "syn", ] +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" @@ -1101,9 +1272,14 @@ dependencies = [ "actix-service", "actix-web", "async-trait", + "chrono", + "diesel", + "diesel_migrations", + "dotenv", "env_logger", "jsonschema", "k8s-openapi", + "r2d2", "schemars", "serde", "serde_json", @@ -1128,6 +1304,16 @@ version = "0.2.159" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "561d97a539a36e26a9a5fad1ea11a3039a67714694aaa379433e580854bc3dc5" +[[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 = "litemap" version = "0.7.3" @@ -1173,6 +1359,27 @@ version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "migrations_internals" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fd01039851e82f8799046eabbb354056283fb265c8ec0996af940f4e85a380ff" +dependencies = [ + "serde", + "toml", +] + +[[package]] +name = "migrations_macros" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ffb161cc72176cb37aa47f1fc520d3ef02263d67d661f44f05d05a079e1237fd" +dependencies = [ + "migrations_internals", + "proc-macro2", + "quote", +] + [[package]] name = "mime" version = "0.3.17" @@ -1384,6 +1591,15 @@ dependencies = [ "zerocopy", ] +[[package]] +name = "pq-sys" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f6cc05d7ea95200187117196eee9edd0644424911821aeb28a18ce60ea0b8793" +dependencies = [ + "vcpkg", +] + [[package]] name = "proc-macro2" version = "1.0.87" @@ -1402,6 +1618,17 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "r2d2" +version = "0.8.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51de85fb3fb6524929c8a2eb85e6b6d363de4e8c48f9e2c2eac4944abc181c93" +dependencies = [ + "log", + "parking_lot", + "scheduled-thread-pool", +] + [[package]] name = "rand" version = "0.8.5" @@ -1566,6 +1793,15 @@ version = "1.0.18" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" +[[package]] +name = "scheduled-thread-pool" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3cbc66816425a074528352f5789333ecff06ca41b36b0b0efdfbb29edc391a19" +dependencies = [ + "parking_lot", +] + [[package]] name = "schemars" version = "0.8.21" @@ -1655,6 +1891,15 @@ dependencies = [ "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87607cb1398ed59d48732e575a4c28a7a8ebf2454b964fe3f224f2afc07909e1" +dependencies = [ + "serde", +] + [[package]] name = "serde_urlencoded" version = "0.7.1" @@ -1724,6 +1969,12 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" +[[package]] +name = "strsim" +version = "0.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" + [[package]] name = "syn" version = "2.0.79" @@ -1841,6 +2092,40 @@ dependencies = [ "tokio", ] +[[package]] +name = "toml" +version = "0.8.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1ed1f98e3fdc28d6d910e6737ae6ab1a93bf1985935a1193e68f93eeb68d24e" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ae48d6208a266e853d946088ed816055e556cc6028c5e8e2b84d9fa5dd7c7f5" +dependencies = [ + "indexmap", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "tower-service" version = "0.3.3" @@ -1946,6 +2231,12 @@ dependencies = [ "vsimd", ] +[[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" @@ -2050,6 +2341,15 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "windows-core" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "33ab640c8d7e35bf8ba19b884ba838ceb4fba93a4e8c65a9059d08afcfc683d9" +dependencies = [ + "windows-targets", +] + [[package]] name = "windows-registry" version = "0.2.0" @@ -2153,6 +2453,15 @@ version = "0.52.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" +[[package]] +name = "winnow" +version = "0.6.20" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b" +dependencies = [ + "memchr", +] + [[package]] name = "write16" version = "1.0.0" diff --git a/Cargo.toml b/Cargo.toml index 9903945662bcb65f04c8aea7674c90af73550450..cf870f4e77724e5bae811f4e5099ec1cddb0af03 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -11,6 +11,10 @@ repository = "https://gitee.com/iscas-system/apiserver" readme = "README.md" [dependencies] +r2d2 = "0.8.10" +dotenv = "0.15.0" +diesel = { version = "2.2.0", features = ["sqlite", "postgres", "r2d2"] } +diesel_migrations = "2.2.0" actix-http = "3.9.0" actix-service = "2.0.2" actix-web = "4.9.0" @@ -23,5 +27,6 @@ tokio = "1.40.0" k8s-openapi = { version = "0.23.0", features = ["latest"] } jsonschema = "0.23.0" schemars = "0.8.21" +chrono = "0.4.38" #log = "0.4" #env_logger = { version = "0.11" } diff --git a/diesel.toml b/diesel.toml new file mode 100644 index 0000000000000000000000000000000000000000..40400e18dab14832d5cbe000c1d7459d56763996 --- /dev/null +++ b/diesel.toml @@ -0,0 +1,9 @@ +# For documentation on how to configure this file, +# see https://diesel.rs/guides/configuring-diesel-cli + +[print_schema] +file = "src/schema.rs" +custom_type_derives = ["diesel::query_builder::QueryId", "Clone"] + +[migrations_directory] +dir = "/home/yuichi/apiserver/migrations" diff --git a/migrations/.keep b/migrations/.keep new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/migrations/2024-11-03-082237_create_metadata_table/down.sql b/migrations/2024-11-03-082237_create_metadata_table/down.sql new file mode 100644 index 0000000000000000000000000000000000000000..d3c389a350ff17bf1dea0735313f861f93e0ad99 --- /dev/null +++ b/migrations/2024-11-03-082237_create_metadata_table/down.sql @@ -0,0 +1,2 @@ +-- This file should undo anything in `up.sql` +DROP TABLE IF EXISTS metadata; \ No newline at end of file diff --git a/migrations/2024-11-03-082237_create_metadata_table/up.sql b/migrations/2024-11-03-082237_create_metadata_table/up.sql new file mode 100644 index 0000000000000000000000000000000000000000..e89b705e21d74c8b8a3e5c19095667a0f1cb5645 --- /dev/null +++ b/migrations/2024-11-03-082237_create_metadata_table/up.sql @@ -0,0 +1,10 @@ +-- Your SQL goes here +CREATE TABLE IF NOT EXISTS metadata ( + name VARCHAR(256), + namespace BOOLEAN, + apigroup VARCHAR(256), + data TEXT, + created_time VARCHAR(256), + updated_time VARCHAR(256), + PRIMARY KEY (name, namespace, apigroup) + ); \ No newline at end of file diff --git a/src/cores/apiserver.rs b/src/cores/apiserver.rs index d648ee1d0cf8f463b7879230c0ddecc22a33c7c2..ea18e79474bce00b58af0e6d01602b2b0088ac19 100644 --- a/src/cores/apiserver.rs +++ b/src/cores/apiserver.rs @@ -10,7 +10,8 @@ use actix_web::{HttpServer, App, web, HttpResponse}; use crate::cores::config::{Config, APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE}; use crate::cores::handlers::{Handler}; -use std::sync::Arc; +use std::sync::{Arc}; +use crate::cores::db::{DbPool}; pub struct ApiServer { @@ -29,7 +30,7 @@ impl ApiServer // TODO 未来加上else // TODO 优化注册流程 pub async fn start - (self: Arc, addr: &str, handler: T) -> Result<(), std::io::Error> { + (self: Arc, addr: &str, handler: T, db_pool: Arc) -> Result<(), std::io::Error> { let handler = Arc::new(handler); @@ -38,44 +39,93 @@ impl ApiServer let mut app = App::new(); let handler = Arc::clone(&handler); let config = Arc::clone(&self.config); + let db_pool = Arc::clone(&db_pool); // URL是手动注册的,不会存在APIS_WITHOUT_NAMESPACE, APIS_WITH_NAMESPACE, // API_WITHOUT_NAMESPACE, API_WITH_NAMESPACE之外情况 for (key, route) in config.create_routes() { if route == API_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::post().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().create_api_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .create_api_without_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == API_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::post().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().create_api_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .create_api_with_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::post().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().create_apis_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .create_apis_without_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::post().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().create_apis_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .create_apis_with_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } @@ -86,38 +136,86 @@ impl ApiServer for (key, route) in config.delete_routes() { if route == API_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::delete().to( - move |path, data| { + move |path| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().delete_api_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .delete_api_without_namespace(path, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == API_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::delete().to( - move |path, data| { + move |path| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().delete_api_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .delete_api_with_namespace(path, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::delete().to( - move |path, data| { + move |path| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().delete_apis_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .delete_apis_without_namespace(path, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::delete().to( - move |path, data| { + move |path| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().delete_apis_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .delete_apis_with_namespace(path, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } @@ -128,38 +226,86 @@ impl ApiServer for (key, route) in config.update_routes() { if route == API_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::put().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().update_api_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .update_api_without_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == API_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::put().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().update_api_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .update_api_with_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::put().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().update_apis_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .update_apis_without_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::put().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().update_apis_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .update_apis_with_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } @@ -170,38 +316,86 @@ impl ApiServer for (key, route) in config.getone_routes() { if route == API_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::get().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().getone_api_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .getone_api_without_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == API_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::get().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().getone_api_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .getone_api_with_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::get().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().getone_apis_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .getone_apis_without_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::get().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().getone_apis_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .getone_apis_with_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } @@ -213,38 +407,86 @@ impl ApiServer for (key, route) in config.listall_routes() { if route == API_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::get().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().listall_api_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .listall_api_without_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == API_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::get().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().listall_api_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .listall_api_with_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITHOUT_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::get().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().listall_apis_without_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .listall_apis_without_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } else if route == APIS_WITH_NAMESPACE { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); app = app.route(key, web::get().to( move |path, data| { let handler = Arc::clone(&handler); + let db_pool = Arc::clone(&db_pool); async move { - handler.default().listall_apis_with_namespace(path, data).await + match db_pool.get_connection() { + Ok(mut conn) => { + handler + .default() + .listall_apis_with_namespace(path, data, &mut conn) + .await + }, + Err(_) => Err(actix_web::error::ErrorInternalServerError( + "Failed to get database connection", + )), + } } })); } diff --git a/src/cores/db.rs b/src/cores/db.rs new file mode 100644 index 0000000000000000000000000000000000000000..1d706464e3ad1ee9076982943dba0edd839061f6 --- /dev/null +++ b/src/cores/db.rs @@ -0,0 +1,176 @@ +use std::collections::HashMap; +use chrono::Utc; +use diesel::pg::PgConnection; +use diesel::{sql_query, QueryResult, RunQueryDsl}; +use diesel::sqlite::SqliteConnection; +use diesel::r2d2::{ConnectionManager, Pool, PooledConnection}; +use diesel_migrations::{embed_migrations, EmbeddedMigrations, MigrationHarness}; +use serde_json::{json, Value}; + +pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!(); + +pub enum DbPool { + Pg(Pool>), + Sqlite(Pool>), +} + +pub enum DbConnection { + Pg(PooledConnection>), + Sqlite(PooledConnection>), +} + +// 定义所有资源模板的静态映射 +fn resource_templates() -> HashMap<&'static str, (bool, Value)> { + let mut templates = HashMap::new(); + templates.insert( + "cargos", + ( + true, + json!({ + "apiVersion": "v1", + "kind": "Cargo", + "metadata": { + "name": "string", + "annotations": "object" + }, + "spec": { + "containers": [ + { + "name": "string", + "image": "string", + "command": "array" + } + ] + } + }), + ), + ); + templates.insert( + "nodes", + ( + false, + json!({ + "apiVersion": "v1", + "kind": "Node", + "metadata": { + "name": "string", + "labels": "object" + }, + "spec": { + "podCIDR": "string", + "providerID": "string" + } + }), + ), + ); + templates.insert( + "jobs", + ( + true, + json!({ + "apiVersion": "batch/v1", + "kind": "Job", + "metadata": { + "name": "string", + "labels": "object" + }, + "spec": { + "template": { + "metadata": { + "labels": "object" + }, + "spec": { + "containers": [ + { + "name": "string", + "image": "string", + "args": "array" + } + ] + } + } + } + }), + ), + ); + // 其他资源可以继续添加 + templates +} + + +impl DbPool { + // 初始化所有资源的 metadata 和表结构 + fn initialize_metadata(conn: &mut DbConnection) -> QueryResult<()> { + let templates = resource_templates(); + + for (table_name, (supports_namespace, template)) in templates.iter() { + + let create_table_query = format!( + "CREATE TABLE IF NOT EXISTS {} ( + name VARCHAR(256), + namespace VARCHAR(256), + apigroup VARCHAR(256), + data TEXT, + created_time VARCHAR(256), + updated_time VARCHAR(256), + PRIMARY KEY (name, namespace, apigroup) + );", + table_name + ); + + // 从模板中提取 `apiVersion` 字段作为 `apigroup` + let apigroup = template.get("apiVersion") + .and_then(Value::as_str) + .unwrap_or("v1"); + + let insert_metadata_query = format!( + "INSERT OR IGNORE INTO metadata (name, namespace, apigroup, data, created_time, updated_time) + VALUES ('{}', {}, '{}', '{}', '{}', '{}');", + table_name, + *supports_namespace, + apigroup, + template.to_string(), + Utc::now().naive_utc().to_string(), + Utc::now().naive_utc().to_string() + ); + + match conn { + DbConnection::Pg(pg_conn) => { + sql_query(create_table_query).execute(pg_conn)?; + sql_query(insert_metadata_query).execute(pg_conn)?; + }, + DbConnection::Sqlite(sqlite_conn) => { + sql_query(create_table_query).execute(sqlite_conn)?; + sql_query(insert_metadata_query).execute(sqlite_conn)?; + } + } + } + Ok(()) + } + pub fn get_connection(&self) -> Result { + match self { + DbPool::Pg(pool) => pool.get().map(DbConnection::Pg), + DbPool::Sqlite(pool) => pool.get().map(DbConnection::Sqlite), + } + } + pub fn new(database_url: &str) -> Result { + if database_url.starts_with("postgres://") { + let manager = ConnectionManager::::new(database_url); + let pool = Pool::builder().build(manager)?; + // 运行迁移 + let mut conn = pool.get()?; + conn.run_pending_migrations(MIGRATIONS).unwrap(); + Self::initialize_metadata(&mut DbPool::Pg(pool.clone()).get_connection()?).unwrap(); + Ok(DbPool::Pg(pool)) + } else { + let manager = ConnectionManager::::new(database_url); + let pool = Pool::builder().build(manager)?; + // 运行迁移 + let mut conn = pool.get()?; + conn.run_pending_migrations(MIGRATIONS).unwrap(); + Self::initialize_metadata(&mut DbPool::Sqlite(pool.clone()).get_connection()?).unwrap(); + Ok(DbPool::Sqlite(pool)) + } + } +} + diff --git a/src/cores/handlers.rs b/src/cores/handlers.rs index 77d964cb6e8aa920c92d4e53ec374f3fbae37ae2..8eee2da4f080af141d2b4128e932ced3505770bc 100644 --- a/src/cores/handlers.rs +++ b/src/cores/handlers.rs @@ -7,70 +7,75 @@ use async_trait::async_trait; use actix_web::{HttpResponse, Result, web}; +use chrono::Utc; +use diesel::{QueryResult, RunQueryDsl, QueryDsl, ExpressionMethods, OptionalExtension, QueryableByName}; use serde_json::json; use serde_json::Value; +use crate::cores::db::DbConnection; +use diesel::sql_types::{Text}; +use actix_web::error::ErrorInternalServerError; #[async_trait] pub trait Handler { // /api/{version}/{plural} - async fn create_api_without_namespace(&self, info: web::Path<(String, String)>, data: web::Json) -> Result; + async fn create_api_without_namespace(&self, info: web::Path<(String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result; // /api/{version}/namespaces/{namespace}/{plural} - async fn create_api_with_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json) -> Result; + async fn create_api_with_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/{plural} - async fn create_apis_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json) -> Result; + async fn create_apis_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn create_apis_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result; + async fn create_apis_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result; // /api/{version}/{plural}/{name} - async fn delete_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json) -> Result; + async fn delete_api_without_namespace(&self, info: web::Path<(String, String, String)>, db_connection: &mut DbConnection) -> Result; // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn delete_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result; + async fn delete_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/{plural}/{name} - async fn delete_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result; + async fn delete_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn delete_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Json) -> Result; + async fn delete_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, db_connection: &mut DbConnection) -> Result; // /api/{version}/{plural}/{name} - async fn update_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json) -> Result; + async fn update_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result; // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn update_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result; + async fn update_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/{plural}/{name} - async fn update_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result; + async fn update_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn update_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Json) -> Result; + async fn update_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result; // /api/{version}/{plural}/{name} - async fn getone_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query) -> Result; + async fn getone_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query, db_connection: &mut DbConnection) -> Result; // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn getone_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query) -> Result; + async fn getone_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/{plural}/{name} - async fn getone_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query) -> Result; + async fn getone_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn getone_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Query) -> Result; + async fn getone_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Query, db_connection: &mut DbConnection) -> Result; // /api/{version}/{plural} - async fn listall_api_without_namespace(&self, info: web::Path<(String, String)>, data: web::Query) -> Result; + async fn listall_api_without_namespace(&self, info: web::Path<(String, String)>, data: web::Query, db_connection: &mut DbConnection) -> Result; // /api/{version}/namespaces/{namespace}/{plural} - async fn listall_api_with_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query) -> Result; + async fn listall_api_with_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/{plural} - async fn listall_apis_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query) -> Result; + async fn listall_apis_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query, db_connection: &mut DbConnection) -> Result; // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn listall_apis_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query) -> Result; + async fn listall_apis_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query, db_connection: &mut DbConnection) -> Result; // 不满足以上请求路径的处理 fn default(&self) -> DefaultHandler; @@ -87,147 +92,977 @@ impl DefaultHandler { } } + + +// 查询 metadata 表中的 plural 是否存在,并检查 namespace 要求是否满足 +async fn check_metadata( + conn: &mut DbConnection, + plural: &str, + version: &str, + requires_namespace: bool, +) -> QueryResult { + use diesel::dsl::count_star; + use crate::schema::metadata::dsl::*; + + let count; + match conn { + DbConnection::Pg(pg_conn) => { + count = metadata + .filter(name.eq(plural)) + .filter(apigroup.eq(version)) + .filter(namespace.eq(requires_namespace)) + .select(count_star()) + .first::(pg_conn)?; + }, + DbConnection::Sqlite(sqlite_conn) => { + count = metadata + .filter(name.eq(plural)) + .filter(apigroup.eq(version)) + .filter(namespace.eq(requires_namespace)) + .select(count_star()) + .first::(sqlite_conn)?; + } + } + Ok(count > 0) +} + + +// 创建新的数据表 +async fn create_table_if_not_exists(conn: &mut DbConnection, table_name: &str) -> QueryResult<()> { + + let create_query = format!( + "CREATE TABLE IF NOT EXISTS {} ( + name VARCHAR(256), + namespace VARCHAR(256), + apigroup VARCHAR(256), + data TEXT, + created_time VARCHAR(256), + updated_time VARCHAR(256), + PRIMARY KEY (name, namespace, apigroup) + )", + table_name + ); + + match conn { + DbConnection::Pg(pg_conn) => { + diesel::sql_query(create_query).execute(pg_conn)?; + }, + DbConnection::Sqlite(sqlite_conn) => { + diesel::sql_query(create_query).execute(sqlite_conn)?; + } + } + + Ok(()) +} + +// 在 metadata 表中插入新记录 +async fn insert_metadata( + conn: &mut DbConnection, + plural: &str, + version: &str, + namespace_required: bool, + json_data: &Value +) -> QueryResult<()> { + use crate::schema::metadata::dsl::*; + match conn { + DbConnection::Pg(pg_conn) => { + diesel::insert_into(metadata) + .values(( + name.eq(plural), + apigroup.eq(version), + namespace.eq(namespace_required), + data.eq(json_data.to_string()), + created_time.eq(Utc::now().naive_utc().to_string()), + updated_time.eq(Utc::now().naive_utc().to_string()), + )) + .execute(pg_conn)?; + }, + DbConnection::Sqlite(sqlite_conn) => { + diesel::insert_into(metadata) + .values(( + name.eq(plural), + apigroup.eq(version), + namespace.eq(namespace_required), + data.eq(json_data.to_string()), + created_time.eq(Utc::now().naive_utc().to_string()), + updated_time.eq(Utc::now().naive_utc().to_string()), + )) + .execute(sqlite_conn)?; + } + } + Ok(()) +} + + + +// 插入数据到指定的表 +async fn insert_into_table( + conn: &mut DbConnection, + table_name: &str, + json_data: &Value, + version: &str, + namespace: Option<&str>, +) -> QueryResult<()> { + match conn { + DbConnection::Pg(pg_conn) => { + // 从 json_data 中提取 metadata.name + let name = json_data + .get("metadata") + .and_then(|metadata| metadata.get("name")) + .and_then(|name| name.as_str()) + .unwrap_or("error"); + + let insert_query = format!( + "INSERT INTO {} (name, namespace, apigroup, data, created_time, updated_time) VALUES ($1, $2, $3, $4, $5, $6)", + table_name + ); + diesel::sql_query(insert_query) + .bind::(name) + .bind::(namespace.unwrap_or("")) + .bind::(version) + .bind::(json_data.to_string()) // 将 JSON 数据存储为 TEXT + .bind::(Utc::now().naive_utc().to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .execute(pg_conn)?; + }, + DbConnection::Sqlite(sqlite_conn) => { + // 从 json_data 中提取 metadata.name + let name = json_data + .get("metadata") + .and_then(|metadata| metadata.get("name")) + .and_then(|name| name.as_str()) + .unwrap_or("error"); + + let insert_query = format!( + "INSERT INTO {} (name, namespace, apigroup, data, created_time, updated_time) VALUES (?, ?, ?, ?, ?, ?)", + table_name + ); + diesel::sql_query(insert_query) + .bind::(name) + .bind::(namespace.unwrap_or("")) + .bind::(version) + .bind::(json_data.to_string()) // 将 JSON 数据存储为 TEXT + .bind::(Utc::now().naive_utc().to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .execute(sqlite_conn)?; + } + } + Ok(()) +} + +// 从 plural 表中删除特定 name 的记录 +async fn delete_from_table( + conn: &mut DbConnection, + table_name: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult { + // 根据是否存在 namespace 动态构建删除查询 + let delete_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!("DELETE FROM {} WHERE name = $1 AND namespace = $2 AND apigroup = $3", table_name), + DbConnection::Sqlite(_) => format!("DELETE FROM {} WHERE name = ? AND namespace = ? AND apigroup = ?", table_name), + } + } else { + match conn { + DbConnection::Pg(_) => format!("DELETE FROM {} WHERE name = $1 AND apigroup = $2", table_name), + DbConnection::Sqlite(_) => format!("DELETE FROM {} WHERE name = ? AND apigroup = ?", table_name), + } + }; + + // 执行删除操作 + let rows_affected = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(delete_query) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(pg_conn)? + } else { + diesel::sql_query(delete_query) + .bind::(item_name) + .bind::(item_version) + .execute(pg_conn)? + } + }, + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(delete_query) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(sqlite_conn)? + } else { + diesel::sql_query(delete_query) + .bind::(item_name) + .bind::(item_version) + .execute(sqlite_conn)? + } + } + }; + Ok(rows_affected > 0) +} + + +async fn update_data_in_table( + conn: &mut DbConnection, + table_name: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, + json_data: &Value, +) -> QueryResult { + + let update_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!("UPDATE {} SET data = $1, updated_time = $2 WHERE name = $3 AND namespace = $4 AND apigroup = $5", table_name), + DbConnection::Sqlite(_) => format!("UPDATE {} SET data = ?, updated_time = ? WHERE name = ? AND namespace = ? AND apigroup = ?", table_name), + } + } else { + match conn { + DbConnection::Pg(_) => format!("UPDATE {} SET data = $1, updated_time = $2 WHERE name = $3 AND apigroup = $4", table_name), + DbConnection::Sqlite(_) => format!("UPDATE {} SET data = ?, updated_time = ? WHERE name = ? AND apigroup = ?", table_name), + } + }; + + let rows_affected = match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(pg_conn)? + } else { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_name) + .bind::(item_version) + .execute(pg_conn)? + } + }, + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .execute(sqlite_conn)? + } else { + diesel::sql_query(update_query) + .bind::(json_data.to_string()) + .bind::(Utc::now().naive_utc().to_string()) + .bind::(item_name) + .bind::(item_version) + .execute(sqlite_conn)? + } + } + }; + + Ok(rows_affected > 0) +} + +// 辅助查询函数,用于获取数据的 `data` 字段 +#[derive(QueryableByName)] +struct DataResult { + #[sql_type = "Text"] + data: String, +} + +async fn get_data_from_table( + conn: &mut DbConnection, + table_name: &str, + item_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult> { + let select_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!("SELECT data FROM {} WHERE name = $1 AND namespace = $2 AND apigroup = $3", table_name), + DbConnection::Sqlite(_) => format!("SELECT data FROM {} WHERE name = ? AND namespace = ? AND apigroup = ?", table_name), + } + } else { + match conn { + DbConnection::Pg(_) => format!("SELECT data FROM {} WHERE name = $1 AND apigroup = $2", table_name), + DbConnection::Sqlite(_) => format!("SELECT data FROM {} WHERE name = ? AND apigroup = ?", table_name), + } + }; + + match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(pg_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } else { + diesel::sql_query(select_query) + .bind::(item_name) + .bind::(item_version) + .get_result::(pg_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } + }, + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(item_name) + .bind::(namespace) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } else { + diesel::sql_query(select_query) + .bind::(item_name) + .bind::(item_version) + .get_result::(sqlite_conn) + .optional() + .map(|res| res.map(|data_result| data_result.data)) + } + } + } +} + + +// 辅助函数:从指定表中获取所有符合条件的数据的 `data` 字段 +async fn get_all_data_from_table( + conn: &mut DbConnection, + table_name: &str, + item_version: &str, + item_namespace: Option<&str>, +) -> QueryResult> { + let select_query = if let Some(_) = item_namespace { + match conn { + DbConnection::Pg(_) => format!("SELECT data FROM {} WHERE namespace = $1 AND apigroup = $2", table_name), + DbConnection::Sqlite(_) => format!("SELECT data FROM {} WHERE namespace = ? AND apigroup = ?", table_name), + } + } else { + match conn { + DbConnection::Pg(_) => format!("SELECT data FROM {} WHERE apigroup = $1", table_name), + DbConnection::Sqlite(_) => format!("SELECT data FROM {} WHERE apigroup = ?", table_name), + } + }; + + match conn { + DbConnection::Pg(pg_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(namespace) + .bind::(item_version) + .load::(pg_conn) + .map(|results| results.into_iter().map(|res| res.data).collect()) + } else { + diesel::sql_query(select_query) + .bind::(item_version) + .load::(pg_conn) + .map(|results| results.into_iter().map(|res| res.data).collect()) + } + }, + DbConnection::Sqlite(sqlite_conn) => { + if let Some(namespace) = item_namespace { + diesel::sql_query(select_query) + .bind::(namespace) + .bind::(item_version) + .load::(sqlite_conn) + .map(|results| results.into_iter().map(|res| res.data).collect()) + } else { + diesel::sql_query(select_query) + .bind::(item_version) + .load::(sqlite_conn) + .map(|results| results.into_iter().map(|res| res.data).collect()) + } + } + } +} + + #[async_trait] impl Handler for DefaultHandler { // /api/{version}/{plural} - async fn create_api_without_namespace(&self, info: web::Path<(String, String)>, data: web::Json) -> Result { + async fn create_api_without_namespace( + &self, + info: web::Path<(String, String)>, + data: web::Json, + db_connection: &mut DbConnection, + ) -> Result { let (version, plural) = info.into_inner(); - json!({"message": format!("Hello, {}, {}!", version, plural)}); - Ok(HttpResponse::Ok().json(data)) + + //判断是正常的插入函数还是 crd 资源的注册函数 + if plural == "crds" { + let table_name = data + .get("spec") + .and_then(|spec| spec.get("names")) + .and_then(|names| names.get("plural")) + .and_then(|plural| plural.as_str()) + .unwrap_or("error"); + + // 调用check_metadata函数 + let metadata_exists = check_metadata(db_connection, table_name, &version,false).await.map_err(ErrorInternalServerError)?; + + if metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); + } + + + create_table_if_not_exists(db_connection, table_name) + .await + .map_err(ErrorInternalServerError)?; + + insert_metadata(db_connection, table_name, &version, false, &data) + .await + .map_err(ErrorInternalServerError)?; + } else { + // 调用check_metadata函数 + let metadata_exists = check_metadata(db_connection, &plural, &version,false).await.map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" }))); + } + + insert_into_table(db_connection, &plural, &data, &version, None) + .await + .map_err(ErrorInternalServerError)?; + } + + Ok(HttpResponse::Ok().json(data.into_inner())) } // /api/{version}/namespaces/{namespace}/{plural} - async fn create_api_with_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json) -> Result { + async fn create_api_with_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result { let (version, namespace, plural) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}!", version, namespace, plural)}); - Ok(HttpResponse::Ok().json(data)) + + //判断是正常的插入函数还是 crd 资源的注册函数 + if plural == "crds" { + let table_name = data + .get("spec") + .and_then(|spec| spec.get("names")) + .and_then(|names| names.get("plural")) + .and_then(|plural| plural.as_str()) + .unwrap_or("error"); + + // 调用check_metadata函数 + let metadata_exists = check_metadata(db_connection, table_name, &version, true).await.map_err(ErrorInternalServerError)?; + + if metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); + } + + + create_table_if_not_exists(db_connection, table_name) + .await + .map_err(ErrorInternalServerError)?; + + insert_metadata(db_connection, table_name, &version, false, &data) + .await + .map_err(ErrorInternalServerError)?; + } else { + // 调用check_metadata函数 + let metadata_exists = check_metadata(db_connection, &plural, &version, true).await.map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" }))); + } + + insert_into_table(db_connection, &plural, &data, &version, Some(&namespace)) + .await + .map_err(ErrorInternalServerError)?; + } + + Ok(HttpResponse::Ok().json(data.into_inner())) } // /apis/{group}/{version}/{plural} - async fn create_apis_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json) -> Result { + async fn create_apis_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result { let (group, version, plural) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}!", group, version, plural)}); - Ok(HttpResponse::Ok().json(data)) + let ver = group + "/" + &*version; + + + //判断是正常的插入函数还是 crd 资源的注册函数 + if plural == "crds" { + let table_name = data + .get("spec") + .and_then(|spec| spec.get("names")) + .and_then(|names| names.get("plural")) + .and_then(|plural| plural.as_str()) + .unwrap_or("error"); + + // 调用check_metadata函数 + let metadata_exists = check_metadata(db_connection, table_name, &ver, false).await.map_err(ErrorInternalServerError)?; + + if metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); + } + + + create_table_if_not_exists(db_connection, table_name) + .await + .map_err(ErrorInternalServerError)?; + + insert_metadata(db_connection, table_name, &version, false, &data) + .await + .map_err(ErrorInternalServerError)?; + } else { + // 调用check_metadata函数 + let metadata_exists = check_metadata(db_connection, &plural, &ver, false).await.map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" }))); + } + + insert_into_table(db_connection, &plural, &data, &ver, None) + .await + .map_err(ErrorInternalServerError)?; + } + + Ok(HttpResponse::Ok().json(data.into_inner())) } // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn create_apis_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result { + async fn create_apis_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result { let (group, version, namespace, plural) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}!", group, version, namespace, plural)}); - Ok(HttpResponse::Ok().json(data)) + + let ver = group + "/" + &*version; + + + //判断是正常的插入函数还是 crd 资源的注册函数 + if plural == "crds" { + let table_name = data + .get("spec") + .and_then(|spec| spec.get("names")) + .and_then(|names| names.get("plural")) + .and_then(|plural| plural.as_str()) + .unwrap_or("error"); + + // 调用 check_metadata 函数 + let metadata_exists = check_metadata(db_connection, table_name, &ver, true).await.map_err(ErrorInternalServerError)?; + + if metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 crd 资源已存在,无需重复注册" }))); + } + + + create_table_if_not_exists(db_connection, table_name) + .await + .map_err(ErrorInternalServerError)?; + + insert_metadata(db_connection, table_name, &version, false, &data) + .await + .map_err(ErrorInternalServerError)?; + } else { + // 调用 check_metadata 函数 + let metadata_exists = check_metadata(db_connection, &plural, &ver, true).await.map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请重新注册或检查 plural 版本以及是否需要 namespace" }))); + } + + insert_into_table(db_connection, &plural, &data, &ver, Some(&namespace)) + .await + .map_err(ErrorInternalServerError)?; + } + + Ok(HttpResponse::Ok().json(data.into_inner())) } // /api/{version}/{plural}/{name} - async fn delete_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json) -> Result { + async fn delete_api_without_namespace(&self, info: web::Path<(String, String, String)>, db_connection: &mut DbConnection) -> Result { let (version, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}!", version, plural, name)}); - Ok(HttpResponse::Ok().json(data)) + + // 检查 metadata 中是否存在该 plural 且不需要 namespace + let metadata_exists = check_metadata(db_connection, &plural, &version, false) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + // 从 plural 表中删除指定的 name + let deleted = delete_from_table(db_connection, &plural, &name, &version, None) + .await + .map_err(ErrorInternalServerError)?; + + if !deleted { + return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); + } + + Ok(HttpResponse::Ok().json(json!({ "status": "delete_api_without_namespace success" }))) } // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn delete_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result { + async fn delete_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, db_connection: &mut DbConnection) -> Result { let (version, namespace, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}!", version, namespace, plural, name)}); - Ok(HttpResponse::Ok().json(data)) + // 检查 metadata 中是否存在该 plural 且需要 namespace + let metadata_exists = check_metadata(db_connection, &plural, &version, true) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + // 从 plural 表中删除指定的数据 + let deleted = delete_from_table(db_connection, &plural, &name, &version, Some(&namespace)) + .await + .map_err(ErrorInternalServerError)?; + + if !deleted { + return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); + } + + Ok(HttpResponse::Ok().json(json!({ "status": "delete_api_with_namespace success" }))) } // /apis/{group}/{version}/{plural}/{name} - async fn delete_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result { + async fn delete_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, db_connection: &mut DbConnection) -> Result { let (group, version, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}!", group, version, plural, name)}); - Ok(HttpResponse::Ok().json(data)) + let ver = format!("{}/{}", group, version); + + let metadata_exists = check_metadata(db_connection, &plural, &ver, false) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let deleted = delete_from_table(db_connection, &plural, &name, &ver, None) + .await + .map_err(ErrorInternalServerError)?; + + if !deleted { + return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); + } + + Ok(HttpResponse::Ok().json(json!({ "status": "delete_apis_without_namespace success" }))) } // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn delete_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Json) -> Result { + async fn delete_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, db_connection: &mut DbConnection) -> Result { let (group, version, namespace, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}, {}!", group, version, namespace, plural, name)}); - Ok(HttpResponse::Ok().json(data)) + let ver = format!("{}/{}", group, version); + + let metadata_exists = check_metadata(db_connection, &plural, &ver, true) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let deleted = delete_from_table(db_connection, &plural, &name, &ver, Some(&namespace)) + .await + .map_err(ErrorInternalServerError)?; + + if !deleted { + return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); + } + + Ok(HttpResponse::Ok().json(json!({ "status": "delete_apis_with_namespace success" }))) } // /api/{version}/{plural}/{name} - async fn update_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json) -> Result { + async fn update_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result { let (version, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}!", version, plural, name)}); - Ok(HttpResponse::Ok().json(data)) + + let metadata_exists = check_metadata(db_connection, &plural, &version, false) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + // 查询 plural 表中是否存在 name 匹配的数据 + let updated = update_data_in_table(db_connection, &plural, &name, &version, None, &data) + .await + .map_err(ErrorInternalServerError)?; + + if !updated { + return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); + } + + Ok(HttpResponse::Ok().json(data.into_inner())) } // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn update_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result { + async fn update_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result { let (version, namespace, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}!", version, namespace, plural, name)}); - Ok(HttpResponse::Ok().json(data)) + // 检查 metadata 中是否存在 plural 且要求 namespace + let metadata_exists = check_metadata(db_connection, &plural, &version, true) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let updated = update_data_in_table(db_connection, &plural, &name, &version, Some(&namespace), &data) + .await + .map_err(ErrorInternalServerError)?; + + if !updated { + return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); + } + + Ok(HttpResponse::Ok().json(data.into_inner())) } // /apis/{group}/{version}/{plural}/{name} - async fn update_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json) -> Result { + async fn update_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result { let (group, version, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}!", group, version, plural, name)}); - Ok(HttpResponse::Ok().json(data)) + let ver = format!("{}/{}", group, version); + + let metadata_exists = check_metadata(db_connection, &plural, &ver, false) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let updated = update_data_in_table(db_connection, &plural, &name, &ver, None, &data) + .await + .map_err(ErrorInternalServerError)?; + + if !updated { + return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); + } + + Ok(HttpResponse::Ok().json(data.into_inner())) } // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn update_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Json) -> Result { + async fn update_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Json, db_connection: &mut DbConnection) -> Result { let (group, version, namespace, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}, {}!", group, version, namespace, plural, name)}); - Ok(HttpResponse::Ok().json(data)) + let ver = format!("{}/{}", group, version); + + let metadata_exists = check_metadata(db_connection, &plural, &ver, true) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let updated = update_data_in_table(db_connection, &plural, &name, &ver, Some(&namespace), &data) + .await + .map_err(ErrorInternalServerError)?; + + if !updated { + return Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))); + } + + Ok(HttpResponse::Ok().json(data.into_inner())) } // /api/{version}/{plural}/{name} - async fn getone_api_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query) -> Result { + async fn getone_api_without_namespace(&self, info: web::Path<(String, String, String)>, _data: web::Query, db_connection: &mut DbConnection) -> Result { let (version, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}!", version, plural, name)}); - Ok(HttpResponse::Ok().json(data.into_inner())) + let metadata_exists = check_metadata(db_connection, &plural, &version, false) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + if let Some(data) = get_data_from_table(db_connection, &plural, &name, &version, None) + .await + .map_err(ErrorInternalServerError)? { + // 将字符串转换为 JSON 格式 + match serde_json::from_str::(&data) { + Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), // 成功解析为 JSON,返回 JSON 响应 + Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ "error": "数据格式错误,无法解析为 JSON" }))), + } + } + Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))) } // /api/{version}/namespaces/{namespace}/{plural}/{name} - async fn getone_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query) -> Result { + async fn getone_api_with_namespace(&self, info: web::Path<(String, String, String, String)>, _data: web::Query, db_connection: &mut DbConnection) -> Result { let (version, namespace, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}!", version, namespace, plural, name)}); - Ok(HttpResponse::Ok().json(data.into_inner())) + let metadata_exists = check_metadata(db_connection, &plural, &version, true) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + if let Some(data) = get_data_from_table(db_connection, &plural, &name, &version, Some(&namespace)) + .await + .map_err(ErrorInternalServerError)? { + // 将字符串转换为 JSON 格式 + match serde_json::from_str::(&data) { + Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), // 成功解析为 JSON,返回 JSON 响应 + Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ "error": "数据格式错误,无法解析为 JSON" }))), + } + } + Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))) } // /apis/{group}/{version}/{plural}/{name} - async fn getone_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query) -> Result { + async fn getone_apis_without_namespace(&self, info: web::Path<(String, String, String, String)>, _data: web::Query, db_connection: &mut DbConnection) -> Result { let (group, version, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}!", group, version, plural, name)}); - Ok(HttpResponse::Ok().json(data.into_inner())) + let ver = format!("{}/{}", group, version); + + let metadata_exists = check_metadata(db_connection, &plural, &ver, false) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + if let Some(data) = get_data_from_table(db_connection, &plural, &name, &ver, None) + .await + .map_err(ErrorInternalServerError)? { + // 将字符串转换为 JSON 格式 + match serde_json::from_str::(&data) { + Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), // 成功解析为 JSON,返回 JSON 响应 + Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ "error": "数据格式错误,无法解析为 JSON" }))), + } + } + Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))) } // /apis/{group}/{version}/namespaces/{namespace}/{plural}/{name} - async fn getone_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, data: web::Query) -> Result { + async fn getone_apis_with_namespace(&self, info: web::Path<(String, String, String, String, String)>, _data: web::Query, db_connection: &mut DbConnection) -> Result { let (group, version, namespace, plural, name) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}, {}!", group, version, namespace, plural, name)}); - Ok(HttpResponse::Ok().json(data.into_inner())) + let ver = format!("{}/{}", group, version); + + let metadata_exists = check_metadata(db_connection, &plural, &ver, true) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + if let Some(data) = get_data_from_table(db_connection, &plural, &name, &ver, Some(&namespace)) + .await + .map_err(ErrorInternalServerError)? { + // 将字符串转换为 JSON 格式 + match serde_json::from_str::(&data) { + Ok(json_data) => return Ok(HttpResponse::Ok().json(json_data)), // 成功解析为 JSON,返回 JSON 响应 + Err(_) => return Ok(HttpResponse::InternalServerError().json(json!({ "error": "数据格式错误,无法解析为 JSON" }))), + } + } + Ok(HttpResponse::NotFound().json(json!({ "error": "指定数据不存在" }))) } // /api/{version}/{plural} - async fn listall_api_without_namespace(&self, info: web::Path<(String, String)>, data: web::Query) -> Result { + async fn listall_api_without_namespace(&self, info: web::Path<(String, String)>, _data: web::Query, db_connection: &mut DbConnection) -> Result { let (version, plural) = info.into_inner(); - json!({"message": format!("Hello, {}, {}!", version, plural)}); - Ok(HttpResponse::Ok().json(data.into_inner())) + let metadata_exists = check_metadata(db_connection, &plural, &version, false) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let data_list = get_all_data_from_table(db_connection, &plural, &version, None) + .await + .map_err(ErrorInternalServerError)?; + + // 将所有字符串解析为 JSON 格式并收集到一个数组中 + let json_array: Vec = data_list + .into_iter() + .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 + .collect(); + + Ok(HttpResponse::Ok().json(json_array)) } // /api/{version}/namespaces/{namespace}/{plural} - async fn listall_api_with_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query) -> Result { + async fn listall_api_with_namespace(&self, info: web::Path<(String, String, String)>, _data: web::Query, db_connection: &mut DbConnection) -> Result { let (version, namespace, plural) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}!", version, namespace, plural)}); - Ok(HttpResponse::Ok().json(data.into_inner())) + let metadata_exists = check_metadata(db_connection, &plural, &version, true) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let data_list = get_all_data_from_table(db_connection, &plural, &version, Some(&namespace)) + .await + .map_err(ErrorInternalServerError)?; + + // 将所有字符串解析为 JSON 格式并收集到一个数组中 + let json_array: Vec = data_list + .into_iter() + .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 + .collect(); + + Ok(HttpResponse::Ok().json(json_array)) } // /apis/{group}/{version}/{plural} - async fn listall_apis_without_namespace(&self, info: web::Path<(String, String, String)>, data: web::Query) -> Result { + async fn listall_apis_without_namespace(&self, info: web::Path<(String, String, String)>, _data: web::Query, db_connection: &mut DbConnection) -> Result { let (group, version, plural) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}!", group, version, plural)}); - Ok(HttpResponse::Ok().json(data.into_inner())) + let ver = format!("{}/{}", group, version); + + let metadata_exists = check_metadata(db_connection, &plural, &ver, false) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let data_list = get_all_data_from_table(db_connection, &plural, &ver, None) + .await + .map_err(ErrorInternalServerError)?; + + // 将所有字符串解析为 JSON 格式并收集到一个数组中 + let json_array: Vec = data_list + .into_iter() + .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 + .collect(); + + Ok(HttpResponse::Ok().json(json_array)) } // /apis/{group}/{version}/namespaces/{namespace}/{plural} - async fn listall_apis_with_namespace(&self, info: web::Path<(String, String, String, String)>, data: web::Query) -> Result { + async fn listall_apis_with_namespace(&self, info: web::Path<(String, String, String, String)>, _data: web::Query, db_connection: &mut DbConnection) -> Result { let (group, version, namespace, plural) = info.into_inner(); - json!({"message": format!("Hello, {}, {}, {}, {}!", group, version, namespace, plural)}); - Ok(HttpResponse::Ok().json(data.into_inner())) + let ver = format!("{}/{}", group, version); + + let metadata_exists = check_metadata(db_connection, &plural, &ver, true) + .await + .map_err(ErrorInternalServerError)?; + + if !metadata_exists { + return Ok(HttpResponse::NotFound().json(json!({ "error": "该 plural 不存在,请检查 plural 版本以及是否需要 namespace" }))); + } + + let data_list = get_all_data_from_table(db_connection, &plural, &ver, Some(&namespace)) + .await + .map_err(ErrorInternalServerError)?; + + // 将所有字符串解析为 JSON 格式并收集到一个数组中 + let json_array: Vec = data_list + .into_iter() + .filter_map(|data_str| serde_json::from_str(&data_str).ok()) // 解析成功的数据 + .collect(); + + Ok(HttpResponse::Ok().json(json_array)) } fn default(&self) -> DefaultHandler { diff --git a/src/cores/mod.rs b/src/cores/mod.rs index 14fe29f5c8602d9c82411dd168cf1bbb4a40fb51..a34795b8c2f5dec2be64c9063eade14e5abae24e 100644 --- a/src/cores/mod.rs +++ b/src/cores/mod.rs @@ -7,4 +7,5 @@ pub mod apiserver; pub mod config; pub mod handlers; -pub mod checker; \ No newline at end of file +pub mod checker; +pub mod db; \ No newline at end of file diff --git a/src/lib.rs b/src/lib.rs index c8b0996c6a6bc19b6c311cdb3452bdba1b3d2f82..31a02fdc8181749881dc6ee2f879a3aab8e5d621 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -22,4 +22,5 @@ //! } //! ``` -pub mod cores; \ No newline at end of file +pub mod cores; +pub mod schema; \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index a819e559960b09589b1a9b227b352d54a605f2a0..b35adf04911210ccb4f78c2b3eeee5680309b679 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,23 +9,33 @@ use k8s_apiserver::cores::apiserver::ApiServer; use k8s_apiserver::cores::config::DefaultConfig; use k8s_apiserver::cores::handlers::DefaultHandler; -use std::sync::Arc; +use std::sync::{Arc}; use env_logger; +use dotenv::dotenv; +use std::env; +use k8s_apiserver::cores::db::DbPool; #[actix_web::main] async fn main() { + dotenv().ok(); + + let database_url = env::var("DATABASE_URL").expect("DATABASE_URL must be set"); + // 创建连接池 + let db_pool = Arc::new(DbPool::new(&database_url).expect("Failed to create database pool")); + env_logger::init(); - start().await; + start(db_pool).await; + } -async fn start() { +async fn start(db_pool: Arc) { let config = Box::new(DefaultConfig::new()); let server = ApiServer::new(config); let handler: DefaultHandler = DefaultHandler::new(); - let _ = Arc::new(server).start("0.0.0.0:8080", handler).await; + let _ = Arc::new(server).start("0.0.0.0:8080", handler, db_pool).await; } // fn validate() { diff --git a/src/schema.rs b/src/schema.rs new file mode 100644 index 0000000000000000000000000000000000000000..29ca97579cb8a67a0f84ece32e1f69e480f97442 --- /dev/null +++ b/src/schema.rs @@ -0,0 +1,12 @@ +// @generated automatically by Diesel CLI. + +diesel::table! { + metadata (name) { + name -> Nullable, + namespace -> Nullable, + apigroup -> Nullable, + data -> Nullable, + created_time -> Nullable, + updated_time -> Nullable, + } +}