From 91ea7eae2d2189c4f2ec15308da7efad30c19c85 Mon Sep 17 00:00:00 2001 From: Henry Fontanier Date: Fri, 10 Nov 2023 11:34:03 +0100 Subject: [PATCH] Add endpoints for upserting, retrieving, and listing database rows --- core/bin/dust_api.rs | 146 ++++++++++++++++++++++++++++++++++++ core/src/stores/postgres.rs | 2 +- core/src/stores/store.rs | 2 +- front/lib/core_api.ts | 94 +++++++++++++++++++++++ 4 files changed, 242 insertions(+), 2 deletions(-) diff --git a/core/bin/dust_api.rs b/core/bin/dust_api.rs index d92927002e9f..15868305b66c 100644 --- a/core/bin/dust_api.rs +++ b/core/bin/dust_api.rs @@ -1871,6 +1871,140 @@ async fn databases_tables_list( } } +#[derive(serde::Deserialize)] +struct DatabasesRowsUpsertPayload { + contents: HashMap, + truncate: Option, +} + +async fn databases_rows_upsert( + extract::Path((project_id, data_source_id, database_id, table_id)): extract::Path<( + i64, + String, + String, + String, + )>, + extract::Json(payload): extract::Json, + extract::Extension(state): extract::Extension>, +) -> (StatusCode, Json) { + let truncate = match payload.truncate { + Some(v) => v, + None => false, + }; + let project = project::Project::new_from_id(project_id); + + match state + .store + .batch_upsert_database_rows( + &project, + &data_source_id, + &database_id, + &table_id, + &payload.contents, + truncate, + ) + .await + { + Err(e) => error_response( + StatusCode::INTERNAL_SERVER_ERROR, + "internal_server_error", + "Failed to upsert database rows", + Some(e), + ), + Ok(()) => ( + StatusCode::OK, + Json(APIResponse { + error: None, + response: Some(json!({ + "success": true + })), + }), + ), + } +} + +async fn databases_rows_retrieve( + extract::Path((project_id, data_source_id, database_id, table_id, row_id)): extract::Path<( + i64, + String, + String, + String, + String, + )>, + + extract::Extension(state): extract::Extension>, +) -> (StatusCode, Json) { + let project = project::Project::new_from_id(project_id); + + match state + .store + .load_database_row(&project, &data_source_id, &database_id, &table_id, &row_id) + .await + { + Err(e) => error_response( + StatusCode::INTERNAL_SERVER_ERROR, + "internal_server_error", + "Failed to upsert database rows", + Some(e), + ), + Ok(row) => ( + StatusCode::OK, + Json(APIResponse { + error: None, + response: Some(json!({ + "row": row + })), + }), + ), + } +} + +#[derive(serde::Deserialize)] +struct DatabasesRowsListQuery { + offset: usize, + limit: usize, + table_id: Option, +} + +async fn databases_rows_list( + extract::Path((project_id, data_source_id, database_id)): extract::Path<(i64, String, String)>, + extract::Query(query): extract::Query, + extract::Extension(state): extract::Extension>, +) -> (StatusCode, Json) { + let project = project::Project::new_from_id(project_id); + + match state + .store + .list_database_rows( + &project, + &data_source_id, + &database_id, + &query.table_id, + Some((query.limit, query.offset)), + ) + .await + { + Err(e) => error_response( + StatusCode::INTERNAL_SERVER_ERROR, + "internal_server_error", + "Failed to list database rows", + Some(e), + ), + Ok((rows, total)) => ( + StatusCode::OK, + Json(APIResponse { + error: None, + response: Some(json!({ + "rows": rows, + "offset": query.offset, + "limit": query.limit, + "total": total, + })), + }), + ), + } +} + // Misc #[derive(serde::Deserialize)] @@ -2076,6 +2210,18 @@ fn main() { "/projects/:project_id/data_sources/:data_source_id/databases/:database_id/tables", get(databases_tables_list), ) + .route( + "/projects/:project_id/data_sources/:data_source_id/databases/:database_id/tables/:table_id", + post(databases_rows_upsert), + ) + .route( + "/projects/:project_id/data_sources/:data_source_id/databases/:database_id/tables/:table_id/rows/:row_id", + get(databases_rows_retrieve), + ) + .route( + "/projects/:project_id/data_sources/:data_source_id/databases/:database_id/rows", + get(databases_rows_list), + ) // Misc .route("/tokenize", post(tokenize)) diff --git a/core/src/stores/postgres.rs b/core/src/stores/postgres.rs index aa0606bc307a..ce34dea2503f 100644 --- a/core/src/stores/postgres.rs +++ b/core/src/stores/postgres.rs @@ -2270,7 +2270,7 @@ impl Store for PostgresStore { project: &Project, data_source_id: &str, database_id: &str, - table_id: Option<&str>, + table_id: &Option, limit_offset: Option<(usize, usize)>, ) -> Result<(Vec, usize)> { let project_id = project.project_id(); diff --git a/core/src/stores/store.rs b/core/src/stores/store.rs index 063d2749f104..47ac03b7f0e4 100644 --- a/core/src/stores/store.rs +++ b/core/src/stores/store.rs @@ -219,7 +219,7 @@ pub trait Store { project: &Project, data_source_id: &str, database_id: &str, - table_id: Option<&str>, + table_id: &Option, limit_offset: Option<(usize, usize)>, ) -> Result<(Vec, usize)>; diff --git a/front/lib/core_api.ts b/front/lib/core_api.ts index fcbc24fe8d92..5e683417422b 100644 --- a/front/lib/core_api.ts +++ b/front/lib/core_api.ts @@ -127,6 +127,13 @@ type CoreAPIDatabaseTable = { description: string; }; +type CoreAPIDatabaseRow = { + created: number; + table_id: string; + row_id: string; + content: Record; +}; + export const CoreAPI = { async createProject(): Promise> { const response = await fetch(`${CORE_API}/projects`, { @@ -953,6 +960,93 @@ export const CoreAPI = { return _resultFromResponse(response); }, + + async upsertDatabaseRows({ + projectId, + dataSourceName, + databaseId, + tableId, + contents, + truncate, + }: { + projectId: string; + dataSourceName: string; + databaseId: string; + tableId: string; + contents: Record; + truncate?: boolean; + }): Promise> { + const response = await fetch( + `${CORE_API}/projects/${projectId}/data_sources/${dataSourceName}/databases/${databaseId}/tables/${tableId}/rows`, + { + method: "POST", + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify({ + contents: contents, + truncate: truncate || false, + }), + } + ); + + return _resultFromResponse(response); + }, + + async getDatabaseRow({ + projectId, + dataSourceName, + databaseId, + tableId, + rowId, + }: { + projectId: string; + dataSourceName: string; + databaseId: string; + tableId: string; + rowId: string; + }): Promise> { + const response = await fetch( + `${CORE_API}/projects/${projectId}/data_sources/${dataSourceName}/databases/${databaseId}/tables/${tableId}/rows/${rowId}`, + { + method: "GET", + } + ); + + return _resultFromResponse(response); + }, + + async getDatabaseRows({ + projectId, + dataSourceName, + databaseId, + tableId, + limit, + offset, + }: { + projectId: string; + dataSourceName: string; + databaseId: string; + tableId: string; + limit: number; + offset: number; + }): Promise< + CoreAPIResponse<{ + rows: CoreAPIDatabaseRow[]; + offset: number; + limit: number; + total: number; + }> + > { + const response = await fetch( + `${CORE_API}/projects/${projectId}/data_sources/${dataSourceName}/databases/${databaseId}/tables/${tableId}/rows?limit=${limit}&offset=${offset}`, + { + method: "GET", + } + ); + + return _resultFromResponse(response); + }, }; async function _resultFromResponse(