feat: add swimlanes

Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
2026-03-07 22:53:48 +01:00
parent 9fe1630986
commit 45353089c2
51 changed files with 3845 additions and 147 deletions

View File

@@ -2,8 +2,8 @@ use forage_core::auth::{
AuthError, AuthTokens, CreatedToken, ForestAuth, PersonalAccessToken, User, UserEmail,
};
use forage_core::platform::{
Artifact, ArtifactContext, ArtifactDestination, ArtifactSource, ForestPlatform, Organisation,
OrgMember, PlatformError,
Artifact, ArtifactContext, ArtifactDestination, ArtifactSource, Destination, ForestPlatform,
Organisation, OrgMember, PlatformError,
};
use forage_grpc::organisation_service_client::OrganisationServiceClient;
use forage_grpc::release_service_client::ReleaseServiceClient;
@@ -274,6 +274,103 @@ impl ForestAuth for GrpcForestClient {
.map_err(map_status)?;
Ok(())
}
async fn update_username(
&self,
access_token: &str,
user_id: &str,
new_username: &str,
) -> Result<User, AuthError> {
let req = Self::authed_request(
access_token,
forage_grpc::UpdateUserRequest {
user_id: user_id.into(),
username: Some(new_username.into()),
},
)?;
let resp = self
.client()
.update_user(req)
.await
.map_err(map_status)?
.into_inner();
let user = resp.user.ok_or(AuthError::Other("no user in response".into()))?;
Ok(convert_user(user))
}
async fn change_password(
&self,
access_token: &str,
user_id: &str,
current_password: &str,
new_password: &str,
) -> Result<(), AuthError> {
let req = Self::authed_request(
access_token,
forage_grpc::ChangePasswordRequest {
user_id: user_id.into(),
current_password: current_password.into(),
new_password: new_password.into(),
},
)?;
self.client()
.change_password(req)
.await
.map_err(map_status)?;
Ok(())
}
async fn add_email(
&self,
access_token: &str,
user_id: &str,
email: &str,
) -> Result<UserEmail, AuthError> {
let req = Self::authed_request(
access_token,
forage_grpc::AddEmailRequest {
user_id: user_id.into(),
email: email.into(),
},
)?;
let resp = self
.client()
.add_email(req)
.await
.map_err(map_status)?
.into_inner();
let email = resp.email.ok_or(AuthError::Other("no email in response".into()))?;
Ok(UserEmail {
email: email.email,
verified: email.verified,
})
}
async fn remove_email(
&self,
access_token: &str,
user_id: &str,
email: &str,
) -> Result<(), AuthError> {
let req = Self::authed_request(
access_token,
forage_grpc::RemoveEmailRequest {
user_id: user_id.into(),
email: email.into(),
},
)?;
self.client()
.remove_email(req)
.await
.map_err(map_status)?;
Ok(())
}
}
fn convert_organisations(
@@ -307,6 +404,21 @@ fn convert_artifact(a: forage_grpc::Artifact) -> Artifact {
.map(|d| ArtifactDestination {
name: d.name,
environment: d.environment,
type_organisation: if d.type_organisation.is_empty() {
None
} else {
Some(d.type_organisation)
},
type_name: if d.type_name.is_empty() {
None
} else {
Some(d.type_name)
},
type_version: if d.type_version == 0 {
None
} else {
Some(d.type_version)
},
})
.collect();
Artifact {
@@ -319,6 +431,8 @@ fn convert_artifact(a: forage_grpc::Artifact) -> Artifact {
} else {
ctx.description
},
web: ctx.web.filter(|v| !v.is_empty()),
pr: ctx.pr.filter(|v| !v.is_empty()),
},
source,
git_ref: None,
@@ -548,6 +662,40 @@ impl ForestPlatform for GrpcForestClient {
.ok_or(PlatformError::Other("no member in response".into()))?;
Ok(convert_member(member))
}
async fn get_artifact_by_slug(
&self,
access_token: &str,
slug: &str,
) -> Result<Artifact, PlatformError> {
let req = platform_authed_request(
access_token,
forage_grpc::GetArtifactBySlugRequest {
slug: slug.into(),
},
)?;
let resp = self
.release_client()
.get_artifact_by_slug(req)
.await
.map_err(map_platform_status)?
.into_inner();
let artifact = resp
.artifact
.ok_or(PlatformError::NotFound("artifact not found".into()))?;
Ok(convert_artifact(artifact))
}
async fn list_destinations(
&self,
_access_token: &str,
_organisation: &str,
) -> Result<Vec<Destination>, PlatformError> {
// DestinationService client not yet generated; return empty for now
Ok(vec![])
}
}
#[cfg(test)]