(refactor): move locker to core, cache service and controller improvements

This commit is contained in:
Maksym Sadovnychyy 2024-06-17 11:40:21 +02:00
parent 5c204e2c1d
commit 2dfc7259fb
11 changed files with 347 additions and 268 deletions

55
src/Core/LockManager.cs Normal file
View File

@ -0,0 +1,55 @@
using System;
using System.Threading;
using System.Threading.Tasks;
public class LockManager : IDisposable {
private readonly SemaphoreSlim _semaphore;
public LockManager(int initialCount, int maxCount) {
_semaphore = new SemaphoreSlim(initialCount, maxCount);
}
public async Task<T> ExecuteWithLockAsync<T>(Func<Task<T>> action) {
await _semaphore.WaitAsync();
try {
return await action();
}
finally {
_semaphore.Release();
}
}
public async Task ExecuteWithLockAsync(Func<Task> action) {
await _semaphore.WaitAsync();
try {
await action();
}
finally {
_semaphore.Release();
}
}
public async Task<T> ExecuteWithLockAsync<T>(Func<T> action) {
await _semaphore.WaitAsync();
try {
return await Task.Run(action);
}
finally {
_semaphore.Release();
}
}
public async Task ExecuteWithLockAsync(Action action) {
await _semaphore.WaitAsync();
try {
await Task.Run(action);
}
finally {
_semaphore.Release();
}
}
public void Dispose() {
_semaphore?.Dispose();
}
}

View File

@ -11,6 +11,18 @@ public class CertificateCache {
public byte[]? Private { get; set; }
}
public class CachedHostname {
public string Hostname { get; set; }
public DateTime Expires { get; set; }
public bool IsUpcomingExpire { get; set; }
public CachedHostname(string hostname, DateTime expires, bool isUpcomingExpire) {
Hostname = hostname;
Expires = expires;
IsUpcomingExpire = isUpcomingExpire;
}
}
public class RegistrationCache {
#region Custom Properties
@ -53,6 +65,26 @@ public class RegistrationCache {
return hostsWithUpcomingSslExpiry.ToArray();
}
public CachedHostname[] GetHosts() {
if (CachedCerts == null)
return Array.Empty<CachedHostname>();
var hosts = new List<CachedHostname>();
foreach (var result in CachedCerts) {
var (subject, cachedChert) = result;
if (cachedChert.Cert != null) {
var cert = new X509Certificate2(Encoding.ASCII.GetBytes(cachedChert.Cert));
hosts.Add(new CachedHostname(subject, cert.NotAfter, (cert.NotAfter - DateTime.UtcNow).TotalDays < 30));
}
}
return hosts.ToArray();
}
/// <summary>
/// Returns cached certificate. Certs older than 30 days are not returned
/// </summary>

View File

@ -9,7 +9,7 @@ using MaksIT.Models.LetsEncryptServer.Cache.Requests;
namespace MaksIT.LetsEncryptServer.Controllers;
[ApiController]
[Route("api/[controller]")]
[Route("api/cache")]
public class CacheController : ControllerBase {
private readonly Configuration _appSettings;
private readonly ICacheRestService _cacheService;
@ -28,27 +28,39 @@ public class CacheController : ControllerBase {
return result.ToActionResult();
}
[HttpPut("account/{accountId:guid}")]
public async Task<IActionResult> PutAccount(Guid accountId, [FromBody] PutAccountRequest requestData) {
var result = await _cacheService.PutAccountAsync(accountId, requestData);
return result.ToActionResult();
}
[HttpPatch("account/{accountId:guid}")]
public async Task<IActionResult> PatchAccount(Guid accountId, [FromBody] PatchAccountRequest requestData) {
var result = await _cacheService.PatchAccountAsync(accountId, requestData);
return result.ToActionResult();
}
#region Contacts
[HttpGet("{accountId}/contacts")]
[HttpGet("account/{accountId:guid}/contacts")]
public async Task<IActionResult> GetContacts(Guid accountId) {
var result = await _cacheService.GetContactsAsync(accountId);
return result.ToActionResult();
}
[HttpPut("{accountId}/contacts")]
[HttpPut("account/{accountId:guid}/contacts")]
public async Task<IActionResult> PutContacts(Guid accountId, [FromBody] PutContactsRequest requestData) {
var result = await _cacheService.PutContactsAsync(accountId, requestData);
return result.ToActionResult();
}
[HttpPatch("{accountId}/contacts")]
public async Task<IActionResult> PatchContacts(Guid accountId, [FromBody] PatchContactRequest requestData) {
[HttpPatch("account/{accountId:guid}/contacts")]
public async Task<IActionResult> PatchContacts(Guid accountId, [FromBody] PatchContactsRequest requestData) {
var result = await _cacheService.PatchContactsAsync(accountId, requestData);
return result.ToActionResult();
}
[HttpDelete("{accountId}/contacts/{index}")]
[HttpDelete("account/{accountId:guid}/contacts/{index:int}")]
public async Task<IActionResult> DeleteContact(Guid accountId, int index) {
var result = await _cacheService.DeleteContactAsync(accountId, index);
return result.ToActionResult();
@ -57,7 +69,7 @@ public class CacheController : ControllerBase {
#region Hostnames
[HttpGet("{accountId}/hostnames")]
[HttpGet("account/{accountId:guid}/hostnames")]
public async Task<IActionResult> GetHostnames(Guid accountId) {
var result = await _cacheService.GetHostnames(accountId);
return result.ToActionResult();

View File

@ -1,13 +1,11 @@
using System.Security.Cryptography.X509Certificates;
using System.Text;
using System.Text.Json;
using System.Text.Json;
using DomainResults.Common;
using MaksIT.Core.Extensions;
using MaksIT.LetsEncrypt.Entities;
using MaksIT.Models;
using MaksIT.Models.LetsEncryptServer.Cache.Requests;
using MaksIT.Models.LetsEncryptServer.Cache.Responses;
using Models.LetsEncryptServer.Cache.Responses;
namespace MaksIT.LetsEncryptServer.Services;
@ -19,289 +17,247 @@ public interface ICacheService {
}
public interface ICacheRestService {
Task<(GetAccountsResponse?, IDomainResult)> GetAccountsAsync();
Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync();
Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId);
#region Contacts
Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData);
Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData);
Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId);
Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData);
Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactRequest requestData);
Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData);
Task<IDomainResult> DeleteContactAsync(Guid accountId, int index);
#endregion
#region Hostnames
Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId);
#endregion
}
public class CacheService : ICacheService, ICacheRestService, IDisposable {
private readonly ILogger<CacheService> _logger;
private readonly string _cacheDirectory;
private readonly SemaphoreSlim _cacheLock = new SemaphoreSlim(1, 1);
private readonly LockManager _lockManager;
public CacheService(
ILogger<CacheService> logger
) {
public CacheService(ILogger<CacheService> logger) {
_logger = logger;
_cacheDirectory = Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "cache");
_lockManager = new LockManager(1, 1);
if (!Directory.Exists(_cacheDirectory)) {
Directory.CreateDirectory(_cacheDirectory);
}
}
/// <summary>
/// Generates the cache file path for the given account ID.
/// </summary>
private string GetCacheFilePath(Guid accountId) {
return Path.Combine(_cacheDirectory, $"{accountId}.json");
}
public async Task<(RegistrationCache?, IDomainResult)> LoadFromCacheAsync(Guid accountId) {
var cacheFilePath = GetCacheFilePath(accountId);
#region Cache Operations
await _cacheLock.WaitAsync();
try {
if (!File.Exists(cacheFilePath)) {
var message = $"Cache file not found for account {accountId}";
_logger.LogWarning(message);
return IDomainResult.Failed<RegistrationCache>(message);
}
var json = await File.ReadAllTextAsync(cacheFilePath);
if (string.IsNullOrEmpty(json)) {
var message = $"Cache file is empty for account {accountId}";
_logger.LogWarning(message);
return IDomainResult.Failed<RegistrationCache>(message);
}
var cache = JsonSerializer.Deserialize<RegistrationCache>(json);
return IDomainResult.Success(cache);
}
catch (Exception ex) {
var message = "Error reading cache file for account {accountId}";
_logger.LogError(ex, message);
return IDomainResult.Failed<RegistrationCache?>(message);
}
finally {
_cacheLock.Release();
}
public Task<(RegistrationCache?, IDomainResult)> LoadFromCacheAsync(Guid accountId) {
return _lockManager.ExecuteWithLockAsync(() => LoadFromCacheInternalAsync(accountId));
}
public async Task<IDomainResult> SaveToCacheAsync(Guid accountId, RegistrationCache cache) {
private async Task<(RegistrationCache?, IDomainResult)> LoadFromCacheInternalAsync(Guid accountId) {
var cacheFilePath = GetCacheFilePath(accountId);
await _cacheLock.WaitAsync();
try {
var json = JsonSerializer.Serialize(cache);
await File.WriteAllTextAsync(cacheFilePath, json);
_logger.LogInformation($"Cache file saved for account {accountId}");
return DomainResult.Success();
if (!File.Exists(cacheFilePath)) {
var message = $"Cache file not found for account {accountId}";
_logger.LogWarning(message);
return IDomainResult.Failed<RegistrationCache>(message);
}
catch (Exception ex) {
var message = "Error writing cache file for account {accountId}";
_logger.LogError(ex, message);
return IDomainResult.Failed(message);
}
finally {
_cacheLock.Release();
var json = await File.ReadAllTextAsync(cacheFilePath);
if (string.IsNullOrEmpty(json)) {
var message = $"Cache file is empty for account {accountId}";
_logger.LogWarning(message);
return IDomainResult.Failed<RegistrationCache>(message);
}
var cache = JsonSerializer.Deserialize<RegistrationCache>(json);
return IDomainResult.Success(cache);
}
public async Task<IDomainResult> DeleteFromCacheAsync(Guid accountId) {
var cacheFilePath = GetCacheFilePath(accountId);
await _cacheLock.WaitAsync();
try {
if (File.Exists(cacheFilePath)) {
File.Delete(cacheFilePath);
_logger.LogInformation($"Cache file deleted for account {accountId}");
}
else {
_logger.LogWarning($"Cache file not found for account {accountId}");
}
return IDomainResult.Success();
}
catch (Exception ex) {
var message = $"Error deleting cache file for account {accountId}";
_logger.LogError(ex, message);
return IDomainResult.Failed(message);
}
finally {
_cacheLock.Release();
}
public Task<IDomainResult> SaveToCacheAsync(Guid accountId, RegistrationCache cache) {
return _lockManager.ExecuteWithLockAsync(() => SaveToCacheInternalAsync(accountId, cache));
}
private async Task<IDomainResult> SaveToCacheInternalAsync(Guid accountId, RegistrationCache cache) {
var cacheFilePath = GetCacheFilePath(accountId);
var json = JsonSerializer.Serialize(cache);
await File.WriteAllTextAsync(cacheFilePath, json);
_logger.LogInformation($"Cache file saved for account {accountId}");
return DomainResult.Success();
}
public Task<IDomainResult> DeleteFromCacheAsync(Guid accountId) {
return _lockManager.ExecuteWithLockAsync(() => DeleteFromCacheInternal(accountId));
}
#region RestService
public async Task<(GetAccountsResponse?, IDomainResult)> GetAccountsAsync() {
await _cacheLock.WaitAsync();
private IDomainResult DeleteFromCacheInternal(Guid accountId) {
var cacheFilePath = GetCacheFilePath(accountId);
if (File.Exists(cacheFilePath)) {
File.Delete(cacheFilePath);
_logger.LogInformation($"Cache file deleted for account {accountId}");
}
else {
_logger.LogWarning($"Cache file not found for account {accountId}");
}
return DomainResult.Success();
}
try {
#endregion
#region Account Operations
public async Task<(GetAccountResponse[]?, IDomainResult)> GetAccountsAsync() {
return await _lockManager.ExecuteWithLockAsync(async () => {
var cacheFiles = Directory.GetFiles(_cacheDirectory);
if (cacheFiles == null)
return IDomainResult.Success(new GetAccountsResponse {
Accounts = Array.Empty<GetAccountResponse>()
});
var accountIds = cacheFiles.Select(x => Path.GetFileNameWithoutExtension(x).ToGuid());
var accountIds = cacheFiles.Select(x => Path.GetFileNameWithoutExtension(x).ToGuid()).ToArray();
var accounts = new List<GetAccountResponse>();
foreach (var accountId in accountIds) {
var (account, getAccountResult) = await GetAccountAsync(accountId);
if(!getAccountResult.IsSuccess || account == null)
return (null, getAccountResult);
foreach (var accountId in accountIds) {
var (account, result) = await GetAccountAsync(accountId);
if (!result.IsSuccess || account == null) {
return (null, result);
}
accounts.Add(account);
}
return IDomainResult.Success(new GetAccountsResponse {
Accounts = accounts.ToArray()
});
}
catch (Exception ex) {
var message = "Error listing cache files";
_logger.LogError(ex, message);
return IDomainResult.Failed<GetAccountsResponse?> (message);
}
finally {
_cacheLock.Release();
}
return IDomainResult.Success(accounts.ToArray());
});
}
public async Task<(GetAccountResponse?, IDomainResult)> GetAccountAsync(Guid accountId) {
return await _lockManager.ExecuteWithLockAsync(async () => {
var (cache, result) = await LoadFromCacheAsync(accountId);
if (!result.IsSuccess || cache == null) {
return (null, result);
}
await _cacheLock.WaitAsync();
try {
var (registrationCache, gerRegistrationCacheResult) = await LoadFromCacheAsync(accountId);
if (!gerRegistrationCacheResult.IsSuccess || registrationCache == null)
return (null, gerRegistrationCacheResult);
return IDomainResult.Success(new GetAccountResponse {
var response = new GetAccountResponse {
AccountId = accountId,
Description = registrationCache.Description,
Contacts = registrationCache.Contacts,
Hostnames = registrationCache.GetHostsWithUpcomingSslExpiry()
});
}
catch (Exception ex) {
var message = "Error listing cache files";
_logger.LogError(ex, message);
Description = cache.Description,
Contacts = cache.Contacts,
Hostnames = GetHostnamesFromCache(cache).ToArray()
};
return IDomainResult.Failed<GetAccountResponse?>(message);
}
finally {
_cacheLock.Release();
}
return IDomainResult.Success(response);
});
}
public async Task<(GetAccountResponse?, IDomainResult)> PutAccountAsync(Guid accountId, PutAccountRequest requestData) {
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null) {
return (null, loadResult);
}
cache.Description = requestData.Description;
cache.Contacts = requestData.Contacts;
var saveResult = await SaveToCacheAsync(accountId, cache);
if (!saveResult.IsSuccess) {
return (null, saveResult);
}
return CreateGetAccountResponse(accountId, cache);
}
public async Task<(GetAccountResponse?, IDomainResult)> PatchAccountAsync(Guid accountId, PatchAccountRequest requestData) {
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null) {
return (null, loadResult);
}
if (requestData.Description != null) {
switch (requestData.Description.Op) {
case PatchOperation.Replace:
cache.Description = requestData.Description.Value;
break;
}
}
if (requestData.Contacts != null && requestData.Contacts.Any()) {
var contacts = cache.Contacts?.ToList() ?? new List<string>();
foreach (var action in requestData.Contacts) {
switch (action.Op)
{
case PatchOperation.Add:
if (action.Value != null) contacts.Add(action.Value);
break;
case PatchOperation.Replace:
if (action.Index != null && action.Index >= 0 && action.Index < contacts.Count)
contacts[action.Index.Value] = action.Value;
break;
case PatchOperation.Remove:
if (action.Index != null && action.Index >= 0 && action.Index < contacts.Count)
contacts.RemoveAt(action.Index.Value);
break;
}
}
cache.Contacts = contacts.ToArray();
}
var saveResult = await SaveToCacheAsync(accountId, cache);
if (!saveResult.IsSuccess) {
return (null, saveResult);
}
return CreateGetAccountResponse(accountId, cache);
}
#endregion
#region Contacts Operations
#region Contacts
/// <summary>
/// Retrieves the contacts list for the account.
/// </summary>
/// <param name="accountId">The ID of the account.</param>
/// <returns>The contacts list and domain result.</returns>
public async Task<(GetContactsResponse?, IDomainResult)> GetContactsAsync(Guid accountId) {
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null)
if (!loadResult.IsSuccess || cache == null) {
return (null, loadResult);
}
return IDomainResult.Success(new GetContactsResponse {
Contacts = cache.Contacts ?? Array.Empty<string>()
});
}
/// <summary>
/// Adds new contacts to the account. This method initializes the contacts list if it is null.
/// </summary>
/// <param name="accountId">The ID of the account.</param>
/// <param name="requestData">The request containing the contacts to add.</param>
/// <returns>The updated account response and domain result.</returns>
public async Task<(GetAccountResponse?, IDomainResult)> PostContactAsync(Guid accountId, PostContactsRequest requestData) {
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null)
return (null, loadResult);
var contacts = cache.Contacts?.ToList() ?? new List<string>();
if (requestData.Contacts != null) {
contacts.AddRange(requestData.Contacts);
}
cache.Contacts = contacts.ToArray();
var saveResult = await SaveToCacheAsync(accountId, cache);
if (!saveResult.IsSuccess)
return (null, saveResult);
return (new GetAccountResponse {
AccountId = accountId,
Description = cache.Description,
Contacts = cache.Contacts,
Hostnames = cache.GetHostsWithUpcomingSslExpiry()
}, IDomainResult.Success());
}
/// <summary>
/// Replaces the entire contacts list for the account.
/// </summary>
/// <param name="accountId">The ID of the account.</param>
/// <param name="requestData">The request containing the new contacts list.</param>
/// <returns>The updated account response and domain result.</returns>
public async Task<(GetAccountResponse?, IDomainResult)> PutContactsAsync(Guid accountId, PutContactsRequest requestData) {
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null)
if (!loadResult.IsSuccess || cache == null) {
return (null, loadResult);
}
cache.Contacts = requestData.Contacts;
var saveResult = await SaveToCacheAsync(accountId, cache);
if (!saveResult.IsSuccess)
if (!saveResult.IsSuccess) {
return (null, saveResult);
}
return (new GetAccountResponse {
AccountId = accountId,
Description = cache.Description,
Contacts = cache.Contacts,
Hostnames = cache.GetHostsWithUpcomingSslExpiry()
}, IDomainResult.Success());
return CreateGetAccountResponse(accountId, cache);
}
/// <summary>
/// Partially updates the contacts list for the account. Supports add, replace, and remove operations.
/// </summary>
/// <param name="accountId">The ID of the account.</param>
/// <param name="requestData">The request containing the patch operations for contacts.</param>
/// <returns>The updated account response and domain result.</returns>
public async Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactRequest requestData) {
public async Task<(GetAccountResponse?, IDomainResult)> PatchContactsAsync(Guid accountId, PatchContactsRequest requestData) {
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null)
if (!loadResult.IsSuccess || cache == null) {
return (null, loadResult);
}
var contacts = cache.Contacts?.ToList() ?? new List<string>();
foreach (var contact in requestData.Contacts) {
switch (contact.Op) {
case PatchOperation.Add:
if (contact.Value != null)
if (contact.Value != null) {
contacts.Add(contact.Value);
}
break;
case PatchOperation.Replace:
if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count && contact.Value != null)
if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count && contact.Value != null) {
contacts[contact.Index.Value] = contact.Value;
}
break;
case PatchOperation.Remove:
if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count)
if (contact.Index.HasValue && contact.Index.Value >= 0 && contact.Index.Value < contacts.Count) {
contacts.RemoveAt(contact.Index.Value);
}
break;
default:
return (null, IDomainResult.Failed("Invalid patch operation."));
@ -310,76 +266,79 @@ public class CacheService : ICacheService, ICacheRestService, IDisposable {
cache.Contacts = contacts.ToArray();
var saveResult = await SaveToCacheAsync(accountId, cache);
if (!saveResult.IsSuccess)
if (!saveResult.IsSuccess) {
return (null, saveResult);
}
return (new GetAccountResponse {
AccountId = accountId,
Description = cache.Description,
Contacts = cache.Contacts,
Hostnames = cache.GetHostsWithUpcomingSslExpiry()
}, IDomainResult.Success());
return CreateGetAccountResponse(accountId, cache);
}
/// <summary>
/// Deletes a contact from the account by index.
/// </summary>
/// <param name="accountId">The ID of the account.</param>
/// <param name="index">The index of the contact to remove.</param>
/// <returns>The domain result indicating success or failure.</returns>
public async Task<IDomainResult> DeleteContactAsync(Guid accountId, int index) {
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache == null)
if (!loadResult.IsSuccess || cache == null) {
return loadResult;
}
var contacts = cache.Contacts?.ToList() ?? new List<string>();
if (index >= 0 && index < contacts.Count)
if (index >= 0 && index < contacts.Count) {
contacts.RemoveAt(index);
}
cache.Contacts = contacts.ToArray();
var saveResult = await SaveToCacheAsync(accountId, cache);
if (!saveResult.IsSuccess)
if (!saveResult.IsSuccess) {
return saveResult;
}
return IDomainResult.Success();
}
#endregion
#region Hostnames
#region Hostnames Operations
public async Task<(GetHostnamesResponse?, IDomainResult)> GetHostnames(Guid accountId) {
var (cache, loadResult) = await LoadFromCacheAsync(accountId);
if (!loadResult.IsSuccess || cache?.CachedCerts == null)
if (!loadResult.IsSuccess || cache?.CachedCerts == null) {
return (null, loadResult);
var hoststWithUpcomingSslExpire = cache.GetHostsWithUpcomingSslExpiry();
var response = new GetHostnamesResponse {
Hostnames = new List<HostnameResponse>()
};
foreach (var result in cache.CachedCerts) {
var (subject, cachedChert) = result;
var cert = new X509Certificate2(Encoding.ASCII.GetBytes(cachedChert.Cert));
response.Hostnames.Add(new HostnameResponse {
Hostname = subject,
Expires = cert.NotBefore,
IsUpcomingExpire = hoststWithUpcomingSslExpire.Contains(subject)
});
}
return IDomainResult.Success(response);
var hostnames = GetHostnamesFromCache(cache);
return IDomainResult.Success(new GetHostnamesResponse {
Hostnames = hostnames
});
}
private List<HostnameResponse> GetHostnamesFromCache(RegistrationCache cache) {
var hosts = cache.GetHosts().Select(x => new HostnameResponse {
Hostname = x.Hostname,
Expires = x.Expires,
IsUpcomingExpire = x.IsUpcomingExpire
}).ToList();
return hosts;
}
#endregion
#endregion
#region Helper Methods
private (GetAccountResponse?, IDomainResult) CreateGetAccountResponse(Guid accountId, RegistrationCache cache) {
var hostnames = GetHostnamesFromCache(cache) ?? new List<HostnameResponse>();
return (new GetAccountResponse {
AccountId = accountId,
Description = cache.Description,
Contacts = cache.Contacts,
Hostnames = hostnames.ToArray()
}, IDomainResult.Success());
}
public void Dispose() {
_cacheLock?.Dispose();
_lockManager?.Dispose();
}
#endregion
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MaksIT.Models.LetsEncryptServer.Cache.Requests {
public class PatchAccountRequest {
public PatchAction<string>? Description { get; set; }
public List<PatchAction<string>>? Contacts { get; set; }
}
}

View File

@ -6,7 +6,7 @@ using System.Threading.Tasks;
namespace MaksIT.Models.LetsEncryptServer.Cache.Requests {
public class PatchContactRequest {
public class PatchContactsRequest {
public List<PatchAction<string>> Contacts { get; set; }
}
}

View File

@ -0,0 +1,12 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MaksIT.Models.LetsEncryptServer.Cache.Requests {
public class PutAccountRequest {
public string Description { get; set; }
public string[] Contacts { get; set; }
}
}

View File

@ -12,6 +12,6 @@ namespace Models.LetsEncryptServer.Cache.Responses {
public string []? Contacts { get; set; }
public string[]? Hostnames { get; set; }
public HostnameResponse[]? Hostnames { get; set; }
}
}

View File

@ -1,12 +0,0 @@
using Models.LetsEncryptServer.Cache.Responses;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace MaksIT.Models.LetsEncryptServer.Cache.Responses {
public class GetAccountsResponse {
public GetAccountResponse[] Accounts { get; set; }
}
}

View File

@ -6,13 +6,6 @@ using System.Threading.Tasks;
namespace Models.LetsEncryptServer.Cache.Responses {
public class HostnameResponse {
public string Hostname { get; set; }
public DateTime Expires { get; set; }
public bool IsUpcomingExpire { get; set; }
}
public class GetHostnamesResponse {
public List<HostnameResponse> Hostnames { get; set; }
}

View File

@ -0,0 +1,14 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Models.LetsEncryptServer.Cache.Responses {
public class HostnameResponse {
public string Hostname { get; set; }
public DateTime Expires { get; set; }
public bool IsUpcomingExpire { get; set; }
}
}