use kotoba_core::types::{Result, Value};
use kotoba_core::prelude::KotobaError;
use kotoba_deploy_core::*;
use serde::Deserialize;
use std::collections::HashMap;
use std::sync::{Arc, RwLock};
use std::time::SystemTime;
use chrono::{DateTime, Utc};
#[derive(Debug)]
pub struct GitIntegration {
github_config: GitHubConfig,
webhook_handler: WebhookHandler,
auto_deploy_manager: AutoDeployManager,
deployment_history: Arc<RwLock<Vec<DeploymentRecord>>>,
}
#[derive(Debug, Clone)]
pub struct GitHubConfig {
pub owner: String,
pub repo: String,
pub access_token: String,
pub webhook_secret: Option<String>,
pub branches: Vec<String>,
pub auto_deploy_enabled: bool,
}
#[derive(Debug)]
pub struct WebhookHandler {
pub active_webhooks: Arc<RwLock<HashMap<String, WebhookInfo>>>,
pub event_queue: Arc<RwLock<Vec<GitHubEvent>>>,
}
#[derive(Debug, Clone)]
pub struct AutoDeployManager {
pub deploy_scripts: HashMap<String, DeployScript>,
pub build_configs: HashMap<String, BuildConfig>,
pub deploy_conditions: Vec<DeployCondition>,
}
#[derive(Debug, Clone)]
pub struct WebhookInfo {
pub id: String,
pub url: String,
pub events: Vec<String>,
pub active: bool,
pub created_at: SystemTime,
}
#[derive(Debug, Clone)]
pub struct GitHubEvent {
pub event_type: String,
pub payload: Value,
pub received_at: SystemTime,
pub signature: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PushEventPayload {
#[serde(rename = "ref")]
pub ref_field: String,
pub commits: Vec<CommitInfo>,
pub sender: UserInfo,
pub repository: RepositoryInfo,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PullRequestEventPayload {
pub action: String,
pub number: u32,
pub pull_request: PullRequestInfo,
pub repository: RepositoryInfo,
}
#[derive(Debug, Clone, Deserialize)]
pub struct CommitInfo {
pub id: String,
pub message: String,
pub timestamp: String,
pub author: UserInfo,
}
#[derive(Debug, Clone, Deserialize)]
pub struct UserInfo {
pub id: u32,
pub login: String,
pub name: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
pub struct RepositoryInfo {
pub id: u32,
pub full_name: String,
pub name: String,
pub owner: UserInfo,
}
#[derive(Debug, Clone, Deserialize)]
pub struct PullRequestInfo {
pub id: u32,
pub number: u32,
pub title: String,
pub body: Option<String>,
pub state: String,
pub merged: bool,
pub merge_commit_sha: Option<String>,
pub head: BranchInfo,
}
#[derive(Debug, Clone, Deserialize)]
pub struct BranchInfo {
#[serde(rename = "ref")]
pub ref_field: String,
pub sha: String,
}
#[derive(Debug, Clone)]
pub struct DeployScript {
pub name: String,
pub content: String,
pub triggers: Vec<ScriptTrigger>,
}
#[derive(Debug, Clone)]
pub struct BuildConfig {
pub name: String,
pub build_command: String,
pub output_dir: String,
pub environment: HashMap<String, String>,
}
#[derive(Debug, Clone)]
pub struct DeployCondition {
pub name: String,
pub condition_type: ConditionType,
pub value: String,
}
#[derive(Debug, Clone)]
pub enum ConditionType {
Branch,
Tag,
FileChanged,
Custom,
}
#[derive(Debug, Clone)]
pub enum ScriptTrigger {
Push,
PullRequest,
Tag,
Manual,
}
#[derive(Debug, Clone)]
pub struct DeploymentRecord {
pub id: String,
pub deployment_name: String,
pub commit_id: String,
pub branch: String,
pub trigger_event: String,
pub status: DeploymentStatus,
pub started_at: DateTime<Utc>,
pub completed_at: Option<DateTime<Utc>>,
pub logs: Vec<String>,
}
impl GitIntegration {
pub fn new(github_config: GitHubConfig) -> Self {
Self {
webhook_handler: WebhookHandler {
active_webhooks: Arc::new(RwLock::new(HashMap::new())),
event_queue: Arc::new(RwLock::new(Vec::new())),
},
auto_deploy_manager: AutoDeployManager {
deploy_scripts: HashMap::new(),
build_configs: HashMap::new(),
deploy_conditions: Vec::new(),
},
deployment_history: Arc::new(RwLock::new(Vec::new())),
github_config,
}
}
pub fn github_config(&self) -> &GitHubConfig {
&self.github_config
}
pub fn webhook_handler(&self) -> &WebhookHandler {
&self.webhook_handler
}
pub fn auto_deploy_manager(&self) -> &AutoDeployManager {
&self.auto_deploy_manager
}
pub fn deployment_history(&self) -> Arc<RwLock<Vec<DeploymentRecord>>> {
Arc::clone(&self.deployment_history)
}
pub async fn process_webhook(&self, event: GitHubEvent) -> Result<()> {
{
let mut queue = self.webhook_handler.event_queue.write().unwrap();
queue.push(event.clone());
}
if self.github_config.auto_deploy_enabled {
self.process_auto_deploy(event).await?;
}
Ok(())
}
async fn process_auto_deploy(&self, event: GitHubEvent) -> Result<()> {
match event.event_type.as_str() {
"push" => {
if let Ok(json_value) = serde_json::to_value(&event.payload) {
if let Ok(payload) = serde_json::from_value::<PushEventPayload>(json_value) {
self.handle_push_event(payload).await?;
}
}
}
"pull_request" => {
if let Ok(json_value) = serde_json::to_value(&event.payload) {
if let Ok(payload) = serde_json::from_value::<PullRequestEventPayload>(json_value) {
self.handle_pull_request_event(payload).await?;
}
}
}
_ => {
}
}
Ok(())
}
async fn handle_push_event(&self, payload: PushEventPayload) -> Result<()> {
let branch = payload.ref_field.strip_prefix("refs/heads/").unwrap_or(&payload.ref_field);
if !self.github_config.branches.contains(&branch.to_string()) {
return Ok(());
}
if !self.check_deploy_conditions(&payload) {
return Ok(());
}
let record = DeploymentRecord {
id: format!("deploy-{}", Utc::now().timestamp()),
deployment_name: format!("{}-{}", self.github_config.repo, branch),
commit_id: payload.commits.last().map(|c| c.id.clone()).unwrap_or_default(),
branch: branch.to_string(),
trigger_event: "push".to_string(),
status: DeploymentStatus::Creating,
started_at: Utc::now(),
completed_at: None,
logs: vec!["Starting deployment from push event".to_string()],
};
{
let mut history = self.deployment_history.write().unwrap();
history.push(record);
}
Ok(())
}
async fn handle_pull_request_event(&self, payload: PullRequestEventPayload) -> Result<()> {
if payload.action == "closed" && payload.pull_request.merged {
let branch = payload.pull_request.head.ref_field.clone();
if !self.github_config.branches.contains(&branch) {
return Ok(());
}
let record = DeploymentRecord {
id: format!("deploy-pr-{}", payload.number),
deployment_name: format!("{}-pr-{}", self.github_config.repo, payload.number),
commit_id: payload.pull_request.merge_commit_sha.unwrap_or_default(),
branch,
trigger_event: "pull_request_merged".to_string(),
status: DeploymentStatus::Creating,
started_at: Utc::now(),
completed_at: None,
logs: vec!["Starting deployment from merged pull request".to_string()],
};
{
let mut history = self.deployment_history.write().unwrap();
history.push(record);
}
}
Ok(())
}
fn check_deploy_conditions(&self, payload: &PushEventPayload) -> bool {
for condition in &self.auto_deploy_manager.deploy_conditions {
match condition.condition_type {
ConditionType::Branch => {
let branch = payload.ref_field.strip_prefix("refs/heads/").unwrap_or(&payload.ref_field);
if branch != condition.value {
return false;
}
}
ConditionType::FileChanged => {
if !payload.commits.iter().any(|commit| {
commit.message.contains(&condition.value)
}) {
return false;
}
}
_ => {
}
}
}
true
}
pub fn verify_webhook_signature(&self, payload: &str, signature: &str) -> Result<bool> {
if let Some(secret) = &self.github_config.webhook_secret {
use hmac::{Hmac, Mac};
use sha2::Sha256;
let mut mac = Hmac::<Sha256>::new_from_slice(secret.as_bytes())
.map_err(|_| KotobaError::InvalidArgument("Invalid secret key length".to_string()))?;
mac.update(payload.as_bytes());
let expected_signature = format!("sha256={}", hex::encode(mac.finalize().into_bytes()));
Ok(signature == expected_signature)
} else {
Ok(false)
}
}
}
impl Default for GitHubConfig {
fn default() -> Self {
Self {
owner: "default".to_string(),
repo: "default".to_string(),
access_token: "".to_string(),
webhook_secret: None,
branches: vec!["main".to_string()],
auto_deploy_enabled: false,
}
}
}
impl WebhookHandler {
pub fn new() -> Self {
Self {
active_webhooks: Arc::new(RwLock::new(HashMap::new())),
event_queue: Arc::new(RwLock::new(Vec::new())),
}
}
pub async fn register_webhook(&self, webhook_info: WebhookInfo) -> Result<()> {
let mut webhooks = self.active_webhooks.write().unwrap();
webhooks.insert(webhook_info.id.clone(), webhook_info);
Ok(())
}
pub async fn unregister_webhook(&self, webhook_id: &str) -> Result<()> {
let mut webhooks = self.active_webhooks.write().unwrap();
webhooks.remove(webhook_id);
Ok(())
}
}
impl Default for WebhookHandler {
fn default() -> Self {
Self::new()
}
}
impl AutoDeployManager {
pub fn new() -> Self {
Self {
deploy_scripts: HashMap::new(),
build_configs: HashMap::new(),
deploy_conditions: Vec::new(),
}
}
pub fn add_deploy_script(&mut self, script: DeployScript) {
self.deploy_scripts.insert(script.name.clone(), script);
}
pub fn add_build_config(&mut self, config: BuildConfig) {
self.build_configs.insert(config.name.clone(), config);
}
pub fn add_deploy_condition(&mut self, condition: DeployCondition) {
self.deploy_conditions.push(condition);
}
}
impl Default for AutoDeployManager {
fn default() -> Self {
Self::new()
}
}
pub use GitIntegration as GitManager;
pub use GitHubConfig as GitConfig;
pub use WebhookHandler as WebhookManager;
pub use AutoDeployManager as AutoDeploy;
pub use DeploymentRecord as DeployRecord;