mirror of
https://github.com/MAKS-IT-COM/maksit-certs-ui.git
synced 2025-12-30 19:50:07 +01:00
(feature): backend controllers review init
This commit is contained in:
parent
5c7bf224c3
commit
7d60b77c62
@ -27,6 +27,11 @@ public class AccountController : ControllerBase {
|
||||
#endregion
|
||||
|
||||
#region Account
|
||||
[HttpGet("account/{accountId:guid}")]
|
||||
public async Task<IActionResult> GetAccount(Guid accountId) {
|
||||
var result = await _accountService.GetAccountAsync(accountId);
|
||||
return result.ToActionResult();
|
||||
}
|
||||
|
||||
[HttpPost("account")]
|
||||
public async Task<IActionResult> PostAccount([FromBody] PostAccountRequest requestData) {
|
||||
|
||||
46
src/LetsEncryptServer/Controllers/CacheController.cs
Normal file
46
src/LetsEncryptServer/Controllers/CacheController.cs
Normal file
@ -0,0 +1,46 @@
|
||||
using MaksIT.LetsEncryptServer.Services;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace LetsEncryptServer.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api")]
|
||||
public class CacheController(ICacheService cacheService) : ControllerBase {
|
||||
private readonly ICacheService _cacheService = cacheService;
|
||||
|
||||
[HttpGet("caches/download")]
|
||||
public async Task<IActionResult> GetCaches() {
|
||||
var result = await _cacheService.DownloadCacheZipAsync();
|
||||
if (!result.IsSuccess || result.Value == null) {
|
||||
return result.ToActionResult();
|
||||
}
|
||||
|
||||
var bytes = result.Value;
|
||||
|
||||
return File(bytes, "application/zip", "caches.zip");
|
||||
}
|
||||
|
||||
[HttpPost("caches/upload")]
|
||||
public async Task<IActionResult> PostCaches([FromBody] byte[] zipBytes) {
|
||||
var result = await _cacheService.UploadCacheZipAsync(zipBytes);
|
||||
return result.ToActionResult();
|
||||
}
|
||||
|
||||
[HttpGet("cache/{accountId:guid}/download")]
|
||||
public async Task<IActionResult> GetCache(Guid accountId) {
|
||||
var result = await _cacheService.DownloadAccountCacheZipAsync(accountId);
|
||||
if (!result.IsSuccess || result.Value == null) {
|
||||
return result.ToActionResult();
|
||||
}
|
||||
|
||||
var bytes = result.Value;
|
||||
|
||||
return File(bytes, "application/zip", $"cache-{accountId}.zip");
|
||||
}
|
||||
|
||||
[HttpPost("cache/{accountId:guid}/upload")]
|
||||
public async Task<IActionResult> PostAccountCache(Guid accountId, [FromBody] byte[] zipBytes) {
|
||||
var result = await _cacheService.UploadAccountCacheZipAsync(accountId, zipBytes);
|
||||
return result.ToActionResult();
|
||||
}
|
||||
}
|
||||
@ -1,11 +1,11 @@
|
||||
using System.Text.Json;
|
||||
|
||||
|
||||
using MaksIT.Core.Extensions;
|
||||
using MaksIT.Core.Extensions;
|
||||
using MaksIT.Core.Threading;
|
||||
using MaksIT.LetsEncrypt.Entities;
|
||||
using MaksIT.Results;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System.IO.Compression;
|
||||
using System.Text.Json;
|
||||
|
||||
namespace MaksIT.LetsEncryptServer.Services;
|
||||
|
||||
@ -14,6 +14,12 @@ public interface ICacheService {
|
||||
Task<Result<RegistrationCache?>> LoadAccountFromCacheAsync(Guid accountId);
|
||||
Task<Result> SaveToCacheAsync(Guid accountId, RegistrationCache cache);
|
||||
Task<Result> DeleteFromCacheAsync(Guid accountId);
|
||||
|
||||
Task<Result<byte[]>> DownloadCacheZipAsync();
|
||||
Task<Result<byte[]?>> DownloadAccountCacheZipAsync(Guid accountId);
|
||||
Task<Result> UploadCacheZipAsync(byte[] zipBytes);
|
||||
Task<Result> UploadAccountCacheZipAsync(Guid accountId, byte[] zipBytes);
|
||||
Task<Result> ClearCacheAsync();
|
||||
}
|
||||
|
||||
public class CacheService : ICacheService, IDisposable {
|
||||
@ -21,6 +27,8 @@ public class CacheService : ICacheService, IDisposable {
|
||||
private readonly string _cacheDirectory;
|
||||
private readonly LockManager _lockManager;
|
||||
|
||||
private readonly string tmpDir = "/tmp";
|
||||
|
||||
public CacheService(
|
||||
ILogger<CacheService> logger,
|
||||
IOptions<Configuration> appsettings
|
||||
@ -112,6 +120,128 @@ public class CacheService : ICacheService, IDisposable {
|
||||
|
||||
#endregion
|
||||
|
||||
|
||||
#region
|
||||
private string GetTempZipPath(string prefix)
|
||||
{
|
||||
var zipName = $"{prefix}_{DateTime.UtcNow:yyyyMMddHHmmss}.zip";
|
||||
return Path.Combine(tmpDir, zipName);
|
||||
}
|
||||
|
||||
private void EnsureTempDirAndDeleteFile(string filePath)
|
||||
{
|
||||
Directory.CreateDirectory(tmpDir);
|
||||
if (File.Exists(filePath))
|
||||
File.Delete(filePath);
|
||||
}
|
||||
|
||||
public async Task<Result<byte[]>> DownloadCacheZipAsync() {
|
||||
try {
|
||||
if (!Directory.Exists(_cacheDirectory)) {
|
||||
var message = "Cache directory not found.";
|
||||
_logger.LogWarning(message);
|
||||
return Result<byte[]>.NotFound(null, message);
|
||||
}
|
||||
|
||||
var zipPath = GetTempZipPath("cache");
|
||||
EnsureTempDirAndDeleteFile(zipPath);
|
||||
ZipFile.CreateFromDirectory(_cacheDirectory, zipPath);
|
||||
var zipBytes = await File.ReadAllBytesAsync(zipPath);
|
||||
File.Delete(zipPath);
|
||||
_logger.LogInformation("Cache zipped to {ZipPath}", zipPath);
|
||||
return Result<byte[]>.Ok(zipBytes);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Error creating or reading cache zip file.";
|
||||
_logger.LogError(ex, message);
|
||||
return Result<byte[]>.InternalServerError(null, [message, .. ex.ExtractMessages()]);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result<byte[]?>> DownloadAccountCacheZipAsync(Guid accountId) {
|
||||
try {
|
||||
var cacheFilePath = GetCacheFilePath(accountId);
|
||||
if (!File.Exists(cacheFilePath)) {
|
||||
var message = $"Cache file not found for account {accountId}.";
|
||||
_logger.LogWarning(message);
|
||||
return Result<byte[]?>.NotFound(null, message);
|
||||
}
|
||||
var zipPath = GetTempZipPath($"account_cache_{accountId}");
|
||||
EnsureTempDirAndDeleteFile(zipPath);
|
||||
using (var zipArchive = ZipFile.Open(zipPath, ZipArchiveMode.Create)) {
|
||||
zipArchive.CreateEntryFromFile(cacheFilePath, Path.GetFileName(cacheFilePath));
|
||||
}
|
||||
var zipBytes = await File.ReadAllBytesAsync(zipPath);
|
||||
File.Delete(zipPath);
|
||||
_logger.LogInformation("Account cache zipped to {ZipPath}", zipPath);
|
||||
return Result<byte[]?>.Ok(zipBytes);
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Error creating or reading account cache zip file.";
|
||||
_logger.LogError(ex, message);
|
||||
return Result<byte[]?>.InternalServerError(null, [message, .. ex.ExtractMessages()]);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> UploadCacheZipAsync(byte[] zipBytes) {
|
||||
try {
|
||||
var zipPath = GetTempZipPath("upload_cache");
|
||||
EnsureTempDirAndDeleteFile(zipPath);
|
||||
await File.WriteAllBytesAsync(zipPath, zipBytes);
|
||||
ZipFile.ExtractToDirectory(zipPath, _cacheDirectory, true);
|
||||
File.Delete(zipPath);
|
||||
_logger.LogInformation("Cache unzipped from {ZipPath}", zipPath);
|
||||
return Result.Ok();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Error uploading or extracting cache zip file.";
|
||||
_logger.LogError(ex, message);
|
||||
return Result.InternalServerError([message, .. ex.ExtractMessages()]);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> UploadAccountCacheZipAsync(Guid accountId, byte[] zipBytes) {
|
||||
try {
|
||||
var zipPath = GetTempZipPath($"upload_account_cache_{accountId}");
|
||||
EnsureTempDirAndDeleteFile(zipPath);
|
||||
await File.WriteAllBytesAsync(zipPath, zipBytes);
|
||||
using (var zipArchive = ZipFile.OpenRead(zipPath)) {
|
||||
foreach (var entry in zipArchive.Entries) {
|
||||
var destinationPath = Path.Combine(_cacheDirectory, entry.FullName);
|
||||
entry.ExtractToFile(destinationPath, true);
|
||||
}
|
||||
}
|
||||
File.Delete(zipPath);
|
||||
_logger.LogInformation("Account cache unzipped from {ZipPath}", zipPath);
|
||||
return Result.Ok();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Error uploading or extracting account cache zip file.";
|
||||
_logger.LogError(ex, message);
|
||||
return Result.InternalServerError([message, .. ex.ExtractMessages()]);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<Result> ClearCacheAsync() {
|
||||
try {
|
||||
if (Directory.Exists(_cacheDirectory)) {
|
||||
Directory.Delete(_cacheDirectory, true);
|
||||
_logger.LogInformation("Cache directory cleared.");
|
||||
}
|
||||
else {
|
||||
_logger.LogWarning("Cache directory not found to clear.");
|
||||
}
|
||||
return Result.Ok();
|
||||
}
|
||||
catch (Exception ex) {
|
||||
var message = "Error clearing cache directory.";
|
||||
_logger.LogError(ex, message);
|
||||
return Result.InternalServerError([message, .. ex.ExtractMessages()]);
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public async Task<Result<RegistrationCache?>> LoadAccountFromCacheAsync(Guid accountId) {
|
||||
return await _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId));
|
||||
}
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests;
|
||||
|
||||
public class PatchAccountRequest {
|
||||
public class PatchAccountRequest : PatchRequestModelBase {
|
||||
|
||||
public PatchAction<string>? Description { get; set; }
|
||||
public string Description { get; set; }
|
||||
|
||||
public PatchAction<bool>? IsDisabled { get; set; }
|
||||
public bool? IsDisabled { get; set; }
|
||||
|
||||
public List<PatchAction<string>>? Contacts { get; set; }
|
||||
public List<string>? Contacts { get; set; }
|
||||
|
||||
public List<PatchHostnameRequest>? Hostnames { get; set; }
|
||||
}
|
||||
|
||||
@ -1,9 +1,10 @@
|
||||
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests;
|
||||
public class PatchHostnameRequest {
|
||||
public PatchAction<string>? Hostname { get; set; }
|
||||
public class PatchHostnameRequest : PatchRequestModelBase {
|
||||
public string? Hostname { get; set; }
|
||||
|
||||
public PatchAction<bool>? IsDisabled { get; set; }
|
||||
public bool? IsDisabled { get; set; }
|
||||
}
|
||||
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.Account.Requests;
|
||||
public class PostAccountRequest : IValidatableObject {
|
||||
public class PostAccountRequest : RequestModelBase {
|
||||
public required string Description { get; set; }
|
||||
public required string[] Contacts { get; set; }
|
||||
public required string ChallengeType { get; set; }
|
||||
public required string[] Hostnames { get; set; }
|
||||
public required bool IsStaging { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (string.IsNullOrWhiteSpace(Description))
|
||||
yield return new ValidationResult("Description is required", new[] { nameof(Description) });
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.Account.Responses {
|
||||
public class GetAccountResponse {
|
||||
public class GetAccountResponse : ResponseModelBase {
|
||||
public Guid AccountId { get; set; }
|
||||
public required bool IsDisabled { get; set; }
|
||||
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.Account.Responses {
|
||||
public class GetHostnameResponse {
|
||||
public class GetHostnameResponse : ResponseModelBase {
|
||||
public required string Hostname { get; set; }
|
||||
public DateTime Expires { get; set; }
|
||||
public bool IsUpcomingExpire { get; set; }
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
using System;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests {
|
||||
public class ConfigureClientRequest {
|
||||
public class ConfigureClientRequest : RequestModelBase {
|
||||
public bool IsStaging { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests
|
||||
{
|
||||
public class GetCertificatesRequest : IValidatableObject {
|
||||
public class GetCertificatesRequest : RequestModelBase {
|
||||
public required string[] Hostnames { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (Hostnames == null || Hostnames.Length == 0)
|
||||
yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) });
|
||||
}
|
||||
|
||||
@ -1,11 +1,12 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests
|
||||
{
|
||||
public class GetOrderRequest : IValidatableObject {
|
||||
public class GetOrderRequest : RequestModelBase {
|
||||
public required string[] Hostnames { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (Hostnames == null || Hostnames.Length == 0)
|
||||
yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) });
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
@ -6,11 +7,11 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests {
|
||||
public class InitRequest: IValidatableObject {
|
||||
public class InitRequest : RequestModelBase {
|
||||
public required string Description { get; set; }
|
||||
public required string[] Contacts { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (string.IsNullOrWhiteSpace(Description))
|
||||
yield return new ValidationResult("Description is required", new[] { nameof(Description) });
|
||||
|
||||
|
||||
@ -1,13 +1,14 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests
|
||||
{
|
||||
public class NewOrderRequest : IValidatableObject {
|
||||
public class NewOrderRequest : RequestModelBase {
|
||||
public required string[] Hostnames { get; set; }
|
||||
|
||||
public required string ChallengeType { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (Hostnames == null || Hostnames.Length == 0)
|
||||
yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) });
|
||||
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using MaksIT.Core.Abstractions.Webapi;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.Linq;
|
||||
@ -6,11 +7,11 @@ using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.Models.LetsEncryptServer.CertsFlow.Requests {
|
||||
public class RevokeCertificatesRequest : IValidatableObject {
|
||||
public class RevokeCertificatesRequest : RequestModelBase {
|
||||
|
||||
public required string [] Hostnames { get; set; }
|
||||
|
||||
public IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
public override IEnumerable<ValidationResult> Validate(ValidationContext validationContext) {
|
||||
if (Hostnames == null || Hostnames.Length == 0)
|
||||
yield return new ValidationResult("Hostnames is required", new[] { nameof(Hostnames) });
|
||||
}
|
||||
|
||||
@ -10,4 +10,8 @@
|
||||
<Folder Include="LetsEncryptServer\CertsFlow\Responses\" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="MaksIT.Core" Version="1.5.1" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.Models {
|
||||
public class PatchAction<T> {
|
||||
public PatchOperation Op { get; set; } // Enum for operation type
|
||||
public int? Index { get; set; } // Index for the operation (for arrays/lists)
|
||||
public T? Value { get; set; } // Value for the operation
|
||||
}
|
||||
}
|
||||
@ -1,13 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MaksIT.Models {
|
||||
public enum PatchOperation {
|
||||
Add,
|
||||
Remove,
|
||||
Replace
|
||||
}
|
||||
}
|
||||
@ -27,6 +27,7 @@ services:
|
||||
volumes:
|
||||
- D:\Compose\MaksIT.CertsUI\acme:/acme
|
||||
- D:\Compose\MaksIT.CertsUI\cache:/cache
|
||||
- D:\Compose\MaksIT.CertsUI\tmp:/tmp
|
||||
- D:\Compose\MaksIT.CertsUI\configMap\appsettings.json:/configMap/appsettings.json:ro
|
||||
- D:\Compose\MaksIT.CertsUI\secrets\appsecrets.json:/secrets/appsecrets.json:ro
|
||||
networks:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user