diff --git a/common/proto/table_definition.proto b/common/proto/table_definition.proto
index 9d3fc8c..01ef71c 100644
--- a/common/proto/table_definition.proto
+++ b/common/proto/table_definition.proto
@@ -7,6 +7,7 @@ import "common.proto";
service TableDefinition {
rpc PostTableDefinition (PostTableDefinitionRequest) returns (TableDefinitionResponse);
rpc GetProfileTree (multieko2.common.Empty) returns (ProfileTreeResponse);
+ rpc DeleteTable (DeleteTableRequest) returns (DeleteTableResponse);
}
message PostTableDefinitionRequest {
@@ -40,3 +41,13 @@ message ProfileTreeResponse {
repeated Profile profiles = 1;
}
+
+message DeleteTableRequest {
+ string profile_name = 1;
+ string table_name = 2;
+}
+
+message DeleteTableResponse {
+ bool success = 1;
+ string message = 2;
+}
diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin
index ba8766b..59e840c 100644
Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ
diff --git a/common/src/proto/multieko2.table_definition.rs b/common/src/proto/multieko2.table_definition.rs
index 30d6c05..bd5870d 100644
--- a/common/src/proto/multieko2.table_definition.rs
+++ b/common/src/proto/multieko2.table_definition.rs
@@ -48,6 +48,20 @@ pub mod profile_tree_response {
pub tables: ::prost::alloc::vec::Vec
,
}
}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct DeleteTableRequest {
+ #[prost(string, tag = "1")]
+ pub profile_name: ::prost::alloc::string::String,
+ #[prost(string, tag = "2")]
+ pub table_name: ::prost::alloc::string::String,
+}
+#[derive(Clone, PartialEq, ::prost::Message)]
+pub struct DeleteTableResponse {
+ #[prost(bool, tag = "1")]
+ pub success: bool,
+ #[prost(string, tag = "2")]
+ pub message: ::prost::alloc::string::String,
+}
/// Generated client implementations.
pub mod table_definition_client {
#![allow(
@@ -197,6 +211,35 @@ pub mod table_definition_client {
);
self.inner.unary(req, path, codec).await
}
+ pub async fn delete_table(
+ &mut self,
+ request: impl tonic::IntoRequest,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ > {
+ self.inner
+ .ready()
+ .await
+ .map_err(|e| {
+ tonic::Status::unknown(
+ format!("Service was not ready: {}", e.into()),
+ )
+ })?;
+ let codec = tonic::codec::ProstCodec::default();
+ let path = http::uri::PathAndQuery::from_static(
+ "/multieko2.table_definition.TableDefinition/DeleteTable",
+ );
+ let mut req = request.into_request();
+ req.extensions_mut()
+ .insert(
+ GrpcMethod::new(
+ "multieko2.table_definition.TableDefinition",
+ "DeleteTable",
+ ),
+ );
+ self.inner.unary(req, path, codec).await
+ }
}
}
/// Generated server implementations.
@@ -226,6 +269,13 @@ pub mod table_definition_server {
tonic::Response,
tonic::Status,
>;
+ async fn delete_table(
+ &self,
+ request: tonic::Request,
+ ) -> std::result::Result<
+ tonic::Response,
+ tonic::Status,
+ >;
}
#[derive(Debug)]
pub struct TableDefinitionServer {
@@ -398,6 +448,51 @@ pub mod table_definition_server {
};
Box::pin(fut)
}
+ "/multieko2.table_definition.TableDefinition/DeleteTable" => {
+ #[allow(non_camel_case_types)]
+ struct DeleteTableSvc(pub Arc);
+ impl<
+ T: TableDefinition,
+ > tonic::server::UnaryService
+ for DeleteTableSvc {
+ type Response = super::DeleteTableResponse;
+ type Future = BoxFuture<
+ tonic::Response,
+ tonic::Status,
+ >;
+ fn call(
+ &mut self,
+ request: tonic::Request,
+ ) -> Self::Future {
+ let inner = Arc::clone(&self.0);
+ let fut = async move {
+ ::delete_table(&inner, request).await
+ };
+ Box::pin(fut)
+ }
+ }
+ let accept_compression_encodings = self.accept_compression_encodings;
+ let send_compression_encodings = self.send_compression_encodings;
+ let max_decoding_message_size = self.max_decoding_message_size;
+ let max_encoding_message_size = self.max_encoding_message_size;
+ let inner = self.inner.clone();
+ let fut = async move {
+ let method = DeleteTableSvc(inner);
+ let codec = tonic::codec::ProstCodec::default();
+ let mut grpc = tonic::server::Grpc::new(codec)
+ .apply_compression_config(
+ accept_compression_encodings,
+ send_compression_encodings,
+ )
+ .apply_max_message_size_config(
+ max_decoding_message_size,
+ max_encoding_message_size,
+ );
+ let res = grpc.unary(method, req).await;
+ Ok(res)
+ };
+ Box::pin(fut)
+ }
_ => {
Box::pin(async move {
let mut response = http::Response::new(empty_body());
diff --git a/server/src/server/services/table_definition_service.rs b/server/src/server/services/table_definition_service.rs
index a96cd6a..919e877 100644
--- a/server/src/server/services/table_definition_service.rs
+++ b/server/src/server/services/table_definition_service.rs
@@ -1,20 +1,27 @@
-// server/src/server/services/table_definition_service.rs
+// src/server/services/table_definition_service.rs
use tonic::{Request, Response, Status};
use common::proto::multieko2::{
common::Empty,
table_definition::{
table_definition_server::TableDefinition,
- PostTableDefinitionRequest, TableDefinitionResponse, ProfileTreeResponse
- }
+ PostTableDefinitionRequest, TableDefinitionResponse,
+ ProfileTreeResponse, DeleteTableRequest, DeleteTableResponse,
+ },
};
use sqlx::PgPool;
-use crate::table_definition::handlers::{post_table_definition, get_profile_tree};
+use crate::table_definition::handlers::{post_table_definition, get_profile_tree, delete_table};
#[derive(Debug)]
pub struct TableDefinitionService {
pub db_pool: PgPool,
}
+impl TableDefinitionService {
+ pub fn new(db_pool: PgPool) -> Self {
+ Self { db_pool }
+ }
+}
+
#[tonic::async_trait]
impl TableDefinition for TableDefinitionService {
async fn post_table_definition(
@@ -27,8 +34,16 @@ impl TableDefinition for TableDefinitionService {
async fn get_profile_tree(
&self,
- request: Request, // Changed from Request<()>
+ request: Request,
) -> Result, Status> {
get_profile_tree::get_profile_tree(&self.db_pool, request).await
}
+
+ async fn delete_table(
+ &self,
+ request: Request,
+ ) -> Result, Status> {
+ let response = delete_table(&self.db_pool, request.into_inner()).await?;
+ Ok(Response::new(response))
+ }
}
diff --git a/server/src/table_definition/handlers.rs b/server/src/table_definition/handlers.rs
index db5b807..d66aa88 100644
--- a/server/src/table_definition/handlers.rs
+++ b/server/src/table_definition/handlers.rs
@@ -1,6 +1,8 @@
// server/src/table_definition/handlers.rs
pub mod post_table_definition;
pub mod get_profile_tree;
+pub mod delete_table;
pub use post_table_definition::post_table_definition;
pub use get_profile_tree::get_profile_tree;
+pub use delete_table::delete_table;
diff --git a/server/src/table_definition/handlers/delete_table.rs b/server/src/table_definition/handlers/delete_table.rs
new file mode 100644
index 0000000..0af789a
--- /dev/null
+++ b/server/src/table_definition/handlers/delete_table.rs
@@ -0,0 +1,84 @@
+// server/src/table_definition/handlers/delete_table.rs
+use tonic::Status;
+use sqlx::{PgPool, Postgres, Transaction};
+use common::proto::multieko2::table_definition::{DeleteTableRequest, DeleteTableResponse};
+
+pub async fn delete_table(
+ db_pool: &PgPool,
+ request: DeleteTableRequest,
+) -> Result {
+ let mut transaction = db_pool.begin().await
+ .map_err(|e| Status::internal(format!("Failed to start transaction: {}", e)))?;
+
+ // Step 1: Get profile and validate existence
+ let profile = sqlx::query!(
+ "SELECT id FROM profiles WHERE name = $1",
+ request.profile_name
+ )
+ .fetch_optional(&mut *transaction)
+ .await
+ .map_err(|e| Status::internal(format!("Profile lookup failed: {}", e)))?;
+
+ let profile_id = match profile {
+ Some(p) => p.id,
+ None => return Err(Status::not_found("Profile not found")),
+ };
+
+ // Step 2: Get table definition and validate existence
+ let table_def = sqlx::query!(
+ "SELECT id FROM table_definitions
+ WHERE profile_id = $1 AND table_name = $2",
+ profile_id,
+ request.table_name
+ )
+ .fetch_optional(&mut *transaction)
+ .await
+ .map_err(|e| Status::internal(format!("Table lookup failed: {}", e)))?;
+
+ let table_def_id = match table_def {
+ Some(t) => t.id,
+ None => return Err(Status::not_found("Table not found in profile")),
+ };
+
+ // Step 3: Drop the actual PostgreSQL table with CASCADE
+ sqlx::query(&format!(r#"DROP TABLE IF EXISTS "{}" CASCADE"#, request.table_name))
+ .execute(&mut *transaction)
+ .await
+ .map_err(|e| Status::internal(format!("Table drop failed: {}", e)))?;
+
+ // Step 4: Delete from table_definitions
+ sqlx::query!(
+ "DELETE FROM table_definitions WHERE id = $1",
+ table_def_id
+ )
+ .execute(&mut *transaction)
+ .await
+ .map_err(|e| Status::internal(format!("Definition deletion failed: {}", e)))?;
+
+ // Step 5: Check and clean up profile if empty
+ let remaining = sqlx::query!(
+ "SELECT COUNT(*) as count FROM table_definitions WHERE profile_id = $1",
+ profile_id
+ )
+ .fetch_one(&mut *transaction)
+ .await
+ .map_err(|e| Status::internal(format!("Count query failed: {}", e)))?;
+
+ if remaining.count.unwrap_or(1) == 0 {
+ sqlx::query!(
+ "DELETE FROM profiles WHERE id = $1",
+ profile_id
+ )
+ .execute(&mut *transaction)
+ .await
+ .map_err(|e| Status::internal(format!("Profile cleanup failed: {}", e)))?;
+ }
+
+ transaction.commit().await
+ .map_err(|e| Status::internal(format!("Transaction commit failed: {}", e)))?;
+
+ Ok(DeleteTableResponse {
+ success: true,
+ message: format!("Table '{}' and its definition were successfully removed", request.table_name),
+ })
+}
diff --git a/server/src/table_definition/handlers/post_table_definition.rs b/server/src/table_definition/handlers/post_table_definition.rs
index 3794ae9..b3fbf3a 100644
--- a/server/src/table_definition/handlers/post_table_definition.rs
+++ b/server/src/table_definition/handlers/post_table_definition.rs
@@ -101,15 +101,6 @@ pub async fn post_table_definition(
}
// Process indexes without year prefix
- let mut indexes = Vec::new();
- for idx in request.indexes.drain(..) {
- let idx_name = sanitize_identifier(&idx); // No year prefix
- if !is_valid_identifier(&idx) {
- return Err(Status::invalid_argument(format!("Invalid index name: {}", idx)));
- }
- indexes.push(idx_name);
- }
-
let mut indexes = Vec::new();
for idx in request.indexes.drain(..) {
let idx_name = sanitize_identifier(&idx);