Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(common): team models #1986

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 24 additions & 23 deletions api-client/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,20 @@ use reqwest::header::HeaderMap;
use reqwest::Response;
use reqwest_middleware::{ClientWithMiddleware, RequestBuilder};
use serde::{Deserialize, Serialize};
use shuttle_common::models::certificate::{
AddCertificateRequest, CertificateListResponse, CertificateResponse, DeleteCertificateRequest,
use shuttle_common::models::{
certificate::{
AddCertificateRequest, CertificateListResponse, CertificateResponse,
DeleteCertificateRequest,
},
deployment::{
DeploymentListResponse, DeploymentRequest, DeploymentResponse, UploadArchiveResponse,
},
log::LogsResponse,
project::{ProjectCreateRequest, ProjectListResponse, ProjectResponse, ProjectUpdateRequest},
resource::{ProvisionResourceRequest, ResourceListResponse, ResourceResponse, ResourceType},
team::TeamListResponse,
user::UserResponse,
};
use shuttle_common::models::deployment::{
DeploymentListResponse, DeploymentRequest, DeploymentResponse, UploadArchiveResponse,
};
use shuttle_common::models::log::LogsResponse;
use shuttle_common::models::project::{
ProjectCreateRequest, ProjectListResponse, ProjectResponse, ProjectUpdateRequest,
};
use shuttle_common::models::resource::{
ProvisionResourceRequest, ResourceListResponse, ResourceResponse, ResourceType,
};
use shuttle_common::models::{team, user};
use tokio::net::TcpStream;
use tokio_tungstenite::tungstenite::client::IntoClientRequest;
use tokio_tungstenite::{connect_async, MaybeTlsStream, WebSocketStream};
Expand Down Expand Up @@ -79,7 +79,7 @@ impl ShuttleApiClient {
}

pub async fn get_device_auth_ws(&self) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>> {
self.ws_get("/device-auth/ws".to_owned())
self.ws_get("/device-auth/ws")
.await
.with_context(|| "failed to connect to auth endpoint")
}
Expand All @@ -97,8 +97,8 @@ impl ShuttleApiClient {
.context("parsing name check response")
}

pub async fn get_current_user(&self) -> Result<user::UserResponse> {
self.get_json("/users/me".to_owned()).await
pub async fn get_current_user(&self) -> Result<UserResponse> {
self.get_json("/users/me").await
}

pub async fn deploy(
Expand Down Expand Up @@ -248,11 +248,9 @@ impl ShuttleApiClient {
self.delete_json(format!("/projects/{project}")).await
}

async fn _get_teams_list(&self) -> Result<Vec<team::Response>> {
self.get_json("/teams".to_string()).await
}
async fn _get_team_projects_list(&self, team_id: &str) -> Result<ProjectListResponse> {
self.get_json(format!("/teams/{team_id}/projects")).await
#[allow(unused)]
async fn get_teams_list(&self) -> Result<TeamListResponse> {
self.get_json("/teams").await
}

pub async fn get_deployment_logs(
Expand Down Expand Up @@ -307,9 +305,12 @@ impl ShuttleApiClient {
self.put("/users/reset-api-key", Option::<()>::None).await
}

pub async fn ws_get(&self, path: String) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>> {
pub async fn ws_get(
&self,
path: impl AsRef<str>,
) -> Result<WebSocketStream<MaybeTlsStream<TcpStream>>> {
let ws_url = self.api_url.clone().replace("http", "ws");
let url = format!("{ws_url}{path}");
let url = format!("{ws_url}{}", path.as_ref());
let mut req = url.into_client_request()?;

#[cfg(feature = "tracing")]
Expand Down
10 changes: 7 additions & 3 deletions cargo-shuttle/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -771,7 +771,7 @@ impl Shuttle {
.get_current_user()
.await
.context("failed to check API key validity")?;
println!("Logged in as {} ({})", u.name.bold(), u.id.bold());
println!("Logged in as {}", u.id.bold());
}
}

Expand Down Expand Up @@ -1643,17 +1643,21 @@ impl Shuttle {
let pid = self.ctx.project_id();

if !no_confirm {
// check that the project exists, and look up the name
let proj = client.get_project(pid).await?;
println!(
"{}",
formatdoc!(
r#"
WARNING:
Are you sure you want to delete "{pid}"?
Are you sure you want to delete '{}' ({})?
This will...
- Shut down you service.
- Delete any databases and secrets in this project.
- Delete any custom domains linked to this project.
This action is permanent."#
This action is permanent."#,
proj.name,
pid,
)
.bold()
.red()
Expand Down
13 changes: 12 additions & 1 deletion common/src/models/project.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@ pub struct ProjectCreateRequest {
#[typeshare::typeshare]
pub struct ProjectResponse {
pub id: String,
/// Display name
pub name: String,
/// Project owner
pub user_id: String,
pub name: String,
/// Team project belongs to
pub team_id: Option<String>,
pub created_at: DateTime<Utc>,
pub compute_tier: Option<ComputeTier>,
/// State of the current deployment if one exists (something has been deployed).
Expand Down Expand Up @@ -73,7 +76,15 @@ pub struct ProjectListResponse {
#[derive(Debug, Default, Deserialize, Serialize, Clone, PartialEq)]
#[typeshare::typeshare]
pub struct ProjectUpdateRequest {
/// Change display name
pub name: Option<String>,
/// Transfer to other user
pub user_id: Option<String>,
/// Transfer to a team
pub team_id: Option<String>,
/// Transfer away from current team
pub remove_from_team: Option<bool>,
Comment on lines +85 to +86
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: remove_from_team field could be redundant since setting team_id to None could achieve the same result. Consider consolidating the team management logic.

/// Change compute tier
pub compute_tier: Option<ComputeTier>,
Comment on lines 78 to 88
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

logic: These fields could create conflicting states if multiple transfer options are set simultaneously (user_id, team_id, and remove_from_team). Consider adding validation or documenting the precedence rules.

}

Expand Down
57 changes: 38 additions & 19 deletions common/src/models/team.rs
Original file line number Diff line number Diff line change
@@ -1,34 +1,53 @@
use serde::{Deserialize, Serialize};
use strum::{Display, EnumString};

/// Minimal team information
#[derive(Debug, Default, Serialize, Deserialize, PartialEq)]
pub struct Response {
/// Team ID
pub id: String,

/// Name used for display purposes
pub display_name: String,

/// Is this user an admin of the team
pub is_admin: bool,
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[typeshare::typeshare]
pub struct TeamListResponse {
pub teams: Vec<TeamResponse>,
}

/// Member of a team
#[derive(Debug, Serialize, Deserialize, PartialEq)]
pub struct MemberResponse {
/// User ID
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[typeshare::typeshare]
pub struct TeamResponse {
pub id: String,
/// Display name
pub name: String,
/// Membership info of the calling user
pub membership: TeamMembership,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[typeshare::typeshare]
pub struct TeamMembersResponse {
pub members: Vec<TeamMembership>,
}

#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[typeshare::typeshare]
pub struct TeamMembership {
pub user_id: String,
/// Role of the user in the team
pub role: MemberRole,
pub role: TeamRole,
}

/// Role of a user in a team
#[derive(Debug, Serialize, Deserialize, PartialEq, Display, EnumString)]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, Display, EnumString)]
#[serde(rename_all = "lowercase")]
#[strum(serialize_all = "lowercase")]
pub enum MemberRole {
#[typeshare::typeshare]
pub enum TeamRole {
Owner,
Admin,
Member,
}

/// Provide user id to add user.
/// Provide email address to invite user via email.
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
#[typeshare::typeshare]
pub struct AddTeamMemberRequest {
pub user_id: Option<String>,
pub email: Option<String>,
/// Role of the user in the team
pub role: Option<TeamRole>,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

style: Consider making role field required since TeamMembership requires a role. Optional roles could lead to inconsistent states

}
5 changes: 3 additions & 2 deletions common/src/models/user.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,14 @@ use strum::{EnumString, IntoStaticStr};
#[derive(Debug, Deserialize, Serialize)]
#[typeshare::typeshare]
pub struct UserResponse {
pub name: String,
pub id: String,
/// Auth0 id
pub name: String,
pub key: String,
pub account_tier: AccountTier,
pub subscriptions: Vec<Subscription>,
pub has_access_to_beta: Option<bool>,
pub flags: Option<Vec<String>>,
}

impl UserResponse {
Expand All @@ -24,7 +26,6 @@ impl UserResponse {
let mut s = String::new();
writeln!(&mut s, "{}", "Account info:".bold()).unwrap();
writeln!(&mut s, " User Id: {}", self.id).unwrap();
writeln!(&mut s, " Username: {}", self.name).unwrap();
writeln!(&mut s, " Account tier: {}", self.account_tier).unwrap();
writeln!(&mut s, " Subscriptions:").unwrap();
for sub in &self.subscriptions {
Expand Down
56 changes: 54 additions & 2 deletions common/types.ts

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.