take selfie

This commit is contained in:
jojo aquino 2024-12-27 07:33:09 +00:00
parent 6f836c2449
commit 5096f79f99
11 changed files with 395 additions and 6 deletions

View File

@ -4,9 +4,11 @@
<TargetFramework>net9.0</TargetFramework> <TargetFramework>net9.0</TargetFramework>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<UserSecretsId>4082ec67-5a76-4239-b16a-4ab16e73b32a</UserSecretsId>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CompreFace.NET.Sdk" Version="1.0.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0" /> <PackageReference Include="Microsoft.AspNetCore.Mvc.Razor.RuntimeCompilation" Version="9.0.0" />
</ItemGroup> </ItemGroup>

View File

@ -0,0 +1,71 @@
@page
@using EnotaryoPH.Web.Pages.Shared.Components.NotaryoSteps
@model EnotaryoPH.Web.Pages.Principal.NotaryoSteps.TakeSelfieModel
@{
}
@section Head {
<link href="\lib\fontawesome-free-6.7.1-web\css\all.min.css" rel="stylesheet" />
<style>
#Photo {
height: 500px;
}
</style>
}
<section class="my-5">
<div class="container">
<div class="row">
<div class="col">
<h1>Notaryo Steps</h1>
<div class="mt-3">
@await Component.InvokeAsync("NotaryoSteps", new
{
NotaryoSteps = new List<NotaryoStep>() {
new NotaryoStep { Name = "Upload Identification", Step = 1 },
new NotaryoStep { Name = "Take Selfie", Step = 2, IsActive = true },
new NotaryoStep { Name = "Upload Document", Step = 3 },
new NotaryoStep { Name = "Choose Notary", Step = 4 },
}})
<div class="tab-content mt-3"></div>
</div>
<form class="mt-3" method="post">
@Html.ValidationSummary()
<div id="VideoContainer" class="bg-light">
<video id="Video" height="500" class="d-block mx-auto">Video stream not available.</video>
</div>
<div class="d-none">
<canvas id="Canvas"></canvas>
</div>
<div id="PhotoContainer" class="photo__container">
<div class="bg-light">
<img id="Photo" class="d-block mx-auto img img-fluid" alt="The screen capture will appear in this box.">
</div>
</div>
<div class="d-flex mt-3">
<button type="button" class="btn btn-secondary btn-lg me-1" id="TakePictureButton"><i class="fas fa-camera me-2"></i>TAKE PICTURE</button>
<button type="button" class="btn btn-secondary btn-lg" id="AgainButton"><i class="fas fa-redo-alt me-2"></i>AGAIN</button>
<div class="flex-grow-1"></div>
<button type="submit" class="btn btn-primary btn-lg" id="NextButton">NEXT<i class="fas fa-chevron-right ms-2"></i></button>
</div>
<input type="hidden" asp-for="SelfieBase64Image" />
</form>
</div>
</div>
</div>
</section>
@section Scripts {
<script src="~/lib/jquery/dist/jquery.min.js"></script>
<partial name="_ValidationScriptsPartial" />
<script src="\dist\js\jfa.min.js"></script>
<script src="~/Pages/Principal/NotaryoSteps/TakeSelfie.cshtml.js" asp-append-version="true"></script>
}

View File

@ -0,0 +1,167 @@
using System.ComponentModel.DataAnnotations;
using EnotaryoPH.Data;
using EnotaryoPH.Data.Constants;
using EnotaryoPH.Data.Entities;
using Exadel.Compreface.Clients.CompreFaceClient;
using Exadel.Compreface.DTOs.FaceVerificationDTOs.FaceVerification;
using Exadel.Compreface.Services;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace EnotaryoPH.Web.Pages.Principal.NotaryoSteps
{
public class TakeSelfieModel : PageModel
{
private readonly NotaryoDBContext _notaryoDBContext;
private readonly ICurrentUserService _currentUserService;
private readonly ICompreFaceClient _compreFaceClient;
private readonly IConfiguration _configuration;
public TakeSelfieModel(NotaryoDBContext notaryoDBContext, ICurrentUserService currentUserService, ICompreFaceClient compreFaceClient, IConfiguration configuration)
{
_notaryoDBContext = notaryoDBContext;
_currentUserService = currentUserService;
_compreFaceClient = compreFaceClient;
_configuration = configuration;
}
public void OnGet()
{
}
public async Task<IActionResult> OnPostAsync()
{
if (!ModelState.IsValid)
{
return Page();
}
var user = _notaryoDBContext.Users.FirstOrDefault(u => u.User_UID == _currentUserService.GetUser_UID());
TransactionSelfie selfieEntity;
if (TransactionSelfie_UID == Guid.Empty)
{
selfieEntity = new TransactionSelfie
{
CreatedOn = DateTime.UtcNow,
TransactionSelfie_UID = Guid.NewGuid(),
UserID = user.UserID
};
TransactionSelfie_UID = selfieEntity.TransactionSelfie_UID.Value;
}
else
{
selfieEntity = _notaryoDBContext.TransactionSelfies.FirstOrDefault(e => e.TransactionSelfie_UID == TransactionSelfie_UID);
}
selfieEntity.File = Convert.FromBase64String(SelfieBase64Image.Split(",")[1]);
if (selfieEntity.TransactionID > 0)
{
_notaryoDBContext.TransactionSelfies.Update(selfieEntity);
}
else
{
_notaryoDBContext.TransactionSelfies.Add(selfieEntity);
}
_notaryoDBContext.SaveChanges();
var identificationDocuments = _notaryoDBContext.IdentificationDocuments.Where(e => e.UserID == user.UserID);
var identificationDocument = identificationDocuments.First();
var selfiePath = System.IO.Path.Combine(System.IO.Path.GetTempPath(), "Selfies");
if (!System.IO.Directory.Exists(selfiePath))
{
System.IO.Directory.CreateDirectory(selfiePath);
}
var identificationDocumentPath = System.IO.Path.Combine(selfiePath, $"{identificationDocument.IdentificationDocument_UID}.jpg");
var selfieImagePath = System.IO.Path.Combine(selfiePath, $"{TransactionSelfie_UID}.png");
var apiKey = _configuration.GetValue<string>("CompreFaceConfig:APIKey");
var client = _compreFaceClient.GetCompreFaceService<VerificationService>(apiKey);
var faceVerificationRequest = new FaceVerificationRequestByBytes()
{
SourceImageInBytes = selfieEntity.File,
TargetImageInBytes = identificationDocument.File,
DetProbThreshold = 0.81m,
Limit = 1,
Status = false,
FacePlugins = []
};
try
{
var result = await client.VerifyAsync(faceVerificationRequest);
var faceMatches = result.Result.SelectMany(x => x.FaceMatches);
if (faceMatches.Any())
{
var newTransaction = new Transaction
{
TransactionDate = DateTime.UtcNow,
CreatedOn = DateTime.UtcNow,
PrincipalID = user.UserID,
Status = nameof(TransactionStatus.New),
Transaction_UID = Guid.NewGuid()
};
_notaryoDBContext.Transactions.Add(newTransaction);
selfieEntity.Transaction = newTransaction;
_notaryoDBContext.SaveChanges();
return RedirectToPage($"/Principal/NotaryoSteps/UploadDocument/{newTransaction.Transaction_UID}");
}
ModelState.AddModelError("", "Face Verification Failed");
}
catch (Exception ex)
{
// capture error here
ModelState.AddModelError("", ex.Message);
}
return Page();
}
//private string GetSelfieUrl()
//{
// var qb = new QueryBuilder
// {
// { "handler", "ViewImage" },
// { "TransactionSelfie_UID", TransactionSelfie_UID.ToString() }
// };
// var url = $"{Request.Scheme}://{Request.Host}/Principal/NotaryoSteps/TakeSelfie" + qb;
// return url;
//}
//private string GetIdentificationDocumentUrl(Guid document_UID)
//{
// var qb = new QueryBuilder
// {
// { "handler", "ViewImage" },
// { "IdentificationDocument_UID", document_UID.ToString() }
// };
// var url = $"{Request.Scheme}://{Request.Host}/Principal/IdentificationDocument/IdentificationDocument" + qb;
// return url;
//}
[AllowAnonymous]
public IActionResult OnGetViewImage()
{
if (TransactionSelfie_UID == Guid.Empty)
{
return NotFound();
}
var selfie = _notaryoDBContext.TransactionSelfies.FirstOrDefault(e => e.TransactionSelfie_UID == TransactionSelfie_UID);
if (selfie == null)
{
return NotFound();
}
return File(selfie.File, "image/png");
}
[BindProperty, Required]
public string SelfieBase64Image { get; set; }
[BindProperty(SupportsGet = true)]
public Guid TransactionSelfie_UID { get; set; }
}
}

View File

@ -0,0 +1,96 @@
"use strict";
(function () {
const
control_againButton = document.getElementById("AgainButton"),
control_selfieBase64Image = document.getElementById("SelfieBase64Image"),
control_canvas = document.getElementById("Canvas"),
control_nextButton = document.getElementById("NextButton"),
control_photo = document.getElementById("Photo"),
control_photoContainer = document.getElementById("PhotoContainer"),
control_takePictureButton = document.getElementById("TakePictureButton"),
control_video = document.getElementById("Video"),
control_videoContainer = document.getElementById("VideoContainer");
function _bindEvents() {
control_video.addEventListener('canplay', _canPlay);
control_takePictureButton.addEventListener("click", _takePicture);
control_againButton.addEventListener("click", _showCamera);
}
function _canPlay() {
let width = 400;
let height = control_video.videoHeight / (control_video.videoWidth / width);
// Firefox currently has a bug where the height can't be read from
// the video, so we will make assumptions if this happens.
if (isNaN(height)) {
height = width / (4 / 3);
}
control_canvas.setAttribute('width', width);
control_canvas.setAttribute('height', height);
}
function _init() {
_loadCamera();
_bindEvents();
_showCamera();
}
function _loadCamera() {
navigator.mediaDevices.getUserMedia({ video: true, audio: false, })
.then(function (stream) {
let options = { mimeType: self.mimeType };
if (navigator.userAgent.indexOf('Chrome') >= 0) {
options = { mimeType: "video/webm; codecs=vp9" };
}
control_video.srcObject = stream;
control_video.play();
})
.catch(function (err) {
console.error("Unable to initialize camera: " + err);
});
}
function _showCamera() {
jfa.utilities.element.hide(control_photoContainer);
jfa.utilities.element.show(control_videoContainer);
jfa.utilities.element.show(control_takePictureButton);
jfa.utilities.element.hide(control_againButton);
jfa.utilities.element.disable(control_nextButton);
}
function _showPhoto() {
jfa.utilities.element.show(control_photoContainer);
jfa.utilities.element.hide(control_videoContainer);
jfa.utilities.element.show(control_againButton);
jfa.utilities.element.hide(control_takePictureButton);
jfa.utilities.element.enable(control_nextButton);
}
function _takePicture() {
let ratio = control_video.videoWidth / control_video.videoHeight;
let newHeight = control_canvas.height;
let newWidth = control_canvas.height * ratio;
control_canvas.width = newWidth;
control_canvas.height = newHeight;
const context = control_canvas.getContext('2d');
context.drawImage(control_video, 0, 0, newWidth, newHeight);
const data = control_canvas.toDataURL('image/jpg');
control_photo.setAttribute('src', data);
control_selfieBase64Image.value = data;
_showPhoto();
}
_init();
})();

View File

@ -0,0 +1,28 @@
@page "{Transaction_UID}"
@using EnotaryoPH.Web.Pages.Shared.Components.NotaryoSteps
@model EnotaryoPH.Web.Pages.Principal.NotaryoSteps.UploadDocumentModel
@{
}
<section class="my-5">
<div class="container">
<div class="row">
<div class="col">
<h1>Notaryo Steps</h1>
<div class="mt-3">
@await Component.InvokeAsync("NotaryoSteps", new
{
NotaryoSteps = new List<NotaryoStep>() {
new NotaryoStep { Name = "Upload Identification", Step = 1 },
new NotaryoStep { Name = "Take Selfie", Step = 2 },
new NotaryoStep { Name = "Upload Document", Step = 3, IsActive = true },
new NotaryoStep { Name = "Choose Notary", Step = 4 },
}
})
<div class="tab-content mt-3"></div>
</div>
</div>
</div>
</div>
</section>

View File

@ -0,0 +1,15 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace EnotaryoPH.Web.Pages.Principal.NotaryoSteps
{
public class UploadDocumentModel : PageModel
{
public void OnGet()
{
}
[BindProperty(SupportsGet = true)]
public Guid Transaction_UID { get; set; }
}
}

View File

@ -16,7 +16,7 @@
<h1>Notaryo Steps</h1> <h1>Notaryo Steps</h1>
<div class="mt-3"> <div class="mt-3">
@await Component.InvokeAsync("NotaryoSteps", new { NotaryoSteps = new List<NotaryoStep>() { @await Component.InvokeAsync("NotaryoSteps", new { NotaryoSteps = new List<NotaryoStep>() {
new NotaryoStep { Name = "Choose Identification", Step = 1, IsActive = true }, new NotaryoStep { Name = "Upload Identification", Step = 1, IsActive = true },
new NotaryoStep { Name = "Take Selfie", Step = 2 }, new NotaryoStep { Name = "Take Selfie", Step = 2 },
new NotaryoStep { Name = "Upload Document", Step = 3 }, new NotaryoStep { Name = "Upload Document", Step = 3 },
new NotaryoStep { Name = "Choose Notary", Step = 4 }, new NotaryoStep { Name = "Choose Notary", Step = 4 },

View File

@ -2,10 +2,7 @@
(function () { (function () {
const const
control_existingDocumentsContainer = document.getElementById("ExistingDocumentsContainer"), control_existingDocumentsContainer = document.getElementById("ExistingDocumentsContainer"),
control_existingIdentificationDocumentCount = document.getElementById("ExistingIdentificationDocumentCount"),
control_newIdentificationDocumentContainer = document.getElementById("NewIdentificationDocumentContainer"), control_newIdentificationDocumentContainer = document.getElementById("NewIdentificationDocumentContainer"),
control_nextButton = document.querySelector(".wizard__nextbutton"),
control_uploadIdentificationForm = document.getElementById("UploadIdentificationForm"),
control_uploadNewIdentificationRadios = document.getElementsByName("UploadNewIdentification"); control_uploadNewIdentificationRadios = document.getElementsByName("UploadNewIdentification");
function _bindEvents() { function _bindEvents() {

View File

@ -1,4 +1,5 @@
@using EnotaryoPH.Web @using EnotaryoPH.Web
@using Microsoft.AspNetCore.Http.Extensions
@namespace EnotaryoPH.Web.Pages @namespace EnotaryoPH.Web.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers
@addTagHelper *, EnotaryoPH @addTagHelper *, EnotaryoPH

View File

@ -1,6 +1,6 @@
using System.Security.Principal; using System.Security.Principal;
using EnotaryoPH.Data; using EnotaryoPH.Data;
using EnotaryoPH.Web.Common.Services; using Exadel.Compreface.Clients.CompreFaceClient;
using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.AspNetCore.Authentication.Cookies;
namespace EnotaryoPH.Web namespace EnotaryoPH.Web
@ -28,6 +28,13 @@ namespace EnotaryoPH.Web
builder.Services.AddTransient<IPasswordService, PasswordService>(); builder.Services.AddTransient<IPasswordService, PasswordService>();
builder.Services.AddTransient<ICurrentUserService, CurrentUserService>(); builder.Services.AddTransient<ICurrentUserService, CurrentUserService>();
builder.Services.AddTransient<ICompreFaceClient>(provider =>
{
var config = provider.GetRequiredService<IConfiguration>();
var host = config.GetValue<string>("CompreFaceConfig:Host");
var port = config.GetValue<string>("CompreFaceConfig:Port");
return new CompreFaceClient(host, port);
});
var app = builder.Build(); var app = builder.Build();

View File

@ -7,6 +7,11 @@
}, },
"AllowedHosts": "*", "AllowedHosts": "*",
"ConnectionStrings": { "ConnectionStrings": {
"NotaryoDatabase": "Host=localhost; Database=enotaryodb; Username=enotaryodbuser; Password=2pW@jfXWJ" "NotaryoDatabase": "Host=localhost; Database=enotaryodb; Username=enotaryodbuser; Password=secret"
},
"CompreFaceConfig": {
"APIKey": "secret",
"Host": "http://localhost",
"Port": "8000"
} }
} }