256 lines
11 KiB
C#
256 lines
11 KiB
C#
using Azure;
|
|
using Azure.Communication;
|
|
using Azure.Communication.CallAutomation;
|
|
using Azure.Communication.Identity;
|
|
using Azure.Communication.Rooms;
|
|
using EnotaryoPH.Data;
|
|
using EnotaryoPH.Data.Entities;
|
|
|
|
namespace EnotaryoPH.Web.Common.Services
|
|
{
|
|
public class VideoConferenceService : IVideoConferenceService
|
|
{
|
|
private const int VideoConferenceExpirationInHours = 2;
|
|
|
|
private readonly CallAutomationClient _callAutomationClient;
|
|
private readonly CommunicationIdentityClient _communicationIdentityClient;
|
|
private readonly IConfiguration _configuration;
|
|
private readonly NotaryoDBContext _dbContext;
|
|
private readonly IEventService _eventService;
|
|
private readonly RoomsClient _roomsClient;
|
|
|
|
public VideoConferenceService(NotaryoDBContext dbContext, CommunicationIdentityClient communicationIdentityClient, RoomsClient roomsClient, CallAutomationClient callAutomationClient, IConfiguration configuration, IEventService eventService)
|
|
{
|
|
_dbContext = dbContext;
|
|
_communicationIdentityClient = communicationIdentityClient;
|
|
_roomsClient = roomsClient;
|
|
_callAutomationClient = callAutomationClient;
|
|
_configuration = configuration;
|
|
_eventService = eventService;
|
|
}
|
|
|
|
public async Task ApproveTransactionAsync(Guid transaction_UID)
|
|
{
|
|
var transaction = _dbContext.Transactions
|
|
.Include(t => t.TransactionSignatories)
|
|
.ThenInclude(ts => ts.User)
|
|
.Include(t => t.Schedule)
|
|
.Include(t => t.Lawyer)
|
|
.ThenInclude(l => l.User)
|
|
.Include(t => t.Principal)
|
|
.FirstOrDefault(t => t.Transaction_UID == transaction_UID);
|
|
NoDataException.ThrowIfNull(transaction, nameof(Transaction), transaction_UID);
|
|
transaction.Status = nameof(TransactionState.Approved);
|
|
transaction.TransactionSignatories.ForEach(ts => ts.Status = nameof(SignatoryStatus.Approved));
|
|
transaction.Schedule.Status = nameof(VideoConferenceStatus.Completed);
|
|
_dbContext.Update(transaction);
|
|
_dbContext.SaveChanges();
|
|
|
|
await Task.WhenAll(
|
|
_eventService.LogAsync(NotaryoEvent.TransactionApproved, transaction_UID),
|
|
StopRecordingAsync(transaction.Schedule)
|
|
);
|
|
}
|
|
|
|
public bool CanStart(Guid transaction_UID)
|
|
{
|
|
if (transaction_UID == Guid.Empty)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var transactionEntity = _dbContext.Transactions
|
|
.AsNoTracking()
|
|
.Include(t => t.TransactionSignatories)
|
|
.FirstOrDefault(t => t.Transaction_UID == transaction_UID);
|
|
if (transactionEntity == null)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
var isReadyForVideoCall = transactionEntity.TransactionSignatories.All(signatory => signatory.Status == nameof(SignatoryStatus.FaceMatch));
|
|
var isAcceptedByLawyer = transactionEntity.Status == nameof(TransactionState.Accepted) && transactionEntity.LawyerID > 0;
|
|
return isReadyForVideoCall && isAcceptedByLawyer;
|
|
}
|
|
|
|
public Guid GetUIDByTransactionUID(Guid transaction_UID)
|
|
{
|
|
var transactionEntity = _dbContext.Transactions
|
|
.AsNoTracking()
|
|
.FirstOrDefault(t => t.Transaction_UID == transaction_UID);
|
|
if (transactionEntity == null)
|
|
{
|
|
return Guid.Empty;
|
|
}
|
|
|
|
var schedule = _dbContext.LawyerVideoConferenceSchedules.AsNoTracking().FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID);
|
|
return schedule == null ? Guid.Empty : schedule.LawyerVideoConferenceSchedule_UID;
|
|
}
|
|
|
|
public bool HasExpired(Guid transaction_UID)
|
|
{
|
|
var transactionEntity = _dbContext.Transactions
|
|
.Include(t => t.TransactionSignatories)
|
|
.FirstOrDefault(t => t.Transaction_UID == transaction_UID);
|
|
if (transactionEntity == null)
|
|
{
|
|
return false;
|
|
}
|
|
var schedule = _dbContext.LawyerVideoConferenceSchedules.FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID);
|
|
if (schedule == null)
|
|
{
|
|
return false;
|
|
}
|
|
if (schedule.Status == nameof(VideoConferenceStatus.Expired))
|
|
{
|
|
return true;
|
|
}
|
|
if ((DateTime.UtcNow - schedule.MeetingDate).TotalHours > VideoConferenceExpirationInHours)
|
|
{
|
|
if (!schedule.Status?.IsInList(VideoConferenceStatus.Abandoned, VideoConferenceStatus.Completed) ?? false)
|
|
{
|
|
schedule.Status = nameof(VideoConferenceStatus.Expired);
|
|
_dbContext.Update(schedule);
|
|
_dbContext.SaveChanges();
|
|
}
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
public async Task<Guid> StartAsync(Guid transaction_UID)
|
|
{
|
|
if (!CanStart(transaction_UID))
|
|
{
|
|
throw new ArgumentException("Transaction is not ready for video conference.");
|
|
}
|
|
var schedule = GetOrCreateNewSchedule(transaction_UID);
|
|
if (schedule.Status == nameof(VideoConferenceStatus.New))
|
|
{
|
|
await CreateMeetingRoomAsync(schedule);
|
|
_dbContext.UpdateOrCreate(schedule);
|
|
_dbContext.SaveChanges();
|
|
}
|
|
return schedule.LawyerVideoConferenceSchedule_UID;
|
|
}
|
|
|
|
public async Task StartRecordingAsync(Guid transaction_UID, string serverCallID)
|
|
{
|
|
var transactionEntity = _dbContext.Transactions
|
|
.Include(t => t.TransactionSignatories)
|
|
.Single(t => t.Transaction_UID == transaction_UID);
|
|
NoDataException.ThrowIfNull(transactionEntity, nameof(Transaction), transaction_UID);
|
|
|
|
var schedule = _dbContext.LawyerVideoConferenceSchedules
|
|
.FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID);
|
|
NoDataException.ThrowIfNull(schedule, nameof(LawyerVideoConferenceSchedule), transactionEntity.TransactionID, FullName.Of(transactionEntity.TransactionID));
|
|
|
|
if (string.IsNullOrEmpty(schedule.RecordingID))
|
|
{
|
|
schedule.RecordingID = await StartRecordingAsync(serverCallID);
|
|
schedule.ServerCallID = serverCallID;
|
|
_dbContext.Update(schedule);
|
|
_dbContext.SaveChanges();
|
|
}
|
|
}
|
|
|
|
private async Task CreateMeetingRoomAsync(LawyerVideoConferenceSchedule schedule)
|
|
{
|
|
var roomParticipants = new List<RoomParticipant>();
|
|
foreach (var participant in schedule.LawyerVideoConferenceParticipants.ToSafeList())
|
|
{
|
|
var attendee = await _communicationIdentityClient.CreateUserAsync();
|
|
participant.MeetingRoomTokenID = await GetTokenResponseAsync(attendee);
|
|
participant.MeetingRoomUserID = attendee.Value.Id;
|
|
roomParticipants.Add(new RoomParticipant(attendee) { Role = ParticipantRole.Attendee });
|
|
}
|
|
|
|
var presenter = await _communicationIdentityClient.CreateUserAsync();
|
|
schedule.MeetingRoomTokenID = await GetTokenResponseAsync(presenter);
|
|
schedule.MeetingRoomUserID = presenter.Value.Id;
|
|
roomParticipants.Add(new RoomParticipant(presenter) { Role = ParticipantRole.Presenter });
|
|
|
|
CommunicationRoom room = await _roomsClient.CreateRoomAsync(DateTime.Now, DateTime.Now.AddHours(2), roomParticipants);
|
|
schedule.MeetingRoomID = room.Id;
|
|
schedule.Status = nameof(VideoConferenceStatus.InProgress);
|
|
}
|
|
|
|
private LawyerVideoConferenceSchedule GetOrCreateNewSchedule(Guid transaction_UID)
|
|
{
|
|
var transactionEntity = _dbContext.Transactions
|
|
.Include(t => t.TransactionSignatories)
|
|
.Single(t => t.Transaction_UID == transaction_UID);
|
|
|
|
var schedule = _dbContext.LawyerVideoConferenceSchedules.FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID);
|
|
if (schedule != null)
|
|
{
|
|
return schedule;
|
|
}
|
|
schedule = new LawyerVideoConferenceSchedule
|
|
{
|
|
LawyerVideoConferenceSchedule_UID = Guid.CreateVersion7(DateTime.UtcNow),
|
|
CreatedOn = DateTime.UtcNow,
|
|
LawyerID = transactionEntity.LawyerID.GetValueOrDefault(),
|
|
TransactionID = transactionEntity.TransactionID,
|
|
Status = nameof(VideoConferenceStatus.New)
|
|
};
|
|
|
|
var participants = transactionEntity.TransactionSignatories.ConvertAll(signatory => new LawyerVideoConferenceParticipant
|
|
{
|
|
CreatedOn = DateTime.UtcNow,
|
|
Status = nameof(VideoConferenceStatus.New),
|
|
LawyerVideoConferenceParticipant_UID = Guid.CreateVersion7(DateTime.UtcNow),
|
|
ParticipantID = signatory.UserID,
|
|
});
|
|
participants.Add(new LawyerVideoConferenceParticipant
|
|
{
|
|
CreatedOn = DateTime.UtcNow,
|
|
Status = nameof(VideoConferenceStatus.New),
|
|
LawyerVideoConferenceParticipant_UID = Guid.CreateVersion7(DateTime.UtcNow),
|
|
ParticipantID = transactionEntity.PrincipalID,
|
|
});
|
|
|
|
schedule.MeetingDate = DateTime.UtcNow;
|
|
schedule.LawyerVideoConferenceParticipants = participants.ToList();
|
|
return schedule;
|
|
}
|
|
|
|
private async Task<string> GetTokenResponseAsync(Response<CommunicationUserIdentifier> user)
|
|
{
|
|
var tokenResponse = await _communicationIdentityClient.GetTokenAsync(user, new[] { CommunicationTokenScope.VoIP });
|
|
return tokenResponse.Value.Token;
|
|
}
|
|
|
|
private async Task<string> StartRecordingAsync(string serverCallID)
|
|
{
|
|
ArgumentException.ThrowIfNullOrWhiteSpace(serverCallID);
|
|
CallLocator callLocator = new ServerCallLocator(serverCallID);
|
|
var uri = _configuration.GetValue<string>("UriRecordingBlobContainer") ?? string.Empty;
|
|
var recordingResult = await _callAutomationClient
|
|
.GetCallRecording().StartAsync(new StartRecordingOptions(callLocator)
|
|
{
|
|
RecordingContent = RecordingContent.AudioVideo,
|
|
RecordingStorage = RecordingStorage.CreateAzureBlobContainerRecordingStorage(new Uri(uri)),
|
|
RecordingFormat = RecordingFormat.Mp4
|
|
});
|
|
return recordingResult.Value.RecordingId;
|
|
}
|
|
|
|
private async Task StopRecordingAsync(LawyerVideoConferenceSchedule schedule)
|
|
{
|
|
if (string.IsNullOrEmpty(schedule.ServerCallID))
|
|
{
|
|
Console.WriteLine("ServerCallID is not set for this transaction.");
|
|
return;
|
|
}
|
|
|
|
if (string.IsNullOrEmpty(schedule.RecordingID))
|
|
{
|
|
Console.WriteLine("Recording ID is not set for this transaction.");
|
|
return;
|
|
}
|
|
|
|
await _callAutomationClient.GetCallRecording().StopAsync(schedule.RecordingID);
|
|
}
|
|
}
|
|
} |