| 214 | (icon(Icon::File)) | 214 | (icon(Icon::File)) |
| 215 | a class="asset-name" href=(format!("/release-asset/{}", a.id)) { (a.name) } | 215 | a class="asset-name" href=(format!("/release-asset/{}", a.id)) { (a.name) } |
| 216 | span class="asset-size muted" { (fmt_bytes(a.size)) } | 216 | span class="asset-size muted" { (fmt_bytes(a.size)) } |
| 217 | @if can_write { | | |
| 218 | form method="post" action=(format!("/release-asset/{}/delete", a.id)) class="inline-form" { | | |
| 219 | input type="hidden" name="_csrf" value=(csrf); | | |
| 220 | button class="icon-btn" type="submit" aria-label="Delete asset" title="Delete asset" { (icon(Icon::X)) } | | |
| 221 | } | | |
| 222 | } | | |
| 223 | } | 217 | } |
| 224 | } | 218 | } |
| 225 | li class="asset-row source" { | 219 | li class="asset-row source" { |
| 231 | a class="asset-name" href=(format!("{base}/-/archive/{}.tar.gz", r.tag)) { "Source code (tar.gz)" } | 225 | a class="asset-name" href=(format!("{base}/-/archive/{}.tar.gz", r.tag)) { "Source code (tar.gz)" } |
| 232 | } | 226 | } |
| 233 | } | 227 | } |
| 234 | @if can_write { | | |
| 235 | form method="post" action=(format!("/release/{}/asset", r.id)) enctype="multipart/form-data" class="asset-upload" { | | |
| 236 | input type="hidden" name="_csrf" value=(csrf); | | |
| 237 | input type="file" name="asset" required; | | |
| 238 | button class="btn inline-btn" type="submit" { "Upload asset" } | | |
| 239 | } | | |
| 240 | } | | |
| 241 | } | 228 | } |
| 242 | } | 229 | } |
| 243 | } | 230 | } |
| 292 | .await? | 279 | .await? |
| 293 | .map_or(repo.owner_id.clone(), |u| u.username); | 280 | .map_or(repo.owner_id.clone(), |u| u.username); |
| 294 | let back = format!("/{owner}/{}/-/releases/{}", repo.path, release.tag); | 281 | let back = format!("/{owner}/{}/-/releases/{}", repo.path, release.tag); |
| | 282 | let assets = state.store.list_release_assets(&release.id).await?; |
| 295 | let (jar, chrome) = build_chrome(&state, jar, Some(user), uri.path().to_string()); | 283 | let (jar, chrome) = build_chrome(&state, jar, Some(user), uri.path().to_string()); |
| 296 | let body = html! { | 284 | let body = html! { |
| 297 | p { a class="back-link" href=(back) { "← Back to release" } } | 285 | p { a class="back-link" href=(back) { "← Back to release" } } |
| 298 | (release_form(&format!("/release/{}/edit", release.id), &chrome.csrf, Some(&release), &[], &[])) | 286 | (release_form(&format!("/release/{}/edit", release.id), &chrome.csrf, Some(&release), &[], &[])) |
| | 287 | (assets_editor(&release.id, &chrome.csrf, &assets)) |
| 299 | }; | 288 | }; |
| 300 | Ok((jar, page(&chrome, "Edit release", body)).into_response()) | 289 | Ok((jar, page(&chrome, "Edit release", body)).into_response()) |
| 301 | } | 290 | } |
| 351 | } | 340 | } |
| 352 | } | 341 | } |
| 353 | | 342 | |
| | 343 | |
| | 344 | |
| | 345 | fn assets_editor(release_id: &str, csrf: &str, assets: &[store::ReleaseAsset]) -> Markup { |
| | 346 | html! { |
| | 347 | section class="listing" { |
| | 348 | h2 { "Assets" } |
| | 349 | div class="card" { |
| | 350 | @if assets.is_empty() { |
| | 351 | p class="muted" { "No uploaded assets yet." } |
| | 352 | } @else { |
| | 353 | ul class="asset-list" { |
| | 354 | @for a in assets { |
| | 355 | li class="asset-row" { |
| | 356 | (icon(Icon::File)) |
| | 357 | a class="asset-name" href=(format!("/release-asset/{}", a.id)) { (a.name) } |
| | 358 | span class="asset-size muted" { (fmt_bytes(a.size)) } |
| | 359 | form method="post" action=(format!("/release-asset/{}/delete", a.id)) class="inline-form" { |
| | 360 | input type="hidden" name="_csrf" value=(csrf); |
| | 361 | button class="icon-btn" type="submit" aria-label="Delete asset" title="Delete asset" { (icon(Icon::X)) } |
| | 362 | } |
| | 363 | } |
| | 364 | } |
| | 365 | } |
| | 366 | } |
| | 367 | form method="post" action=(format!("/release/{release_id}/asset")) enctype="multipart/form-data" class="asset-upload" { |
| | 368 | input type="hidden" name="_csrf" value=(csrf); |
| | 369 | input type="file" name="asset" required; |
| | 370 | button class="btn inline-btn" type="submit" { "Upload asset" } |
| | 371 | } |
| | 372 | } |
| | 373 | } |
| | 374 | } |
| | 375 | } |
| | 376 | |
| 354 | | 377 | |
| 355 | #[derive(Debug, Deserialize)] | 378 | #[derive(Debug, Deserialize)] |
| 356 | pub struct ReleaseForm { | 379 | pub struct ReleaseForm { |
| 565 | return AppError::Forbidden.into_response(); | 588 | return AppError::Forbidden.into_response(); |
| 566 | } | 589 | } |
| 567 | let result = async { | 590 | let result = async { |
| 568 | let (release, repo, user) = writer_release(&state, viewer.as_ref(), &id).await?; | 591 | let (release, _repo, _user) = writer_release(&state, viewer.as_ref(), &id).await?; |
| 569 | if let Some((tmp_name, name, size, ctype)) = saved { | 592 | if let Some((tmp_name, name, size, ctype)) = saved { |
| 570 | let asset = state | 593 | let asset = state |
| 571 | .store | 594 | .store |
| 579 | return Err(AppError::internal("storage error")); | 602 | return Err(AppError::internal("storage error")); |
| 580 | } | 603 | } |
| 581 | } | 604 | } |
| 582 | Ok::<String, AppError>(format!( | 605 | Ok::<String, AppError>(format!("/release/{}/edit", release.id)) |
| 583 | "/{}/{}/-/releases/{}", | | |
| 584 | user.username, repo.path, release.tag | | |
| 585 | )) | | |
| 586 | } | 606 | } |
| 587 | .await; | 607 | .await; |
| 588 | match result { | 608 | match result { |
| 643 | .release_asset_by_id(&id) | 663 | .release_asset_by_id(&id) |
| 644 | .await? | 664 | .await? |
| 645 | .ok_or(AppError::NotFound)?; | 665 | .ok_or(AppError::NotFound)?; |
| 646 | let (release, repo, user) = | 666 | let (release, _repo, _user) = |
| 647 | writer_release(&state, viewer.as_ref(), &asset.release_id).await?; | 667 | writer_release(&state, viewer.as_ref(), &asset.release_id).await?; |
| 648 | let _ = tokio::fs::remove_file(assets_dir(&state).join(&asset.id)).await; | 668 | let _ = tokio::fs::remove_file(assets_dir(&state).join(&asset.id)).await; |
| 649 | state.store.delete_release_asset(&asset.id).await?; | 669 | state.store.delete_release_asset(&asset.id).await?; |
| 650 | Ok::<String, AppError>(format!( | 670 | Ok::<String, AppError>(format!("/release/{}/edit", release.id)) |
| 651 | "/{}/{}/-/releases/{}", | | |
| 652 | user.username, repo.path, release.tag | | |
| 653 | )) | | |
| 654 | } | 671 | } |
| 655 | .await; | 672 | .await; |
| 656 | crate::issues::respond_redirect(result) | 673 | crate::issues::respond_redirect(result) |