diff --git a/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml b/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
index 6de3b67..0c9d2dd 100644
--- a/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
+++ b/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
@@ -6,6 +6,8 @@
+
+
@@ -13,6 +15,7 @@
+
diff --git a/Flawless.Client/AppDefaultValues.cs b/Flawless.Client/AppDefaultValues.cs
new file mode 100644
index 0000000..1e6362b
--- /dev/null
+++ b/Flawless.Client/AppDefaultValues.cs
@@ -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");
+}
\ No newline at end of file
diff --git a/Flawless.Client/Flawless.Client.csproj b/Flawless.Client/Flawless.Client.csproj
index 8c78081..744b21e 100644
--- a/Flawless.Client/Flawless.Client.csproj
+++ b/Flawless.Client/Flawless.Client.csproj
@@ -29,6 +29,7 @@
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
@@ -38,10 +39,21 @@
-
+
-
+
+ LoginPageView.axaml
+ Code
+
+
+ RegisterPageView.axaml
+ Code
+
+
+ ServerSetupPageView.axaml
+ Code
+
diff --git a/Flawless.Client/Models/AppSettingModel.cs b/Flawless.Client/Models/AppSettingModel.cs
new file mode 100644
index 0000000..95a7b4a
--- /dev/null
+++ b/Flawless.Client/Models/AppSettingModel.cs
@@ -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;
+}
\ No newline at end of file
diff --git a/Flawless.Client/Models/CommitModel.cs b/Flawless.Client/Models/CommitModel.cs
new file mode 100644
index 0000000..123cd93
--- /dev/null
+++ b/Flawless.Client/Models/CommitModel.cs
@@ -0,0 +1,6 @@
+namespace Flawless.Client.Models;
+
+public record CommitModel : ReactiveRecordModel
+{
+
+}
\ No newline at end of file
diff --git a/Flawless.Client/Models/ReactiveModel.cs b/Flawless.Client/Models/ReactiveModel.cs
new file mode 100644
index 0000000..6f57667
--- /dev/null
+++ b/Flawless.Client/Models/ReactiveModel.cs
@@ -0,0 +1,7 @@
+using ReactiveUI;
+
+namespace Flawless.Client.Models;
+
+public abstract class ReactiveModel : ReactiveObject {}
+
+public abstract record ReactiveRecordModel : ReactiveRecord {}
\ No newline at end of file
diff --git a/Flawless.Client/Models/RepositoryHomePageModel.cs b/Flawless.Client/Models/RepositoryHomePageModel.cs
deleted file mode 100644
index 33b4862..0000000
--- a/Flawless.Client/Models/RepositoryHomePageModel.cs
+++ /dev/null
@@ -1,29 +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,
- bool IsLocalAvailable,
- 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,
- true,
- r.LatestCommitId.ToString().Substring(0, 6));
- }
-}
\ No newline at end of file
diff --git a/Flawless.Client/Models/RepositoryModel.cs b/Flawless.Client/Models/RepositoryModel.cs
new file mode 100644
index 0000000..c4e2671
--- /dev/null
+++ b/Flawless.Client/Models/RepositoryModel.cs
@@ -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 Members { get; } = new();
+
+ public ObservableCollection Commits { get; } = new();
+
+ public ObservableCollection Depots { get; } = new();
+
+ public ObservableCollection 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 Dependencies { get; } = new();
+ }
+}
diff --git a/Flawless.Client/Models/UserModel.cs b/Flawless.Client/Models/UserModel.cs
new file mode 100644
index 0000000..731443f
--- /dev/null
+++ b/Flawless.Client/Models/UserModel.cs
@@ -0,0 +1,6 @@
+namespace Flawless.Client.Models;
+
+public record UserModel : ReactiveRecordModel
+{
+
+}
\ No newline at end of file
diff --git a/Flawless.Client/Service/Api.cs b/Flawless.Client/Service/Api.cs
index ebe81e9..f716a17 100644
--- a/Flawless.Client/Service/Api.cs
+++ b/Flawless.Client/Service/Api.cs
@@ -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
{
- #region Instance
+ class ApiAuthenticationHandler : DelegatingHandler
+ {
+ public ApiAuthenticationHandler(HttpMessageHandler innerHandler) : base(innerHandler)
+ {
+ }
- private static Api? _instance;
-
- public static Api Current => _instance ??= new Api();
+ protected override Task 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);
+ }
- #endregion
+ }
public IObservable 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($"Bearer {_token}") : Task.FromResult(string.Empty)
+ HttpMessageHandlerFactory = () => new ApiAuthenticationHandler(new HttpClientHandler())
};
var tempGateway = RestService.For(host, setting);
diff --git a/Flawless.Client/Service/BaseService.cs b/Flawless.Client/Service/BaseService.cs
new file mode 100644
index 0000000..14a1aba
--- /dev/null
+++ b/Flawless.Client/Service/BaseService.cs
@@ -0,0 +1,10 @@
+namespace Flawless.Client.Service;
+
+public class BaseService where TService : class, new()
+{
+ private static TService? _currentService;
+
+ public static TService Current => _currentService ??= new TService();
+
+ public static TService C => Current;
+}
\ No newline at end of file
diff --git a/Flawless.Client/Service/Remote_Generated.cs b/Flawless.Client/Service/Remote_Generated.cs
index 62ba23b..3c714c3 100644
--- a/Flawless.Client/Service/Remote_Generated.cs
+++ b/Flawless.Client/Service/Remote_Generated.cs
@@ -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
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/admin/user/delete/{username}")]
- Task Delete(string username, CancellationToken cancellationToken = default);
+ Task Delete(string username);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/admin/user/enable/{username}")]
- Task Enable(string username, CancellationToken cancellationToken = default);
+ Task Enable(string username);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/admin/user/disable/{username}")]
- Task Disable(string username, CancellationToken cancellationToken = default);
+ Task Disable(string username);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/admin/user/reset_password")]
- Task ResetPassword([Body] ResetPasswordRequest body, CancellationToken cancellationToken = default);
+ Task ResetPassword([Body] ResetPasswordRequest body);
/// OK
/// Thrown when the request returns a non-success status code.
[Headers("Accept: text/plain, application/json, text/json")]
[Get("/api/auth/status")]
- Task Status(CancellationToken cancellationToken = default);
+ Task Status();
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/auth/register")]
- Task Register([Body] RegisterRequest body, CancellationToken cancellationToken = default);
+ Task Register([Body] RegisterRequest body);
/// OK
/// Thrown when the request returns a non-success status code.
[Headers("Accept: text/plain, application/json, text/json")]
[Post("/api/auth/login")]
- Task Login([Body] LoginRequest body, CancellationToken cancellationToken = default);
+ Task Login([Body] LoginRequest body);
/// OK
/// Thrown when the request returns a non-success status code.
[Headers("Accept: text/plain, application/json, text/json")]
[Post("/api/auth/refresh")]
- Task Refresh([Body] TokenInfo body, CancellationToken cancellationToken = default);
+ Task Refresh([Body] TokenInfo body);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/auth/logout_all")]
- Task LogoutAll(CancellationToken cancellationToken = default);
+ Task LogoutAll();
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/auth/renew_password")]
- Task RenewPassword([Body] ResetPasswordRequest body, CancellationToken cancellationToken = default);
+ Task RenewPassword([Body] ResetPasswordRequest body);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Get("/")]
- Task Index(CancellationToken cancellationToken = default);
+ Task Index();
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/repo/{userName}/{repositoryName}/delete_repo")]
- Task DeleteRepo(string repositoryName, string userName, CancellationToken cancellationToken = default);
+ Task DeleteRepo(string repositoryName, string userName);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [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 GetInfo(string repositoryName, string userName);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/repo/{userName}/{repositoryName}/archive_repo")]
- Task ArchiveRepo(string repositoryName, string userName, CancellationToken cancellationToken = default);
+ Task ArchiveRepo(string repositoryName, string userName);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/repo/{userName}/{repositoryName}/unarchive_repo")]
- Task UnarchiveRepo(string repositoryName, string userName, CancellationToken cancellationToken = default);
+ Task UnarchiveRepo(string repositoryName, string userName);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [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 GetUsers(string repositoryName, string userName);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[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);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[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);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [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 FetchManifest(string userName, string repositoryName, [Query] string commitId);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [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 FetchDepot(string userName, string repositoryName, [Query] string depotId);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [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 ListCommit(string userName, string repositoryName);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [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 ListLockedFiles(string userName, string repositoryName);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [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 PeekCommit(string userName, string repositoryName);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[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);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[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);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
[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 workspaceSnapshot, IEnumerable requiredDepots, string mainDepotId, CancellationToken cancellationToken = default);
+ Task CreateCommit(string userName, string repositoryName, StreamPart depot, string message, IEnumerable workspaceSnapshot, IEnumerable requiredDepots, string mainDepotId);
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [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 RepoList();
- /// A that completes when the request is finished.
+ /// OK
/// Thrown when the request returns a non-success status code.
+ [Headers("Accept: text/plain, application/json, text/json")]
[Post("/api/repo_create")]
- Task RepoCreate([Query] string repositoryName, CancellationToken cancellationToken = default);
+ Task RepoCreate([Query] string repositoryName);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/user/update_info")]
- Task UpdateInfo([Body] UserInfoModifyResponse body, CancellationToken cancellationToken = default);
+ Task UpdateInfo([Body] UserInfoModifyResponse body);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/user/update_email")]
- Task UpdateEmail([Body] UserContactModifyResponse body, CancellationToken cancellationToken = default);
+ Task UpdateEmail([Body] UserContactModifyResponse body);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[Post("/api/user/update_phone")]
- Task UpdatePhone([Body] UserContactModifyResponse body, CancellationToken cancellationToken = default);
+ Task UpdatePhone([Body] UserContactModifyResponse body);
/// OK
/// Thrown when the request returns a non-success status code.
[Headers("Accept: text/plain, application/json, text/json")]
[Get("/api/user/get_info")]
- Task GetInfo([Query] string username, CancellationToken cancellationToken = default);
+ Task GetInfo([Query] string username);
/// OK
/// Thrown when the request returns a non-success status code.
[Headers("Accept: text/plain, application/json, text/json")]
[Get("/api/user/query_info")]
- Task QueryInfo([Query] string keyword, [Body] QueryPagesRequest body, CancellationToken cancellationToken = default);
+ Task QueryInfo([Query] string keyword);
/// A that completes when the request is finished.
/// Thrown when the request returns a non-success status code.
[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 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 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 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 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 Data { get; set; }
+ [JsonPropertyName("result")]
+ public ICollection 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> Headers { get; private set; }
+
+ public System.IO.Stream Stream { get; private set; }
+
+ public bool IsPartial
+ {
+ get { return StatusCode == 206; }
+ }
+
+ public FileResponse(int statusCode, IReadOnlyDictionary> 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();
+ }
+ }
+
+
}
diff --git a/Flawless.Client/Service/RepositoryService.cs b/Flawless.Client/Service/RepositoryService.cs
new file mode 100644
index 0000000..f8f57ed
--- /dev/null
+++ b/Flawless.Client/Service/RepositoryService.cs
@@ -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
+{
+ public ObservableCollection Repositories => _repositories;
+
+
+ private readonly ObservableCollection _repositories = new();
+
+ public async ValueTask 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 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 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 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;
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/Service/SettingService.cs b/Flawless.Client/Service/SettingService.cs
new file mode 100644
index 0000000..e5be8e6
--- /dev/null
+++ b/Flawless.Client/Service/SettingService.cs
@@ -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
+{
+
+ 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);
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/ViewModels/HomeViewModel.cs b/Flawless.Client/ViewModels/HomeViewModel.cs
index e089cd3..1d7ecb9 100644
--- a/Flawless.Client/ViewModels/HomeViewModel.cs
+++ b/Flawless.Client/ViewModels/HomeViewModel.cs
@@ -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;
@@ -18,34 +19,51 @@ public partial class HomeViewModel : ViewModelBase, IRoutableViewModel
public string? UrlPathSegment { get; } = Guid.NewGuid().ToString();
public IScreen HostScreen { get; }
-
- public ObservableCollection Repositories { get; } = new(new[]
- {
- new RepositoryHomePageModel(
- "cardidi", "test1", "Abc", false, true, false, ""),
- new RepositoryHomePageModel(
- "cardidi", "RT001", "Abc", false, true, 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(form, AppDefaultValues.HostId, opt);
+
+ if (mr == DialogResult.OK)
+ {
+ await RepoSrc.CreateRepositoryOnServerAsync(form.RepositoryName);
+ }
}
[ReactiveCommand]
@@ -53,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));
+ }
}
\ No newline at end of file
diff --git a/Flawless.Client/ViewModels/LoginPageViewModel.cs b/Flawless.Client/ViewModels/LoginPageViewModel.cs
index 0deccdc..87de465 100644
--- a/Flawless.Client/ViewModels/LoginPageViewModel.cs
+++ b/Flawless.Client/ViewModels/LoginPageViewModel.cs
@@ -25,7 +25,7 @@ public partial class LoginPageViewModel : ViewModelBase, IRoutableViewModel
public IObservable CanLogin;
- public IObservable CanRegister => Api.Current.Status.Select(s => s != null && s.AllowPublicRegister);
+ public IObservable 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)
{
diff --git a/Flawless.Client/ViewModels/MainWindowViewModel.cs b/Flawless.Client/ViewModels/MainWindowViewModel.cs
index 1db7b7b..865346f 100644
--- a/Flawless.Client/ViewModels/MainWindowViewModel.cs
+++ b/Flawless.Client/ViewModels/MainWindowViewModel.cs
@@ -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
}
diff --git a/Flawless.Client/ViewModels/ModalBox/CreateRepositoryDialogViewModel.cs b/Flawless.Client/ViewModels/ModalBox/CreateRepositoryDialogViewModel.cs
new file mode 100644
index 0000000..411fdcb
--- /dev/null
+++ b/Flawless.Client/ViewModels/ModalBox/CreateRepositoryDialogViewModel.cs
@@ -0,0 +1,8 @@
+using ReactiveUI.SourceGenerators;
+
+namespace Flawless.Client.ViewModels.ModalBox;
+
+public partial class CreateRepositoryDialogViewModel : ViewModelBase
+{
+ [Reactive] private string _repositoryName;
+}
\ No newline at end of file
diff --git a/Flawless.Client/ViewModels/RegisterPageViewModel.cs b/Flawless.Client/ViewModels/RegisterPageViewModel.cs
index 3845be4..d351ea9 100644
--- a/Flawless.Client/ViewModels/RegisterPageViewModel.cs
+++ b/Flawless.Client/ViewModels/RegisterPageViewModel.cs
@@ -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)
{
diff --git a/Flawless.Client/ViewModels/RepositoryViewModel.cs b/Flawless.Client/ViewModels/RepositoryViewModel.cs
new file mode 100644
index 0000000..0078e17
--- /dev/null
+++ b/Flawless.Client/ViewModels/RepositoryViewModel.cs
@@ -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;
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/ViewModels/ServerConnectViewModel.cs b/Flawless.Client/ViewModels/ServerConnectViewModel.cs
index 4087f13..6ff8f71 100644
--- a/Flawless.Client/ViewModels/ServerConnectViewModel.cs
+++ b/Flawless.Client/ViewModels/ServerConnectViewModel.cs
@@ -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)
diff --git a/Flawless.Client/ViewModels/ServerSetupPageViewModel.cs b/Flawless.Client/ViewModels/ServerSetupPageViewModel.cs
index 9535866..dad59b1 100644
--- a/Flawless.Client/ViewModels/ServerSetupPageViewModel.cs
+++ b/Flawless.Client/ViewModels/ServerSetupPageViewModel.cs
@@ -9,12 +9,8 @@ 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/";
@@ -22,12 +18,11 @@ public partial class ServerSetupPageViewModel : ViewModelBase, IRoutableViewMode
public IObservable 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)
diff --git a/Flawless.Client/ViewModels/SettingViewModel.cs b/Flawless.Client/ViewModels/SettingViewModel.cs
new file mode 100644
index 0000000..fa3b287
--- /dev/null
+++ b/Flawless.Client/ViewModels/SettingViewModel.cs
@@ -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)
+ {
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/ViewModels/ViewModelBase.cs b/Flawless.Client/ViewModels/ViewModelBase.cs
index 229260c..de4f774 100644
--- a/Flawless.Client/ViewModels/ViewModelBase.cs
+++ b/Flawless.Client/ViewModels/ViewModelBase.cs
@@ -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 GoBackCommand => HostScreen.Router.NavigateBack;
+
+ protected RoutableViewModelBase(IScreen hostScreen)
+ {
+ HostScreen = hostScreen;
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/Views/LoginPageView.axaml b/Flawless.Client/Views/HelloSetup/LoginPageView.axaml
similarity index 100%
rename from Flawless.Client/Views/LoginPageView.axaml
rename to Flawless.Client/Views/HelloSetup/LoginPageView.axaml
diff --git a/Flawless.Client/Views/LoginPageView.axaml.cs b/Flawless.Client/Views/HelloSetup/LoginPageView.axaml.cs
similarity index 100%
rename from Flawless.Client/Views/LoginPageView.axaml.cs
rename to Flawless.Client/Views/HelloSetup/LoginPageView.axaml.cs
diff --git a/Flawless.Client/Views/RegisterPageView.axaml b/Flawless.Client/Views/HelloSetup/RegisterPageView.axaml
similarity index 100%
rename from Flawless.Client/Views/RegisterPageView.axaml
rename to Flawless.Client/Views/HelloSetup/RegisterPageView.axaml
diff --git a/Flawless.Client/Views/RegisterPageView.axaml.cs b/Flawless.Client/Views/HelloSetup/RegisterPageView.axaml.cs
similarity index 100%
rename from Flawless.Client/Views/RegisterPageView.axaml.cs
rename to Flawless.Client/Views/HelloSetup/RegisterPageView.axaml.cs
diff --git a/Flawless.Client/Views/ServerSetupPageView.axaml b/Flawless.Client/Views/HelloSetup/ServerSetupPageView.axaml
similarity index 100%
rename from Flawless.Client/Views/ServerSetupPageView.axaml
rename to Flawless.Client/Views/HelloSetup/ServerSetupPageView.axaml
diff --git a/Flawless.Client/Views/ServerSetupPageView.axaml.cs b/Flawless.Client/Views/HelloSetup/ServerSetupPageView.axaml.cs
similarity index 100%
rename from Flawless.Client/Views/ServerSetupPageView.axaml.cs
rename to Flawless.Client/Views/HelloSetup/ServerSetupPageView.axaml.cs
diff --git a/Flawless.Client/Views/HomeView.axaml b/Flawless.Client/Views/HomeView.axaml
index 789fceb..31b7692 100644
--- a/Flawless.Client/Views/HomeView.axaml
+++ b/Flawless.Client/Views/HomeView.axaml
@@ -17,8 +17,10 @@
-
-
+
+
@@ -29,31 +31,37 @@
-
-
+
+
+
+
+
+
-
+ IsVisible="{Binding !IsDownloaded}"/>
-
+ IsVisible="{Binding IsDownloaded}"/>
+
@@ -64,7 +72,7 @@
-
@@ -73,10 +81,6 @@
-
-
-
-
diff --git a/Flawless.Client/Views/MainWindowView.axaml b/Flawless.Client/Views/MainWindowView.axaml
index de714b2..c0ab123 100644
--- a/Flawless.Client/Views/MainWindowView.axaml
+++ b/Flawless.Client/Views/MainWindowView.axaml
@@ -20,6 +20,6 @@
-
+
\ No newline at end of file
diff --git a/Flawless.Client/Views/ModalBox/CreateRepositoryDialogView.axaml b/Flawless.Client/Views/ModalBox/CreateRepositoryDialogView.axaml
new file mode 100644
index 0000000..5d0863b
--- /dev/null
+++ b/Flawless.Client/Views/ModalBox/CreateRepositoryDialogView.axaml
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flawless.Client/Views/ModalBox/CreateRepositoryDialogView.axaml.cs b/Flawless.Client/Views/ModalBox/CreateRepositoryDialogView.axaml.cs
new file mode 100644
index 0000000..e2bc176
--- /dev/null
+++ b/Flawless.Client/Views/ModalBox/CreateRepositoryDialogView.axaml.cs
@@ -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
+{
+ public CreateRepositoryDialogView()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml.cs b/Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml.cs
index 5ee064a..31c3c88 100644
--- a/Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml.cs
+++ b/Flawless.Client/Views/RepositoryPage/RepoCommitPageView.axaml.cs
@@ -1,10 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Ursa.Controls;
namespace Flawless.Client.Views.RepositoryPage;
-public partial class RepoCommitPageView : UserControl
+public partial class RepoCommitPageView : UrsaView
{
public RepoCommitPageView()
{
diff --git a/Flawless.Client/Views/RepositoryPage/RepoDashboardPageView.axaml.cs b/Flawless.Client/Views/RepositoryPage/RepoDashboardPageView.axaml.cs
index bcaeed4..4be2d22 100644
--- a/Flawless.Client/Views/RepositoryPage/RepoDashboardPageView.axaml.cs
+++ b/Flawless.Client/Views/RepositoryPage/RepoDashboardPageView.axaml.cs
@@ -1,10 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Ursa.Controls;
namespace Flawless.Client.Views.RepositoryPage;
-public partial class RepoDashboardPageView : UserControl
+public partial class RepoDashboardPageView : UrsaView
{
public RepoDashboardPageView()
{
diff --git a/Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml.cs b/Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml.cs
index 4de5b37..7a718e9 100644
--- a/Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml.cs
+++ b/Flawless.Client/Views/RepositoryPage/RepoFileTreePageView.axaml.cs
@@ -1,10 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Ursa.Controls;
namespace Flawless.Client.Views.RepositoryPage;
-public partial class RepoFileTreePageView : UserControl
+public partial class RepoFileTreePageView : UrsaView
{
public RepoFileTreePageView()
{
diff --git a/Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml.cs b/Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml.cs
index 8f02919..323bfba 100644
--- a/Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml.cs
+++ b/Flawless.Client/Views/RepositoryPage/RepoIssuePageView.axaml.cs
@@ -1,10 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Ursa.Controls;
namespace Flawless.Client.Views.RepositoryPage;
-public partial class RepoIssuePageView : UserControl
+public partial class RepoIssuePageView : UrsaView
{
public RepoIssuePageView()
{
diff --git a/Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml.cs b/Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml.cs
index e64d44f..9c53abc 100644
--- a/Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml.cs
+++ b/Flawless.Client/Views/RepositoryPage/RepoSettingPageView.axaml.cs
@@ -1,10 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Ursa.Controls;
namespace Flawless.Client.Views.RepositoryPage;
-public partial class RepoSettingPageView : UserControl
+public partial class RepoSettingPageView : UrsaView
{
public RepoSettingPageView()
{
diff --git a/Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml.cs b/Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml.cs
index 35d0b36..1c015b8 100644
--- a/Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml.cs
+++ b/Flawless.Client/Views/RepositoryPage/RepoWorkspacePageView.axaml.cs
@@ -1,10 +1,11 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using Ursa.Controls;
namespace Flawless.Client.Views.RepositoryPage;
-public partial class RepoWorkspacePageView : UserControl
+public partial class RepoWorkspacePageView : UrsaView
{
public RepoWorkspacePageView()
{
diff --git a/Flawless.Client/Views/SettingView.axaml b/Flawless.Client/Views/SettingView.axaml
index 664a162..d8801f1 100644
--- a/Flawless.Client/Views/SettingView.axaml
+++ b/Flawless.Client/Views/SettingView.axaml
@@ -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">
-
+
@@ -18,10 +20,14 @@
+
+
+
+
diff --git a/Flawless.Client/Views/SettingView.axaml.cs b/Flawless.Client/Views/SettingView.axaml.cs
index b27bebb..67cb531 100644
--- a/Flawless.Client/Views/SettingView.axaml.cs
+++ b/Flawless.Client/Views/SettingView.axaml.cs
@@ -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
{
public SettingView()
{
diff --git a/Flawless.Communication/Response/RepositoryInfoResponse.cs b/Flawless.Communication/Response/RepositoryInfoResponse.cs
index d5f792f..3c84d1a 100644
--- a/Flawless.Communication/Response/RepositoryInfoResponse.cs
+++ b/Flawless.Communication/Response/RepositoryInfoResponse.cs
@@ -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; }
diff --git a/Flawless.Server/Controllers/RepositoryInnieController.cs b/Flawless.Server/Controllers/RepositoryInnieController.cs
index eadc4da..7864d85 100644
--- a/Flawless.Server/Controllers/RepositoryInnieController.cs
+++ b/Flawless.Server/Controllers/RepositoryInnieController.cs
@@ -49,7 +49,7 @@ public class RepositoryInnieController(
}
[HttpGet("get_info")]
- public async Task IsRepositoryArchiveAsync(string repositoryName)
+ public async Task> IsRepositoryArchiveAsync(string repositoryName)
{
if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
return BadRequest(new FailedResponse("Repository name is empty or too short!"));
@@ -68,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
diff --git a/Flawless.Server/Controllers/RepositoryOutieController.cs b/Flawless.Server/Controllers/RepositoryOutieController.cs
index fcf0f4d..9355631 100644
--- a/Flawless.Server/Controllers/RepositoryOutieController.cs
+++ b/Flawless.Server/Controllers/RepositoryOutieController.cs
@@ -24,16 +24,14 @@ public class RepositoryOutieController(AppDbContext dbContext, UserManager repository.Commits)
.Include(repository => repository.Members)
.ThenInclude(repositoryMember => repositoryMember.User)
- .Where(rp => rp.Members.Any(m => m.User == u))
+ .Where(rp => rp.Members.Any(m => m.User == u) || rp.Owner == u)
.Select(rp => new RepositoryInfoResponse
{
RepositoryName = rp.Name,
OwnerUsername = rp.Owner.UserName!,
- LatestCommitId = rp.Commits.OrderByDescending(cm => cm.CommittedOn)
- .Select(cm => cm.Id).FirstOrDefault(),
Description = rp.Description,
IsArchived = rp.IsArchived,
- Role = rp.Members.First(m => m.User == u).Role
+ Role = rp.Owner == u ? RepositoryRole.Owner : rp.Members.First(m => m.User == u).Role
})
.ToArray();
@@ -41,7 +39,7 @@ public class RepositoryOutieController(AppDbContext dbContext, UserManager CreateRepositoryAsync([FromQuery] string repositoryName)
+ public async Task> CreateRepositoryAsync([FromQuery] string repositoryName)
{
if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
return BadRequest(new FailedResponse("Repository name is empty or too short!"));
@@ -50,13 +48,21 @@ public class RepositoryOutieController(AppDbContext dbContext, UserManager 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
+ });
}
}
\ No newline at end of file
diff --git a/Flawless.Server/Program.cs b/Flawless.Server/Program.cs
index ddb68f9..63c0c96 100644
--- a/Flawless.Server/Program.cs
+++ b/Flawless.Server/Program.cs
@@ -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