Skip to content

Commit

Permalink
Implement first successful package upload
Browse files Browse the repository at this point in the history
  • Loading branch information
ricardoboss committed Dec 31, 2024
1 parent 5daaa82 commit a080893
Show file tree
Hide file tree
Showing 52 changed files with 2,426 additions and 201 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using PubNet.Database.Entities;
using PubNet.Database.Entities.Nuget;

namespace PubNet.API.Abstractions.CQRS.Commands.Packages;

public interface INugetPackageDmo
{
Task<NugetPackageVersion> CreateAsync(Author author, byte[] nupkg, CancellationToken cancellationToken = default);

Task<NugetPackageVersion> AddVersionAsync(NugetPackage package, byte[] nupkg, CancellationToken cancellationToken = default);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace PubNet.API.Abstractions.Packages.Nuget;

public interface INugetPackageUploadService
{

}
63 changes: 63 additions & 0 deletions Backend/PubNet.API.Abstractions/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,56 @@
"resolved": "8.0.0",
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"NuGet.Common": {
"type": "Transitive",
"resolved": "6.11.1",
"contentHash": "6ouw0UC3TGaFHNJyoEK2/Q5jSryRHzcbKGv9C0+t/TPnTP8PoLqnyFxO1dwmQUmJkWuKAUo6Vu0kIXHY/8LyKQ==",
"dependencies": {
"NuGet.Frameworks": "6.11.1"
}
},
"NuGet.Configuration": {
"type": "Transitive",
"resolved": "6.11.1",
"contentHash": "vbEe2acKrI2QmNx9U94ewhgUt6cLpwBeQfMtrie6NMz+GkJcX/4qIkKsX3SeBO4gFgCf8eeTyURl5hxPtiUctw==",
"dependencies": {
"NuGet.Common": "6.11.1",
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"NuGet.Frameworks": {
"type": "Transitive",
"resolved": "6.11.1",
"contentHash": "plTZ3ariSWQVsFn2mk83SsdmSg1VpgIMTSZpP/eSE/NNQF02p+M9ItxAYeUZBMX+cQ2nFkSwxQRJ0/fkaV9Hbg=="
},
"NuGet.Versioning": {
"type": "Transitive",
"resolved": "6.11.1",
"contentHash": "YNn3BB71F+guJW42TbAhGcMh3gpyqFMZcPVD9pm5vcvGivTALtRely/VCPWQQ6JQ5PfwIrjPaJMO7VnqyeK3rg=="
},
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "T6fD00dQ3NTbPDy31m4eQUwKW84s03z0N2C8HpOklyeaDgaJPa/TexP4/SkORMSOwc7WhKifnA6Ya33AkzmafA=="
},
"System.Security.Cryptography.Pkcs": {
"type": "Transitive",
"resolved": "6.0.4",
"contentHash": "LGbXi1oUJ9QgCNGXRO9ndzBL/GZgANcsURpMhNR8uO+rca47SZmciS3RSQUvlQRwK3QHZSHNOXzoMUASKA+Anw==",
"dependencies": {
"System.Formats.Asn1": "6.0.0"
}
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog=="
},
"pubnet.api.dto": {
"type": "Project",
"dependencies": {
Expand All @@ -125,6 +175,7 @@
"DartLang.PubSpec.Serialization.Json": "[3.0.1, )",
"Microsoft.EntityFrameworkCore": "[8.0.10, )",
"Microsoft.EntityFrameworkCore.Relational": "[8.0.10, )",
"NuGet.Packaging": "[6.11.1, )",
"PubNet.Auth": "[1.0.0, )"
}
},
Expand Down Expand Up @@ -190,6 +241,18 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
}
},
"NuGet.Packaging": {
"type": "CentralTransitive",
"requested": "[6.11.1, )",
"resolved": "6.11.1",
"contentHash": "wiofIUUr7khwuaGXiOibMb7+dEkF97EVsAmzlaNc188HV9ujjqweQMuVCoAK2/MqXdhnrKjvicUfKo9CPsNnfg==",
"dependencies": {
"Newtonsoft.Json": "13.0.3",
"NuGet.Configuration": "6.11.1",
"NuGet.Versioning": "6.11.1",
"System.Security.Cryptography.Pkcs": "6.0.4"
}
},
"Riok.Mapperly": {
"type": "CentralTransitive",
"requested": "[4.1.0, )",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace PubNet.API.DTO.Packages.Nuget;

public class NugetInvalidPackageErrorDto
{
public required string Message { get; init; }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace PubNet.API.DTO.Packages.Nuget;

public class NugetPackageAlreadyExistsErrorDto
{
public required string Message { get; init; }
}
6 changes: 6 additions & 0 deletions Backend/PubNet.API.DTO/Packages/Nuget/NugetSuccessDto.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
namespace PubNet.API.DTO.Packages.Nuget;

public class NugetSuccessDto
{
public required string Success { get; init; }
}
4 changes: 4 additions & 0 deletions Backend/PubNet.API.DTO/PubNet.API.DTO.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@
<PackageReference Include="Riok.Mapperly"/>
</ItemGroup>

<ItemGroup>
<FrameworkReference Include="Microsoft.AspNetCore.App"/>
</ItemGroup>

</Project>
63 changes: 63 additions & 0 deletions Backend/PubNet.API.DTO/packages.lock.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,56 @@
"resolved": "8.0.0",
"contentHash": "bXJEZrW9ny8vjMF1JV253WeLhpEVzFo1lyaZu1vQ4ZxWUlVvknZ/+ftFgVheLubb4eZPSwwxBeqS1JkCOjxd8g=="
},
"Newtonsoft.Json": {
"type": "Transitive",
"resolved": "13.0.3",
"contentHash": "HrC5BXdl00IP9zeV+0Z848QWPAoCr9P3bDEZguI+gkLcBKAOxix/tLEAAHC+UvDNPv4a2d18lOReHMOagPa+zQ=="
},
"NuGet.Common": {
"type": "Transitive",
"resolved": "6.11.1",
"contentHash": "6ouw0UC3TGaFHNJyoEK2/Q5jSryRHzcbKGv9C0+t/TPnTP8PoLqnyFxO1dwmQUmJkWuKAUo6Vu0kIXHY/8LyKQ==",
"dependencies": {
"NuGet.Frameworks": "6.11.1"
}
},
"NuGet.Configuration": {
"type": "Transitive",
"resolved": "6.11.1",
"contentHash": "vbEe2acKrI2QmNx9U94ewhgUt6cLpwBeQfMtrie6NMz+GkJcX/4qIkKsX3SeBO4gFgCf8eeTyURl5hxPtiUctw==",
"dependencies": {
"NuGet.Common": "6.11.1",
"System.Security.Cryptography.ProtectedData": "4.4.0"
}
},
"NuGet.Frameworks": {
"type": "Transitive",
"resolved": "6.11.1",
"contentHash": "plTZ3ariSWQVsFn2mk83SsdmSg1VpgIMTSZpP/eSE/NNQF02p+M9ItxAYeUZBMX+cQ2nFkSwxQRJ0/fkaV9Hbg=="
},
"NuGet.Versioning": {
"type": "Transitive",
"resolved": "6.11.1",
"contentHash": "YNn3BB71F+guJW42TbAhGcMh3gpyqFMZcPVD9pm5vcvGivTALtRely/VCPWQQ6JQ5PfwIrjPaJMO7VnqyeK3rg=="
},
"System.Formats.Asn1": {
"type": "Transitive",
"resolved": "6.0.0",
"contentHash": "T6fD00dQ3NTbPDy31m4eQUwKW84s03z0N2C8HpOklyeaDgaJPa/TexP4/SkORMSOwc7WhKifnA6Ya33AkzmafA=="
},
"System.Security.Cryptography.Pkcs": {
"type": "Transitive",
"resolved": "6.0.4",
"contentHash": "LGbXi1oUJ9QgCNGXRO9ndzBL/GZgANcsURpMhNR8uO+rca47SZmciS3RSQUvlQRwK3QHZSHNOXzoMUASKA+Anw==",
"dependencies": {
"System.Formats.Asn1": "6.0.0"
}
},
"System.Security.Cryptography.ProtectedData": {
"type": "Transitive",
"resolved": "4.4.0",
"contentHash": "cJV7ScGW7EhatRsjehfvvYVBvtiSMKgN8bOVI0bQhnF5bU7vnHVIsH49Kva7i7GWaWYvmEzkYVk1TC+gZYBEog=="
},
"pubnet.auth": {
"type": "Project",
"dependencies": {
Expand All @@ -105,6 +155,7 @@
"DartLang.PubSpec.Serialization.Json": "[3.0.1, )",
"Microsoft.EntityFrameworkCore": "[8.0.10, )",
"Microsoft.EntityFrameworkCore.Relational": "[8.0.10, )",
"NuGet.Packaging": "[6.11.1, )",
"PubNet.Auth": "[1.0.0, )"
}
},
Expand Down Expand Up @@ -164,6 +215,18 @@
"Microsoft.Extensions.DependencyInjection.Abstractions": "8.0.2"
}
},
"NuGet.Packaging": {
"type": "CentralTransitive",
"requested": "[6.11.1, )",
"resolved": "6.11.1",
"contentHash": "wiofIUUr7khwuaGXiOibMb7+dEkF97EVsAmzlaNc188HV9ujjqweQMuVCoAK2/MqXdhnrKjvicUfKo9CPsNnfg==",
"dependencies": {
"Newtonsoft.Json": "13.0.3",
"NuGet.Configuration": "6.11.1",
"NuGet.Versioning": "6.11.1",
"System.Security.Cryptography.Pkcs": "6.0.4"
}
},
"Semver": {
"type": "CentralTransitive",
"requested": "[3.0.0, )",
Expand Down
107 changes: 107 additions & 0 deletions Backend/PubNet.API.Services/CQRS/Commands/Packages/NugetPackageDmo.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
using NuGet.Packaging;
using PubNet.API.Abstractions.CQRS.Commands.Packages;
using PubNet.API.Services.Extensions;
using PubNet.Database.Context;
using PubNet.Database.Entities;
using PubNet.Database.Entities.Nuget;
using PubNet.PackageStorage.Abstractions;

namespace PubNet.API.Services.CQRS.Commands.Packages;

public class NugetPackageDmo(PubNetContext context, IArchiveStorage archiveStorage) : INugetPackageDmo
{
public async Task<NugetPackageVersion> CreateAsync(Author author, byte[] nupkg,
CancellationToken cancellationToken = default)
{
var package = new NugetPackage
{
Id = Guid.NewGuid(),
Author = author,
Versions = [],
};

var version = ReadPackageVersion(package, nupkg);

package.Name = version.NuspecId ?? throw new InvalidOperationException("Package ID is missing");
package.Versions.Add(version);
package.LatestVersion = version;
package.LatestVersionId = version.Id;

await context.NugetPackageVersions.AddAsync(version, cancellationToken);
await context.NugetPackages.AddAsync(package, cancellationToken);

version.Package = package;

await context.SaveChangesAsync(cancellationToken);

await context.Entry(package).ReloadAsync(cancellationToken);
await context.Entry(version).ReloadAsync(cancellationToken);

return version;
}

public async Task<NugetPackageVersion> AddVersionAsync(NugetPackage package, byte[] nupkg,
CancellationToken cancellationToken = default)
{
var version = ReadPackageVersion(package, nupkg);

_ = await archiveStorage.StoreArchiveAsync(package.Author.UserName, package.Name, version.Version, nupkg,
cancellationToken);

await context.NugetPackageVersions.AddAsync(version, cancellationToken);

package.Versions.Add(version);
package.LatestVersion = version;

await context.SaveChangesAsync(cancellationToken);

await context.Entry(package).ReloadAsync(cancellationToken);
await context.Entry(version).ReloadAsync(cancellationToken);

return version;
}

private static NugetPackageVersion ReadPackageVersion(NugetPackage package, byte[] nupkg)
{
using var packageStream = new MemoryStream(nupkg);

using var reader = new PackageArchiveReader(packageStream);
var nuspec = reader.NuspecReader;

var id = nuspec.GetId().ToNullIfEmpty();
var version = nuspec.GetVersion();
var title = nuspec.GetTitle().ToNullIfEmpty();
var description = nuspec.GetDescription().ToNullIfEmpty();
var authors = nuspec.GetAuthors().ToNullIfEmpty();
var iconUrl = nuspec.GetIconUrl().ToNullIfEmpty();
var projectUrl = nuspec.GetProjectUrl().ToNullIfEmpty();
var copyright = nuspec.GetCopyright().ToNullIfEmpty();
var tags = nuspec.GetTags().ToNullIfEmpty();
var readme = nuspec.GetReadme().ToNullIfEmpty();
var icon = nuspec.GetIcon().ToNullIfEmpty();
var repositoryMetadata = nuspec.GetRepositoryMetadata();
var dependencyGroups = nuspec.GetDependencyGroups() ?? [];

var versionEntity = new NugetPackageVersion
{
Id = Guid.NewGuid(),
Version = version.ToString(),
PublishedAt = DateTimeOffset.UtcNow,
NuspecId = id,
NuspecVersion = version,
Title = title,
Description = description,
Authors = authors,
IconUrl = iconUrl,
ProjectUrl = projectUrl,
Copyright = copyright,
Tags = tags,
ReadmeFile = readme,
IconFile = icon,
RepositoryMetadata = repositoryMetadata,
DependencyGroups = dependencyGroups.ToArray(),
};

return versionEntity;
}
}
11 changes: 11 additions & 0 deletions Backend/PubNet.API.Services/Extensions/AuthProviderExtensions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using PubNet.API.Abstractions.Authentication;
using PubNet.Database.Entities;
using PubNet.Database.Entities.Auth;

namespace PubNet.API.Services.Extensions;
Expand All @@ -24,4 +25,14 @@ public static async Task<Token> GetCurrentTokenAsync(this IAuthProvider authProv

return maybeToken;
}

/// <exception cref="UnauthorizedAccessException">Thrown when the <see cref="IAuthProvider"/> fails to provide an <see cref="Author"/></exception>
public static async Task<Author> GetCurrentAuthorAsync(this IAuthProvider authProvider, CancellationToken cancellationToken = default)
{
var maybeAuthor = await authProvider.TryGetCurrentAuthorAsync(cancellationToken);
if (maybeAuthor is null)
throw new UnauthorizedAccessException("Unauthorized request");

return maybeAuthor;
}
}
9 changes: 9 additions & 0 deletions Backend/PubNet.API.Services/Extensions/StringExtensions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
namespace PubNet.API.Services.Extensions;

public static class StringExtensions
{
public static string? ToNullIfEmpty(this string? value)
{
return string.IsNullOrWhiteSpace(value) ? null : value;
}
}
Loading

0 comments on commit a080893

Please sign in to comment.