gitdataai/libs/service/project/init.rs

116 lines
3.9 KiB
Rust

use crate::AppService;
use crate::error::AppError;
use chrono::{DateTime, Utc};
use models::projects::{MemberRole, project, project_audit_log, project_members};
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 display_name: Option<String>,
pub description: Option<String>,
pub is_public: bool,
}
#[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 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?;
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.display_name.unwrap_or(params.name.clone())),
avatar_url: Set(None),
description: Set(params.description),
is_public: Set(params.is_public),
created_by: Set(user.uid),
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 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?;
observability::incr!(observability::PROJECTS_CREATED_TOTAL);
// Initialize project billing ($20 for first project, $0 otherwise)
if let Err(e) =
agent::billing::initialize_project_billing(&self.db, _project.id, user.uid).await
{
tracing::warn!(project_id = %_project.id, error = %e, "Failed to initialize project billing — non-critical, continuing");
}
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,
created_by: _project.created_by,
created_at: _project.created_at,
updated_at: _project.updated_at,
},
})
}
}