gitdataai/libs/service/project/init.rs
2026-04-14 19:02:01 +08:00

141 lines
4.9 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use chrono::{DateTime, Utc};
use models::Decimal;
use models::projects::{MemberRole, project, project_audit_log, project_billing, project_members};
use models::workspaces::workspace_membership;
use sea_orm::*;
use serde::{Deserialize, Serialize};
use session::Session;
use uuid::Uuid;
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct ProjectInitParams {
pub name: String,
pub description: Option<String>,
pub is_public: bool,
/// Optional workspace slug to associate this project with.
pub workspace_slug: Option<String>,
}
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct ProjectInitResponse {
pub params: ProjectInitParams,
pub project: ProjectModel,
}
#[derive(Deserialize, Serialize, Clone, Debug, utoipa::ToSchema)]
pub struct ProjectModel {
pub uid: Uuid,
pub name: String,
pub display_name: String,
pub avatar_url: Option<String>,
pub description: Option<String>,
pub is_public: bool,
pub workspace_id: Option<Uuid>,
pub created_by: Uuid,
pub created_at: DateTime<Utc>,
pub updated_at: DateTime<Utc>,
}
impl AppService {
pub async fn project_init(
&self,
ctx: &Session,
params: ProjectInitParams,
) -> Result<ProjectInitResponse, AppError> {
let inner = params.clone();
if let Ok(_) = self.utils_find_project_by_name(params.name.clone()).await {
return Err(AppError::ProjectNameAlreadyExists);
}
let user = ctx.user().ok_or(AppError::Unauthorized)?;
let user = self.utils_find_user_by_uid(user).await?;
// Resolve workspace if provided
let workspace_id = match &params.workspace_slug {
Some(slug) => {
let ws = self.utils_find_workspace_by_slug(slug.clone()).await?;
let membership = workspace_membership::Entity::find()
.filter(workspace_membership::Column::WorkspaceId.eq(ws.id))
.filter(workspace_membership::Column::UserId.eq(user.uid))
.filter(workspace_membership::Column::Status.eq("active"))
.one(&self.db)
.await?;
if membership.is_none() {
return Err(AppError::NotWorkspaceMember);
}
Some(ws.id)
}
None => None,
};
let project_uid = Uuid::now_v7();
let txn = self.db.begin().await?;
let project = project::ActiveModel {
id: Set(project_uid),
name: Set(params.name.clone()),
display_name: Set(params.name),
avatar_url: Set(None),
description: Set(params.description),
is_public: Set(params.is_public),
created_by: Set(user.uid),
workspace_id: Set(workspace_id),
created_at: Set(Utc::now()),
updated_at: Set(Utc::now()),
};
let _project = project.insert(&txn).await?;
let project_member = project_members::ActiveModel {
id: Default::default(),
project: Set(_project.id),
user: Set(user.uid),
scope: Set(MemberRole::Owner.to_string()),
joined_at: Set(Utc::now()),
};
project_member.insert(&txn).await?;
let billing = project_billing::ActiveModel {
project: Set(_project.id),
balance: Set(Decimal::from(200i64)),
currency: Set("USD".to_string()),
user: Set(Some(user.uid)),
updated_at: Set(Utc::now()),
created_at: Set(Utc::now()),
..Default::default()
};
billing.insert(&txn).await?;
let log = project_audit_log::ActiveModel {
project: Set(_project.id),
actor: Set(user.uid),
action: Set("project_create".to_string()),
details: Set(Some(serde_json::json!({
"project_name": _project.name.clone(),
"project_uid": _project.id,
"is_public": _project.is_public,
"description": _project.description.clone(),
}))),
created_at: Set(Utc::now()),
..Default::default()
};
log.insert(&txn).await?;
txn.commit().await?;
Ok(ProjectInitResponse {
params: inner,
project: ProjectModel {
uid: _project.id,
name: _project.name.clone(),
display_name: _project.display_name.clone(),
avatar_url: _project.avatar_url.clone(),
description: _project.description.clone(),
is_public: _project.is_public,
workspace_id: _project.workspace_id,
created_by: _project.created_by,
created_at: _project.created_at,
updated_at: _project.updated_at,
},
})
}
}