diff --git a/common/proto/tables_data.proto b/common/proto/tables_data.proto index 2cfb0b7..c0e613c 100644 --- a/common/proto/tables_data.proto +++ b/common/proto/tables_data.proto @@ -10,7 +10,7 @@ service TablesData { rpc DeleteTableData (DeleteTableDataRequest) returns (DeleteTableDataResponse); rpc GetTableData(GetTableDataRequest) returns (GetTableDataResponse); rpc GetTableDataCount(GetTableDataCountRequest) returns (multieko2.common.CountResponse); - + rpc GetTableDataByPosition(GetTableDataByPositionRequest) returns (GetTableDataResponse); } message PostTableDataRequest { @@ -62,3 +62,9 @@ message GetTableDataCountRequest { string profile_name = 1; string table_name = 2; } + +message GetTableDataByPositionRequest { + string profile_name = 1; + string table_name = 2; + int32 position = 3; +} diff --git a/common/src/proto/descriptor.bin b/common/src/proto/descriptor.bin index d99a613..042b396 100644 Binary files a/common/src/proto/descriptor.bin and b/common/src/proto/descriptor.bin differ diff --git a/common/src/proto/multieko2.tables_data.rs b/common/src/proto/multieko2.tables_data.rs index 38a5ce4..2e2fcd9 100644 --- a/common/src/proto/multieko2.tables_data.rs +++ b/common/src/proto/multieko2.tables_data.rs @@ -81,6 +81,15 @@ pub struct GetTableDataCountRequest { #[prost(string, tag = "2")] pub table_name: ::prost::alloc::string::String, } +#[derive(Clone, PartialEq, ::prost::Message)] +pub struct GetTableDataByPositionRequest { + #[prost(string, tag = "1")] + pub profile_name: ::prost::alloc::string::String, + #[prost(string, tag = "2")] + pub table_name: ::prost::alloc::string::String, + #[prost(int32, tag = "3")] + pub position: i32, +} /// Generated client implementations. pub mod tables_data_client { #![allow( @@ -308,6 +317,35 @@ pub mod tables_data_client { ); self.inner.unary(req, path, codec).await } + pub async fn get_table_data_by_position( + &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.tables_data.TablesData/GetTableDataByPosition", + ); + let mut req = request.into_request(); + req.extensions_mut() + .insert( + GrpcMethod::new( + "multieko2.tables_data.TablesData", + "GetTableDataByPosition", + ), + ); + self.inner.unary(req, path, codec).await + } } } /// Generated server implementations. @@ -358,6 +396,13 @@ pub mod tables_data_server { tonic::Response, tonic::Status, >; + async fn get_table_data_by_position( + &self, + request: tonic::Request, + ) -> std::result::Result< + tonic::Response, + tonic::Status, + >; } #[derive(Debug)] pub struct TablesDataServer { @@ -661,6 +706,55 @@ pub mod tables_data_server { }; Box::pin(fut) } + "/multieko2.tables_data.TablesData/GetTableDataByPosition" => { + #[allow(non_camel_case_types)] + struct GetTableDataByPositionSvc(pub Arc); + impl< + T: TablesData, + > tonic::server::UnaryService + for GetTableDataByPositionSvc { + type Response = super::GetTableDataResponse; + 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 { + ::get_table_data_by_position( + &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 = GetTableDataByPositionSvc(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/tables_data_service.rs b/server/src/server/services/tables_data_service.rs index c3a93ea..105481a 100644 --- a/server/src/server/services/tables_data_service.rs +++ b/server/src/server/services/tables_data_service.rs @@ -1,15 +1,15 @@ // src/server/services/tables_data_service.rs use tonic::{Request, Response, Status}; use common::proto::multieko2::tables_data::tables_data_server::TablesData; -use common::proto::multieko2::common::CountResponse; +use common::proto::multieko2::common::{CountResponse, PositionRequest}; use common::proto::multieko2::tables_data::{ PostTableDataRequest, PostTableDataResponse, PutTableDataRequest, PutTableDataResponse, DeleteTableDataRequest, DeleteTableDataResponse, GetTableDataRequest, GetTableDataResponse, - GetTableDataCountRequest, + GetTableDataCountRequest, GetTableDataByPositionRequest, }; -use crate::tables_data::handlers::{post_table_data, put_table_data, delete_table_data, get_table_data, get_table_data_count}; +use crate::tables_data::handlers::{post_table_data, put_table_data, delete_table_data, get_table_data, get_table_data_count, get_table_data_by_position}; use sqlx::PgPool; #[derive(Debug)] @@ -64,4 +64,13 @@ impl TablesData for TablesDataService { let response = get_table_data_count(&self.db_pool, request).await?; Ok(Response::new(response)) } + + async fn get_table_data_by_position( + &self, + request: Request, + ) -> Result, Status> { + let request = request.into_inner(); + let response = get_table_data_by_position(&self.db_pool, request).await?; + Ok(Response::new(response)) + } } diff --git a/server/src/tables_data/handlers.rs b/server/src/tables_data/handlers.rs index 6237ec3..3bca6dd 100644 --- a/server/src/tables_data/handlers.rs +++ b/server/src/tables_data/handlers.rs @@ -4,9 +4,11 @@ pub mod put_table_data; pub mod delete_table_data; pub mod get_table_data; pub mod get_table_data_count; +pub mod get_table_data_by_position; pub use post_table_data::post_table_data; pub use put_table_data::put_table_data; pub use delete_table_data::delete_table_data; pub use get_table_data::get_table_data; pub use get_table_data_count::get_table_data_count; +pub use get_table_data_by_position::get_table_data_by_position; diff --git a/server/src/tables_data/handlers/get_table_data_by_position.rs b/server/src/tables_data/handlers/get_table_data_by_position.rs new file mode 100644 index 0000000..a254646 --- /dev/null +++ b/server/src/tables_data/handlers/get_table_data_by_position.rs @@ -0,0 +1,72 @@ +// src/tables_data/handlers/get_table_data_by_position.rs +use tonic::Status; +use sqlx::PgPool; +use common::proto::multieko2::tables_data::{ + GetTableDataByPositionRequest, GetTableDataRequest, GetTableDataResponse +}; +use super::get_table_data; + +pub async fn get_table_data_by_position( + db_pool: &PgPool, + request: GetTableDataByPositionRequest, +) -> Result { + let profile_name = request.profile_name; + let table_name = request.table_name; + + if request.position < 1 { + return Err(Status::invalid_argument("Position must be at least 1")); + } + + let profile = sqlx::query!( + "SELECT id FROM profiles WHERE name = $1", + profile_name + ) + .fetch_optional(db_pool) + .await + .map_err(|e| Status::internal(format!("Profile lookup error: {}", e)))?; + + let profile_id = profile.ok_or_else(|| Status::not_found("Profile not found"))?.id; + + let table_exists = sqlx::query!( + r#"SELECT EXISTS( + SELECT 1 FROM table_definitions + WHERE profile_id = $1 AND table_name = $2 + )"#, + profile_id, + table_name + ) + .fetch_one(db_pool) + .await + .map_err(|e| Status::internal(format!("Table verification error: {}", e)))? + .exists + .unwrap_or(false); + + if !table_exists { + return Err(Status::not_found("Table not found")); + } + + let id: i64 = sqlx::query_scalar( + &format!( + r#"SELECT id FROM "{}" + WHERE deleted = FALSE + ORDER BY id ASC + OFFSET $1 + LIMIT 1"#, + table_name + ) + ) + .bind(request.position - 1) + .fetch_optional(db_pool) + .await + .map_err(|e| Status::internal(format!("Position query failed: {}", e)))? + .ok_or_else(|| Status::not_found("Position out of bounds"))?; + + get_table_data( + db_pool, + GetTableDataRequest { + profile_name, + table_name, + id, + } + ).await +}