141 lines
4.9 KiB
Rust
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 ¶ms.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,
|
|
},
|
|
})
|
|
}
|
|
}
|