From 8589da498ed5f1ce1ac2e99d5d892c31dcf599b7 Mon Sep 17 00:00:00 2001 From: viktor436 Date: Wed, 23 Nov 2022 18:40:30 +0200 Subject: [PATCH 01/19] Role enum, Code cleanup. --- .../Identity/Pages/Account/Register.cshtml.cs | 13 +++++----- Controllers/PostingController.cs | 24 ------------------- Controllers/RoleRequestsController.cs | 7 +++--- Data/DbInitializer.cs | 17 ++++++------- Misc/Role.cs | 10 ++++++++ Models/Posts.cs | 21 ---------------- Views/Posting/CreatePost.cshtml | 23 ------------------ Views/Posting/Created.cshtml | 7 ------ Views/Posting/Index.cshtml | 18 -------------- Views/Shared/_Layout.cshtml | 2 +- 10 files changed, 31 insertions(+), 111 deletions(-) delete mode 100644 Controllers/PostingController.cs create mode 100644 Misc/Role.cs delete mode 100644 Models/Posts.cs delete mode 100644 Views/Posting/CreatePost.cshtml delete mode 100644 Views/Posting/Created.cshtml delete mode 100644 Views/Posting/Index.cshtml diff --git a/Areas/Identity/Pages/Account/Register.cshtml.cs b/Areas/Identity/Pages/Account/Register.cshtml.cs index f135183..5a1301f 100644 --- a/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -24,6 +24,7 @@ using DoctorSystem.Data; using DoctorSystem.Data.Migrations; using Microsoft.Extensions.Hosting; +using DoctorSystem.Misc; namespace DoctorSystem.Areas.Identity.Pages.Account { @@ -151,13 +152,13 @@ public async Task OnPostAsync(string returnUrl = null) user.FirstName = Input.FirstName; user.LastName = Input.LastName; user.Gender = Input.Gender; - if (Input.Role == "Patient") + if (Input.Role == Role.Patient) { - await _userManager.AddToRoleAsync(user, "Patient"); + await _userManager.AddToRoleAsync(user, Role.Patient); } else { - await _userManager.AddToRoleAsync(user, "Guest"); + await _userManager.AddToRoleAsync(user, Role.Guest); user.DoctorUID = Input.DoctorUID; } @@ -180,16 +181,16 @@ public async Task OnPostAsync(string returnUrl = null) { if (Input.Role == null) { - await _userManager.AddToRoleAsync(user, "Guest"); + await _userManager.AddToRoleAsync(user, Role.Guest); } else { - if (Input.Role != "Doctor") + if (Input.Role != Role.Doctor) { await _userManager.AddToRoleAsync(user, Input.Role); } - await _userManager.AddToRoleAsync(user, "Guest"); + await _userManager.AddToRoleAsync(user, Role.Guest); } _logger.LogInformation("User created a new account with password."); diff --git a/Controllers/PostingController.cs b/Controllers/PostingController.cs deleted file mode 100644 index 19f1388..0000000 --- a/Controllers/PostingController.cs +++ /dev/null @@ -1,24 +0,0 @@ -using DoctorSystem.Models; -using Microsoft.AspNetCore.Mvc; - -namespace DoctorSystem.Controllers -{ - public class PostingController : Controller - { - public IActionResult Index() - { - return View(); - } - - public IActionResult Post() - { - return View("CreatePost"); - } - [HttpPost] - public IActionResult Post(Posts post) - { - return View("Created", post); - } - - } -} diff --git a/Controllers/RoleRequestsController.cs b/Controllers/RoleRequestsController.cs index a0c019a..5e0d1d1 100644 --- a/Controllers/RoleRequestsController.cs +++ b/Controllers/RoleRequestsController.cs @@ -9,6 +9,7 @@ using DoctorSystem.Models; using Microsoft.AspNetCore.Identity; using Microsoft.AspNetCore.Authorization; +using DoctorSystem.Misc; namespace DoctorSystem.Controllers { @@ -53,8 +54,8 @@ public async Task Approve(int? id) _context.Update(roleRequest); await _context.SaveChangesAsync(); var user = await _userManager.FindByIdAsync(roleRequest.Requester.Id); - await _userManager.AddToRoleAsync(user, "Doctor"); - await _userManager.RemoveFromRoleAsync(user, "Guest"); + await _userManager.AddToRoleAsync(user, Role.Doctor); + await _userManager.RemoveFromRoleAsync(user, Role.Guest); } catch (DbUpdateConcurrencyException) @@ -107,7 +108,7 @@ public async Task Edit(int id, [Bind("Id,DateCreated,IsApproved,D _context.Update(roleRequest); await _context.SaveChangesAsync(); var user = await _userManager.FindByIdAsync(roleRequest.UserId); - await _userManager.AddToRoleAsync(user, "Doctor"); + await _userManager.AddToRoleAsync(user, Role.Doctor); } catch (DbUpdateConcurrencyException) diff --git a/Data/DbInitializer.cs b/Data/DbInitializer.cs index d4ea145..3f055b4 100644 --- a/Data/DbInitializer.cs +++ b/Data/DbInitializer.cs @@ -1,4 +1,5 @@ using Microsoft.AspNetCore.Identity; +using DoctorSystem.Misc; namespace DoctorSystem.Data; public static class DbInitializer @@ -12,26 +13,26 @@ public static void Initialize(ApplicationDbContext context) { context.Roles.Add(new IdentityRole { - Name = "Admin", - NormalizedName = "ADMIN" + Name = Role.Admin, + NormalizedName = Role.Admin.ToUpper(), }); context.Roles.Add(new IdentityRole { - Name = "Guest", - NormalizedName = "GUEST" + Name = Role.Guest, + NormalizedName = Role.Guest.ToUpper(), }); context.Roles.Add(new IdentityRole { - Name = "Patient", - NormalizedName = "PATIENT" + Name = Role.Patient, + NormalizedName = Role.Patient.ToUpper(), }); context.Roles.Add(new IdentityRole { - Name = "Doctor", - NormalizedName = "DOCTOR" + Name = Role.Doctor, + NormalizedName = Role.Doctor.ToUpper(), }); context.SaveChanges(); } diff --git a/Misc/Role.cs b/Misc/Role.cs new file mode 100644 index 0000000..4d247d2 --- /dev/null +++ b/Misc/Role.cs @@ -0,0 +1,10 @@ +namespace DoctorSystem.Misc +{ + public static class Role + { + public static string Guest = "Guest"; + public static string Admin = "Admin"; + public static string Doctor = "Doctor"; + public static string Patient = "Patient"; + } +} diff --git a/Models/Posts.cs b/Models/Posts.cs deleted file mode 100644 index f464a78..0000000 --- a/Models/Posts.cs +++ /dev/null @@ -1,21 +0,0 @@ -using System.ComponentModel.DataAnnotations; - -namespace DoctorSystem.Models -{ - public class Posts - { - public int Id { get; set; } - - public string Title { get; set; } - - public string Description { get; set; } - - public DateOnly DateCreated { get; set; } - - //така или свързочна таблица(OneToMany) - public IEnumerable Comments { get; set; } - - - - } -} diff --git a/Views/Posting/CreatePost.cshtml b/Views/Posting/CreatePost.cshtml deleted file mode 100644 index d67b00c..0000000 --- a/Views/Posting/CreatePost.cshtml +++ /dev/null @@ -1,23 +0,0 @@ -@model Posts - -@{ - ViewData["Title"] = "Posting Page"; -} -@* - For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 -*@ -@{ -} -

@ViewData["Title"]

-

Create

-
-

- - -

-

- - -

-

-
diff --git a/Views/Posting/Created.cshtml b/Views/Posting/Created.cshtml deleted file mode 100644 index 6a20f2d..0000000 --- a/Views/Posting/Created.cshtml +++ /dev/null @@ -1,7 +0,0 @@ -@model Posts -@* - For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 -*@ -@{ -} -

Post @Model.Title Created

diff --git a/Views/Posting/Index.cshtml b/Views/Posting/Index.cshtml deleted file mode 100644 index 50ae8df..0000000 --- a/Views/Posting/Index.cshtml +++ /dev/null @@ -1,18 +0,0 @@ -@* - For more information on enabling MVC for empty projects, visit https://go.microsoft.com/fwlink/?LinkID=397860 -*@ -@{ - ViewData["Title"] = "All posts"; -} -

@ViewData["Title"]

- - - - - - - - - - - diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 3be5963..5286334 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -52,7 +52,7 @@
  • Investors
  • -
  • Requests
  • +
  • Requests
  • Досие
  • Случаи
  • Контакти
  • From 047161c7e00dea7be9b05ce321fc60c2f7c1efbe Mon Sep 17 00:00:00 2001 From: viktor436 Date: Fri, 9 Dec 2022 22:39:22 +0200 Subject: [PATCH 02/19] Comments CRUD functionality added. --- Controllers/PostsController.cs | 203 ++++++++++++++++++++++++++++++- Data/ApplicationDbContext.cs | 2 +- Views/Posts/CreateComment.cshtml | 41 +++++++ Views/Posts/DeleteComment.cshtml | 40 ++++++ Views/Posts/Details.cshtml | 59 +++++++++ Views/Posts/Edit.cshtml | 6 - Views/Posts/EditComment.cshtml | 43 +++++++ Views/Shared/_Layout.cshtml | 4 +- 8 files changed, 387 insertions(+), 11 deletions(-) create mode 100644 Views/Posts/CreateComment.cshtml create mode 100644 Views/Posts/DeleteComment.cshtml create mode 100644 Views/Posts/EditComment.cshtml diff --git a/Controllers/PostsController.cs b/Controllers/PostsController.cs index a45a937..befd50f 100644 --- a/Controllers/PostsController.cs +++ b/Controllers/PostsController.cs @@ -35,6 +35,31 @@ public async Task Index() return View(await _context.Post.Include(p => p.Comments).ToListAsync()); } + public async Task AddComment(int? id) + { + if (id == null || _context.Post == null) + { + return NotFound(); + } + + var post = await _context.Post + .FirstOrDefaultAsync(m => m.Id == id); + if (post == null) + { + return NotFound(); + } + //var newComment = new Comment() + //{ + // CreatedAt = DateTime.Now, + // Creator = await _userManager.GetUserAsync(User), + // Text = "Dummy text", + // Post = post, + //}; + //_context.Add(newComment); + //var comment = await _context.Comment.ToListAsync(); + + return RedirectToAction("Details"); + } // GET: Posts/Details/5 public async Task Details(int? id) { @@ -43,13 +68,13 @@ public async Task Details(int? id) return NotFound(); } - var post = await _context.Post + var post = await _context.Post.Include(p=>p.Comments) .FirstOrDefaultAsync(m => m.Id == id); if (post == null) { return NotFound(); } - var comment = await _context.Comment.ToListAsync(); + var comment = await _context.Comment.Include(c=>c.Creator).ToListAsync(); return View(post); } @@ -166,10 +191,184 @@ public async Task DeleteConfirmed(int id) return RedirectToAction(nameof(Index)); } + public async Task CreateComment(int id) + { + var post = await _context.Post.FindAsync(id); + + ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description"); + if (id == null || _context.Post == null) + { + return NotFound(); + } + + return View(); + } + + [HttpPost] + [ValidateAntiForgeryToken] + public async Task CreateComment([Bind("Text")] Comment comment, Post postident) + { + comment.Creator = await _userManager.GetUserAsync(User); + comment.Post = await _context.Post.FindAsync(postident.Id); + comment.CreatedAt = DateTime.Now; + + _context.Add(comment); + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + + //ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description", comment.PostId); + return View(comment); + } + + public async Task EditComment(int? id) + { + if (id == null || _context.Comment == null) + { + return NotFound(); + } + + var comment = await _context.Comment.FindAsync(id); + if (comment == null) + { + return NotFound(); + } + ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description", comment.PostId); + return View(comment); + } + + // POST: Comments/Edit/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task EditComment(int id, [Bind("Id,PostId,CreatedAt,Text")] Comment comment) + { + if (id != comment.Id) + { + return NotFound(); + } + + if (ModelState.IsValid) + { + try + { + _context.Update(comment); + await _context.SaveChangesAsync(); + } + catch (DbUpdateConcurrencyException) + { + if (!CommentExists(comment.Id)) + { + return NotFound(); + } + else + { + throw; + } + } + return RedirectToAction(nameof(Index)); + } + ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description", comment.PostId); + return View(comment); + } + + + + public async Task DeleteComment(int? id) + { + if (id == null || _context.Comment == null) + { + return NotFound(); + } + + var comment = await _context.Comment + .Include(c => c.Post) + .FirstOrDefaultAsync(m => m.Id == id); + if (comment == null) + { + return NotFound(); + } + + return View(comment); + } + + // POST: Comments/Delete/5 + [HttpPost] + [ValidateAntiForgeryToken] + public async Task DeleteCommentConfirmed(int id) + { + if (_context.Comment == null) + { + return Problem("Entity set 'ApplicationDbContext.Comment' is null."); + } + var comment = await _context.Comment.FindAsync(id); + if (comment != null) + { + _context.Comment.Remove(comment); + } + + await _context.SaveChangesAsync(); + return RedirectToAction(nameof(Index)); + } + private bool PostExists(int id) { return _context.Post.Any(e => e.Id == id); } + private bool CommentExists(int id) + { + return _context.Comment.Any(e => e.Id == id); + } + + + //public async Task Comment(int? id) + //{ + // if (id == null || _context.Post == null) + // { + // return NotFound(); + // } + + // var post = await _context.Post + // .FirstOrDefaultAsync(m => m.Id == id); + // if (post == null) + // { + // return NotFound(); + // } + // var comment = await _context.Comment.ToListAsync(); + // return View(post); + //} + + //[HttpPost] + //[ValidateAntiForgeryToken] + //public async Task Comment(int? id) + //{ + // if (id == null || _context.Post == null) + // { + // return NotFound(); + // } + + // var post = await _context.Post + // .FirstOrDefaultAsync(m => m.Id == id); + // if (post == null) + // { + // return NotFound(); + // } + // var comment = await _context.Comment.ToListAsync(); + // //return View(post); + + // Comment newComment = null; + // newComment.Creator = await _userManager.GetUserAsync(User); + // newComment.Post = await _context.Post.FindAsync(id); + // newComment.CreatedAt = DateTime.Now; + + // _context.Add(comment); + // await _context.SaveChangesAsync(); + // return RedirectToAction(nameof(Index)); + + // //ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description", comment.PostId); + // return View(); + + + } } + diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs index 48a7ff7..3179ccb 100644 --- a/Data/ApplicationDbContext.cs +++ b/Data/ApplicationDbContext.cs @@ -14,7 +14,7 @@ public ApplicationDbContext(DbContextOptions options) public DbSet Post { get; set; } public DbSet Comment { get; set; } public DbSet RoleRequests { get; set; } - + protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/Views/Posts/CreateComment.cshtml b/Views/Posts/CreateComment.cshtml new file mode 100644 index 0000000..5ce9ed4 --- /dev/null +++ b/Views/Posts/CreateComment.cshtml @@ -0,0 +1,41 @@ +@model DoctorSystem.Models.Comment + +@{ + ViewData["Title"] = "Create"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

    Create

    + +

    Comment

    +
    +
    +
    +
    +
    + @*
    + + +
    *@ +
    + +
    +
    + + + +
    +
    + +
    +
    +
    +
    + + + +@section Scripts { + @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} +} diff --git a/Views/Posts/DeleteComment.cshtml b/Views/Posts/DeleteComment.cshtml new file mode 100644 index 0000000..f3a3c8d --- /dev/null +++ b/Views/Posts/DeleteComment.cshtml @@ -0,0 +1,40 @@ +@model DoctorSystem.Models.Comment + +@{ + ViewData["Title"] = "Delete"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

    Delete

    + +

    Are you sure you want to delete this comment?

    +
    +

    Comment

    +
    +
    +
    + @Html.DisplayNameFor(model => model.CreatedAt) +
    +
    + @Html.DisplayFor(model => model.CreatedAt) +
    +
    + @Html.DisplayNameFor(model => model.Text) +
    +
    + @Html.DisplayFor(model => model.Text) +
    +
    + @Html.DisplayNameFor(model => model.Post) +
    +
    + @Html.DisplayFor(model => model.Post.Description) +
    +
    + +
    + + | + Back to List +
    +
    diff --git a/Views/Posts/Details.cshtml b/Views/Posts/Details.cshtml index 40e3db9..75c1634 100644 --- a/Views/Posts/Details.cshtml +++ b/Views/Posts/Details.cshtml @@ -30,5 +30,64 @@ @Html.DisplayFor(model => model.DateCreated) + + + + + + + + + + + @foreach (var item in Model.Comments) + { + + + + + + + } + +
    + @*@Html.DisplayNameFor(model => model.Comments)*@ + Creator + + @Html.DisplayNameFor(model => model.Comments) + + @Html.DisplayNameFor(model => model.Comments) +
    + @Html.DisplayFor(modelItem => item.Creator.Email) + + @Html.DisplayFor(modelItem => item.Text) + + @Html.DisplayFor(modelItem => item.CreatedAt) + + EditComment | + DeleteComment +
    +

    + Create New Comment +

    + @*
    +
    +
    +
    +
    + + +
    +
    + + + +
    +
    + +
    +
    +
    +
    *@ diff --git a/Views/Posts/Edit.cshtml b/Views/Posts/Edit.cshtml index af049f1..af9394f 100644 --- a/Views/Posts/Edit.cshtml +++ b/Views/Posts/Edit.cshtml @@ -24,12 +24,6 @@ - @*ToDo-да махна възможността за редактиране на дата*@ -
    - - - -
    diff --git a/Views/Posts/EditComment.cshtml b/Views/Posts/EditComment.cshtml new file mode 100644 index 0000000..b84b929 --- /dev/null +++ b/Views/Posts/EditComment.cshtml @@ -0,0 +1,43 @@ +@model DoctorSystem.Models.Comment + +@{ + ViewData["Title"] = "Edit"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

    Edit

    + +

    Comment

    +
    +
    +
    +
    +
    + + @**@ + @*
    + + + +
    *@ +
    + + + +
    +
    + +
    +
    +
    +
    + + + +@section Scripts { + @{ + await Html.RenderPartialAsync("_ValidationScriptsPartial"); + } + } diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 5286334..465edfc 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -38,7 +38,7 @@ - + +
    @@ -112,4 +113,5 @@ }) }) + } diff --git a/Areas/Identity/Pages/Account/Register.cshtml.cs b/Areas/Identity/Pages/Account/Register.cshtml.cs index 5a1301f..d32465a 100644 --- a/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -25,6 +25,11 @@ using DoctorSystem.Data.Migrations; using Microsoft.Extensions.Hosting; using DoctorSystem.Misc; +using Newtonsoft.Json.Linq; +using System.Net; + +//using Microsoft.Extensions.Configuration; +//using Microsoft.Extensions.Configuration.Json; namespace DoctorSystem.Areas.Identity.Pages.Account { @@ -39,8 +44,10 @@ public class RegisterModel : PageModel private readonly RoleManager _roleManager; private readonly ApplicationDbContext _context; + //private readonly ConfigurationManager _configuration; public RegisterModel( + //ConfigurationManager configuration, UserManager userManager, IUserStore userStore, SignInManager signInManager, @@ -49,6 +56,7 @@ public RegisterModel( RoleManager roleManager, ApplicationDbContext context) { + //_configuration = configuration; _context = context; _roleManager = roleManager; _userManager = userManager; @@ -138,15 +146,29 @@ public async Task OnGetAsync(string returnUrl = null) { ReturnUrl = returnUrl; ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); - + //ViewData["ReCaptchaKey"] = _configuration.GetSection("GoogleReCaptcha:key").Value; + } public async Task OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); + //ViewData["ReCaptchaKey"] = _configuration.GetSection("GoogleReCaptcha:key").Value; + if (ModelState.IsValid) { + if (!ReCaptchaPassed( + Request.Form["g-recaptcha-response"], // that's how you get it from the Request object + //_configuration.GetSection("GoogleReCaptcha:secret").Value, + + _logger + )) + { + ModelState.AddModelError(string.Empty, "You failed the CAPTCHA, stupid robot. Go play some 1x1 on SFs instead."); + return Page(); + } + var user = CreateUser(); user.DateOfBirth = Input.DateOfBirth; user.FirstName = Input.FirstName; @@ -248,5 +270,25 @@ private IUserEmailStore GetEmailStore() } return (IUserEmailStore)_userStore; } + + public static bool ReCaptchaPassed(string gRecaptchaResponse, string secret, ILogger logger) + { + HttpClient httpClient = new HttpClient(); + var res = httpClient.GetAsync($"https://www.google.com/recaptcha/api/siteverify?secret={secret}&response={gRecaptchaResponse}").Result; + if (res.StatusCode != HttpStatusCode.OK) + { + logger.LogError("Error while sending request to ReCaptcha"); + return false; + } + + string JSONres = res.Content.ReadAsStringAsync().Result; + dynamic JSONdata = JObject.Parse(JSONres); + if (JSONdata.success != "true") + { + return false; + } + + return true; + } } } diff --git a/Controllers/PostsController.cs b/Controllers/PostsController.cs index befd50f..11cc658 100644 --- a/Controllers/PostsController.cs +++ b/Controllers/PostsController.cs @@ -82,6 +82,13 @@ public async Task Details(int? id) // GET: Posts/Create public IActionResult Create() { + IEnumerable CategoryList = _context.Category.Select( + u => new SelectListItem + { + Text = u.Name, + Value = u.Id.ToString() + }); + ViewBag.CategoryList = CategoryList; return View(); } diff --git a/Data/ApplicationDbContext.cs b/Data/ApplicationDbContext.cs index 3179ccb..78246b9 100644 --- a/Data/ApplicationDbContext.cs +++ b/Data/ApplicationDbContext.cs @@ -14,7 +14,8 @@ public ApplicationDbContext(DbContextOptions options) public DbSet Post { get; set; } public DbSet Comment { get; set; } public DbSet RoleRequests { get; set; } - + public DbSet Category { get; set; } + protected override void OnModelCreating(ModelBuilder modelBuilder) diff --git a/Models/Category.cs b/Models/Category.cs new file mode 100644 index 0000000..9da865a --- /dev/null +++ b/Models/Category.cs @@ -0,0 +1,11 @@ +using Microsoft.Build.Framework; + +namespace DoctorSystem.Models +{ + public class Category + { + public int Id { get; set; } + [Required] + public string Name { get; set; } + } +} diff --git a/Models/Post.cs b/Models/Post.cs index c054779..0a7e1ed 100644 --- a/Models/Post.cs +++ b/Models/Post.cs @@ -18,7 +18,8 @@ public class Post public ICollection Comments { get; set; } = new List(); - + public int CategoryId { get; set; } + public Category Category { get; set; } } } diff --git a/Views/Posts/Create.cshtml b/Views/Posts/Create.cshtml index 1b16c38..cc0c370 100644 --- a/Views/Posts/Create.cshtml +++ b/Views/Posts/Create.cshtml @@ -23,6 +23,13 @@ +
    + + + +
    From 66742025d96f3496d350e55c3cf7d0e8b09fbabd Mon Sep 17 00:00:00 2001 From: Viktor Kolev Date: Tue, 13 Dec 2022 15:18:42 +0200 Subject: [PATCH 04/19] DI of configuration --- .../Identity/Pages/Account/Register.cshtml.cs | 19 ++++++++++--------- Program.cs | 4 +++- Singleton/Config.cs | 7 +++++++ 3 files changed, 20 insertions(+), 10 deletions(-) create mode 100644 Singleton/Config.cs diff --git a/Areas/Identity/Pages/Account/Register.cshtml.cs b/Areas/Identity/Pages/Account/Register.cshtml.cs index d32465a..680b937 100644 --- a/Areas/Identity/Pages/Account/Register.cshtml.cs +++ b/Areas/Identity/Pages/Account/Register.cshtml.cs @@ -27,6 +27,8 @@ using DoctorSystem.Misc; using Newtonsoft.Json.Linq; using System.Net; +using DoctorSystem.Singleton; +using Microsoft.Extensions.Options; //using Microsoft.Extensions.Configuration; //using Microsoft.Extensions.Configuration.Json; @@ -44,10 +46,10 @@ public class RegisterModel : PageModel private readonly RoleManager _roleManager; private readonly ApplicationDbContext _context; - //private readonly ConfigurationManager _configuration; + private readonly Config _config; public RegisterModel( - //ConfigurationManager configuration, + IOptions config, UserManager userManager, IUserStore userStore, SignInManager signInManager, @@ -56,7 +58,7 @@ public RegisterModel( RoleManager roleManager, ApplicationDbContext context) { - //_configuration = configuration; + _config = config.Value; _context = context; _roleManager = roleManager; _userManager = userManager; @@ -146,22 +148,21 @@ public async Task OnGetAsync(string returnUrl = null) { ReturnUrl = returnUrl; ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); - //ViewData["ReCaptchaKey"] = _configuration.GetSection("GoogleReCaptcha:key").Value; - + ViewData["ReCaptchaKey"] = _config.CaptchaKey; + } public async Task OnPostAsync(string returnUrl = null) { returnUrl ??= Url.Content("~/"); ExternalLogins = (await _signInManager.GetExternalAuthenticationSchemesAsync()).ToList(); - //ViewData["ReCaptchaKey"] = _configuration.GetSection("GoogleReCaptcha:key").Value; - + ViewData["ReCaptchaKey"] = _config.CaptchaKey; + if (ModelState.IsValid) { if (!ReCaptchaPassed( Request.Form["g-recaptcha-response"], // that's how you get it from the Request object - //_configuration.GetSection("GoogleReCaptcha:secret").Value, - + _config.CaptchaSecret, _logger )) { diff --git a/Program.cs b/Program.cs index a9da318..f640946 100644 --- a/Program.cs +++ b/Program.cs @@ -8,6 +8,7 @@ using System.Configuration; using Microsoft.AspNetCore.Authentication.Google; using Microsoft.Extensions.Options; +using DoctorSystem.Singleton; var builder = WebApplication.CreateBuilder(args); @@ -20,7 +21,8 @@ builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) .AddRoles() .AddEntityFrameworkStores(); - + +builder.Services.Configure(builder.Configuration.GetSection("Config")); builder.Services.AddControllersWithViews(); builder.Services.AddTransient(); diff --git a/Singleton/Config.cs b/Singleton/Config.cs new file mode 100644 index 0000000..534aa92 --- /dev/null +++ b/Singleton/Config.cs @@ -0,0 +1,7 @@ +namespace DoctorSystem.Singleton; + +public class Config +{ + public string CaptchaKey { get; set; } + public string CaptchaSecret { get; set; } +} From 675296860c3eda9faf019e567e0c89a872987ea5 Mon Sep 17 00:00:00 2001 From: viktor436 Date: Thu, 15 Dec 2022 16:03:03 +0200 Subject: [PATCH 05/19] Post categories, bug fixes. --- Areas/Identity/Pages/Account/Register.cshtml | 5 + .../Identity/Pages/Account/Register.cshtml.cs | 12 +- Controllers/PostsController.cs | 165 ++++-- .../20221213134819_Category.Designer.cs | 505 ++++++++++++++++++ Data/Migrations/20221213134819_Category.cs | 63 +++ .../ApplicationDbContextModelSnapshot.cs | 30 ++ Models/Category.cs | 6 + Models/DefaultUser.cs | 6 + Models/Post.cs | 1 + Models/PostCategoryViewModel.cs | 9 + Models/PostDetailViewModel.cs | 10 + Views/Posts/Create.cshtml | 4 + Views/Posts/CreateComment.cshtml | 41 -- Views/Posts/Details.cshtml | 44 +- Views/Posts/Index.cshtml | 47 +- Views/Shared/_Layout.cshtml | 18 +- 16 files changed, 849 insertions(+), 117 deletions(-) create mode 100644 Data/Migrations/20221213134819_Category.Designer.cs create mode 100644 Data/Migrations/20221213134819_Category.cs create mode 100644 Models/PostCategoryViewModel.cs create mode 100644 Models/PostDetailViewModel.cs delete mode 100644 Views/Posts/CreateComment.cshtml diff --git a/Areas/Identity/Pages/Account/Register.cshtml b/Areas/Identity/Pages/Account/Register.cshtml index 3898c0b..de7fa73 100644 --- a/Areas/Identity/Pages/Account/Register.cshtml +++ b/Areas/Identity/Pages/Account/Register.cshtml @@ -32,6 +32,11 @@ +
    + + + +
    + @*
    + + +
    *@
    diff --git a/Views/Posts/CreateComment.cshtml b/Views/Posts/CreateComment.cshtml deleted file mode 100644 index 5ce9ed4..0000000 --- a/Views/Posts/CreateComment.cshtml +++ /dev/null @@ -1,41 +0,0 @@ -@model DoctorSystem.Models.Comment - -@{ - ViewData["Title"] = "Create"; - Layout = "~/Views/Shared/_Layout.cshtml"; -} - -

    Create

    - -

    Comment

    -
    -
    -
    -
    -
    - @*
    - - -
    *@ -
    - -
    -
    - - - -
    -
    - -
    -
    -
    -
    - - - -@section Scripts { - @{await Html.RenderPartialAsync("_ValidationScriptsPartial");} -} diff --git a/Views/Posts/Details.cshtml b/Views/Posts/Details.cshtml index 75c1634..d115d23 100644 --- a/Views/Posts/Details.cshtml +++ b/Views/Posts/Details.cshtml @@ -1,4 +1,4 @@ -@model DoctorSystem.Models.Post +@model DoctorSystem.Models.PostDetailViewModel @{ ViewData["Title"] = "Details"; @@ -12,42 +12,41 @@
    - @Html.DisplayNameFor(model => model.Title) + @Html.DisplayNameFor(model => model.Post.Title)
    - @Html.DisplayFor(model => model.Title) + @Html.DisplayFor(model => model.Post.Title)
    - @Html.DisplayNameFor(model => model.Description) + @Html.DisplayNameFor(model => model.Post.Description)
    - @Html.DisplayFor(model => model.Description) + @Html.DisplayFor(model => model.Post.Description)
    - @Html.DisplayNameFor(model => model.DateCreated) + @Html.DisplayNameFor(model => model.Post.DateCreated)
    - @Html.DisplayFor(model => model.DateCreated) + @Html.DisplayFor(model => model.Post.DateCreated)
    - @foreach (var item in Model.Comments) + @foreach (var item in Model.Post.Comments) {
    - @*@Html.DisplayNameFor(model => model.Comments)*@ - Creator + @Html.DisplayNameFor(model => model.Post.Comments) - @Html.DisplayNameFor(model => model.Comments) + @Html.DisplayNameFor(model => model.Text) - @Html.DisplayNameFor(model => model.Comments) + @Html.DisplayNameFor(model => model.Text)
    @@ -67,9 +66,24 @@ }
    -

    - Create New Comment -

    +
    +
    +
    + @*
    + + +
    *@ + +
    + + + +
    +
    + +
    +
    +
    @*
    diff --git a/Views/Posts/Index.cshtml b/Views/Posts/Index.cshtml index 723089b..1407af2 100644 --- a/Views/Posts/Index.cshtml +++ b/Views/Posts/Index.cshtml @@ -1,5 +1,6 @@ @model IEnumerable +@*@model IEnumerable*@ @{ ViewData["Title"] = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; @@ -7,9 +8,40 @@

    Posts

    +@*
    +
    + +
    +
    + + + +
    +
    + +
    + +
    +
    *@ +@*
    + + +
    *@

    Create New

    + +@*@Html.DropDownList("searchString",(SelectList)ViewBag.CategoryList,"Select Category") +Edit*@ +@using (Html.BeginForm()) +{ +

    + Find by Category: @Html.TextBox("SearchString") + +

    +} @@ -19,6 +51,9 @@ + @@ -34,13 +69,19 @@ + } diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 556ab08..78580d0 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -38,6 +38,8 @@ + + - + + --> + diff --git a/wwwroot/css/style.css b/wwwroot/css/style.css index 1d0b13a..ad8888a 100644 --- a/wwwroot/css/style.css +++ b/wwwroot/css/style.css @@ -13,6 +13,13 @@ Note: Licence under Creative Commons Attribution 3.0 #AboutUs { margin-top: -150px; + font-size: 20px; +} + +#AboutImg { + position: relative; + left: 470px; + top: -125px; } #registerForm { diff --git a/wwwroot/img/about.jpg b/wwwroot/img/about.jpg deleted file mode 100644 index 3069746043a1540e6180fb8abb28e8e1657ccca2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 44213 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 S8sq-}s6JDln0%&>lmFQiU8~Um From f522e30c0eab67835de7fbb7f7243ca6418505de Mon Sep 17 00:00:00 2001 From: viktor436 Date: Thu, 15 Dec 2022 20:13:19 +0200 Subject: [PATCH 07/19] RoleRequests visible only to Administrator --- Views/Shared/_Layout.cshtml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 78580d0..724871b 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -54,10 +54,10 @@
  • Investors
  • - @*@if (User.IsInRole("Admin")) + @if (User.IsInRole("Admin")) {
  • Requests
  • - }*@ + } @if (User.IsInRole("Patient")) {
  • Досие
  • @@ -66,7 +66,6 @@ {
  • Случаи
  • } -
  • Requests
  • Контакти
  • From 24cc8e35425580f8cb4a9e72a2af9d374b2a06b6 Mon Sep 17 00:00:00 2001 From: viktor436 Date: Thu, 15 Dec 2022 20:37:33 +0200 Subject: [PATCH 08/19] Display comment creator. Display post category.Code cleanup. --- Controllers/PostsController.cs | 148 +-------------------------------- Views/Posts/Create.cshtml | 4 - Views/Posts/Details.cshtml | 27 +----- Views/Posts/Index.cshtml | 31 ++----- 4 files changed, 12 insertions(+), 198 deletions(-) diff --git a/Controllers/PostsController.cs b/Controllers/PostsController.cs index c1917f5..cd7c966 100644 --- a/Controllers/PostsController.cs +++ b/Controllers/PostsController.cs @@ -33,73 +33,15 @@ public PostsController(ApplicationDbContext context, UserManager us _userManager = userManager; } - //[Authorize(Roles = "Doctor")] - //// GET: Posts - //public async Task Index() - //{ - // IEnumerable CategoryList = _context.Category.Select( - // u => new SelectListItem - // { - // Text = u.Name, - // Value = u.Id.ToString() - // }); - // ViewBag.CategoryList = CategoryList; - - // var category = await _context.FindAsync(); - - // return View(await _context.Post.Include(p => p.Comments).ThenInclude(x=>x.Creator).Include(t=>t.Creator).ToListAsync()); - // //.Where(s => s.Category == category) - //} public ViewResult Index(string searchString) { - //IEnumerable CategoryList = _context.Category.Select( - //u => new SelectListItem - //{ - // Text = u.Name, - // Value = u.Id.ToString() - //}).ToList(); - //ViewBag.CategoryList = new SelectList(_context.Category, _context.Category.FirstOrDefault()); if (!String.IsNullOrEmpty(searchString)) { - return View(_context.Post.Where(s => s.Category.Name == searchString).Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator).ToList()); + return View(_context.Post.Where(s => s.Category.Name == searchString).Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator).Include(v=>v.Category).ToList()); } - return View( _context.Post.Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator).ToList()); + return View( _context.Post.Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator).Include(v=>v.Category).ToList()); } - //[HttpPost] - //[ValidateAntiForgeryToken] - //public async Task Index([Bind("CategoryId")] int? id) - //{ - // ViewData["CategoryId"] = new SelectList(_context.Category, "Id", "Name", id); - // var category = await _context.FindAsync(id); - // return View(await _context.Post.Include(p => p.Comments).Where(s => s.Category == category).ToListAsync()); - //} - - //public async Task AddComment(int? id) - //{ - // if (id == null || _context.Post == null) - // { - // return NotFound(); - // } - - // var post = await _context.Post - // .FirstOrDefaultAsync(m => m.Id == id); - // if (post == null) - // { - // return NotFound(); - // } - // //var newComment = new Comment() - // //{ - // // CreatedAt = DateTime.Now, - // // Creator = await _userManager.GetUserAsync(User), - // // Text = "Dummy text", - // // Post = post, - // //}; - // //_context.Add(newComment); - // //var comment = await _context.Comment.ToListAsync(); - - // return RedirectToAction("Details"); - //} // GET: Posts/Details/5 public async Task Details(int? id) { @@ -108,7 +50,7 @@ public async Task Details(int? id) return NotFound(); } - var post = await _context.Post.Include(p=>p.Comments) + var post = await _context.Post.Include(p=>p.Comments).ThenInclude(c=>c.Creator) .FirstOrDefaultAsync(m => m.Id == id); if (post == null) { @@ -259,35 +201,6 @@ public async Task DeleteConfirmed(int id) return RedirectToAction(nameof(Index)); } - //public async Task CreateComment(int id) - //{ - // var post = await _context.Post.FindAsync(id); - - // ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description"); - // if (id == null || _context.Post == null) - // { - // return NotFound(); - // } - - // return View(); - //} - - //[HttpPost] - //[ValidateAntiForgeryToken] - //public async Task CreateComment([Bind("Text")] ) - //{ - // comment.Creator = await _userManager.GetUserAsync(User); - // comment.Post = await _context.Post.FindAsync(postident.Id); - // comment.CreatedAt = DateTime.Now; - - // _context.Add(comment); - // await _context.SaveChangesAsync(); - // return RedirectToAction(nameof(Index)); - - // //ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description", comment.PostId); - // return View(comment); - //} - public async Task EditComment(int? id) { if (id == null || _context.Comment == null) @@ -336,9 +249,7 @@ public async Task EditComment(int id, [Bind("Id,PostId,CreatedAt, } ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description", comment.PostId); return View(comment); - } - - + } public async Task DeleteComment(int? id) { @@ -386,57 +297,6 @@ private bool CommentExists(int id) { return _context.Comment.Any(e => e.Id == id); } - - - //public async Task Comment(int? id) - //{ - // if (id == null || _context.Post == null) - // { - // return NotFound(); - // } - - // var post = await _context.Post - // .FirstOrDefaultAsync(m => m.Id == id); - // if (post == null) - // { - // return NotFound(); - // } - // var comment = await _context.Comment.ToListAsync(); - // return View(post); - //} - - //[HttpPost] - //[ValidateAntiForgeryToken] - //public async Task Comment(int? id) - //{ - // if (id == null || _context.Post == null) - // { - // return NotFound(); - // } - - // var post = await _context.Post - // .FirstOrDefaultAsync(m => m.Id == id); - // if (post == null) - // { - // return NotFound(); - // } - // var comment = await _context.Comment.ToListAsync(); - // //return View(post); - - // Comment newComment = null; - // newComment.Creator = await _userManager.GetUserAsync(User); - // newComment.Post = await _context.Post.FindAsync(id); - // newComment.CreatedAt = DateTime.Now; - - // _context.Add(comment); - // await _context.SaveChangesAsync(); - // return RedirectToAction(nameof(Index)); - - // //ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description", comment.PostId); - // return View(); - - - } } diff --git a/Views/Posts/Create.cshtml b/Views/Posts/Create.cshtml index 489a6c7..cc0c370 100644 --- a/Views/Posts/Create.cshtml +++ b/Views/Posts/Create.cshtml @@ -30,10 +30,6 @@ - @*
    - - -
    *@
    diff --git a/Views/Posts/Details.cshtml b/Views/Posts/Details.cshtml index d115d23..9f0ad5a 100644 --- a/Views/Posts/Details.cshtml +++ b/Views/Posts/Details.cshtml @@ -34,7 +34,7 @@
    + @@ -75,6 +53,9 @@ + @@ -59,8 +65,11 @@ @Html.DisplayFor(modelItem => item.CreatedAt) } diff --git a/Views/Posts/Index.cshtml b/Views/Posts/Index.cshtml index 1886807..cf0e79c 100644 --- a/Views/Posts/Index.cshtml +++ b/Views/Posts/Index.cshtml @@ -57,7 +57,7 @@ @Html.DisplayFor(modelItem => item.Category.Name) diff --git a/Views/Posts/Edit.cshtml b/Views/Posts/Edit.cshtml index af9394f..d1c9649 100644 --- a/Views/Posts/Edit.cshtml +++ b/Views/Posts/Edit.cshtml @@ -32,7 +32,14 @@
    - Back to List + @if (User.IsInRole("Patient")) + { + Back to List + } + else + { + Back to List + }
    @section Scripts { diff --git a/Views/Posts/EditComment.cshtml b/Views/Posts/EditComment.cshtml index b84b929..7a25906 100644 --- a/Views/Posts/EditComment.cshtml +++ b/Views/Posts/EditComment.cshtml @@ -32,9 +32,6 @@ - @section Scripts { @{ diff --git a/Views/Posts/Index.cshtml b/Views/Posts/Index.cshtml index cf0e79c..dd95b2c 100644 --- a/Views/Posts/Index.cshtml +++ b/Views/Posts/Index.cshtml @@ -59,8 +59,8 @@ From 060802f29beff43a9b8df3c943c89f23f1e3290f Mon Sep 17 00:00:00 2001 From: viktor436 Date: Sun, 18 Dec 2022 16:10:08 +0200 Subject: [PATCH 14/19] Bug patient access denied resolved. Code improvements. --- Controllers/PostsController.cs | 44 ++++++++++---------- Views/Posts/Delete.cshtml | 9 +---- Views/Posts/Edit.cshtml | 9 +---- Views/Posts/IndexPatient.cshtml | 71 --------------------------------- Views/Shared/_Layout.cshtml | 2 +- 5 files changed, 23 insertions(+), 112 deletions(-) delete mode 100644 Views/Posts/IndexPatient.cshtml diff --git a/Controllers/PostsController.cs b/Controllers/PostsController.cs index ed90d4c..c4c9095 100644 --- a/Controllers/PostsController.cs +++ b/Controllers/PostsController.cs @@ -33,35 +33,31 @@ public PostsController(ApplicationDbContext context, UserManager us _userManager = userManager; } - [Authorize(Roles = "Doctor")] public ViewResult Index(string searchString) { - if (!String.IsNullOrEmpty(searchString)) + var str = _context.Post + .Include(p => p.Comments) + .ThenInclude(x => x.Creator) + .Include(t => t.Creator) + .OrderByDescending(s => s.DateCreated) + .Include(v => v.Category).ToList(); + if (User.IsInRole(Role.Doctor)) { - return View(_context.Post - .Where(s => s.Category.Name == searchString) - .Include(p => p.Comments) - .ThenInclude(x => x.Creator) - .Include(t => t.Creator) - .OrderByDescending(s => s.DateCreated) - .Include(v=>v.Category).ToList()); + if (!String.IsNullOrEmpty(searchString)) + { + return View(str.Where(s => s.Category.Name == searchString)); + } + return View(str); } - return View( _context.Post.Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator).Include(v=>v.Category).OrderByDescending(s => s.DateCreated).ToList()); - } - - [Authorize(Roles = "Patient")] - public ViewResult IndexPatient(string searchString) - { - if (!String.IsNullOrEmpty(searchString)) + else { - return View(_context.Post.Where(s => s.Creator.Email == User.Identity.Name) - .Where(s => s.Category.Name == searchString) - .Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator) - .Include(v => v.Category).OrderByDescending(s => s.DateCreated).ToList()); + if (!String.IsNullOrEmpty(searchString)) + { + return View(str.Where(s => s.Creator.Email == User.Identity.Name) + .Where(s => s.Category.Name == searchString)); + } + return View(str.Where(s => s.Creator.Email == User.Identity.Name)); } - return View(_context.Post.Where(s => s.Creator.Email == User.Identity.Name) - .Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator) - .Include(v => v.Category).OrderByDescending(s => s.DateCreated).ToList()); } // GET: Posts/Details/5 public async Task Details(int? id) @@ -270,7 +266,7 @@ public async Task EditComment(int id, [Bind("Id,PostId,CreatedAt, } ViewData["PostId"] = new SelectList(_context.Post, "Id", "Description", comment.PostId); return View(comment); - } + } public async Task DeleteComment(int? id) { diff --git a/Views/Posts/Delete.cshtml b/Views/Posts/Delete.cshtml index 9dac113..fc289ef 100644 --- a/Views/Posts/Delete.cshtml +++ b/Views/Posts/Delete.cshtml @@ -36,13 +36,6 @@
    | - @if (User.IsInRole("Patient")) - { - Back to List - } - else - { - Back to List - } + Back to List diff --git a/Views/Posts/Edit.cshtml b/Views/Posts/Edit.cshtml index 8c6630d..479c188 100644 --- a/Views/Posts/Edit.cshtml +++ b/Views/Posts/Edit.cshtml @@ -33,14 +33,7 @@
    - @if (User.IsInRole("Patient")) - { - Back to List - } - else - { - Back to List - } + Back to List
    @section Scripts { diff --git a/Views/Posts/IndexPatient.cshtml b/Views/Posts/IndexPatient.cshtml deleted file mode 100644 index 94be33e..0000000 --- a/Views/Posts/IndexPatient.cshtml +++ /dev/null @@ -1,71 +0,0 @@ -@model IEnumerable - -@{ - ViewData["Title"] = "Index"; - Layout = "~/Views/Shared/_Layout.cshtml"; -} - -
    -

    Posts

    -

    - Create New -

    -
    -@using (Html.BeginForm()) -{ -

    - Find by Category: @Html.TextBox("SearchString") - -

    -} -
    @Html.DisplayNameFor(model => model.Description) + @Html.DisplayNameFor(model => model.Creator) + @Html.DisplayNameFor(model => model.DateCreated) @Html.DisplayFor(modelItem => item.Description) + @Html.DisplayFor(model => item.Creator.FullName) + @Html.DisplayFor(modelItem => item.DateCreated) - Edit | - Details | - Delete + @if(item.Creator == User.Identity) + { + Delete + Edit + } + Details
    - @Html.DisplayNameFor(model => model.Post.Comments) + @Html.DisplayNameFor(model => model.Post.Creator) @Html.DisplayNameFor(model => model.Text) @@ -50,7 +50,7 @@ {
    - @Html.DisplayFor(modelItem => item.Creator.Email) + @Html.DisplayFor(modelItem => item.Creator.FullName) @Html.DisplayFor(modelItem => item.Text) @@ -69,10 +69,6 @@
    - @*
    - - -
    *@
    @@ -84,24 +80,5 @@
    - @*
    -
    -
    -
    -
    - - -
    -
    - - - -
    -
    - -
    -
    -
    -
    *@ diff --git a/Views/Posts/Index.cshtml b/Views/Posts/Index.cshtml index 1407af2..1886807 100644 --- a/Views/Posts/Index.cshtml +++ b/Views/Posts/Index.cshtml @@ -1,40 +1,15 @@ @model IEnumerable -@*@model IEnumerable*@ @{ ViewData["Title"] = "Index"; Layout = "~/Views/Shared/_Layout.cshtml"; }

    Posts

    - -@*
    -
    -
    -
    -
    - - - -
    -
    - -
    -
    -
    -
    *@ -@*
    - - -
    *@

    Create New

    -@*@Html.DropDownList("searchString",(SelectList)ViewBag.CategoryList,"Select Category") -Edit*@ @using (Html.BeginForm()) {

    @@ -57,6 +32,9 @@

    @Html.DisplayNameFor(model => model.DateCreated) + @Html.DisplayNameFor(model => model.Category) +
    @Html.DisplayFor(modelItem => item.DateCreated) + @Html.DisplayFor(modelItem => item.Category.Name) + @if(item.Creator == User.Identity) { From 772e6842d7c00bf57de3e7eed45a799be0c45937 Mon Sep 17 00:00:00 2001 From: viktor436 Date: Thu, 15 Dec 2022 22:11:49 +0200 Subject: [PATCH 09/19] MyPosts is working now.User can onlyvedit/delete his posts/comments. --- Controllers/PostsController.cs | 8 ++++ Views/Posts/Details.cshtml | 15 +++++-- Views/Posts/Index.cshtml | 2 +- Views/Posts/IndexPatient.cshtml | 70 +++++++++++++++++++++++++++++++++ Views/Shared/_Layout.cshtml | 4 +- 5 files changed, 93 insertions(+), 6 deletions(-) create mode 100644 Views/Posts/IndexPatient.cshtml diff --git a/Controllers/PostsController.cs b/Controllers/PostsController.cs index cd7c966..8c2f44d 100644 --- a/Controllers/PostsController.cs +++ b/Controllers/PostsController.cs @@ -42,6 +42,14 @@ public ViewResult Index(string searchString) return View( _context.Post.Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator).Include(v=>v.Category).ToList()); } + public ViewResult IndexPatient(string searchString) + { + if (!String.IsNullOrEmpty(searchString)) + { + return View(_context.Post.Where(s => s.Creator.Email == User.Identity.Name).Where(s => s.Category.Name == searchString).Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator).Include(v => v.Category).ToList()); + } + return View(_context.Post.Where(s => s.Creator.Email == User.Identity.Name).Include(p => p.Comments).ThenInclude(x => x.Creator).Include(t => t.Creator).Include(v => v.Category).ToList()); + } // GET: Posts/Details/5 public async Task Details(int? id) { diff --git a/Views/Posts/Details.cshtml b/Views/Posts/Details.cshtml index 9f0ad5a..181dea7 100644 --- a/Views/Posts/Details.cshtml +++ b/Views/Posts/Details.cshtml @@ -11,6 +11,12 @@

    Post


    +
    + @Html.DisplayNameFor(model => model.Post.Creator) +
    +
    + @Html.DisplayFor(model => model.Post.Creator.FullName) +
    @Html.DisplayNameFor(model => model.Post.Title)
    @@ -40,7 +46,7 @@ @Html.DisplayNameFor(model => model.Text)
    - @Html.DisplayNameFor(model => model.Text) + Date Created
    - EditComment | - DeleteComment + @if (item.Creator.Email == User.Identity.Name) + { + EditComment + DeleteComment + }
    - @if(item.Creator == User.Identity) + @if (item.Creator.Email == User.Identity.Name) { Delete Edit diff --git a/Views/Posts/IndexPatient.cshtml b/Views/Posts/IndexPatient.cshtml new file mode 100644 index 0000000..cf0e79c --- /dev/null +++ b/Views/Posts/IndexPatient.cshtml @@ -0,0 +1,70 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Index"; + Layout = "~/Views/Shared/_Layout.cshtml"; +} + +

    Posts

    +

    + Create New +

    + +@using (Html.BeginForm()) +{ +

    + Find by Category: @Html.TextBox("SearchString") + +

    +} + + + + + + + + + + + + +@foreach (var item in Model) { + + + + + + + + +} + +
    + @Html.DisplayNameFor(model => model.Title) + + @Html.DisplayNameFor(model => model.Description) + + @Html.DisplayNameFor(model => model.Creator) + + @Html.DisplayNameFor(model => model.DateCreated) + + @Html.DisplayNameFor(model => model.Category) +
    + @Html.DisplayFor(modelItem => item.Title) + + @Html.DisplayFor(modelItem => item.Description) + + @Html.DisplayFor(model => item.Creator.FullName) + + @Html.DisplayFor(modelItem => item.DateCreated) + + @Html.DisplayFor(modelItem => item.Category.Name) + + @if (item.Creator.Email == User.Identity.Name) + { + Delete + Edit + } + Details +
    diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index 4ff4f44..aa409dd 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -39,7 +39,7 @@ - +
    @if (item.Creator.Email == User.Identity.Name) { - EditComment + EditComment| DeleteComment } @if (item.Creator.Email == User.Identity.Name) { - Delete - Edit + Delete| + Edit| } Details
    - - - - - - - - - - - -@foreach (var item in Model) { - - - - - - - - -} - -
    - @Html.DisplayNameFor(model => model.Title) - - @Html.DisplayNameFor(model => model.Description) - - @Html.DisplayNameFor(model => model.Creator) - - @Html.DisplayNameFor(model => model.DateCreated) - - @Html.DisplayNameFor(model => model.Category) -
    - @Html.DisplayFor(modelItem => item.Title) - - @Html.DisplayFor(modelItem => item.Description) - - @Html.DisplayFor(model => item.Creator.FullName) - - @Html.DisplayFor(modelItem => item.DateCreated) - - @Html.DisplayFor(modelItem => item.Category.Name) - - @if (item.Creator.Email == User.Identity.Name) - { - Delete - Edit - } - Details -
    diff --git a/Views/Shared/_Layout.cshtml b/Views/Shared/_Layout.cshtml index aa409dd..1eea4eb 100644 --- a/Views/Shared/_Layout.cshtml +++ b/Views/Shared/_Layout.cshtml @@ -59,7 +59,7 @@ } @if (User.IsInRole("Patient")) { -
  • Досие
  • +
  • Досие
  • } @if(User.IsInRole("Doctor")||User.IsInRole("Admin")) { From 8134828cb9f336b9fab1f7b6466b162e155e9e30 Mon Sep 17 00:00:00 2001 From: viktor436 Date: Sun, 18 Dec 2022 19:50:36 +0200 Subject: [PATCH 15/19] "Dr." is auto displayed before full name . --- Controllers/PostsController.cs | 2 -- Models/DefaultUser.cs | 11 ++++++++--- Models/Post.cs | 1 + 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/Controllers/PostsController.cs b/Controllers/PostsController.cs index c4c9095..74cfdb0 100644 --- a/Controllers/PostsController.cs +++ b/Controllers/PostsController.cs @@ -137,7 +137,6 @@ public async Task Edit(int? id) { return NotFound(); } - var post = await _context.Post.FindAsync(id); if (post == null) { @@ -157,7 +156,6 @@ public async Task Edit(int id, [Bind("Id,Title,Description,DateCr { return NotFound(); } - if (ModelState.IsValid) { try diff --git a/Models/DefaultUser.cs b/Models/DefaultUser.cs index a759471..ae57440 100644 --- a/Models/DefaultUser.cs +++ b/Models/DefaultUser.cs @@ -1,4 +1,5 @@ using Humanizer; +using MessagePack; using Microsoft.AspNetCore.Identity; using System.ComponentModel.DataAnnotations; @@ -11,19 +12,23 @@ public class DefaultUser : IdentityUser public string? LastName { get; set; } public string? Gender { get; set; } - + public string FullName { - get { return FirstName + " " + LastName; } + get { + if (DoctorUID != null) { return "Dr." + FirstName + " " + LastName; } + return FirstName + " " + LastName; + } } - [DataType(DataType.Date)] public DateTime? DateOfBirth { get; set; } public string? DoctorUID { get; set; } public ICollection FriendsOf { get; set; } public ICollection Friends { get; set; } + + } public class UserFriendship diff --git a/Models/Post.cs b/Models/Post.cs index 2432788..762a275 100644 --- a/Models/Post.cs +++ b/Models/Post.cs @@ -20,6 +20,7 @@ public class Post //!! public int CategoryId { get; set; } + public Category Category { get; set; } } From 77adf1b9bb99eab8ee211b52d789b37ff0e1569b Mon Sep 17 00:00:00 2001 From: Viktor Kolev Date: Mon, 19 Dec 2022 15:32:02 +0200 Subject: [PATCH 16/19] Program --- Program.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/Program.cs b/Program.cs index a9da318..948544a 100644 --- a/Program.cs +++ b/Program.cs @@ -20,7 +20,13 @@ builder.Services.AddDefaultIdentity(options => options.SignIn.RequireConfirmedAccount = true) .AddRoles() .AddEntityFrameworkStores(); - + +builder.Services.Configure(options => +{ + options.Lockout.MaxFailedAccessAttempts = 10; + options.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(10); +}); + builder.Services.AddControllersWithViews(); builder.Services.AddTransient(); From c23ec955f24312bd2a4ab690e31d4cb05c87f455 Mon Sep 17 00:00:00 2001 From: Viktor Kolev Date: Mon, 19 Dec 2022 15:35:33 +0200 Subject: [PATCH 17/19] Anti-XSS --- DoctorSystem.csproj | 1 + Program.cs | 9 +++++++- Singleton/AntiXssConverter.cs | 28 ++++++++++++++++++++++++ Singleton/AntiXssMiddleware.cs | 40 ++++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 Singleton/AntiXssConverter.cs create mode 100644 Singleton/AntiXssMiddleware.cs diff --git a/DoctorSystem.csproj b/DoctorSystem.csproj index 70018ef..a4392dc 100644 --- a/DoctorSystem.csproj +++ b/DoctorSystem.csproj @@ -9,6 +9,7 @@ + diff --git a/Program.cs b/Program.cs index 51e9f0c..3f308ef 100644 --- a/Program.cs +++ b/Program.cs @@ -30,7 +30,13 @@ builder.Services.Configure(builder.Configuration.GetSection("Config")); -builder.Services.AddControllersWithViews(); +builder.Services.AddControllersWithViews() + .AddJsonOptions(options => + { + options.JsonSerializerOptions.Converters.Add(new AntiXssConverter()); + }); + + builder.Services.AddTransient(); var configuration = builder.Configuration; @@ -84,6 +90,7 @@ app.UseAuthentication(); app.UseAuthorization(); +app.UseMiddleware(); app.MapControllerRoute( name: "default", diff --git a/Singleton/AntiXssConverter.cs b/Singleton/AntiXssConverter.cs new file mode 100644 index 0000000..8d3ea55 --- /dev/null +++ b/Singleton/AntiXssConverter.cs @@ -0,0 +1,28 @@ +using Ganss.Xss; +using System.Text.Json; +using System.Text.Json.Serialization; + +namespace DoctorSystem.Singleton +{ + public class AntiXssConverter : JsonConverter + { + public override string? Read(ref Utf8JsonReader reader, + Type typeToConvert, JsonSerializerOptions options) + { + var sanitiser = new HtmlSanitizer(); + var raw = reader.GetString(); + var sanitised = sanitiser.Sanitize(raw); + + if (raw == sanitised) + return sanitised; + + throw new Exception("XSS injection detected."); + } + + public override void Write(Utf8JsonWriter writer, string value, + JsonSerializerOptions options) + { + writer.WriteStringValue(value); + } + } +} diff --git a/Singleton/AntiXssMiddleware.cs b/Singleton/AntiXssMiddleware.cs new file mode 100644 index 0000000..5d3cff9 --- /dev/null +++ b/Singleton/AntiXssMiddleware.cs @@ -0,0 +1,40 @@ +using Ganss.Xss; +using System.Text; + +namespace DoctorSystem.Singleton +{ + public class AntiXssMiddleware + { + private readonly RequestDelegate _next; + + public AntiXssMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task Invoke(HttpContext httpContext) + { + // enable buffering so that the request can be read by the model binders next + httpContext.Request.EnableBuffering(); + + // leaveOpen: true to leave the stream open after disposing, + // so it can be read by the model binders + using (var streamReader = new StreamReader + (httpContext.Request.Body, Encoding.UTF8, leaveOpen: true)) + { + var raw = await streamReader.ReadToEndAsync(); + var sanitiser = new HtmlSanitizer(); + var sanitised = sanitiser.Sanitize(raw); + + if (raw != sanitised) + { + throw new BadRequestException("XSS injection detected from middleware."); + } + } + + // rewind the stream for the next middleware + httpContext.Request.Body.Seek(0, SeekOrigin.Begin); + await _next.Invoke(httpContext); + } + } +} From 744f2f9650783edc69d3c9bfd9638bb088fdfa48 Mon Sep 17 00:00:00 2001 From: Viktor Kolev Date: Mon, 19 Dec 2022 15:38:20 +0200 Subject: [PATCH 18/19] middleware --- Singleton/AntiXssMiddleware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Singleton/AntiXssMiddleware.cs b/Singleton/AntiXssMiddleware.cs index 5d3cff9..6991382 100644 --- a/Singleton/AntiXssMiddleware.cs +++ b/Singleton/AntiXssMiddleware.cs @@ -28,7 +28,7 @@ public async Task Invoke(HttpContext httpContext) if (raw != sanitised) { - throw new BadRequestException("XSS injection detected from middleware."); + throw new Exception("XSS injection detected from middleware."); } } From d8a178f5361dbd2c06f18094b04bd9364ffeeaf6 Mon Sep 17 00:00:00 2001 From: Viktor Kolev Date: Thu, 26 Jan 2023 15:46:07 +0200 Subject: [PATCH 19/19] No exception on Middleware detection --- Singleton/AntiXssMiddleware.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Singleton/AntiXssMiddleware.cs b/Singleton/AntiXssMiddleware.cs index 6991382..df962cb 100644 --- a/Singleton/AntiXssMiddleware.cs +++ b/Singleton/AntiXssMiddleware.cs @@ -28,7 +28,7 @@ public async Task Invoke(HttpContext httpContext) if (raw != sanitised) { - throw new Exception("XSS injection detected from middleware."); + Console.WriteLine("XSS injection detected from middleware."); } }