mirror of
https://github.com/encounter/decomp.dev.git
synced 2026-07-10 03:18:48 -07:00
Cleanup & add commit delete endpoint
This commit is contained in:
@@ -1106,6 +1106,35 @@ impl Database {
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn delete_reports_by_commit(
|
||||
&self,
|
||||
project_id: u64,
|
||||
commit_sha: &str,
|
||||
) -> Result<usize> {
|
||||
let mut conn = self.pool.acquire().await?;
|
||||
let project_id_db = project_id as i64;
|
||||
let deleted_count = sqlx::query!(
|
||||
r#"
|
||||
DELETE FROM reports
|
||||
WHERE project_id = ? AND git_commit = ? COLLATE NOCASE
|
||||
"#,
|
||||
project_id_db,
|
||||
commit_sha,
|
||||
)
|
||||
.execute(&mut *conn)
|
||||
.await?
|
||||
.rows_affected();
|
||||
if deleted_count > 0 {
|
||||
tracing::info!(
|
||||
"Deleted {} reports for project ID {} commit {}",
|
||||
deleted_count,
|
||||
project_id,
|
||||
commit_sha
|
||||
);
|
||||
}
|
||||
Ok(deleted_count as usize)
|
||||
}
|
||||
}
|
||||
|
||||
thread_local! {
|
||||
|
||||
@@ -166,7 +166,7 @@ fn measure_line_simple(name: &str, from: u64, to: u64) -> String {
|
||||
const MAX_CHANGE_LINES: usize = 30;
|
||||
|
||||
// Note: The order the tables are printed in is determined by the order of the variants in this enum.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Debug)]
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Copy, Clone, Debug)]
|
||||
enum ChangeKind {
|
||||
NewMatch,
|
||||
BrokenMatch,
|
||||
@@ -174,6 +174,35 @@ enum ChangeKind {
|
||||
Regression,
|
||||
}
|
||||
|
||||
impl ChangeKind {
|
||||
fn emoji(self) -> &'static str {
|
||||
match self {
|
||||
ChangeKind::NewMatch => "✅",
|
||||
ChangeKind::BrokenMatch => "💔",
|
||||
ChangeKind::Improvement => "📈",
|
||||
ChangeKind::Regression => "📉",
|
||||
}
|
||||
}
|
||||
|
||||
fn singular_description(self) -> &'static str {
|
||||
match self {
|
||||
ChangeKind::NewMatch => "new match",
|
||||
ChangeKind::BrokenMatch => "broken match",
|
||||
ChangeKind::Improvement => "improvement in unmatched functions",
|
||||
ChangeKind::Regression => "regression in unmatched functions",
|
||||
}
|
||||
}
|
||||
|
||||
fn plural_description(self) -> &'static str {
|
||||
match self {
|
||||
ChangeKind::NewMatch => "new matches",
|
||||
ChangeKind::BrokenMatch => "broken matches",
|
||||
ChangeKind::Improvement => "improvements in unmatched functions",
|
||||
ChangeKind::Regression => "regressions in unmatched functions",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ChangeLine {
|
||||
kind: ChangeKind,
|
||||
unit_name: String,
|
||||
@@ -206,25 +235,25 @@ fn generate_changes_list(changes: Vec<ChangeLine>, out: &mut String) {
|
||||
changes_by_kind.entry(change.kind.clone()).or_insert(vec![]).push(change);
|
||||
}
|
||||
for (change_kind, mut changes) in changes_by_kind {
|
||||
let (emoji, description) = match change_kind {
|
||||
ChangeKind::NewMatch => ("✅", "new matches"),
|
||||
ChangeKind::BrokenMatch => ("💔", "broken matches"),
|
||||
ChangeKind::Improvement => ("📈", "improvements in unmatched functions"),
|
||||
ChangeKind::Regression => ("📉", "regressions in unmatched functions"),
|
||||
};
|
||||
|
||||
let total_changes = changes.len();
|
||||
if total_changes == 0 {
|
||||
out.push_str(&format!("No {description}.\n"));
|
||||
let description = if total_changes == 0 {
|
||||
out.push_str(&format!("No {}.\n", change_kind.plural_description()));
|
||||
continue;
|
||||
}
|
||||
} else if total_changes == 1 {
|
||||
change_kind.singular_description()
|
||||
} else {
|
||||
change_kind.plural_description()
|
||||
};
|
||||
|
||||
if change_kind == ChangeKind::BrokenMatch {
|
||||
out.push_str("<details open>\n");
|
||||
} else {
|
||||
out.push_str("<details>\n");
|
||||
}
|
||||
out.push_str(&format!("<summary>{emoji} {total_changes} {description}</summary>\n"));
|
||||
out.push_str(&format!(
|
||||
"<summary>{} {total_changes} {description}</summary>\n",
|
||||
change_kind.emoji()
|
||||
));
|
||||
out.push('\n'); // Must include a blank line before a table
|
||||
out.push_str("| Unit | Function | Bytes | Before | After |\n");
|
||||
out.push_str("| - | - | - | - | - |\n");
|
||||
|
||||
@@ -22,7 +22,8 @@ use decomp_dev_github::{
|
||||
};
|
||||
use itertools::Itertools;
|
||||
use maud::{DOCTYPE, Markup, html};
|
||||
use serde::Deserialize;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use tower_sessions::Session;
|
||||
|
||||
use crate::{
|
||||
AppState,
|
||||
@@ -339,6 +340,7 @@ pub async fn manage_project(
|
||||
State(state): State<AppState>,
|
||||
current_user: CurrentUser,
|
||||
ctx: TemplateContext,
|
||||
session: Session,
|
||||
) -> Result<Response, AppError> {
|
||||
let Some(info) = state.db.get_project_info(¶ms.owner, ¶ms.repo, None).await? else {
|
||||
return Err(AppError::Status(StatusCode::NOT_FOUND));
|
||||
@@ -361,10 +363,12 @@ pub async fn manage_project(
|
||||
} else {
|
||||
None
|
||||
};
|
||||
render_manage_project(ctx, &state, &info, report.as_ref(), ¤t_user, Message::None).await
|
||||
render_manage_project(ctx, &state, &info, report.as_ref(), ¤t_user, session).await
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize, Default)]
|
||||
enum Message {
|
||||
#[default]
|
||||
None,
|
||||
Info(String),
|
||||
Error(String),
|
||||
@@ -388,7 +392,7 @@ async fn render_manage_project(
|
||||
project_info: &ProjectInfo,
|
||||
latest_report: Option<&CachedReportFile>,
|
||||
current_user: &CurrentUser,
|
||||
message: Message,
|
||||
session: Session,
|
||||
) -> Result<Response, AppError> {
|
||||
let project_short_name = project_info.project.short_name();
|
||||
let project_manage_path =
|
||||
@@ -409,6 +413,11 @@ async fn render_manage_project(
|
||||
None
|
||||
};
|
||||
|
||||
let message = session
|
||||
.remove::<Message>(&format!("manage_{}_message", project_info.project.id))
|
||||
.await?
|
||||
.unwrap_or_default();
|
||||
|
||||
// Check if the project is hidden based on matched code percentage
|
||||
let visibility =
|
||||
project_visibility(&project_info.project, latest_report.map(|r| &r.report.measures));
|
||||
@@ -635,10 +644,10 @@ pub async fn manage_project_save(
|
||||
}
|
||||
|
||||
pub async fn manage_project_refresh(
|
||||
ctx: TemplateContext,
|
||||
Path(params): Path<ProjectParams>,
|
||||
State(state): State<AppState>,
|
||||
current_user: CurrentUser,
|
||||
session: Session,
|
||||
) -> Result<Response, AppError> {
|
||||
let Some(info) = state.db.get_project_info(¶ms.owner, ¶ms.repo, None).await? else {
|
||||
return Err(AppError::Status(StatusCode::NOT_FOUND));
|
||||
@@ -658,20 +667,40 @@ pub async fn manage_project_refresh(
|
||||
Message::Error(format!("Failed to refresh project: {e}"))
|
||||
}
|
||||
};
|
||||
session.insert(&format!("manage_{}_message", info.project.id), message).await?;
|
||||
let redirect_url = format!("/manage/{}/{}", params.owner, params.repo);
|
||||
Ok(Redirect::to(&redirect_url).into_response())
|
||||
}
|
||||
|
||||
let report = if let (Some(version), Some(commit)) = (info.default_version(), &info.commit) {
|
||||
state
|
||||
.db
|
||||
.get_report(&info.project.owner, &info.project.repo, &commit.sha, version)
|
||||
.await
|
||||
.with_context(|| {
|
||||
format!(
|
||||
"Failed to fetch report for {}/{} sha {} version {}",
|
||||
info.project.owner, info.project.repo, commit.sha, version
|
||||
)
|
||||
})?
|
||||
#[derive(Deserialize)]
|
||||
pub struct DeleteCommitParams {
|
||||
#[serde(flatten)]
|
||||
project: ProjectParams,
|
||||
commit: String,
|
||||
}
|
||||
|
||||
pub async fn delete_commit(
|
||||
Path(params): Path<DeleteCommitParams>,
|
||||
State(state): State<AppState>,
|
||||
current_user: CurrentUser,
|
||||
session: Session,
|
||||
) -> Result<Response, AppError> {
|
||||
let Some(info) =
|
||||
state.db.get_project_info(¶ms.project.owner, ¶ms.project.repo, None).await?
|
||||
else {
|
||||
return Err(AppError::Status(StatusCode::NOT_FOUND));
|
||||
};
|
||||
if !current_user.can_manage_repo(info.project.id) {
|
||||
return Err(AppError::Status(StatusCode::FORBIDDEN));
|
||||
}
|
||||
let num_reports_deleted =
|
||||
state.db.delete_reports_by_commit(info.project.id, ¶ms.commit).await?;
|
||||
let message = if num_reports_deleted > 0 {
|
||||
Message::Info(format!("Deleted {num_reports_deleted} reports"))
|
||||
} else {
|
||||
None
|
||||
Message::Error("No reports found. Is the commit SHA correct?".to_string())
|
||||
};
|
||||
render_manage_project(ctx, &state, &info, report.as_ref(), ¤t_user, message).await
|
||||
session.insert(&format!("manage_{}_message", info.project.id), message).await?;
|
||||
let redirect_url = format!("/manage/{}/{}", params.project.owner, params.project.repo);
|
||||
Ok(Redirect::to(&redirect_url).into_response())
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ use axum::{
|
||||
extract::{DefaultBodyLimit, Request},
|
||||
http::{HeaderMap, HeaderValue, header, header::Entry},
|
||||
response::Response,
|
||||
routing::{get, post},
|
||||
routing::{delete, get, post},
|
||||
};
|
||||
use decomp_dev_images::image_mime_from_ext;
|
||||
use mime::Mime;
|
||||
@@ -69,6 +69,7 @@ pub fn build_router() -> Router<AppState> {
|
||||
.layer(DefaultBodyLimit::max(50 * 1000 * 1000 /* 50MB */)),
|
||||
)
|
||||
.route("/manage/{owner}/{repo}/refresh", post(manage::manage_project_refresh))
|
||||
.route("/manage/{owner}/{repo}/commit/{commit}", delete(manage::delete_commit))
|
||||
.route("/og.png", get(decomp_dev_images::get_og))
|
||||
.route("/", get(project::get_projects))
|
||||
.route("/projects", get(project::get_projects))
|
||||
|
||||
Reference in New Issue
Block a user