Skip to content
Clone
a06673b
237 lines 7.1 KB Rust
use db::conn::sea_orm::*;
use db::repo::repo_release as release_entity;
use time::OffsetDateTime;
use uuid::Uuid;
use crate::error::{RepoError, RepoResult};
use crate::features::RepoService;
use crate::path::validate_tag_name;
use super::types::{
CreateReleaseOption, EditReleaseOption, ListReleaseQuery, ReleasePage, ReleaseView,
};
fn require_authenticated(actor: Uuid) -> RepoResult<()> {
if actor.is_nil() {
return Err(RepoError::Unauthorized("authentication required".into()));
}
Ok(())
}
async fn load_release(
svc: &RepoService,
repo_id: Uuid,
release_id: Uuid,
) -> RepoResult<release_entity::Model> {
let db = svc.db.reader();
let release = release_entity::Entity::find_by_id(release_id)
.one(&db)
.await?
.ok_or_else(|| RepoError::NotFound(format!("release {release_id}")))?;
if release.repo_id != repo_id {
return Err(RepoError::NotFound(format!("release {release_id}")));
}
Ok(release)
}
pub async fn list_releases(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
q: ListReleaseQuery,
) -> RepoResult<ReleasePage> {
let _ = actor;
let limit = q.limit.unwrap_or(svc.config.default_pagination);
let offset = q.page.unwrap_or(0).saturating_mul(limit);
let limit = std::cmp::min(limit, svc.config.max_pagination);
let db = svc.db.reader();
let mut query = release_entity::Entity::find()
.filter(release_entity::Column::RepoId.eq(repo_id))
.filter(release_entity::Column::IsTag.eq(false));
if let Some(d) = q.draft {
query = query.filter(release_entity::Column::IsDraft.eq(d));
}
if let Some(p) = q.prerelease {
query = query.filter(release_entity::Column::IsPrerelease.eq(p));
}
let total = query.clone().count(&db).await?;
let items = query
.order_by_desc(release_entity::Column::CreatedAt)
.limit(Some(limit))
.offset(Some(offset))
.all(&db)
.await?
.into_iter()
.map(ReleaseView::from)
.collect();
Ok(ReleasePage {
items,
total: total as i64,
})
}
pub async fn get_release(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
release_id: Uuid,
) -> RepoResult<ReleaseView> {
let _ = actor;
let release = load_release(svc, repo_id, release_id).await?;
Ok(ReleaseView::from(release))
}
pub async fn get_latest_release(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
) -> RepoResult<ReleaseView> {
let _ = actor;
let db = svc.db.reader();
let release = release_entity::Entity::find()
.filter(release_entity::Column::RepoId.eq(repo_id))
.filter(release_entity::Column::IsTag.eq(false))
.filter(release_entity::Column::IsDraft.eq(false))
.order_by_desc(release_entity::Column::CreatedAt)
.one(&db)
.await?
.ok_or_else(|| RepoError::NotFound("no published release".into()))?;
Ok(ReleaseView::from(release))
}
pub async fn get_release_by_tag(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
tag: &str,
) -> RepoResult<ReleaseView> {
let _ = actor;
let db = svc.db.reader();
let release = release_entity::Entity::find()
.filter(release_entity::Column::RepoId.eq(repo_id))
.filter(release_entity::Column::TagName.eq(tag))
.one(&db)
.await?
.ok_or_else(|| RepoError::NotFound(format!("release for tag {tag}")))?;
if release.is_tag {
return Err(RepoError::NotFound(format!("release for tag {tag}")));
}
Ok(ReleaseView::from(release))
}
pub async fn create_release(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
input: CreateReleaseOption,
) -> RepoResult<ReleaseView> {
require_authenticated(actor)?;
let tag = validate_tag_name(&input.tag_name, svc.config.max_tag_name_len)?;
if let Some(ref c) = input.content {
if c.len() > svc.config.max_release_body_len {
return Err(RepoError::BadRequest(format!(
"release content too long (max {})",
svc.config.max_release_body_len
)));
}
}
let _ = input.tag_message;
let db = svc.db.writer();
let now = OffsetDateTime::now_utc();
let active: release_entity::ActiveModel = release_entity::ActiveModel {
id: Set(Uuid::new_v4()),
repo_id: Set(repo_id),
publisher_id: Set(actor),
publisher_name: Set(String::new()),
tag_name: Set(tag.clone()),
lower_tag_name: Set(tag.to_lowercase()),
target: Set(input.target.unwrap_or_else(|| "main".to_string())),
title: Set(input.title),
content: Set(input.content),
sha: Set(String::new()),
num_commits: Set(0),
is_draft: Set(input.is_draft.unwrap_or(false)),
is_prerelease: Set(input.is_prerelease.unwrap_or(false)),
is_tag: Set(false),
created_at: Set(now),
updated_at: Set(now),
};
let saved = active.insert(&db).await?;
Ok(ReleaseView::from(saved))
}
pub async fn edit_release(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
release_id: Uuid,
input: EditReleaseOption,
) -> RepoResult<ReleaseView> {
require_authenticated(actor)?;
let release = load_release(svc, repo_id, release_id).await?;
let db = svc.db.writer();
let mut active: release_entity::ActiveModel = release.into();
if let Some(t) = input.tag_name {
let validated = validate_tag_name(&t, svc.config.max_tag_name_len)?;
active.tag_name = Set(validated.clone());
active.lower_tag_name = Set(validated.to_lowercase());
}
if let Some(t) = input.target {
active.target = Set(t);
}
if let Some(t) = input.title {
active.title = Set(t);
}
if let Some(c) = input.content {
if let Some(ref body) = c {
if body.len() > svc.config.max_release_body_len {
return Err(RepoError::BadRequest(format!(
"release content too long (max {})",
svc.config.max_release_body_len
)));
}
}
active.content = Set(c);
}
if let Some(d) = input.is_draft {
active.is_draft = Set(d);
}
if let Some(p) = input.is_prerelease {
active.is_prerelease = Set(p);
}
active.updated_at = Set(OffsetDateTime::now_utc());
let saved = active.update(&db).await?;
Ok(ReleaseView::from(saved))
}
pub async fn delete_release(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
release_id: Uuid,
) -> RepoResult<()> {
require_authenticated(actor)?;
let _ = load_release(svc, repo_id, release_id).await?;
let db = svc.db.writer();
release_entity::Entity::delete_by_id(release_id)
.exec(&db)
.await?;
Ok(())
}
pub async fn delete_release_by_tag(
svc: &RepoService,
actor: Uuid,
repo_id: Uuid,
tag: &str,
) -> RepoResult<()> {
require_authenticated(actor)?;
let db = svc.db.writer();
release_entity::Entity::delete_many()
.filter(release_entity::Column::RepoId.eq(repo_id))
.filter(release_entity::Column::TagName.eq(tag))
.filter(release_entity::Column::IsTag.eq(false))
.exec(&db)
.await?;
Ok(())
}