Skip to content

Commit

Permalink
Add downbload statistics, hash validation on package query, and downl…
Browse files Browse the repository at this point in the history
…oad tracking
  • Loading branch information
kMutagene committed Mar 5, 2024
1 parent 90e4362 commit 74b871e
Show file tree
Hide file tree
Showing 13 changed files with 496 additions and 8 deletions.
27 changes: 27 additions & 0 deletions src/PackageRegistryService/API/Endpoints/StatisticsEndpointsV1.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using PackageRegistryService.API.Handlers;
using PackageRegistryService.Authentication;

namespace PackageRegistryService.API.Endpoints
{
public static class StatisticsEndpointsV1
{
public static RouteGroupBuilder MapStatisticsApiV1(this RouteGroupBuilder group)
{

// packages endpoints
group.MapGet("/downloads", DownloadsHandlers.GetAllDownloads)
.WithOpenApi()
.WithName("GetAllDownloads");

group.MapGet("/downloads/{name}", DownloadsHandlers.GetAllDownloadsByName)
.WithOpenApi()
.WithName("GetAllDownloadsByName");

group.MapGet("/downloads/{name}/{version}", DownloadsHandlers.GetDownloadsByNameAndVersion)
.WithOpenApi()
.WithName("GetDownloadsByNameAndVersion");

return group.WithTags("Statistics"); ;
}
}
}
54 changes: 54 additions & 0 deletions src/PackageRegistryService/API/Handlers/DownloadsHandlers.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Http.HttpResults;
using Microsoft.EntityFrameworkCore;
using PackageRegistryService.Models;

namespace PackageRegistryService.API.Handlers
{
public class DownloadsHandlers
{
// get all download stats
public static async Task<Ok<PackageDownloads[]>> GetAllDownloads(ValidationPackageDb database)
{
var downloads = await database.Downloads.ToArrayAsync();
return TypedResults.Ok(downloads);
}

public static async Task<Results<Ok<PackageDownloads[]>, NotFound<string>>> GetAllDownloadsByName(string name, ValidationPackageDb database)
{
var downloads =
await database.Downloads
.Where(p => p.PackageName == name)
.ToArrayAsync();

return downloads is null || downloads.Length == 0
? TypedResults.NotFound($"No download stats for package '{name}' available.")
: TypedResults.Ok(downloads);
}

public static async Task<Results<BadRequest<string>, NotFound<string>, Ok<PackageDownloads>>> GetDownloadsByNameAndVersion(string name, string version, ValidationPackageDb database)
{
var splt = version.Split('.');
if (splt.Length != 3)
{
return TypedResults.BadRequest("version was not a of valid format MAJOR.MINOR.REVISION");
}

int major; int minor; int revision;

if (
!int.TryParse(splt[0], out major)
|| !int.TryParse(splt[1], out minor)
|| !int.TryParse(splt[2], out revision)
)
{
return TypedResults.BadRequest("version was not a of valid format MAJOR.MINOR.REVISION");
}

var downloads = await database.Downloads.FindAsync(name, major, minor, revision);

return downloads is null
? TypedResults.NotFound($"No download stats for package '{name}' version '{version}' available.")
: TypedResults.Ok(downloads);
}
}
}
42 changes: 37 additions & 5 deletions src/PackageRegistryService/API/Handlers/PackageHandlers.cs
Original file line number Diff line number Diff line change
@@ -1,19 +1,30 @@
using Microsoft.AspNetCore.Http.HttpResults;
using PackageRegistryService.Models;
using Microsoft.EntityFrameworkCore;
using PackageRegistryService.Pages.Components;

namespace PackageRegistryService.API.Handlers
{
public class PackageHandlers
{
// get all validation packages
public static async Task<Ok<ValidationPackage[]>> GetAllPackages(ValidationPackageDb database)
public static async Task<Results<Ok<ValidationPackage[]>, Conflict<string>>> GetAllPackages(ValidationPackageDb database)
{
var packages = await database.ValidationPackages.ToArrayAsync();

// Hash validation
if (packages.Any(p => !ValidationPackageDb.ValidatePackageContent(p, database)))
{
return TypedResults.Conflict("Internal package hash collision");
}

Array.ForEach(packages, (p => ValidationPackageDb.IncrementDownloadCount(p, database)));
await database.SaveChangesAsync();

return TypedResults.Ok(packages);
}

public static async Task<Ok<ValidationPackage>> GetLatestPackageByName(string name, ValidationPackageDb database)
public static async Task<Results<Ok<ValidationPackage>, NotFound<string>, Conflict<string>>> GetLatestPackageByName(string name, ValidationPackageDb database)
{
var package = await database.ValidationPackages
.Where(p => p.Name == name)
Expand All @@ -22,10 +33,23 @@ public static async Task<Ok<ValidationPackage>> GetLatestPackageByName(string na
.ThenByDescending(p => p.PatchVersion)
.FirstOrDefaultAsync();

if (package is null)
{
return TypedResults.NotFound($"No package '{name}' available.");
}

if (!ValidationPackageDb.ValidatePackageContent(package, database))
{
return TypedResults.Conflict("Internal package hash collision");
}

ValidationPackageDb.IncrementDownloadCount(package, database);
await database.SaveChangesAsync();

return TypedResults.Ok(package);
}

public static async Task<Results<BadRequest<string>, NotFound, Ok<ValidationPackage>>> GetPackageByNameAndVersion(string name, string version, ValidationPackageDb database)
public static async Task<Results<BadRequest<string>, NotFound<string>, Conflict<string>, Ok<ValidationPackage>>> GetPackageByNameAndVersion(string name, string version, ValidationPackageDb database)
{
var splt = version.Split('.');
if (splt.Length != 3)
Expand All @@ -44,13 +68,21 @@ public static async Task<Results<BadRequest<string>, NotFound, Ok<ValidationPack
return TypedResults.BadRequest("version was not a of valid format MAJOR.MINOR.REVISION");
}


var package = await database.ValidationPackages.FindAsync(name, major, minor, revision);

if (package is null)
{
return TypedResults.NotFound();
return TypedResults.NotFound($"No package '{name}' @ {version} available.");
}

if (!ValidationPackageDb.ValidatePackageContent(package, database))
{
return TypedResults.Conflict("Internal package hash collision");
}

ValidationPackageDb.IncrementDownloadCount(package, database);
await database.SaveChangesAsync();

return TypedResults.Ok(package);
}

Expand Down
16 changes: 16 additions & 0 deletions src/PackageRegistryService/Data/DataInitializer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,22 @@ public static void SeedData(ValidationPackageDb context)

context.AddRange(hashes);

var downloads =
index
.Select((i) =>
{
return new PackageDownloads
{
PackageName = i.Metadata.Name,
PackageMajorVersion = i.Metadata.MajorVersion,
PackageMinorVersion = i.Metadata.MinorVersion,
PackagePatchVersion = i.Metadata.PatchVersion,
Downloads = 0
};
});

context.AddRange(downloads);

context.SaveChanges();
}
}
Expand Down

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 74b871e

Please sign in to comment.