Compare commits
15 Commits
09df86000b
...
feat/signF
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
456c591fae | ||
|
|
41b2841c25 | ||
|
|
6a6da4a876 | ||
|
|
af280ee64e | ||
|
|
9b945ce232 | ||
|
|
8b1199bc71 | ||
|
|
1d74b7ca06 | ||
|
|
c1bce7c639 | ||
|
|
4401a70217 | ||
|
|
fd53f5bfd6 | ||
|
|
6126fce24d | ||
|
|
ce41090979 | ||
|
|
3fa113003c | ||
|
|
5504093591 | ||
|
|
040cf8641d |
@@ -21,5 +21,5 @@ public interface IEnvelopeReceiverRepository : ICRUDRepository<EnvelopeReceiver,
|
|||||||
|
|
||||||
Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
|
Task<IEnumerable<EnvelopeReceiver>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, params int[] ignore_statuses);
|
||||||
|
|
||||||
Task<EnvelopeReceiver?> ReadLastByReceiver(string email);
|
Task<EnvelopeReceiver?> ReadLastByReceiverAsync(string? email = null, int? id = null, string? signature = null);
|
||||||
}
|
}
|
||||||
@@ -35,7 +35,7 @@ public interface IEnvelopeReceiverService : IBasicCRUDService<EnvelopeReceiverDt
|
|||||||
|
|
||||||
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, EnvelopeQuery? envelopeQuery = null, ReadReceiverQuery? receiverQuery = null, params int[] ignore_statuses);
|
Task<DataResult<IEnumerable<EnvelopeReceiverDto>>> ReadByUsernameAsync(string username, int? min_status = null, int? max_status = null, EnvelopeQuery? envelopeQuery = null, ReadReceiverQuery? receiverQuery = null, params int[] ignore_statuses);
|
||||||
|
|
||||||
Task<DataResult<string?>> ReadLastUsedReceiverNameByMail(string mail);
|
Task<DataResult<string?>> ReadLastUsedReceiverNameByMailAsync(string? mail = null, int? id = null, string? signature = null);
|
||||||
|
|
||||||
Task<DataResult<SmsResponse>> SendSmsAsync(string envelopeReceiverId, string message);
|
Task<DataResult<SmsResponse>> SendSmsAsync(string envelopeReceiverId, string message);
|
||||||
Task<DataResult<IEnumerable<EnvelopeReceiverSecretDto>>> ReadWithSecretByUuidAsync(string uuid);
|
Task<DataResult<IEnumerable<EnvelopeReceiverSecretDto>>> ReadWithSecretByUuidAsync(string uuid);
|
||||||
|
|||||||
@@ -21,9 +21,9 @@ namespace EnvelopeGenerator.Application.DTOs
|
|||||||
|
|
||||||
[TemplatePlaceholder("[MESSAGE]")]
|
[TemplatePlaceholder("[MESSAGE]")]
|
||||||
public string Message { get; set; }
|
public string Message { get; set; }
|
||||||
public DateTime? ExpiresWhen { get; set; }
|
|
||||||
public DateTime? ExpiresWarningWhen { get; set; }
|
|
||||||
public DateTime AddedWhen { get; set; }
|
public DateTime AddedWhen { get; set; }
|
||||||
|
|
||||||
public DateTime? ChangedWhen { get; set; }
|
public DateTime? ChangedWhen { get; set; }
|
||||||
|
|
||||||
[TemplatePlaceholder("[DOCUMENT_TITLE]")]
|
[TemplatePlaceholder("[DOCUMENT_TITLE]")]
|
||||||
@@ -33,40 +33,22 @@ namespace EnvelopeGenerator.Application.DTOs
|
|||||||
|
|
||||||
public string Language { get; set; }
|
public string Language { get; set; }
|
||||||
|
|
||||||
public bool? SendReminderEmails { get; set; }
|
|
||||||
|
|
||||||
public int? FirstReminderDays { get; set; }
|
|
||||||
|
|
||||||
public int? ReminderIntervalDays { get; set; }
|
|
||||||
|
|
||||||
public int? EnvelopeTypeId { get; set; }
|
public int? EnvelopeTypeId { get; set; }
|
||||||
|
|
||||||
public int? CertificationType { get; set; }
|
public int? CertificationType { get; set; }
|
||||||
|
|
||||||
public bool? UseAccessCode { get; set; }
|
public bool? UseAccessCode { get; set; }
|
||||||
|
|
||||||
public int? FinalEmailToCreator { get; set; }
|
|
||||||
|
|
||||||
public int? FinalEmailToReceivers { get; set; }
|
|
||||||
|
|
||||||
public int? ExpiresWhenDays { get; set; }
|
|
||||||
|
|
||||||
public int? ExpiresWarningWhenDays { get; set; }
|
|
||||||
|
|
||||||
public bool TFAEnabled { get; init; }
|
public bool TFAEnabled { get; init; }
|
||||||
|
|
||||||
public bool DmzMoved { get; set; }
|
|
||||||
public UserReadDto? User { get; set; }
|
public UserReadDto? User { get; set; }
|
||||||
|
|
||||||
public EnvelopeType? EnvelopeType { get; set; }
|
public EnvelopeType? EnvelopeType { get; set; }
|
||||||
|
|
||||||
public string? EnvelopeTypeTitle { get; set; }
|
public string? EnvelopeTypeTitle { get; set; }
|
||||||
|
|
||||||
public bool IsAlreadySent { get; set; }
|
public bool IsAlreadySent { get; set; }
|
||||||
|
|
||||||
public string? StatusTranslated { get; set; }
|
|
||||||
|
|
||||||
public string? ContractTypeTranslated { get; set; }
|
|
||||||
|
|
||||||
public byte[]? DocResult { get; init; }
|
public byte[]? DocResult { get; init; }
|
||||||
|
|
||||||
public IEnumerable<EnvelopeDocumentDto>? Documents { get; set; }
|
public IEnumerable<EnvelopeDocumentDto>? Documents { get; set; }
|
||||||
|
|||||||
@@ -5,19 +5,32 @@ using EnvelopeGenerator.Application.DTOs.Receiver;
|
|||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using static EnvelopeGenerator.Common.Constants;
|
using static EnvelopeGenerator.Common.Constants;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory
|
namespace EnvelopeGenerator.Application.DTOs.EnvelopeHistory;
|
||||||
{
|
|
||||||
[ApiExplorerSettings(IgnoreApi = true)]
|
/// <summary>
|
||||||
public record EnvelopeHistoryDto(
|
///
|
||||||
long Id,
|
/// </summary>
|
||||||
int EnvelopeId,
|
/// <param name="Id"></param>
|
||||||
string UserReference,
|
/// <param name="EnvelopeId"></param>
|
||||||
int Status,
|
/// <param name="UserReference"></param>
|
||||||
string? StatusName,
|
/// <param name="Status"></param>
|
||||||
DateTime AddedWhen,
|
/// <param name="StatusName"></param>
|
||||||
DateTime? ActionDate,
|
/// <param name="AddedWhen"></param>
|
||||||
UserCreateDto? Sender,
|
/// <param name="ActionDate"></param>
|
||||||
ReceiverReadDto? Receiver,
|
/// <param name="Sender"></param>
|
||||||
ReferenceType ReferenceType,
|
/// <param name="Receiver"></param>
|
||||||
string? Comment = null) : BaseDTO<long>(Id), IUnique<long>;
|
/// <param name="ReferenceType"></param>
|
||||||
}
|
/// <param name="Comment"></param>
|
||||||
|
[ApiExplorerSettings(IgnoreApi = true)]
|
||||||
|
public record EnvelopeHistoryDto(
|
||||||
|
long Id,
|
||||||
|
int EnvelopeId,
|
||||||
|
string UserReference,
|
||||||
|
int Status,
|
||||||
|
string? StatusName,
|
||||||
|
DateTime AddedWhen,
|
||||||
|
DateTime? ActionDate,
|
||||||
|
UserCreateDto? Sender,
|
||||||
|
ReceiverReadDto? Receiver,
|
||||||
|
ReferenceType ReferenceType,
|
||||||
|
string? Comment = null) : BaseDTO<long>(Id), IUnique<long>;
|
||||||
@@ -31,6 +31,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="..\EnvelopeGenerator.Common\EnvelopeGenerator.Common.vbproj" />
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
<ProjectReference Include="..\EnvelopeGenerator.Domain\EnvelopeGenerator.Domain.csproj" />
|
||||||
<ProjectReference Include="..\EnvelopeGenerator.Extensions\EnvelopeGenerator.Extensions.csproj" />
|
<ProjectReference Include="..\EnvelopeGenerator.Extensions\EnvelopeGenerator.Extensions.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|||||||
@@ -0,0 +1,18 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class ReadHistoryMappingProfile: Profile
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public ReadHistoryMappingProfile()
|
||||||
|
{
|
||||||
|
CreateMap<EnvelopeHistory, ReadHistoryResponse>();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
using EnvelopeGenerator.Application.Envelopes.Queries.Read;
|
using EnvelopeGenerator.Common;
|
||||||
using EnvelopeGenerator.Application.Receivers.Queries.Read;
|
using MediatR;
|
||||||
using EnvelopeGenerator.Common;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
|
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
|
||||||
|
|
||||||
@@ -9,9 +9,12 @@ namespace EnvelopeGenerator.Application.Histories.Queries.Read;
|
|||||||
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
|
/// Repräsentiert eine Abfrage für die Verlaufshistorie eines Umschlags.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="EnvelopeId">Die eindeutige Kennung des Umschlags.</param>
|
/// <param name="EnvelopeId">Die eindeutige Kennung des Umschlags.</param>
|
||||||
/// <param name="Related">Abfrage, die angibt, worauf sich der Datensatz bezieht. Ob er sich auf den Empfänger, den Sender oder das System bezieht, wird durch 0, 1 bzw. 2 dargestellt.</param>
|
/// <param name="Status">Der Status des Umschlags, der abgefragt werden soll. Kann optional angegeben werden, um die Ergebnisse zu filtern.</param>
|
||||||
/// <param name="OnlyLast">Abfrage zur Steuerung, ob nur der aktuelle Status oder der gesamte Datensatz zurückgegeben wird.</param>
|
/// <param name="OnlyLast">Abfrage zur Steuerung, ob nur der aktuelle Status oder der gesamte Datensatz zurückgegeben wird.</param>
|
||||||
public record ReadHistoryQuery(
|
public record ReadHistoryQuery(
|
||||||
int? EnvelopeId = null,
|
[Required]
|
||||||
Constants.ReferenceType? Related = null,
|
int EnvelopeId,
|
||||||
bool? OnlyLast = true);
|
Constants.EnvelopeStatus? Status = null,
|
||||||
|
bool? OnlyLast = true) : IRequest<IEnumerable<ReadHistoryResponse>>
|
||||||
|
{
|
||||||
|
};
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
using AutoMapper;
|
||||||
|
using EnvelopeGenerator.Application.Contracts.Repositories;
|
||||||
|
using EnvelopeGenerator.Application.Exceptions;
|
||||||
|
using MediatR;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public class ReadHistoryQueryHandler : IRequestHandler<ReadHistoryQuery, IEnumerable<ReadHistoryResponse>>
|
||||||
|
{
|
||||||
|
private readonly IEnvelopeHistoryRepository _repository;
|
||||||
|
|
||||||
|
private readonly IMapper _mapper;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="repository"></param>
|
||||||
|
/// <param name="mapper"></param>
|
||||||
|
public ReadHistoryQueryHandler(IEnvelopeHistoryRepository repository, IMapper mapper)
|
||||||
|
{
|
||||||
|
_repository = repository;
|
||||||
|
_mapper = mapper;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="request"></param>
|
||||||
|
/// <param name="cancellationToken"></param>
|
||||||
|
/// <returns></returns>
|
||||||
|
/// <exception cref="NotFoundException"></exception>
|
||||||
|
public async Task<IEnumerable<ReadHistoryResponse>> Handle(ReadHistoryQuery request, CancellationToken cancellationToken)
|
||||||
|
{
|
||||||
|
var hists = await _repository.ReadAsync(request.EnvelopeId, status: request.Status is null ? null : (int) request.Status);
|
||||||
|
|
||||||
|
if (!hists.Any())
|
||||||
|
throw new NotFoundException();
|
||||||
|
|
||||||
|
return _mapper.Map<IEnumerable<ReadHistoryResponse>>(hists);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,60 @@
|
|||||||
|
using EnvelopeGenerator.Common;
|
||||||
|
|
||||||
|
namespace EnvelopeGenerator.Application.Histories.Queries.Read;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Represents the history of an envelope, including its status, user actions, and references.
|
||||||
|
/// </summary>
|
||||||
|
public class ReadHistoryResponse
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the unique identifier of the envelope history record.
|
||||||
|
/// </summary>
|
||||||
|
public long Id { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the identifier of the associated envelope.
|
||||||
|
/// </summary>
|
||||||
|
public int EnvelopeId { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the reference identifier of the user who performed the action.
|
||||||
|
/// </summary>
|
||||||
|
public string UserReference { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the status code of the envelope.
|
||||||
|
/// </summary>
|
||||||
|
public int Status { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
///
|
||||||
|
/// </summary>
|
||||||
|
public Common.Constants.ReferenceType ReferenceType => Status.ToString().FirstOrDefault() switch
|
||||||
|
{
|
||||||
|
'1' => Constants.ReferenceType.Sender,
|
||||||
|
'2' => Constants.ReferenceType.Receiver,
|
||||||
|
_ => Constants.ReferenceType.System,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the date and time when the record was added.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime AddedWhen { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the date and time when the action occurred.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime? ActionDate { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Gets or sets the optional comment about the envelope history record.
|
||||||
|
/// </summary>
|
||||||
|
public string? Comment { get; set; }
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public override int GetHashCode()
|
||||||
|
{
|
||||||
|
return Id.GetHashCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
using Dapper;
|
using Dapper;
|
||||||
using EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
using EnvelopeGenerator.Application.Contracts.SQLExecutor;
|
||||||
|
using EnvelopeGenerator.Application.Exceptions;
|
||||||
using EnvelopeGenerator.Domain.Entities;
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Application.SQL;
|
namespace EnvelopeGenerator.Application.SQL;
|
||||||
@@ -34,9 +35,16 @@ public class DocumentCreateReadSQL : ISQL<EnvelopeDocument>
|
|||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
public static DynamicParameters CreateParmas(string base64)
|
public static DynamicParameters CreateParmas(string base64)
|
||||||
{
|
{
|
||||||
var parameters = new DynamicParameters();
|
try
|
||||||
byte[] byteData = Convert.FromBase64String(base64);
|
{
|
||||||
parameters.Add("ByteData", byteData, System.Data.DbType.Binary);
|
var parameters = new DynamicParameters();
|
||||||
return parameters;
|
byte[] byteData = Convert.FromBase64String(base64);
|
||||||
|
parameters.Add("ByteData", byteData, System.Data.DbType.Binary);
|
||||||
|
return parameters;
|
||||||
|
}
|
||||||
|
catch(FormatException ex)
|
||||||
|
{
|
||||||
|
throw new BadRequestException(ex.Message.Replace("input", "dataAsBase64"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ namespace EnvelopeGenerator.Application.Services
|
|||||||
{
|
{
|
||||||
if (readOnlyDto?.Envelope is not null && readOnlyDto.Receiver is not null)
|
if (readOnlyDto?.Envelope is not null && readOnlyDto.Receiver is not null)
|
||||||
{
|
{
|
||||||
_placeholders["[NAME_RECEIVER]"] = await _envRcvService.ReadLastUsedReceiverNameByMail(readOnlyDto.AddedWho).ThenAsync(res => res, (msg, ntc) => string.Empty) ?? string.Empty;
|
_placeholders["[NAME_RECEIVER]"] = await _envRcvService.ReadLastUsedReceiverNameByMailAsync(readOnlyDto.AddedWho).ThenAsync(res => res, (msg, ntc) => string.Empty) ?? string.Empty;
|
||||||
var erReadOnlyId = (readOnlyDto.Id).EncodeEnvelopeReceiverId();
|
var erReadOnlyId = (readOnlyDto.Id).EncodeEnvelopeReceiverId();
|
||||||
var sigHost = await _configService.ReadDefaultSignatureHost();
|
var sigHost = await _configService.ReadDefaultSignatureHost();
|
||||||
var linkToDoc = $"{sigHost}/EnvelopeKey/{erReadOnlyId}";
|
var linkToDoc = $"{sigHost}/EnvelopeKey/{erReadOnlyId}";
|
||||||
|
|||||||
@@ -162,9 +162,9 @@ public class EnvelopeReceiverService : BasicCRUDService<IEnvelopeReceiverReposit
|
|||||||
return Result.Success(dto_list);
|
return Result.Success(dto_list);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<DataResult<string?>> ReadLastUsedReceiverNameByMail(string mail)
|
public async Task<DataResult<string?>> ReadLastUsedReceiverNameByMailAsync(string? mail = null, int? id = null, string? signature = null)
|
||||||
{
|
{
|
||||||
var er = await _repository.ReadLastByReceiver(mail);
|
var er = await _repository.ReadLastByReceiverAsync(mail, id, signature);
|
||||||
return er is null ? Result.Fail<string?>().Notice(LogLevel.None, Flag.NotFound) : Result.Success(er.Name);
|
return er is null ? Result.Fail<string?>().Notice(LogLevel.None, Flag.NotFound) : Result.Success(er.Name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
AccessCodeIncorrect = 2003
|
AccessCodeIncorrect = 2003
|
||||||
DocumentOpened = 2004
|
DocumentOpened = 2004
|
||||||
DocumentSigned = 2005
|
DocumentSigned = 2005
|
||||||
DocumentForwarded = 4001
|
DocumentForwarded = 2006
|
||||||
DocumentRejected = 2007
|
DocumentRejected = 2007
|
||||||
EnvelopeShared = 2008
|
EnvelopeShared = 2008
|
||||||
EnvelopeViewed = 2009
|
EnvelopeViewed = 2009
|
||||||
@@ -48,8 +48,8 @@
|
|||||||
|
|
||||||
'TODO: standardize in xwiki
|
'TODO: standardize in xwiki
|
||||||
Public Enum ReferenceType
|
Public Enum ReferenceType
|
||||||
Receiver = 0
|
Sender = 1
|
||||||
Sender
|
Receiver
|
||||||
System
|
System
|
||||||
Unknown
|
Unknown
|
||||||
End Enum
|
End Enum
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
using DigitalData.UserManager.Domain.Entities;
|
using DigitalData.UserManager.Domain.Entities;
|
||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
using System.ComponentModel.DataAnnotations.Schema;
|
using System.ComponentModel.DataAnnotations.Schema;
|
||||||
using static EnvelopeGenerator.Common.Constants;
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Domain.Entities
|
namespace EnvelopeGenerator.Domain.Entities
|
||||||
{
|
{
|
||||||
@@ -42,19 +41,5 @@ namespace EnvelopeGenerator.Domain.Entities
|
|||||||
|
|
||||||
[ForeignKey("UserReference")]
|
[ForeignKey("UserReference")]
|
||||||
public virtual Receiver? Receiver { get; set; }
|
public virtual Receiver? Receiver { get; set; }
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
public ReferenceType ReferenceType => (Status / 1000) switch
|
|
||||||
{
|
|
||||||
1 => ReferenceType.Sender,
|
|
||||||
2 or 3 => ReferenceType.Receiver,
|
|
||||||
_ => ReferenceType.Unknown,
|
|
||||||
};
|
|
||||||
|
|
||||||
[NotMapped]
|
|
||||||
public string? StatusName
|
|
||||||
=> (Enum.IsDefined(typeof(EnvelopeStatus), Status))
|
|
||||||
? Enum.GetName(typeof(EnvelopeStatus), Status)
|
|
||||||
: null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,11 +6,24 @@ public static class MemoryCacheExtensions
|
|||||||
{
|
{
|
||||||
private static readonly Guid BaseId = Guid.NewGuid();
|
private static readonly Guid BaseId = Guid.NewGuid();
|
||||||
|
|
||||||
public static IDictionary<string, int> GetEnumAsDictionary<TEnum>(this IMemoryCache memoryCache)
|
public static IDictionary<string, int> GetEnumAsDictionary<TEnum>(this IMemoryCache memoryCache, string key = "", params object[] ignores)
|
||||||
where TEnum : Enum
|
where TEnum : Enum
|
||||||
=> memoryCache.GetOrCreate(BaseId + typeof(TEnum).FullName, _ =>
|
=> memoryCache.GetOrCreate(BaseId + typeof(TEnum).FullName + key, _ =>
|
||||||
Enum.GetValues(typeof(TEnum))
|
{
|
||||||
|
var mergedIgnores = new List<TEnum>();
|
||||||
|
|
||||||
|
foreach (var ignore in ignores)
|
||||||
|
{
|
||||||
|
if (ignore is IEnumerable<TEnum> ignoreList)
|
||||||
|
mergedIgnores.AddRange(ignoreList);
|
||||||
|
else if (ignore is TEnum ignoreVal)
|
||||||
|
mergedIgnores.Add(ignoreVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Enum.GetValues(typeof(TEnum))
|
||||||
.Cast<TEnum>()
|
.Cast<TEnum>()
|
||||||
.ToDictionary(e => e.ToString(), e => Convert.ToInt32(e)))
|
.Where(e => !mergedIgnores.Contains(e))
|
||||||
|
.ToDictionary(e => e.ToString(), e => Convert.ToInt32(e));
|
||||||
|
})
|
||||||
?? throw new InvalidOperationException($"Failed to cache or retrieve enum dictionary for type '{typeof(TEnum).FullName}'.");
|
?? throw new InvalidOperationException($"Failed to cache or retrieve enum dictionary for type '{typeof(TEnum).FullName}'.");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ public class EnvelopeController : ControllerBase
|
|||||||
if (envelope.Uuid is string uuid)
|
if (envelope.Uuid is string uuid)
|
||||||
envelopes = envelopes.Where(e => e.Uuid == uuid);
|
envelopes = envelopes.Where(e => e.Uuid == uuid);
|
||||||
|
|
||||||
return Ok(envelopes);
|
return envelopes.Any() ? Ok(envelopes) : NotFound();
|
||||||
},
|
},
|
||||||
Fail: IActionResult (msg, ntc) =>
|
Fail: IActionResult (msg, ntc) =>
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class EnvelopeReceiverController : ControllerBase
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ruft den Namen des zuletzt verwendeten Empfängers basierend auf der angegebenen E-Mail-Adresse ab.
|
/// Ruft den Namen des zuletzt verwendeten Empfängers basierend auf der angegebenen E-Mail-Adresse ab.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="receiverName">Die Abfrage, die die E-Mail-Adresse des Empfängers enthält.</param>
|
/// <param name="receiver">Abfrage, bei der nur eine der Angaben ID, Signatur oder E-Mail-Adresse des Empfängers eingegeben werden muss.</param>
|
||||||
/// <returns>Eine HTTP-Antwort mit dem Namen des Empfängers oder einem Fehlerstatus.</returns>
|
/// <returns>Eine HTTP-Antwort mit dem Namen des Empfängers oder einem Fehlerstatus.</returns>
|
||||||
/// <remarks>
|
/// <remarks>
|
||||||
/// Dieser Endpunkt ermöglicht es, den Namen des zuletzt verwendeten Empfängers basierend auf der E-Mail-Adresse abzurufen.
|
/// Dieser Endpunkt ermöglicht es, den Namen des zuletzt verwendeten Empfängers basierend auf der E-Mail-Adresse abzurufen.
|
||||||
@@ -123,13 +123,10 @@ public class EnvelopeReceiverController : ControllerBase
|
|||||||
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
|
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
|
||||||
[Authorize]
|
[Authorize]
|
||||||
[HttpGet("salute")]
|
[HttpGet("salute")]
|
||||||
public async Task<IActionResult> GetReceiverName([FromQuery] ReadReceiverNameQuery receiverName)
|
public async Task<IActionResult> GetReceiverName([FromQuery] ReadReceiverNameQuery receiver)
|
||||||
{
|
{
|
||||||
if (receiverName.EmailAddress is null)
|
return await _erService.ReadLastUsedReceiverNameByMailAsync(receiver.EmailAddress, receiver.Id, receiver.Signature).ThenAsync(
|
||||||
return BadRequest();
|
Success: res => res is null ? NotFound() : Ok(res),
|
||||||
|
|
||||||
return await _erService.ReadLastUsedReceiverNameByMail(receiverName.EmailAddress).ThenAsync(
|
|
||||||
Success: res => res is null ? Ok(string.Empty) : Ok(res),
|
|
||||||
Fail: IActionResult (msg, ntc) =>
|
Fail: IActionResult (msg, ntc) =>
|
||||||
{
|
{
|
||||||
if (ntc.HasFlag(Flag.NotFound))
|
if (ntc.HasFlag(Flag.NotFound))
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
using DigitalData.EmailProfilerDispatcher.Abstraction.Entities;
|
using EnvelopeGenerator.Application.Contracts.Services;
|
||||||
using EnvelopeGenerator.Application.Contracts.Services;
|
|
||||||
using EnvelopeGenerator.Application.Histories.Queries.Read;
|
using EnvelopeGenerator.Application.Histories.Queries.Read;
|
||||||
using EnvelopeGenerator.Extensions;
|
using EnvelopeGenerator.Extensions;
|
||||||
|
using MediatR;
|
||||||
using Microsoft.AspNetCore.Authorization;
|
using Microsoft.AspNetCore.Authorization;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.Extensions.Caching.Memory;
|
using Microsoft.Extensions.Caching.Memory;
|
||||||
using static EnvelopeGenerator.Common.Constants;
|
using static EnvelopeGenerator.Common.Constants;
|
||||||
|
|
||||||
|
|
||||||
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
|
namespace EnvelopeGenerator.GeneratorAPI.Controllers;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -18,34 +17,35 @@ namespace EnvelopeGenerator.GeneratorAPI.Controllers;
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public class HistoryController : ControllerBase
|
public class HistoryController : ControllerBase
|
||||||
{
|
{
|
||||||
private readonly ILogger<HistoryController> _logger;
|
|
||||||
|
|
||||||
private readonly IEnvelopeHistoryService _service;
|
private readonly IEnvelopeHistoryService _service;
|
||||||
|
|
||||||
private readonly IMemoryCache _memoryCache;
|
private readonly IMemoryCache _memoryCache;
|
||||||
|
|
||||||
|
private readonly IMediator _mediator;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Konstruktor für den HistoryController.
|
/// Konstruktor für den HistoryController.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="logger">Der Logger, der für das Protokollieren von Informationen verwendet wird.</param>
|
|
||||||
/// <param name="service">Der Dienst, der für die Verarbeitung der Umschlaghistorie verantwortlich ist.</param>
|
/// <param name="service">Der Dienst, der für die Verarbeitung der Umschlaghistorie verantwortlich ist.</param>
|
||||||
public HistoryController(ILogger<HistoryController> logger, IEnvelopeHistoryService service, IMemoryCache memoryCache)
|
/// <param name="memoryCache"></param>
|
||||||
|
/// param name="mediator"
|
||||||
|
public HistoryController(IEnvelopeHistoryService service, IMemoryCache memoryCache, IMediator mediator)
|
||||||
{
|
{
|
||||||
_logger = logger;
|
|
||||||
_service = service;
|
_service = service;
|
||||||
_memoryCache = memoryCache;
|
_memoryCache = memoryCache;
|
||||||
|
_mediator = mediator;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gibt alle möglichen Verweise auf alle möglichen Status in einem Verlaufsdatensatz zurück. (z. B. DocumentSigned bezieht sich auf Receiver.)
|
/// Gibt alle möglichen Verweise auf alle möglichen Status in einem Verlaufsdatensatz zurück. (z. B. DocumentSigned bezieht sich auf Receiver.)
|
||||||
/// Dies wird hinzugefügt, damit Client-Anwendungen sich selbst auf dem neuesten Stand halten können.
|
/// Dies wird hinzugefügt, damit Client-Anwendungen sich selbst auf dem neuesten Stand halten können.
|
||||||
/// 0 - Receiver:
|
|
||||||
/// Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 1* beginnen.
|
|
||||||
/// 1 - Sender:
|
/// 1 - Sender:
|
||||||
/// Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 2* beginnen.
|
/// Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 1* beginnen.
|
||||||
/// 2 - System:
|
/// 2 - Receiver:
|
||||||
|
/// Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 2* beginnen.
|
||||||
|
/// 3 - System:
|
||||||
/// Historische Datensätze, die sich auf den allgemeinen Zustand des Umschlags beziehen. Diese haben Statuscodes, die mit 3* beginnen.
|
/// Historische Datensätze, die sich auf den allgemeinen Zustand des Umschlags beziehen. Diese haben Statuscodes, die mit 3* beginnen.
|
||||||
/// 3 - Unknown:
|
/// 4 - Unknown:
|
||||||
/// Ein unbekannter Datensatz weist auf einen möglichen Mangel oder eine Unstimmigkeit im Aktualisierungsprozess der Anwendung hin.
|
/// Ein unbekannter Datensatz weist auf einen möglichen Mangel oder eine Unstimmigkeit im Aktualisierungsprozess der Anwendung hin.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <returns></returns>
|
/// <returns></returns>
|
||||||
@@ -54,29 +54,25 @@ public class HistoryController : ControllerBase
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public IActionResult GetReferenceTypes(ReferenceType? referenceType = null)
|
public IActionResult GetReferenceTypes(ReferenceType? referenceType = null)
|
||||||
{
|
{
|
||||||
return referenceType is null ? Ok(_memoryCache.GetEnumAsDictionary<ReferenceType>()) : Ok(referenceType.ToString());
|
return referenceType is null ? Ok(_memoryCache.GetEnumAsDictionary<ReferenceType>("gen.api", ReferenceType.Unknown)) : Ok(referenceType.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Gibt alle möglichen Status in einem Verlaufsdatensatz zurück.
|
/// Gibt alle möglichen Status in einem Verlaufsdatensatz zurück.
|
||||||
/// Dies wird hinzugefügt, damit Client-Anwendungen sich selbst auf dem neuesten Stand halten können.
|
/// Dies wird hinzugefügt, damit Client-Anwendungen sich selbst auf dem neuesten Stand halten können.
|
||||||
/// 0: Invalid
|
|
||||||
/// 1001: EnvelopeCreated
|
|
||||||
/// 1002: EnvelopeSaved
|
|
||||||
/// 1003: EnvelopeQueued
|
/// 1003: EnvelopeQueued
|
||||||
/// 1004: EnvelopeSent (Nicht verwendet)
|
|
||||||
/// 1005: EnvelopePartlySigned
|
|
||||||
/// 1006: EnvelopeCompletelySigned
|
/// 1006: EnvelopeCompletelySigned
|
||||||
/// 1007: EnvelopeReportCreated
|
/// 1007: EnvelopeReportCreated
|
||||||
/// 1008: EnvelopeArchived
|
/// 1008: EnvelopeArchived
|
||||||
/// 1009: EnvelopeDeleted
|
/// 1009: EnvelopeDeleted
|
||||||
|
/// 10007: EnvelopeRejected
|
||||||
|
/// 10009: EnvelopeWithdrawn
|
||||||
/// 2001: AccessCodeRequested
|
/// 2001: AccessCodeRequested
|
||||||
/// 2002: AccessCodeCorrect
|
/// 2002: AccessCodeCorrect
|
||||||
/// 2003: AccessCodeIncorrect
|
/// 2003: AccessCodeIncorrect
|
||||||
/// 2004: DocumentOpened
|
/// 2004: DocumentOpened
|
||||||
/// 2005: DocumentSigned
|
/// 2005: DocumentSigned
|
||||||
/// 4001: DocumentForwarded
|
/// 2006: DocumentForwarded
|
||||||
/// 2006: SignatureConfirmed
|
|
||||||
/// 2007: DocumentRejected
|
/// 2007: DocumentRejected
|
||||||
/// 2008: EnvelopeShared
|
/// 2008: EnvelopeShared
|
||||||
/// 2009: EnvelopeViewed
|
/// 2009: EnvelopeViewed
|
||||||
@@ -88,9 +84,9 @@ public class HistoryController : ControllerBase
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="status">
|
/// <param name="status">
|
||||||
/// Abfrageparameter, der angibt, auf welche Referenz sich der Status bezieht.
|
/// Abfrageparameter, der angibt, auf welche Referenz sich der Status bezieht.
|
||||||
/// 0 - Sender: Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 1* beginnen.
|
/// 1 - Sender: Historische Datensätze, die sich auf den Status des Absenders beziehen. Sie haben Statuscodes, die mit 1* beginnen.
|
||||||
/// 1 - Receiver: Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 2* beginnen.
|
/// 2 - Receiver: Historische Datensätze über den Status der Empfänger. Diese haben Statuscodes, die mit 2* beginnen.
|
||||||
/// 2 - System: Diese werden durch Datenbank-Trigger aktualisiert und sind in den Tabellen EnvelopeHistory und EmailOut zu finden.Sie arbeiten
|
/// 3 - System: Diese werden durch Datenbank-Trigger aktualisiert und sind in den Tabellen EnvelopeHistory und EmailOut zu finden.Sie arbeiten
|
||||||
/// integriert mit der Anwendung EmailProfiler, um E-Mails zu versenden und haben die Codes, die mit 3* beginnen.
|
/// integriert mit der Anwendung EmailProfiler, um E-Mails zu versenden und haben die Codes, die mit 3* beginnen.
|
||||||
/// </param>
|
/// </param>
|
||||||
/// <returns>Gibt die HTTP-Antwort zurück.</returns>
|
/// <returns>Gibt die HTTP-Antwort zurück.</returns>
|
||||||
@@ -99,13 +95,13 @@ public class HistoryController : ControllerBase
|
|||||||
[Authorize]
|
[Authorize]
|
||||||
public IActionResult GetEnvelopeStatus([FromQuery] EnvelopeStatus? status = null)
|
public IActionResult GetEnvelopeStatus([FromQuery] EnvelopeStatus? status = null)
|
||||||
{
|
{
|
||||||
return status is null ? Ok(_memoryCache.GetEnumAsDictionary<EnvelopeStatus>()) : Ok(status.ToString());
|
return status is null ? Ok(_memoryCache.GetEnumAsDictionary<EnvelopeStatus>("gen.api", Status.NonHist, Status.RelatedToFormApp)) : Ok(status.ToString());
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Ruft die gesamte Umschlaghistorie basierend auf den angegebenen Abfrageparametern ab.
|
/// Ruft die gesamte Umschlaghistorie basierend auf den angegebenen Abfrageparametern ab.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="history">Die Abfrageparameter, die die Filterkriterien für die Umschlaghistorie definieren.</param>
|
/// <param name="historyQuery">Die Abfrageparameter, die die Filterkriterien für die Umschlaghistorie definieren.</param>
|
||||||
/// <returns>Eine Liste von Historieneinträgen, die den angegebenen Kriterien entsprechen, oder nur der letzte Eintrag.</returns>
|
/// <returns>Eine Liste von Historieneinträgen, die den angegebenen Kriterien entsprechen, oder nur der letzte Eintrag.</returns>
|
||||||
/// <response code="200">Die Anfrage war erfolgreich, und die Umschlaghistorie wird zurückgegeben.</response>
|
/// <response code="200">Die Anfrage war erfolgreich, und die Umschlaghistorie wird zurückgegeben.</response>
|
||||||
/// <response code="400">Die Anfrage war ungültig oder unvollständig.</response>
|
/// <response code="400">Die Anfrage war ungültig oder unvollständig.</response>
|
||||||
@@ -114,29 +110,10 @@ public class HistoryController : ControllerBase
|
|||||||
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
|
/// <response code="500">Ein unerwarteter Fehler ist aufgetreten.</response>
|
||||||
[HttpGet]
|
[HttpGet]
|
||||||
[Authorize]
|
[Authorize]
|
||||||
public async Task<IActionResult> GetAllAsync([FromQuery] ReadHistoryQuery history)
|
public async Task<IActionResult> GetAllAsync([FromQuery] ReadHistoryQuery historyQuery)
|
||||||
{
|
{
|
||||||
|
var history = await _mediator.Send(historyQuery);
|
||||||
|
|
||||||
|
return Ok((historyQuery.OnlyLast ?? false) ? history.MaxBy(h => h.AddedWhen) : history);
|
||||||
bool withReceiver = false;
|
|
||||||
bool withSender = false;
|
|
||||||
|
|
||||||
switch (history.Related)
|
|
||||||
{
|
|
||||||
case ReferenceType.Receiver:
|
|
||||||
withReceiver = true;
|
|
||||||
break;
|
|
||||||
case ReferenceType.Sender:
|
|
||||||
withSender = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
var histories = await _service.ReadAsync(
|
|
||||||
envelopeId: history.EnvelopeId,
|
|
||||||
referenceType: history.Related,
|
|
||||||
withSender: withSender,
|
|
||||||
withReceiver: withReceiver);
|
|
||||||
|
|
||||||
return Ok(histories);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,9 +10,9 @@
|
|||||||
<Authors>Digital Data GmbH</Authors>
|
<Authors>Digital Data GmbH</Authors>
|
||||||
<Company>Digital Data GmbH</Company>
|
<Company>Digital Data GmbH</Company>
|
||||||
<Product>EnvelopeGenerator.GeneratorAPI</Product>
|
<Product>EnvelopeGenerator.GeneratorAPI</Product>
|
||||||
<Version>1.2.2</Version>
|
<Version>1.2.3</Version>
|
||||||
<FileVersion>1.2.2</FileVersion>
|
<FileVersion>1.2.3</FileVersion>
|
||||||
<AssemblyVersion>1.2.2</AssemblyVersion>
|
<AssemblyVersion>1.2.3</AssemblyVersion>
|
||||||
<PackageOutputPath>Copyright © 2025 Digital Data GmbH. All rights reserved.</PackageOutputPath>
|
<PackageOutputPath>Copyright © 2025 Digital Data GmbH. All rights reserved.</PackageOutputPath>
|
||||||
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|||||||
@@ -26,9 +26,13 @@ try
|
|||||||
{
|
{
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Logging.ClearProviders();
|
|
||||||
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
|
builder.Logging.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace);
|
||||||
builder.Host.UseNLog();
|
|
||||||
|
if (!builder.Environment.IsDevelopment())
|
||||||
|
{
|
||||||
|
builder.Logging.ClearProviders();
|
||||||
|
builder.Host.UseNLog();
|
||||||
|
}
|
||||||
|
|
||||||
var config = builder.Configuration;
|
var config = builder.Configuration;
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,9 @@
|
|||||||
using EnvelopeGenerator.Domain.Entities;
|
using EnvelopeGenerator.Domain.Entities;
|
||||||
using EnvelopeGenerator.Application.Contracts.Repositories;
|
using EnvelopeGenerator.Application.Contracts.Repositories;
|
||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
|
using Microsoft.AspNetCore.Http.HttpResults;
|
||||||
|
using EnvelopeGenerator.Application.Exceptions;
|
||||||
|
using EnvelopeGenerator.Common.My.Resources;
|
||||||
|
|
||||||
namespace EnvelopeGenerator.Infrastructure.Repositories;
|
namespace EnvelopeGenerator.Infrastructure.Repositories;
|
||||||
|
|
||||||
@@ -93,8 +96,26 @@ public class EnvelopeReceiverRepository : CRUDRepository<EnvelopeReceiver, (int
|
|||||||
return await query.Include(er => er.Envelope).Include(er => er.Receiver).ToListAsync();
|
return await query.Include(er => er.Envelope).Include(er => er.Receiver).ToListAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<EnvelopeReceiver?> ReadLastByReceiver(string email)
|
public async Task<EnvelopeReceiver?> ReadLastByReceiverAsync(string? email = null, int? id = null, string? signature = null)
|
||||||
{
|
{
|
||||||
return await _dbSet.Where(er => er.Receiver!.EmailAddress == email).OrderBy(er => er.EnvelopeId).LastOrDefaultAsync();
|
var parameters = new[] { email, id?.ToString(), signature }.Count(p => p != null);
|
||||||
|
|
||||||
|
if (parameters == 0)
|
||||||
|
throw new BadRequestException("You must provide either 'email', 'id', or 'signature' for the query.");
|
||||||
|
if (parameters > 1)
|
||||||
|
throw new BadRequestException("Please provide only one parameter: either 'email', 'id', or 'signature'.");
|
||||||
|
|
||||||
|
var query = _dbSet.AsNoTracking();
|
||||||
|
|
||||||
|
if(email is not null)
|
||||||
|
query = query.Where(er => er.Receiver!.EmailAddress == email);
|
||||||
|
|
||||||
|
if (id is not null)
|
||||||
|
query = query.Where(er => er.Receiver!.Id == id);
|
||||||
|
|
||||||
|
if (signature is not null)
|
||||||
|
query = query.Where(er => er.Receiver!.Signature == signature);
|
||||||
|
|
||||||
|
return await query.OrderBy(er => er.EnvelopeId).LastOrDefaultAsync();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user