one time password
This commit is contained in:
parent
1d29b6ef4d
commit
77c665f64e
@ -0,0 +1,9 @@
|
|||||||
|
namespace EnotaryoPH.Data.Constants
|
||||||
|
{
|
||||||
|
public enum DocumentSigningStatus
|
||||||
|
{
|
||||||
|
New = 0,
|
||||||
|
ReadyForSigning = 10,
|
||||||
|
Signed = 20
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,9 @@
|
|||||||
TransactionApproved = 41,
|
TransactionApproved = 41,
|
||||||
TransactionRejected = 42,
|
TransactionRejected = 42,
|
||||||
|
|
||||||
|
OTPSent = 45,
|
||||||
|
OTPVerified = 46,
|
||||||
|
|
||||||
VideoConferenceStarted = 50,
|
VideoConferenceStarted = 50,
|
||||||
VideoRecordingStarted = 51,
|
VideoRecordingStarted = 51,
|
||||||
VideoRecordingStopped = 52,
|
VideoRecordingStopped = 52,
|
||||||
|
@ -8,6 +8,15 @@ namespace EnotaryoPH.Data.Entities
|
|||||||
[Column("CreatedOn")]
|
[Column("CreatedOn")]
|
||||||
public DateTime? CreatedOn { get; set; }
|
public DateTime? CreatedOn { get; set; }
|
||||||
|
|
||||||
|
[Column("Device")]
|
||||||
|
public string? Device { get; set; }
|
||||||
|
|
||||||
|
[Column("IPAddress")]
|
||||||
|
public string? IPAddress { get; set; }
|
||||||
|
|
||||||
|
[Column("Latitude")]
|
||||||
|
public decimal? Latitude { get; set; }
|
||||||
|
|
||||||
[Column("LawyerVideoConferenceParticipant_UID")]
|
[Column("LawyerVideoConferenceParticipant_UID")]
|
||||||
public Guid? LawyerVideoConferenceParticipant_UID { get; set; }
|
public Guid? LawyerVideoConferenceParticipant_UID { get; set; }
|
||||||
|
|
||||||
@ -20,12 +29,21 @@ namespace EnotaryoPH.Data.Entities
|
|||||||
[Column("LawyerVideoConferenceScheduleID")]
|
[Column("LawyerVideoConferenceScheduleID")]
|
||||||
public int LawyerVideoConferenceScheduleID { get; set; }
|
public int LawyerVideoConferenceScheduleID { get; set; }
|
||||||
|
|
||||||
|
[Column("Longitude")]
|
||||||
|
public decimal? Longitude { get; set; }
|
||||||
|
|
||||||
[Column("MeetingRoomTokenID")]
|
[Column("MeetingRoomTokenID")]
|
||||||
public string? MeetingRoomTokenID { get; set; }
|
public string? MeetingRoomTokenID { get; set; }
|
||||||
|
|
||||||
[Column("MeetingRoomUserID")]
|
[Column("MeetingRoomUserID")]
|
||||||
public string? MeetingRoomUserID { get; set; }
|
public string? MeetingRoomUserID { get; set; }
|
||||||
|
|
||||||
|
[Column("OTPEntered")]
|
||||||
|
public string? OTPEntered { get; set; }
|
||||||
|
|
||||||
|
[Column("OTPHash")]
|
||||||
|
public string? OTPHash { get; set; }
|
||||||
|
|
||||||
[ForeignKey("ParticipantID")]
|
[ForeignKey("ParticipantID")]
|
||||||
public User Participant { get; set; }
|
public User Participant { get; set; }
|
||||||
|
|
||||||
|
@ -0,0 +1,8 @@
|
|||||||
|
namespace EnotaryoPH.Web.Common.Jobs.Models
|
||||||
|
{
|
||||||
|
public class OTPEmailModel
|
||||||
|
{
|
||||||
|
public Dictionary<Guid, string> ParticipantOTP { get; set; }
|
||||||
|
public Guid Transaction_UID { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,75 @@
|
|||||||
|
using Coravel.Invocable;
|
||||||
|
using Coravel.Mailer.Mail.Interfaces;
|
||||||
|
using EnotaryoPH.Data;
|
||||||
|
using EnotaryoPH.Web.Common.Jobs.Models;
|
||||||
|
using EnotaryoPH.Web.Common.Models;
|
||||||
|
using EnotaryoPH.Web.Mailables;
|
||||||
|
|
||||||
|
namespace EnotaryoPH.Web.Common.Jobs
|
||||||
|
{
|
||||||
|
public class OneTimePasswordInvocable : IInvocable, IInvocableWithPayload<OTPEmailModel>
|
||||||
|
{
|
||||||
|
private readonly IMailer _mailer;
|
||||||
|
private readonly NotaryoDBContext _notaryoDBContext;
|
||||||
|
private readonly Settings _settings;
|
||||||
|
private readonly IPasswordService _passwordService;
|
||||||
|
private readonly IEventService _eventService;
|
||||||
|
|
||||||
|
public OneTimePasswordInvocable(IMailer mailer, NotaryoDBContext notaryoDBContext, Settings settings, IPasswordService passwordService, IEventService eventService)
|
||||||
|
{
|
||||||
|
_mailer = mailer;
|
||||||
|
_notaryoDBContext = notaryoDBContext;
|
||||||
|
_settings = settings;
|
||||||
|
_passwordService = passwordService;
|
||||||
|
_eventService = eventService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public OTPEmailModel Payload { get; set; }
|
||||||
|
|
||||||
|
public async Task Invoke()
|
||||||
|
{
|
||||||
|
var schedule = _notaryoDBContext.LawyerVideoConferenceSchedules
|
||||||
|
.AsNoTracking()
|
||||||
|
.Include(sched => sched.Transaction)
|
||||||
|
.ThenInclude(transaction => transaction.TransactionDocument)
|
||||||
|
.Include(sched => sched.Transaction)
|
||||||
|
.ThenInclude(transaction => transaction.Lawyer)
|
||||||
|
.ThenInclude(lawyer => lawyer.User)
|
||||||
|
.Include(sched => sched.LawyerVideoConferenceParticipants)
|
||||||
|
.ThenInclude(participant => participant.Participant)
|
||||||
|
.FirstOrDefault(sched => sched.Transaction.Transaction_UID == Payload.Transaction_UID);
|
||||||
|
|
||||||
|
if (schedule == null)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var participantDic = schedule.LawyerVideoConferenceParticipants.ToDictionary(p => p.LawyerVideoConferenceParticipant_UID.GetValueOrDefault(), p => p);
|
||||||
|
foreach (var otp in Payload.ParticipantOTP)
|
||||||
|
{
|
||||||
|
if (!participantDic.ContainsKey(otp.Key))
|
||||||
|
{
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
var participant = participantDic[otp.Key];
|
||||||
|
|
||||||
|
var emailViewModel = new OneTimePasswordViewModel
|
||||||
|
{
|
||||||
|
DocumentType = schedule.Transaction.TransactionDocument.DocumentType,
|
||||||
|
Email = participant.Participant.Email,
|
||||||
|
OTP = otp.Value,
|
||||||
|
ParticipantName = participant.Participant.Fullname,
|
||||||
|
LawyerName = schedule.Transaction.Lawyer.User.Fullname,
|
||||||
|
MeetingRoomURL = $"{_settings.BaseUrl}/Participant/VideoCall/Room/{schedule.Transaction.Transaction_UID}",
|
||||||
|
};
|
||||||
|
await _mailer.SendAsync(new OneTimePasswordMailable(emailViewModel));
|
||||||
|
await _eventService.LogAsync(NotaryoEvent.OTPSent, Payload.Transaction_UID, new
|
||||||
|
{
|
||||||
|
emailViewModel.Email,
|
||||||
|
emailViewModel.ParticipantName,
|
||||||
|
emailViewModel.MeetingRoomURL
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,14 @@
|
|||||||
using Azure;
|
using System.Security.Cryptography;
|
||||||
|
using Azure;
|
||||||
using Azure.Communication;
|
using Azure.Communication;
|
||||||
using Azure.Communication.CallAutomation;
|
using Azure.Communication.CallAutomation;
|
||||||
using Azure.Communication.Identity;
|
using Azure.Communication.Identity;
|
||||||
using Azure.Communication.Rooms;
|
using Azure.Communication.Rooms;
|
||||||
|
using Coravel.Queuing.Interfaces;
|
||||||
using EnotaryoPH.Data;
|
using EnotaryoPH.Data;
|
||||||
using EnotaryoPH.Data.Entities;
|
using EnotaryoPH.Data.Entities;
|
||||||
|
using EnotaryoPH.Web.Common.Jobs;
|
||||||
|
using EnotaryoPH.Web.Common.Jobs.Models;
|
||||||
|
|
||||||
namespace EnotaryoPH.Web.Common.Services
|
namespace EnotaryoPH.Web.Common.Services
|
||||||
{
|
{
|
||||||
@ -17,9 +21,11 @@ namespace EnotaryoPH.Web.Common.Services
|
|||||||
private readonly IConfiguration _configuration;
|
private readonly IConfiguration _configuration;
|
||||||
private readonly NotaryoDBContext _dbContext;
|
private readonly NotaryoDBContext _dbContext;
|
||||||
private readonly IEventService _eventService;
|
private readonly IEventService _eventService;
|
||||||
|
private readonly IPasswordService _passwordService;
|
||||||
|
private readonly IQueue _queue;
|
||||||
private readonly RoomsClient _roomsClient;
|
private readonly RoomsClient _roomsClient;
|
||||||
|
|
||||||
public VideoConferenceService(NotaryoDBContext dbContext, CommunicationIdentityClient communicationIdentityClient, RoomsClient roomsClient, CallAutomationClient callAutomationClient, IConfiguration configuration, IEventService eventService)
|
public VideoConferenceService(NotaryoDBContext dbContext, CommunicationIdentityClient communicationIdentityClient, RoomsClient roomsClient, CallAutomationClient callAutomationClient, IConfiguration configuration, IEventService eventService, IPasswordService passwordService, IQueue queue)
|
||||||
{
|
{
|
||||||
_dbContext = dbContext;
|
_dbContext = dbContext;
|
||||||
_communicationIdentityClient = communicationIdentityClient;
|
_communicationIdentityClient = communicationIdentityClient;
|
||||||
@ -27,6 +33,8 @@ namespace EnotaryoPH.Web.Common.Services
|
|||||||
_callAutomationClient = callAutomationClient;
|
_callAutomationClient = callAutomationClient;
|
||||||
_configuration = configuration;
|
_configuration = configuration;
|
||||||
_eventService = eventService;
|
_eventService = eventService;
|
||||||
|
_passwordService = passwordService;
|
||||||
|
_queue = queue;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ApproveTransactionAsync(Guid transaction_UID)
|
public async Task ApproveTransactionAsync(Guid transaction_UID)
|
||||||
@ -128,6 +136,7 @@ namespace EnotaryoPH.Web.Common.Services
|
|||||||
if (schedule.Status == nameof(VideoConferenceStatus.New))
|
if (schedule.Status == nameof(VideoConferenceStatus.New))
|
||||||
{
|
{
|
||||||
await CreateMeetingRoomAsync(schedule);
|
await CreateMeetingRoomAsync(schedule);
|
||||||
|
await CreateAndEmailOTPAsync(schedule);
|
||||||
_dbContext.UpdateOrCreate(schedule);
|
_dbContext.UpdateOrCreate(schedule);
|
||||||
_dbContext.SaveChanges();
|
_dbContext.SaveChanges();
|
||||||
}
|
}
|
||||||
@ -154,6 +163,23 @@ namespace EnotaryoPH.Web.Common.Services
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task CreateAndEmailOTPAsync(LawyerVideoConferenceSchedule schedule)
|
||||||
|
{
|
||||||
|
var participantOTP = new Dictionary<Guid, string>();
|
||||||
|
const string validChars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
|
||||||
|
foreach (var participant in schedule.LawyerVideoConferenceParticipants)
|
||||||
|
{
|
||||||
|
var otp = RandomNumberGenerator.GetString(validChars, 5);
|
||||||
|
participant.OTPHash = _passwordService.HashPassword(otp);
|
||||||
|
participantOTP.Add(participant.LawyerVideoConferenceParticipant_UID.GetValueOrDefault(), otp);
|
||||||
|
}
|
||||||
|
_queue.QueueInvocableWithPayload<OneTimePasswordInvocable, OTPEmailModel>(new OTPEmailModel
|
||||||
|
{
|
||||||
|
ParticipantOTP = participantOTP,
|
||||||
|
Transaction_UID = schedule.Transaction.Transaction_UID.GetValueOrDefault()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async Task CreateMeetingRoomAsync(LawyerVideoConferenceSchedule schedule)
|
private async Task CreateMeetingRoomAsync(LawyerVideoConferenceSchedule schedule)
|
||||||
{
|
{
|
||||||
var roomParticipants = new List<RoomParticipant>();
|
var roomParticipants = new List<RoomParticipant>();
|
||||||
@ -181,7 +207,9 @@ namespace EnotaryoPH.Web.Common.Services
|
|||||||
.Include(t => t.TransactionSignatories)
|
.Include(t => t.TransactionSignatories)
|
||||||
.Single(t => t.Transaction_UID == transaction_UID);
|
.Single(t => t.Transaction_UID == transaction_UID);
|
||||||
|
|
||||||
var schedule = _dbContext.LawyerVideoConferenceSchedules.FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID);
|
var schedule = _dbContext.LawyerVideoConferenceSchedules
|
||||||
|
.Include(sched => sched.Transaction)
|
||||||
|
.FirstOrDefault(sched => sched.TransactionID == transactionEntity.TransactionID);
|
||||||
if (schedule != null)
|
if (schedule != null)
|
||||||
{
|
{
|
||||||
return schedule;
|
return schedule;
|
||||||
@ -200,7 +228,7 @@ namespace EnotaryoPH.Web.Common.Services
|
|||||||
CreatedOn = DateTime.UtcNow,
|
CreatedOn = DateTime.UtcNow,
|
||||||
Status = nameof(VideoConferenceStatus.New),
|
Status = nameof(VideoConferenceStatus.New),
|
||||||
LawyerVideoConferenceParticipant_UID = Guid.CreateVersion7(DateTime.UtcNow),
|
LawyerVideoConferenceParticipant_UID = Guid.CreateVersion7(DateTime.UtcNow),
|
||||||
ParticipantID = signatory.UserID,
|
ParticipantID = signatory.UserID
|
||||||
});
|
});
|
||||||
participants.Add(new LawyerVideoConferenceParticipant
|
participants.Add(new LawyerVideoConferenceParticipant
|
||||||
{
|
{
|
||||||
@ -212,6 +240,7 @@ namespace EnotaryoPH.Web.Common.Services
|
|||||||
|
|
||||||
schedule.MeetingDate = DateTime.UtcNow;
|
schedule.MeetingDate = DateTime.UtcNow;
|
||||||
schedule.LawyerVideoConferenceParticipants = participants.ToList();
|
schedule.LawyerVideoConferenceParticipants = participants.ToList();
|
||||||
|
schedule.Transaction = transactionEntity;
|
||||||
return schedule;
|
return schedule;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,16 @@
|
|||||||
|
using Coravel.Mailer.Mail;
|
||||||
|
|
||||||
|
namespace EnotaryoPH.Web.Mailables
|
||||||
|
{
|
||||||
|
public class OneTimePasswordMailable : Mailable<OneTimePasswordViewModel>
|
||||||
|
{
|
||||||
|
private readonly OneTimePasswordViewModel _model;
|
||||||
|
|
||||||
|
public OneTimePasswordMailable(OneTimePasswordViewModel model) => _model = model;
|
||||||
|
|
||||||
|
public override void Build() => this
|
||||||
|
.To(_model.Email)
|
||||||
|
.From("noreply@enotaryoph.com")
|
||||||
|
.View("~/Views/Mail/OneTimePassword.cshtml", _model);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,12 @@
|
|||||||
|
namespace EnotaryoPH.Web.Mailables
|
||||||
|
{
|
||||||
|
public class OneTimePasswordViewModel
|
||||||
|
{
|
||||||
|
public string DocumentType { get; set; }
|
||||||
|
public string Email { get; set; }
|
||||||
|
public string LawyerName { get; set; }
|
||||||
|
public string MeetingRoomURL { get; set; }
|
||||||
|
public string OTP { get; set; }
|
||||||
|
public string ParticipantName { get; set; }
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,6 @@
|
|||||||
<div class="sidemenu">
|
<div class="sidemenu">
|
||||||
<div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#available-transactions"><i class="far fa-check-circle fs-4 text-success sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">Available</span></a></div>
|
<div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#available-transactions"><i class="far fa-check-circle fs-4 text-success sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">Available</span></a></div>
|
||||||
<div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#incomplete-docs"><i class="far fa-clock fs-4 text-warning sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">My Jobs</span></a></div>
|
<div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#incomplete-docs"><i class="far fa-clock fs-4 text-warning sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">My Jobs</span></a></div>
|
||||||
<div class="align-items-center sidemenu__menuitem"><a class="d-flex flex-grow-1 justify-content-center align-items-center justify-content-md-start p-1 text-decoration-none" href="#identification-docs"><i class="far fa-address-card fs-4 text-dark sidemenu__menuitem__icon" style="padding: 5px;"></i><span class="d-none d-md-inline-block ms-1 sidemenu__menuitem__text">Identification Docs</span></a></div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col g-0 mx-2">
|
<div class="col g-0 mx-2">
|
||||||
@ -27,7 +26,6 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<th>Type</th>
|
<th>Type</th>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Status</th>
|
|
||||||
<th>Link</th>
|
<th>Link</th>
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
@ -37,8 +35,7 @@
|
|||||||
<tr>
|
<tr>
|
||||||
<td>@transaction.Type</td>
|
<td>@transaction.Type</td>
|
||||||
<td>@transaction.Date.ToString("g")</td>
|
<td>@transaction.Date.ToString("g")</td>
|
||||||
<td>@transaction.Status</td>
|
<td><a href="@transaction.Link">View</a></td>
|
||||||
<td><a href="@transaction.Link">Link</a></td>
|
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -67,7 +64,7 @@
|
|||||||
<td>@transaction.Type</td>
|
<td>@transaction.Type</td>
|
||||||
<td>@transaction.Date.ToShortDateString()</td>
|
<td>@transaction.Date.ToShortDateString()</td>
|
||||||
<td>@transaction.Status</td>
|
<td>@transaction.Status</td>
|
||||||
<td><a href="@transaction.Link">Link</a></td>
|
<td><a href="@transaction.Link">View</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
}
|
}
|
||||||
</tbody>
|
</tbody>
|
||||||
@ -79,3 +76,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
|
||||||
|
@section Scripts {
|
||||||
|
<script src="~/lib/fontawesome-free-6.7.1-web/js/all.min.js"></script>
|
||||||
|
|
||||||
|
}
|
@ -51,6 +51,8 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
|||||||
return Redirect("/");
|
return Redirect("/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
DocumentType = _Transaction.TransactionDocument.DocumentType;
|
||||||
|
|
||||||
var schedule_UID = await _videoConferenceService.StartAsync(Transaction_UID);
|
var schedule_UID = await _videoConferenceService.StartAsync(Transaction_UID);
|
||||||
|
|
||||||
CommunicationUserToken = currentUser.Role == nameof(UserType.Notary)
|
CommunicationUserToken = currentUser.Role == nameof(UserType.Notary)
|
||||||
@ -70,7 +72,58 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
|||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
private LawyerVideoConferenceParticipant GetParticipant(User currentUser) => _Transaction.Schedule.LawyerVideoConferenceParticipants.First(u => u.ParticipantID == currentUser.UserID);
|
public IActionResult OnGetDocument()
|
||||||
|
{
|
||||||
|
var document = _dbContext.TransactionDocuments
|
||||||
|
.Include(doc => doc.Transaction)
|
||||||
|
.FirstOrDefault(doc => doc.Transaction.Transaction_UID == Transaction_UID);
|
||||||
|
|
||||||
|
return new FileContentResult(document.File, "application/pdf");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult OnGetIdentificationDocument(string meetingRoomUserID)
|
||||||
|
{
|
||||||
|
var participant = _dbContext.LawyerVideoConferenceParticipants
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefault(participant => participant.MeetingRoomUserID == meetingRoomUserID);
|
||||||
|
if (participant == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
var identificationDocumentID = _dbContext.TransactionSelfies.AsNoTracking()
|
||||||
|
.Where(selfie => selfie.UserID == participant.ParticipantID && selfie.Transaction.Transaction_UID == Transaction_UID)
|
||||||
|
.Select(selfie => selfie.IdentificationDocumentID).FirstOrDefault();
|
||||||
|
if (identificationDocumentID == 0)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
var identificationDocument = _dbContext.IdentificationDocuments.FirstOrDefault(id => id.IdentificationDocumentID == identificationDocumentID);
|
||||||
|
if (identificationDocument == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileContentResult(identificationDocument.File, "image/jpeg");
|
||||||
|
}
|
||||||
|
|
||||||
|
public IActionResult OnGetSelfieImage(string meetingRoomUserID)
|
||||||
|
{
|
||||||
|
var participant = _dbContext.LawyerVideoConferenceParticipants
|
||||||
|
.AsNoTracking()
|
||||||
|
.FirstOrDefault(participant => participant.MeetingRoomUserID == meetingRoomUserID);
|
||||||
|
if (participant == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
var transactionSelfie = _dbContext.TransactionSelfies.FirstOrDefault(selfie => selfie.UserID == participant.ParticipantID && selfie.Transaction.Transaction_UID == Transaction_UID);
|
||||||
|
if (transactionSelfie == null)
|
||||||
|
{
|
||||||
|
return NotFound();
|
||||||
|
}
|
||||||
|
|
||||||
|
return new FileContentResult(transactionSelfie.File, "image/jpeg");
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<JsonResult> OnPostApproveAsync()
|
public async Task<JsonResult> OnPostApproveAsync()
|
||||||
{
|
{
|
||||||
@ -85,6 +138,8 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
|||||||
return new JsonResult(true);
|
return new JsonResult(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private LawyerVideoConferenceParticipant GetParticipant(User currentUser) => _Transaction.Schedule.LawyerVideoConferenceParticipants.First(u => u.ParticipantID == currentUser.UserID);
|
||||||
|
|
||||||
private void LoadTransaction() => _Transaction = _dbContext.Transactions
|
private void LoadTransaction() => _Transaction = _dbContext.Transactions
|
||||||
.Include(t => t.TransactionSignatories)
|
.Include(t => t.TransactionSignatories)
|
||||||
.Include(t => t.Lawyer)
|
.Include(t => t.Lawyer)
|
||||||
@ -92,6 +147,7 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
|||||||
.Include(t => t.Principal)
|
.Include(t => t.Principal)
|
||||||
.Include(t => t.Schedule)
|
.Include(t => t.Schedule)
|
||||||
.ThenInclude(sch => sch.LawyerVideoConferenceParticipants)
|
.ThenInclude(sch => sch.LawyerVideoConferenceParticipants)
|
||||||
|
.Include(t => t.TransactionDocument)
|
||||||
.FirstOrDefault(t => t.Transaction_UID == Transaction_UID);
|
.FirstOrDefault(t => t.Transaction_UID == Transaction_UID);
|
||||||
|
|
||||||
public string CommunicationRoomId { get; private set; }
|
public string CommunicationRoomId { get; private set; }
|
||||||
@ -102,7 +158,7 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
|||||||
|
|
||||||
public string DisplayName { get; private set; }
|
public string DisplayName { get; private set; }
|
||||||
|
|
||||||
public string ParticipantType { get; set; }
|
public string DocumentType { get; set; }
|
||||||
|
|
||||||
public List<RoomParticipantViewModel> Participants
|
public List<RoomParticipantViewModel> Participants
|
||||||
{
|
{
|
||||||
@ -134,52 +190,9 @@ namespace EnotaryoPH.Web.Pages.Participant.VideoCall
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string ParticipantType { get; set; }
|
||||||
[BindProperty(SupportsGet = true)] public string ServerCallID { get; set; }
|
[BindProperty(SupportsGet = true)] public string ServerCallID { get; set; }
|
||||||
|
|
||||||
[BindProperty(SupportsGet = true)] public Guid Transaction_UID { get; set; }
|
[BindProperty(SupportsGet = true)] public Guid Transaction_UID { get; set; }
|
||||||
|
|
||||||
public IActionResult OnGetSelfieImage(string meetingRoomUserID)
|
|
||||||
{
|
|
||||||
var participant = _dbContext.LawyerVideoConferenceParticipants
|
|
||||||
.AsNoTracking()
|
|
||||||
.FirstOrDefault(participant => participant.MeetingRoomUserID == meetingRoomUserID);
|
|
||||||
if (participant == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
var transactionSelfie = _dbContext.TransactionSelfies.FirstOrDefault(selfie => selfie.UserID == participant.ParticipantID && selfie.Transaction.Transaction_UID == Transaction_UID);
|
|
||||||
if (transactionSelfie == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FileContentResult(transactionSelfie.File, "image/jpeg");
|
|
||||||
}
|
|
||||||
|
|
||||||
public IActionResult OnGetIdentificationDocument(string meetingRoomUserID)
|
|
||||||
{
|
|
||||||
var participant = _dbContext.LawyerVideoConferenceParticipants
|
|
||||||
.AsNoTracking()
|
|
||||||
.FirstOrDefault(participant => participant.MeetingRoomUserID == meetingRoomUserID);
|
|
||||||
if (participant == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
var identificationDocumentID = _dbContext.TransactionSelfies.AsNoTracking()
|
|
||||||
.Where(selfie => selfie.UserID == participant.ParticipantID && selfie.Transaction.Transaction_UID == Transaction_UID)
|
|
||||||
.Select(selfie => selfie.IdentificationDocumentID).FirstOrDefault();
|
|
||||||
if (identificationDocumentID == 0)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
var identificationDocument = _dbContext.IdentificationDocuments.FirstOrDefault(id => id.IdentificationDocumentID == identificationDocumentID);
|
|
||||||
if (identificationDocument == null)
|
|
||||||
{
|
|
||||||
return NotFound();
|
|
||||||
}
|
|
||||||
|
|
||||||
return new FileContentResult(identificationDocument.File, "image/jpeg");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -6,21 +6,22 @@
|
|||||||
control_communicationUserId = document.getElementById("CommunicationUserId"),
|
control_communicationUserId = document.getElementById("CommunicationUserId"),
|
||||||
control_communicationUserToken = document.getElementById("CommunicationUserToken"),
|
control_communicationUserToken = document.getElementById("CommunicationUserToken"),
|
||||||
control_displayName = document.getElementById("DisplayName"),
|
control_displayName = document.getElementById("DisplayName"),
|
||||||
control_participants = document.getElementById("Participants"),
|
|
||||||
control_reject = document.getElementById("Reject"),
|
|
||||||
control_templateSidePane = document.getElementById("TemplateSidePane"),
|
|
||||||
control_templateParticipantItem = document.getElementById("TemplateParticipantItem"),
|
|
||||||
control_draggableModal = document.getElementById("DraggableModal"),
|
control_draggableModal = document.getElementById("DraggableModal"),
|
||||||
|
control_identificationImage = document.getElementById("IdentificationImage"),
|
||||||
|
control_otpForm = document.getElementById("OtpForm"),
|
||||||
|
control_participantListGroup = document.getElementById("ParticipantListGroup"),
|
||||||
|
control_participants = document.getElementById("Participants"),
|
||||||
|
control_participantType = document.getElementById("ParticipantType"),
|
||||||
|
control_pdfViewer = document.getElementById("PdfViewer"),
|
||||||
|
control_reject = document.getElementById("Reject"),
|
||||||
control_rightSidebarModal = document.getElementById("RightSidebarModal"),
|
control_rightSidebarModal = document.getElementById("RightSidebarModal"),
|
||||||
/*control_videoGrid = document.getElementById("VideoGrid"),*/
|
control_selfieImage = document.getElementById("SelfieImage"),
|
||||||
|
control_serverCallIID = document.getElementById("ServerCallID"),
|
||||||
|
control_signatoryName = document.getElementById("SignatoryName"),
|
||||||
|
control_templateParticipantItem = document.getElementById("TemplateParticipantItem"),
|
||||||
|
control_templateSidePane = document.getElementById("TemplateSidePane"),
|
||||||
control_videoGridContainer = document.getElementById("videoGrid-container"),
|
control_videoGridContainer = document.getElementById("videoGrid-container"),
|
||||||
control_viewDocument = document.getElementById("ViewDocument"),
|
control_viewDocument = document.getElementById("ViewDocument"),
|
||||||
control_serverCallIID = document.getElementById("ServerCallID"),
|
|
||||||
control_participantType = document.getElementById("ParticipantType"),
|
|
||||||
control_participantListGroup = document.getElementById("ParticipantListGroup"),
|
|
||||||
control_selfieImage = document.getElementById("SelfieImage"),
|
|
||||||
control_identificationImage = document.getElementById("IdentificationImage"),
|
|
||||||
control_signatoryName = document.getElementById("SignatoryName"),
|
|
||||||
x = 1;
|
x = 1;
|
||||||
|
|
||||||
let participants = JSON.parse(control_participants.value);
|
let participants = JSON.parse(control_participants.value);
|
||||||
@ -40,7 +41,21 @@
|
|||||||
tooltipContent: 'View the document.'
|
tooltipContent: 'View the document.'
|
||||||
},
|
},
|
||||||
onItemClick: function () {
|
onItemClick: function () {
|
||||||
alert('Document Modal goes here.');
|
const modal = bootstrap.Modal.getOrCreateInstance(control_pdfViewer);
|
||||||
|
modal.show();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
function (args) {
|
||||||
|
return {
|
||||||
|
placement: 'primary',
|
||||||
|
strings: {
|
||||||
|
label: 'OTP',
|
||||||
|
tooltipContent: 'Enter your OTP'
|
||||||
|
},
|
||||||
|
onItemClick: function () {
|
||||||
|
const modal = bootstrap.Modal.getOrCreateInstance(control_otpForm);
|
||||||
|
modal.show();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -180,56 +195,16 @@
|
|||||||
}
|
}
|
||||||
|
|
||||||
function approveTransaction(e) {
|
function approveTransaction(e) {
|
||||||
jfa.communication.videocall.stopCall(true);
|
|
||||||
let url = jfa.utilities.routing.getCurrentURLWithHandler("Approve");
|
let url = jfa.utilities.routing.getCurrentURLWithHandler("Approve");
|
||||||
jfa.utilities.request.post(url, {})
|
jfa.utilities.request.post(url, {})
|
||||||
.then(resp => {
|
.then(resp => {
|
||||||
if (resp.ok === true) {
|
if (resp.ok === true) {
|
||||||
debugger;
|
jfa.communication.videocall.stopCall(true);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch(err => console.error(err));
|
.catch(err => console.error(err));
|
||||||
}
|
}
|
||||||
|
|
||||||
//function _updateGrid() {
|
|
||||||
// control_videoGrid.innerHTML = '';
|
|
||||||
|
|
||||||
// participants.forEach((participant, index) => {
|
|
||||||
// const col = document.createElement('div');
|
|
||||||
// col.className = 'participant-col';
|
|
||||||
// const tmpl = control_templateVideo.cloneNode(true).content;
|
|
||||||
// const vidcontainer = tmpl.querySelector(".video-container")
|
|
||||||
// if (vidcontainer) {
|
|
||||||
// vidcontainer.id = participant.Id;
|
|
||||||
// vidcontainer.classList.add(participant.Id);
|
|
||||||
// vidcontainer.classList.add(participant.Type == 'Notary' ? 'local-video-container' : 'remote-video-container');
|
|
||||||
// }
|
|
||||||
// let participantName = tmpl.querySelector(".participant-name")
|
|
||||||
// if (participantName) {
|
|
||||||
// participantName.textContent = participant.DisplayName;
|
|
||||||
// }
|
|
||||||
// col.appendChild(tmpl);
|
|
||||||
// control_videoGrid.appendChild(col);
|
|
||||||
// });
|
|
||||||
|
|
||||||
// // Dynamically adjust grid columns based on participant count
|
|
||||||
// const count = participants.length;
|
|
||||||
// const cols = count <= 2 ? 'col-12 col-sm-12 offset-md-1 col-md-10 offset-lg-0 col-lg-6' :
|
|
||||||
// count <= 4 ? 'col-6 col-sm-6 col-md-6 col-lg-6 col-xl-6' :
|
|
||||||
// count <= 8 ? 'col-6 col-md-6 col-lg-4 col-xl-4' :
|
|
||||||
// count <= 9 ? 'col-4' :
|
|
||||||
// 'col-6 col-sm-4 col-lg-3';
|
|
||||||
|
|
||||||
// document.querySelectorAll('.participant-col').forEach(el => {
|
|
||||||
// el.className = `participant-col ${cols}`;
|
|
||||||
// });
|
|
||||||
|
|
||||||
// const fluid = count <= 2 ? 'container-fluid' :
|
|
||||||
// count <= 8 ? 'container-xxl' :
|
|
||||||
// 'container-xl'
|
|
||||||
// control_videoGridContainer.className = `${fluid} py-3`;
|
|
||||||
//}
|
|
||||||
|
|
||||||
function _createParticipantListItems() {
|
function _createParticipantListItems() {
|
||||||
control_participantListGroup.innerHTML = '';
|
control_participantListGroup.innerHTML = '';
|
||||||
participants.forEach(participant => {
|
participants.forEach(participant => {
|
||||||
@ -250,7 +225,6 @@
|
|||||||
control_participantListGroup.addEventListener('click', function (event) {
|
control_participantListGroup.addEventListener('click', function (event) {
|
||||||
let target = event.target?.closest('.list-group-item');
|
let target = event.target?.closest('.list-group-item');
|
||||||
if (target) {
|
if (target) {
|
||||||
|
|
||||||
const participant = participants.find(p => p.UID == target.dataset.participantUid);
|
const participant = participants.find(p => p.UID == target.dataset.participantUid);
|
||||||
if (!participant) {
|
if (!participant) {
|
||||||
return;
|
return;
|
||||||
@ -260,7 +234,6 @@
|
|||||||
let selfieUrl = jfa.utilities.routing.getCurrentURLWithHandler("SelfieImage");
|
let selfieUrl = jfa.utilities.routing.getCurrentURLWithHandler("SelfieImage");
|
||||||
selfieUrl.searchParams.append("meetingRoomUserID", participant.RoomUserID);
|
selfieUrl.searchParams.append("meetingRoomUserID", participant.RoomUserID);
|
||||||
|
|
||||||
|
|
||||||
let identificationUrl = jfa.utilities.routing.getCurrentURLWithHandler("IdentificationDocument");
|
let identificationUrl = jfa.utilities.routing.getCurrentURLWithHandler("IdentificationDocument");
|
||||||
identificationUrl.searchParams.append("meetingRoomUserID", participant.RoomUserID);
|
identificationUrl.searchParams.append("meetingRoomUserID", participant.RoomUserID);
|
||||||
control_selfieImage.src = selfieUrl;
|
control_selfieImage.src = selfieUrl;
|
||||||
@ -304,9 +277,7 @@
|
|||||||
|
|
||||||
async function _init() {
|
async function _init() {
|
||||||
_bindEvents();
|
_bindEvents();
|
||||||
//_updateGrid();
|
|
||||||
await _initVideoCall();
|
await _initVideoCall();
|
||||||
debugger;
|
|
||||||
_createParticipantListItems();
|
_createParticipantListItems();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,6 @@ namespace EnotaryoPH.Web.Pages.Principal.TransactionStatus
|
|||||||
|
|
||||||
Title = transaction.TransactionDocument.DocumentType;
|
Title = transaction.TransactionDocument.DocumentType;
|
||||||
StartedOn = transaction.CreatedOn.GetValueOrDefault();
|
StartedOn = transaction.CreatedOn.GetValueOrDefault();
|
||||||
StatusDescription = "Not all signatories have signed up to enotaryo and completed the onboarding process. This transaction cannot proceed until this has been resolved.";
|
|
||||||
|
|
||||||
Signatories = transaction.TransactionSignatories.ConvertAll(s => new SignatoryViewModel
|
Signatories = transaction.TransactionSignatories.ConvertAll(s => new SignatoryViewModel
|
||||||
{
|
{
|
||||||
@ -41,6 +40,10 @@ namespace EnotaryoPH.Web.Pages.Principal.TransactionStatus
|
|||||||
Status = s.Status
|
Status = s.Status
|
||||||
});
|
});
|
||||||
|
|
||||||
|
StatusDescription = Signatories.Count > 1
|
||||||
|
? "Not all signatories have signed up to enotaryo and completed the onboarding process. This transaction cannot proceed until this has been resolved."
|
||||||
|
: "Please wait while our Notary Public team reviews your document.";
|
||||||
|
|
||||||
return Page();
|
return Page();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -64,6 +64,7 @@ namespace EnotaryoPH.Web
|
|||||||
builder.Services.AddScheduler();
|
builder.Services.AddScheduler();
|
||||||
builder.Services.AddMailer(config);
|
builder.Services.AddMailer(config);
|
||||||
builder.Services.AddTransient<SignatoryInvitationInvocable>();
|
builder.Services.AddTransient<SignatoryInvitationInvocable>();
|
||||||
|
builder.Services.AddTransient<OneTimePasswordInvocable>();
|
||||||
builder.Services.AddTransient<CheckRecordingAvailabilityInvocable>();
|
builder.Services.AddTransient<CheckRecordingAvailabilityInvocable>();
|
||||||
builder.Services.AddTransient<IVideoConferenceService, VideoConferenceService>();
|
builder.Services.AddTransient<IVideoConferenceService, VideoConferenceService>();
|
||||||
builder.Services.AddTransient<IEventService, EventService>();
|
builder.Services.AddTransient<IEventService, EventService>();
|
||||||
|
25
EnotaryoPH/EnotaryoPH.Web/Views/Mail/OneTimePassword.cshtml
Normal file
25
EnotaryoPH/EnotaryoPH.Web/Views/Mail/OneTimePassword.cshtml
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
@using EnotaryoPH.Web.Mailables
|
||||||
|
@model OneTimePasswordViewModel
|
||||||
|
@{
|
||||||
|
ViewBag.Heading = "One Time Password";
|
||||||
|
ViewBag.Preview = "Your OTP";
|
||||||
|
}
|
||||||
|
|
||||||
|
<p>
|
||||||
|
Hello @Model.ParticipantName,
|
||||||
|
|
||||||
|
This is your one time password:
|
||||||
|
|
||||||
|
<blockquote>
|
||||||
|
@Model.OTP
|
||||||
|
</blockquote>
|
||||||
|
|
||||||
|
Please do not share this code with anyone other than the Notary Public, Atty. @Model.LawyerName during the Video Conference.
|
||||||
|
|
||||||
|
You can join the e-Notaryo video conference by clicking this button: @await Component.InvokeAsync("EmailLinkButton", new { text = "Video Conference", url = Model.MeetingRoomURL })
|
||||||
|
</p>
|
||||||
|
|
||||||
|
@section links
|
||||||
|
{
|
||||||
|
<a href="@Model.MeetingRoomURL">Meeting Room Page</a>
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user