Add publishing integration events traces
This commit is contained in:
parent
cda590e4f6
commit
405e5be62b
@ -80,13 +80,13 @@ namespace Microsoft.eShopOnContainers.Services.Basket.API.Controllers
|
|||||||
// order creation process
|
// order creation process
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} at {AppShortName} - ({@IntegrationEvent})", eventMessage.Id, Program.AppShortName, eventMessage);
|
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppShortName} - ({@IntegrationEvent})", eventMessage.Id, Program.AppShortName, eventMessage);
|
||||||
|
|
||||||
_eventBus.Publish(eventMessage);
|
_eventBus.Publish(eventMessage);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "----- ERROR Publishing integration event: {IntegrationEventId} at {AppShortName} - ({@IntegrationEvent})", eventMessage.Id, Program.AppShortName, eventMessage);
|
_logger.LogError(ex, "----- ERROR Publishing integration event: {IntegrationEventId} from {AppShortName}", eventMessage.Id, Program.AppShortName);
|
||||||
|
|
||||||
throw;
|
throw;
|
||||||
}
|
}
|
||||||
|
@ -39,7 +39,7 @@ namespace Catalog.API.IntegrationEvents
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId_published} at {AppShortName} - ({@IntegrationEvent})", evt.Id, Program.AppShortName, evt);
|
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId_published} from {AppShortName} - ({@IntegrationEvent})", evt.Id, Program.AppShortName, evt);
|
||||||
|
|
||||||
await _eventLogService.MarkEventAsInProgressAsync(evt.Id);
|
await _eventLogService.MarkEventAsInProgressAsync(evt.Id);
|
||||||
_eventBus.Publish(evt);
|
_eventBus.Publish(evt);
|
||||||
@ -47,7 +47,7 @@ namespace Catalog.API.IntegrationEvents
|
|||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
_logger.LogError(ex, "----- ERROR Publishing integration event: {IntegrationEventId} at {AppShortName} - ({@IntegrationEvent})", evt.Id, Program.AppShortName, evt);
|
_logger.LogError(ex, "----- ERROR Publishing integration event: {IntegrationEventId} from {AppShortName} - ({@IntegrationEvent})", evt.Id, Program.AppShortName, evt);
|
||||||
await _eventLogService.MarkEventAsFailedAsync(evt.Id);
|
await _eventLogService.MarkEventAsFailedAsync(evt.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
using Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events;
|
using Microsoft.eShopOnContainers.Services.Locations.API.IntegrationEvents.Events;
|
||||||
using Microsoft.eShopOnContainers.Services.Locations.API.Model;
|
using Microsoft.eShopOnContainers.Services.Locations.API.Model;
|
||||||
using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel;
|
using Microsoft.eShopOnContainers.Services.Locations.API.ViewModel;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Threading.Tasks;
|
using System.Threading.Tasks;
|
||||||
@ -14,11 +15,16 @@
|
|||||||
{
|
{
|
||||||
private readonly ILocationsRepository _locationsRepository;
|
private readonly ILocationsRepository _locationsRepository;
|
||||||
private readonly IEventBus _eventBus;
|
private readonly IEventBus _eventBus;
|
||||||
|
private readonly ILogger<LocationsService> _logger;
|
||||||
|
|
||||||
public LocationsService(ILocationsRepository locationsRepository, IEventBus eventBus)
|
public LocationsService(
|
||||||
|
ILocationsRepository locationsRepository,
|
||||||
|
IEventBus eventBus,
|
||||||
|
ILogger<LocationsService> logger)
|
||||||
{
|
{
|
||||||
_locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository));
|
_locationsRepository = locationsRepository ?? throw new ArgumentNullException(nameof(locationsRepository));
|
||||||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
||||||
|
_logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Locations> GetLocationAsync(int locationId)
|
public async Task<Locations> GetLocationAsync(int locationId)
|
||||||
@ -37,11 +43,11 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> AddOrUpdateUserLocationAsync(string userId, LocationRequest currentPosition)
|
public async Task<bool> AddOrUpdateUserLocationAsync(string userId, LocationRequest currentPosition)
|
||||||
{
|
{
|
||||||
// Get the list of ordered regions the user currently is within
|
// Get the list of ordered regions the user currently is within
|
||||||
var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition);
|
var currentUserAreaLocationList = await _locationsRepository.GetCurrentUserRegionsListAsync(currentPosition);
|
||||||
|
|
||||||
if(currentUserAreaLocationList is null)
|
if (currentUserAreaLocationList is null)
|
||||||
{
|
{
|
||||||
throw new LocationDomainException("User current area not found");
|
throw new LocationDomainException("User current area not found");
|
||||||
}
|
}
|
||||||
@ -66,13 +72,17 @@
|
|||||||
{
|
{
|
||||||
var newUserLocations = MapUserLocationDetails(newLocations);
|
var newUserLocations = MapUserLocationDetails(newLocations);
|
||||||
var @event = new UserLocationUpdatedIntegrationEvent(userId, newUserLocations);
|
var @event = new UserLocationUpdatedIntegrationEvent(userId, newUserLocations);
|
||||||
|
|
||||||
|
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppShortName} - ({@IntegrationEvent})", @event.Id, Program.ShortAppName, @event);
|
||||||
|
|
||||||
_eventBus.Publish(@event);
|
_eventBus.Publish(@event);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<UserLocationDetails> MapUserLocationDetails(List<Locations> newLocations)
|
private List<UserLocationDetails> MapUserLocationDetails(List<Locations> newLocations)
|
||||||
{
|
{
|
||||||
var result = new List<UserLocationDetails>();
|
var result = new List<UserLocationDetails>();
|
||||||
newLocations.ForEach(location => {
|
newLocations.ForEach(location =>
|
||||||
|
{
|
||||||
result.Add(new UserLocationDetails()
|
result.Add(new UserLocationDetails()
|
||||||
{
|
{
|
||||||
LocationId = location.LocationId,
|
LocationId = location.LocationId,
|
||||||
|
@ -45,7 +45,7 @@ namespace Ordering.API.Application.IntegrationEvents
|
|||||||
|
|
||||||
foreach (var logEvt in pendindLogEvents)
|
foreach (var logEvt in pendindLogEvents)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} at {AppShortName} - ({@IntegrationEvent})", logEvt.EventId, Program.AppShortName, logEvt);
|
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppShortName} - ({@IntegrationEvent})", logEvt.EventId, Program.AppShortName, logEvt.IntegrationEvent);
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@ -53,8 +53,10 @@ namespace Ordering.API.Application.IntegrationEvents
|
|||||||
_eventBus.Publish(logEvt.IntegrationEvent);
|
_eventBus.Publish(logEvt.IntegrationEvent);
|
||||||
await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId);
|
await _eventLogService.MarkEventAsPublishedAsync(logEvt.EventId);
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
_logger.LogError(ex, "----- ERROR publishing integration event: {IntegrationEventId} from {AppShortName}", logEvt.EventId, Program.AppShortName);
|
||||||
|
|
||||||
await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId);
|
await _eventLogService.MarkEventAsFailedAsync(logEvt.EventId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -62,7 +64,7 @@ namespace Ordering.API.Application.IntegrationEvents
|
|||||||
|
|
||||||
public async Task AddAndSaveEventAsync(IntegrationEvent evt)
|
public async Task AddAndSaveEventAsync(IntegrationEvent evt)
|
||||||
{
|
{
|
||||||
_logger.LogInformation("----- Saving integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt);
|
_logger.LogInformation("----- Enqueuing integration event {IntegrationEventId} to repository ({@IntegrationEvent})", evt.Id, evt);
|
||||||
|
|
||||||
await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction());
|
await _eventLogService.SaveEventAsync(evt, _orderingContext.GetCurrentTransaction.GetDbTransaction());
|
||||||
}
|
}
|
||||||
|
@ -20,9 +20,10 @@ namespace Ordering.BackgroundTasks.Tasks
|
|||||||
private readonly BackgroundTaskSettings _settings;
|
private readonly BackgroundTaskSettings _settings;
|
||||||
private readonly IEventBus _eventBus;
|
private readonly IEventBus _eventBus;
|
||||||
|
|
||||||
public GracePeriodManagerService(IOptions<BackgroundTaskSettings> settings,
|
public GracePeriodManagerService(
|
||||||
IEventBus eventBus,
|
IOptions<BackgroundTaskSettings> settings,
|
||||||
ILogger<GracePeriodManagerService> logger)
|
IEventBus eventBus,
|
||||||
|
ILogger<GracePeriodManagerService> logger)
|
||||||
{
|
{
|
||||||
_settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));
|
_settings = settings?.Value ?? throw new ArgumentNullException(nameof(settings));
|
||||||
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
_eventBus = eventBus ?? throw new ArgumentNullException(nameof(eventBus));
|
||||||
@ -60,6 +61,8 @@ namespace Ordering.BackgroundTasks.Tasks
|
|||||||
{
|
{
|
||||||
var confirmGracePeriodEvent = new GracePeriodConfirmedIntegrationEvent(orderId);
|
var confirmGracePeriodEvent = new GracePeriodConfirmedIntegrationEvent(orderId);
|
||||||
|
|
||||||
|
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppShortName} - ({@IntegrationEvent})", confirmGracePeriodEvent.Id, Program.ShortAppName, confirmGracePeriodEvent);
|
||||||
|
|
||||||
_eventBus.Publish(confirmGracePeriodEvent);
|
_eventBus.Publish(confirmGracePeriodEvent);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
using MediatR;
|
using MediatR;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
using Microsoft.eShopOnContainers.Services.Ordering.Infrastructure.Idempotency;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
@ -17,11 +18,13 @@ namespace UnitTest.Ordering.Application
|
|||||||
{
|
{
|
||||||
private readonly Mock<IRequestManager> _requestManager;
|
private readonly Mock<IRequestManager> _requestManager;
|
||||||
private readonly Mock<IMediator> _mediator;
|
private readonly Mock<IMediator> _mediator;
|
||||||
|
private readonly Mock<ILogger<IdentifiedCommandHandler<CreateOrderCommand, bool>>> _loggerMock;
|
||||||
|
|
||||||
public IdentifiedCommandHandlerTest()
|
public IdentifiedCommandHandlerTest()
|
||||||
{
|
{
|
||||||
_requestManager = new Mock<IRequestManager>();
|
_requestManager = new Mock<IRequestManager>();
|
||||||
_mediator = new Mock<IMediator>();
|
_mediator = new Mock<IMediator>();
|
||||||
|
_loggerMock = new Mock<ILogger<IdentifiedCommandHandler<CreateOrderCommand, bool>>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -38,7 +41,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(true));
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object);
|
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object, _loggerMock.Object);
|
||||||
var cltToken = new System.Threading.CancellationToken();
|
var cltToken = new System.Threading.CancellationToken();
|
||||||
var result = await handler.Handle(fakeOrderCmd, cltToken);
|
var result = await handler.Handle(fakeOrderCmd, cltToken);
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(true));
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object);
|
var handler = new IdentifiedCommandHandler<CreateOrderCommand, bool>(_mediator.Object, _requestManager.Object, _loggerMock.Object);
|
||||||
var cltToken = new System.Threading.CancellationToken();
|
var cltToken = new System.Threading.CancellationToken();
|
||||||
var result = await handler.Handle(fakeOrderCmd, cltToken);
|
var result = await handler.Handle(fakeOrderCmd, cltToken);
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@ using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Commands;
|
|||||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries;
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Application.Queries;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers;
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Controllers;
|
||||||
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
|
using Microsoft.eShopOnContainers.Services.Ordering.API.Infrastructure.Services;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using Moq;
|
using Moq;
|
||||||
using Ordering.API.Application.Commands;
|
using Ordering.API.Application.Commands;
|
||||||
using System;
|
using System;
|
||||||
@ -19,12 +20,14 @@ namespace UnitTest.Ordering.Application
|
|||||||
private readonly Mock<IMediator> _mediatorMock;
|
private readonly Mock<IMediator> _mediatorMock;
|
||||||
private readonly Mock<IOrderQueries> _orderQueriesMock;
|
private readonly Mock<IOrderQueries> _orderQueriesMock;
|
||||||
private readonly Mock<IIdentityService> _identityServiceMock;
|
private readonly Mock<IIdentityService> _identityServiceMock;
|
||||||
|
private readonly Mock<ILogger<OrdersController>> _loggerMock;
|
||||||
|
|
||||||
public OrdersWebApiTest()
|
public OrdersWebApiTest()
|
||||||
{
|
{
|
||||||
_mediatorMock = new Mock<IMediator>();
|
_mediatorMock = new Mock<IMediator>();
|
||||||
_orderQueriesMock = new Mock<IOrderQueries>();
|
_orderQueriesMock = new Mock<IOrderQueries>();
|
||||||
_identityServiceMock = new Mock<IIdentityService>();
|
_identityServiceMock = new Mock<IIdentityService>();
|
||||||
|
_loggerMock = new Mock<ILogger<OrdersController>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
@ -35,7 +38,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(true));
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
|
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
|
||||||
var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
|
var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
@ -51,7 +54,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(true));
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
|
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
|
||||||
var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), String.Empty) as BadRequestResult;
|
var actionResult = await orderController.CancelOrderAsync(new CancelOrderCommand(1), String.Empty) as BadRequestResult;
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
@ -66,7 +69,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(true));
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
|
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
|
||||||
var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
|
var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), Guid.NewGuid().ToString()) as OkResult;
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
@ -82,7 +85,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(true));
|
.Returns(Task.FromResult(true));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
|
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
|
||||||
var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), String.Empty) as BadRequestResult;
|
var actionResult = await orderController.ShipOrderAsync(new ShipOrderCommand(1), String.Empty) as BadRequestResult;
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
@ -102,7 +105,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(fakeDynamicResult));
|
.Returns(Task.FromResult(fakeDynamicResult));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
|
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
|
||||||
var actionResult = await orderController.GetOrdersAsync();
|
var actionResult = await orderController.GetOrdersAsync();
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
@ -119,7 +122,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(fakeDynamicResult));
|
.Returns(Task.FromResult(fakeDynamicResult));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
|
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
|
||||||
var actionResult = await orderController.GetOrderAsync(fakeOrderId) as OkObjectResult;
|
var actionResult = await orderController.GetOrderAsync(fakeOrderId) as OkObjectResult;
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
@ -135,7 +138,7 @@ namespace UnitTest.Ordering.Application
|
|||||||
.Returns(Task.FromResult(fakeDynamicResult));
|
.Returns(Task.FromResult(fakeDynamicResult));
|
||||||
|
|
||||||
//Act
|
//Act
|
||||||
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object);
|
var orderController = new OrdersController(_mediatorMock.Object, _orderQueriesMock.Object, _identityServiceMock.Object, _loggerMock.Object);
|
||||||
var actionResult = await orderController.GetCardTypesAsync();
|
var actionResult = await orderController.GetCardTypesAsync();
|
||||||
|
|
||||||
//Assert
|
//Assert
|
||||||
|
@ -48,6 +48,8 @@
|
|||||||
orderPaymentIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId);
|
orderPaymentIntegrationEvent = new OrderPaymentFailedIntegrationEvent(@event.OrderId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_logger.LogInformation("----- Publishing integration event: {IntegrationEventId} from {AppShortName} - ({@IntegrationEvent})", orderPaymentIntegrationEvent.Id, Program.AppShortName, orderPaymentIntegrationEvent);
|
||||||
|
|
||||||
_eventBus.Publish(orderPaymentIntegrationEvent);
|
_eventBus.Publish(orderPaymentIntegrationEvent);
|
||||||
|
|
||||||
await Task.CompletedTask;
|
await Task.CompletedTask;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user