1
0

feat: Add basic supports on Repository support.

This commit is contained in:
Ca2didi 2025-03-24 01:56:37 +08:00
parent 271165fb88
commit 111eca1b3c
30 changed files with 503 additions and 136 deletions

View File

@ -1,3 +0,0 @@
namespace Flawless.Communication.Request;
public record LocateUserRequest(string? UserId, string? Username);

View File

@ -7,4 +7,11 @@ public class QueryPagesRequest<T>
public required int Length { get; init; }
public T? Parameter { get; init; }
}
public class QueryPagesRequest
{
public required int Offset { get; init; }
public required int Length { get; init; }
}

View File

@ -6,7 +6,7 @@ public record PagedResponse<T>
public required int Length { get; init; }
public uint? Total { get; init; }
public int? Total { get; init; }
public IEnumerable<T>? Data { get; init; }
}

View File

@ -0,0 +1,18 @@
using Flawless.Communication.Shared;
namespace Flawless.Communication.Response;
public record RepositoryInfoResponse
{
public required string RepositoryName { get; set; }
public required string OwnerUsername { get; set; }
public required Guid LatestCommitId { get; set; }
public string? Description { get; set; }
public required bool IsArchived { get; set; }
public required RepositoryRole Role { get; set; }
}

View File

@ -0,0 +1,8 @@
namespace Flawless.Communication.Shared;
public record RepoUserRole
{
public required string Username { get; set; }
public RepositoryRole? Role { get; set; }
}

View File

@ -0,0 +1,9 @@
namespace Flawless.Communication.Shared;
public enum RepositoryRole
{
Guest = 0,
Reporter = 1,
Developer = 2,
Owner = 3,
}

View File

@ -1,18 +0,0 @@
namespace Flawless.Core.Interface;
/// <summary>
/// Standardized interface to describe a place to store depots and how they connected with each other.
/// </summary>
public interface IReadonlyRepository
{
public bool IsReadonly { get; }
public IEnumerable<IRepositoryCommit> GetCommits();
public IRepositoryCommit? GetCommitById(uint commitId);
public IAsyncEnumerable<IRepositoryCommit> GetCommitsAsync(CancellationToken cancellationToken = default);
public Task<IRepositoryCommit?> GetCommitByIdAsync(uint commitId, CancellationToken cancellationToken = default);
}

View File

@ -1,22 +0,0 @@
using Flawless.Core.Modal;
namespace Flawless.Core.Interface;
public interface IRepositoryCommit
{
public IReadonlyRepository Repository { get; }
public UInt64 CommitId { get; }
public Author Author { get; }
public DateTime CommitTime { get; }
public string Message { get; }
public ulong ManifestId { get; }
public IRepositoryCommit? GetParentCommit();
public IRepositoryCommit? GetChildCommit();
}

View File

@ -4,4 +4,4 @@
/// An author setup to indicate who create a depot or identify a depot author when uploading it.
/// </summary>
[Serializable]
public record struct Author(string Name, string Email);
public record struct Author(string Name, Guid Email);

View File

@ -1,4 +1,4 @@
namespace Flawless.Core.Modal;
[Serializable]
public record struct CommitManifest(ulong ManifestId, DepotLabel Depot, string[] FilePaths);
public record struct CommitManifest(Guid ManifestId, DepotLabel Depot, string[] FilePaths);

View File

@ -3,4 +3,4 @@
namespace Flawless.Core.Modal;
[Serializable]
public record struct DepotLabel(HashId Id, HashId[] Baselines);
public record struct DepotLabel(HashId Id, HashId[] Dependency);

View File

@ -11,11 +11,10 @@ namespace Flawless.Server.Controllers;
public class AdminUserController(
UserManager<AppUser> userManager) : ControllerBase
{
[HttpPost("user/delete")]
public async Task<IActionResult> DeleteUserAsync(LocateUserRequest r)
[HttpPost("user/delete/{username}")]
public async Task<IActionResult> DeleteUserAsync(string username)
{
if (r.UserId == null) return BadRequest(new FailedResponse("User id is not set!"));
var user = await userManager.FindByIdAsync(r.UserId);
var user = await userManager.FindByNameAsync(username);
if (user == null) return BadRequest(new FailedResponse("User does not exist!"));
var result = await userManager.DeleteAsync(user);
@ -24,11 +23,10 @@ public class AdminUserController(
return Ok();
}
[HttpPost("user/enable")]
public async Task<IActionResult> EnableUserAsync(LocateUserRequest r)
[HttpPost("user/enable/{username}")]
public async Task<IActionResult> EnableUserAsync(string username)
{
if (r.UserId == null) return BadRequest(new FailedResponse("User id is not set!"));
var user = await userManager.FindByIdAsync(r.UserId);
var user = await userManager.FindByNameAsync(username);
if (user == null) return BadRequest(new FailedResponse("User does not exist!"));
var result = await userManager.SetLockoutEnabledAsync(user, false);
@ -37,11 +35,10 @@ public class AdminUserController(
return Ok();
}
[HttpPost("user/disable")]
public async Task<IActionResult> DisableUserAsync(LocateUserRequest r)
[HttpPost("user/disable/{username}")]
public async Task<IActionResult> DisableUserAsync(string username)
{
if (r.UserId == null) return BadRequest(new FailedResponse("User id is not set!"));
var user = await userManager.FindByIdAsync(r.UserId);
var user = await userManager.FindByNameAsync(username);
if (user == null) return BadRequest(new FailedResponse("User does not exist!"));
var result = await userManager.SetLockoutEnabledAsync(user, true);

View File

@ -0,0 +1,10 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace Flawless.Server.Controllers;
[ApiController, Authorize, Route("api/repo/{userName}/{repositoryName}")]
public class RepositoryControl : ControllerBase
{
}

View File

@ -1,9 +0,0 @@
using Microsoft.AspNetCore.Mvc;
namespace Flawless.Server.Controllers;
[ApiController, Route("api/repository")]
public class RepositoryController : ControllerBase
{
}

View File

@ -0,0 +1,232 @@
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<AppUser> userManager) : ControllerBase
{
[HttpGet("list")]
public async Task<IActionResult> 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<RepositoryInfoResponse>
{
Length = r.Length,
Offset = r.Offset,
Data = query.Select(rp => new RepositoryInfoResponse
{
RepositoryName = rp.Name,
OwnerUsername = rp.Owner.UserName!,
LatestCommitId = rp.Commits.OrderByDescending(cm => cm.CommittedOn).FirstOrDefault()?.Id ?? Guid.Empty,
Description = rp.Description,
IsArchived = rp.IsArchived,
Role = rp.Members.First(m => m.User == u).Role
}),
});
}
[HttpPost("create/{repositoryName}")]
public async Task<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<IActionResult> 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<RepoUserRole>
{
Length = r.Length,
Offset = r.Offset,
Total = rp.Members.Count,
Data = rp.Members.Select(pm => new RepoUserRole
{
Username = pm.User.UserName!,
Role = pm.Role
})
});
}
[HttpPost("update_user/{repositoryName}")]
public async Task<IActionResult> 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<IActionResult> 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();
}
}

View File

@ -81,38 +81,31 @@ public class UserController(
return Ok();
}
[HttpGet("get/info")]
public async Task<ActionResult<UserInfoResponse>> GetUserInfoAsync(LocateUserRequest r)
[HttpGet("get")]
public async Task<ActionResult<UserInfoResponse>> GetUserInfoAsync()
{
var self = (await userManager.GetUserAsync(HttpContext.User))!;
if (r.UserId != null)
{
var u = await userManager.FindByIdAsync(r.UserId);
if (u == null) return BadRequest(new FailedResponse("User is not existed!"));
return Ok(GetUserInfoInternal(u, self));
}
if (r.Username != null)
{
var u = await userManager.FindByNameAsync(r.Username);
if (u == null) return BadRequest(new FailedResponse("User is not existed!"));
return Ok(GetUserInfoInternal(u, self));
}
// Return self as default
return Ok(GetUserInfoInternal(self, self));
}
[HttpGet("query/info")]
public async Task<ActionResult<PagedResponse<UserInfoResponse>>> GetUserInfoAsync(QueryPagesRequest<LocateUserRequest> r)
[HttpGet("get/{username}")]
public async Task<ActionResult<UserInfoResponse>> GetUserInfoAsync(string username)
{
var self = (await userManager.GetUserAsync(HttpContext.User))!;
var u = await userManager.FindByNameAsync(username);
if (u == null) return BadRequest(new FailedResponse("User is not existed!"));
return Ok(GetUserInfoInternal(u, self));
}
[HttpGet("query/{keyword}")]
public async Task<ActionResult<PagedResponse<UserInfoResponse>>> GetUserInfoAsync(QueryPagesRequest r, string keyword)
{
var queryNamePrefix = r.Parameter?.Username ?? String.Empty;
var queryId = r.Parameter == null ? Guid.Empty : Guid.Parse(r.Parameter.UserId!);
var payload = await userManager.Users
.Where(u => u.UserName!.StartsWith(queryNamePrefix) || u.Id == queryId)
.Where(u => u.UserName!.Contains(keyword) || (u.NickName != null && u.NickName.Contains(keyword)))
.Skip(r.Offset)
.Take(r.Length)
.Select(u => GetUserInfoInternal(u, null))
@ -141,12 +134,15 @@ public class UserController(
var authorized = queryUser.Id == currentUser?.Id;
return new UserInfoResponse
{
Authorized = authorized,
Username = queryUser.UserName,
CreatedAt = queryUser.CreatedOn,
Bio = queryUser.Bio,
Gender = queryUser.Gender,
NickName = queryUser.NickName,
PublicEmail = authorized ? queryUser.PublicEmail : null,
Email = queryUser.PublicEmail || authorized ? queryUser.Email : null,
Phone = authorized ? queryUser.PhoneNumber : null,
};
}
}

View File

@ -1,19 +1,46 @@
using Microsoft.AspNetCore.Mvc;
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]
[Route("ws/transfer")]
public class WebSocketTransferController : ControllerBase
[ApiController, Authorize, Route("ws/transfer")]
public class WebSocketTransferController(
PathTransformer transformer,
RepoTransmissionContext transmission,
UserManager<AppUser> userManager) : ControllerBase
{
[Route("download")]
public async Task DownloadDepotAsync([FromHeader] string resource, [FromHeader] string? validate)
{
}
[Route("upload")]
public async Task UploadDepotAsync([FromHeader] string resource, [FromHeader] string? validate)
[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);
}
}

View File

@ -7,6 +7,7 @@ namespace Flawless.Server.Models;
public class AppUser : IdentityUser<Guid>
{
[Required]
public DateTime CreatedOn { get; set; }
public UserSex Gender { get; set; }
@ -16,8 +17,9 @@ public class AppUser : IdentityUser<Guid>
[MaxLength(200)]
public string? Bio { get; set; }
public bool PublicEmail { get; set; }
[Required]
public bool PublicEmail { get; set; } = true;
public void RenewSecurityStamp()

View File

@ -5,9 +5,12 @@ namespace Flawless.Server.Models;
public class AppUserRefreshKey
{
[Key]
[Required]
public required string RefreshToken { get; set; }
public Guid UserId { get; set; }
[Required]
public required Guid UserId { get; set; }
public DateTime ExpireIn { get; set; }
[Required]
public required DateTime ExpireIn { get; set; }
}

View File

@ -0,0 +1,12 @@
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; }
}

View File

@ -0,0 +1,25 @@
using System.ComponentModel.DataAnnotations;
namespace Flawless.Server.Models;
public class Repository
{
[Key]
[Required]
public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required AppUser Owner { get; set; }
[Required]
public required string Name { get; set; }
public string? Description { get; set; }
[Required]
public bool IsArchived { get; set; } = false;
public List<RepositoryMember> Members { get; set; } = new();
public List<RepositoryCommit> Commits { get; set; } = new();
}

View File

@ -0,0 +1,20 @@
using System.ComponentModel.DataAnnotations;
namespace Flawless.Server.Models;
public class RepositoryCommit
{
[Required]
public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required DateTime CommittedOn { get; set; }
[Required]
public string Message { get; set; } = String.Empty;
[Required]
public required RepositoryDepot MainDepot { get; set; }
public RepositoryCommit? Parent { get; set; }
}

View File

@ -0,0 +1,13 @@
using System.ComponentModel.DataAnnotations;
namespace Flawless.Server.Models;
public class RepositoryDepot
{
[Key]
[Required]
public Guid DepotId { get; set; }
[Required]
public List<RepositoryDepot> Dependencies { get; set; } = new();
}

View File

@ -0,0 +1,17 @@
using System.ComponentModel.DataAnnotations;
using Flawless.Communication.Shared;
namespace Flawless.Server.Models;
public class RepositoryMember
{
[Key]
[Required]
public Guid Id { get; set; } = Guid.NewGuid();
[Required]
public required AppUser User { get; set; }
[Required]
public RepositoryRole Role { get; set; } = RepositoryRole.Guest;
}

View File

@ -5,6 +5,7 @@ using Flawless.Communication.Response;
using Flawless.Server.Middlewares;
using Flawless.Server.Models;
using Flawless.Server.Services;
using Flawless.Server.Utility;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Identity;
@ -33,6 +34,8 @@ public static class Program
private static void ConfigAppService(WebApplicationBuilder builder)
{
// Api related
builder.Services.AddSingleton<PathTransformer>();
builder.Services.AddSingleton<RepoTransmissionContext>();
builder.Services.AddOpenApi();
builder.Services.AddControllers()
.AddJsonOptions(opt =>
@ -50,7 +53,6 @@ public static class Program
private static void ConfigDbContext(WebApplicationBuilder builder)
{
// Data connection related.
builder.Services.AddDbContextFactory<RepositoryContext, RepositoryContextFactory>();
builder.Services.AddDbContext<AppDbContext>(opt =>
{
opt.UseNpgsql(builder.Configuration.GetConnectionString("CoreDb"));

View File

@ -10,4 +10,5 @@ public class AppDbContext(DbContextOptions<AppDbContext> options)
{
public DbSet<AppUserRefreshKey> RefreshTokens { get; set; }
public DbSet<Repository> Repositories { get; set; }
}

View File

@ -0,0 +1,15 @@
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;
}

View File

@ -1,17 +0,0 @@
using Microsoft.EntityFrameworkCore;
namespace Flawless.Server.Services;
public class RepositoryContext : DbContext
{
private readonly string RepositoryPath;
public RepositoryContext(IConfiguration config, DbContextOptions<RepositoryContext> options) : base(options)
{
RepositoryPath = Path.Combine(config["LocalStoragePath"] ?? "./Data", "Repository");
if (!Directory.Exists(RepositoryPath))
Directory.CreateDirectory(RepositoryPath);
}
}

View File

@ -1,12 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Internal;
namespace Flawless.Server.Services;
public class RepositoryContextFactory : DbContextFactory<RepositoryContext>
{
public RepositoryContextFactory(IServiceProvider serviceProvider, DbContextOptions<RepositoryContext> options, IDbContextFactorySource<RepositoryContext> factorySource)
: base(serviceProvider, options, factorySource)
{
}
}

View File

@ -0,0 +1,34 @@
namespace Flawless.Server.Utility;
public class PathTransformer(IConfiguration config)
{
private readonly string _rootPath = config.GetValue<string>("LocalStoragePath") ?? "./Storage";
public string GetLocalStoragePath()
{
return _rootPath;
}
public string GetRepositoryRootPath(Guid repositoryId)
{
if (repositoryId == Guid.Empty) throw new ArgumentException(nameof(repositoryId));
return Path.Combine(_rootPath, repositoryId.ToString());
}
public string GetCommitManifest(Guid repositoryId, Guid commitId)
{
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());
}
public string GetDepotManifest(Guid repositoryId, Guid depotId)
{
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());
}
}