524 lines
20 KiB
C#
524 lines
20 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Data.Entity.Core.Common.CommandTrees;
|
|
using System.Linq;
|
|
using System.Web;
|
|
using System.Web.Mvc;
|
|
using FIT5032_Assignment.Models;
|
|
using System.Data.Entity;
|
|
using System.Diagnostics;
|
|
using Microsoft.IdentityModel.Tokens;
|
|
using System.IdentityModel.Tokens.Jwt;
|
|
using System.Text;
|
|
using Org.BouncyCastle.Crypto;
|
|
using Org.BouncyCastle.OpenSsl;
|
|
using Org.BouncyCastle.Security;
|
|
using System.IO;
|
|
using System.Security.Cryptography;
|
|
using System.Globalization;
|
|
using RestSharp.Authenticators;
|
|
using RestSharp;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Threading.Tasks;
|
|
using Newtonsoft.Json;
|
|
|
|
namespace FIT5032_Assignment.Controllers {
|
|
public class AppointmentsController : Controller {
|
|
public class PassageUserAPI {
|
|
public PassageUserEntity User { get; set; }
|
|
}
|
|
|
|
public class PassageUserEntity {
|
|
public string CreatedAt { get; set; }
|
|
public string Email { get; set; }
|
|
public bool EmailVerified { get; set; }
|
|
}
|
|
|
|
private Database1Entities db = new Database1Entities();
|
|
|
|
// Login check
|
|
private static RsaSecurityKey LoadRsaSecurityKeyFromPem(string pem) {
|
|
TextReader textReader = new StringReader(pem);
|
|
PemReader pemReader = new PemReader(textReader);
|
|
AsymmetricKeyParameter keyParameter = (AsymmetricKeyParameter)pemReader.ReadObject();
|
|
|
|
RSAParameters rsaParameters = DotNetUtilities.ToRSAParameters((Org.BouncyCastle.Crypto.Parameters.RsaKeyParameters)keyParameter);
|
|
RSACryptoServiceProvider rsa = new RSACryptoServiceProvider();
|
|
rsa.ImportParameters(rsaParameters);
|
|
|
|
return new RsaSecurityKey(rsa);
|
|
}
|
|
private String psgCredentialVerify(string token) {
|
|
|
|
var jwtHandler = new JwtSecurityTokenHandler();
|
|
var jwtToken = jwtHandler.ReadJwtToken(token);
|
|
|
|
string base64Publickey = "LS0tLS1CRUdJTiBSU0EgUFVCTElDIEtFWS0tLS0tCk1JSUJDZ0tDQVFFQTRUWEQwVEh4NnJjNXlQcXM0Skw5M01nVEgvTS95Z2s3V1pYWWsrS01XTTA0bDdzM3owRlMKODlDRE56SFJkbVpJb3RCbDgrcUJ5TUwvck5VcUhXMnJ1Uzg0dmxFaWdza2djK2RsaitCZXFsaGsySFRpQitpegpQcGdCU1FJc2YrZjdSU3dkYktFS2hRQm1La3MxVGF4YWNDUndPVWJKT1VQbjJXZmhVSHhRd0FwZGNCQWdNdHVNCld3QzJZRThGblFRZDhxc3dMTTBGQWhoSzUrdXRXY0s0bHdCVlFxUGJRaUJZYnZmWXkwYVF6UFB2V2NMR1JvR00KUVA1b1JCTmRuRzQ4Sm9Eb2tCSEJkbCt4RzM1L1U2N1BvejFKY0VVSnpWTHdIUFNHa0xyRU1OYlFrbnJSK2tHZwpnS1dWNFpvYWVOSHZVeFE3YVg3SElFMlc1UnIwRmxGUG1RSURBUUFCCi0tLS0tRU5EIFJTQSBQVUJMSUMgS0VZLS0tLS0K";
|
|
RsaSecurityKey rsaKey = LoadRsaSecurityKeyFromPem(Encoding.UTF8.GetString(Convert.FromBase64String(base64Publickey)));
|
|
// Valid time 3600s
|
|
var validationParameters = new TokenValidationParameters() {
|
|
ValidIssuer = "https://auth.passage.id/v1/apps/ZHM5whW5xsZEczTn2loffzjN",
|
|
ValidateAudience = false,
|
|
IssuerSigningKey = rsaKey,
|
|
ValidateLifetime = true,
|
|
ClockSkew = TimeSpan.FromSeconds(3600)
|
|
};
|
|
|
|
try {
|
|
var claimsPrincipal = jwtHandler.ValidateToken(token, validationParameters, out var rawValidatedToken);
|
|
} catch (SecurityTokenExpiredException) {
|
|
Trace.WriteLine("Token has expired");
|
|
return null;
|
|
} catch (SecurityTokenInvalidSignatureException) {
|
|
Trace.WriteLine("Token has invalid signature");
|
|
return null;
|
|
} catch (SecurityTokenInvalidIssuerException) {
|
|
Trace.WriteLine("Token has invalid issuer");
|
|
return null;
|
|
} catch (SecurityTokenInvalidAudienceException) {
|
|
Trace.WriteLine("Token has invalid audience");
|
|
return null;
|
|
} catch (SecurityTokenValidationException) {
|
|
Trace.WriteLine("Token failed validation");
|
|
return null;
|
|
} catch (ArgumentException) {
|
|
Trace.WriteLine("Token was empty or null");
|
|
return null;
|
|
}
|
|
|
|
string sub = jwtToken.Claims.First(claim => claim.Type == "sub").Value;
|
|
|
|
return sub;
|
|
}
|
|
private Users loginInfo(string user) {
|
|
var db = new Database1Entities();
|
|
var credential = db.Credentials.Where(res => (res.uniqueIdCode == user) && (res.provider == 0));
|
|
if (credential.Count() == 0) {
|
|
return null;
|
|
} else {
|
|
var userUuid = credential.First().user;
|
|
var dbUser = db.Users.Where(res => res.uuid == userUuid);
|
|
if (dbUser.Count() == 0) {
|
|
return null;
|
|
} else {
|
|
return dbUser.First();
|
|
}
|
|
}
|
|
}
|
|
|
|
// GET: Appointments
|
|
public ActionResult Index() {
|
|
// Check login
|
|
if (Request.Cookies["psg_auth_token"] == null) {
|
|
// Redirect to home page
|
|
return RedirectToAction("Index");
|
|
}
|
|
var user = psgCredentialVerify(Request.Cookies["psg_auth_token"].Value);
|
|
if (user == null) {
|
|
// Redirect to home page
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
var userProfile = loginInfo(user);
|
|
if (userProfile == null) {
|
|
// Redirect to home page, and remove cookies
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
// Detect user role
|
|
ViewBag.role = db.Users.Find(userProfile.uuid).role;
|
|
|
|
var appointments = new List<Tuple<Appointments, Users>>();
|
|
if (userProfile.role == 1) { // patient
|
|
var dbData = db.Appointments.Where(a => a.patient == userProfile.uuid).OrderByDescending(a => a.createdAt).ToList();
|
|
foreach (var item in dbData) {
|
|
Trace.WriteLine(item.uuid);
|
|
appointments.Add(new Tuple<Appointments, Users>(item, db.Users.Find(item.responsibleBy)));
|
|
}
|
|
} else if (userProfile.role == 2) { // doctor
|
|
var dbData = db.Appointments.Where(a => a.responsibleBy == userProfile.uuid).OrderByDescending(a => a.createdAt).ToList();
|
|
foreach (var item in dbData) {
|
|
Trace.WriteLine(item.uuid);
|
|
appointments.Add(new Tuple<Appointments, Users>(item, db.Users.Find(item.patient)));
|
|
}
|
|
}
|
|
ViewBag.appointments = appointments;
|
|
ViewBag.tip = TempData["tip"];
|
|
return View();
|
|
}
|
|
|
|
// GET: Appointments/Cancel/uuid
|
|
public ActionResult Cancel(string id) {
|
|
// Check login
|
|
if (Request.Cookies["psg_auth_token"] == null) {
|
|
// Redirect to home page
|
|
return RedirectToAction("Index");
|
|
}
|
|
var user = psgCredentialVerify(Request.Cookies["psg_auth_token"].Value);
|
|
if (user == null) {
|
|
// Redirect to home page
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
var userProfile = loginInfo(user);
|
|
if (userProfile == null) {
|
|
// Redirect to home page, and remove cookies
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
|
|
// Check if the appointment is belong to the doctor or patient
|
|
var appointment = db.Appointments.Find(id);
|
|
if (appointment == null) {
|
|
TempData["tip"] = "The appointment does not exist.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
if (appointment.patient != userProfile.uuid && appointment.responsibleBy != userProfile.uuid) {
|
|
TempData["tip"] = "The appointment does not exist.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// Check status == 0
|
|
if (appointment.status != 0) {
|
|
TempData["tip"] = "Operation invalid";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// Update status
|
|
appointment.status = -1;
|
|
db.Entry(appointment).State = EntityState.Modified;
|
|
db.SaveChanges();
|
|
|
|
TempData["tip"] = "The appointment has been cancelled.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
public ActionResult Approve(string id) {
|
|
// Check login
|
|
if (Request.Cookies["psg_auth_token"] == null) {
|
|
// Redirect to home page
|
|
return RedirectToAction("Index");
|
|
}
|
|
var user = psgCredentialVerify(Request.Cookies["psg_auth_token"].Value);
|
|
if (user == null) {
|
|
// Redirect to home page
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
var userProfile = loginInfo(user);
|
|
if (userProfile == null) {
|
|
// Redirect to home page, and remove cookies
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
// Detect user role
|
|
ViewBag.role = db.Users.Find(userProfile.uuid).role;
|
|
|
|
// Only doctor can approve appointment
|
|
if (userProfile.role != 2) {
|
|
TempData["tip"] = "This operation is not allowed.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// Check if the appointment is belong to the doctor or patient
|
|
var appointment = db.Appointments.Find(id);
|
|
if (appointment == null) {
|
|
TempData["tip"] = "The appointment does not exist.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
if (appointment.responsibleBy != userProfile.uuid) {
|
|
TempData["tip"] = "The appointment does not exist.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// Check status == 0
|
|
if (appointment.status != 0) {
|
|
TempData["tip"] = "Operation invalid";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// Update status
|
|
appointment.status = 1;
|
|
db.Entry(appointment).State = EntityState.Modified;
|
|
db.SaveChanges();
|
|
|
|
TempData["tip"] = "The appointment has been approved.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// GET: Appointments/Create
|
|
public ActionResult Create(string id) {
|
|
if (Request.Cookies["psg_auth_token"] == null) {
|
|
// Redirect to home page
|
|
return RedirectToAction("Index");
|
|
}
|
|
if (id == null) {
|
|
// Pass a dropdown list for all doctors
|
|
List<Doctors> doctors = db.Doctors.ToList();
|
|
List<Tuple<Users, Doctors>> usersList = new List<Tuple<Users, Doctors>>();
|
|
foreach (var doctor in doctors) {
|
|
// Fetch the user obj
|
|
Users user = db.Users.Find(doctor.user);
|
|
usersList.Add(new Tuple<Users, Doctors>(user, doctor));
|
|
}
|
|
ViewBag.doctorsList = usersList;
|
|
} else {
|
|
ViewBag.doctorId = id;
|
|
// Fetch doctor profile
|
|
ViewBag.doctor = db.Doctors.Where(d => d.user == id);
|
|
ViewBag.doctorUser = db.Users.Find(id);
|
|
}
|
|
return View();
|
|
|
|
}
|
|
|
|
|
|
// POST: Appointments/Create/doctorId
|
|
[HttpPost]
|
|
public ActionResult Create(FormCollection collection) {
|
|
try {
|
|
// TODO: Add insert logic here
|
|
Trace.WriteLine(collection["doctorUuid"]);
|
|
|
|
// Verify login
|
|
if (Request.Cookies["psg_auth_token"] == null) {
|
|
// Redirect to home page
|
|
return RedirectToAction("Index");
|
|
}
|
|
Trace.WriteLine("User has cookie");
|
|
var user = psgCredentialVerify(Request.Cookies["psg_auth_token"].Value);
|
|
if (user == null) {
|
|
// Redirect to home page
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
Trace.WriteLine("User has login");
|
|
var userProfile = loginInfo(user);
|
|
if (userProfile == null) {
|
|
// Redirect to home page, and remove cookies
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
Trace.WriteLine("User login available");
|
|
// Detect if user logined is patient
|
|
if (userProfile.role != 1) {
|
|
// Redirect to home page
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
Trace.WriteLine("User is patient");
|
|
|
|
// Transfer MM/dd/yyyy (18/10/2023) to C# DateTime
|
|
Trace.WriteLine(collection["appointmentDate"]);
|
|
DateTime date = DateTime.ParseExact(collection["appointmentDate"], "dd/MM/yyyy", CultureInfo.InvariantCulture);
|
|
Trace.WriteLine(date);
|
|
|
|
// The date cannot be earlier than today 0:00
|
|
|
|
if (date < DateTime.Today) {
|
|
ViewBag.tip = "The date cannot be earlier than today.";
|
|
ViewBag.doctorId = collection["doctorUuid"];
|
|
ViewBag.doctorUser = db.Users.Find(collection["doctorUuid"]);
|
|
return View();
|
|
}
|
|
|
|
// Create appointment
|
|
var uuid = Guid.NewGuid().ToString();
|
|
Appointments appointment = new Appointments {
|
|
uuid = uuid,
|
|
patient = userProfile.uuid,
|
|
responsibleBy = collection["doctorUuid"],
|
|
createdAt = DateTime.Now,
|
|
appointmentDate = date,
|
|
status = 0,
|
|
createdBy = 0
|
|
};
|
|
db.Appointments.Add(appointment);
|
|
db.SaveChanges();
|
|
|
|
TempData["tip"] = "The appointment has been booked.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
catch {
|
|
ViewBag.tip = "System error";
|
|
ViewBag.doctorId = collection["doctorUuid"];
|
|
ViewBag.doctorUser = db.Users.Find(collection["doctorUuid"]);
|
|
return View();
|
|
}
|
|
}
|
|
|
|
// GET: Appointments/UploadImage/uuid
|
|
public ActionResult UploadImage(string id) {
|
|
// Check login
|
|
if (Request.Cookies["psg_auth_token"] == null) {
|
|
// Redirect to home page
|
|
return RedirectToAction("Index");
|
|
}
|
|
var user = psgCredentialVerify(Request.Cookies["psg_auth_token"].Value);
|
|
if (user == null) {
|
|
// Redirect to home page
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
var userProfile = loginInfo(user);
|
|
if (userProfile == null) {
|
|
// Redirect to home page, and remove cookies
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
// Detect user role
|
|
ViewBag.role = db.Users.Find(userProfile.uuid).role;
|
|
if (userProfile.role != 2) {
|
|
TempData["tip"] = "This operation is not allowed.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// Check if the appointment is belong to the doctor
|
|
var appointment = db.Appointments.Find(id);
|
|
if (appointment == null) {
|
|
TempData["tip"] = "The appointment does not exist.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
if (appointment.responsibleBy != userProfile.uuid) {
|
|
TempData["tip"] = "The appointment does not exist.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// Check status == 1
|
|
if (appointment.status != 1) {
|
|
TempData["tip"] = "Operation invalid";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
ViewBag.appointment = appointment;
|
|
ViewBag.patient = db.Users.Find(appointment.patient);
|
|
return View();
|
|
}
|
|
|
|
[HttpPost]
|
|
public async Task<ActionResult> UploadImage(HttpPostedFileBase image, string appointmentUuid) {
|
|
try {
|
|
// Check login
|
|
if (Request.Cookies["psg_auth_token"] == null) {
|
|
// Redirect to home page
|
|
return RedirectToAction("Index");
|
|
}
|
|
var user = psgCredentialVerify(Request.Cookies["psg_auth_token"].Value);
|
|
if (user == null) {
|
|
// Redirect to home page
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
var userProfile = loginInfo(user);
|
|
if (userProfile == null) {
|
|
// Redirect to home page, and remove cookies
|
|
Response.Cookies["psg_auth_token"].Expires = DateTime.Now.AddDays(-1);
|
|
return RedirectToAction("Index");
|
|
}
|
|
// Detect user role
|
|
ViewBag.role = db.Users.Find(userProfile.uuid).role;
|
|
if (userProfile.role != 2) {
|
|
TempData["tip"] = "This operation is not allowed.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// Check the appointment exist & status == 1 & responsibleBy == doctor
|
|
var appointment = db.Appointments.Find(appointmentUuid);
|
|
if (appointment == null) {
|
|
TempData["tip"] = "The appointment does not exist.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
if (appointment.responsibleBy != userProfile.uuid) {
|
|
TempData["tip"] = "The appointment does not exist.";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
if (appointment.status != 1) {
|
|
TempData["tip"] = "Operation invalid";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
|
|
// check uploaded file
|
|
if (image == null) {
|
|
ViewBag.tip = "Please upload a file";
|
|
ViewBag.appointment = appointment;
|
|
ViewBag.patient = db.Users.Find(appointment.patient);
|
|
return View();
|
|
}
|
|
|
|
// check format: png, jpg, jpeg
|
|
string[] formats = new string[] { ".png", ".jpg", ".jpeg" };
|
|
string extension = Path.GetExtension(image.FileName);
|
|
if (!formats.Contains(extension)) {
|
|
ViewBag.tip = "Please upload a png, jpg or jpeg file";
|
|
ViewBag.appointment = appointment;
|
|
ViewBag.patient = db.Users.Find(appointment.patient);
|
|
return View();
|
|
}
|
|
|
|
// save file
|
|
var fileId = Guid.NewGuid().ToString();
|
|
var fileName = fileId + extension;
|
|
var filePath = Path.Combine(Server.MapPath("~/App_Data/upload_images"), fileName);
|
|
image.SaveAs(filePath);
|
|
|
|
// Create image entity to database
|
|
var appointmentId = appointment.uuid;
|
|
var imageId = Guid.NewGuid().ToString();
|
|
var patientUuid = appointment.patient;
|
|
var dbImage = new Images {
|
|
uuid = imageId,
|
|
appointment = appointmentId,
|
|
patient = patientUuid,
|
|
responsibleBy = userProfile.uuid,
|
|
createdAt = DateTime.Now,
|
|
file = fileName,
|
|
status = 0,
|
|
};
|
|
// Change appointment status to 2
|
|
appointment.status = 2;
|
|
db.Entry(appointment).State = EntityState.Modified;
|
|
db.SaveChanges();
|
|
|
|
// Find patient email from Passage service
|
|
var credential = db.Credentials.Where(res => (res.user == patientUuid) && (res.provider == 0));
|
|
Trace.WriteLine(credential.First().uniqueIdCode);
|
|
var user_id = credential.First().uniqueIdCode;
|
|
var app_id = "ZHM5whW5xsZEczTn2loffzjN";
|
|
RestClient passageClient = new RestClient(new RestClientOptions($"https://api.passage.id/v1/apps/{app_id}"));
|
|
passageClient.AddDefaultHeader("Authorization", "Bearer vF4ch1wUf8.1cqOms9JMmUbqMGohlkJLzGDVlbF51D03fJnLfxwkn8kyAaVVjfvySufW9vXb3p3");
|
|
var psgRequest = new RestRequest($"users/{user_id}", Method.Get);
|
|
|
|
// Send request
|
|
var passageResponse = await passageClient.ExecuteAsync(psgRequest);
|
|
Trace.WriteLine(passageResponse.Content);
|
|
|
|
// Transfer response
|
|
var passageResponseJson = JsonConvert.DeserializeObject<PassageUserAPI>(passageResponse.Content);
|
|
var patientEmail = passageResponseJson.User.Email;
|
|
|
|
// Send attached email with mailgun
|
|
var doctorName = userProfile.displayName;
|
|
var patient = db.Users.Find(patientUuid);
|
|
RestClient client = new RestClient(new RestClientOptions("https://api.mailgun.net/v3/test.astrian.moe") {
|
|
Authenticator = new HttpBasicAuthenticator("api", "365900a7818241eafcbbf82e59cf99e8-5465e583-b4966e64"),
|
|
});
|
|
var request = new RestRequest("messages", Method.Post);
|
|
request.AddParameter("from", "Xpectrum <xpectrum@test.astrian.moe>");
|
|
request.AddParameter("to", patientEmail);
|
|
request.AddParameter("subject", "Xpectrum: New image available");
|
|
request.AddParameter("text", $"Hi {patient.displayName},\n\nDr. {doctorName} has uploaded a new image for you.\n\nPlease check the attachment.\n\nBest regards,\nXpectrum");
|
|
request.AddFile("attachment", filePath);
|
|
|
|
// Send request
|
|
var response = await client.ExecuteAsync(request);
|
|
Trace.WriteLine(response.Content);
|
|
|
|
TempData["tip"] = "The image has been attached to the appointment.";
|
|
return Redirect("/Appointments/Index");
|
|
} catch {
|
|
TempData["tip"] = "System error";
|
|
return Redirect("/Appointments/Index");
|
|
}
|
|
}
|
|
}
|
|
}
|