Skip to content

Commit 13d584c

Browse files
committed
refactor: standardize "GitHub" casing across services, interfaces, and handlers
1 parent a9082e9 commit 13d584c

12 files changed

Lines changed: 191 additions & 99 deletions

src/BadgeSmith.Api/Domain/Services/Contracts/IGithubOrgSecretsService.cs renamed to src/BadgeSmith.Api/Domain/Services/Contracts/IGitHubOrgSecretsService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
using BadgeSmith.Api.Domain.Services.Github;
1+
using BadgeSmith.Api.Domain.Services.GitHub;
22

33
namespace BadgeSmith.Api.Domain.Services.Contracts;
44

55
/// <summary>
66
/// Service for managing GitHub authentication secrets with caching.
77
/// Retrieves Personal Access Tokens from AWS Secrets Manager and caches them in-memory.
88
/// </summary>
9-
internal interface IGithubOrgSecretsService
9+
internal interface IGitHubOrgSecretsService
1010
{
1111
/// <summary>
1212
/// Retrieves a GitHub Personal Access Token for the specified organization.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using BadgeSmith.Api.Domain.Services.GitHub;
2+
3+
namespace BadgeSmith.Api.Domain.Services.Contracts;
4+
5+
internal interface IGitHubPackageServiceFactory
6+
{
7+
public GitHubOrgSecretsService GitHubOrgSecretsService { get; }
8+
public GitHubPackageService GitHubPackageService { get; }
9+
}

src/BadgeSmith.Api/Domain/Services/Contracts/IGithubPackageServiceFactory.cs

Lines changed: 0 additions & 8 deletions
This file was deleted.

src/BadgeSmith.Api/Domain/Services/Github/GithubOrgSecretsService.cs renamed to src/BadgeSmith.Api/Domain/Services/GitHub/GitHubOrgSecretsService.cs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -8,24 +8,24 @@
88
using Microsoft.Extensions.Logging;
99
using ResourceNotFoundException = Amazon.SecretsManager.Model.ResourceNotFoundException;
1010

11-
namespace BadgeSmith.Api.Domain.Services.Github;
11+
namespace BadgeSmith.Api.Domain.Services.GitHub;
1212

13-
internal sealed class GithubOrgSecretsService : IGithubOrgSecretsService
13+
internal sealed class GitHubOrgSecretsService : IGitHubOrgSecretsService
1414
{
1515
private readonly IAmazonSecretsManager _secretsManager;
1616
private readonly IAmazonDynamoDB _dynamoDb;
1717
private readonly string _tableName;
1818
private readonly IAppCache _cache;
19-
private readonly ILogger<GithubOrgSecretsService> _logger;
19+
private readonly ILogger<GitHubOrgSecretsService> _logger;
2020

2121
private static readonly TimeSpan CacheTtl = TimeSpan.FromMinutes(15);
2222

23-
public GithubOrgSecretsService(
23+
public GitHubOrgSecretsService(
2424
IAmazonSecretsManager secretsManager,
2525
IAmazonDynamoDB dynamoDb,
2626
string tableName,
2727
IAppCache cache,
28-
ILogger<GithubOrgSecretsService> logger)
28+
ILogger<GitHubOrgSecretsService> logger)
2929
{
3030
_secretsManager = secretsManager ?? throw new ArgumentNullException(nameof(secretsManager));
3131
_dynamoDb = dynamoDb ?? throw new ArgumentNullException(nameof(dynamoDb));
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
using BadgeSmith.Api.Domain.AWS;
2+
using BadgeSmith.Api.Domain.Services.Contracts;
3+
using BadgeSmith.Api.Domain.Services.Package;
4+
using BadgeSmith.Api.Infrastructure.Caching;
5+
using BadgeSmith.Api.Infrastructure.Http;
6+
using BadgeSmith.Api.Infrastructure.Observability;
7+
8+
namespace BadgeSmith.Api.Domain.Services.GitHub;
9+
10+
internal class GitHubPackageServiceFactory : IGitHubPackageServiceFactory
11+
{
12+
private static readonly Lazy<GitHubOrgSecretsService> GithubOrgSecretsServiceLazy = new(CreateGithubOrgSecretsService);
13+
private static readonly Lazy<GitHubPackageService> GitHubPackageServiceLazy = new(CreateGitHubPackageService);
14+
15+
public GitHubOrgSecretsService GitHubOrgSecretsService => GithubOrgSecretsServiceLazy.Value;
16+
public GitHubPackageService GitHubPackageService => GitHubPackageServiceLazy.Value;
17+
18+
private static GitHubOrgSecretsService CreateGithubOrgSecretsService()
19+
{
20+
var amazonDynamoDbClient = AwsClientFactory.AmazonDynamoDbClient;
21+
var amazonSecretsManagerClient = AwsClientFactory.AmazonSecretsManagerClient;
22+
23+
var secretsTableName = Environment.GetEnvironmentVariable("AWS_RESOURCE_ORG_SECRETS_TABLE");
24+
25+
if (string.IsNullOrWhiteSpace(secretsTableName))
26+
{
27+
throw new InvalidOperationException("AWS_RESOURCE_ORG_SECRETS_TABLE environment variable is not set");
28+
}
29+
30+
var githubSecretsLogger = LoggerFactory.CreateLogger<GitHubOrgSecretsService>();
31+
32+
var memoryAppCache = new MemoryAppCache();
33+
return new GitHubOrgSecretsService(amazonSecretsManagerClient, amazonDynamoDbClient, secretsTableName, memoryAppCache, githubSecretsLogger);
34+
}
35+
36+
private static GitHubPackageService CreateGitHubPackageService()
37+
{
38+
var githubClient = HttpClientFactory.CreateGithubClient();
39+
var githubOrgSecretsService = GithubOrgSecretsServiceLazy.Value;
40+
var nuGetVersionService = new NuGetVersionService();
41+
var memoryAppCache = new MemoryAppCache();
42+
var gitHubPackageLogger = LoggerFactory.CreateLogger<GitHubPackageService>();
43+
44+
return new GitHubPackageService(githubClient, githubOrgSecretsService, nuGetVersionService, memoryAppCache, gitHubPackageLogger);
45+
}
46+
}
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
using System.Text.Json.Serialization;
2+
using OneOf;
3+
4+
namespace BadgeSmith.Api.Domain.Services.GitHub;
5+
6+
internal record SecretNotFound(string Reason) : NotFoundFailure(Reason);
7+
8+
internal sealed record PackageNotFound(string Reason) : NotFoundFailure(Reason);
9+
10+
[GenerateOneOf]
11+
internal partial class GithubSecretResult : OneOfBase<string, SecretNotFound, Error>
12+
{
13+
public bool IsSuccess => IsT0 && AsT0 != null;
14+
15+
public bool IsFailure => !IsSuccess;
16+
17+
public string? GithubSecret => AsT0;
18+
19+
public OneOf<SecretNotFound, Error> Failure
20+
{
21+
get
22+
{
23+
if (TryPickT1(out var notFound, out _))
24+
{
25+
return notFound;
26+
}
27+
28+
if (TryPickT2(out var error, out _))
29+
{
30+
return error;
31+
}
32+
33+
throw new InvalidOperationException("Failure was not found");
34+
}
35+
}
36+
}
37+
38+
[GenerateOneOf]
39+
internal sealed partial class GitHubPackageResult : OneOfBase<GitHubPackageInfo, PackageNotFound, Error>
40+
{
41+
public bool IsSuccess => IsT0 && AsT0 != null;
42+
public GitHubPackageInfo? GitHubPackageInfo => IsT0 ? AsT0 : null;
43+
44+
public OneOf<PackageNotFound, Error> Failure => IsT0
45+
? throw new InvalidOperationException("Result is successful")
46+
: Match<OneOf<PackageNotFound, Error>>(
47+
_ => throw new InvalidOperationException("Result is successful"),
48+
notFound => notFound,
49+
error => error
50+
);
51+
}
52+
53+
internal sealed record GitHubPackageInfo(string PackageName, string Organization, string VersionString, bool IsPrerelease, DateTime? LastModifiedUtc);
54+
55+
internal record GithubPackageVersion(
56+
[property: JsonPropertyName("id")] int Id,
57+
[property: JsonPropertyName("name")] string Name,
58+
[property: JsonPropertyName("url")] string Url,
59+
[property: JsonPropertyName("package_html_url")] string PackageHtmlUrl,
60+
[property: JsonPropertyName("created_at")] DateTime CreatedAt,
61+
[property: JsonPropertyName("updated_at")] DateTime UpdatedAt,
62+
[property: JsonPropertyName("html_url")] string HtmlUrl,
63+
[property: JsonPropertyName("metadata")] PackageMetadata Metadata
64+
);
65+
66+
internal record PackageMetadata(
67+
[property: JsonPropertyName("package_type")] string PackageType
68+
);

src/BadgeSmith.Api/Domain/Services/Github/GithubPackageServiceFactory.cs

Lines changed: 0 additions & 31 deletions
This file was deleted.

src/BadgeSmith.Api/Domain/Services/Github/GithubResults.cs

Lines changed: 0 additions & 33 deletions
This file was deleted.

src/BadgeSmith.Api/Infrastructure/Handlers/GithubPackagesBadgeHandler.cs

Lines changed: 46 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using Amazon.Lambda.APIGatewayEvents;
1+
using Amazon.Lambda.APIGatewayEvents;
22
using BadgeSmith.Api.Domain.Models;
33
using BadgeSmith.Api.Domain.Services.Contracts;
44
using BadgeSmith.Api.Infrastructure.Handlers.Contracts;
@@ -12,12 +12,14 @@ namespace BadgeSmith.Api.Infrastructure.Handlers;
1212
internal class GithubPackagesBadgeHandler : IGithubPackagesBadgeHandler
1313
{
1414
private readonly ILogger<GithubPackagesBadgeHandler> _logger;
15-
private readonly IGithubOrgSecretsService _githubOrgSecretsService;
15+
private readonly IGitHubOrgSecretsService _gitHubOrgSecretsService;
16+
private readonly IGitHubPackageService _gitHubPackageService;
1617

17-
public GithubPackagesBadgeHandler(ILogger<GithubPackagesBadgeHandler> logger, IGithubOrgSecretsService githubOrgSecretsService)
18+
public GithubPackagesBadgeHandler(ILogger<GithubPackagesBadgeHandler> logger, IGitHubOrgSecretsService gitHubOrgSecretsService, IGitHubPackageService gitHubPackageService)
1819
{
1920
_logger = logger;
20-
_githubOrgSecretsService = githubOrgSecretsService;
21+
_gitHubOrgSecretsService = gitHubOrgSecretsService;
22+
_gitHubPackageService = gitHubPackageService;
2123
}
2224

2325
public async Task<APIGatewayHttpApiV2ProxyResponse> HandleAsync(RouteContext routeContext, CancellationToken ct = default)
@@ -28,10 +30,18 @@ public async Task<APIGatewayHttpApiV2ProxyResponse> HandleAsync(RouteContext rou
2830
return errorResponse!;
2931
}
3032

31-
// ReSharper disable once UnusedVariable
32-
#pragma warning disable S1481
33-
var secret = await _githubOrgSecretsService.GetGitHubTokenAsync(org, ct).ConfigureAwait(false);
34-
#pragma warning restore S1481
33+
var orgLower = org.ToLowerInvariant();
34+
35+
var tokenResult = await _gitHubOrgSecretsService.GetGitHubTokenAsync(orgLower, ct).ConfigureAwait(false);
36+
if (tokenResult is { IsSuccess: false, GithubSecret: null })
37+
{
38+
return tokenResult.Failure.Match(
39+
_ => ResponseHelper.Unauthorized(),
40+
error => ResponseHelper.InternalServerError(error.ToErrorResponse())
41+
);
42+
}
43+
44+
var token = tokenResult.GithubSecret;
3545

3646
_logger.LogInformation("Github packages badge request received for {Org}/{Package}", org, packageId);
3747

@@ -43,16 +53,35 @@ public async Task<APIGatewayHttpApiV2ProxyResponse> HandleAsync(RouteContext rou
4353
SwrSeconds: 15,
4454
SieSeconds: 60);
4555

46-
var shieldsBadgeResponse = new ShieldsBadgeResponse(1, "github", "1.0.0", "green", NamedLogo: "github");
47-
48-
await Task.Yield(); // Ensure we're truly async
56+
var packageResult = await _gitHubPackageService.GetLatestVersionAsync(org, packageId, ct: ct).ConfigureAwait(false);
4957

50-
return ResponseHelper.OkCached(
51-
shieldsBadgeResponse,
52-
LambdaFunctionJsonSerializerContext.Default.ShieldsBadgeResponse,
53-
ifNoneMatchHeader: ifNoneMatch,
54-
cache: cache,
55-
lastModifiedUtc: null // set to a real value when you have ‘updatedAt’
58+
return packageResult.Match(
59+
success =>
60+
{
61+
var shieldsBadgeResponse = new ShieldsBadgeResponse(1, "github", success.VersionString, "green", NamedLogo: "github");
62+
return ResponseHelper.OkCached(
63+
shieldsBadgeResponse,
64+
LambdaFunctionJsonSerializerContext.Default.ShieldsBadgeResponse,
65+
ifNoneMatchHeader: ifNoneMatch,
66+
cache: cache,
67+
lastModifiedUtc: success.LastModifiedUtc);
68+
},
69+
_ =>
70+
{
71+
_logger.LogWarning("GitHub package not found: {Org}/{Package}", org, packageId);
72+
var error = new ErrorResponse(
73+
$"Package '{packageId}' not found in organization '{org}'",
74+
[new ErrorDetail("PACKAGE_NOT_FOUND", "package")]);
75+
return ResponseHelper.NotFound(error);
76+
},
77+
gitHubError =>
78+
{
79+
_logger.LogError("GitHub API error for {Org}/{Package}: {Message}", org, packageId, gitHubError.Reason);
80+
var error = new ErrorResponse(
81+
"GitHub API error occurred",
82+
[new ErrorDetail("GITHUB_API_ERROR", "server")]);
83+
return ResponseHelper.InternalServerError(error);
84+
}
5685
);
5786
}
5887

src/BadgeSmith.Api/Infrastructure/Handlers/HandlerFactoy.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#pragma warning disable S125, RCS1093
22

3-
using BadgeSmith.Api.Domain.Services.Github;
3+
using BadgeSmith.Api.Domain.Services.GitHub;
44
using BadgeSmith.Api.Domain.Services.Nuget;
55
using BadgeSmith.Api.Infrastructure.Handlers.Contracts;
66
using BadgeSmith.Api.Infrastructure.Observability;
@@ -45,8 +45,8 @@ private static NuGetPackageBadgeHandler CreateNugetPackageBadgeHandler()
4545
private static GithubPackagesBadgeHandler CreateGithubPackagesBadgeHandler()
4646
{
4747
var githubPackagesLogger = LoggerFactory.CreateLogger<GithubPackagesBadgeHandler>();
48-
var githubPackageServiceFactory = new GithubPackageServiceFactory();
49-
return new GithubPackagesBadgeHandler(githubPackagesLogger, githubPackageServiceFactory.GithubOrgSecretsService);
48+
var factory = new GitHubPackageServiceFactory();
49+
return new GithubPackagesBadgeHandler(githubPackagesLogger, factory.GitHubOrgSecretsService, factory.GitHubPackageService);
5050
}
5151

5252
private static TestResultsBadgeHandler CreateTestResultsBadgeHandler()

0 commit comments

Comments
 (0)