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> | ||||||
| @ -78,4 +75,10 @@ | |||||||
|             </div> |             </div> | ||||||
|         </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