use crate::AppService; use crate::error::AppError; use chrono::Utc; use models::WorkspaceRole; use models::workspaces::workspace; use models::workspaces::workspace_membership; use sea_orm::*; use serde::{Deserialize, Serialize}; use session::Session; #[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)] pub struct WorkspaceInitParams { pub slug: String, pub name: String, pub description: Option, } impl AppService { pub async fn workspace_init( &self, ctx: &Session, params: WorkspaceInitParams, ) -> Result { let user_uid = ctx.user().ok_or(AppError::Unauthorized)?; let user = self.utils_find_user_by_uid(user_uid).await?; // Validate slug format: alphanumeric, dashes, underscores if !params .slug .chars() .all(|c| c.is_alphanumeric() || c == '-' || c == '_') { return Err(AppError::BadRequest( "Slug must contain only letters, numbers, hyphens and underscores".to_string(), )); } // Check slug uniqueness if workspace::Entity::find() .filter(workspace::Column::Slug.eq(¶ms.slug)) .filter(workspace::Column::DeletedAt.is_null()) .one(&self.db) .await? .is_some() { return Err(AppError::WorkspaceSlugAlreadyExists); } // Check name uniqueness if workspace::Entity::find() .filter(workspace::Column::Name.eq(¶ms.name)) .filter(workspace::Column::DeletedAt.is_null()) .one(&self.db) .await? .is_some() { return Err(AppError::WorkspaceNameAlreadyExists); } let txn = self.db.begin().await?; let ws = workspace::ActiveModel { id: Set(Uuid::now_v7()), slug: Set(params.slug), name: Set(params.name), description: Set(params.description), avatar_url: Set(None), plan: Set("free".to_string()), billing_email: Set(None), stripe_customer_id: Set(None), stripe_subscription_id: Set(None), plan_expires_at: Set(None), deleted_at: Set(None), created_at: Set(Utc::now()), updated_at: Set(Utc::now()), }; let ws = ws.insert(&txn).await?; let membership = workspace_membership::ActiveModel { id: Default::default(), workspace_id: Set(ws.id), user_id: Set(user.uid), role: Set(WorkspaceRole::Owner.to_string()), status: Set("active".to_string()), invited_by: Set(None), joined_at: Set(Utc::now()), invite_token: Set(None), invite_expires_at: Set(None), }; membership.insert(&txn).await?; txn.commit().await?; Ok(ws) } } use uuid::Uuid;