From b22ed048f2f39bc15717180c6590d02fc3c2613b Mon Sep 17 00:00:00 2001 From: github-ops Date: Sun, 21 Jun 2026 02:04:41 +0000 Subject: [PATCH] Split admin workspaces service module --- apps/admin-api/src/service.rs | 142 ++-------------------- apps/admin-api/src/service/workspaces.rs | 143 +++++++++++++++++++++++ 2 files changed, 151 insertions(+), 134 deletions(-) create mode 100644 apps/admin-api/src/service/workspaces.rs diff --git a/apps/admin-api/src/service.rs b/apps/admin-api/src/service.rs index c8ff195..01613a1 100644 --- a/apps/admin-api/src/service.rs +++ b/apps/admin-api/src/service.rs @@ -13,17 +13,17 @@ use crank_core::{ PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, PolicyEngine, ProductEdition, Protocol, ResponseCachePolicy, SampleId, Samples, SecretKind, Target, ToolQualityMappingRule, ToolQualityMappingSet, ToolQualitySchemaKind, ToolQualitySchemaNode, UsagePeriod, - UserSessionId, WizardState, Workspace, WorkspaceId, WorkspaceStatus, + UserSessionId, WizardState, WorkspaceId, WorkspaceStatus, }; use crank_mapping::{JsonPathRoot, MappingRule, MappingSet, infer_mapping_from_samples}; use crank_registry::{ AgentSummary, CreateInvocationLogRequest, CreatePlatformApiKeyRequest, CreateVersionRequest, - CreateWorkspaceRequest, ListInvocationLogsQuery, OperationAgentRef, OperationSampleMetadata, - OperationSummary, OperationUsageSummary, OperationVersionRecord, PlatformApiKeyRecord, - PostgresRegistry, PublishRequest, RegistryOperation, SampleKind, SaveSampleMetadataRequest, - SaveWorkspaceUpstreamRequest, UpdateWorkspaceRequest, UsageAgentBreakdown, UsageBucket, - UsageOperationBreakdown, UsageSummary, UsageTimelinePoint, WorkspaceMembershipRecord, - WorkspaceRecord, WorkspaceUpstream, WorkspaceUpstreamId, + ListInvocationLogsQuery, OperationAgentRef, OperationSampleMetadata, OperationSummary, + OperationUsageSummary, OperationVersionRecord, PlatformApiKeyRecord, PostgresRegistry, + PublishRequest, RegistryOperation, SampleKind, SaveSampleMetadataRequest, + SaveWorkspaceUpstreamRequest, UsageAgentBreakdown, UsageBucket, UsageOperationBreakdown, + UsageSummary, UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord, + WorkspaceUpstream, WorkspaceUpstreamId, }; use crank_runtime::{ PreparedRequest, ResolvedAuth, RuntimeError, RuntimeExecutor, RuntimeOperation, @@ -41,6 +41,7 @@ mod agents; mod import_export; mod observability; mod secrets; +mod workspaces; use crate::{ auth::{ @@ -794,55 +795,6 @@ impl AdminService { Ok(()) } - pub async fn list_workspaces_for_user( - &self, - user_id: &crank_core::UserId, - ) -> Result, ApiError> { - Ok(self.registry.list_workspaces_for_user(user_id).await?) - } - - pub async fn user_has_workspace_access( - &self, - user_id: &crank_core::UserId, - workspace_id: &WorkspaceId, - ) -> Result { - Ok(self - .registry - .user_has_workspace_access(user_id, workspace_id) - .await?) - } - - #[instrument(skip(self, payload), fields(workspace_slug = %payload.slug, user_id = %user_id.as_str()))] - pub async fn create_workspace( - &self, - user_id: &crank_core::UserId, - payload: WorkspacePayload, - ) -> Result { - let now = OffsetDateTime::now_utc(); - let workspace = Workspace { - id: WorkspaceId::new(new_prefixed_id("ws")), - slug: payload.slug, - display_name: payload.display_name, - status: WorkspaceStatus::Active, - settings: payload.settings, - created_at: now, - updated_at: now, - }; - - self.registry - .create_workspace(CreateWorkspaceRequest { - workspace: &workspace, - }) - .await?; - self.registry - .ensure_membership(&workspace.id, user_id, MembershipRole::Owner) - .await?; - self.ensure_default_workspace_upstreams(&workspace.id) - .await?; - - Ok(WorkspaceRecord { workspace }) - } - #[instrument(skip(self))] pub async fn session_response( &self, @@ -883,43 +835,6 @@ impl AdminService { }) } - pub async fn set_current_workspace( - &self, - session_id: &UserSessionId, - user_id: &crank_core::UserId, - workspace_id: &WorkspaceId, - ) -> Result { - if !self - .user_has_workspace_access(user_id, workspace_id) - .await? - { - return Err(ApiError::forbidden("workspace access denied")); - } - - self.registry - .set_user_session_current_workspace(session_id, workspace_id) - .await?; - - let user = self - .registry - .get_auth_user_by_id(user_id) - .await? - .ok_or_else(|| { - ApiError::not_found_with_context( - format!("user {} was not found", user_id.as_str()), - json!({ "user_id": user_id.as_str() }), - ) - })? - .user; - let memberships = self.registry.list_workspaces_for_user(user_id).await?; - - Ok(SessionResponse { - user, - memberships, - current_workspace_id: Some(workspace_id.as_str().to_owned()), - }) - } - pub async fn change_password( &self, user_id: &crank_core::UserId, @@ -959,47 +874,6 @@ impl AdminService { Ok(()) } - pub async fn get_workspace( - &self, - workspace_id: &WorkspaceId, - ) -> Result { - self.registry - .get_workspace(workspace_id) - .await? - .ok_or_else(|| { - ApiError::not_found_with_context( - format!("workspace {} was not found", workspace_id.as_str()), - json!({ "workspace_id": workspace_id.as_str() }), - ) - }) - } - - #[instrument(skip(self, payload), fields(workspace_id = %workspace_id.as_str()))] - pub async fn update_workspace( - &self, - workspace_id: &WorkspaceId, - payload: UpdateWorkspacePayload, - ) -> Result { - let existing = self.get_workspace(workspace_id).await?.workspace; - let workspace = Workspace { - id: existing.id, - slug: payload.slug.unwrap_or(existing.slug), - display_name: payload.display_name.unwrap_or(existing.display_name), - status: payload.status.unwrap_or(existing.status), - settings: payload.settings.unwrap_or(existing.settings), - created_at: existing.created_at, - updated_at: OffsetDateTime::now_utc(), - }; - - self.registry - .update_workspace(UpdateWorkspaceRequest { - workspace: &workspace, - }) - .await?; - - Ok(WorkspaceRecord { workspace }) - } - pub async fn export_workspace( &self, workspace_id: &WorkspaceId, diff --git a/apps/admin-api/src/service/workspaces.rs b/apps/admin-api/src/service/workspaces.rs new file mode 100644 index 0000000..cbf8e7f --- /dev/null +++ b/apps/admin-api/src/service/workspaces.rs @@ -0,0 +1,143 @@ +use crank_core::{MembershipRole, UserSessionId, Workspace, WorkspaceId, WorkspaceStatus}; +use crank_registry::{ + CreateWorkspaceRequest, UpdateWorkspaceRequest, WorkspaceMembershipRecord, WorkspaceRecord, +}; +use serde_json::json; +use time::OffsetDateTime; +use tracing::instrument; + +use crate::{ + error::ApiError, + service::{ + AdminService, SessionResponse, UpdateWorkspacePayload, WorkspacePayload, new_prefixed_id, + }, +}; + +impl AdminService { + pub async fn list_workspaces_for_user( + &self, + user_id: &crank_core::UserId, + ) -> Result, ApiError> { + Ok(self.registry.list_workspaces_for_user(user_id).await?) + } + + pub async fn user_has_workspace_access( + &self, + user_id: &crank_core::UserId, + workspace_id: &WorkspaceId, + ) -> Result { + Ok(self + .registry + .user_has_workspace_access(user_id, workspace_id) + .await?) + } + + #[instrument(skip(self, payload), fields(workspace_slug = %payload.slug, user_id = %user_id.as_str()))] + pub async fn create_workspace( + &self, + user_id: &crank_core::UserId, + payload: WorkspacePayload, + ) -> Result { + let now = OffsetDateTime::now_utc(); + let workspace = Workspace { + id: WorkspaceId::new(new_prefixed_id("ws")), + slug: payload.slug, + display_name: payload.display_name, + status: WorkspaceStatus::Active, + settings: payload.settings, + created_at: now, + updated_at: now, + }; + + self.registry + .create_workspace(CreateWorkspaceRequest { + workspace: &workspace, + }) + .await?; + self.registry + .ensure_membership(&workspace.id, user_id, MembershipRole::Owner) + .await?; + self.ensure_default_workspace_upstreams(&workspace.id) + .await?; + + Ok(WorkspaceRecord { workspace }) + } + + pub async fn set_current_workspace( + &self, + session_id: &UserSessionId, + user_id: &crank_core::UserId, + workspace_id: &WorkspaceId, + ) -> Result { + if !self + .user_has_workspace_access(user_id, workspace_id) + .await? + { + return Err(ApiError::forbidden("workspace access denied")); + } + + self.registry + .set_user_session_current_workspace(session_id, workspace_id) + .await?; + + let user = self + .registry + .get_auth_user_by_id(user_id) + .await? + .ok_or_else(|| { + ApiError::not_found_with_context( + format!("user {} was not found", user_id.as_str()), + json!({ "user_id": user_id.as_str() }), + ) + })? + .user; + let memberships = self.registry.list_workspaces_for_user(user_id).await?; + + Ok(SessionResponse { + user, + memberships, + current_workspace_id: Some(workspace_id.as_str().to_owned()), + }) + } + + pub async fn get_workspace( + &self, + workspace_id: &WorkspaceId, + ) -> Result { + self.registry + .get_workspace(workspace_id) + .await? + .ok_or_else(|| { + ApiError::not_found_with_context( + format!("workspace {} was not found", workspace_id.as_str()), + json!({ "workspace_id": workspace_id.as_str() }), + ) + }) + } + + #[instrument(skip(self, payload), fields(workspace_id = %workspace_id.as_str()))] + pub async fn update_workspace( + &self, + workspace_id: &WorkspaceId, + payload: UpdateWorkspacePayload, + ) -> Result { + let existing = self.get_workspace(workspace_id).await?.workspace; + let workspace = Workspace { + id: existing.id, + slug: payload.slug.unwrap_or(existing.slug), + display_name: payload.display_name.unwrap_or(existing.display_name), + status: payload.status.unwrap_or(existing.status), + settings: payload.settings.unwrap_or(existing.settings), + created_at: existing.created_at, + updated_at: OffsetDateTime::now_utc(), + }; + + self.registry + .update_workspace(UpdateWorkspaceRequest { + workspace: &workspace, + }) + .await?; + + Ok(WorkspaceRecord { workspace }) + } +}