Skip to content

Commit

Permalink
Merge pull request #3 from eallegretta/feature/repackage
Browse files Browse the repository at this point in the history
Added support for package repackaging
  • Loading branch information
TheBinaryLoop authored Jun 27, 2023
2 parents 775c8ec + 6e2ebf9 commit 870b619
Show file tree
Hide file tree
Showing 9 changed files with 209 additions and 2 deletions.
8 changes: 8 additions & 0 deletions src/BaGet.Core/IUrlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -101,5 +101,13 @@ public interface IUrlGenerator
/// <param name="id">The package's ID</param>
/// <param name="version">The package's version</param>
string GetPackageIconDownloadUrl(string id, NuGetVersion version);

/// <summary>
/// Get the package repackage url with a replacement token for the new version {newVersion}
/// </summary>
/// <param name="id">The package ID</param>
/// <param name="version">The package version</param>
/// <param name="newVersion">The package new version</param>
string GetPackageRepackageUrl(string id, NuGetVersion version, NuGetVersion newVersion);
}
}
7 changes: 7 additions & 0 deletions src/BaGet.Web/BaGetEndpointBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,13 @@ public void MapPackagePublishRoutes(IEndpointRouteBuilder endpoints)
pattern: "api/v2/package/{id}/{version}",
defaults: new { controller = "PackagePublish", action = "Relist" },
constraints: new { httpMethod = new HttpMethodRouteConstraint("POST") });

// This is an unofficial API to repackage a package as a new version
endpoints.MapControllerRoute(
name: Routes.RepackageRouteName,
pattern: "api/v2/package/{id}/{version}/repackage/{newVersion}",
defaults: new { controller = "PackagePublish", action = "Repackage" },
constraints: new { httpMethod = new HttpMethodRouteConstraint("POST") });
}

public void MapSymbolRoutes(IEndpointRouteBuilder endpoints)
Expand Down
17 changes: 17 additions & 0 deletions src/BaGet.Web/BaGetUrlGenerator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -161,5 +161,22 @@ private string AbsoluteUrl(string relativePath)
"/",
relativePath);
}

public string GetPackageRepackageUrl(string id, NuGetVersion version, NuGetVersion newVersion)
{
id = id.ToLowerInvariant();
var versionString = version.ToNormalizedString().ToLowerInvariant();
var newVersionString = newVersion.ToNormalizedString().ToLowerInvariant();

return _linkGenerator.GetUriByRouteValues(
_httpContextAccessor.HttpContext,
Routes.RepackageRouteName,
values: new
{
Id = id,
Version = versionString,
NewVersion = newVersionString
});
}
}
}
89 changes: 89 additions & 0 deletions src/BaGet.Web/Controllers/PackagePublishController.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
using System;
using System.IO;
using System.IO.Compression;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Xml;
using System.Xml.Linq;
using BaGet.Core;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using NuGet.Packaging;
using NuGet.Versioning;

namespace BaGet.Web
Expand All @@ -15,6 +22,7 @@ public class PackagePublishController : Controller
private readonly IPackageIndexingService _indexer;
private readonly IPackageDatabase _packages;
private readonly IPackageDeletionService _deleteService;
private readonly IPackageStorageService _storageService;
private readonly IOptionsSnapshot<BaGetOptions> _options;
private readonly ILogger<PackagePublishController> _logger;

Expand All @@ -23,13 +31,15 @@ public PackagePublishController(
IPackageIndexingService indexer,
IPackageDatabase packages,
IPackageDeletionService deletionService,
IPackageStorageService storageService,
IOptionsSnapshot<BaGetOptions> options,
ILogger<PackagePublishController> logger)
{
_authentication = authentication ?? throw new ArgumentNullException(nameof(authentication));
_indexer = indexer ?? throw new ArgumentNullException(nameof(indexer));
_packages = packages ?? throw new ArgumentNullException(nameof(packages));
_deleteService = deletionService ?? throw new ArgumentNullException(nameof(deletionService));
_storageService = storageService ?? throw new ArgumentNullException(nameof(storageService));
_options = options ?? throw new ArgumentNullException(nameof(options));
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
Expand Down Expand Up @@ -135,5 +145,84 @@ public async Task<IActionResult> Relist(string id, string version, CancellationT
return NotFound();
}
}

[HttpPost]
public async Task<IActionResult> Repackage(string id, string version, string newVersion, CancellationToken cancellationToken)
{
if (_options.Value.IsReadOnlyMode)
{
return Unauthorized();
}

if (!NuGetVersion.TryParse(version, out var nugetVersion))
{
return NotFound();
}

if (!NuGetVersion.TryParse(newVersion, out var newNugetVersion))
{
return BadRequest("Invalid version");
}

if (!await _authentication.AuthenticateAsync(Request.GetApiKey(), cancellationToken))
{
return Unauthorized();
}

var packageStream = await _storageService.GetPackageStreamAsync(id, nugetVersion, cancellationToken);

if (packageStream == null)
{
return NotFound();
}

if(await _packages.ExistsAsync(id, newNugetVersion, cancellationToken))
{
return BadRequest("Package version already exists");
}

using (var ms = new MemoryStream())
{
await packageStream.CopyToAsync(ms);
using (var archive = new ZipArchive(ms, ZipArchiveMode.Update))
{
var nuspecEntry = archive.Entries.FirstOrDefault(x => x.Name.EndsWith(".nuspec", StringComparison.OrdinalIgnoreCase));

if (nuspecEntry == null)
{
return BadRequest("Nuget file is missing nuspec");
}

string updatedNuspec;
using (var nuspecStream = nuspecEntry.Open())
{
var doc = XDocument.Load(nuspecStream);
var ns = XNamespace.Get("http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd");
doc.Descendants(ns + "version").First().Value = newNugetVersion.ToNormalizedString();
using(var docMs = new MemoryStream())
{
doc.Save(docMs);
updatedNuspec = Encoding.UTF8.GetString(docMs.ToArray());
}
}

nuspecEntry.Delete();
nuspecEntry = archive.CreateEntry($"{id}.nuspec");

using(var writer = new StreamWriter(nuspecEntry.Open()))
{
writer.Write(updatedNuspec);
}
}


using(var repackgedStream = new MemoryStream(ms.ToArray()))
{
await _indexer.IndexAsync(repackgedStream, cancellationToken);
}

return Ok();
}
}
}
}
72 changes: 72 additions & 0 deletions src/BaGet.Web/Pages/Package.cshtml
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,11 @@ else
<i class="ms-Icon ms-Icon--CloudDownload"></i>
<a href="@Model.PackageDownloadUrl">Download package</a>
</li>

<li>
<i class="ms-Icon ms-Icon--Package"></i>
<a href="#" data-toggle="modal" data-target="#repackage">Repackage</a>
</li>
</ul>
</div>

Expand Down Expand Up @@ -268,6 +273,44 @@ else
}
</aside>
</div>

<!-- Modal -->
<form id="repackage-form">
<div class="modal fade" id="repackage" tabindex="-1" role="dialog" aria-labelledby="repackageLabel" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="repackageLabel">Repackage Nuget Package</h5>
<button type="button" class="close" data-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body">
<div id="repackage-alert" class="alert alert-danger alert-dismissible fade hide" role="alert">
<div id="repackage-error"></div>
</div>
<div class="form-group">
<label>Package name</label>
<input type="text" readonly class="form-control" value="@Model.Package.Id" />
</div>
<div class="form-group">
<label>Package version</label>
<input type="text" readonly class="form-control" value="@Model.Package.NormalizedVersionString" />
</div>

<div class="form-group">
<label for="repackage-version">New Version</label>
<input type="text" class="form-control" id="repackage-version" value="@Model.Package.NormalizedVersionString">
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-dismiss="modal">Close</button>
<button type="button" id="repackage-button" class="btn btn-primary">Repackage</button>
</div>
</div>
</div>
</div>
</form>
}

@if (Model.Found)
Expand Down Expand Up @@ -321,6 +364,35 @@ else
}
];
</script>

@section Scripts {
<script>
const repackageButton = $("#repackage-button");
repackageButton.click(() => {
const repackageForm = $("#repackage-form");
const repackageVersion = $("#repackage-version");
const postUrl = "@Model.RepackageUrl".replace("0.0.0", repackageVersion.val());
$.ajax({
type: "POST",
url: postUrl,
success: () => {
location.href = "@Url.Page("package", new{ id= Model.Package.Id, version = "0.0.0"})".replace("0.0.0", repackageVersion.val());
},
error: response => {
$("#repackage-error").text(response.responseText);
$("#repackage-alert").removeClass("fade").addClass("show")
},
headers: {
"X-NuGet-ApiKey": "@Model.ApiKey"
}
});
});
</script>
}
}

@functions {
Expand Down
9 changes: 9 additions & 0 deletions src/BaGet.Web/Pages/Package.cshtml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using Markdig;
using Microsoft.AspNetCore.Html;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.Extensions.Options;
using NuGet.Frameworks;
using NuGet.Versioning;

Expand All @@ -20,6 +21,7 @@ public class PackageModel : PageModel
private readonly IPackageService _packages;
private readonly IPackageContentService _content;
private readonly ISearchService _search;
private readonly IOptionsSnapshot<BaGetOptions> _options;
private readonly IUrlGenerator _url;

static PackageModel()
Expand All @@ -33,11 +35,13 @@ public PackageModel(
IPackageService packages,
IPackageContentService content,
ISearchService search,
IOptionsSnapshot<BaGetOptions> options,
IUrlGenerator url)
{
_packages = packages ?? throw new ArgumentNullException(nameof(packages));
_content = content ?? throw new ArgumentNullException(nameof(content));
_search = search ?? throw new ArgumentNullException(nameof(search));
_options = options ?? throw new ArgumentNullException(nameof(options));
_url = url ?? throw new ArgumentNullException(nameof(url));
}

Expand All @@ -59,6 +63,8 @@ public PackageModel(
public string IconUrl { get; private set; }
public string LicenseUrl { get; private set; }
public string PackageDownloadUrl { get; private set; }
public string RepackageUrl { get; private set; }
public string ApiKey { get; private set; }

public async Task OnGetAsync(string id, string version, CancellationToken cancellationToken)
{
Expand All @@ -84,6 +90,8 @@ public async Task OnGetAsync(string id, string version, CancellationToken cancel
return;
}

ApiKey = _options.Value.ApiKey;

var packageVersion = Package.Version;

Found = true;
Expand All @@ -108,6 +116,7 @@ public async Task OnGetAsync(string id, string version, CancellationToken cancel
: Package.IconUrlString;
LicenseUrl = Package.LicenseUrlString;
PackageDownloadUrl = _url.GetPackageDownloadUrl(Package.Id, packageVersion);
RepackageUrl = _url.GetPackageRepackageUrl(Package.Id, packageVersion, NuGetVersion.Parse("0.0.0"));
}

private IReadOnlyList<DependencyGroupModel> ToDependencyGroups(Package package)
Expand Down
1 change: 1 addition & 0 deletions src/BaGet.Web/Routes.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ public class Routes
public const string DeleteRouteName = "delete";
public const string RelistRouteName = "relist";
public const string SearchRouteName = "search";
public const string RepackageRouteName = "repackage";
public const string AutocompleteRouteName = "autocomplete";
public const string DependentsRouteName = "dependents";
public const string RegistrationIndexRouteName = "registration-index";
Expand Down
2 changes: 1 addition & 1 deletion src/BaGet/appsettings.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

"Storage": {
"Type": "FileSystem",
"Path": ""
"Path": "D:\\baget-packages"
},

"Search": {
Expand Down
6 changes: 5 additions & 1 deletion tests/BaGet.Web.Tests/Pages/PackageModelFacts.cs
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
using System;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using BaGet.Core;
using Microsoft.Extensions.Options;
using Moq;
using NuGet.Versioning;
using Xunit;
Expand All @@ -17,6 +18,7 @@ public class PackageModelFacts
private readonly Mock<IPackageService> _packages;
private readonly Mock<ISearchService> _search;
private readonly Mock<IUrlGenerator> _url;
private readonly Mock<IOptionsSnapshot<BaGetOptions>> _options;
private readonly PackageModel _target;

private readonly CancellationToken _cancellation = CancellationToken.None;
Expand All @@ -27,10 +29,12 @@ public PackageModelFacts()
_packages = new Mock<IPackageService>();
_search = new Mock<ISearchService>();
_url = new Mock<IUrlGenerator>();
_options = new Mock<IOptionsSnapshot<BaGetOptions>>();
_target = new PackageModel(
_packages.Object,
_content.Object,
_search.Object,
_options.Object,
_url.Object);

_search
Expand Down

0 comments on commit 870b619

Please sign in to comment.