Split admin workspaces service module
Deploy / deploy (push) Successful in 1m36s
CI / Rust Checks (push) Failing after 5m3s
CI / UI Checks (push) Has been skipped
CI / Frontend E2E (push) Has been skipped
CI / Deployment Manifests (push) Has been skipped

This commit is contained in:
github-ops
2026-06-21 02:04:41 +00:00
parent 11874eb16e
commit b22ed048f2
2 changed files with 151 additions and 134 deletions
+8 -134
View File
@@ -13,17 +13,17 @@ use crank_core::{
PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, PolicyEngine, ProductEdition, PlatformApiKeyId, PlatformApiKeyScope, PlatformApiKeyStatus, PolicyEngine, ProductEdition,
Protocol, ResponseCachePolicy, SampleId, Samples, SecretKind, Target, ToolQualityMappingRule, Protocol, ResponseCachePolicy, SampleId, Samples, SecretKind, Target, ToolQualityMappingRule,
ToolQualityMappingSet, ToolQualitySchemaKind, ToolQualitySchemaNode, UsagePeriod, 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_mapping::{JsonPathRoot, MappingRule, MappingSet, infer_mapping_from_samples};
use crank_registry::{ use crank_registry::{
AgentSummary, CreateInvocationLogRequest, CreatePlatformApiKeyRequest, CreateVersionRequest, AgentSummary, CreateInvocationLogRequest, CreatePlatformApiKeyRequest, CreateVersionRequest,
CreateWorkspaceRequest, ListInvocationLogsQuery, OperationAgentRef, OperationSampleMetadata, ListInvocationLogsQuery, OperationAgentRef, OperationSampleMetadata, OperationSummary,
OperationSummary, OperationUsageSummary, OperationVersionRecord, PlatformApiKeyRecord, OperationUsageSummary, OperationVersionRecord, PlatformApiKeyRecord, PostgresRegistry,
PostgresRegistry, PublishRequest, RegistryOperation, SampleKind, SaveSampleMetadataRequest, PublishRequest, RegistryOperation, SampleKind, SaveSampleMetadataRequest,
SaveWorkspaceUpstreamRequest, UpdateWorkspaceRequest, UsageAgentBreakdown, UsageBucket, SaveWorkspaceUpstreamRequest, UsageAgentBreakdown, UsageBucket, UsageOperationBreakdown,
UsageOperationBreakdown, UsageSummary, UsageTimelinePoint, WorkspaceMembershipRecord, UsageSummary, UsageTimelinePoint, WorkspaceMembershipRecord, WorkspaceRecord,
WorkspaceRecord, WorkspaceUpstream, WorkspaceUpstreamId, WorkspaceUpstream, WorkspaceUpstreamId,
}; };
use crank_runtime::{ use crank_runtime::{
PreparedRequest, ResolvedAuth, RuntimeError, RuntimeExecutor, RuntimeOperation, PreparedRequest, ResolvedAuth, RuntimeError, RuntimeExecutor, RuntimeOperation,
@@ -41,6 +41,7 @@ mod agents;
mod import_export; mod import_export;
mod observability; mod observability;
mod secrets; mod secrets;
mod workspaces;
use crate::{ use crate::{
auth::{ auth::{
@@ -794,55 +795,6 @@ impl AdminService {
Ok(()) Ok(())
} }
pub async fn list_workspaces_for_user(
&self,
user_id: &crank_core::UserId,
) -> Result<Vec<WorkspaceMembershipRecord>, 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<bool, ApiError> {
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<WorkspaceRecord, ApiError> {
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))] #[instrument(skip(self))]
pub async fn session_response( pub async fn session_response(
&self, &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<SessionResponse, ApiError> {
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( pub async fn change_password(
&self, &self,
user_id: &crank_core::UserId, user_id: &crank_core::UserId,
@@ -959,47 +874,6 @@ impl AdminService {
Ok(()) Ok(())
} }
pub async fn get_workspace(
&self,
workspace_id: &WorkspaceId,
) -> Result<WorkspaceRecord, ApiError> {
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<WorkspaceRecord, ApiError> {
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( pub async fn export_workspace(
&self, &self,
workspace_id: &WorkspaceId, workspace_id: &WorkspaceId,
+143
View File
@@ -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<Vec<WorkspaceMembershipRecord>, 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<bool, ApiError> {
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<WorkspaceRecord, ApiError> {
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<SessionResponse, ApiError> {
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<WorkspaceRecord, ApiError> {
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<WorkspaceRecord, ApiError> {
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 })
}
}