diff --git a/DotNetRu.Commune.GithubFilesystem/ClientFactory.cs b/DotNetRu.Commune.GithubFilesystem/ClientFactory.cs new file mode 100644 index 0000000..009dd7f --- /dev/null +++ b/DotNetRu.Commune.GithubFilesystem/ClientFactory.cs @@ -0,0 +1,42 @@ +using Octokit; +using Octokit.Internal; + +namespace DotNetRu.Commune.GithubFileSystem +{ + /// + /// Factoy for building clients + /// + public class ClientFactory + { + private const string ProductName = "DotNetRuCommune"; + + /// + /// Build anonymous client - no credentials needed + /// + /// Github client with anonymous credentials + public GitHubClient Anonymous() + { + var credStore = new InMemoryCredentialStore(Credentials.Anonymous); + var client = new GitHubClient(new Connection(new ProductHeaderValue(ProductName), + GitHubClient.GitHubApiUrl, credStore, + new HttpClientAdapter(Net5HttpMessageHandlerFactory.CreateDefault), + new SimpleJsonSerializer())); + return client; + } + + /// + /// Get an authenticated client with specific token + /// + /// personal access token for github + /// GitHUb client with injeccted personal access token + public GitHubClient WithToken(string token) + { + var credStore = new InMemoryCredentialStore(new (token, AuthenticationType.Bearer)); + var client = new GitHubClient(new Connection(new ProductHeaderValue(ProductName), + GitHubClient.GitHubApiUrl, credStore, + new HttpClientAdapter(Net5HttpMessageHandlerFactory.CreateDefault), + new SimpleJsonSerializer())); + return client; + } + } +} diff --git a/DotNetRu.Commune.GithubFilesystem/DotNetRu.Commune.GithubFilesystem.csproj b/DotNetRu.Commune.GithubFilesystem/DotNetRu.Commune.GithubFilesystem.csproj index 2b613b1..d2a6fb9 100644 --- a/DotNetRu.Commune.GithubFilesystem/DotNetRu.Commune.GithubFilesystem.csproj +++ b/DotNetRu.Commune.GithubFilesystem/DotNetRu.Commune.GithubFilesystem.csproj @@ -9,7 +9,7 @@ - + diff --git a/DotNetRu.Commune.GithubFilesystem/GitHubDirectory.cs b/DotNetRu.Commune.GithubFilesystem/GitHubDirectory.cs new file mode 100644 index 0000000..e51559a --- /dev/null +++ b/DotNetRu.Commune.GithubFilesystem/GitHubDirectory.cs @@ -0,0 +1,86 @@ +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using DotNetRu.Auditor.Storage.FileSystem; +using Octokit; + +namespace DotNetRu.Commune.GithubFileSystem +{ + /// + /// Virtual directory in github repository. Implements + /// + public class GitHubDirectory : GitHubFilesystemEntry, IDirectory + { + private GitHubDirectory(IGitHubClient gitHubClient, Repository repository, Reference branch, string name, string fullName) : + base(gitHubClient, repository, branch, name, fullName) + { + } + + /// + /// Factory method for building root directory + /// + /// Github client for this directory, stores authentication data in it + /// repository, contents are accessed to + /// branch in the reposiroty, to work with + /// new directory instance pointing to the root of content of this branch in this repository + public static IDirectory ForRoot(IGitHubClient gitHubClient, Repository repository, Reference branch) + { + return new GitHubDirectory(gitHubClient, repository, branch, string.Empty, "/"); + } + + private string GetChildFullName(string childDirectoryName) => + FullName switch + { + null => childDirectoryName, + "" => childDirectoryName, + {} s when s.EndsWith("/") => $"{FullName}{childDirectoryName}", + _ => $"{FullName}/{childDirectoryName}" + }; + + /// + public IDirectory GetDirectory(string childDirectoryName) + { + var childFullName = GetChildFullName(childDirectoryName); + return new GitHubDirectory(GitHubClient, Repository, Branch, childDirectoryName, childFullName); + } + + /// + public IFile GetFile(string childFileName) + { + var childFullName = GetChildFullName(childFileName); + return new GitHubFile(GitHubClient, Repository, Branch, childFileName, childFullName); + } + + /// + public async IAsyncEnumerable EnumerateDirectoriesAsync() + { + var contents = await ContentsClient.GetAllContentsByRef(Repository.Id, FullName, Branch.Ref) + .ConfigureAwait(false); + foreach (var content in contents.Where(x => x.Type.Value == ContentType.Dir)) + { + yield return new GitHubDirectory(GitHubClient, Repository, Branch, content.Name, content.Path); + } + } + + /// + public async IAsyncEnumerable EnumerateFilesAsync() + { + var contents = await ContentsClient.GetAllContentsByRef(Repository.Id, FullName, Branch.Ref) + .ConfigureAwait(false); + foreach (var content in contents.Where(x => x.Type.Value == ContentType.File)) + { + yield return new GitHubFile(GitHubClient, Repository, Branch, content.Name, content.Path); + } + } + + /// + public override async ValueTask ExistsAsync() + { + var parentDirectory = GetParentDirectory(); + var contents = await ContentsClient.GetAllContentsByRef(Repository.Id, parentDirectory, Branch.Ref) + .ConfigureAwait(false); + + return contents.Any(x => x.Name == Name && x.Type.Value == ContentType.File); + } + } +} diff --git a/DotNetRu.Commune.GithubFilesystem/GitHubFile.cs b/DotNetRu.Commune.GithubFilesystem/GitHubFile.cs new file mode 100644 index 0000000..2425362 --- /dev/null +++ b/DotNetRu.Commune.GithubFilesystem/GitHubFile.cs @@ -0,0 +1,50 @@ +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using DotNetRu.Auditor.Storage.FileSystem; +using Octokit; + +namespace DotNetRu.Commune.GithubFileSystem +{ + /// + /// Files from github repository + /// + public class GitHubFile : GitHubFilesystemEntry, IFile + { + /// + /// ctor + /// + /// github client + /// github repository + /// branch in this repository + /// file name + /// file full path in repository + public GitHubFile(IGitHubClient gitHubClient, Repository repository, Reference branch, string name, string fullName) : + base(gitHubClient, repository, branch, name, fullName) + { + } + + /// + public override async ValueTask ExistsAsync() + { + var parentDirectory = GetParentDirectory(); + var contents = await ContentsClient.GetAllContentsByRef(Repository.Id, parentDirectory, Branch.Ref) + .ConfigureAwait(false); + + return contents.Any(x => x.Name == Name && x.Type.Value == ContentType.File); + } + + /// + public async Task OpenForReadAsync() + { + var contents = await ContentsClient + .GetRawContentByRef(Repository.Owner.Login, Repository.Name, FullName, Branch.Ref) + .ConfigureAwait(false); + return new MemoryStream(contents, false); + } + + /// + public Task RequestWriteAccessAsync() => throw new System.NotImplementedException(); + + } +} diff --git a/DotNetRu.Commune.GithubFilesystem/GitHubFilesystemEntry.cs b/DotNetRu.Commune.GithubFilesystem/GitHubFilesystemEntry.cs new file mode 100644 index 0000000..ece93fa --- /dev/null +++ b/DotNetRu.Commune.GithubFilesystem/GitHubFilesystemEntry.cs @@ -0,0 +1,69 @@ +using System.Threading.Tasks; +using DotNetRu.Auditor.Storage.FileSystem; +using Octokit; + +namespace DotNetRu.Commune.GithubFileSystem +{ + /// + /// base class for all filesystem objects - directories and files + /// + public abstract class GitHubFilesystemEntry : IFileSystemEntry + { + /// + /// Github client, used to access github data + /// + protected readonly IGitHubClient GitHubClient; + + /// + /// repository in github where data contents are stored + /// + protected readonly Repository Repository; + + /// + /// repository branch wich contents are browsed or modified + /// + protected readonly Reference Branch; + + /// + /// helper property to access contents client. Contents client is a wrapper over contents endpoint, it is used for manipulating data in repository + /// + protected IRepositoryContentsClient ContentsClient => GitHubClient.Repository.Content; + + /// + /// ctor + /// + /// github client + /// github repository + /// branch in repository + /// name of this entry + /// full name, aka path of this entry + protected GitHubFilesystemEntry(IGitHubClient gitHubClient, Repository repository, Reference branch, string name, string fullName) + { + GitHubClient = gitHubClient; + Repository = repository; + Branch = branch; + Name = name; + FullName = fullName; + } + + /// + public string Name { get; } + + /// + public string FullName { get; } + + /// + public abstract ValueTask ExistsAsync(); + + /// + /// Get parent directory name containing this entry + /// + /// path of the parent directory + protected string GetParentDirectory() => + FullName.Remove(FullName.Length - Name.Length) switch + { + "/" => string.Empty, + {} s => s + }; + } +} diff --git a/DotNetRu.Commune.GithubFilesystem/GithubFileSystem.cs b/DotNetRu.Commune.GithubFilesystem/GithubFileSystem.cs deleted file mode 100644 index c718baa..0000000 --- a/DotNetRu.Commune.GithubFilesystem/GithubFileSystem.cs +++ /dev/null @@ -1,68 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Threading.Tasks; -using DotNetRu.Auditor.Storage.FileSystem; -using Octokit; -using Octokit.Helpers; -using Octokit.Internal; - -namespace DotNetRu.Commune.GithubFileSystem -{ - /// - /// Virtual filesystem implementation using GitHub repositories for storing files. Implements - /// - public class GithubFileSystem : IFileSystem - { - private const string ProductName = "DotNetRuCommune"; - private EditingContext? _editingContext; - - /// - /// Begin work with filesytem. Creates a fork of original repository, then creates a new branch in this fork and - /// stores all the data inside editing context. Without calling this method you can't do anything else - enumerate, read, create - /// - /// Personal access token - /// Original repository name - /// Original repository owner (user or organization) - public async Task StartContext(string token, string originRepo, string originOwner) - { - var credStore = new InMemoryCredentialStore(new(token, AuthenticationType.Bearer)); - var client = new GitHubClient(new Connection(new ProductHeaderValue(ProductName), - GitHubClient.GitHubApiUrl, credStore, - new HttpClientAdapter(Net5HttpMessageHandlerFactory.CreateDefault), - new SimpleJsonSerializer())); - - var originalRepo = await client.Repository.Get(originOwner, originRepo).ConfigureAwait(false); - var originalBranch = await client.Git.Reference.Get(originalRepo.Id, "heads/master").ConfigureAwait(false); - var fork = await client.Repository.Forks.Create(originalRepo.Id, new ()).ConfigureAwait(false); - var currentBranch = await client.Git.Reference.CreateBranch(fork.Owner.Login, fork.Name, Guid.NewGuid().ToString("N")).ConfigureAwait(false); - _editingContext = new EditingContext(client, originalRepo, originalBranch, fork, currentBranch); - } - - /// - public string Name => "/"; - - /// - public string FullName => "/"; - - /// - public bool Exists => true; - - /// - public ValueTask GetDirectoryInfoAsync(string subPath) => throw new System.NotImplementedException(); - - /// - public ValueTask GetFileInfoAsync(string subPath) => throw new System.NotImplementedException(); - - /// - public IAsyncEnumerable EnumerateDirectoriesAsync() => throw new System.NotImplementedException(); - - /// - public IAsyncEnumerable EnumerateFilesAsync() => throw new System.NotImplementedException(); - - /// - public ValueTask CreateFileAsync(string subPath) => throw new System.NotImplementedException(); - - /// - public ValueTask DeleteFileAsync(string subPath) => throw new System.NotImplementedException(); - } -} diff --git a/DotNetRu.Commune.WasmClient/BizLayerServiceRegistry.cs b/DotNetRu.Commune.WasmClient/BizLayerServiceRegistry.cs index 7bab80e..8889b39 100644 --- a/DotNetRu.Commune.WasmClient/BizLayerServiceRegistry.cs +++ b/DotNetRu.Commune.WasmClient/BizLayerServiceRegistry.cs @@ -1,5 +1,6 @@ using System; using System.Diagnostics.CodeAnalysis; +using DotNetRu.Commune.GithubFileSystem; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection.Extensions; @@ -16,11 +17,11 @@ internal static class BizLayerServiceRegistry /// коллекция служб /// она же для соединения в цепочку /// если переданная коллекция была null - [return:NotNull] public static IServiceCollection AddBizLogic([NotNull]this IServiceCollection services) + public static IServiceCollection AddBizLogic(this IServiceCollection services) { if (services is null) throw new ArgumentNullException(nameof(services)); // здесь регистрируются службы слоя бизнес-логики - services.TryAddSingleton(); + services.TryAddSingleton(); return services; } } diff --git a/DotNetRu.Commune.WasmClient/DotNetRu.Commune.WasmClient.csproj b/DotNetRu.Commune.WasmClient/DotNetRu.Commune.WasmClient.csproj index 5f13f53..5299770 100644 --- a/DotNetRu.Commune.WasmClient/DotNetRu.Commune.WasmClient.csproj +++ b/DotNetRu.Commune.WasmClient/DotNetRu.Commune.WasmClient.csproj @@ -8,16 +8,17 @@ - - + + + - - - - + + + + diff --git a/DotNetRu.Commune.WasmClient/Pages/Index.razor b/DotNetRu.Commune.WasmClient/Pages/Index.razor index e461fab..19d2429 100644 --- a/DotNetRu.Commune.WasmClient/Pages/Index.razor +++ b/DotNetRu.Commune.WasmClient/Pages/Index.razor @@ -3,35 +3,73 @@ @using Microsoft.Extensions.Options @using DotNetRu.Commune.WasmClient.Model @using DotNetRu.Commune.GithubFileSystem +@using Radzen +@using DotNetRu.Auditor.Storage +@using DotNetRu.Auditor.Data.Model @inject ILogger _logger; -@inject GithubFileSystem githubFs; +@inject ClientFactory _clientFactory; @inject IOptions auditSettings; @inject NavigationManager _navigationManager; +@inject NotificationService _notificationService; DotNetRu Commune - - Это тестовое приложение DotNetRU.Commune. - Введите свой PAT для авторизации клиента github: - - Продолжить - + + + + + + + + + + + + + + + + + + + + + + + + + + + @code { - string pat = string.Empty; - - private async Task ProvidePat() + private List _communities = new(); + private int _communityCnt; + private RadzenDataGrid? _dataGrid; + protected override async Task OnInitializedAsync() { - try - { - await githubFs.StartContext(pat, auditSettings.Value.RepositoryName, auditSettings.Value.OriginalOwner); - _navigationManager.NavigateTo("AuthSuccess"); - } - catch (Exception e) + var client = _clientFactory.Anonymous(); + + var repository = await client.Repository.Get(auditSettings.Value.OriginalOwner, auditSettings.Value.RepositoryName); + _logger.LogDebug("Got repository for {Owner}/{RepoName}: {@Repo}", auditSettings.Value.OriginalOwner, auditSettings.Value.RepositoryName, repository); + + var masterBranch = await client.Git.Reference.Get(repository.Id, "heads/master"); + _logger.LogDebug("Got masterbransh ref: {@Branch}", masterBranch); + + var auditDirectory = GitHubDirectory.ForRoot(client, repository, masterBranch).GetDirectory("db"); + var store = await AuditStore.OpenAsync(auditDirectory); + var session = store.OpenSession(); + var communities = session.QueryAsync(); + _logger.LogDebug("Got communities, starting to enumerate the sequence"); + var buffer = new List(); + await foreach (var item in communities) { - _logger.LogError(e, "Ошибка старта контекста реадктирования"); - _navigationManager.NavigateTo("AuthFailed"); + _logger.LogDebug("Got new community: {@Commuinity}", item); + buffer.Add(item); } + _communities = buffer; + _communityCnt = _communities.Count; + await base.OnInitializedAsync(); } } diff --git a/DotNetRu.Commune.WasmClient/Program.cs b/DotNetRu.Commune.WasmClient/Program.cs index 22c524d..f8f7702 100644 --- a/DotNetRu.Commune.WasmClient/Program.cs +++ b/DotNetRu.Commune.WasmClient/Program.cs @@ -4,6 +4,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Radzen; using Serilog; namespace DotNetRu.Commune.WasmClient @@ -29,6 +30,13 @@ public static async Task Main(string[] args) /// Конфигурация private static void ConfigureServices(IServiceCollection services, IConfiguration configuration) { + // registering radzen blazor services for using notifications, dialogs, tooltips and custom context menus + services + .AddScoped() + .AddScoped() + .AddScoped() + .AddScoped(); + services.Configure(configuration.GetSection(nameof(AuditSettings))); services.AddBizLogic(); } diff --git a/DotNetRu.Commune.WasmClient/Shared/MainLayout.razor b/DotNetRu.Commune.WasmClient/Shared/MainLayout.razor index 2c5c391..f163994 100644 --- a/DotNetRu.Commune.WasmClient/Shared/MainLayout.razor +++ b/DotNetRu.Commune.WasmClient/Shared/MainLayout.razor @@ -7,3 +7,8 @@ + + + + + diff --git a/DotNetRu.Commune.WasmClient/_Imports.razor b/DotNetRu.Commune.WasmClient/_Imports.razor index 38bbe35..504943a 100644 --- a/DotNetRu.Commune.WasmClient/_Imports.razor +++ b/DotNetRu.Commune.WasmClient/_Imports.razor @@ -8,3 +8,4 @@ @using Microsoft.JSInterop @using DotNetRu.Commune.WasmClient @using DotNetRu.Commune.WasmClient.Shared +@using Radzen.Blazor diff --git a/DotNetRu.Commune.WasmClient/wwwroot/appsettings.json b/DotNetRu.Commune.WasmClient/wwwroot/appsettings.json index d797186..891984d 100644 --- a/DotNetRu.Commune.WasmClient/wwwroot/appsettings.json +++ b/DotNetRu.Commune.WasmClient/wwwroot/appsettings.json @@ -11,7 +11,7 @@ } }, "AuditSettings":{ - "RepositoryName":"DEMO", - "OriginalOwner":"SelectFromGroup-By" + "RepositoryName":"Audit", + "OriginalOwner":"DotNetRu" } } diff --git a/DotNetRu.Commune.WasmClient/wwwroot/index.html b/DotNetRu.Commune.WasmClient/wwwroot/index.html index cf39cdc..728eca1 100644 --- a/DotNetRu.Commune.WasmClient/wwwroot/index.html +++ b/DotNetRu.Commune.WasmClient/wwwroot/index.html @@ -9,6 +9,7 @@ + @@ -20,6 +21,7 @@ 🗙 +
- Это тестовое приложение DotNetRU.Commune. - Введите свой PAT для авторизации клиента github: - - Продолжить -