Compare commits
3 Commits
3b4ecefb08
...
4d24eb80f9
| Author | SHA1 | Date | |
|---|---|---|---|
| 4d24eb80f9 | |||
| 621d9f4318 | |||
| d2041ea5e4 |
@ -6,6 +6,8 @@
|
||||
<entry key="Flawless.Client.Avanonia/Views/MainWindow.axaml" value="Flawless.Client.Avanonia/Flawless.Client.Avanonia.csproj" />
|
||||
<entry key="Flawless.Client/App.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Theme/ToggleSwitch.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloSetup/LoginPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloSetup/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HelloWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/HomeView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
@ -13,7 +15,15 @@
|
||||
<entry key="Flawless.Client/Views/MainView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/MainWindow.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/MainWindowView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/ModalBox/CreateRepositoryDialog.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RegisterPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RegisterView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RepositoryPage/RepositoryDashboardPageView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/RepositoryView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/ServerConnectView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
<entry key="Flawless.Client/Views/ServerConnectionView.axaml" value="Flawless.Client/Flawless.Client.csproj" />
|
||||
|
||||
2
Flawless-Version-Control.sln.DotSettings
Normal file
2
Flawless-Version-Control.sln.DotSettings
Normal file
@ -0,0 +1,2 @@
|
||||
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
|
||||
<s:Boolean x:Key="/Default/CodeEditing/SuppressUninitializedWarningFix/Enabled/@EntryValue">False</s:Boolean></wpf:ResourceDictionary>
|
||||
15
Flawless.Client/AppDefaultValues.cs
Normal file
15
Flawless.Client/AppDefaultValues.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
|
||||
namespace Flawless.Client;
|
||||
|
||||
public static class AppDefaultValues
|
||||
{
|
||||
public const string HostId = "Overlay";
|
||||
|
||||
public static string ProgramDataDirectory { get; } =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData), "Ca2dWorks", "FlawlessClient");
|
||||
|
||||
public static string DefaultRepositoryDirectory { get; } =
|
||||
Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments), "FlawlessRepositories");
|
||||
}
|
||||
@ -14,6 +14,7 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="11.2.1" />
|
||||
<PackageReference Include="Avalonia.Controls.TreeDataGrid" Version="11.1.1" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="11.2.1" />
|
||||
<PackageReference Include="Avalonia.Fonts.Inter" Version="11.2.1" />
|
||||
<!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
|
||||
@ -25,8 +26,10 @@
|
||||
<PackageReference Include="Irihi.Ursa" Version="1.10.0" />
|
||||
<PackageReference Include="Irihi.Ursa.ReactiveUIExtension" Version="1.0.1" />
|
||||
<PackageReference Include="Irihi.Ursa.Themes.Semi" Version="1.10.0" />
|
||||
<PackageReference Include="Markdown.Avalonia" Version="11.0.2" />
|
||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="9.0.3" />
|
||||
<PackageReference Include="Microsoft.Extensions.Hosting" Version="9.0.3" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
|
||||
<PackageReference Include="ReactiveUI.SourceGenerators" Version="2.1.27">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
@ -36,10 +39,21 @@
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\Flawless.Communication\Flawless.Communication.csproj" />
|
||||
<UpToDateCheckInput Remove="Views\Templates\WithBackButtonLayout.axaml" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<UpToDateCheckInput Remove="Views\Templates\WithBackButtonLayout.axaml" />
|
||||
<Compile Update="Views\HelloSetup\LoginPageView.axaml.cs">
|
||||
<DependentUpon>LoginPageView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Views\HelloSetup\RegisterPageView.axaml.cs">
|
||||
<DependentUpon>RegisterPageView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
<Compile Update="Views\HelloSetup\ServerSetupPageView.axaml.cs">
|
||||
<DependentUpon>ServerSetupPageView.axaml</DependentUpon>
|
||||
<SubType>Code</SubType>
|
||||
</Compile>
|
||||
</ItemGroup>
|
||||
</Project>
|
||||
|
||||
11
Flawless.Client/Models/AppSettingModel.cs
Normal file
11
Flawless.Client/Models/AppSettingModel.cs
Normal file
@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
|
||||
namespace Flawless.Client.Models;
|
||||
|
||||
[Serializable]
|
||||
public partial class AppSettingModel : ReactiveModel
|
||||
{
|
||||
[Reactive, NonSerialized]
|
||||
private string _repositoryPath = AppDefaultValues.DefaultRepositoryDirectory;
|
||||
}
|
||||
6
Flawless.Client/Models/CommitModel.cs
Normal file
6
Flawless.Client/Models/CommitModel.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Flawless.Client.Models;
|
||||
|
||||
public record CommitModel : ReactiveRecordModel
|
||||
{
|
||||
|
||||
}
|
||||
7
Flawless.Client/Models/ReactiveModel.cs
Normal file
7
Flawless.Client/Models/ReactiveModel.cs
Normal file
@ -0,0 +1,7 @@
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Flawless.Client.Models;
|
||||
|
||||
public abstract class ReactiveModel : ReactiveObject {}
|
||||
|
||||
public abstract record ReactiveRecordModel : ReactiveRecord {}
|
||||
@ -1,27 +0,0 @@
|
||||
using System;
|
||||
using Flawless.Communication.Response;
|
||||
using Flawless.Communication.Shared;
|
||||
|
||||
namespace Flawless.Client.Models;
|
||||
|
||||
public record RepositoryHomePageModel(
|
||||
string OwnerName,
|
||||
string Name,
|
||||
string Description,
|
||||
bool IsArchived,
|
||||
bool IsOwner,
|
||||
string LatestCommitId)
|
||||
{
|
||||
public string FullName { get; } = $"{OwnerName}/{Name}";
|
||||
|
||||
public static RepositoryHomePageModel FromResponse(RepositoryInfoResponse r)
|
||||
{
|
||||
return new RepositoryHomePageModel(
|
||||
r.OwnerUsername,
|
||||
r.RepositoryName,
|
||||
r.Description ?? String.Empty,
|
||||
r.IsArchived,
|
||||
r.Role == RepositoryRole.Owner,
|
||||
r.LatestCommitId.ToString().Substring(0, 6));
|
||||
}
|
||||
}
|
||||
80
Flawless.Client/Models/RepositoryModel.cs
Normal file
80
Flawless.Client/Models/RepositoryModel.cs
Normal file
@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Collections.ObjectModel;
|
||||
using Flawless.Client.Remote;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
|
||||
namespace Flawless.Client.Models;
|
||||
|
||||
public partial class RepositoryModel : ReactiveModel
|
||||
{
|
||||
public static string GetStandaloneName(string name, string ownerName)
|
||||
{
|
||||
return $"{ownerName}/{name}";
|
||||
}
|
||||
|
||||
[Reactive] private string _standaloneName;
|
||||
|
||||
[Reactive] private string _ownerName;
|
||||
|
||||
[Reactive] private string _name;
|
||||
|
||||
[Reactive] private bool _archived;
|
||||
|
||||
[Reactive] private string _description;
|
||||
|
||||
[Reactive] private bool _ownByCurrentUser;
|
||||
|
||||
[Reactive] private bool _isDownloaded;
|
||||
|
||||
public ObservableCollection<Member> Members { get; } = new();
|
||||
|
||||
public ObservableCollection<Commit> Commits { get; } = new();
|
||||
|
||||
public ObservableCollection<Depot> Depots { get; } = new();
|
||||
|
||||
public ObservableCollection<Lock> Locks { get; } = new();
|
||||
|
||||
public enum RepositoryRole
|
||||
{
|
||||
Guest = 0,
|
||||
Reporter = 1,
|
||||
Developer = 2,
|
||||
Owner = 3,
|
||||
}
|
||||
|
||||
public partial class Member : ReactiveModel
|
||||
{
|
||||
[Reactive] private string _username;
|
||||
|
||||
[Reactive] private RepositoryRole _role;
|
||||
}
|
||||
|
||||
public partial class Commit : ReactiveModel
|
||||
{
|
||||
[Reactive] private Guid _commitId;
|
||||
|
||||
[Reactive] private string _author;
|
||||
|
||||
[Reactive] private DateTime _committedOn;
|
||||
|
||||
[Reactive] private string _message;
|
||||
|
||||
[Reactive] private Guid _depotId;
|
||||
}
|
||||
|
||||
public partial class Lock : ReactiveModel
|
||||
{
|
||||
[Reactive] private string _path;
|
||||
|
||||
[Reactive] private string _ownerName;
|
||||
}
|
||||
|
||||
public partial class Depot : ReactiveModel
|
||||
{
|
||||
[Reactive] private Guid _depotId;
|
||||
|
||||
[Reactive] private long _length;
|
||||
|
||||
public ObservableCollection<Guid> Dependencies { get; } = new();
|
||||
}
|
||||
}
|
||||
6
Flawless.Client/Models/UserModel.cs
Normal file
6
Flawless.Client/Models/UserModel.cs
Normal file
@ -0,0 +1,6 @@
|
||||
namespace Flawless.Client.Models;
|
||||
|
||||
public record UserModel : ReactiveRecordModel
|
||||
{
|
||||
|
||||
}
|
||||
@ -1,4 +1,7 @@
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Flawless.Client.Remote;
|
||||
using ReactiveUI;
|
||||
@ -6,15 +9,24 @@ using Refit;
|
||||
|
||||
namespace Flawless.Client.Service;
|
||||
|
||||
public class Api
|
||||
public class Api : BaseService<Api>
|
||||
{
|
||||
#region Instance
|
||||
class ApiAuthenticationHandler : DelegatingHandler
|
||||
{
|
||||
public ApiAuthenticationHandler(HttpMessageHandler innerHandler) : base(innerHandler)
|
||||
{
|
||||
}
|
||||
|
||||
private static Api? _instance;
|
||||
protected override Task<HttpResponseMessage> SendAsync(
|
||||
HttpRequestMessage request,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var tk = Api.C._token.Value;
|
||||
if (tk != null) request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", tk.Token);
|
||||
return base.SendAsync(request, cancellationToken);
|
||||
}
|
||||
|
||||
public static Api Current => _instance ??= new Api();
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
public IObservable<bool> IsLoggedIn => _isLoggedIn;
|
||||
|
||||
@ -49,12 +61,11 @@ public class Api
|
||||
_token.Value = null;
|
||||
}
|
||||
|
||||
public async Task SetGatewayAsync(string host)
|
||||
public async ValueTask SetGatewayAsync(string host)
|
||||
{
|
||||
var setting = new RefitSettings
|
||||
var setting = new RefitSettings()
|
||||
{
|
||||
AuthorizationHeaderValueGetter = (req, ct) => _token.Value != null ?
|
||||
Task.FromResult<string>($"Bearer {_token}") : Task.FromResult(string.Empty)
|
||||
HttpMessageHandlerFactory = () => new ApiAuthenticationHandler(new HttpClientHandler())
|
||||
};
|
||||
|
||||
var tempGateway = RestService.For<IFlawlessServer>(host, setting);
|
||||
|
||||
10
Flawless.Client/Service/BaseService.cs
Normal file
10
Flawless.Client/Service/BaseService.cs
Normal file
@ -0,0 +1,10 @@
|
||||
namespace Flawless.Client.Service;
|
||||
|
||||
public class BaseService<TService> where TService : class, new()
|
||||
{
|
||||
private static TService? _currentService;
|
||||
|
||||
public static TService Current => _currentService ??= new TService();
|
||||
|
||||
public static TService C => Current;
|
||||
}
|
||||
@ -6,7 +6,6 @@
|
||||
using Refit;
|
||||
using System.Collections.Generic;
|
||||
using System.Text.Json.Serialization;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
#nullable enable annotations
|
||||
@ -19,178 +18,188 @@ namespace Flawless.Client.Remote
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/admin/user/delete/{username}")]
|
||||
Task Delete(string username, CancellationToken cancellationToken = default);
|
||||
Task Delete(string username);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/admin/user/enable/{username}")]
|
||||
Task Enable(string username, CancellationToken cancellationToken = default);
|
||||
Task Enable(string username);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/admin/user/disable/{username}")]
|
||||
Task Disable(string username, CancellationToken cancellationToken = default);
|
||||
Task Disable(string username);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/admin/user/reset_password")]
|
||||
Task ResetPassword([Body] ResetPasswordRequest body, CancellationToken cancellationToken = default);
|
||||
Task ResetPassword([Body] ResetPasswordRequest body);
|
||||
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/auth/status")]
|
||||
Task<ServerStatusResponse> Status(CancellationToken cancellationToken = default);
|
||||
Task<ServerStatusResponse> Status();
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/auth/register")]
|
||||
Task Register([Body] RegisterRequest body, CancellationToken cancellationToken = default);
|
||||
Task Register([Body] RegisterRequest body);
|
||||
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Post("/api/auth/login")]
|
||||
Task<TokenInfo> Login([Body] LoginRequest body, CancellationToken cancellationToken = default);
|
||||
Task<TokenInfo> Login([Body] LoginRequest body);
|
||||
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Post("/api/auth/refresh")]
|
||||
Task<TokenInfo> Refresh([Body] TokenInfo body, CancellationToken cancellationToken = default);
|
||||
Task<TokenInfo> Refresh([Body] TokenInfo body);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/auth/logout_all")]
|
||||
Task LogoutAll(CancellationToken cancellationToken = default);
|
||||
Task LogoutAll();
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/auth/renew_password")]
|
||||
Task RenewPassword([Body] ResetPasswordRequest body, CancellationToken cancellationToken = default);
|
||||
Task RenewPassword([Body] ResetPasswordRequest body);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Get("/")]
|
||||
Task Index(CancellationToken cancellationToken = default);
|
||||
Task Index();
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/repo/{userName}/{repositoryName}/delete_repo")]
|
||||
Task DeleteRepo(string repositoryName, string userName, CancellationToken cancellationToken = default);
|
||||
Task DeleteRepo(string repositoryName, string userName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/repo/{userName}/{repositoryName}/get_info")]
|
||||
Task GetInfo(string repositoryName, string userName, CancellationToken cancellationToken = default);
|
||||
Task<RepositoryInfoResponse> GetInfo(string repositoryName, string userName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/repo/{userName}/{repositoryName}/archive_repo")]
|
||||
Task ArchiveRepo(string repositoryName, string userName, CancellationToken cancellationToken = default);
|
||||
Task ArchiveRepo(string repositoryName, string userName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/repo/{userName}/{repositoryName}/unarchive_repo")]
|
||||
Task UnarchiveRepo(string repositoryName, string userName, CancellationToken cancellationToken = default);
|
||||
Task UnarchiveRepo(string repositoryName, string userName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/repo/{userName}/{repositoryName}/get_users")]
|
||||
Task GetUsers(string repositoryName, string userName, [Body] QueryPagesRequest body, CancellationToken cancellationToken = default);
|
||||
Task<RepoUserRoleListingResponse> GetUsers(string repositoryName, string userName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/repo/{userName}/{repositoryName}/update_user")]
|
||||
Task UpdateUser(string repositoryName, string userName, [Body] RepoUserRole body, CancellationToken cancellationToken = default);
|
||||
Task UpdateUser(string repositoryName, string userName, [Body] RepoUserRole body);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/repo/{userName}/{repositoryName}/delete_user")]
|
||||
Task DeleteUser(string repositoryName, string userName, [Body] RepoUserRole body, CancellationToken cancellationToken = default);
|
||||
Task DeleteUser(string repositoryName, string userName, [Body] RepoUserRole body);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/repo/{userName}/{repositoryName}/fetch_manifest")]
|
||||
Task FetchManifest(string userName, string repositoryName, [Query] string commitId, CancellationToken cancellationToken = default);
|
||||
Task<FileResponse> FetchManifest(string userName, string repositoryName, [Query] string commitId);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/repo/{userName}/{repositoryName}/fetch_depot")]
|
||||
Task FetchDepot(string userName, string repositoryName, [Query] string depotId, CancellationToken cancellationToken = default);
|
||||
Task<FileResponse> FetchDepot(string userName, string repositoryName, [Query] string depotId);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/repo/{userName}/{repositoryName}/list_commit")]
|
||||
Task ListCommit(string userName, string repositoryName, CancellationToken cancellationToken = default);
|
||||
Task<RepositoryCommitResponseListingResponse> ListCommit(string userName, string repositoryName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/repo/{userName}/{repositoryName}/list_locked_files")]
|
||||
Task ListLockedFiles(string userName, string repositoryName, CancellationToken cancellationToken = default);
|
||||
Task<LockFileInfoListingResponse> ListLockedFiles(string userName, string repositoryName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/repo/{userName}/{repositoryName}/peek_commit")]
|
||||
Task PeekCommit(string userName, string repositoryName, CancellationToken cancellationToken = default);
|
||||
Task<GuidPeekResponse> PeekCommit(string userName, string repositoryName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/repo/{userName}/{repositoryName}/lock_file")]
|
||||
Task LockFile(string userName, string repositoryName, [Query] string path, CancellationToken cancellationToken = default);
|
||||
Task LockFile(string userName, string repositoryName, [Query] string path);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/repo/{userName}/{repositoryName}/unlock_file")]
|
||||
Task UnlockFile(string userName, string repositoryName, [Query] string path, CancellationToken cancellationToken = default);
|
||||
Task UnlockFile(string userName, string repositoryName, [Query] string path);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Multipart]
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Post("/api/repo/{userName}/{repositoryName}/create_commit")]
|
||||
Task CreateCommit(string userName, string repositoryName, StreamPart depot, string message, IEnumerable<WorkspaceFile> workspaceSnapshot, IEnumerable<string> requiredDepots, string mainDepotId, CancellationToken cancellationToken = default);
|
||||
Task<CommitSuccessResponse> CreateCommit(string userName, string repositoryName, StreamPart depot, string message, IEnumerable<WorkspaceFile> workspaceSnapshot, IEnumerable<string> requiredDepots, string mainDepotId);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/repo_list")]
|
||||
Task RepoList([Query, AliasAs("Offset")] int offset, [Query, AliasAs("Length")] int length, CancellationToken cancellationToken = default);
|
||||
Task<RepositoryInfoResponseListingResponse> RepoList();
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Post("/api/repo_create")]
|
||||
Task RepoCreate([Query] string repositoryName, CancellationToken cancellationToken = default);
|
||||
Task<RepositoryInfoResponse> RepoCreate([Query] string repositoryName);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/user/update_info")]
|
||||
Task UpdateInfo([Body] UserInfoModifyResponse body, CancellationToken cancellationToken = default);
|
||||
Task UpdateInfo([Body] UserInfoModifyResponse body);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/user/update_email")]
|
||||
Task UpdateEmail([Body] UserContactModifyResponse body, CancellationToken cancellationToken = default);
|
||||
Task UpdateEmail([Body] UserContactModifyResponse body);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Post("/api/user/update_phone")]
|
||||
Task UpdatePhone([Body] UserContactModifyResponse body, CancellationToken cancellationToken = default);
|
||||
Task UpdatePhone([Body] UserContactModifyResponse body);
|
||||
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/user/get_info")]
|
||||
Task<UserInfoResponse> GetInfo([Query] string username, CancellationToken cancellationToken = default);
|
||||
Task<UserInfoResponse> GetInfo([Query] string username);
|
||||
|
||||
/// <returns>OK</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Headers("Accept: text/plain, application/json, text/json")]
|
||||
[Get("/api/user/query_info")]
|
||||
Task<UserInfoResponsePagedResponse> QueryInfo([Query] string keyword, [Body] QueryPagesRequest body, CancellationToken cancellationToken = default);
|
||||
Task<UserInfoResponseListingResponse> QueryInfo([Query] string keyword);
|
||||
|
||||
/// <returns>A <see cref="Task"/> that completes when the request is finished.</returns>
|
||||
/// <exception cref="ApiException">Thrown when the request returns a non-success status code.</exception>
|
||||
[Get("/api/user/delete")]
|
||||
Task Delete(CancellationToken cancellationToken = default);
|
||||
Task Delete();
|
||||
|
||||
|
||||
}
|
||||
@ -223,6 +232,48 @@ namespace Flawless.Client.Remote
|
||||
|
||||
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class CommitSuccessResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("committedOn")]
|
||||
public System.DateTimeOffset CommittedOn { get; set; }
|
||||
|
||||
[JsonPropertyName("commitId")]
|
||||
public System.Guid CommitId { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class GuidPeekResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public System.Guid Result { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class LockFileInfo
|
||||
{
|
||||
|
||||
[JsonPropertyName("path")]
|
||||
public string Path { get; set; }
|
||||
|
||||
[JsonPropertyName("owner")]
|
||||
public string Owner { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class LockFileInfoListingResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public ICollection<LockFileInfo> Result { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class LoginRequest
|
||||
{
|
||||
@ -237,18 +288,6 @@ namespace Flawless.Client.Remote
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class QueryPagesRequest
|
||||
{
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int Offset { get; set; }
|
||||
|
||||
[JsonPropertyName("length")]
|
||||
public int Length { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class RegisterRequest
|
||||
{
|
||||
@ -280,6 +319,77 @@ namespace Flawless.Client.Remote
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class RepoUserRoleListingResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public ICollection<RepoUserRole> Result { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class RepositoryCommitResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("id")]
|
||||
public System.Guid Id { get; set; }
|
||||
|
||||
[JsonPropertyName("author")]
|
||||
public string Author { get; set; }
|
||||
|
||||
[JsonPropertyName("commitedOn")]
|
||||
public System.DateTimeOffset CommitedOn { get; set; }
|
||||
|
||||
[JsonPropertyName("message")]
|
||||
public string Message { get; set; }
|
||||
|
||||
[JsonPropertyName("mainDepotId")]
|
||||
public System.Guid MainDepotId { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class RepositoryCommitResponseListingResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public ICollection<RepositoryCommitResponse> Result { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class RepositoryInfoResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("repositoryName")]
|
||||
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
|
||||
public string RepositoryName { get; set; }
|
||||
|
||||
[JsonPropertyName("ownerUsername")]
|
||||
[System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
|
||||
public string OwnerUsername { get; set; }
|
||||
|
||||
[JsonPropertyName("description")]
|
||||
public string Description { get; set; }
|
||||
|
||||
[JsonPropertyName("isArchived")]
|
||||
public bool IsArchived { get; set; }
|
||||
|
||||
[JsonPropertyName("role")]
|
||||
public RepositoryRole Role { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class RepositoryInfoResponseListingResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("result")]
|
||||
public ICollection<RepositoryInfoResponse> Result { get; set; }
|
||||
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public enum RepositoryRole
|
||||
{
|
||||
@ -314,6 +424,9 @@ namespace Flawless.Client.Remote
|
||||
public partial class ServerStatusResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("friendlyName")]
|
||||
public string FriendlyName { get; set; }
|
||||
|
||||
[JsonPropertyName("allowPublicRegister")]
|
||||
public bool AllowPublicRegister { get; set; }
|
||||
|
||||
@ -396,20 +509,11 @@ namespace Flawless.Client.Remote
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class UserInfoResponsePagedResponse
|
||||
public partial class UserInfoResponseListingResponse
|
||||
{
|
||||
|
||||
[JsonPropertyName("offset")]
|
||||
public int Offset { get; set; }
|
||||
|
||||
[JsonPropertyName("length")]
|
||||
public int Length { get; set; }
|
||||
|
||||
[JsonPropertyName("total")]
|
||||
public int? Total { get; set; }
|
||||
|
||||
[JsonPropertyName("data")]
|
||||
public ICollection<UserInfoResponse> Data { get; set; }
|
||||
[JsonPropertyName("result")]
|
||||
public ICollection<UserInfoResponse> Result { get; set; }
|
||||
|
||||
}
|
||||
|
||||
@ -460,6 +564,43 @@ namespace Flawless.Client.Remote
|
||||
public string ContentType { get; private set; }
|
||||
}
|
||||
|
||||
[System.CodeDom.Compiler.GeneratedCode("NSwag", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
|
||||
public partial class FileResponse : System.IDisposable
|
||||
{
|
||||
private System.IDisposable _client;
|
||||
private System.IDisposable _response;
|
||||
|
||||
public int StatusCode { get; private set; }
|
||||
|
||||
public IReadOnlyDictionary<string, IEnumerable<string>> Headers { get; private set; }
|
||||
|
||||
public System.IO.Stream Stream { get; private set; }
|
||||
|
||||
public bool IsPartial
|
||||
{
|
||||
get { return StatusCode == 206; }
|
||||
}
|
||||
|
||||
public FileResponse(int statusCode, IReadOnlyDictionary<string, IEnumerable<string>> headers, System.IO.Stream stream, System.IDisposable client, System.IDisposable response)
|
||||
{
|
||||
StatusCode = statusCode;
|
||||
Headers = headers;
|
||||
Stream = stream;
|
||||
_client = client;
|
||||
_response = response;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stream.Dispose();
|
||||
if (_response != null)
|
||||
_response.Dispose();
|
||||
if (_client != null)
|
||||
_client.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
161
Flawless.Client/Service/RepositoryService.cs
Normal file
161
Flawless.Client/Service/RepositoryService.cs
Normal file
@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading.Tasks;
|
||||
using Flawless.Client.Models;
|
||||
using Flawless.Client.Remote;
|
||||
|
||||
namespace Flawless.Client.Service;
|
||||
|
||||
public class RepositoryService : BaseService<RepositoryService>
|
||||
{
|
||||
public ObservableCollection<RepositoryModel> Repositories => _repositories;
|
||||
|
||||
|
||||
private readonly ObservableCollection<RepositoryModel> _repositories = new();
|
||||
|
||||
public async ValueTask<bool> CreateRepositoryOnServerAsync(string repositoryName)
|
||||
{
|
||||
|
||||
var api = Api.C;
|
||||
try
|
||||
{
|
||||
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
||||
{
|
||||
api.ClearGateway();
|
||||
return false;
|
||||
}
|
||||
|
||||
var r = await api.Gateway.RepoCreate(repositoryName);
|
||||
|
||||
var repo = new RepositoryModel();
|
||||
repo.Name = r.RepositoryName;
|
||||
repo.OwnerName = r.OwnerUsername;
|
||||
repo.StandaloneName = RepositoryModel.GetStandaloneName(r.RepositoryName, r.OwnerUsername);
|
||||
repo.Description = r.Description;
|
||||
repo.Archived = r.IsArchived;
|
||||
repo.OwnByCurrentUser = (int) r.Role == (int) RepositoryModel.RepositoryRole.Owner;
|
||||
|
||||
Repositories.Insert(0, repo);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> UpdateRepositoriesFromServerAsync()
|
||||
{
|
||||
var api = Api.C;
|
||||
try
|
||||
{
|
||||
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
||||
{
|
||||
api.ClearGateway();
|
||||
return false;
|
||||
}
|
||||
|
||||
var result = (await api.Gateway.RepoList()).Result;
|
||||
var dict = result.ToDictionary(rp => RepositoryModel.GetStandaloneName(rp.RepositoryName, rp.OwnerUsername));
|
||||
for (var i = 0; i < Repositories.Count; i++)
|
||||
{
|
||||
var ele = Repositories[i];
|
||||
if (!dict.Remove(ele.StandaloneName, out var role))
|
||||
{
|
||||
Repositories.RemoveAt(i);
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ele.Archived = ele.Archived;
|
||||
ele.Description = ele.Description;
|
||||
}
|
||||
|
||||
foreach (var (repoStandaloneName, rsp) in dict)
|
||||
{
|
||||
var repo = new RepositoryModel();
|
||||
repo.Name = rsp.RepositoryName;
|
||||
repo.OwnerName = rsp.OwnerUsername;
|
||||
repo.StandaloneName = repoStandaloneName;
|
||||
repo.Description = rsp.Description;
|
||||
repo.Archived = rsp.IsArchived;
|
||||
repo.OwnByCurrentUser = (int) rsp.Role == (int) RepositoryModel.RepositoryRole.Owner;
|
||||
|
||||
Repositories.Add(repo);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> UpdateRepositoriesDownloadedFromDiskAsync()
|
||||
{
|
||||
foreach (var repo in _repositories)
|
||||
{
|
||||
var fsPath = Path.Combine(SettingService.C.AppSetting.RepositoryPath, repo.OwnerName, repo.Name);
|
||||
repo.IsDownloaded = Directory.Exists(fsPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async ValueTask<bool> UpdateRepositoryMembersFromServerAsync(RepositoryModel repo)
|
||||
{
|
||||
var api = Api.C;
|
||||
try
|
||||
{
|
||||
if (api.RequireRefreshToken() && !(await api.TryRefreshTokenAsync()))
|
||||
{
|
||||
api.ClearGateway();
|
||||
return false;
|
||||
}
|
||||
|
||||
var members = await api.Gateway.GetUsers(repo.Name, repo.OwnerName);
|
||||
|
||||
// Update existed
|
||||
var dict = members.Result.ToDictionary(m => m.Username);
|
||||
for (var i = 0; i < repo.Members.Count; i++)
|
||||
{
|
||||
var ele = repo.Members[i];
|
||||
if (!dict.Remove(ele.Username, out var role))
|
||||
{
|
||||
repo.Members.RemoveAt(i);
|
||||
i -= 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
ele.Username = role.Username;
|
||||
ele.Role = (RepositoryModel.RepositoryRole) role.Role;
|
||||
}
|
||||
|
||||
// Add missing
|
||||
foreach (var role in dict.Values)
|
||||
{
|
||||
var r = new RepositoryModel.Member
|
||||
{
|
||||
Username = role.Username,
|
||||
Role = (RepositoryModel.RepositoryRole) role.Role
|
||||
};
|
||||
|
||||
repo.Members.Add(r);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
33
Flawless.Client/Service/SettingService.cs
Normal file
33
Flawless.Client/Service/SettingService.cs
Normal file
@ -0,0 +1,33 @@
|
||||
using System.IO;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using Flawless.Client.Models;
|
||||
using Newtonsoft.Json;
|
||||
using Path = System.IO.Path;
|
||||
|
||||
namespace Flawless.Client.Service;
|
||||
|
||||
public class SettingService : BaseService<SettingService>
|
||||
{
|
||||
|
||||
private readonly JsonSerializer _serializer = JsonSerializer.Create();
|
||||
|
||||
public static string SettingFilePath { get; } =
|
||||
Path.Combine(AppDefaultValues.ProgramDataDirectory, "settings.json");
|
||||
|
||||
public AppSettingModel AppSetting { get; } = new AppSettingModel();
|
||||
|
||||
public async ValueTask WriteToDiskAsync()
|
||||
{
|
||||
var stream = File.Exists(SettingFilePath) ? File.OpenWrite(SettingFilePath) : File.Create(SettingFilePath);
|
||||
await using var fs = new StreamWriter(stream, Encoding.UTF8);
|
||||
_serializer.Serialize(fs, AppSetting);
|
||||
}
|
||||
|
||||
public async ValueTask ReadFromDiskAsync()
|
||||
{
|
||||
if (!File.Exists(SettingFilePath)) return;
|
||||
using var fs = new StreamReader(File.OpenRead(SettingFilePath), Encoding.UTF8);
|
||||
_serializer.Populate(fs, AppSetting);
|
||||
}
|
||||
}
|
||||
@ -6,10 +6,11 @@ using System.Threading.Tasks;
|
||||
using Avalonia.ReactiveUI;
|
||||
using Flawless.Client.Models;
|
||||
using Flawless.Client.Service;
|
||||
using Flawless.Communication.Response;
|
||||
using Flawless.Communication.Shared;
|
||||
using Flawless.Client.ViewModels.ModalBox;
|
||||
using Flawless.Client.Views.ModalBox;
|
||||
using ReactiveUI;
|
||||
using ReactiveUI.SourceGenerators;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace Flawless.Client.ViewModels;
|
||||
|
||||
@ -19,31 +20,50 @@ public partial class HomeViewModel : ViewModelBase, IRoutableViewModel
|
||||
|
||||
public IScreen HostScreen { get; }
|
||||
|
||||
public ObservableCollection<RepositoryHomePageModel> Repositories { get; } = new(new[]
|
||||
{
|
||||
new RepositoryHomePageModel(
|
||||
"cardidi", "test1", "Abc", false, true, ""),
|
||||
});
|
||||
|
||||
[Reactive] private RepositoryHomePageModel? _selectedRepository;
|
||||
[Reactive] private RepositoryModel? _selectedRepository;
|
||||
|
||||
[Reactive] private string _serverFriendlyName;
|
||||
|
||||
public RepositoryService RepoSrc => RepositoryService.C;
|
||||
|
||||
public HomeViewModel(IScreen hostScreen)
|
||||
{
|
||||
HostScreen = hostScreen;
|
||||
Api.Current.ServerUrl.SubscribeOn(AvaloniaScheduler.Instance)
|
||||
Api.C.ServerUrl.SubscribeOn(AvaloniaScheduler.Instance)
|
||||
.Subscribe(v => ServerFriendlyName = v ?? "Unknown Server");
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task RefreshRepositoriesAsync()
|
||||
{
|
||||
if (await RepoSrc.UpdateRepositoriesFromServerAsync())
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task CreateRepositoryAsync()
|
||||
{
|
||||
var form = new CreateRepositoryDialogViewModel();
|
||||
var opt = new OverlayDialogOptions
|
||||
{
|
||||
FullScreen = false,
|
||||
Buttons = DialogButton.OK,
|
||||
CanResize = false,
|
||||
CanDragMove = false,
|
||||
IsCloseButtonVisible = true,
|
||||
CanLightDismiss = true,
|
||||
Mode = DialogMode.Question
|
||||
};
|
||||
|
||||
var mr = await OverlayDialog
|
||||
.ShowModal<CreateRepositoryDialogView, CreateRepositoryDialogViewModel>(form, AppDefaultValues.HostId, opt);
|
||||
|
||||
if (mr == DialogResult.OK)
|
||||
{
|
||||
await RepoSrc.CreateRepositoryOnServerAsync(form.RepositoryName);
|
||||
}
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
@ -51,8 +71,25 @@ public partial class HomeViewModel : ViewModelBase, IRoutableViewModel
|
||||
{
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task DownloadRepositoryAsync()
|
||||
{
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task DeleteRepositoryAsync()
|
||||
{
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private async Task QuitLoginAsync()
|
||||
{
|
||||
Api.C.ClearGateway();
|
||||
}
|
||||
|
||||
[ReactiveCommand]
|
||||
private void OpenGlobalSettingAsync()
|
||||
{
|
||||
HostScreen.Router.Navigate.Execute(new SettingViewModel(HostScreen));
|
||||
}
|
||||
}
|
||||
@ -25,7 +25,7 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel
|
||||
|
||||
public IObservable<bool> CanLogin;
|
||||
|
||||
public IObservable<bool> CanRegister => Api.Current.Status.Select(s => s != null && s.AllowPublicRegister);
|
||||
public IObservable<bool> CanRegister => Api.C.Status.Select(s => s != null && s.AllowPublicRegister);
|
||||
|
||||
public LoginPageViewModel(IScreen hostScreen)
|
||||
{
|
||||
@ -50,7 +50,8 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel
|
||||
{
|
||||
try
|
||||
{
|
||||
await Api.Current.LoginAsync(Username, Password);
|
||||
await Api.C.LoginAsync(Username, Password);
|
||||
await RepositoryService.C.UpdateRepositoriesFromServerAsync();
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
|
||||
@ -13,9 +13,9 @@ public partial class MainWindowViewModel : ViewModelBase, IScreen
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
#pragma warning disable VSTHRD110
|
||||
Api.Current.IsLoggedIn.Where(x => x)
|
||||
Api.C.IsLoggedIn.Where(x => x)
|
||||
.SubscribeOnUIThread(_ => Router.Navigate.Execute(new HomeViewModel(this)));
|
||||
Api.Current.IsLoggedIn.Where(x => !x)
|
||||
Api.C.IsLoggedIn.Where(x => !x)
|
||||
.SubscribeOnUIThread(_ => Router.Navigate.Execute(new ServerConnectViewModel(this)));
|
||||
#pragma warning restore VSTHRD110
|
||||
}
|
||||
|
||||
@ -0,0 +1,8 @@
|
||||
using ReactiveUI.SourceGenerators;
|
||||
|
||||
namespace Flawless.Client.ViewModels.ModalBox;
|
||||
|
||||
public partial class CreateRepositoryDialogViewModel : ViewModelBase
|
||||
{
|
||||
[Reactive] private string _repositoryName;
|
||||
}
|
||||
@ -36,7 +36,7 @@ public partial class RegisterPageViewModel : ViewModelBase, IRoutableViewModel
|
||||
{
|
||||
try
|
||||
{
|
||||
await Api.Current.Gateway.Register(new RegisterRequest
|
||||
await Api.C.Gateway.Register(new RegisterRequest
|
||||
{
|
||||
Email = _email,
|
||||
Username = _username,
|
||||
@ -44,7 +44,7 @@ public partial class RegisterPageViewModel : ViewModelBase, IRoutableViewModel
|
||||
});
|
||||
|
||||
|
||||
await Api.Current.LoginAsync(Username, Password);
|
||||
await Api.C.LoginAsync(Username, Password);
|
||||
}
|
||||
catch (ApiException ex)
|
||||
{
|
||||
|
||||
18
Flawless.Client/ViewModels/RepositoryViewModel.cs
Normal file
18
Flawless.Client/ViewModels/RepositoryViewModel.cs
Normal file
@ -0,0 +1,18 @@
|
||||
using Flawless.Client.Models;
|
||||
using Flawless.Client.Service;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Flawless.Client.ViewModels;
|
||||
|
||||
public class RepositoryViewModel : RoutableViewModelBase
|
||||
{
|
||||
public RepositoryModel Repository { get; }
|
||||
|
||||
public RepositoryService RepoSrv { get; }
|
||||
|
||||
public RepositoryViewModel(RepositoryModel repo, IScreen hostScreen) : base(hostScreen)
|
||||
{
|
||||
Repository = repo;
|
||||
RepoSrv = RepositoryService.C;
|
||||
}
|
||||
}
|
||||
@ -26,7 +26,7 @@ public partial class ServerConnectViewModel : ViewModelBase, IScreen, IRoutableV
|
||||
[ReactiveCommand]
|
||||
private async ValueTask OpenRepoPageAsync()
|
||||
{
|
||||
if (Api.Current.RequireRefreshToken()) await Router.NavigateAndReset.Execute(new ServerSetupPageViewModel(this)).ToTask();
|
||||
if (Api.C.RequireRefreshToken()) await Router.NavigateAndReset.Execute(new ServerSetupPageViewModel(this)).ToTask();
|
||||
}
|
||||
|
||||
public ServerConnectViewModel(IScreen hostScreen)
|
||||
|
||||
@ -9,25 +9,20 @@ using Refit;
|
||||
|
||||
namespace Flawless.Client.ViewModels;
|
||||
|
||||
public partial class ServerSetupPageViewModel : ViewModelBase, IRoutableViewModel
|
||||
public partial class ServerSetupPageViewModel : RoutableViewModelBase
|
||||
{
|
||||
|
||||
public string? UrlPathSegment { get; } = Guid.NewGuid().ToString();
|
||||
|
||||
public IScreen HostScreen { get; }
|
||||
|
||||
[Reactive] private string _host = "http://localhost:5256/";
|
||||
|
||||
[Reactive(SetModifier = AccessModifier.Protected)] private string? _issue;
|
||||
|
||||
public IObservable<bool> CanSetHost { get; }
|
||||
|
||||
public ServerSetupPageViewModel(IScreen hostScreen)
|
||||
public ServerSetupPageViewModel(IScreen hostScreen) : base(hostScreen)
|
||||
{
|
||||
HostScreen = hostScreen;
|
||||
|
||||
// Must clear this gateway
|
||||
if (Api.Current.IsGatewayReady) Api.Current.ClearGateway();
|
||||
if (Api.C.IsGatewayReady) Api.C.ClearGateway();
|
||||
|
||||
CanSetHost = this.WhenAnyValue(x => x.Host, s => !string.IsNullOrWhiteSpace(s));
|
||||
}
|
||||
@ -39,7 +34,7 @@ public partial class ServerSetupPageViewModel : ViewModelBase, IRoutableViewMode
|
||||
try
|
||||
{
|
||||
Issue = string.Empty;
|
||||
await Api.Current.SetGatewayAsync(Host);
|
||||
await Api.C.SetGatewayAsync(Host);
|
||||
HostScreen.Router.Navigate.Execute(new LoginPageViewModel(HostScreen));
|
||||
}
|
||||
catch (ApiException ex)
|
||||
|
||||
12
Flawless.Client/ViewModels/SettingViewModel.cs
Normal file
12
Flawless.Client/ViewModels/SettingViewModel.cs
Normal file
@ -0,0 +1,12 @@
|
||||
using System;
|
||||
using System.Windows.Input;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Flawless.Client.ViewModels;
|
||||
|
||||
public class SettingViewModel : RoutableViewModelBase
|
||||
{
|
||||
public SettingViewModel(IScreen hostScreen) : base(hostScreen)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,21 @@
|
||||
using ReactiveUI;
|
||||
using System;
|
||||
using System.Reactive;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace Flawless.Client.ViewModels;
|
||||
|
||||
public abstract class ViewModelBase : ReactiveObject {}
|
||||
|
||||
public abstract class RoutableViewModelBase : ViewModelBase, IRoutableViewModel
|
||||
{
|
||||
public string? UrlPathSegment { get; } = Guid.NewGuid().ToString();
|
||||
|
||||
public IScreen HostScreen { get; }
|
||||
|
||||
public ReactiveCommand<Unit, IRoutableViewModel> GoBackCommand => HostScreen.Router.NavigateBack;
|
||||
|
||||
protected RoutableViewModelBase(IScreen hostScreen)
|
||||
{
|
||||
HostScreen = hostScreen;
|
||||
}
|
||||
}
|
||||
@ -10,11 +10,20 @@
|
||||
x:Class="Flawless.Client.Views.HomeView">
|
||||
|
||||
<DockPanel Margin="50">
|
||||
<StackPanel DockPanel.Dock="Top">
|
||||
<Label Content="{Binding ServerFriendlyName, StringFormat='Server {0}'}" FontSize="18" FontWeight="400"></Label>
|
||||
<Label Content="Repositories" FontSize="32" FontWeight="600"></Label>
|
||||
<Rectangle Height="18"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<Grid RowDefinitions="Auto, 18, Auto" ColumnDefinitions="*, Auto" DockPanel.Dock="Top">
|
||||
<StackPanel Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2">
|
||||
<Label Content="{Binding ServerFriendlyName, StringFormat='Server {0}', FallbackValue='Server LocalTest'}" FontSize="18" FontWeight="400"></Label>
|
||||
<Label Content="Repositories" FontSize="32" FontWeight="600"></Label>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="0" Grid.Column="1" VerticalAlignment="Center">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<u:IconButton Icon="{StaticResource SemiIconSetting}"
|
||||
Command="{Binding OpenGlobalSettingAsyncCommand}"/>
|
||||
<u:IconButton Classes="Danger" Icon="{StaticResource SemiIconQuit}"
|
||||
Command="{Binding QuitLoginCommand}"/>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Row="2" Grid.Column="0" Grid.ColumnSpan="2" Orientation="Horizontal" Spacing="8">
|
||||
<u:IconButton Icon="{StaticResource SemiIconRefresh}" Content="Refresh"
|
||||
Command="{Binding RefreshRepositoriesCommand}"/>
|
||||
<u:IconButton Icon="{StaticResource SemiIconPlus}" Content="Create"
|
||||
@ -22,23 +31,38 @@
|
||||
<u:DisableContainer IsEnabled="{Binding SelectedRepository, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<StackPanel Orientation="Horizontal" Spacing="8">
|
||||
<u:IconButton Icon="{StaticResource SemiIconFolderOpen}" Content="Open"
|
||||
IsEnabled="{Binding SelectedRepository.IsDownloaded}"
|
||||
Command="{Binding OpenRepositoryCommand}"/>
|
||||
<u:IconButton Icon="{StaticResource SemiIconDownload}" Content="Download"
|
||||
IsEnabled="{Binding !SelectedRepository.IsDownloaded}"
|
||||
Command="{Binding OpenRepositoryCommand}"/>
|
||||
<u:IconButton Classes="Danger" Icon="{StaticResource SemiIconDelete}" Content="Delete"
|
||||
IsEnabled="{Binding SelectedRepository.IsOwner}"
|
||||
IsEnabled="{Binding SelectedRepository.IsDownloaded}"
|
||||
Command="{Binding DeleteRepositoryCommand}"/>
|
||||
</StackPanel>
|
||||
</u:DisableContainer>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
<Rectangle DockPanel.Dock="Top" Height="18"/>
|
||||
<StackPanel IsVisible="{Binding !RepoSrc.Repositories.Count}"
|
||||
VerticalAlignment="Center" Spacing="18">
|
||||
<PathIcon Data="{StaticResource SemiIconAlertCircle}" HorizontalAlignment="Left" Width="48" Height="48"/>
|
||||
<Label FontSize="18" Content="Repository is empty, try refresh or create repository."/>
|
||||
</StackPanel>
|
||||
<Rectangle DockPanel.Dock="Top" Height="20"/>
|
||||
<Grid ColumnDefinitions="*, 10, *" VerticalAlignment="Stretch">
|
||||
<Grid IsVisible="{Binding RepoSrc.Repositories.Count}" ColumnDefinitions="*, 10, *" VerticalAlignment="Stretch">
|
||||
<ScrollViewer Grid.Column="0" VerticalScrollBarVisibility="Auto" AllowAutoHide="True">
|
||||
<u:SelectionList ItemsSource="{Binding Repositories, Mode=TwoWay}"
|
||||
<u:SelectionList ItemsSource="{Binding RepoSrc.Repositories, Mode=TwoWay}"
|
||||
SelectedItem="{Binding SelectedRepository, Mode=TwoWay}"
|
||||
Margin="0, 0, 8, 0">
|
||||
<u:SelectionList.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<TextBlock Margin="0, 12" VerticalAlignment="Center" Text="{Binding FullName, Mode=OneWay}"/>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6" Margin="6 0">
|
||||
<PathIcon MaxHeight="16" MaxWidth="16" Data="{StaticResource SemiIconDownload}"
|
||||
IsVisible="{Binding !IsDownloaded}"/>
|
||||
<PathIcon MaxHeight="16" MaxWidth="16" Data="{StaticResource SemiIconFolder}"
|
||||
IsVisible="{Binding IsDownloaded}"/>
|
||||
<TextBlock Margin="0, 12" VerticalAlignment="Center" Text="{Binding StandaloneName, Mode=OneWay}"/>
|
||||
</StackPanel>
|
||||
</DataTemplate>
|
||||
</u:SelectionList.ItemTemplate>
|
||||
</u:SelectionList>
|
||||
@ -47,9 +71,8 @@
|
||||
<StackPanel VerticalAlignment="Stretch" Spacing="20">
|
||||
<Label FontWeight="400" FontSize="24"
|
||||
Content="{Binding SelectedRepository.Name, FallbackValue='Select a Repository'}"/>
|
||||
|
||||
<StackPanel Spacing="10" IsVisible="{Binding SelectedRepository, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<StackPanel IsVisible="{Binding SelectedRepository.IsArchived, FallbackValue=False}"
|
||||
<StackPanel IsVisible="{Binding SelectedRepository.Archived, FallbackValue=False}"
|
||||
Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconArchive}"/>
|
||||
<Label Content="Archived"/>
|
||||
@ -58,12 +81,11 @@
|
||||
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconUser}"/>
|
||||
<Label Content="{Binding SelectedRepository.OwnerName, FallbackValue='Owner'}"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" Spacing="6" VerticalAlignment="Center">
|
||||
<PathIcon MaxHeight="14" MaxWidth="14" Data="{StaticResource SemiIconActivity}"/>
|
||||
<Label Content="{Binding SelectedRepository.LatestCommitId, FallbackValue='No Commit'}"/>
|
||||
</StackPanel>
|
||||
<ScrollViewer IsVisible="{Binding SelectedRepository}">
|
||||
<Label Content="{Binding SelectedRepository.Description, FallbackValue='Description as below.'}"/>
|
||||
<StackPanel Orientation="Vertical" Spacing="8">
|
||||
<Label Content="{Binding SelectedRepository.Description, FallbackValue='Description as below.'}"/>
|
||||
<u:Divider Content="Recent Activities"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</StackPanel>
|
||||
</StackPanel>
|
||||
|
||||
@ -20,6 +20,6 @@
|
||||
|
||||
<Panel>
|
||||
<rxui:RoutedViewHost Router="{Binding Router}"/>
|
||||
<ursa:OverlayDialogHost/>
|
||||
<ursa:OverlayDialogHost HostId="Overlay"/>
|
||||
</Panel>
|
||||
</ursa:UrsaWindow>
|
||||
@ -0,0 +1,22 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:vm="using:Flawless.Client.ViewModels.ModalBox"
|
||||
x:DataType="vm:CreateRepositoryDialogViewModel"
|
||||
d:DesignHeight="450" d:DesignWidth="800" mc:Ignorable="d"
|
||||
MinWidth="400"
|
||||
x:Class="Flawless.Client.Views.ModalBox.CreateRepositoryDialogView">
|
||||
|
||||
<u:Form HorizontalAlignment="Stretch" LabelPosition="Top">
|
||||
<u:Form.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<Grid ColumnDefinitions="*" RowDefinitions="*" />
|
||||
</ItemsPanelTemplate>
|
||||
</u:Form.ItemsPanel>
|
||||
<u:FormItem Label="Name">
|
||||
<TextBox Text="{Binding RepositoryName}"/>
|
||||
</u:FormItem>
|
||||
</u:Form>
|
||||
</UserControl>
|
||||
@ -0,0 +1,15 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Flawless.Client.ViewModels.ModalBox;
|
||||
using Ursa.ReactiveUIExtension;
|
||||
|
||||
namespace Flawless.Client.Views.ModalBox;
|
||||
|
||||
public partial class CreateRepositoryDialogView : ReactiveUrsaView<CreateRepositoryDialogViewModel>
|
||||
{
|
||||
public CreateRepositoryDialogView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoCommitPageView">
|
||||
<Grid ColumnDefinitions="2*, *">
|
||||
<TreeDataGrid Grid.Column="0">
|
||||
</TreeDataGrid>
|
||||
<Border Grid.Column="1" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="4">
|
||||
<Label Content="Commit Message"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace Flawless.Client.Views.RepositoryPage;
|
||||
|
||||
public partial class RepoCommitPageView : UrsaView
|
||||
{
|
||||
public RepoCommitPageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,29 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoDashboardPageView">
|
||||
|
||||
<Grid ColumnDefinitions="2*, *">
|
||||
<Border Grid.Column="0" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||
<StackPanel>
|
||||
<Label Content="No Readme File Existed"/>
|
||||
<!-- <md:MarkdownScrollViewer Markdown="## Hello World!!"/> -->
|
||||
</StackPanel>
|
||||
</Border>
|
||||
<ScrollViewer Grid.Column="1" Margin="36 20">
|
||||
<StackPanel Orientation="Vertical">
|
||||
<u:Timeline
|
||||
HorizontalAlignment="Left"
|
||||
Mode="Left">
|
||||
<u:TimelineItem Header="New Commit"/>
|
||||
<u:TimelineItem/>
|
||||
<u:TimelineItem/>
|
||||
<u:TimelineItem/>
|
||||
</u:Timeline>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace Flawless.Client.Views.RepositoryPage;
|
||||
|
||||
public partial class RepoDashboardPageView : UrsaView
|
||||
{
|
||||
public RepoDashboardPageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoFileTreePageView">
|
||||
<Grid ColumnDefinitions="2*, *">
|
||||
<TreeDataGrid Grid.Column="0">
|
||||
</TreeDataGrid>
|
||||
<Border Grid.Column="1" Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||
<ScrollViewer>
|
||||
<StackPanel Spacing="4">
|
||||
<Label Content="File History"/>
|
||||
</StackPanel>
|
||||
</ScrollViewer>
|
||||
</Border>
|
||||
</Grid>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace Flawless.Client.Views.RepositoryPage;
|
||||
|
||||
public partial class RepoFileTreePageView : UrsaView
|
||||
{
|
||||
public RepoFileTreePageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
10
Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml
Normal file
10
Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml
Normal file
@ -0,0 +1,10 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoIssuePageView">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<Label Content="Sit down and wait patience."></Label>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace Flawless.Client.Views.RepositoryPage;
|
||||
|
||||
public partial class RepoIssuePageView : UrsaView
|
||||
{
|
||||
public RepoIssuePageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,10 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoSettingPageView">
|
||||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center">
|
||||
<Label Content="Sit down and wait patience."></Label>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace Flawless.Client.Views.RepositoryPage;
|
||||
|
||||
public partial class RepoSettingPageView : UrsaView
|
||||
{
|
||||
public RepoSettingPageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,50 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="Flawless.Client.Views.RepositoryPage.RepoWorkspacePageView">
|
||||
<DockPanel>
|
||||
<StackPanel DockPanel.Dock="Bottom" Spacing="6" Margin="8">
|
||||
<StackPanel Orientation="Horizontal">
|
||||
<Label Content="Commit Message"/>
|
||||
</StackPanel>
|
||||
<TextBox Height="80"/>
|
||||
<StackPanel HorizontalAlignment="Left" Orientation="Horizontal">
|
||||
|
||||
</StackPanel>
|
||||
<Grid ColumnDefinitions="Auto, *, Auto">
|
||||
<StackPanel Grid.Column="0" Orientation="Horizontal" HorizontalAlignment="Left">
|
||||
<Button Content="Sync"></Button>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" Spacing="8">
|
||||
<SplitButton Content="Commit">
|
||||
<SplitButton.Flyout>
|
||||
<MenuFlyout Placement="Top">
|
||||
<MenuItem Header="Lock"/>
|
||||
</MenuFlyout>
|
||||
</SplitButton.Flyout>
|
||||
</SplitButton>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<Border Classes="Shadow" Theme="{StaticResource CardBorder}">
|
||||
<Grid ColumnDefinitions="*, 8, *">
|
||||
<StackPanel Grid.Column="0" Orientation="Vertical" Spacing="8">
|
||||
<Grid RowDefinitions="Auto, Auto">
|
||||
<Label Grid.Row="0" FontSize="12" Content="Changes" HorizontalAlignment="Left"/>
|
||||
<TreeDataGrid Grid.Row="1">
|
||||
</TreeDataGrid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
<StackPanel Grid.Column="2" Orientation="Vertical" Spacing="8">
|
||||
<Grid RowDefinitions="Auto, Auto">
|
||||
<Label Grid.Row="0" FontSize="12" Content="Ready" HorizontalAlignment="Right"/>
|
||||
<TreeDataGrid Grid.Row="1">
|
||||
</TreeDataGrid>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</Border>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
@ -0,0 +1,14 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Ursa.Controls;
|
||||
|
||||
namespace Flawless.Client.Views.RepositoryPage;
|
||||
|
||||
public partial class RepoWorkspacePageView : UrsaView
|
||||
{
|
||||
public RepoWorkspacePageView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@ -5,11 +5,13 @@
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:semi="https://irihi.tech/semi"
|
||||
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||
xmlns:page="using:Flawless.Client.Views.RepositoryPage"
|
||||
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="768"
|
||||
x:Class="Flawless.Client.Views.RepositoryView">
|
||||
|
||||
<DockPanel Margin="50">
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="20">
|
||||
<u:IconButton Icon="{StaticResource SemiIconArrowLeft}" Content="All Repositories"/>
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" VerticalAlignment="Center" Spacing="20">
|
||||
<u:IconButton Height="4" Icon="{StaticResource SemiIconArrowLeft}" Content="All Repositories"/>
|
||||
<Label FontWeight="400" FontSize="28" Content="Name of Repository"/>
|
||||
</StackPanel>
|
||||
<TabControl TabStripPlacement="Top" Margin="0 20">
|
||||
@ -20,6 +22,7 @@
|
||||
<Label Content="Dashboard"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<page:RepoDashboardPageView Margin="18" DataContext="{Binding $self}"/>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
@ -28,6 +31,7 @@
|
||||
<Label Content="Workspace"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<page:RepoWorkspacePageView Margin="18" DataContext="{Binding $self}"/>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
@ -36,6 +40,7 @@
|
||||
<Label Content="File Tree"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<page:RepoFileTreePageView Margin="18" DataContext="{Binding $self}"/>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
@ -44,6 +49,7 @@
|
||||
<Label Content="Commit"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<page:RepoCommitPageView Margin="18" DataContext="{Binding $self}"/>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
@ -52,6 +58,7 @@
|
||||
<Label Content="Issue"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<page:RepoIssuePageView Margin="18" DataContext="{Binding $self}"/>
|
||||
</TabItem>
|
||||
<TabItem>
|
||||
<TabItem.Header>
|
||||
@ -60,6 +67,7 @@
|
||||
<Label Content="Setting"/>
|
||||
</StackPanel>
|
||||
</TabItem.Header>
|
||||
<page:RepoSettingPageView Margin="18" DataContext="{Binding $self}"/>
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
|
||||
@ -5,12 +5,14 @@
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:semi="https://irihi.tech/semi"
|
||||
xmlns:vm="using:Flawless.Client.ViewModels"
|
||||
x:DataType="vm:SettingViewModel"
|
||||
mc:Ignorable="d" d:DesignWidth="1280" d:DesignHeight="768"
|
||||
x:Class="Flawless.Client.Views.SettingView">
|
||||
|
||||
<DockPanel Margin="50">
|
||||
<StackPanel DockPanel.Dock="Top" Orientation="Horizontal" Spacing="20">
|
||||
<u:IconButton Icon="{StaticResource SemiIconArrowLeft}" Content="Back"/>
|
||||
<u:IconButton Height="4" Icon="{StaticResource SemiIconArrowLeft}" Content="Back"
|
||||
Command="{Binding GoBackCommand}"/>
|
||||
<Label FontWeight="400" FontSize="28" Content="Settings"/>
|
||||
</StackPanel>
|
||||
<TabControl TabStripPlacement="Left" Margin="0 20">
|
||||
@ -18,10 +20,14 @@
|
||||
</TabItem>
|
||||
<TabItem Header="Local Storage">
|
||||
</TabItem>
|
||||
<TabItem Header="Server Configuration">
|
||||
</TabItem>
|
||||
<TabItem Header="Server Users">
|
||||
</TabItem>
|
||||
<TabItem Header="Server Info">
|
||||
</TabItem>
|
||||
<TabItem Header="Client Info">
|
||||
</TabItem>
|
||||
</TabControl>
|
||||
</DockPanel>
|
||||
</UserControl>
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using Flawless.Client.ViewModels;
|
||||
using Ursa.ReactiveUIExtension;
|
||||
|
||||
namespace Flawless.Client.Views;
|
||||
|
||||
public partial class SettingView : UserControl
|
||||
public partial class SettingView : ReactiveUrsaView<SettingViewModel>
|
||||
{
|
||||
public SettingView()
|
||||
{
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
namespace Flawless.Communication.Request;
|
||||
|
||||
public class QueryPagesRequest<T>
|
||||
{
|
||||
public required int Offset { get; init; }
|
||||
|
||||
public required int Length { get; init; }
|
||||
|
||||
public T? Parameter { get; init; }
|
||||
}
|
||||
|
||||
public class QueryPagesRequest
|
||||
{
|
||||
public required int Offset { get; init; }
|
||||
|
||||
public required int Length { get; init; }
|
||||
}
|
||||
3
Flawless.Communication/Response/CommitSuccessResponse.cs
Normal file
3
Flawless.Communication/Response/CommitSuccessResponse.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Flawless.Communication.Response;
|
||||
|
||||
public record CommitSuccessResponse(DateTime CommittedOn, Guid CommitId);
|
||||
3
Flawless.Communication/Response/ListingResponse.cs
Normal file
3
Flawless.Communication/Response/ListingResponse.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Flawless.Communication.Response;
|
||||
|
||||
public record ListingResponse<T>(T[] Result);
|
||||
@ -1,25 +0,0 @@
|
||||
namespace Flawless.Communication.Response;
|
||||
|
||||
public record PagedResponse<T>
|
||||
{
|
||||
public required int Offset { get; init; }
|
||||
|
||||
public required int Length { get; init; }
|
||||
|
||||
public int? Total { get; init; }
|
||||
|
||||
public IEnumerable<T>? Data { get; init; }
|
||||
}
|
||||
|
||||
public record PagedResponse<TData, TMetadata>
|
||||
{
|
||||
public required int Offset { get; init; }
|
||||
|
||||
public required int Length { get; init; }
|
||||
|
||||
public int? Total { get; init; }
|
||||
|
||||
public IEnumerable<TData>? Data { get; init; }
|
||||
|
||||
public TMetadata? Metadata { get; init; }
|
||||
}
|
||||
3
Flawless.Communication/Response/PeekResponse.cs
Normal file
3
Flawless.Communication/Response/PeekResponse.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Flawless.Communication.Response;
|
||||
|
||||
public record PeekResponse<T>(T Result);
|
||||
@ -0,0 +1,4 @@
|
||||
namespace Flawless.Communication.Response;
|
||||
|
||||
public record RepositoryCommitResponse(
|
||||
Guid Id, string Author, DateTime CommitedOn, string Message, Guid MainDepotId);
|
||||
@ -8,8 +8,6 @@ public record RepositoryInfoResponse
|
||||
|
||||
public required string OwnerUsername { get; set; }
|
||||
|
||||
public required Guid LatestCommitId { get; set; }
|
||||
|
||||
public string? Description { get; set; }
|
||||
|
||||
public required bool IsArchived { get; set; }
|
||||
|
||||
@ -2,5 +2,7 @@
|
||||
|
||||
public record ServerStatusResponse
|
||||
{
|
||||
public required string? FriendlyName { get; init; }
|
||||
|
||||
public required bool AllowPublicRegister { get; set; }
|
||||
}
|
||||
3
Flawless.Communication/Shared/LockFileInfo.cs
Normal file
3
Flawless.Communication/Shared/LockFileInfo.cs
Normal file
@ -0,0 +1,3 @@
|
||||
namespace Flawless.Communication.Shared;
|
||||
|
||||
public record LockFileInfo(string Path, string Owner);
|
||||
@ -27,7 +27,8 @@ public class AuthenticationController(
|
||||
{
|
||||
return Task.FromResult<ActionResult<ServerStatusResponse>>(Ok(new ServerStatusResponse()
|
||||
{
|
||||
AllowPublicRegister = true
|
||||
AllowPublicRegister = true,
|
||||
FriendlyName = "Ca2didi Server"
|
||||
}));
|
||||
}
|
||||
|
||||
|
||||
@ -13,6 +13,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.OpenApi.Validations.Rules;
|
||||
|
||||
namespace Flawless.Server.Controllers;
|
||||
|
||||
@ -48,7 +49,7 @@ public class RepositoryInnieController(
|
||||
}
|
||||
|
||||
[HttpGet("get_info")]
|
||||
public async Task<IActionResult> IsRepositoryArchiveAsync(string repositoryName)
|
||||
public async Task<ActionResult<RepositoryInfoResponse>> IsRepositoryArchiveAsync(string repositoryName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
|
||||
return BadRequest(new FailedResponse("Repository name is empty or too short!"));
|
||||
@ -67,7 +68,6 @@ public class RepositoryInnieController(
|
||||
{
|
||||
RepositoryName = rp.Name,
|
||||
OwnerUsername = rp.Owner.UserName!,
|
||||
LatestCommitId = rp.Commits.Max(cm => cm.Id),
|
||||
Description = rp.Description,
|
||||
IsArchived = rp.IsArchived,
|
||||
Role = rp.Members.First(m => m.User == u).Role
|
||||
@ -108,7 +108,7 @@ public class RepositoryInnieController(
|
||||
}
|
||||
|
||||
[HttpGet("get_users")]
|
||||
public async Task<IActionResult> GetUsersAsync(string repositoryName, QueryPagesRequest r)
|
||||
public async Task<ActionResult<ListingResponse<RepoUserRole>>> GetUsersAsync(string repositoryName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
|
||||
return BadRequest(new FailedResponse("Repository name is empty or too short!"));
|
||||
@ -122,17 +122,11 @@ public class RepositoryInnieController(
|
||||
|
||||
if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
|
||||
|
||||
return Ok(new PagedResponse<RepoUserRole>
|
||||
return Ok(new ListingResponse<RepoUserRole>(rp.Members.Select(pm => new RepoUserRole
|
||||
{
|
||||
Length = r.Length,
|
||||
Offset = r.Offset,
|
||||
Total = rp.Members.Count,
|
||||
Data = rp.Members.Select(pm => new RepoUserRole
|
||||
{
|
||||
Username = pm.User.UserName!,
|
||||
Role = pm.Role
|
||||
})
|
||||
});
|
||||
Username = pm.User.UserName!,
|
||||
Role = pm.Role
|
||||
}).ToArray()));
|
||||
}
|
||||
|
||||
[HttpPost("update_user")]
|
||||
@ -230,6 +224,7 @@ public class RepositoryInnieController(
|
||||
|
||||
|
||||
[HttpGet("fetch_manifest")]
|
||||
[ProducesResponseType<FileStreamResult>(200)]
|
||||
public async Task<IActionResult> DownloadManifestAsync(string userName, string repositoryName, [FromQuery] string commitId)
|
||||
{
|
||||
if (!Guid.TryParse(commitId, out var commitGuid)) return BadRequest(new FailedResponse("Invalid commit id"));
|
||||
@ -245,6 +240,7 @@ public class RepositoryInnieController(
|
||||
|
||||
|
||||
[HttpGet("fetch_depot")]
|
||||
[ProducesResponseType<FileStreamResult>(200)]
|
||||
public async Task<IActionResult> DownloadDepotAsync(string userName, string repositoryName, [FromQuery] string depotId)
|
||||
{
|
||||
if (!Guid.TryParse(depotId, out var depotGuid)) return BadRequest(new FailedResponse("Invalid depot id"));
|
||||
@ -260,50 +256,49 @@ public class RepositoryInnieController(
|
||||
}
|
||||
|
||||
[HttpGet("list_commit")]
|
||||
public async Task<IActionResult> ListCommitsAsync(string userName, string repositoryName)
|
||||
public async Task<ActionResult<ListingResponse<RepositoryCommitResponse>>> ListCommitsAsync(string userName, string repositoryName)
|
||||
{
|
||||
var user = (await userManager.GetUserAsync(HttpContext.User))!;
|
||||
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Guest);
|
||||
if (grantIssue is not Repository) return (IActionResult) grantIssue;
|
||||
if (grantIssue is not Repository) return (ActionResult) grantIssue;
|
||||
|
||||
var commit = await dbContext.Repositories
|
||||
var commit = dbContext.Repositories
|
||||
.Where(r => r.Owner.UserName == userName && r.Name == repositoryName)
|
||||
.SelectMany(r => r.Commits)
|
||||
.Include(r => r.Author)
|
||||
.Include(r => r.Author).Include(repositoryCommit => repositoryCommit.MainDepot)
|
||||
.OrderByDescending(r => r.CommittedOn)
|
||||
.ToArrayAsync();
|
||||
.AsAsyncEnumerable();
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Commits = commit
|
||||
});
|
||||
List<RepositoryCommitResponse> r = new();
|
||||
await foreach (var cm in commit)
|
||||
r.Add(new RepositoryCommitResponse(
|
||||
cm.Id, cm.Author.UserName!, cm.CommittedOn, cm.Message, cm.MainDepot.DepotId));
|
||||
|
||||
return Ok(new ListingResponse<RepositoryCommitResponse>(r.ToArray()));
|
||||
}
|
||||
|
||||
[HttpGet("list_locked_files")]
|
||||
public async Task<IActionResult> ListLocksAsync(string userName, string repositoryName)
|
||||
public async Task<ActionResult<ListingResponse<LockFileInfo>>> ListLocksAsync(string userName, string repositoryName)
|
||||
{
|
||||
var user = (await userManager.GetUserAsync(HttpContext.User))!;
|
||||
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Developer);
|
||||
if (grantIssue is not Repository) return (IActionResult) grantIssue;
|
||||
if (grantIssue is not Repository) return (ActionResult) grantIssue;
|
||||
|
||||
var lockers = await dbContext.Repositories
|
||||
.Where(r => r.Owner.UserName == userName && r.Name == repositoryName)
|
||||
.SelectMany(r => r.Locked)
|
||||
.Select(l => new { Path = l.Path, Owner = l.LockDownUser.UserName})
|
||||
.Select(l => new LockFileInfo(l.Path, l.LockDownUser.UserName))
|
||||
.ToArrayAsync();
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Lockers = lockers
|
||||
});
|
||||
return Ok(new ListingResponse<LockFileInfo>(lockers));
|
||||
}
|
||||
|
||||
[HttpGet("peek_commit")]
|
||||
public async Task<IActionResult> PeekCommitAsync(string userName, string repositoryName)
|
||||
public async Task<ActionResult<PeekResponse<Guid>>> PeekCommitAsync(string userName, string repositoryName)
|
||||
{
|
||||
var user = (await userManager.GetUserAsync(HttpContext.User))!;
|
||||
var grantIssue = await ValidateRepositoryAsync(userName, repositoryName, user, RepositoryRole.Guest);
|
||||
if (grantIssue is not Repository rp) return (IActionResult) grantIssue;
|
||||
if (grantIssue is not Repository rp) return (ActionResult) grantIssue;
|
||||
|
||||
var commit = await dbContext.Repositories
|
||||
.Where(r => r.Owner.UserName == userName && r.Name == repositoryName)
|
||||
@ -312,10 +307,7 @@ public class RepositoryInnieController(
|
||||
.OrderByDescending(r => r.CommittedOn)
|
||||
.FirstOrDefaultAsync();
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
Commit = commit?.Id.ToString() ?? string.Empty
|
||||
});
|
||||
return Ok(new PeekResponse<Guid>(commit?.Id ?? Guid.Empty));
|
||||
}
|
||||
|
||||
|
||||
@ -394,6 +386,7 @@ public class RepositoryInnieController(
|
||||
}
|
||||
|
||||
[HttpPost("create_commit")]
|
||||
[ProducesResponseType<CommitSuccessResponse>(200)]
|
||||
public async Task<IActionResult> CommitAsync(string userName, string repositoryName, [FromForm] FormCommitRequest req)
|
||||
{
|
||||
var user = (await userManager.GetUserAsync(HttpContext.User))!;
|
||||
@ -538,11 +531,7 @@ public class RepositoryInnieController(
|
||||
throw;
|
||||
}
|
||||
|
||||
return Ok(new
|
||||
{
|
||||
CreatedAt = commit.CommittedOn,
|
||||
CommitId = commit.Id
|
||||
});
|
||||
return Ok(new CommitSuccessResponse(commit.CommittedOn, commit.Id));
|
||||
}
|
||||
|
||||
private static async ValueTask StencilWorkspaceSnapshotAsync(Stream depotStream, HashSet<WorkspaceFile> unresolved)
|
||||
|
||||
@ -14,38 +14,32 @@ namespace Flawless.Server.Controllers;
|
||||
[ApiController, Authorize, Route("api")]
|
||||
public class RepositoryOutieController(AppDbContext dbContext, UserManager<AppUser> userManager) : ControllerBase
|
||||
{
|
||||
|
||||
[HttpGet("repo_list")]
|
||||
public async Task<IActionResult> ListAllAvailableRepositoriesAsync([FromQuery] QueryPagesRequest r)
|
||||
public async Task<ActionResult<ListingResponse<RepositoryInfoResponse>>> ListAllAvailableRepositoriesAsync()
|
||||
{
|
||||
var u = (await userManager.GetUserAsync(HttpContext.User))!;
|
||||
var query = await dbContext.Repositories
|
||||
var query = dbContext.Repositories
|
||||
.Include(repository => repository.Owner)
|
||||
.Include(repository => repository.Commits)
|
||||
.Include(repository => repository.Members)
|
||||
.ThenInclude(repositoryMember => repositoryMember.User)
|
||||
.Where(rp => rp.Members.Any(m => m.User == u))
|
||||
.Skip(r.Offset)
|
||||
.Take(r.Length)
|
||||
.ToArrayAsync();
|
||||
.Where(rp => rp.Members.Any(m => m.User == u) || rp.Owner == u)
|
||||
.Select(rp => new RepositoryInfoResponse
|
||||
{
|
||||
RepositoryName = rp.Name,
|
||||
OwnerUsername = rp.Owner.UserName!,
|
||||
Description = rp.Description,
|
||||
IsArchived = rp.IsArchived,
|
||||
Role = rp.Owner == u ? RepositoryRole.Owner : rp.Members.First(m => m.User == u).Role
|
||||
})
|
||||
.ToArray();
|
||||
|
||||
return Ok(new PagedResponse<RepositoryInfoResponse>
|
||||
{
|
||||
Length = r.Length,
|
||||
Offset = r.Offset,
|
||||
Data = query.Select(rp => new RepositoryInfoResponse
|
||||
{
|
||||
RepositoryName = rp.Name,
|
||||
OwnerUsername = rp.Owner.UserName!,
|
||||
LatestCommitId = rp.Commits.OrderByDescending(cm => cm.CommittedOn).FirstOrDefault()?.Id ?? Guid.Empty,
|
||||
Description = rp.Description,
|
||||
IsArchived = rp.IsArchived,
|
||||
Role = rp.Members.First(m => m.User == u).Role
|
||||
}),
|
||||
});
|
||||
return Ok(new ListingResponse<RepositoryInfoResponse>(query));
|
||||
}
|
||||
|
||||
[HttpPost("repo_create")]
|
||||
public async Task<IActionResult> CreateRepositoryAsync([FromQuery] string repositoryName)
|
||||
public async Task<ActionResult<RepositoryInfoResponse>> CreateRepositoryAsync([FromQuery] string repositoryName)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
|
||||
return BadRequest(new FailedResponse("Repository name is empty or too short!"));
|
||||
@ -54,13 +48,21 @@ public class RepositoryOutieController(AppDbContext dbContext, UserManager<AppUs
|
||||
if (await dbContext.Repositories.AnyAsync(rp => rp.Name == repositoryName && u == rp.Owner))
|
||||
return BadRequest(new FailedResponse("Repository name has already created!"));
|
||||
|
||||
await dbContext.Repositories.AddAsync(new Repository()
|
||||
var repo = new Repository()
|
||||
{
|
||||
Name = repositoryName,
|
||||
Owner = u,
|
||||
});
|
||||
};
|
||||
|
||||
await dbContext.Repositories.AddAsync(repo);
|
||||
await dbContext.SaveChangesAsync();
|
||||
return Ok();
|
||||
return Ok(new RepositoryInfoResponse()
|
||||
{
|
||||
RepositoryName = repositoryName,
|
||||
OwnerUsername = u.UserName!,
|
||||
Description = string.Empty,
|
||||
IsArchived = false,
|
||||
Role = RepositoryRole.Owner
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -94,22 +94,15 @@ public class UserController(
|
||||
}
|
||||
|
||||
[HttpGet("query_info")]
|
||||
public async Task<ActionResult<PagedResponse<UserInfoResponse>>> GetUserInfoAsync(QueryPagesRequest r, [FromQuery] string keyword)
|
||||
public async Task<ActionResult<ListingResponse<UserInfoResponse>>> QueryUserInfoAsync([FromQuery] string keyword)
|
||||
{
|
||||
var payload = await userManager.Users
|
||||
.Where(u => u.UserName!.Contains(keyword) || (u.NickName != null && u.NickName.Contains(keyword)))
|
||||
.Skip(r.Offset)
|
||||
.Take(r.Length)
|
||||
.Select(u => GetUserInfoInternal(u, null))
|
||||
.ToArrayAsync();
|
||||
|
||||
// Return self as default
|
||||
return Ok(new PagedResponse<UserInfoResponse>
|
||||
{
|
||||
Offset = r.Offset,
|
||||
Length = r.Length,
|
||||
Data = payload
|
||||
});
|
||||
return Ok(new ListingResponse<UserInfoResponse>(payload));
|
||||
}
|
||||
|
||||
[HttpGet("delete")]
|
||||
|
||||
@ -37,4 +37,8 @@
|
||||
<Compile Remove="Migrations\20250322194407_InitialCreate.Designer.cs" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Folder Include="Exceptions\" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -6,7 +6,10 @@ public class ExceptionTransformMiddleware(RequestDelegate next)
|
||||
{
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
try { await next(context); }
|
||||
try
|
||||
{
|
||||
await next(context);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
context.Response.StatusCode = 500;
|
||||
|
||||
@ -11,6 +11,7 @@ using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.IdentityModel.Tokens;
|
||||
using Microsoft.OpenApi.Models;
|
||||
|
||||
namespace Flawless.Server;
|
||||
|
||||
@ -46,6 +47,26 @@ public static class Program
|
||||
{
|
||||
opt.DocInclusionPredicate((name, api) => api.HttpMethod != null); // Filter out WebSocket methods
|
||||
opt.SupportNonNullableReferenceTypes();
|
||||
opt.AddSecurityDefinition("Bearer", new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
Description = "JWT Authorization header using the Bearer scheme.",
|
||||
Name = "Authorization",
|
||||
Type = SecuritySchemeType.ApiKey,
|
||||
});
|
||||
opt.AddSecurityRequirement(new OpenApiSecurityRequirement
|
||||
{
|
||||
{
|
||||
new OpenApiSecurityScheme
|
||||
{
|
||||
Reference = new OpenApiReference
|
||||
{
|
||||
Type = ReferenceType.SecurityScheme,
|
||||
Id = "Bearer"
|
||||
}
|
||||
}, []
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -164,7 +185,7 @@ public static class Program
|
||||
if (u == null) throw new SecurityTokenExpiredException("User is not existed.");
|
||||
|
||||
if (u.SecurityStamp != stamp) throw new SecurityTokenExpiredException("SecurityStamp is mismatched.");
|
||||
if (u.LockoutEnabled) throw new SecurityTokenExpiredException("User has been locked.");
|
||||
// if (u.LockoutEnabled) throw new SecurityTokenExpiredException("User has been locked."); //todo Fix lockout prob
|
||||
}
|
||||
|
||||
// Extract user info into HttpContext
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user