194c44c
use models::repo::repo_star;
use models::repo::repos;
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, QueryOrder, QuerySelect, Set,
TransactionTrait, Unchanged,
};
use serde::{Deserialize, Serialize};
use time::OffsetDateTime;
use utoipa::ToSchema;
use uuid::Uuid;
use crate::context::facade::GitContext;
use crate::service::GitService;
use crate::share::errors::GitError;
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct RepoStarDto {
pub user_id: i64,
pub repo_id: Uuid,
pub created_at: OffsetDateTime,
}
#[derive(Debug, Clone, Serialize, Deserialize, ToSchema)]
pub struct RepoStarResp {
pub stars: i32,
}
#[derive(Debug, Clone, Serialize, ToSchema)]
pub struct StargazerDto {
pub user_id: i64,
pub username: String,
pub display_name: String,
pub avatar_url: String,
pub starred_at: OffsetDateTime,
}
impl GitService {
pub async fn star_repo(
&self,
ctx: &GitContext,
user_id: i64,
) -> Result<RepoStarResp, GitError> {
let db = self.db_write();
let repo_id = ctx.model.id;
let now = OffsetDateTime::now_utc();
let existing = repo_star::Entity::find()
.filter(repo_star::Column::UserId.eq(user_id))
.filter(repo_star::Column::RepoId.eq(repo_id))
.one(db)
.await?;
if existing.is_none() {
let row = repo_star::ActiveModel {
id: Set(Uuid::now_v7()),
user_id: Set(user_id),
repo_id: Set(repo_id),
created_at: Set(now),
updated_at: Set(now),
};
row.insert(db).await?;
}
let after = repo_star::Entity::find()
.filter(repo_star::Column::RepoId.eq(repo_id))
.all(db)
.await?
.len() as i32;
let am = repos::ActiveModel {
id: Unchanged(repo_id),
nums_star: Set(after),
updated_at: Set(now),
..Default::default()
};
am.update(db).await?;
Ok(RepoStarResp { stars: after })
}
pub async fn unstar_repo(
&self,
ctx: &GitContext,
user_id: i64,
) -> Result<RepoStarResp, GitError> {
let db = self.db_write();
let repo_id = ctx.model.id;
let now = OffsetDateTime::now_utc();
TransactionTrait::transaction(db, |txn| {
Box::pin(async move {
repo_star::Entity::delete_many()
.filter(repo_star::Column::UserId.eq(user_id))
.filter(repo_star::Column::RepoId.eq(repo_id))
.exec(txn)
.await?;
Ok::<(), sea_orm::DbErr>(())
})
})
.await?;
let after = repo_star::Entity::find()
.filter(repo_star::Column::RepoId.eq(repo_id))
.all(db)
.await?
.len() as i32;
let am = repos::ActiveModel {
id: Unchanged(repo_id),
nums_star: Set(after),
updated_at: Set(now),
..Default::default()
};
am.update(db).await?;
Ok(RepoStarResp { stars: after })
}
pub async fn is_starred(&self, ctx: &GitContext, user_id: i64) -> Result<bool, GitError> {
let row = repo_star::Entity::find()
.filter(repo_star::Column::UserId.eq(user_id))
.filter(repo_star::Column::RepoId.eq(ctx.model.id))
.one(self.db_read())
.await?;
Ok(row.is_some())
}
pub async fn list_stargazers(
&self,
ctx: &GitContext,
limit: u64,
offset: u64,
) -> Result<Vec<StargazerDto>, GitError> {
let rows = repo_star::Entity::find()
.filter(repo_star::Column::RepoId.eq(ctx.model.id))
.order_by_desc(repo_star::Column::CreatedAt)
.limit(limit)
.offset(offset)
.all(self.db_read())
.await?;
if rows.is_empty() {
return Ok(Vec::new());
}
let user_ids: Vec<i64> = rows.iter().map(|r| r.user_id).collect();
let users = models::users::user::Entity::find()
.filter(models::users::user::Column::Uid.is_in(user_ids.iter().copied()))
.all(self.db_read())
.await?;
let by_id: std::collections::HashMap<i64, models::users::user::Model> =
users.into_iter().map(|u| (u.uid, u)).collect();
Ok(rows
.into_iter()
.filter_map(|row| {
by_id.get(&row.user_id).map(|u| StargazerDto {
user_id: u.uid,
username: u.username.clone(),
display_name: u.display_name.clone(),
avatar_url: export::resolve_user_avatar_url(u.uid, &u.avatar_url),
starred_at: row.created_at,
})
})
.collect())
}
}