Split admin workspaces service module
This commit is contained in:
@@ -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,
|
||||||
|
|||||||
@@ -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 })
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user