diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json
new file mode 100644
index 0000000..41c2068
--- /dev/null
+++ b/.config/dotnet-tools.json
@@ -0,0 +1,13 @@
+{
+ "version": 1,
+ "isRoot": true,
+ "tools": {
+ "nswag.consolecore": {
+ "version": "14.2.0",
+ "commands": [
+ "nswag"
+ ],
+ "rollForward": false
+ }
+ }
+}
\ No newline at end of file
diff --git a/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml b/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
index caa4d57..8859eb9 100644
--- a/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
+++ b/.idea/.idea.Flawless-Version-Control/.idea/avalonia.xml
@@ -4,6 +4,8 @@
diff --git a/Flawless-Version-Control.sln b/Flawless-Version-Control.sln
index 3b01626..5c7fc60 100644
--- a/Flawless-Version-Control.sln
+++ b/Flawless-Version-Control.sln
@@ -10,6 +10,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flawless.Communication", "F
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flawless.Server", "Flawless.Server\Flawless.Server.csproj", "{66142212-034C-4702-92FE-5C625D725048}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flawless.Client", "Flawless.Client\Flawless.Client.csproj", "{CEC2183E-0097-4972-BBEB-7CE3C6D874B9}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -36,5 +38,9 @@ Global
{66142212-034C-4702-92FE-5C625D725048}.Debug|Any CPU.Build.0 = Debug|Any CPU
{66142212-034C-4702-92FE-5C625D725048}.Release|Any CPU.ActiveCfg = Release|Any CPU
{66142212-034C-4702-92FE-5C625D725048}.Release|Any CPU.Build.0 = Release|Any CPU
+ {CEC2183E-0097-4972-BBEB-7CE3C6D874B9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {CEC2183E-0097-4972-BBEB-7CE3C6D874B9}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {CEC2183E-0097-4972-BBEB-7CE3C6D874B9}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {CEC2183E-0097-4972-BBEB-7CE3C6D874B9}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
EndGlobal
diff --git a/Flawless-Version-Control.sln.DotSettings.user b/Flawless-Version-Control.sln.DotSettings.user
index 7c73cf0..739c965 100644
--- a/Flawless-Version-Control.sln.DotSettings.user
+++ b/Flawless-Version-Control.sln.DotSettings.user
@@ -1,5 +1,6 @@
ForceIncluded
+ ForceIncluded
ForceIncluded
ForceIncluded
ForceIncluded
diff --git a/Flawless.Client/App.axaml b/Flawless.Client/App.axaml
new file mode 100644
index 0000000..a17c221
--- /dev/null
+++ b/Flawless.Client/App.axaml
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/Flawless.Client/App.axaml.cs b/Flawless.Client/App.axaml.cs
new file mode 100644
index 0000000..8889aed
--- /dev/null
+++ b/Flawless.Client/App.axaml.cs
@@ -0,0 +1,40 @@
+using System;
+using System.Net.Http;
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using Flawless.Client.Remote;
+using Flawless.Client.ViewModels;
+using Flawless.Client.Views;
+using Refit;
+
+namespace Flawless.Client;
+
+public partial class App : Application
+{
+ public IFlawlessServer ApiGateway { get; private set; }
+
+ public override void Initialize()
+ {
+ AvaloniaXamlLoader.Load(this);
+ ApiGateway = RestService.For(new HttpClient(new AuthHeaderHandler())
+ {
+ BaseAddress = new Uri("http://localhost:5256/"),
+ Timeout = TimeSpan.FromSeconds(60)
+ });
+ }
+
+ public override void OnFrameworkInitializationCompleted()
+ {
+
+ if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+ {
+ desktop.MainWindow = new MainWindow
+ {
+ DataContext = new MainWindowViewModel(),
+ };
+ }
+
+ base.OnFrameworkInitializationCompleted();
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/Assets/avalonia-logo.ico b/Flawless.Client/Assets/avalonia-logo.ico
new file mode 100644
index 0000000..da8d49f
Binary files /dev/null and b/Flawless.Client/Assets/avalonia-logo.ico differ
diff --git a/Flawless.Client/AuthHeaderHandler.cs b/Flawless.Client/AuthHeaderHandler.cs
new file mode 100644
index 0000000..b506429
--- /dev/null
+++ b/Flawless.Client/AuthHeaderHandler.cs
@@ -0,0 +1,52 @@
+using System;
+using System.Linq;
+using System.Net.Http;
+using System.Net.Http.Headers;
+using System.Text.Json;
+using System.Threading;
+using System.Threading.Tasks;
+using Flawless.Client.Remote;
+
+namespace Flawless.Client;
+
+public class AuthHeaderHandler : DelegatingHandler
+{
+ private string? AuthenticationHeader { get; set; }
+
+ protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ var response = await SendCommandAsync(request, cancellationToken);
+ var retryCount = 0;
+
+ while (response.Headers.TryGetValues("Token-Expired", out var expired) && expired.Any(s => s == "true"))
+ {
+ if (retryCount++ > 3)
+ {
+ AuthenticationHeader = null;
+ throw new TimeoutException("Too many retries, login info was cleared");
+ }
+
+ var refreshRequest = new HttpRequestMessage(HttpMethod.Post, "api/auth/refresh");
+ var refresh = await base.SendAsync(refreshRequest, cancellationToken);
+ if (!response.IsSuccessStatusCode) throw new ApplicationException("Login is expired and require login!");
+
+ await using var st = await refresh.Content.ReadAsStreamAsync(cancellationToken);
+ var tk = await JsonSerializer.DeserializeAsync(st, cancellationToken: cancellationToken)
+ ?? throw new ApplicationException("Not able to refresh token, please login again!");
+
+ AuthenticationHeader = tk.Token;
+ response = await SendCommandAsync(request, cancellationToken);
+ }
+
+ return response;
+ }
+
+ private Task SendCommandAsync(HttpRequestMessage request, CancellationToken cancellationToken)
+ {
+ // Prefill this header
+ if (AuthenticationHeader != null)
+ request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", AuthenticationHeader);
+
+ return base.SendAsync(request, cancellationToken);
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/Flawless.Client.csproj b/Flawless.Client/Flawless.Client.csproj
new file mode 100644
index 0000000..3609a1b
--- /dev/null
+++ b/Flawless.Client/Flawless.Client.csproj
@@ -0,0 +1,36 @@
+
+
+ WinExe
+ net9.0
+ enable
+ true
+ app.manifest
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ None
+ All
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flawless.Client/Program.cs b/Flawless.Client/Program.cs
new file mode 100644
index 0000000..af0733a
--- /dev/null
+++ b/Flawless.Client/Program.cs
@@ -0,0 +1,23 @@
+using Avalonia;
+using Avalonia.ReactiveUI;
+using System;
+
+namespace Flawless.Client;
+
+sealed class Program
+{
+ // Initialization code. Don't use any Avalonia, third-party APIs or any
+ // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+ // yet and stuff might break.
+ [STAThread]
+ public static void Main(string[] args) => BuildAvaloniaApp()
+ .StartWithClassicDesktopLifetime(args);
+
+ // Avalonia configuration, don't remove; also used by visual designer.
+ public static AppBuilder BuildAvaloniaApp()
+ => AppBuilder.Configure()
+ .UsePlatformDetect()
+ .WithInterFont()
+ .LogToTrace()
+ .UseReactiveUI();
+}
\ No newline at end of file
diff --git a/Flawless.Client/Service/Remote_Generated.cs b/Flawless.Client/Service/Remote_Generated.cs
new file mode 100644
index 0000000..67d5579
--- /dev/null
+++ b/Flawless.Client/Service/Remote_Generated.cs
@@ -0,0 +1,458 @@
+//
+// This code was generated by Refitter.
+//
+
+
+using Refit;
+using System.Collections.Generic;
+using System.Text.Json.Serialization;
+using System.Threading;
+using System.Threading.Tasks;
+
+#nullable enable annotations
+
+namespace Flawless.Client.Remote
+{
+ [System.CodeDom.Compiler.GeneratedCode("Refitter", "1.5.2.0")]
+ public partial interface IFlawlessServer
+ {
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/")]
+ Task Index(CancellationToken cancellationToken = default);
+
+ /// 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);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/api/repo/{userName}/{repositoryName}/get_info")]
+ Task GetInfo(string repositoryName, string userName, CancellationToken cancellationToken = default);
+
+ /// 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);
+
+ /// 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);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/api/repo/{userName}/{repositoryName}/get_users")]
+ Task GetUsers(string repositoryName, string userName, [Body] QueryPagesRequest body, CancellationToken cancellationToken = default);
+
+ /// 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);
+
+ /// 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);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/api/repo/{userName}/{repositoryName}/fetch_manifest")]
+ Task FetchManifest(string userName, string repositoryName, [Query] string commitId, CancellationToken cancellationToken = default);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/api/repo/{userName}/{repositoryName}/fetch_depot")]
+ Task FetchDepot(string userName, string repositoryName, [Query] string depotId, CancellationToken cancellationToken = default);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/api/repo/{userName}/{repositoryName}/list_commit")]
+ Task ListCommit(string userName, string repositoryName, CancellationToken cancellationToken = default);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/api/repo/{userName}/{repositoryName}/list_locked_files")]
+ Task ListLockedFiles(string userName, string repositoryName, CancellationToken cancellationToken = default);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/api/repo/{userName}/{repositoryName}/peek_commit")]
+ Task PeekCommit(string userName, string repositoryName, CancellationToken cancellationToken = default);
+
+ /// 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);
+
+ /// 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);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Multipart]
+ [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);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Get("/api/repo_list")]
+ Task RepoList([Query, AliasAs("Offset")] int offset, [Query, AliasAs("Length")] int length, CancellationToken cancellationToken = default);
+
+ /// A that completes when the request is finished.
+ /// Thrown when the request returns a non-success status code.
+ [Post("/api/repo_create")]
+ Task RepoCreate([Query] string repositoryName, CancellationToken cancellationToken = default);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+ /// 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);
+
+
+ }
+
+}
+
+//----------------------
+//
+// Generated using the NSwag toolchain v14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0)) (http://NSwag.org)
+//
+//----------------------
+
+#pragma warning disable 108 // Disable "CS0108 '{derivedDto}.ToJson()' hides inherited member '{dtoBase}.ToJson()'. Use the new keyword if hiding was intended."
+#pragma warning disable 114 // Disable "CS0114 '{derivedDto}.RaisePropertyChanged(String)' hides inherited member 'dtoBase.RaisePropertyChanged(String)'. To make the current member override that implementation, add the override keyword. Otherwise add the new keyword."
+#pragma warning disable 472 // Disable "CS0472 The result of the expression is always 'false' since a value of type 'Int32' is never equal to 'null' of type 'Int32?'
+#pragma warning disable 612 // Disable "CS0612 '...' is obsolete"
+#pragma warning disable 649 // Disable "CS0649 Field is never assigned to, and will always have its default value null"
+#pragma warning disable 1573 // Disable "CS1573 Parameter '...' has no matching param tag in the XML comment for ...
+#pragma warning disable 1591 // Disable "CS1591 Missing XML comment for publicly visible type or member ..."
+#pragma warning disable 8073 // Disable "CS8073 The result of the expression is always 'false' since a value of type 'T' is never equal to 'null' of type 'T?'"
+#pragma warning disable 3016 // Disable "CS3016 Arrays as attribute arguments is not CLS-compliant"
+#pragma warning disable 8603 // Disable "CS8603 Possible null reference return"
+#pragma warning disable 8604 // Disable "CS8604 Possible null reference argument for parameter"
+#pragma warning disable 8625 // Disable "CS8625 Cannot convert null literal to non-nullable reference type"
+#pragma warning disable 8765 // Disable "CS8765 Nullability of type of parameter doesn't match overridden member (possibly because of nullability attributes)."
+
+namespace Flawless.Client.Remote
+{
+ using System = global::System;
+
+
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
+ public partial class LoginRequest
+ {
+
+ [JsonPropertyName("username")]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string Username { get; set; }
+
+ [JsonPropertyName("password")]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string Password { 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 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
+ {
+
+ [JsonPropertyName("email")]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string Email { get; set; }
+
+ [JsonPropertyName("username")]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string Username { get; set; }
+
+ [JsonPropertyName("password")]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string Password { 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 RepoUserRole
+ {
+
+ [JsonPropertyName("username")]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string Username { 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 enum RepositoryRole
+ {
+
+ _0 = 0,
+
+ _1 = 1,
+
+ _2 = 2,
+
+ _3 = 3,
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
+ public partial class ResetPasswordRequest
+ {
+
+ [JsonPropertyName("identity")]
+ public string Identity { get; set; }
+
+ [JsonPropertyName("oldPassword")]
+ public string OldPassword { get; set; }
+
+ [JsonPropertyName("newPassword")]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string NewPassword { 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 TokenInfo
+ {
+
+ [JsonPropertyName("token")]
+ [System.ComponentModel.DataAnnotations.Required(AllowEmptyStrings = true)]
+ public string Token { 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 UserContactModifyResponse
+ {
+
+ [JsonPropertyName("email")]
+ public string Email { get; set; }
+
+ [JsonPropertyName("phone")]
+ public string Phone { 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 UserInfoModifyResponse
+ {
+
+ [JsonPropertyName("nickName")]
+ public string NickName { get; set; }
+
+ [JsonPropertyName("gender")]
+ public UserSex Gender { get; set; }
+
+ [JsonPropertyName("bio")]
+ public string Bio { get; set; }
+
+ [JsonPropertyName("publicEmail")]
+ public bool? PublicEmail { 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 UserInfoResponse
+ {
+
+ [JsonPropertyName("authorized")]
+ public bool Authorized { get; set; }
+
+ [JsonPropertyName("username")]
+ public string Username { get; set; }
+
+ [JsonPropertyName("nickName")]
+ public string NickName { get; set; }
+
+ [JsonPropertyName("gender")]
+ public UserSex Gender { get; set; }
+
+ [JsonPropertyName("bio")]
+ public string Bio { get; set; }
+
+ [JsonPropertyName("email")]
+ public string Email { get; set; }
+
+ [JsonPropertyName("phone")]
+ public string Phone { get; set; }
+
+ [JsonPropertyName("publicEmail")]
+ public bool? PublicEmail { get; set; }
+
+ [JsonPropertyName("createdAt")]
+ public System.DateTimeOffset? CreatedAt { 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 UserInfoResponsePagedResponse
+ {
+
+ [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; }
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
+ public enum UserSex
+ {
+
+ _0 = 0,
+
+ _1 = 1,
+
+ _2 = 2,
+
+ _3 = 3,
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NJsonSchema", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
+ public partial class WorkspaceFile
+ {
+
+ }
+
+ [System.CodeDom.Compiler.GeneratedCode("NSwag", "14.2.0.0 (NJsonSchema v11.1.0.0 (Newtonsoft.Json v13.0.0.0))")]
+ public partial class FileParameter
+ {
+ public FileParameter(System.IO.Stream data)
+ : this (data, null, null)
+ {
+ }
+
+ public FileParameter(System.IO.Stream data, string fileName)
+ : this (data, fileName, null)
+ {
+ }
+
+ public FileParameter(System.IO.Stream data, string fileName, string contentType)
+ {
+ Data = data;
+ FileName = fileName;
+ ContentType = contentType;
+ }
+
+ public System.IO.Stream Data { get; private set; }
+
+ public string FileName { get; private set; }
+
+ public string ContentType { get; private set; }
+ }
+
+
+}
+
+#pragma warning restore 108
+#pragma warning restore 114
+#pragma warning restore 472
+#pragma warning restore 612
+#pragma warning restore 1573
+#pragma warning restore 1591
+#pragma warning restore 8073
+#pragma warning restore 3016
+#pragma warning restore 8603
+#pragma warning restore 8604
+#pragma warning restore 8625
\ No newline at end of file
diff --git a/Flawless.Client/ViewLocator.cs b/Flawless.Client/ViewLocator.cs
new file mode 100644
index 0000000..39a0d91
--- /dev/null
+++ b/Flawless.Client/ViewLocator.cs
@@ -0,0 +1,30 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Controls.Templates;
+using Flawless.Client.ViewModels;
+
+namespace Flawless.Client;
+
+public class ViewLocator : IDataTemplate
+{
+ public Control? Build(object? param)
+ {
+ if (param is null)
+ return null;
+
+ var name = param.GetType().FullName!.Replace("ViewModel", "View", StringComparison.Ordinal);
+ var type = Type.GetType(name);
+
+ if (type != null)
+ {
+ return (Control)Activator.CreateInstance(type)!;
+ }
+
+ return new TextBlock { Text = "Not Found: " + name };
+ }
+
+ public bool Match(object? data)
+ {
+ return data is ViewModelBase;
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/ViewModels/MainWindowViewModel.cs b/Flawless.Client/ViewModels/MainWindowViewModel.cs
new file mode 100644
index 0000000..a8791f5
--- /dev/null
+++ b/Flawless.Client/ViewModels/MainWindowViewModel.cs
@@ -0,0 +1,6 @@
+namespace Flawless.Client.ViewModels;
+
+public class MainWindowViewModel : ViewModelBase
+{
+ public string Greeting { get; } = "Welcome to Avalonia!";
+}
\ No newline at end of file
diff --git a/Flawless.Client/ViewModels/ViewModelBase.cs b/Flawless.Client/ViewModels/ViewModelBase.cs
new file mode 100644
index 0000000..1d53eb3
--- /dev/null
+++ b/Flawless.Client/ViewModels/ViewModelBase.cs
@@ -0,0 +1,7 @@
+using ReactiveUI;
+
+namespace Flawless.Client.ViewModels;
+
+public class ViewModelBase : ReactiveObject
+{
+}
\ No newline at end of file
diff --git a/Flawless.Client/Views/MainWindow.axaml b/Flawless.Client/Views/MainWindow.axaml
new file mode 100644
index 0000000..af84898
--- /dev/null
+++ b/Flawless.Client/Views/MainWindow.axaml
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/Flawless.Client/Views/MainWindow.axaml.cs b/Flawless.Client/Views/MainWindow.axaml.cs
new file mode 100644
index 0000000..4ff525d
--- /dev/null
+++ b/Flawless.Client/Views/MainWindow.axaml.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Threading;
+using Avalonia.Controls;
+using Flawless.Client.Remote;
+
+namespace Flawless.Client.Views;
+
+public partial class MainWindow : Window
+{
+ public MainWindow()
+ {
+ InitializeComponent();
+ Test();
+ }
+
+ private async void Test()
+ {
+ var result = await (App.Current as App).ApiGateway.Login(new LoginRequest
+ {
+ Username = "cardidi",
+ Password = "8888"
+ }, CancellationToken.None);
+
+ Console.WriteLine(result);
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Client/app.manifest b/Flawless.Client/app.manifest
new file mode 100644
index 0000000..f38423a
--- /dev/null
+++ b/Flawless.Client/app.manifest
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/Flawless.Server/Controllers/AuthenticationController.cs b/Flawless.Server/Controllers/AuthenticationController.cs
index cd2c93c..1312e20 100644
--- a/Flawless.Server/Controllers/AuthenticationController.cs
+++ b/Flawless.Server/Controllers/AuthenticationController.cs
@@ -50,7 +50,7 @@ public class AuthenticationController(
}
[HttpPost("login")]
- public async Task LoginAsync(LoginRequest r)
+ public async Task> LoginAsync(LoginRequest r)
{
var user = await userManager.FindByNameAsync(r.Username);
if (user == null) return BadRequest(new FailedResponse("Invalid username or password."));
diff --git a/Flawless.Server/Controllers/RepositoryControl.cs b/Flawless.Server/Controllers/RepositoryInnieController.cs
similarity index 67%
rename from Flawless.Server/Controllers/RepositoryControl.cs
rename to Flawless.Server/Controllers/RepositoryInnieController.cs
index dce3c88..2abad88 100644
--- a/Flawless.Server/Controllers/RepositoryControl.cs
+++ b/Flawless.Server/Controllers/RepositoryInnieController.cs
@@ -17,7 +17,7 @@ using Microsoft.EntityFrameworkCore;
namespace Flawless.Server.Controllers;
[ApiController, Authorize, Route("api/repo/{userName}/{repositoryName}")]
-public class RepositoryControl(
+public class RepositoryInnieController(
UserManager userManager,
AppDbContext dbContext,
PathTransformer transformer,
@@ -30,6 +30,178 @@ public class RepositoryControl(
}
+ #region Unresoted
+
+ [HttpPost("delete_repo")]
+ public async Task DeleteRepositoryAsync(string repositoryName)
+ {
+ if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
+ return BadRequest(new FailedResponse("Repository name is empty or too short!"));
+
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ var rp = await dbContext.Repositories.FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
+ if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
+
+ dbContext.Repositories.Remove(rp);
+ await dbContext.SaveChangesAsync();
+ return Ok();
+ }
+
+ [HttpGet("get_info")]
+ public async Task IsRepositoryArchiveAsync(string repositoryName)
+ {
+ if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
+ return BadRequest(new FailedResponse("Repository name is empty or too short!"));
+
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ var rp = await dbContext.Repositories
+ .Include(repository => repository.Owner)
+ .Include(repository => repository.Commits)
+ .Include(repository => repository.Members)
+ .ThenInclude(repositoryMember => repositoryMember.User)
+ .FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
+
+ if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
+
+ return Ok(new RepositoryInfoResponse
+ {
+ 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
+ });
+ }
+
+ [HttpPost("archive_repo")]
+ public async Task ArchiveRepositoryAsync(string repositoryName)
+ {
+ if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
+ return BadRequest(new FailedResponse("Repository name is empty or too short!"));
+
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ var rp = await dbContext.Repositories.FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
+ if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
+ if (rp.IsArchived) return BadRequest(new FailedResponse("Repository is archived!"));
+
+ rp.IsArchived = true;
+ await dbContext.SaveChangesAsync();
+ return Ok();
+ }
+
+
+ [HttpPost("unarchive_repo")]
+ public async Task UnarchiveRepositoryAsync(string repositoryName)
+ {
+ if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
+ return BadRequest(new FailedResponse("Repository name is empty or too short!"));
+
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ var rp = await dbContext.Repositories.FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
+ if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
+ if (!rp.IsArchived) return BadRequest(new FailedResponse("Repository is not archived!"));
+
+ rp.IsArchived = false;
+ await dbContext.SaveChangesAsync();
+ return Ok();
+ }
+
+ [HttpGet("get_users")]
+ public async Task GetUsersAsync(string repositoryName, QueryPagesRequest r)
+ {
+ if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
+ return BadRequest(new FailedResponse("Repository name is empty or too short!"));
+
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ var rp = await dbContext.Repositories
+ .Include(repository => repository.Owner)
+ .Include(repository => repository.Members)
+ .ThenInclude(repositoryMember => repositoryMember.User)
+ .FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
+
+ if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
+
+ return Ok(new PagedResponse
+ {
+ Length = r.Length,
+ Offset = r.Offset,
+ Total = rp.Members.Count,
+ Data = rp.Members.Select(pm => new RepoUserRole
+ {
+ Username = pm.User.UserName!,
+ Role = pm.Role
+ })
+ });
+ }
+
+ [HttpPost("update_user")]
+ public async Task UpdateUserAsync(string repositoryName, [FromBody] RepoUserRole r)
+ {
+ if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
+ return BadRequest(new FailedResponse("Repository name is empty or too short!"));
+
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ var tu = await userManager.FindByNameAsync(r.Username);
+ if (tu == null) return BadRequest(new FailedResponse("User not found!"));
+ if (u == tu) return BadRequest(new FailedResponse("Not able to update the role on self-own repository!"));
+
+ var rp = await dbContext.Repositories
+ .Include(repository => repository.Members)
+ .ThenInclude(repositoryMember => repositoryMember.User)
+ .FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
+
+ if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
+
+ var m = rp.Members.FirstOrDefault(m => m.User == tu);
+ if (m == null)
+ {
+ m = new RepositoryMember
+ {
+ User = tu,
+ Role = r.Role ?? RepositoryRole.Guest
+ };
+
+ rp.Members.Add(m);
+ }
+ else
+ {
+ m.Role = r.Role ?? RepositoryRole.Guest;
+ }
+
+ await dbContext.SaveChangesAsync();
+ return Ok();
+ }
+
+ [HttpPost("delete_user")]
+ public async Task DeleteUserAsync(string repositoryName, [FromBody] RepoUserRole r)
+ {
+ if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
+ return BadRequest(new FailedResponse("Repository name is empty or too short!"));
+
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ var tu = await userManager.FindByNameAsync(r.Username);
+ if (tu == null) return BadRequest(new FailedResponse("User not found!"));
+ if (u == tu) return BadRequest(new FailedResponse("Not able to update the role on self-own repository!"));
+
+ var rp = await dbContext.Repositories
+ .Include(repository => repository.Members)
+ .ThenInclude(repositoryMember => repositoryMember.User)
+ .FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
+
+ if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
+
+ var m = rp.Members.FirstOrDefault(m => m.User == tu);
+ if (m == null) return BadRequest(new FailedResponse("User is not being granted to this repository!"));
+
+ rp.Members.Remove(m);
+ await dbContext.SaveChangesAsync();
+ return Ok();
+ }
+
+ #endregion
+
+
private bool UserNotGranted(out IActionResult? rsp, AppUser user, Repository repo, RepositoryRole minRole)
{
if (repo.Owner == user || repo.Members.Any(m => m.User == user && m.Role >= minRole))
@@ -57,8 +229,8 @@ public class RepositoryControl(
}
- [HttpGet("fetch/manifest/{commitId}")]
- public async Task DownloadManifestAsync(string userName, string repositoryName, string commitId)
+ [HttpGet("fetch_manifest")]
+ public async Task DownloadManifestAsync(string userName, string repositoryName, [FromQuery] string commitId)
{
if (!Guid.TryParse(commitId, out var commitGuid)) return BadRequest(new FailedResponse("Invalid commit id"));
var user = (await userManager.GetUserAsync(HttpContext.User))!;
@@ -72,8 +244,8 @@ public class RepositoryControl(
}
- [HttpGet("fetch/depot/{depotId}")]
- public async Task DownloadDepotAsync(string userName, string repositoryName, string depotId)
+ [HttpGet("fetch_depot")]
+ public async Task DownloadDepotAsync(string userName, string repositoryName, [FromQuery] string depotId)
{
if (!Guid.TryParse(depotId, out var depotGuid)) return BadRequest(new FailedResponse("Invalid depot id"));
var user = (await userManager.GetUserAsync(HttpContext.User))!;
@@ -87,7 +259,7 @@ public class RepositoryControl(
}
- [HttpGet("list/commit")]
+ [HttpGet("list_commit")]
public async Task ListCommitsAsync(string userName, string repositoryName)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
@@ -107,7 +279,7 @@ public class RepositoryControl(
});
}
- [HttpGet("list/locked")]
+ [HttpGet("list_locked_files")]
public async Task ListLocksAsync(string userName, string repositoryName)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
@@ -126,7 +298,7 @@ public class RepositoryControl(
});
}
- [HttpGet("peek/commit")]
+ [HttpGet("peek_commit")]
public async Task PeekCommitAsync(string userName, string repositoryName)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
@@ -147,7 +319,7 @@ public class RepositoryControl(
}
- [HttpGet("lock")]
+ [HttpPost("lock_file")]
public async Task LockAsync(string userName, string repositoryName, string path)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
@@ -184,7 +356,7 @@ public class RepositoryControl(
return BadRequest("Unknown error");
}
- [HttpGet("unlock")]
+ [HttpPost("unlock_file")]
public async Task UnockAsync(string userName, string repositoryName, string path)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
@@ -221,7 +393,7 @@ public class RepositoryControl(
return BadRequest("Unknown error");
}
- [HttpPost("commit")]
+ [HttpPost("create_commit")]
public async Task CommitAsync(string userName, string repositoryName, [FromForm] FormCommitRequest req)
{
var user = (await userManager.GetUserAsync(HttpContext.User))!;
diff --git a/Flawless.Server/Controllers/RepositoryManageController.cs b/Flawless.Server/Controllers/RepositoryManageController.cs
deleted file mode 100644
index 641f6f9..0000000
--- a/Flawless.Server/Controllers/RepositoryManageController.cs
+++ /dev/null
@@ -1,232 +0,0 @@
-using Flawless.Communication.Request;
-using Flawless.Communication.Response;
-using Flawless.Communication.Shared;
-using Flawless.Server.Models;
-using Flawless.Server.Services;
-using Microsoft.AspNetCore.Authorization;
-using Microsoft.AspNetCore.Identity;
-using Microsoft.AspNetCore.Mvc;
-using Microsoft.EntityFrameworkCore;
-
-namespace Flawless.Server.Controllers;
-
-[ApiController, Authorize, Route("api/repo_manage")]
-public class RepositoryManageController(AppDbContext dbContext, UserManager userManager) : ControllerBase
-{
- [HttpGet("list")]
- public async Task ListAllAvailableRepositoriesAsync(QueryPagesRequest r)
- {
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- var query = await 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();
-
- return Ok(new PagedResponse
- {
- 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
- }),
- });
- }
-
- [HttpPost("create/{repositoryName}")]
- public async Task CreateRepositoryAsync(string repositoryName)
- {
- if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
- return BadRequest(new FailedResponse("Repository name is empty or too short!"));
-
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- 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()
- {
- Name = repositoryName,
- Owner = u,
- });
-
- await dbContext.SaveChangesAsync();
- return Ok();
- }
-
- [HttpPost("delete/{repositoryName}")]
- public async Task DeleteRepositoryAsync(string repositoryName)
- {
- if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
- return BadRequest(new FailedResponse("Repository name is empty or too short!"));
-
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- var rp = await dbContext.Repositories.FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
- if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
-
- dbContext.Repositories.Remove(rp);
- await dbContext.SaveChangesAsync();
- return Ok();
- }
-
- [HttpGet("info/{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!"));
-
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- var rp = await dbContext.Repositories
- .Include(repository => repository.Owner)
- .Include(repository => repository.Commits)
- .Include(repository => repository.Members)
- .ThenInclude(repositoryMember => repositoryMember.User)
- .FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
-
- if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
-
- return Ok(new RepositoryInfoResponse
- {
- 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
- });
- }
-
- [HttpPost("archive/{repositoryName}")]
- public async Task ArchiveRepositoryAsync(string repositoryName)
- {
- if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
- return BadRequest(new FailedResponse("Repository name is empty or too short!"));
-
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- var rp = await dbContext.Repositories.FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
- if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
- if (rp.IsArchived) return BadRequest(new FailedResponse("Repository is archived!"));
-
- rp.IsArchived = true;
- await dbContext.SaveChangesAsync();
- return Ok();
- }
-
-
- [HttpPost("unarchive/{repositoryName}")]
- public async Task UnarchiveRepositoryAsync(string repositoryName)
- {
- if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
- return BadRequest(new FailedResponse("Repository name is empty or too short!"));
-
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- var rp = await dbContext.Repositories.FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
- if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
- if (!rp.IsArchived) return BadRequest(new FailedResponse("Repository is not archived!"));
-
- rp.IsArchived = false;
- await dbContext.SaveChangesAsync();
- return Ok();
- }
-
- [HttpGet("get_users/{repositoryName}")]
- public async Task GetUsersAsync(string repositoryName, QueryPagesRequest r)
- {
- if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
- return BadRequest(new FailedResponse("Repository name is empty or too short!"));
-
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- var rp = await dbContext.Repositories
- .Include(repository => repository.Owner)
- .Include(repository => repository.Members)
- .ThenInclude(repositoryMember => repositoryMember.User)
- .FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
-
- if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
-
- return Ok(new PagedResponse
- {
- Length = r.Length,
- Offset = r.Offset,
- Total = rp.Members.Count,
- Data = rp.Members.Select(pm => new RepoUserRole
- {
- Username = pm.User.UserName!,
- Role = pm.Role
- })
- });
- }
-
- [HttpPost("update_user/{repositoryName}")]
- public async Task UpdateUserAsync(string repositoryName, [FromBody] RepoUserRole r)
- {
- if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
- return BadRequest(new FailedResponse("Repository name is empty or too short!"));
-
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- var tu = await userManager.FindByNameAsync(r.Username);
- if (tu == null) return BadRequest(new FailedResponse("User not found!"));
- if (u == tu) return BadRequest(new FailedResponse("Not able to update the role on self-own repository!"));
-
- var rp = await dbContext.Repositories
- .Include(repository => repository.Members)
- .ThenInclude(repositoryMember => repositoryMember.User)
- .FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
-
- if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
-
- var m = rp.Members.FirstOrDefault(m => m.User == tu);
- if (m == null)
- {
- m = new RepositoryMember
- {
- User = tu,
- Role = r.Role ?? RepositoryRole.Guest
- };
-
- rp.Members.Add(m);
- }
- else
- {
- m.Role = r.Role ?? RepositoryRole.Guest;
- }
-
- await dbContext.SaveChangesAsync();
- return Ok();
- }
-
- [HttpPost("delete_user/{repositoryName}")]
- public async Task DeleteUserAsync(string repositoryName, [FromBody] RepoUserRole r)
- {
- if (string.IsNullOrWhiteSpace(repositoryName) || repositoryName.Length <= 3)
- return BadRequest(new FailedResponse("Repository name is empty or too short!"));
-
- var u = (await userManager.GetUserAsync(HttpContext.User))!;
- var tu = await userManager.FindByNameAsync(r.Username);
- if (tu == null) return BadRequest(new FailedResponse("User not found!"));
- if (u == tu) return BadRequest(new FailedResponse("Not able to update the role on self-own repository!"));
-
- var rp = await dbContext.Repositories
- .Include(repository => repository.Members)
- .ThenInclude(repositoryMember => repositoryMember.User)
- .FirstOrDefaultAsync(rp => rp.Name == repositoryName && u == rp.Owner);
-
- if (rp == null) return BadRequest(new FailedResponse("Repository not found!"));
-
- var m = rp.Members.FirstOrDefault(m => m.User == tu);
- if (m == null) return BadRequest(new FailedResponse("User is not being granted to this repository!"));
-
- rp.Members.Remove(m);
- await dbContext.SaveChangesAsync();
- return Ok();
- }
-}
\ No newline at end of file
diff --git a/Flawless.Server/Controllers/RepositoryOutieController.cs b/Flawless.Server/Controllers/RepositoryOutieController.cs
new file mode 100644
index 0000000..eb425ac
--- /dev/null
+++ b/Flawless.Server/Controllers/RepositoryOutieController.cs
@@ -0,0 +1,66 @@
+using Flawless.Communication.Request;
+using Flawless.Communication.Response;
+using Flawless.Communication.Shared;
+using Flawless.Server.Models;
+using Flawless.Server.Services;
+using Microsoft.AspNetCore.Authorization;
+using Microsoft.AspNetCore.Identity;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.EntityFrameworkCore;
+
+namespace Flawless.Server.Controllers;
+
+//todo Merging RepositoryManageController and RepositoryController
+[ApiController, Authorize, Route("api")]
+public class RepositoryOutieController(AppDbContext dbContext, UserManager userManager) : ControllerBase
+{
+ [HttpGet("repo_list")]
+ public async Task ListAllAvailableRepositoriesAsync([FromQuery] QueryPagesRequest r)
+ {
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ var query = await 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();
+
+ return Ok(new PagedResponse
+ {
+ 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
+ }),
+ });
+ }
+
+ [HttpPost("repo_create")]
+ 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!"));
+
+ var u = (await userManager.GetUserAsync(HttpContext.User))!;
+ 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()
+ {
+ Name = repositoryName,
+ Owner = u,
+ });
+
+ await dbContext.SaveChangesAsync();
+ return Ok();
+ }
+}
\ No newline at end of file
diff --git a/Flawless.Server/Controllers/UserController.cs b/Flawless.Server/Controllers/UserController.cs
index f920560..5dc08b2 100644
--- a/Flawless.Server/Controllers/UserController.cs
+++ b/Flawless.Server/Controllers/UserController.cs
@@ -15,7 +15,7 @@ public class UserController(
) : ControllerBase
{
- [HttpPost("update/info")]
+ [HttpPost("update_info")]
public async Task UpdateUserInfoAsync(UserInfoModifyResponse r)
{
bool update = false;
@@ -54,7 +54,7 @@ public class UserController(
return Ok();
}
- [HttpPost("update/email")]
+ [HttpPost("update_email")]
public async Task UpdateEmailAsync(UserContactModifyResponse r)
{
if (string.IsNullOrWhiteSpace(r.Email))
@@ -68,7 +68,7 @@ public class UserController(
}
- [HttpPost("update/phone")]
+ [HttpPost("update_phone")]
public async Task UpdatePhoneAsync(UserContactModifyResponse r)
{
if (string.IsNullOrWhiteSpace(r.Phone))
@@ -81,19 +81,11 @@ public class UserController(
return Ok();
}
- [HttpGet("get")]
- public async Task> GetUserInfoAsync()
- {
- var self = (await userManager.GetUserAsync(HttpContext.User))!;
-
- // Return self as default
- return Ok(GetUserInfoInternal(self, self));
- }
-
- [HttpGet("get/{username}")]
- public async Task> GetUserInfoAsync(string username)
+ [HttpGet("get_info")]
+ public async Task> GetUserInfoAsync([FromQuery] string username)
{
var self = (await userManager.GetUserAsync(HttpContext.User))!;
+ if (string.IsNullOrWhiteSpace(username)) return Ok(GetUserInfoInternal(self, self));
var u = await userManager.FindByNameAsync(username);
if (u == null) return BadRequest(new FailedResponse("User is not existed!"));
@@ -101,8 +93,8 @@ public class UserController(
return Ok(GetUserInfoInternal(u, self));
}
- [HttpGet("query/{keyword}")]
- public async Task>> GetUserInfoAsync(QueryPagesRequest r, string keyword)
+ [HttpGet("query_info")]
+ public async Task>> GetUserInfoAsync(QueryPagesRequest r, [FromQuery] string keyword)
{
var payload = await userManager.Users
.Where(u => u.UserName!.Contains(keyword) || (u.NickName != null && u.NickName.Contains(keyword)))
diff --git a/README.md b/README.md
index 19d91fa..e549707 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,10 @@
# Flawless Version Control
Yet another version control software for programmer, project manager, artist and designer which provides a **FLAWLESS**
-felling on deploy and manage.
\ No newline at end of file
+felling on deploy and manage.
+
+# Create Interfaces
+
+```
+refitter http://localhost:5256/swagger/v1/swagger.json --namespace "Flawless.Client.Remote" --multiple-interfaces ByTag --multiple-files --cancellation-tokens --contracts-namespac "Flawless.Communication" --output ".\Flawless.Client\Service\Remote"
+```
\ No newline at end of file