feat: Add basic file transmission via HTTP
This commit is contained in:
parent
111eca1b3c
commit
db0c95b3e8
@ -22,13 +22,13 @@
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUserManager_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fb3abda585dc54c3f81f64fdda8fc5ba72b708_003F2f_003F1d20f21a_003FUserManager_00601_002Ecs_002Fz_003A2_002D1/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AUserManager_00601_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fd56cb0a089b14dab96ad3ee133819f966d938_003F9c_003F183f8355_003FUserManager_00601_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/CodeInspection/ExcludedFiles/FilesAndFoldersToSkip2/=7020124F_002D9FFC_002D4AC3_002D8F3D_002DAAB8E0240759_002Ff_003AValueTuple_00602_002Ecs_002Fl_003A_002E_002E_003F_002E_002E_003FAppData_003FRoaming_003FJetBrains_003FRider2024_002E3_003Fresharper_002Dhost_003FDecompilerCache_003Fdecompiler_003Fa6b7f037ba7b44df80b8d3aa7e58eeb2e8e938_003Fa7_003F76eb4679_003FValueTuple_00602_002Ecs/@EntryIndexedValue">ForceIncluded</s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=b5573ef9_002Db554_002D4a56_002D82c4_002D2531c8feef65/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" IsLocked="True" Name="PathValidationTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>MSTest::5B1CB26D-99F5-491A-B368-7E3552FE67E9::net9.0::Flawless.Abstract.Test.WorkPathTestUnit</TestId>
|
||||
</TestAncestor>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=b5573ef9_002Db554_002D4a56_002D82c4_002D2531c8feef65/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" IsActive="True" IsLocked="True" Name="PathValidationTest" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>MSTest::5B1CB26D-99F5-491A-B368-7E3552FE67E9::net9.0::Flawless.Abstract.Test.WorkPathTestUnit</TestId>
|
||||
</TestAncestor>
|
||||
</SessionState></s:String>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=f3f8a684_002Dc08e_002D489f_002D949c_002D6c38a1ed63b0/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="PathValidationTest #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>MSTest::5B1CB26D-99F5-491A-B368-7E3552FE67E9::net9.0::Flawless.Abstract.Test.WorkPathTestUnit.PathValidationTest</TestId>
|
||||
</TestAncestor>
|
||||
<s:String x:Key="/Default/Environment/UnitTesting/UnitTestSessionStore/Sessions/=f3f8a684_002Dc08e_002D489f_002D949c_002D6c38a1ed63b0/@EntryIndexedValue"><SessionState ContinuousTestingMode="0" Name="PathValidationTest #2" xmlns="urn:schemas-jetbrains-com:jetbrains-ut-session">
|
||||
<TestAncestor>
|
||||
<TestId>MSTest::5B1CB26D-99F5-491A-B368-7E3552FE67E9::net9.0::Flawless.Abstract.Test.WorkPathTestUnit.PathValidationTest</TestId>
|
||||
</TestAncestor>
|
||||
</SessionState></s:String></wpf:ResourceDictionary>
|
||||
@ -1,15 +1,9 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<PropertyGroup>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
<ImplicitUsings>enable</ImplicitUsings>
|
||||
<Nullable>enable</Nullable>
|
||||
<TargetFramework>net9.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<Reference Include="Microsoft.Extensions.Identity.Core">
|
||||
<HintPath>..\..\..\..\..\Program Files\dotnet\shared\Microsoft.AspNetCore.App\9.0.2\Microsoft.Extensions.Identity.Core.dll</HintPath>
|
||||
</Reference>
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
13
Flawless.Communication/Request/CommitRequest.cs
Normal file
13
Flawless.Communication/Request/CommitRequest.cs
Normal file
@ -0,0 +1,13 @@
|
||||
namespace Flawless.Communication.Request;
|
||||
|
||||
public class CommitRequest
|
||||
{
|
||||
public required string Message { get; set; }
|
||||
|
||||
public required string[] WorkspaceSnapshot { get; set; }
|
||||
|
||||
public string[]? RequiredDepots { get; set; }
|
||||
|
||||
public string? MainDepotId { get; set; } // If commit is not modify files, but changes workspace files (Delete) We will not require a main depot id.
|
||||
|
||||
}
|
||||
@ -1,7 +0,0 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
|
||||
namespace Flawless.Communication.Response;
|
||||
|
||||
public class UserLoginHistoryResponse
|
||||
{
|
||||
}
|
||||
@ -1,10 +1,102 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Flawless.Communication.Request;
|
||||
using Flawless.Communication.Response;
|
||||
using Flawless.Communication.Shared;
|
||||
using Flawless.Server.Models;
|
||||
using Flawless.Server.Services;
|
||||
using Flawless.Server.Utility;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
|
||||
namespace Flawless.Server.Controllers;
|
||||
|
||||
[ApiController, Authorize, Route("api/repo/{userName}/{repositoryName}")]
|
||||
public class RepositoryControl : ControllerBase
|
||||
public class RepositoryControl(
|
||||
UserManager<AppUser> userManager,
|
||||
AppDbContext dbContext,
|
||||
PathTransformer transformer) : ControllerBase
|
||||
{
|
||||
|
||||
public class FormCommitRequest : CommitRequest
|
||||
{
|
||||
public IFormFile Depot { get; set; }
|
||||
}
|
||||
|
||||
|
||||
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))
|
||||
{
|
||||
rsp = null;
|
||||
return false;
|
||||
}
|
||||
|
||||
rsp = Unauthorized(new FailedResponse("You are not allowed to do this operation"));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("fetch/manifest/{commitId}")]
|
||||
public async Task<IActionResult> DownloadManifestAsync(string userName, string repositoryName, string commitId)
|
||||
{
|
||||
if (!Guid.TryParse(commitId, out var commitGuid)) return BadRequest(new FailedResponse("Invalid commit id"));
|
||||
var rp = await dbContext.Repositories
|
||||
.Include(r => r.Owner)
|
||||
.Include(r => r.Members)
|
||||
.FirstOrDefaultAsync(r => r.Owner.UserName == userName && r.Name == repositoryName);
|
||||
|
||||
if (rp == null) return NotFound(new FailedResponse($"Could not find repository {repositoryName}"));
|
||||
var u = (await userManager.FindByNameAsync(userName))!;
|
||||
if (UserNotGranted(out var rsp, u, rp, RepositoryRole.Guest)) return rsp!;
|
||||
|
||||
// Start checkout file.
|
||||
var target = transformer.GetCommitManifest(rp.Id, commitGuid);
|
||||
if (System.IO.File.Exists(target)) return File(System.IO.File.OpenRead(target), "application/octet-stream");
|
||||
return NotFound(new FailedResponse($"Could not find commit manifest {target}"));
|
||||
}
|
||||
|
||||
|
||||
[HttpGet("fetch/depot/{depotId}")]
|
||||
public async Task<IActionResult> DownloadDepotAsync(string userName, string repositoryName, string depotId)
|
||||
{
|
||||
if (!Guid.TryParse(depotId, out var depotGuid)) return BadRequest(new FailedResponse("Invalid depot id"));
|
||||
var rp = await dbContext.Repositories
|
||||
.Include(r => r.Owner)
|
||||
.Include(r => r.Members)
|
||||
.FirstOrDefaultAsync(r => r.Owner.UserName == userName && r.Name == repositoryName);
|
||||
|
||||
if (rp == null) return NotFound(new FailedResponse($"Could not find repository {repositoryName}"));
|
||||
var u = (await userManager.FindByNameAsync(userName))!;
|
||||
if (UserNotGranted(out var rsp, u, rp, RepositoryRole.Guest)) return rsp!;
|
||||
|
||||
// Start checkout file.
|
||||
var target = transformer.GetDepotManifest(rp.Id, depotGuid);
|
||||
if (System.IO.File.Exists(target)) return File(System.IO.File.OpenRead(target), "application/octet-stream");
|
||||
return NotFound(new FailedResponse($"Could not find depot {target}"));
|
||||
|
||||
}
|
||||
|
||||
// [HttpPost("commit")]
|
||||
// public async Task<IActionResult> CommitAsync([FromForm] FormCommitRequest request)
|
||||
// {
|
||||
// }
|
||||
//
|
||||
// [HttpGet("list/commit")]
|
||||
// public async Task<IActionResult> ListCommitsAsync(string userName, string repositoryName)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// [HttpGet("query/commit/{keyword}")]
|
||||
// public async Task<IActionResult> QueryCommitsAsync(string userName, string repositoryName, string keyword)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
//
|
||||
// [HttpGet("get/commit/{commitId}")]
|
||||
// public async Task<IActionResult> GetCommitAsync(string userName, string repositoryName, string commitId)
|
||||
// {
|
||||
//
|
||||
// }
|
||||
}
|
||||
@ -1,46 +0,0 @@
|
||||
using Flawless.Server.Models;
|
||||
using Flawless.Server.Services;
|
||||
using Flawless.Server.Utility;
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace Flawless.Server.Controllers;
|
||||
|
||||
[ApiController, Authorize, Route("ws/transfer")]
|
||||
public class WebSocketTransferController(
|
||||
PathTransformer transformer,
|
||||
RepoTransmissionContext transmission,
|
||||
UserManager<AppUser> userManager) : ControllerBase
|
||||
{
|
||||
[Route("download/{taskId}")]
|
||||
public async Task DownloadDepotAsync(string taskId)
|
||||
{
|
||||
var (u, task) = await GetTaskAsync(transmission.DownloadTask, taskId);
|
||||
|
||||
}
|
||||
|
||||
[Route("upload/{taskId}")]
|
||||
public async Task UploadDepotAsync(string taskId)
|
||||
{
|
||||
var (u, task) = await GetTaskAsync(transmission.DownloadTask, taskId);
|
||||
|
||||
}
|
||||
|
||||
private async ValueTask<(AppUser user, RepoDepotTransmissionTask task)> GetTaskAsync
|
||||
(IDictionary<Guid, RepoDepotTransmissionTask> taskList, string taskId)
|
||||
{
|
||||
if (!Guid.TryParse(taskId, out var id))
|
||||
throw new ArgumentException("Not a valid task id!", nameof(taskId));
|
||||
|
||||
if (!taskList.TryGetValue(id, out var task))
|
||||
throw new ArgumentException("Task not found.", nameof(taskId));
|
||||
|
||||
var u = (await userManager.GetUserAsync(HttpContext.User))!;
|
||||
if (task.UserId != u.Id) throw new ArgumentException("Not being authorized!", nameof(taskId));
|
||||
if (DateTime.UtcNow > task.ExpiresOn)
|
||||
throw new ArgumentException("Task is expired!", nameof(taskId));
|
||||
|
||||
return (u, task);
|
||||
}
|
||||
}
|
||||
@ -1,21 +0,0 @@
|
||||
using Flawless.Communication.Response;
|
||||
|
||||
namespace Flawless.Server.Middlewares;
|
||||
|
||||
public class WebSocketHandoffMiddleware(RequestDelegate next)
|
||||
{
|
||||
public async Task InvokeAsync(HttpContext context)
|
||||
{
|
||||
// Is a WebSocket call and no ws presents.
|
||||
if (context.Request.Path.StartsWithSegments("/ws") && !context.WebSockets.IsWebSocketRequest)
|
||||
{
|
||||
context.Response.StatusCode = 400;
|
||||
await context.Response.WriteAsJsonAsync(
|
||||
new FailedResponse("Connection should be a WebSocket!", "NotWebSocketRequest"));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
await next(context);
|
||||
}
|
||||
}
|
||||
@ -1,12 +0,0 @@
|
||||
namespace Flawless.Server.Models;
|
||||
|
||||
public class RepoDepotTransmissionTask
|
||||
{
|
||||
public required Guid UserId { get; set; }
|
||||
|
||||
public required Guid RepositoryId { get; set; }
|
||||
|
||||
public required Guid DepotId { get; set; }
|
||||
|
||||
public required DateTime ExpiresOn { get; set; }
|
||||
}
|
||||
@ -35,7 +35,6 @@ public static class Program
|
||||
{
|
||||
// Api related
|
||||
builder.Services.AddSingleton<PathTransformer>();
|
||||
builder.Services.AddSingleton<RepoTransmissionContext>();
|
||||
builder.Services.AddOpenApi();
|
||||
builder.Services.AddControllers()
|
||||
.AddJsonOptions(opt =>
|
||||
@ -121,7 +120,6 @@ public static class Program
|
||||
KeepAliveInterval = TimeSpan.FromSeconds(60),
|
||||
KeepAliveTimeout = TimeSpan.FromSeconds(300),
|
||||
});
|
||||
app.UseMiddleware<WebSocketHandoffMiddleware>();
|
||||
|
||||
// Configure identity control
|
||||
app.UseAuthentication();
|
||||
|
||||
@ -1,15 +0,0 @@
|
||||
using System.Collections.Concurrent;
|
||||
using Flawless.Server.Models;
|
||||
|
||||
namespace Flawless.Server.Services;
|
||||
|
||||
public class RepoTransmissionContext
|
||||
{
|
||||
private readonly ConcurrentDictionary<Guid, RepoDepotTransmissionTask> _uploadTask = new();
|
||||
|
||||
private readonly ConcurrentDictionary<Guid, RepoDepotTransmissionTask> _downloadTask = new();
|
||||
|
||||
public IDictionary<Guid, RepoDepotTransmissionTask> UploadTask => _uploadTask;
|
||||
|
||||
public IDictionary<Guid, RepoDepotTransmissionTask> DownloadTask => _downloadTask;
|
||||
}
|
||||
@ -21,7 +21,7 @@ public class PathTransformer(IConfiguration config)
|
||||
if (repositoryId == Guid.Empty) throw new ArgumentException(nameof(repositoryId));
|
||||
if (commitId == Guid.Empty) throw new ArgumentException(nameof(commitId));
|
||||
|
||||
return Path.Combine(_rootPath, repositoryId.ToString(), commitId.ToString());
|
||||
return Path.Combine(_rootPath, repositoryId.ToString(), "Manifests", commitId.ToString());
|
||||
}
|
||||
|
||||
public string GetDepotManifest(Guid repositoryId, Guid depotId)
|
||||
@ -29,6 +29,6 @@ public class PathTransformer(IConfiguration config)
|
||||
if (repositoryId == Guid.Empty) throw new ArgumentException(nameof(repositoryId));
|
||||
if (depotId == Guid.Empty) throw new ArgumentException(nameof(depotId));
|
||||
|
||||
return Path.Combine(_rootPath, repositoryId.ToString(), depotId.ToString());
|
||||
return Path.Combine(_rootPath, repositoryId.ToString(), "Depots", depotId.ToString());
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user