From 769c12475846c47f99de9bd0ee4192a4dc500f80 Mon Sep 17 00:00:00 2001 From: zerj9 <96551236+zerj9@users.noreply.github.com> Date: Thu, 12 Dec 2024 19:06:09 +0000 Subject: [PATCH] fix: conflicts --- DATA_MODEL.md | 28 +- gridwalk-backend/src/core/connector.rs | 78 ++++-- gridwalk-backend/src/core/user.rs | 20 ++ gridwalk-backend/src/core/workspace.rs | 10 + gridwalk-backend/src/data/config.rs | 13 +- gridwalk-backend/src/data/dynamodb/config.rs | 91 ++++--- .../src/data/dynamodb/conversions.rs | 37 ++- gridwalk-backend/src/main.rs | 2 +- gridwalk-backend/src/routes/connector.rs | 243 +++++++----------- gridwalk-backend/src/routes/mod.rs | 4 +- gridwalk-backend/src/routes/user.rs | 1 + gridwalk-backend/src/server.rs | 18 +- 12 files changed, 301 insertions(+), 244 deletions(-) diff --git a/DATA_MODEL.md b/DATA_MODEL.md index 2af4d14..9b128b4 100644 --- a/DATA_MODEL.md +++ b/DATA_MODEL.md @@ -3,13 +3,21 @@ This is the data model used for the Gridwalk application. Currently, only DynamoDB is supported, with plans to add support for other databases in the future. ## DynamoDB (Single Table) -| PK | SK | Attributes | GSIs | -|---|---|---|---| -| USER# | USER# | user_name
created_at
primary_email | | -| EMAIL# | EMAIL# | user_id | | -| WSP# | WSP# | workspace_name
workspace_owner
created_at | GSI_WORKSPACE_BY_NAME
workspace_name: | -| WSP# | USER# | user_role
joined_at
user_id | GSI_USER_ID
user_id: | -| WSP# | CON# | name
connector_type
created_by
pg_host
pg_port
pg_db
pg_username
pg_password | | -| WSP# | LAYER# | name
connection_id
uploaded_by
created_at | | -| WSP# | PROJ# | name
owner
created_at | | -| SESSION# | SESSION# | user_id
created_at | GSI_USER
user_id: | + +| Entity | PK | SK | user_id | wsp_id | con_id | Attributes | +|-------------------|---------------|---------------------------------|---------|--------|---------|----------------------------------------| +| User | USER#{id} | USER#{id} | | | | created_at, active | +| User Global Role | USER#{id} | ROLE#[SUPER/SUPPORT/READ] | | | | | +| Email | EMAIL#{email} | EMAIL#{email} | ✓ | | | [primary, secondary] | +| Session | SESSION#{id} | SESSION#{id} | ✓ | | | created_at, login_ip | +| | | | | | | | +| Connection | CON#{id/name} | CON#{id/name} | | | | name, connector_type, connector_config | +| Connection Access | WSP#{id} | CONACC#{id/name}#{wsp_id}:level | | | ✓ | | +| | | | | | | | +| Workspace | WSP#{id} | WSP#{id} | | | | name, owner, created_at, active | +| Workspace Member | WSP#{id} | USER#{id} | ✓ | | | role, joined_at | +| Layer | WSP#{id} | LAYER#{layer_name} | | | ✓ | created_by, created_at | +| Project | WSP#{id} | PROJ#{id} | | | | name, owner, created_at | + +## Notes + - The Connection entity may have an ID or a name. A Connection with a name is used for global connectors created by the system administrators. A Connection with an ID is used for user-created connectors. diff --git a/gridwalk-backend/src/core/connector.rs b/gridwalk-backend/src/core/connector.rs index effabf9..a7121c5 100644 --- a/gridwalk-backend/src/core/connector.rs +++ b/gridwalk-backend/src/core/connector.rs @@ -9,13 +9,13 @@ use tokio_postgres::NoTls; use crate::data::Database; +use super::Workspace; + // TODO: Switch connector_type/postgis_uri to enum to support other connectors #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Connection { pub id: String, - pub workspace_id: String, pub name: String, - pub created_by: String, pub connector_type: String, pub config: PostgresConnection, } @@ -26,27 +26,68 @@ impl Connection { Ok(()) } - pub async fn from_id( - database: &Arc, - workspace_id: &str, - connection_id: &str, - ) -> Result { - let con = database - .get_workspace_connection(workspace_id, connection_id) - .await?; + pub async fn from_name(database: &Arc, connection_name: &str) -> Result { + let con = database.get_connection(connection_name).await?; Ok(con) } - pub async fn get_all( - database: &Arc, - workspace_id: &str, - ) -> Result> { - let connections = database.get_workspace_connections(workspace_id).await?; - Ok(connections) + //pub async fn delete(&self, database: &Arc) -> Result<()> { + // database.delete_workspace_connection(self).await + //} +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub struct ConnectionAccess { + pub connection_id: String, + pub workspace_id: String, + pub access_config: ConnectionAccessConfig, +} + +#[derive(Debug, Clone, Deserialize, Serialize)] +pub enum ConnectionAccessConfig { + Admin(String), + ReadWrite(String), + ReadOnly(String), +} + +impl ConnectionAccessConfig { + pub fn from_str(variant: &str, path: String) -> Result { + match variant.to_lowercase().as_str() { + "admin" => Ok(ConnectionAccessConfig::Admin(path)), + "readwrite" | "read_write" => Ok(ConnectionAccessConfig::ReadWrite(path)), + "readonly" | "read_only" => Ok(ConnectionAccessConfig::ReadOnly(path)), + _ => Err(format!("Invalid variant name: {}", variant)), + } + } + + pub fn variant_name(&self) -> &'static str { + match self { + ConnectionAccessConfig::Admin(_) => "Admin", + ConnectionAccessConfig::ReadWrite(_) => "ReadWrite", + ConnectionAccessConfig::ReadOnly(_) => "ReadOnly", + } + } + + pub fn path(&self) -> &String { + match self { + ConnectionAccessConfig::Admin(v) + | ConnectionAccessConfig::ReadWrite(v) + | ConnectionAccessConfig::ReadOnly(v) => v, + } } +} - pub async fn delete(&self, database: &Arc) -> Result<()> { - database.delete_workspace_connection(self).await +impl ConnectionAccess { + pub async fn create_record(self, database: &Arc) -> Result<()> { + database.create_connection_access(&self).await?; + Ok(()) + } + + pub async fn get_all( + database: &Arc, + wsp: &Workspace, + ) -> Result> { + database.get_accessible_connections(wsp).await } } @@ -187,6 +228,7 @@ FROM mvt_data; } } +// The GeospatialConfig struct and its impl block are used to manage live connections #[derive(Clone)] pub struct GeospatialConfig { sources: Arc>>>, diff --git a/gridwalk-backend/src/core/user.rs b/gridwalk-backend/src/core/user.rs index 0269388..389cfa8 100644 --- a/gridwalk-backend/src/core/user.rs +++ b/gridwalk-backend/src/core/user.rs @@ -3,6 +3,15 @@ use crate::data::Database; use anyhow::{anyhow, Result}; use serde::{Deserialize, Serialize}; use std::sync::Arc; +use strum_macros::{Display, EnumString}; + +#[derive(PartialEq, Debug, Display, EnumString, Clone, Deserialize, Serialize)] +#[strum(serialize_all = "snake_case")] +pub enum GlobalRole { + Super, + Support, + Read, +} #[derive(Debug, Clone, Deserialize, Serialize)] pub struct User { @@ -10,6 +19,7 @@ pub struct User { pub email: String, pub first_name: String, pub last_name: String, + pub global_role: Option, pub active: bool, pub created_at: u64, pub hash: String, @@ -20,6 +30,7 @@ pub struct CreateUser { pub email: String, pub first_name: String, pub last_name: String, + pub global_role: Option, pub password: String, } @@ -78,12 +89,14 @@ impl User { email: create_user.email.to_string(), first_name: create_user.first_name.to_string(), last_name: create_user.last_name.to_string(), + global_role: create_user.clone().global_role, active, created_at: now, hash: password_hash, } } +<<<<<<< HEAD pub async fn update_password( &mut self, database: &Arc, @@ -103,5 +116,12 @@ impl User { user.update_password(database, new_password).await?; Ok(user) +======= + pub async fn check_global_role(&self) -> Option { + match &self.global_role { + Some(support_level) => Some(support_level.clone()), + None => None, + } +>>>>>>> c688619 (chore: separate connection record) } } diff --git a/gridwalk-backend/src/core/workspace.rs b/gridwalk-backend/src/core/workspace.rs index 030cc34..c327484 100644 --- a/gridwalk-backend/src/core/workspace.rs +++ b/gridwalk-backend/src/core/workspace.rs @@ -6,6 +6,8 @@ use std::fmt; use std::str::FromStr; use std::sync::Arc; +use super::{ConnectionAccess, ConnectionAccessConfig}; + #[derive(Debug, Clone, Deserialize, Serialize)] pub struct Workspace { pub id: String, @@ -72,6 +74,14 @@ impl Workspace { // Check for existing org with same name let db_resp = database.create_workspace(wsp).await; + // Create ConnectionAccess to shared primary db + let connection_access = ConnectionAccess { + connection_id: "primary".to_string(), + workspace_id: wsp.id.clone(), + access_config: ConnectionAccessConfig::ReadWrite(wsp.id.clone()), + }; + connection_access.create_record(database).await?; + match db_resp { Ok(_) => Ok(()), Err(_) => Err(anyhow!("failed to create workspace")), diff --git a/gridwalk-backend/src/data/config.rs b/gridwalk-backend/src/data/config.rs index 149b503..b76894b 100644 --- a/gridwalk-backend/src/data/config.rs +++ b/gridwalk-backend/src/data/config.rs @@ -1,5 +1,6 @@ use crate::core::{ - Connection, Layer, Project, Session, User, Workspace, WorkspaceMember, WorkspaceRole, + Connection, ConnectionAccess, Layer, Project, Session, User, Workspace, WorkspaceMember, + WorkspaceRole, }; use anyhow::Result; use async_trait::async_trait; @@ -25,13 +26,9 @@ pub trait UserStore: Send + Sync + 'static { async fn get_workspace_members(&self, wsp: &Workspace) -> Result>; async fn remove_workspace_member(&self, org: &Workspace, user: &User) -> Result<()>; async fn create_connection(&self, connection: &Connection) -> Result<()>; - async fn get_workspace_connection( - &self, - workspace_id: &str, - connection_id: &str, - ) -> Result; - async fn get_workspace_connections(&self, workspace_id: &str) -> Result>; - async fn delete_workspace_connection(&self, con: &Connection) -> Result<()>; + async fn get_connection(&self, connection_id: &str) -> Result; + async fn create_connection_access(&self, ca: &ConnectionAccess) -> Result<()>; + async fn get_accessible_connections(&self, wsp: &Workspace) -> Result>; async fn create_layer(&self, layer: &Layer) -> Result<()>; async fn create_project(&self, project: &Project) -> Result<()>; async fn get_workspaces(&self, user: &User) -> Result>; diff --git a/gridwalk-backend/src/data/dynamodb/config.rs b/gridwalk-backend/src/data/dynamodb/config.rs index ccc76af..fe9ac48 100644 --- a/gridwalk-backend/src/data/dynamodb/config.rs +++ b/gridwalk-backend/src/data/dynamodb/config.rs @@ -1,5 +1,6 @@ use crate::core::{ - Connection, CreateUser, Email, Layer, Project, User, Workspace, WorkspaceMember, WorkspaceRole, + Connection, ConnectionAccess, CreateUser, Email, GlobalRole, Layer, Project, User, Workspace, + WorkspaceMember, WorkspaceRole, }; use crate::data::{Database, UserStore}; use anyhow::{anyhow, Result}; @@ -146,6 +147,7 @@ impl Dynamodb { email: String::from("test@example.com"), first_name: String::from("Admin"), last_name: String::from("Istrator"), + global_role: Some(GlobalRole::Super), password: String::from("admin"), }; User::create(&dynamodb, &admin_user).await?; @@ -176,6 +178,10 @@ impl UserStore for Dynamodb { ); item.insert(String::from("hash"), AV::S(user.hash.clone())); + if let Some(global_role) = &user.global_role { + item.insert(String::from("global_role"), AV::S(global_role.to_string())); + } + self.client .put_item() .table_name(&self.table_name) @@ -498,13 +504,9 @@ impl UserStore for Dynamodb { // Create the connection item to insert let mut item = std::collections::HashMap::new(); - item.insert( - String::from("PK"), - AV::S(format!("WSP#{}", con.clone().workspace_id)), - ); + item.insert(String::from("PK"), AV::S(format!("CON#{}", con.clone().id))); item.insert(String::from("SK"), AV::S(format!("CON#{}", con.clone().id))); item.insert(String::from("name"), AV::S(con.clone().name)); - item.insert(String::from("created_by"), AV::S(con.clone().created_by)); item.insert( String::from("connector_type"), AV::S(con.clone().connector_type), @@ -523,9 +525,6 @@ impl UserStore for Dynamodb { String::from("pg_password"), AV::S(con.clone().config.password), ); - if let Some(schema) = &con.config.schema { - item.insert(String::from("pg_schema"), AV::S(schema.to_string())); - } self.client .put_item() @@ -537,12 +536,8 @@ impl UserStore for Dynamodb { Ok(()) } - async fn get_workspace_connection( - &self, - workspace_id: &str, - connection_id: &str, - ) -> Result { - let pk = format!("WSP#{workspace_id}"); + async fn get_connection(&self, connection_id: &str) -> Result { + let pk = format!("CON#{connection_id}"); let sk = format!("CON#{connection_id}"); match self .client @@ -554,42 +549,62 @@ impl UserStore for Dynamodb { .await { Ok(response) => Ok(response.item.unwrap().into()), - Err(_e) => Err(anyhow!("workspace not found")), + Err(_e) => Err(anyhow!("connection not found")), } } - async fn get_workspace_connections(&self, workspace_id: &str) -> Result> { - let pk = format!("WSP#{}", workspace_id); - let query_output = self - .client - .query() + async fn create_connection_access(&self, ca: &ConnectionAccess) -> Result<()> { + // Create the connection item to insert + let mut item = std::collections::HashMap::new(); + let sk = format!( + "CONACC#{}#{}:{}", + ca.clone().connection_id, + ca.access_config.path(), + ca.access_config.variant_name(), + ); + + item.insert( + String::from("PK"), + AV::S(format!("WSP#{}", ca.clone().workspace_id)), + ); + item.insert(String::from("SK"), AV::S(sk)); + item.insert( + String::from("source_workspace"), + AV::S(ca.clone().workspace_id), + ); + + self.client + .put_item() .table_name(&self.table_name) - .key_condition_expression("PK = :pk AND begins_with(SK, :sk_prefix)") - .expression_attribute_values(":pk", AV::S(pk)) - .expression_attribute_values(":sk_prefix", AV::S("CON#".to_string())) + .set_item(Some(item)) .send() .await?; - match query_output.items { - Some(items) => { - let connections: Vec = - items.into_iter().map(|item| item.into()).collect(); - Ok(connections) - } - None => Ok(vec![]), - } + Ok(()) } - async fn delete_workspace_connection(&self, con: &Connection) -> Result<()> { - self.client - .delete_item() + async fn get_accessible_connections(&self, wsp: &Workspace) -> Result> { + let pk = format!("WSP#{}", wsp.id); + let sk_prefix = "CONACC#"; + + let accessible_connections = self + .client + .query() .table_name(&self.table_name) - .key("PK", AV::S(format!("WSP#{}", con.workspace_id))) - .key("SK", AV::S(format!("CON#{}", con.id))) + .key_condition_expression("PK = :pk AND begins_with(SK, :sk_prefix)") + .expression_attribute_values(":pk", AV::S(pk)) + .expression_attribute_values(":sk_prefix", AV::S(sk_prefix.to_string())) .send() .await?; - Ok(()) + let connections: Vec = accessible_connections + .items + .unwrap_or_default() + .into_iter() + .map(|item| item.into()) + .collect(); + + Ok(connections) } async fn create_layer(&self, layer: &Layer) -> Result<()> { diff --git a/gridwalk-backend/src/data/dynamodb/conversions.rs b/gridwalk-backend/src/data/dynamodb/conversions.rs index 60837b1..fb44e63 100644 --- a/gridwalk-backend/src/data/dynamodb/conversions.rs +++ b/gridwalk-backend/src/data/dynamodb/conversions.rs @@ -1,5 +1,6 @@ use crate::core::{ - Connection, Email, PostgresConnection, Project, Session, User, Workspace, WorkspaceMember, + Connection, ConnectionAccess, ConnectionAccessConfig, Email, PostgresConnection, Project, + Session, User, Workspace, WorkspaceMember, }; use aws_sdk_dynamodb::types::AttributeValue as AV; use std::collections::HashMap; @@ -8,6 +9,7 @@ fn split_at_hash(input: &str) -> &str { input.split_once('#').unwrap().1 } +// Convert DynamoDB response into User struct impl From> for User { fn from(value: HashMap) -> Self { let user = User { @@ -20,6 +22,10 @@ impl From> for User { .to_string(), first_name: value.get("first_name").unwrap().as_s().unwrap().to_string(), last_name: value.get("last_name").unwrap().as_s().unwrap().to_string(), + global_role: value + .get("global_role") + .and_then(|v| v.as_s().ok()) + .and_then(|s| s.parse().ok()), active: *value.get("active").unwrap().as_bool().unwrap(), created_at: value .get("created_at") @@ -97,14 +103,12 @@ impl From> for Session { } } -// Convert DynamoDB response into ConnectionInfo struct +// Convert DynamoDB response into Connection struct impl From> for Connection { fn from(value: HashMap) -> Self { Connection { - id: split_at_hash(value.get("SK").unwrap().as_s().unwrap()).to_string(), - workspace_id: split_at_hash(value.get("PK").unwrap().as_s().unwrap()).to_string(), + id: value.get("PK").unwrap().as_s().unwrap().into(), name: value.get("name").unwrap().as_s().unwrap().into(), - created_by: value.get("created_by").unwrap().as_s().unwrap().into(), connector_type: value.get("connector_type").unwrap().as_s().unwrap().into(), config: PostgresConnection { host: value.get("pg_host").unwrap().as_s().unwrap().into(), @@ -127,6 +131,29 @@ impl From> for Connection { } } +// Convert DynamoDB response into ConnectionAccess struct +impl From> for ConnectionAccess { + fn from(value: HashMap) -> Self { + println!("{:?}", value); + let parts: Vec<&str> = value + .get("SK") + .unwrap() + .as_s() + .unwrap() + .split('#') + .skip(1) + .collect(); + let last_part: Vec<&str> = parts[1].split(':').collect(); + + ConnectionAccess { + workspace_id: split_at_hash(value.get("PK").unwrap().as_s().unwrap()).to_string(), + connection_id: parts[0].to_string(), + access_config: ConnectionAccessConfig::from_str(last_part[1], last_part[0].to_string()) + .unwrap(), + } + } +} + impl From> for Project { fn from(value: HashMap) -> Self { Project { diff --git a/gridwalk-backend/src/main.rs b/gridwalk-backend/src/main.rs index 9d2421c..955e4a9 100644 --- a/gridwalk-backend/src/main.rs +++ b/gridwalk-backend/src/main.rs @@ -22,7 +22,7 @@ async fn main() -> Result<()> { let table_name = env::var("DYNAMODB_TABLE").unwrap_or_else(|_| "gridwalk".to_string()); // let app_db = Dynamodb::new(false, &table_name).await.unwrap(); // FOR LOCAL DB DEV - let app_db = Dynamodb::new(true, &table_name).await.unwrap(); + let app_db = Dynamodb::new(false, &table_name).await.unwrap(); // Create GeospatialConfig let geospatial_config = GeospatialConfig::new(); diff --git a/gridwalk-backend/src/routes/connector.rs b/gridwalk-backend/src/routes/connector.rs index 3c333fe..e637392 100644 --- a/gridwalk-backend/src/routes/connector.rs +++ b/gridwalk-backend/src/routes/connector.rs @@ -1,7 +1,8 @@ use crate::app_state::AppState; use crate::auth::AuthUser; use crate::core::{ - Connection, PostgisConfig, PostgresConnection, User, Workspace, WorkspaceMember, + Connection, ConnectionAccess, GlobalRole, PostgisConfig, PostgresConnection, Workspace, + WorkspaceMember, }; use axum::{ extract::{Extension, Path, State}, @@ -11,23 +12,20 @@ use axum::{ }; use serde::{Deserialize, Serialize}; use std::sync::Arc; -use uuid::Uuid; // TODO: Allow other connector types #[derive(Debug, Deserialize)] -pub struct CreateConnectionRequest { - workspace_id: String, +pub struct CreateGlobalConnectionRequest { name: String, + display_name: String, config: PostgresConnection, } impl Connection { - pub fn from_req(req: CreateConnectionRequest, user: User) -> Self { + pub fn from_req(req: CreateGlobalConnectionRequest) -> Self { Connection { - id: Uuid::new_v4().to_string(), - workspace_id: req.workspace_id, - name: req.name, - created_by: user.id, + id: req.name, + name: req.display_name, connector_type: "postgis".into(), config: req.config, } @@ -37,100 +35,52 @@ impl Connection { pub async fn create_connection( State(state): State>, Extension(auth_user): Extension, - Json(req): Json, + Json(req): Json, ) -> impl IntoResponse { - if let Some(user) = auth_user.user { - let workspace = match Workspace::from_id(&state.app_data, &req.workspace_id).await { - Ok(workspace) => workspace, - Err(_) => return (StatusCode::NOT_FOUND, "".to_string()), - }; - - // Check if the requesting user is a member of the workspace - let member = match WorkspaceMember::get(&state.app_data, &workspace, &user).await { - Ok(member) => member, - Err(_) => return (StatusCode::NOT_FOUND, "".to_string()), - }; - - // Only allow admins to create connections - if !member.is_admin() { - return (StatusCode::FORBIDDEN, "".to_string()); - } - - let connection_info = Connection::from_req(req, user); - match connection_info.clone().create_record(&state.app_data).await { - Ok(_) => { - println!("Created connection record"); - let connections = state - .app_data - .clone() - .get_workspace_connections(&connection_info.workspace_id) - .await; - println!("{:?}", connections); - (StatusCode::OK, "connection creation submitted".to_string()) - } - Err(_) => ( - StatusCode::INTERNAL_SERVER_ERROR, - "connection creation failed".to_string(), - ), - } - } else { - (StatusCode::FORBIDDEN, "".to_string()) + let user = match auth_user.user { + Some(user) => user, + None => return (StatusCode::FORBIDDEN, "Unauthorized").into_response(), + }; + + // Check support level + let global_role = match user.check_global_role().await { + Some(level) => level, + None => return (StatusCode::FORBIDDEN, "Unauthorized").into_response(), + }; + + // Only allow user with Super global role to create connections + if global_role != GlobalRole::Super { + return (StatusCode::FORBIDDEN, "Unauthorized").into_response(); } -} -pub async fn delete_connection( - State(state): State>, - Extension(auth_user): Extension, - Path((workspace_id, connection_id)): Path<(String, String)>, -) -> Response { - if let Some(req_user) = auth_user.user { - // Get the workspace - let workspace = match Workspace::from_id(&state.app_data, &workspace_id).await { - Ok(ws) => ws, - Err(_) => return "workspace not found".into_response(), - }; - - // Check permissions - match WorkspaceMember::get(&state.app_data, &workspace, &req_user).await { - Ok(mem) => { - if !mem.is_admin() { - return "permission denied".into_response(); - }; - } - Err(_) => return "member not found".into_response(), - }; - - let con = match Connection::from_id(&state.app_data, &workspace_id, &connection_id).await { - Ok(c) => c, - Err(_) => return "connection not found".into_response(), - }; - - match con.delete(&state.app_data).await { - Ok(_) => "".into_response(), - Err(_) => "delete failed".into_response(), - } - } else { - "unauthorized".into_response() + // Create connection info + let connection_info = Connection::from_req(req); + + // Attempt to create record + match connection_info.create_record(&state.app_data).await { + Ok(_) => (StatusCode::OK, "Connection creation submitted").into_response(), + Err(e) => ( + StatusCode::INTERNAL_SERVER_ERROR, + format!("Connection creation failed: {}", e), + ) + .into_response(), } } #[derive(Debug, Clone, Deserialize, Serialize)] pub struct ConnectionResponse { pub id: String, - pub workspace_id: String, pub name: String, - pub created_by: String, pub connector_type: String, } -impl From for ConnectionResponse { - fn from(con: Connection) -> Self { +// TODO: Switch to using Connection after retrieving the connection from the database +impl From for ConnectionResponse { + fn from(con: ConnectionAccess) -> Self { ConnectionResponse { - id: con.id, - workspace_id: con.workspace_id, - name: con.name, - created_by: con.created_by, - connector_type: con.connector_type, + id: con.connection_id.clone(), + name: con.connection_id, + connector_type: con.access_config.path().to_string(), } } } @@ -141,7 +91,6 @@ pub async fn list_connections( Path(workspace_id): Path, ) -> impl IntoResponse { match auth_user.user { - // TODO: Check permissions Some(user) => { let workspace = Workspace::from_id(&state.app_data, &workspace_id) .await @@ -152,14 +101,16 @@ pub async fn list_connections( .await .map_err(|_| (StatusCode::FORBIDDEN, "unauthorized".to_string()))?; - let connections = Connection::get_all(&state.app_data, &workspace_id) + let connection_access_list = ConnectionAccess::get_all(&state.app_data, &workspace) .await .ok() .unwrap(); + println!("Connection access list: {:?}", connection_access_list); + // Convert Vec to Vec // Removes the config from the response - let connection_responses: Vec = connections + let connection_responses: Vec = connection_access_list .into_iter() .map(ConnectionResponse::from) .collect(); @@ -170,57 +121,57 @@ pub async fn list_connections( } } -pub async fn list_sources( - State(state): State>, - Extension(auth_user): Extension, - Path((workspace_id, connection_id)): Path<(String, String)>, -) -> impl IntoResponse { - let workspace = Workspace::from_id(&state.app_data, &workspace_id) - .await - .unwrap(); - let _member = workspace - .get_member(&state.app_data, &auth_user.user.unwrap()) - .await - .unwrap(); - // Check member role - let connection = Connection::from_id(&state.app_data, &workspace_id, &connection_id) - .await - .unwrap(); - - match state.geospatial_config.get_connection(&connection_id).await { - Ok(connector) => match connector.list_sources().await { - Ok(sources) => Json(sources).into_response(), - Err(e) => { - eprintln!("Error listing sources: {:?}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Failed to list sources").into_response() - } - }, - Err(_) => { - println!("Connection not found, adding new connection to state"); - let pg_config = PostgisConfig::new(connection.config).unwrap(); - state - .geospatial_config - .add_connection(connection_id.clone(), pg_config) - .await; - - match state.geospatial_config.get_connection(&connection_id).await { - Ok(connector) => match connector.list_sources().await { - Ok(sources) => Json(sources).into_response(), - Err(e) => { - eprintln!("Error listing sources: {:?}", e); - (StatusCode::INTERNAL_SERVER_ERROR, "Failed to list sources") - .into_response() - } - }, - Err(e) => { - eprintln!("Error getting connection after adding: {:?}", e); - ( - StatusCode::INTERNAL_SERVER_ERROR, - "Failed to get connection", - ) - .into_response() - } - } - } - } -} +//pub async fn list_sources( +// State(state): State>, +// Extension(auth_user): Extension, +// Path((workspace_id, connection_id)): Path<(String, String)>, +//) -> impl IntoResponse { +// let workspace = Workspace::from_id(&state.app_data, &workspace_id) +// .await +// .unwrap(); +// let _member = workspace +// .get_member(&state.app_data, &auth_user.user.unwrap()) +// .await +// .unwrap(); +// // Check member role +// let connection = Connection::from_id(&state.app_data, &workspace_id, &connection_id) +// .await +// .unwrap(); +// +// match state.geospatial_config.get_connection(&connection_id).await { +// Ok(connector) => match connector.list_sources().await { +// Ok(sources) => Json(sources).into_response(), +// Err(e) => { +// eprintln!("Error listing sources: {:?}", e); +// (StatusCode::INTERNAL_SERVER_ERROR, "Failed to list sources").into_response() +// } +// }, +// Err(_) => { +// println!("Connection not found, adding new connection to state"); +// let pg_config = PostgisConfig::new(connection.config).unwrap(); +// state +// .geospatial_config +// .add_connection(connection_id.clone(), pg_config) +// .await; +// +// match state.geospatial_config.get_connection(&connection_id).await { +// Ok(connector) => match connector.list_sources().await { +// Ok(sources) => Json(sources).into_response(), +// Err(e) => { +// eprintln!("Error listing sources: {:?}", e); +// (StatusCode::INTERNAL_SERVER_ERROR, "Failed to list sources") +// .into_response() +// } +// }, +// Err(e) => { +// eprintln!("Error getting connection after adding: {:?}", e); +// ( +// StatusCode::INTERNAL_SERVER_ERROR, +// "Failed to get connection", +// ) +// .into_response() +// } +// } +// } +// } +//} diff --git a/gridwalk-backend/src/routes/mod.rs b/gridwalk-backend/src/routes/mod.rs index 44cd455..b0f8843 100644 --- a/gridwalk-backend/src/routes/mod.rs +++ b/gridwalk-backend/src/routes/mod.rs @@ -2,7 +2,7 @@ mod connector; mod layer; mod os_token; mod project; -mod tiles; +//mod tiles; mod user; mod workspace; @@ -10,6 +10,6 @@ pub use connector::*; pub use layer::*; pub use os_token::*; pub use project::*; -pub use tiles::*; +//pub use tiles::*; pub use user::*; pub use workspace::*; diff --git a/gridwalk-backend/src/routes/user.rs b/gridwalk-backend/src/routes/user.rs index 0a6eaa1..4ac5618 100644 --- a/gridwalk-backend/src/routes/user.rs +++ b/gridwalk-backend/src/routes/user.rs @@ -35,6 +35,7 @@ pub async fn register( email: req.email, first_name: req.first_name, last_name: req.last_name, + global_role: None, password: req.password, }; match User::create(&state.app_data, &user).await { diff --git a/gridwalk-backend/src/server.rs b/gridwalk-backend/src/server.rs index 3ab3706..4f77d45 100644 --- a/gridwalk-backend/src/server.rs +++ b/gridwalk-backend/src/server.rs @@ -1,11 +1,6 @@ use crate::app_state::AppState; use crate::auth::auth_middleware; -use crate::routes::{ - add_workspace_member, create_connection, create_project, create_workspace, delete_connection, - delete_project, generate_os_token, get_projects, get_workspace_members, get_workspaces, - health_check, list_connections, list_sources, login, logout, profile, register, - remove_workspace_member, reset_password, tiles, upload_layer, -}; +use crate::routes::*; use axum::{ extract::DefaultBodyLimit, middleware, @@ -38,7 +33,7 @@ pub fn create_app(app_state: AppState) -> Router { .route("/workspace", post(create_workspace)) .route("/workspace/members", post(add_workspace_member)) .route( - "/workspace/:workspace_id/members", + "/workspacei/:workspace_id/members", get(get_workspace_members), ) .route( @@ -46,18 +41,10 @@ pub fn create_app(app_state: AppState) -> Router { delete(remove_workspace_member), ) .route("/connection", post(create_connection)) - .route( - "/workspace/:workspace_id/connection/:connection_id/sources", - get(list_sources), - ) .route( "/workspaces/:workspace_id/connections", get(list_connections), ) - .route( - "/workspaces/:workspace_id/connections/:connection_id", - delete(delete_connection), - ) .route("/create_project", post(create_project)) .route("/upload_layer", post(upload_layer)) .layer(DefaultBodyLimit::disable()) @@ -66,7 +53,6 @@ pub fn create_app(app_state: AppState) -> Router { shared_state.clone(), auth_middleware, )) - .route("/tiles/:workspace_id/:connection_id/:z/:x/:y", get(tiles)) .route("/register", post(register)) .route("/login", post(login)) .route("/os-token", get(generate_os_token))