diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..facb2b13
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,4 @@
+FROM mcr.microsoft.com/dotnet/aspnet:7.0
+WORKDIR /app
+COPY publish ./
+ENTRYPOINT ["dotnet", "CrystallineSociety.Server.Api.dll"]
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
new file mode 100644
index 00000000..ef1d2d16
--- /dev/null
+++ b/azure-pipelines.yml
@@ -0,0 +1,92 @@
+name: $(version).$(Build.BuildId)
+
+variables:
+ WEB_APP_DEPLOYMENT_TYPE: 'DefaultDeploymentType'
+
+jobs:
+
+- job: build_blazor_api_wasm
+ displayName: 'build blazor api + web assembly'
+
+ pool:
+ vmImage: 'ubuntu-latest'
+
+ steps:
+
+ - task: Assembly-Info-NetCore@3
+ displayName: 'Set Assembly Manifest Data'
+ inputs:
+ Company: Cs Internship
+ Product: Cs System
+ VersionNumber: '$(Build.BuildNumber)'
+ FileVersionNumber: '$(Build.BuildNumber)'
+ InformationalVersion: '$(Build.BuildNumber)'
+ PackageVersion: '$(Build.BuildNumber)'
+
+ - task: UseDotNet@2
+ displayName: 'Setup .NET'
+ inputs:
+ useGlobalJson: true
+ workingDirectory: 'src'
+
+ - task: UseDotNet@2
+ displayName: 'Use dotnet sdk 6.x for LibSassBuilder'
+ inputs:
+ version: 6.x
+
+ - task: Bash@3
+ displayName: 'Restore workloads'
+ inputs:
+ targetType: 'inline'
+ script: 'dotnet workload restore src/CrystallineSociety/Client/Web/CrystallineSociety.Client.Web.csproj -p:BlazorMode=BlazorWebAssembly'
+
+ - task: Bash@3
+ displayName: 'Switch to blazor web assembly'
+ inputs:
+ targetType: 'inline'
+ script: sed -i 's/Microsoft.NET.Sdk.Web/Microsoft.NET.Sdk.BlazorWebAssembly/g' src/CrystallineSociety/Client/Web/CrystallineSociety.Client.Web.csproj
+ - task: Bash@3
+ displayName: 'Build migrations bundle'
+ inputs:
+ targetType: 'inline'
+ script: |
+ dotnet tool install --global dotnet-ef --version 7.0.0
+ dotnet ef migrations bundle --self-contained -r linux-x64 --project src/CrystallineSociety/Server/Api/CrystallineSociety.Server.Api.csproj
+ failOnStderr: true
+ - task: Bash@3
+ displayName: 'Install wasm-tools'
+ inputs:
+ targetType: 'inline'
+ script: dotnet workload install wasm-tools
+
+ - task: Bash@3
+ displayName: 'Build (To generate CSS/JS files)'
+ inputs:
+ targetType: 'inline'
+ script: 'dotnet build src/CrystallineSociety/Client/Web/CrystallineSociety.Client.Web.csproj -p:Configuration=Release -p:BlazorMode=BlazorWebAssembly -p:WebAppDeploymentType="${{ variables.WEB_APP_DEPLOYMENT_TYPE }}"'
+
+ - task: Bash@3
+ displayName: 'Publish'
+ inputs:
+ targetType: 'inline'
+ script: 'dotnet publish src/CrystallineSociety/Server/Api/CrystallineSociety.Server.Api.csproj -p:BlazorMode=BlazorWebAssembly -p:WebAppDeploymentType="${{ variables.WEB_APP_DEPLOYMENT_TYPE }}" -p:Configuration=Release -o api-web'
+
+ - task: PublishPipelineArtifact@1
+ displayName: Upload api-web artifact
+ inputs:
+ targetPath: 'api-web'
+ artifact: 'api-web-bundle'
+ publishLocation: 'pipeline'
+
+ - task: PublishPipelineArtifact@1
+ displayName: Upload ef migrations bundle
+ inputs:
+ targetPath: 'efbundle'
+ artifact: 'migrations-bundle'
+ publishLocation: 'pipeline'
+ - task: PublishPipelineArtifact@1
+ inputs:
+ targetPath: 'Dockerfile'
+ artifact: 'Docker'
+ publishLocation: 'pipeline'
+
\ No newline at end of file
diff --git a/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor b/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor
new file mode 100644
index 00000000..ae0508f8
--- /dev/null
+++ b/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor
@@ -0,0 +1,15 @@
+@using CrystallineSociety.Shared.Dtos.BadgeSystem
+@inherits AppComponentBase
+
+@if (Badge != null)
+{
+
+
Badge Content
+
Badge Code : @Badge.Code
+
+
Badge Level : @Badge.Level
+
+
Badge Description : @Badge.Description
+
+}
+
diff --git a/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor.cs b/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor.cs
new file mode 100644
index 00000000..53b21121
--- /dev/null
+++ b/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor.cs
@@ -0,0 +1,14 @@
+using CrystallineSociety.Shared.Dtos.BadgeSystem;
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+namespace CrystallineSociety.Client.Shared.Components
+{
+ public partial class BadgeContent
+ {
+ [Parameter] public BadgeDto? Badge { get; set; }
+ }
+}
diff --git a/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor.scss b/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor.scss
new file mode 100644
index 00000000..e02abfc9
--- /dev/null
+++ b/src/CrystallineSociety/Client/Shared/Components/BadgeContent.razor.scss
@@ -0,0 +1 @@
+
diff --git a/src/CrystallineSociety/Client/Shared/Components/BadgeSystem.razor b/src/CrystallineSociety/Client/Shared/Components/BadgeSystem.razor
index fa897fab..e537cb12 100644
--- a/src/CrystallineSociety/Client/Shared/Components/BadgeSystem.razor
+++ b/src/CrystallineSociety/Client/Shared/Components/BadgeSystem.razor
@@ -1,6 +1,7 @@
-
- Tada from Badge System
+@using CrystallineSociety.Shared.Dtos.BadgeSystem
+@inherits AppComponentBase
+
+
+
+
-
- @GetBundleText(Bundle)
-
diff --git a/src/CrystallineSociety/Client/Shared/Components/BadgeSystem.razor.cs b/src/CrystallineSociety/Client/Shared/Components/BadgeSystem.razor.cs
index 8f406408..1ab98806 100644
--- a/src/CrystallineSociety/Client/Shared/Components/BadgeSystem.razor.cs
+++ b/src/CrystallineSociety/Client/Shared/Components/BadgeSystem.razor.cs
@@ -15,13 +15,9 @@ public partial class BadgeSystem
[AutoInject] private IBadgeSystemService BadgeSystemService { get; set; } = default!;
[AutoInject] private IBadgeUtilService BadgeUtilService { get; set; } = default!;
[Parameter] public BadgeBundleDto? Bundle { get; set; }
+ private BadgeDto? BadgeDto {get; set;}
- protected override Task OnInitializedAsync()
- {
- return base.OnInitializedAsync();
- }
-
private string? GetBundleText(BadgeBundleDto bundle)
{
var builder = new StringBuilder();
@@ -35,5 +31,10 @@ protected override Task OnInitializedAsync()
return builder.ToString();
}
+
+ private void GetBadge(BadgeDto badgeDto)
+ {
+ BadgeDto = badgeDto;
+ }
}
}
diff --git a/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor b/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor
new file mode 100644
index 00000000..4886aabb
--- /dev/null
+++ b/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor
@@ -0,0 +1,16 @@
+@using CrystallineSociety.Shared.Dtos.BadgeSystem
+@inherits AppComponentBase
+
+@if (Badges != null)
+{
+
+
+ OnBadgeClick(badge)">
+
Name: @badge.Code
+
+
+
+}
diff --git a/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor.cs b/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor.cs
new file mode 100644
index 00000000..9ef42581
--- /dev/null
+++ b/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+
+using CrystallineSociety.Shared.Dtos.BadgeSystem;
+
+namespace CrystallineSociety.Client.Shared.Components
+{
+ public partial class BadgeTree
+ {
+ [Parameter] public BadgeBundleDto? BadgeBundleDto { get; set; }
+ [Parameter] public EventCallback
BadgeDtoCallBack { get; set; }
+
+ private List? Badges { get; set; }
+
+ protected override Task OnParamsSetAsync()
+ {
+ if (BadgeBundleDto != null)
+ {
+ Badges = BadgeBundleDto.Badges.ToList();
+ }
+ return base.OnParamsSetAsync();
+ }
+
+ private async Task OnBadgeClick(BadgeDto badgeDto)
+ {
+ await BadgeDtoCallBack.InvokeAsync(badgeDto);
+ }
+ }
+}
diff --git a/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor.scss b/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor.scss
new file mode 100644
index 00000000..41353eb4
--- /dev/null
+++ b/src/CrystallineSociety/Client/Shared/Components/BadgeTree.razor.scss
@@ -0,0 +1,21 @@
+.list {
+ border: 1px #a19f9d solid;
+ border-radius: 3px;
+ height: auto;
+}
+
+.badge-row {
+
+ &:hover {
+ cursor: pointer;
+ background-color: lightskyblue;
+
+ div {
+ color: black;
+ }
+ }
+
+ .badge-name-color {
+ color: white;
+ }
+}
diff --git a/src/CrystallineSociety/Client/Shared/Pages/GitHubBadgeSystemExplorerPage.razor b/src/CrystallineSociety/Client/Shared/Pages/GitHubBadgeSystemExplorerPage.razor
index 2e580ff8..35878f5a 100644
--- a/src/CrystallineSociety/Client/Shared/Pages/GitHubBadgeSystemExplorerPage.razor
+++ b/src/CrystallineSociety/Client/Shared/Pages/GitHubBadgeSystemExplorerPage.razor
@@ -2,10 +2,14 @@
@using CrystallineSociety.Shared.Dtos.BadgeSystem
@inherits AppComponentBase
-
-
GitHub URL:
-
-
Load Badge System
-
@GitHubUrl
+
diff --git a/src/CrystallineSociety/CrystallineSociety.Server.Api.Test/GitHubBadgeServiceTests.cs b/src/CrystallineSociety/CrystallineSociety.Server.Api.Test/GitHubBadgeServiceTests.cs
index 57958e2f..4a086948 100644
--- a/src/CrystallineSociety/CrystallineSociety.Server.Api.Test/GitHubBadgeServiceTests.cs
+++ b/src/CrystallineSociety/CrystallineSociety.Server.Api.Test/GitHubBadgeServiceTests.cs
@@ -1,6 +1,7 @@
-using CrystallineSociety.Shared.Dtos.BadgeSystem;
using CrystallineSociety.Shared.Services.Implementations.BadgeSystem;
+using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Hosting;
+using Octokit;
namespace CrystallineSociety.Server.Api.Test
{
@@ -9,59 +10,260 @@ public class GitHubBadgeServiceTests
{
public TestContext TestContext { get; set; } = default!;
+
[TestMethod]
public async Task GitHubBadge_LoadSimple()
{
var testHost = Host.CreateDefaultBuilder()
- .ConfigureServices((_, services) =>
- {
- services.AddSharedServices();
- services.AddServerServices();
- }
- ).Build();
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
var githubService = testHost.Services.GetRequiredService
();
- var factory = testHost.Services.GetRequiredService();
var badgeUrl =
- "https://github.com/cs-internship/cs-system/tree/main/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample";
+ "https://github.com/hootanht/cs-system/blob/feature/initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec-badge.json";
+
var badge = await githubService.GetBadgeAsync(badgeUrl);
Assert.IsNotNull(badge);
+ }
+
+ [TestMethod]
+ public async Task GitHubBadge_LoadByFolderAddress_FileNotFoundException()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
+
+ var githubService = testHost.Services.GetRequiredService();
+ var factory = testHost.Services.GetRequiredService();
+
+ var badgesFolderUrl =
+ "https://github.com/hootanht/cs-system/blob/feature/initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample";
+
+
+ var exception = await Assert.ThrowsExceptionAsync(async () =>
+ {
+ await githubService.GetBadgeAsync(badgesFolderUrl);
+ });
+
+ Assert.IsNotNull(exception);
+ Assert.AreEqual($"Badge file not found in: {badgesFolderUrl}", exception.Message);
+ }
+
+ [TestMethod]
+ public async Task GetBadgeAsync_IncorrectBranchName_ThrowResourceNotFoundException()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
+
+ var githubService = testHost.Services.GetRequiredService();
+
+ var badgeUrl =
+ "https://github.com/hootanht/cs-system/tree/feature-initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample";
+
+ var ex = await Assert.ThrowsExceptionAsync(async () =>
+ {
+ await githubService.GetBadgeAsync(badgeUrl);
+ });
+ }
+
+ [TestMethod]
+ public async Task GetBadgeAsync_IncorrectOrganizationOrRepositoryName_ThrowOctokitNotFoundException()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
+
+ var githubService = testHost.Services.GetRequiredService();
+
+ var badgeUrl =
+ "https://github.com/hootanhtbug/cs-system/tree/feature/initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample";
+
+ var ex = await Assert.ThrowsExceptionAsync(async () =>
+ {
+ await githubService.GetBadgeAsync(badgeUrl);
+ });
+ }
+
+ [TestMethod]
+ public async Task GetBadgeAsync_InvalidBadgeFileName_ThrowFileNotFoundException()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
+
+ var githubService = testHost.Services.GetRequiredService();
+
+ var badgeUrl =
+ "https://github.com/hootanht/cs-system/tree/feature/initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec.json";
- var bundle = new BadgeBundleDto();
- bundle.Badges.Add(badge);
+ var exception = await Assert.ThrowsExceptionAsync(async () =>
+ {
+ await githubService.GetBadgeAsync(badgeUrl);
+ });
+ }
+
+ [TestMethod]
+ public async Task GetBadgeAsync_UnparsableBadgeFileFormat_ThrowFormatException()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
- var badgeSystem = factory.CreateNew(bundle);
+ var githubService = testHost.Services.GetRequiredService();
- Assert.IsNotNull(badgeSystem.Validations);
- Assert.IsFalse(badgeSystem.Validations.Any());
+ var badgeUrl =
+ "https://github.com/hootanht/cs-system/tree/feature/initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec-empty-badge.json";
+ var exception = await Assert.ThrowsExceptionAsync(async () =>
+ {
+ await githubService.GetBadgeAsync(badgeUrl);
+ });
+ }
+
+
+ [TestMethod]
+ public async Task GetBadgeAsync_InvalidRepoIdOrSha_ThrowNotFoundException()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
+
+ var githubService = testHost.Services.GetRequiredService();
+
+ long repoId = 619299373; //correct: 619299373
+ string invalidSha =
+ "cf74dc41bb1a474fe7ff3e2624aae055ee5338ec"; //correct: cf74dc41bb1a474fe7ff3e2624aae055ee5338eb
+
+ var exception = await Assert.ThrowsExceptionAsync(async () =>
+ {
+ await githubService.GetBadgeAsync(repoId, invalidSha);
+ });
+ }
+
+ [TestMethod]
+ public async Task GetBadgeAsync_ValidRepoIdAndSha_AreEqual()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
+
+ var githubService = testHost.Services.GetRequiredService();
+
+ long repoId = 619299373;
+ string sha = "cf74dc41bb1a474fe7ff3e2624aae055ee5338eb";
+
+ var result = await githubService.GetBadgeAsync(repoId, sha);
+ Assert.AreEqual("doc-beginner", result.Code);
+ }
+
+ [TestMethod]
+ public async Task GetLightBadgesAsync_ValidBadgeFileUrl_ThrowInvalidOperationException()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
+
+ var githubService = testHost.Services.GetRequiredService();
+ var badgesFolderUrl =
+ "https://github.com/hootanht/cs-system/blob/feature/initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec-badge.json";
+
+ var exception = await Assert.ThrowsExceptionAsync(async () =>
+ {
+ await githubService.GetLightBadgesAsync(badgesFolderUrl);
+ });
+ }
+
+ [TestMethod]
+ public async Task GetLightBadgesAsync_ValidBadgesFolderUrl_AreEqualShaValues()
+ {
+ var testHost = Host.CreateDefaultBuilder()
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
+
+ var githubService = testHost.Services.GetRequiredService();
+ var badgesFolderUrl =
+ "https://github.com/hootanht/cs-system/tree/feature/initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/github-sample-folder";
+
+ var result = await githubService.GetLightBadgesAsync(badgesFolderUrl);
+ Assert.AreEqual(result[0].Sha,
+ "cf74dc41bb1a474fe7ff3e2624aae055ee5338eb");
+ Assert.AreEqual(result[1].Sha,
+ "7399b9c26213221edf619a9ae5558f7138b970a2");
+ Assert.AreEqual(result[2].Sha,
+ "1c0ba797e86cd01c5ef35673f5951d41de4b69ce");
+ Assert.AreEqual(result[3].Sha,
+ "31b0dabb38f40a631aae96ae4066c828bfd21611");
+ Assert.AreEqual(result[4].Sha,
+ "05ac8473cee780f202ad1f77df18428db5631e85");
+ Assert.AreEqual(result[5].Sha,
+ "ce329c0bcc7307c53b8a695c59e62768c30e84e8");
}
[TestMethod]
public async Task GitHubBadgesList_LoadSimple()
{
var testHost = Host.CreateDefaultBuilder()
- .ConfigureServices((_, services) =>
- {
- services.AddSharedServices();
- services.AddServerServices();
- }
- ).Build();
+ .ConfigureServices((_, services) =>
+ {
+ services.AddSharedServices();
+ services.AddServerServices();
+ }
+ ).Build();
var githubService = testHost.Services.GetRequiredService();
var factory = testHost.Services.GetRequiredService();
var badgesFolderUrl =
- "https://github.com/cs-internship/cs-system/tree/main/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/github-sample-folder";
+ "https://github.com/hootanht/cs-system/tree/feature/initial-get-badge-system/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/github-sample-folder";
var badges = await githubService.GetBadgesAsync(badgesFolderUrl);
Assert.IsNotNull(badges);
Assert.AreEqual(6, badges.Count);
var badgeSystem = factory.CreateNew(badges);
-
}
}
}
\ No newline at end of file
diff --git a/src/CrystallineSociety/Server/Api/Extensions/IServiceCollectionExtensions.cs b/src/CrystallineSociety/Server/Api/Extensions/IServiceCollectionExtensions.cs
index 4d3036d4..02ffd905 100644
--- a/src/CrystallineSociety/Server/Api/Extensions/IServiceCollectionExtensions.cs
+++ b/src/CrystallineSociety/Server/Api/Extensions/IServiceCollectionExtensions.cs
@@ -9,6 +9,9 @@
using CrystallineSociety.Server.Api.AppHooks;
using CrystallineSociety.Server.Api.Models.Account;
using CrystallineSociety.Server.Api.Services.Implementations;
+using Octokit;
+using User = CrystallineSociety.Server.Api.Models.Account.User;
+
namespace Microsoft.Extensions.DependencyInjection;
@@ -16,9 +19,11 @@ public static class IServiceCollectionExtensions
{
public static void AddServerServices(this IServiceCollection services)
{
- services.AddTransient();
+ services.AddTransient();
services.AddAppHook();
services.AddTransient();
+ // ToDo: Complete.
+ services.AddTransient(CreateGitHubClient);
}
public static void AddIdentity(this IServiceCollection services, IConfiguration configuration)
@@ -176,4 +181,16 @@ public static void AddHealthChecks(this IServiceCollection services, IWebHostEnv
});
}
}
+
+ private static GitHubClient CreateGitHubClient(IServiceProvider serviceProvider)
+ {
+ var productHeaderValue = new ProductHeaderValue("CS-System");
+ var gitHubToken = "ghp_" + serviceProvider.GetRequiredService().GetSection("GitHub")["GitHubAccessToken"];
+ var tokenAuth = new Credentials(gitHubToken);
+ var client = new GitHubClient(productHeaderValue)
+ {
+ Credentials = tokenAuth
+ };
+ return client;
+ }
}
diff --git a/src/CrystallineSociety/Server/Api/Services/Implementations/GitHubBadgeService.cs b/src/CrystallineSociety/Server/Api/Services/Implementations/GitHubBadgeService.cs
new file mode 100644
index 00000000..821176d1
--- /dev/null
+++ b/src/CrystallineSociety/Server/Api/Services/Implementations/GitHubBadgeService.cs
@@ -0,0 +1,205 @@
+using System.Reflection.Metadata;
+using System.Text;
+using System.Text.RegularExpressions;
+
+using CrystallineSociety.Shared.Dtos.BadgeSystem;
+
+using Octokit;
+
+namespace CrystallineSociety.Server.Api.Services.Implementations
+{
+ public partial class GitHubBadgeService : IGitHubBadgeService
+ {
+ [AutoInject] public IBadgeUtilService BadgeUtilService { get; set; }
+
+ [AutoInject] public GitHubClient GitHubClient { get; set; }
+
+ ///
+ /// Asynchronously retrieves objects from badge files located at the GitHub URL provided.
+ /// This method performs a recursive search for files and selects those whose filename ends with `-badge.json`.
+ ///
+ /// GitHub folder URL containing badge files.
+ /// A task that represents a list of parsed badge files.
+ /// When the RepoId of light badge is null.
+ /// When the Sha of light badge is null.
+ public async Task> GetBadgesAsync(string folderUrl)
+ {
+ var lightBadges = await GetLightBadgesAsync(folderUrl);
+
+ var badges = new List();
+
+ foreach (var lightBadge in lightBadges)
+ {
+ if (lightBadge.Url is null)
+ continue;
+
+ //todo Replace Exception with NullReferenceException or another relevant one
+ var badgeDto = await GetBadgeAsync(
+ lightBadge.RepoId ?? throw new Exception("RepoId of light badge is null."),
+ lightBadge.Sha ?? throw new Exception("Sha of light badge is null.")
+ );
+
+ badges.Add(badgeDto);
+ }
+
+ return badges;
+ }
+
+ ///
+ /// Asynchronously loads and parses a lightweight version of badges specifications from a GitHub URL pointing to a folder recursively.
+ ///
+ /// The GitHub URL pointing to a folder containing badge file(s). All badge files will load recursively. Badge filename must ends with `-badge.json`.
+ /// A task that represents a list of lightweight version of s.
+ public async Task> GetLightBadgesAsync(string folderUrl)
+ {
+ var (orgName, repoName) = GetRepoAndOrgNameFromUrl(folderUrl);
+ var repo = await GitHubClient.Repository.Get(orgName, repoName);
+ var refs = await GitHubClient.Git.Reference.GetAll(repo.Id);
+
+ var lastSegment = GetLastSegmentFromUrl(folderUrl,refs,out var parentFolderPath);
+ var repositoryId = repo.Id;
+
+ var folderContents = await GitHubClient.Repository.Content.GetAllContents(repositoryId, parentFolderPath);
+ var folderSha = folderContents?.First(f => f.Name == lastSegment).Sha;
+ var allContents = await GitHubClient.Git.Tree.GetRecursive(repositoryId, folderSha);
+
+ return allContents.Tree
+ .Where(t=>t.Type == TreeType.Blob )
+ .Select(t => new BadgeDto { RepoId = repositoryId, Sha = t.Sha, Url = t.Url })
+ .ToList();
+ }
+
+ ///
+ /// Asynchronously loads a badge specification from the given and parses it.
+ ///
+ /// The badge file GitHub URL. Badge filename must ends with `-badge.json`.
+ /// A task that represents the parsed badge object.
+ /// When unable to locate the GitHub repo branchName.
+ /// When the given is not a valid badge file URL.
+ /// When the loaded badge file has an incorrect format and cannot be parsed.
+ public async Task GetBadgeAsync(string badgeUrl)
+ {
+ var (orgName, repoName) = GetRepoAndOrgNameFromUrl(badgeUrl);
+ var repo = await GitHubClient.Repository.Get(orgName, repoName);
+ var refs = await GitHubClient.Git.Reference.GetAll(repo.Id);
+ var branchName = GetBranchNameFromUrl(badgeUrl, refs) ??
+ throw new ResourceNotFoundException($"Unable to locate branchName: {badgeUrl}");
+
+ var branchRef = refs.First(r => r.Ref.Contains($"refs/heads/{branchName}"));
+ var badgeFilePath = GetRelativePath(badgeUrl.EndsWith("-badge.json")
+ ? badgeUrl
+ : throw new FileNotFoundException($"Badge file not found in: {badgeUrl}"), refs);
+
+ var badgeFileContentByte =
+ await GitHubClient.Repository.Content.GetRawContentByRef(orgName, repoName, badgeFilePath, branchRef.Ref);
+
+ var badgeFileContent = Encoding.UTF8.GetString(badgeFileContentByte);
+
+ try
+ {
+ var badge = BadgeUtilService.ParseBadge(badgeFileContent);
+ return badge;
+ }
+ catch (Exception exception)
+ {
+ throw new FormatException($"Can not parse badge with badgeUrl: '{badgeUrl}'", exception);
+ }
+ }
+
+ ///
+ /// Asynchronously loads and parses a badge specification from a badge file on GitHub identified by the and parameters.
+ ///
+ /// The Id of the repository on GtiHub.
+ /// The SHA Id of the file in the repository on GtiHub.
+ /// A task that represents the parsed badge object.
+ public async Task GetBadgeAsync(long repositoryId, string sha)
+ {
+ var badgeBlob = await GitHubClient.Git.Blob.Get(repositoryId, sha);
+
+ var bytes = Convert.FromBase64String(badgeBlob.Content);
+ var badgeContent = Encoding.UTF8.GetString(bytes);
+ var badgeDto = BadgeUtilService.ParseBadge(badgeContent);
+ return badgeDto;
+ }
+
+ ///
+ /// The relative path is created by eliminating the URL segments from the left up to the branch name. Branch name is exclude.
+ ///
+ /// A GitHub URL pointing to a file/folder.
+ /// Octokit references to the GitHub repo.
+ /// The relative path.
+ private static string? GetRelativePath(string url, IEnumerable refs)
+ {
+ var uri = new Uri(url);
+ var afterTreeSegments = string.Join("", uri.Segments[4..]);
+ foreach (var reference in refs)
+ {
+ var branchInRefWithEndingSlash = $"{Regex.Replace(reference.Ref, @"^[^/]+/[^/]+/", "")}/";
+
+ if (!afterTreeSegments.StartsWith(branchInRefWithEndingSlash))
+ continue;
+
+ var path = afterTreeSegments.Replace(branchInRefWithEndingSlash, string.Empty).TrimEnd('/');
+ return path;
+ }
+
+ return null;
+ }
+
+ ///
+ /// Get the branch name from a GitHub URL.
+ ///
+ /// A GitHub URL.
+ /// Octokit references to the GitHub repo.
+ /// The branch name.
+ private static string? GetBranchNameFromUrl(string url, IEnumerable refs)
+ {
+ var uri = new Uri(url);
+ var afterTreeSegments = string.Join("", uri.Segments[4..]);
+ foreach (var reference in refs)
+ {
+ var branchInRefWithEndingSlash = $"{Regex.Replace(reference.Ref, @"^[^/]+/[^/]+/", "")}/";
+ if (afterTreeSegments.StartsWith(branchInRefWithEndingSlash))
+ {
+ return branchInRefWithEndingSlash.TrimEnd('/');
+ }
+ }
+
+ return null;
+ }
+
+ ///
+ /// Extract the last segment of a GitHub URL pointing to a folder.
+ ///
+ /// A GitHub URL pointing to a folder.
+ /// Octokit references to the GitHub repo.
+ /// Extracted parent folder of the given GitHub URL.
+ /// The last segment of a GitHub URL.
+ private static string GetLastSegmentFromUrl(string url, IEnumerable refs, out string? parentFolderPath)
+ {
+ var uri = new Uri(url);
+ var lastSegment = uri.Segments.Last().TrimEnd('/');
+ var parentFolderUrl = uri.GetLeftPart(UriPartial.Authority) +
+ string.Join("", uri.Segments.Take(uri.Segments.Length - 1));
+
+ parentFolderPath = GetRelativePath(parentFolderUrl, refs);
+
+ return lastSegment;
+ }
+
+ ///
+ /// Retrieves a repository and organization/owner name from GitHub URL.
+ ///
+ /// A GitHub URL
+ /// The repository and organization/owner name.
+ private static (string org, string repo) GetRepoAndOrgNameFromUrl(string url)
+ {
+ var uri = new Uri(url);
+ var segments = uri.Segments;
+ var org = segments[1].TrimEnd('/');
+ var repo = segments[2].TrimEnd('/');
+
+ return (org, repo);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/CrystallineSociety/Server/Api/Services/Implementations/ServerGitHubBadgeService.cs b/src/CrystallineSociety/Server/Api/Services/Implementations/ServerGitHubBadgeService.cs
deleted file mode 100644
index 55be8194..00000000
--- a/src/CrystallineSociety/Server/Api/Services/Implementations/ServerGitHubBadgeService.cs
+++ /dev/null
@@ -1,39 +0,0 @@
-using CrystallineSociety.Shared.Dtos.BadgeSystem;
-using Octokit;
-
-namespace CrystallineSociety.Server.Api.Services.Implementations
-{
- public partial class ServerGitHubBadgeService : IGitHubBadgeService
- {
- [AutoInject]
- public IBadgeUtilService BadgeUtilService { get; set; }
-
- public async Task> GetBadgesAsync(string url)
- {
- // Todo: return the real list.
- return new List()
- {
- BadgeUtilService.ParseBadge($$"""{"code": "github-test-badge-a", "description": "from: {{url}}"}"""),
- BadgeUtilService.ParseBadge($$"""{"code": "github-test-badge-b", "description": "from: {{url}}"}"""),
- };
- }
-
- public async Task GetBadgeAsync(string url)
- {
- throw new NotImplementedException();
- //var client = new GitHubClient(new ProductHeaderValue("CS-System"));
- //var repos = await client.Repository.GetAllForOrg("cs-internship");
- //var repo = repos.First(r => r.Name == "cs-system");
-
- //var refs= await client.Git.Reference.GetAll(repo.Id);
-
- //var main = refs.First(r => r.Ref.Contains("refs/heads/main"));
- ////var main = refs.First(r => r.Ref.Contains("refs/heads/main"));
- //var refx =
- // "refs/heads/main/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample";
- //var xx = await client.Git.Tree.GetRecursive(repo.Id, main.Ref);
-
- //return default;
- }
- }
-}
diff --git a/src/CrystallineSociety/Server/Api/appsettings.json b/src/CrystallineSociety/Server/Api/appsettings.json
index d7422a3f..d9a28fc0 100644
--- a/src/CrystallineSociety/Server/Api/appsettings.json
+++ b/src/CrystallineSociety/Server/Api/appsettings.json
@@ -1,44 +1,47 @@
{
- "ConnectionStrings": {
- "SqlServerConnectionString": "Data Source=.\\sqlexpress; Initial Catalog=CrystallineSocietyDb;Integrated Security=true;Application Name=Todo;TrustServerCertificate=True;"
+ "ConnectionStrings": {
+ "SqlServerConnectionString": "Data Source=.\\sqlexpress; Initial Catalog=CrystallineSocietyDb;Integrated Security=true;Application Name=Todo;TrustServerCertificate=True;"
+ },
+ "GitHub": {
+ "GitHubAccessToken": "5MttcI7TZ8cijBPklSxwUE5zKgOjKo1bnaTr"
+ },
+ "Logging": {
+ "LogLevel": {
+ "Default": "Information",
+ "Microsoft": "Warning",
+ "Microsoft.Hosting.Lifetime": "Information"
+ }
+ },
+ "AppSettings": {
+ "JwtSettings": {
+ "IdentityCertificatePassword": "P@ssw0rdP@ssw0rd",
+ "Issuer": "CrystallineSociety",
+ "Audience": "CrystallineSociety",
+ "NotBeforeMinutes": "0",
+ "ExpirationMinutes": "1440"
},
- "Logging": {
- "LogLevel": {
- "Default": "Information",
- "Microsoft": "Warning",
- "Microsoft.Hosting.Lifetime": "Information"
- }
+ "IdentitySettings": {
+ "PasswordRequireDigit": "false",
+ "PasswordRequiredLength": "6",
+ "PasswordRequireNonAlphanumeric": "false",
+ "PasswordRequireUppercase": "false",
+ "PasswordRequireLowercase": "false",
+ "RequireUniqueEmail": "true",
+ "ConfirmationEmailResendDelay": "0.00:02:00", //Format: D.HH:mm:nn
+ "ResetPasswordEmailResendDelay": "0.00:02:00" //Format: D.HH:mm:nn
},
- "AppSettings": {
- "JwtSettings": {
- "IdentityCertificatePassword": "P@ssw0rdP@ssw0rd",
- "Issuer": "CrystallineSociety",
- "Audience": "CrystallineSociety",
- "NotBeforeMinutes": "0",
- "ExpirationMinutes": "1440"
- },
- "IdentitySettings": {
- "PasswordRequireDigit": "false",
- "PasswordRequiredLength": "6",
- "PasswordRequireNonAlphanumeric": "false",
- "PasswordRequireUppercase": "false",
- "PasswordRequireLowercase": "false",
- "RequireUniqueEmail": "true",
- "ConfirmationEmailResendDelay": "0.00:02:00", //Format: D.HH:mm:nn
- "ResetPasswordEmailResendDelay": "0.00:02:00" //Format: D.HH:mm:nn
- },
- "EmailSettings": {
- "Host": "LocalFolder", // Local folder means storing emails in bin\sent-emails folder (Recommended for testing purposes only) instead of sending them using smtp server.
- "Port": "25",
- "DefaulFromEmail": "info@CrystallineSociety.com",
- "DefaultFromName": "CrystallineSociety",
- "UserName": null,
- "Password": null
- },
- "HealthCheckSettings": {
- "EnableHealthChecks": false
- },
- "UserProfileImagePath": "Attachments/Profiles/"
+ "EmailSettings": {
+ "Host": "LocalFolder", // Local folder means storing emails in bin\sent-emails folder (Recommended for testing purposes only) instead of sending them using smtp server.
+ "Port": "25",
+ "DefaulFromEmail": "info@CrystallineSociety.com",
+ "DefaultFromName": "CrystallineSociety",
+ "UserName": null,
+ "Password": null
},
- "AllowedHosts": "*"
+ "HealthCheckSettings": {
+ "EnableHealthChecks": false
+ },
+ "UserProfileImagePath": "Attachments/Profiles/"
+ },
+ "AllowedHosts": "*"
}
diff --git a/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec-badge.json b/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec-badge.json
new file mode 100644
index 00000000..6d89aadf
--- /dev/null
+++ b/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec-badge.json
@@ -0,0 +1,59 @@
+{
+ "code": "doc-guru-badge-sample",
+ "description": "Description for doc-guru-badge-sample",
+ "level": "Gold",
+ "info": {
+ "en": "info_en.md",
+ "fa": "info_fa.md"
+ },
+ "appraisal-methods": [
+ {
+ "title": "Main Method",
+ "badge-requirements": [
+ "requirement-badge-code-A*2",
+ "requirement-badge-code-B",
+ "requirement-badge-code-C*1|requirement-badge-code-D*2"
+ ],
+ "activity-requirements": [
+ "requirement-activity-code-A",
+ "requirement-activity-code-B*1",
+ "requirement-activity-code-C|requirement-activity-code-D*2"
+ ],
+ "approving-steps": [
+ {
+ "step": 1,
+ "title": "Initial Approval",
+ "approver-required-badges": [
+ "requirement-badge-code-A*2",
+ "requirement-badge-code-B",
+ "requirement-badge-code-C*2|requirement-badge-code-D"
+ ],
+ "required-approval-count": 2
+ },
+ {
+ "step": 2,
+ "title": "Final Approval",
+ "approver-required-badges": [
+ "requirement-badge-code-D*2"
+ ],
+ "required-approval-count": 2
+ }
+ ]
+ },
+ {
+ "title": "Superpower Method",
+ "badge-requirements": [],
+ "activity-requirements": [],
+ "approving-steps": [
+ {
+ "step": 1,
+ "title": "Superpower Admin",
+ "approver-required-badges": [
+ "superpower"
+ ],
+ "required-approval-count": 1
+ }
+ ]
+ }
+ ]
+}
\ No newline at end of file
diff --git a/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec-empty-badge.json b/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/BadgeSystem/SampleBadgeDocs/serialization-badge-sample/spec-empty-badge.json
new file mode 100644
index 00000000..e69de29b
diff --git a/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/CrystallineSociety.Shared.Test.csproj b/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/CrystallineSociety.Shared.Test.csproj
index 62c9f522..53878c5c 100644
--- a/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/CrystallineSociety.Shared.Test.csproj
+++ b/src/CrystallineSociety/Shared/CrystallineSociety.Shared.Test/CrystallineSociety.Shared.Test.csproj
@@ -18,6 +18,7 @@
+
@@ -55,6 +56,12 @@
Always
+
+ Always
+
+
+ Always
+
Always
diff --git a/src/CrystallineSociety/Shared/Shared/Dtos/BadgeSystem/BadgeBundleDto.cs b/src/CrystallineSociety/Shared/Shared/Dtos/BadgeSystem/BadgeBundleDto.cs
index 9cd1253b..7753d3e1 100644
--- a/src/CrystallineSociety/Shared/Shared/Dtos/BadgeSystem/BadgeBundleDto.cs
+++ b/src/CrystallineSociety/Shared/Shared/Dtos/BadgeSystem/BadgeBundleDto.cs
@@ -8,6 +8,9 @@ namespace CrystallineSociety.Shared.Dtos.BadgeSystem
{
public class BadgeBundleDto
{
+ public List Badges { get; set; } = new();
+ public List? Validations { get; set; }
+
public BadgeBundleDto()
{
@@ -18,9 +21,6 @@ public BadgeBundleDto(List badges)
Badges = badges;
}
- public List Badges { get; set; } = new();
- public List? Validations { get; set; }
-
public bool BadgeExists(string badgeCode)
{
return Badges.Any(b => b.Code == badgeCode);
diff --git a/src/CrystallineSociety/Shared/Shared/Dtos/BadgeSystem/BadgeDto.cs b/src/CrystallineSociety/Shared/Shared/Dtos/BadgeSystem/BadgeDto.cs
index 5952581b..01468015 100644
--- a/src/CrystallineSociety/Shared/Shared/Dtos/BadgeSystem/BadgeDto.cs
+++ b/src/CrystallineSociety/Shared/Shared/Dtos/BadgeSystem/BadgeDto.cs
@@ -9,15 +9,18 @@ namespace CrystallineSociety.Shared.Dtos.BadgeSystem
{
public class BadgeDto
{
- public string Code { get; set; } = default!;
+ public string? Code { get; set; } = default!;
public string? Description { get; set; }
- public BadgeLevel Level { get; set; }
- public Dictionary Info { get; set; } = new();
- public List AppraisalMethods { get; set; } = new();
-
+ public BadgeLevel? Level { get; set; }
+ public Dictionary? Info { get; set; } = new();
+ public List? AppraisalMethods { get; set; } = new();
+
+ public string? Sha { get; set; }
+ public string? Url { get; set; }
+ public long? RepoId { get; set; }
public override string ToString()
{
- return Code;
+ return Code ?? "";
}
}
diff --git a/src/CrystallineSociety/Shared/Shared/Exceptions/FileContentIsNullException.cs b/src/CrystallineSociety/Shared/Shared/Exceptions/FileContentIsNullException.cs
new file mode 100644
index 00000000..06c4d1e9
--- /dev/null
+++ b/src/CrystallineSociety/Shared/Shared/Exceptions/FileContentIsNullException.cs
@@ -0,0 +1,21 @@
+using System.Runtime.Serialization;
+
+namespace CrystallineSociety.Shared.Exceptions;
+
+public class FileContentIsNullException : IOException
+{
+ public FileContentIsNullException()
+ : base(nameof(AppStrings.FileContentIsNullException))
+ {
+ }
+
+ public FileContentIsNullException(string message)
+ : base(message)
+ {
+ }
+
+ public FileContentIsNullException(string message, Exception? innerException)
+ : base(message, innerException)
+ {
+ }
+}
diff --git a/src/CrystallineSociety/Shared/Shared/Resources/AppStrings.Designer.cs b/src/CrystallineSociety/Shared/Shared/Resources/AppStrings.Designer.cs
index 040c053d..5c501970 100644
--- a/src/CrystallineSociety/Shared/Shared/Resources/AppStrings.Designer.cs
+++ b/src/CrystallineSociety/Shared/Shared/Resources/AppStrings.Designer.cs
@@ -502,6 +502,15 @@ public static string Error {
}
}
+ ///
+ /// Looks up a localized string similar to File content is null..
+ ///
+ public static string FileContentIsNullException {
+ get {
+ return ResourceManager.GetString("FileContentIsNullException", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to The {0} field only accepts files with the following extensions: {1}.
///
diff --git a/src/CrystallineSociety/Shared/Shared/Resources/AppStrings.resx b/src/CrystallineSociety/Shared/Shared/Resources/AppStrings.resx
index 086eca07..4a33cd58 100644
--- a/src/CrystallineSociety/Shared/Shared/Resources/AppStrings.resx
+++ b/src/CrystallineSociety/Shared/Shared/Resources/AppStrings.resx
@@ -207,8 +207,7 @@
Error when passwords do not have an uppercase letter
- {0} must be at least {1} characters.
- Error message for passwords that are too short
+ The field {0} must be a string or array type with a minimum length of '{1}'.
Role {0} does not exist.
@@ -535,9 +534,6 @@ Please confirm your email by clicking on the link.
MinLengthAttribute must have a Length value that is zero or greater.
-
- The field {0} must be a string or array type with a minimum length of '{1}'.
-
The {0} field is not a valid phone number.
@@ -649,4 +645,7 @@ Please confirm your email by clicking on the link.
Title
+
+ File content is null.
+
\ No newline at end of file
diff --git a/src/CrystallineSociety/Shared/Shared/Services/Contracts/IGitHubBadgeService.cs b/src/CrystallineSociety/Shared/Shared/Services/Contracts/IGitHubBadgeService.cs
index 2f1a100b..f0893ea2 100644
--- a/src/CrystallineSociety/Shared/Shared/Services/Contracts/IGitHubBadgeService.cs
+++ b/src/CrystallineSociety/Shared/Shared/Services/Contracts/IGitHubBadgeService.cs
@@ -9,7 +9,39 @@ namespace CrystallineSociety.Shared.Services.Contracts
{
public interface IGitHubBadgeService
{
- Task> GetBadgesAsync(string url);
- Task GetBadgeAsync(string url);
+ ///
+ /// Asynchronously retrieves objects from badge files located at the GitHub URL provided.
+ /// This method performs a recursive search for files and selects those whose filename ends with `-badge.json`.
+ ///
+ /// GitHub folder URL containing badge files.
+ /// A task that represents a list of parsed badge files.
+ /// When the RepoId of light badge is null.
+ /// When the Sha of light badge is null.
+ Task> GetBadgesAsync(string folderUrl);
+
+ ///
+ /// Asynchronously loads a badge specification from the given and parses it.
+ ///
+ /// The badge file GitHub URL. Badge filename must ends with `-badge.json`.
+ /// A task that represents the parsed badge object.
+ /// When unable to locate the GitHub repo branchName.
+ /// When the given is not a valid badge file URL.
+ /// When the loaded badge file has an incorrect format and cannot be parsed.
+ Task GetBadgeAsync(string badgeUrl);
+
+ ///
+ /// Asynchronously loads and parses a badge specification from a badge file on GitHub identified by the and parameters.
+ ///
+ /// The Id of the repository on GtiHub.
+ /// The SHA Id of the file in the repository on GtiHub.
+ /// A task that represents the parsed badge object.
+ Task GetBadgeAsync(long repositoryId, string sha);
+
+ ///
+ /// Asynchronously loads and parses a lightweight version of badges specifications from a GitHub URL pointing to a folder recursively.
+ ///
+ /// The GitHub URL pointing to a folder containing badge file(s). All badge files will load recursively. Badge filename must ends with `-badge.json`.
+ /// A task that represents a list of lightweight version of s.
+ Task> GetLightBadgesAsync(string folderUrl);
}
-}
+}
\ No newline at end of file