From 59337f17d9c24a4eb8603c3791200c2fc2d4a1fb Mon Sep 17 00:00:00 2001 From: Michael Gordeev Date: Wed, 11 Dec 2019 15:29:40 +0300 Subject: [PATCH] Redone contacts, gallery, projects and resume pages. Added some admin panel pages --- .../MyWebsite/Areas/API/FoxTubeController.cs | 76 ++++++++- .../MyWebsite/Areas/API/Models/Changelog.cs | 18 +++ .../API/Models/FoxTubeDatabaseContext.cs | 16 ++ .../MyWebsite/Areas/API/Models/InboxItem.cs | 42 +++++ .../MyWebsite/Areas/API/Models/Message.cs | 45 ++++++ .../Areas/API/Models/MetricsPackage.cs | 28 ++++ .../Admin/Controllers/ContactsController.cs | 32 ++++ MyWebsite/MyWebsite/Areas/Admin/NewFile.txt | 0 .../Areas/Admin/Views/Contacts/Edit.cshtml | 45 ++++++ .../Areas/Admin/Views/Contacts/Index.cshtml | 55 +++++++ .../Areas/Admin/Views/_ViewImports.cshtml | 3 + .../Areas/Admin/Views/_ViewStart.cshtml | 3 + .../MyWebsite/Controllers/AdminController.cs | 59 +++++++ .../Controllers/GalleryController.cs | 3 +- .../MyWebsite/Controllers/HomeController.cs | 10 +- .../MyWebsite/Controllers/ResumeController.cs | 36 +++++ MyWebsite/MyWebsite/Helpers/Cryptography.cs | 144 ++++++++++++++++++ MyWebsite/MyWebsite/Models/Badge.cs | 18 +++ MyWebsite/MyWebsite/Models/DatabaseContext.cs | 3 + MyWebsite/MyWebsite/Models/Image.cs | 19 ++- MyWebsite/MyWebsite/Models/Link.cs | 15 +- MyWebsite/MyWebsite/Models/LoginModel.cs | 16 ++ MyWebsite/MyWebsite/Models/MetricsPackage.cs | 12 -- MyWebsite/MyWebsite/Models/Project.cs | 12 ++ MyWebsite/MyWebsite/Models/Resume.cs | 15 ++ MyWebsite/MyWebsite/MyWebsite.csproj | 10 ++ MyWebsite/MyWebsite/MyWebsite.csproj.user | 3 +- MyWebsite/MyWebsite/Startup.cs | 17 ++- MyWebsite/MyWebsite/Views/Admin/Index.cshtml | 23 +++ MyWebsite/MyWebsite/Views/Admin/Login.cshtml | 27 ++++ .../MyWebsite/Views/Gallery/Details.cshtml | 6 +- .../MyWebsite/Views/Gallery/Index.cshtml | 4 +- MyWebsite/MyWebsite/Views/Resume/Index.cshtml | 17 +++ MyWebsite/MyWebsite/Views/Resume/Print.cshtml | 23 +++ .../Views/{Home => Shared}/Contacts.cshtml | 4 +- MyWebsite/MyWebsite/Views/Shared/Error.cshtml | 31 ++-- .../Views/{Home => Shared}/Index.cshtml | 0 .../MyWebsite/Views/Shared/Projects.cshtml | 35 +++++ .../MyWebsite/Views/Shared/_Layout.cshtml | 6 +- .../Shared/_ValidationScriptsPartial.cshtml | 2 - MyWebsite/MyWebsite/appsettings.json | 3 +- MyWebsite/MyWebsite/wwwroot/css/Admin.css | 21 +++ MyWebsite/MyWebsite/wwwroot/css/Projects.css | 10 -- MyWebsite/MyWebsite/wwwroot/css/Style.css | 9 +- .../MyWebsite/wwwroot/images/Badges/nuget.png | Bin 0 -> 4527 bytes 45 files changed, 900 insertions(+), 76 deletions(-) create mode 100644 MyWebsite/MyWebsite/Areas/API/Models/Changelog.cs create mode 100644 MyWebsite/MyWebsite/Areas/API/Models/FoxTubeDatabaseContext.cs create mode 100644 MyWebsite/MyWebsite/Areas/API/Models/InboxItem.cs create mode 100644 MyWebsite/MyWebsite/Areas/API/Models/Message.cs create mode 100644 MyWebsite/MyWebsite/Areas/API/Models/MetricsPackage.cs create mode 100644 MyWebsite/MyWebsite/Areas/Admin/Controllers/ContactsController.cs create mode 100644 MyWebsite/MyWebsite/Areas/Admin/NewFile.txt create mode 100644 MyWebsite/MyWebsite/Areas/Admin/Views/Contacts/Edit.cshtml create mode 100644 MyWebsite/MyWebsite/Areas/Admin/Views/Contacts/Index.cshtml create mode 100644 MyWebsite/MyWebsite/Areas/Admin/Views/_ViewImports.cshtml create mode 100644 MyWebsite/MyWebsite/Areas/Admin/Views/_ViewStart.cshtml create mode 100644 MyWebsite/MyWebsite/Controllers/AdminController.cs create mode 100644 MyWebsite/MyWebsite/Controllers/ResumeController.cs create mode 100644 MyWebsite/MyWebsite/Helpers/Cryptography.cs create mode 100644 MyWebsite/MyWebsite/Models/Badge.cs create mode 100644 MyWebsite/MyWebsite/Models/LoginModel.cs delete mode 100644 MyWebsite/MyWebsite/Models/MetricsPackage.cs create mode 100644 MyWebsite/MyWebsite/Models/Resume.cs create mode 100644 MyWebsite/MyWebsite/Views/Admin/Index.cshtml create mode 100644 MyWebsite/MyWebsite/Views/Admin/Login.cshtml create mode 100644 MyWebsite/MyWebsite/Views/Resume/Index.cshtml create mode 100644 MyWebsite/MyWebsite/Views/Resume/Print.cshtml rename MyWebsite/MyWebsite/Views/{Home => Shared}/Contacts.cshtml (88%) rename MyWebsite/MyWebsite/Views/{Home => Shared}/Index.cshtml (100%) create mode 100644 MyWebsite/MyWebsite/Views/Shared/Projects.cshtml delete mode 100644 MyWebsite/MyWebsite/Views/Shared/_ValidationScriptsPartial.cshtml create mode 100644 MyWebsite/MyWebsite/wwwroot/images/Badges/nuget.png diff --git a/MyWebsite/MyWebsite/Areas/API/FoxTubeController.cs b/MyWebsite/MyWebsite/Areas/API/FoxTubeController.cs index d283ca9..1a1f26f 100644 --- a/MyWebsite/MyWebsite/Areas/API/FoxTubeController.cs +++ b/MyWebsite/MyWebsite/Areas/API/FoxTubeController.cs @@ -1,6 +1,8 @@ using System; +using System.Linq; +using System.Collections.Generic; using Microsoft.AspNetCore.Mvc; -using MyWebsite.Models; +using MyWebsite.Areas.API.Models; namespace MyWebsite.Areas.API { @@ -9,6 +11,10 @@ namespace MyWebsite.Areas.API [ApiController] public class FoxTubeController : ControllerBase { + FoxTubeDatabaseContext db; + public FoxTubeController(FoxTubeDatabaseContext context) => + db = context; + [HttpPost("AddMetrics")] public string AddMetrics() { @@ -16,34 +22,88 @@ namespace MyWebsite.Areas.API { Title = Request.Form["Title"], Content = Request.Form["Content"], - Version = Request.Form["Version"] + Version = Request.Form["Version"], + TimeStamp = DateTime.Now }; - // TODO: Insert package to database + db.Metrics.Add(metrics); + db.SaveChanges(); - return metrics.Id.ToString(); + return db.Metrics.FirstOrDefault(i => i.TimeStamp == metrics.TimeStamp)?.Id.ToString(); } [HttpGet("Messages")] - public IActionResult Messages(bool toast = false, DateTime? publishedAfter = null, string lang = "en-US") + public object Messages(bool toast = false, DateTime? publishedAfter = null, string lang = "en-US") { if(toast) { + Message message = publishedAfter.HasValue ? + db.Messages.FirstOrDefault(i => i.PublishedAt >= publishedAfter && i.PublishedAt <= DateTime.Now && i.Language == lang) : + db.Messages.OrderByDescending(i => i.PublishedAt).FirstOrDefault(); + if (message == null) + return NoContent(); + + return $@" + + + + + {message.Title} + {message.Description} + + + "; } + else + { + List messages = db.Messages.Where(i => i.PublishedAt <= DateTime.Now).ToList(); - return NoContent(); + return messages; + } } [HttpGet("Changelogs")] - public IActionResult Changelogs(bool toast = false, string version = null, string lang = "en-US") + public object Changelogs(bool toast = false, string version = null, string lang = "en-US") { + if (string.IsNullOrWhiteSpace(version)) + throw new ArgumentNullException("You must specify required version number"); + if(toast) { + Changelog changelog = db.Changelogs.FirstOrDefault(i => i.Version == version && i.Language == lang); + if (changelog == null) + return NoContent(); + return $@" + + + + + {changelog.Description} + {changelog.Title} + + + "; } + else + { + List changelogs = db.Changelogs.Where(i => !IsVersionGreater(i.Version, version)).ToList(); - return NoContent(); + return changelogs; + } + } + + private static bool IsVersionGreater(string versionOne, string versionTwo) + { + string[] v1 = versionOne.Split('.'); + string[] v2 = versionTwo.Split('.'); + + for (int i = 0; i < v1.Length && i < v2.Length; i++) + if (byte.Parse(v1[i]) > byte.Parse(v2[i])) + return true; + + return false; } } } \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Areas/API/Models/Changelog.cs b/MyWebsite/MyWebsite/Areas/API/Models/Changelog.cs new file mode 100644 index 0000000..cb3b08d --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/API/Models/Changelog.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations.Schema; +using System.ComponentModel.DataAnnotations; + +namespace MyWebsite.Areas.API.Models +{ + public class Changelog : Message + { + [Required] + [Column(TypeName = "varchar(5)")] + public string Version { get; set; } + + public Changelog() + { + Icon = "https://xfox111.net/projects-assets/FoxTube/ChangelogIcon.png"; + HeroImage = "https://xfox111.net/projects-assets/FoxTube/ChangelogHero.png"; + } + } +} diff --git a/MyWebsite/MyWebsite/Areas/API/Models/FoxTubeDatabaseContext.cs b/MyWebsite/MyWebsite/Areas/API/Models/FoxTubeDatabaseContext.cs new file mode 100644 index 0000000..b460f39 --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/API/Models/FoxTubeDatabaseContext.cs @@ -0,0 +1,16 @@ +using Microsoft.EntityFrameworkCore; + +namespace MyWebsite.Areas.API.Models +{ + public class FoxTubeDatabaseContext : DbContext + { + public DbSet Metrics { get; set; } + public DbSet Messages { get; set; } + public DbSet Changelogs { get; set; } + + public FoxTubeDatabaseContext(DbContextOptions options) : base(options) + { + Database.EnsureCreated(); + } + } +} diff --git a/MyWebsite/MyWebsite/Areas/API/Models/InboxItem.cs b/MyWebsite/MyWebsite/Areas/API/Models/InboxItem.cs new file mode 100644 index 0000000..b0c86da --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/API/Models/InboxItem.cs @@ -0,0 +1,42 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace MyWebsite.Areas.API.Models +{ + public class InboxItem + { + // Metadata + [Key] + [Required] + [Column(TypeName = "varchae(20)")] + public string Id { get; set; } + + [Required] + public DateTime PublishedAt { get; set; } = DateTime.Now; + + + // Content + [Required] + [Column(TypeName = "varchar(255)")] + public string Title { get; set; } + + [Column(TypeName = "varchar(255)")] + public string Description { get; set; } + + [Required] + [Column(TypeName = "text")] + public string Content { get; set; } + + + // Media + [Column(TypeName = "varchar(255")] + public string Icon { get; set; } + + [Column(TypeName = "varchar(255")] + public string HeroImage { get; set; } + + [Column(TypeName = "varchar(255)")] + public string HtmlEmbed { get; set; } + } +} diff --git a/MyWebsite/MyWebsite/Areas/API/Models/Message.cs b/MyWebsite/MyWebsite/Areas/API/Models/Message.cs new file mode 100644 index 0000000..d6102ea --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/API/Models/Message.cs @@ -0,0 +1,45 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace MyWebsite.Areas.API.Models +{ + public class Message + { + // Metadata + [Key] + [Column(TypeName = "varchar(50)")] + public string Id { get; set; } + + [Required] + public DateTime PublishedAt { get; set; } = DateTime.Now; + + [Required] + [Column(TypeName = "varchar(10)")] + public string Language { get; set; } = "en-US"; + + + // Content + [Required] + [Column(TypeName = "varchar(50)")] + public string Title { get; set; } + + [Column(TypeName = "varchar(100)")] + public string Description { get; set; } + + [Required] + [Column(TypeName = "text")] + public string Content { get; set; } + + + // Media + [Column(TypeName = "varchar(255")] + public string Icon { get; set; } = "https://xfox111.net/projects-assets/FoxTube/DefaultIcon.png"; + + [Column(TypeName = "varchar(255")] + public string HeroImage { get; set; } = "https://xfox111.net/projects-assets/FoxTube/DefaultHero.png"; + + [Column(TypeName = "varchar(255)")] + public string HtmlEmbed { get; set; } + } +} diff --git a/MyWebsite/MyWebsite/Areas/API/Models/MetricsPackage.cs b/MyWebsite/MyWebsite/Areas/API/Models/MetricsPackage.cs new file mode 100644 index 0000000..0041b65 --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/API/Models/MetricsPackage.cs @@ -0,0 +1,28 @@ +using System; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace MyWebsite.Areas.API.Models +{ + public class MetricsPackage + { + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + + [Required] + [Column(TypeName = "varchar(100)")] + public string Title { get; set; } + + [Required] + [Column(TypeName = "mediumtext")] + public string Content { get; set; } + + [Required] + [Column(TypeName = "varchar(5)")] + public string Version { get; set; } + + [Required] + public DateTime TimeStamp { get; set; } + } +} diff --git a/MyWebsite/MyWebsite/Areas/Admin/Controllers/ContactsController.cs b/MyWebsite/MyWebsite/Areas/Admin/Controllers/ContactsController.cs new file mode 100644 index 0000000..459e459 --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/Admin/Controllers/ContactsController.cs @@ -0,0 +1,32 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Mvc; +using MyWebsite.Models; + +namespace MyWebsite.Areas.Admin.Controllers +{ + [Area("Admin")] + public class ContactsController : Controller + { + public ContactsController(DatabaseContext context) => + Startup.Database = context; + + public IActionResult Index() => + View(Startup.Database.Links); + + [HttpGet] + public IActionResult Edit(string id) => + View(Startup.Database.Links.Find(id)); + + [HttpPost] + public IActionResult Edit(Link model) + { + Startup.Database.Links.Update(model); + Startup.Database.SaveChanges(); + + return RedirectToAction("Index"); + } + } +} \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Areas/Admin/NewFile.txt b/MyWebsite/MyWebsite/Areas/Admin/NewFile.txt new file mode 100644 index 0000000..e69de29 diff --git a/MyWebsite/MyWebsite/Areas/Admin/Views/Contacts/Edit.cshtml b/MyWebsite/MyWebsite/Areas/Admin/Views/Contacts/Edit.cshtml new file mode 100644 index 0000000..e9630a0 --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/Admin/Views/Contacts/Edit.cshtml @@ -0,0 +1,45 @@ +@using MyWebsite.Models; +@model Link; +@{ + ViewData["Title"] = "Edit link"; +} + +
+

Edit contact link

+

Back to the list

+
+ +
+
+
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + + +
+
+ + +
+
+ +
+
+
+ + \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Areas/Admin/Views/Contacts/Index.cshtml b/MyWebsite/MyWebsite/Areas/Admin/Views/Contacts/Index.cshtml new file mode 100644 index 0000000..1714b5f --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/Admin/Views/Contacts/Index.cshtml @@ -0,0 +1,55 @@ +@model IEnumerable + +@{ + ViewData["Title"] = "Contacts - Admin panel"; +} + +
+

Contact links list

+

Back to admin panel

+
+ +
+

+ // + Create New +

+ + + + + + + + + + + + @foreach (var item in Model.OrderByDescending(i => i.CanContactMe)) + { + + + + + + + + + } + +
NameTitleUsernameURLContact link
+ @Html.DisplayFor(modelItem => item.Name) + + @Html.DisplayFor(modelItem => item.Title) + + @Html.DisplayFor(modelItem => item.Username) + + @Html.DisplayFor(modelItem => item.Url) + + @Html.DisplayFor(modelItem => item.CanContactMe) + + @Html.ActionLink("Edit", "Edit", new { id = item.Name }) | + @Html.ActionLink("Delete", "Delete", new { id = item.Name }) +
+
+ + \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Areas/Admin/Views/_ViewImports.cshtml b/MyWebsite/MyWebsite/Areas/Admin/Views/_ViewImports.cshtml new file mode 100644 index 0000000..5ecdfbb --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/Admin/Views/_ViewImports.cshtml @@ -0,0 +1,3 @@ +@using MyWebsite +@using MyWebsite.Models +@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers diff --git a/MyWebsite/MyWebsite/Areas/Admin/Views/_ViewStart.cshtml b/MyWebsite/MyWebsite/Areas/Admin/Views/_ViewStart.cshtml new file mode 100644 index 0000000..a5f1004 --- /dev/null +++ b/MyWebsite/MyWebsite/Areas/Admin/Views/_ViewStart.cshtml @@ -0,0 +1,3 @@ +@{ + Layout = "_Layout"; +} diff --git a/MyWebsite/MyWebsite/Controllers/AdminController.cs b/MyWebsite/MyWebsite/Controllers/AdminController.cs new file mode 100644 index 0000000..ee81c54 --- /dev/null +++ b/MyWebsite/MyWebsite/Controllers/AdminController.cs @@ -0,0 +1,59 @@ +using System.Linq; +using System.Security.Claims; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Authentication; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using MyWebsite.Helpers; +using MyWebsite.Models; + +namespace MyWebsite.Controllers +{ + [Authorize] + public class AdminController : Controller + { + public AdminController(DatabaseContext context) => + Startup.Database = context; + + public IActionResult Index() => + View(); + + [AllowAnonymous] + [HttpGet] + public IActionResult Login() => + View(); + + [AllowAnonymous] + [HttpPost] + [ValidateAntiForgeryToken] + public async Task Login(LoginModel model) + { + if (!ModelState.IsValid) + { + ModelState.AddModelError("Authorization error", "Invalid data"); + return View(model); + } + + LoginModel user = Startup.Database.Users.FirstOrDefault(i => i.Email == model.Email); + if (user == null || !Cryptography.VerifyHash(model.Password, "SHA512", user.Password)) + { + ModelState.AddModelError("Authorization error", "Invaild e-mail or password"); + return View(model); + } + + Claim claim = new Claim(ClaimsIdentity.DefaultNameClaimType, user.Email); + + ClaimsIdentity id = new ClaimsIdentity(new Claim[] { claim }, "ApplicationCookie", ClaimsIdentity.DefaultNameClaimType, ClaimsIdentity.DefaultRoleClaimType); + await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, new ClaimsPrincipal(id)); + + return RedirectToAction("Index", "Admin"); + } + + public async Task Logout() + { + await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); + return RedirectToAction("Login", "Admin"); + } + } +} \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Controllers/GalleryController.cs b/MyWebsite/MyWebsite/Controllers/GalleryController.cs index 41105a0..5e44ea9 100644 --- a/MyWebsite/MyWebsite/Controllers/GalleryController.cs +++ b/MyWebsite/MyWebsite/Controllers/GalleryController.cs @@ -1,6 +1,5 @@ using Microsoft.AspNetCore.Mvc; using MyWebsite.Models; -using System.Threading.Tasks; using System.Linq; namespace MyWebsite.Controllers @@ -13,7 +12,7 @@ namespace MyWebsite.Controllers public IActionResult Index() => View(); - public async Task Details(string id) => + public IActionResult Details(string id) => View(Startup.Database.Gallery.FirstOrDefault(i => i.FileName == id)); } } \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Controllers/HomeController.cs b/MyWebsite/MyWebsite/Controllers/HomeController.cs index 6255857..cb65705 100644 --- a/MyWebsite/MyWebsite/Controllers/HomeController.cs +++ b/MyWebsite/MyWebsite/Controllers/HomeController.cs @@ -1,8 +1,6 @@ -using System.Collections.Generic; -using System.Diagnostics; +using System.Diagnostics; using Microsoft.AspNetCore.Mvc; using MyWebsite.Models; -using System.Linq; namespace MyWebsite.Controllers { @@ -18,9 +16,13 @@ namespace MyWebsite.Controllers public IActionResult Contacts() => View(); + [Route("Projects")] + public IActionResult Projects() => + View(); + [Route("Error")] [ResponseCache(Duration = 0, Location = ResponseCacheLocation.None, NoStore = true)] public IActionResult Error() => View("Views/Shared/Error.cshtml", new ErrorViewModel { RequestId = Activity.Current?.Id ?? HttpContext.TraceIdentifier }); } -} +} \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Controllers/ResumeController.cs b/MyWebsite/MyWebsite/Controllers/ResumeController.cs new file mode 100644 index 0000000..9b4281b --- /dev/null +++ b/MyWebsite/MyWebsite/Controllers/ResumeController.cs @@ -0,0 +1,36 @@ +using Microsoft.AspNetCore.Mvc; +using MyWebsite.Models; +using SelectPdf; + +namespace MyWebsite.Controllers +{ + public class ResumeController : Controller + { + public ResumeController(DatabaseContext context) => + Startup.Database = context; + + public IActionResult Index() => + View(Startup.Database.Resume.Find("en-US")); + + public IActionResult Print() => + View(Startup.Database.Resume.Find("en-US")); + + public IActionResult Download() + { + HtmlToPdf converter = new HtmlToPdf(); + converter.Options.MarginTop = 25; + converter.Options.MarginBottom = 25; + converter.Options.MarginLeft = 25; + converter.Options.MarginRight = 25; + PdfDocument doc = converter.ConvertHtmlString( + $@" + {Startup.Database.Resume.Find("en-US").Content} + + + "); + byte[] data = doc.Save(); + doc.Close(); + return File(data, "application/pdf", "[Michael Gordeev] CV.pdf"); + } + } +} \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Helpers/Cryptography.cs b/MyWebsite/MyWebsite/Helpers/Cryptography.cs new file mode 100644 index 0000000..f4f2c55 --- /dev/null +++ b/MyWebsite/MyWebsite/Helpers/Cryptography.cs @@ -0,0 +1,144 @@ +using System; +using System.Security.Cryptography; +using System.Text; + +namespace MyWebsite.Helpers +{ + public class Cryptography + { + public static string ComputeHash(string plainText, string hashAlgorithm, byte[] saltBytes) + { + // If salt is not specified, generate it. + if (saltBytes == null) + { + // Define min and max salt sizes. + int minSaltSize = 4; + int maxSaltSize = 8; + + // Generate a random number for the size of the salt. + Random random = new Random(); + int saltSize = random.Next(minSaltSize, maxSaltSize); + + // Allocate a byte array, which will hold the salt. + saltBytes = new byte[saltSize]; + + // Initialize a random number generator. + RNGCryptoServiceProvider rng = new RNGCryptoServiceProvider(); + + // Fill the salt with cryptographically strong byte values. + rng.GetNonZeroBytes(saltBytes); + } + + // Convert plain text into a byte array. + byte[] plainTextBytes = Encoding.UTF8.GetBytes(plainText); + + // Allocate array, which will hold plain text and salt. + byte[] plainTextWithSaltBytes = + new byte[plainTextBytes.Length + saltBytes.Length]; + + // Copy plain text bytes into resulting array. + for (int i = 0; i < plainTextBytes.Length; i++) + plainTextWithSaltBytes[i] = plainTextBytes[i]; + + // Append salt bytes to the resulting array. + for (int i = 0; i < saltBytes.Length; i++) + plainTextWithSaltBytes[plainTextBytes.Length + i] = saltBytes[i]; + + HashAlgorithm hash; + + // Make sure hashing algorithm name is specified. + if (hashAlgorithm == null) + hashAlgorithm = ""; + + // Initialize appropriate hashing algorithm class. + switch (hashAlgorithm.ToUpper()) + { + + case "SHA384": + hash = new SHA384Managed(); + break; + + case "SHA512": + hash = new SHA512Managed(); + break; + + default: + hash = new MD5CryptoServiceProvider(); + break; + } + + // Compute hash value of our plain text with appended salt. + byte[] hashBytes = hash.ComputeHash(plainTextWithSaltBytes); + + // Create array which will hold hash and original salt bytes. + byte[] hashWithSaltBytes = new byte[hashBytes.Length + + saltBytes.Length]; + + // Copy hash bytes into resulting array. + for (int i = 0; i < hashBytes.Length; i++) + hashWithSaltBytes[i] = hashBytes[i]; + + // Append salt bytes to the result. + for (int i = 0; i < saltBytes.Length; i++) + hashWithSaltBytes[hashBytes.Length + i] = saltBytes[i]; + + // Convert result into a base64-encoded string. + string hashValue = Convert.ToBase64String(hashWithSaltBytes); + + // Return the result. + return hashValue; + } + + public static bool VerifyHash(string plainText, string hashAlgorithm, string hashValue) + { + + // Convert base64-encoded hash value into a byte array. + byte[] hashWithSaltBytes = Convert.FromBase64String(hashValue); + + // We must know size of hash (without salt). + int hashSizeInBits, hashSizeInBytes; + + // Make sure that hashing algorithm name is specified. + if (hashAlgorithm == null) + hashAlgorithm = ""; + + // Size of hash is based on the specified algorithm. + switch (hashAlgorithm.ToUpper()) + { + + case "SHA384": + hashSizeInBits = 384; + break; + + case "SHA512": + hashSizeInBits = 512; + break; + + default: // Must be MD5 + hashSizeInBits = 128; + break; + } + + // Convert size of hash from bits to bytes. + hashSizeInBytes = hashSizeInBits / 8; + + // Make sure that the specified hash value is long enough. + if (hashWithSaltBytes.Length < hashSizeInBytes) + return false; + + // Allocate array to hold original salt bytes retrieved from hash. + byte[] saltBytes = new byte[hashWithSaltBytes.Length - hashSizeInBytes]; + + // Copy salt from the end of the hash to the new array. + for (int i = 0; i < saltBytes.Length; i++) + saltBytes[i] = hashWithSaltBytes[hashSizeInBytes + i]; + + // Compute a new hash string. + string expectedHashString = ComputeHash(plainText, hashAlgorithm, saltBytes); + + // If the computed hash matches the specified hash, + // the plain text value must be correct. + return (hashValue == expectedHashString); + } + } +} diff --git a/MyWebsite/MyWebsite/Models/Badge.cs b/MyWebsite/MyWebsite/Models/Badge.cs new file mode 100644 index 0000000..e9b491e --- /dev/null +++ b/MyWebsite/MyWebsite/Models/Badge.cs @@ -0,0 +1,18 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace MyWebsite.Models +{ + public class Badge + { + [Key] + [Column(TypeName = "varchar(10)")] + public string Name { get; set; } + [Column(TypeName = "varchar(100)")] + [Required] + public string Description { get; set; } + [Column(TypeName = "varchar(20)")] + [Required] + public string Image { get; set; } + } +} diff --git a/MyWebsite/MyWebsite/Models/DatabaseContext.cs b/MyWebsite/MyWebsite/Models/DatabaseContext.cs index f7837f1..87b5dbd 100644 --- a/MyWebsite/MyWebsite/Models/DatabaseContext.cs +++ b/MyWebsite/MyWebsite/Models/DatabaseContext.cs @@ -7,6 +7,9 @@ namespace MyWebsite.Models public DbSet Links { get; set; } public DbSet Gallery { get; set; } public DbSet Projects { get; set; } + public DbSet Users { get; set; } + public DbSet Badges { get; set; } + public DbSet Resume { get; set; } public DatabaseContext(DbContextOptions options) : base(options) { diff --git a/MyWebsite/MyWebsite/Models/Image.cs b/MyWebsite/MyWebsite/Models/Image.cs index c87075e..0396e69 100644 --- a/MyWebsite/MyWebsite/Models/Image.cs +++ b/MyWebsite/MyWebsite/Models/Image.cs @@ -1,14 +1,27 @@ using System; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace MyWebsite.Models { public class Image { - public string Title { get; set; } - public string Description { get; set; } - public DateTime CreationDate { get; set; } [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid Id { get; set; } + [Required] + [Column(TypeName = "varchar(100)")] + public string Title { get; set; } + [Required] + [Column(TypeName = "varchar(255)")] + public string Description { get; set; } + [Required] + public DateTime CreationDate { get; set; } + [Required] + [Column(TypeName = "varchar(20)")] public string FileName { get; set; } + [Required] + [Column(TypeName = "varchar(10)")] + public string Language { get; set; } } } diff --git a/MyWebsite/MyWebsite/Models/Link.cs b/MyWebsite/MyWebsite/Models/Link.cs index be14c94..50cc1bb 100644 --- a/MyWebsite/MyWebsite/Models/Link.cs +++ b/MyWebsite/MyWebsite/Models/Link.cs @@ -1,14 +1,27 @@ using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace MyWebsite.Models { public class Link { [Key] + public int Id { get; set; } + [Required] + [Column(TypeName = "varchar(20)")] public string Name { get; set; } + [Required] + [Column(TypeName = "varchar(20)")] public string Title { get; set; } + [Required] + [Column(TypeName = "varchar(50)")] public string Username { get; set; } + [Required] + [Column(TypeName = "varchar(255)")] public string Url { get; set; } - public bool CanContactMe { get; set; } + [Required] + public bool CanContactMe { get; set; } = false; + [Required] + public bool DisplayInFooter { get; set; } = false; } } diff --git a/MyWebsite/MyWebsite/Models/LoginModel.cs b/MyWebsite/MyWebsite/Models/LoginModel.cs new file mode 100644 index 0000000..967aa5e --- /dev/null +++ b/MyWebsite/MyWebsite/Models/LoginModel.cs @@ -0,0 +1,16 @@ +using System.ComponentModel.DataAnnotations; + +namespace MyWebsite.Models +{ + public class LoginModel + { + [Key] + [Required(ErrorMessage = "This field is required")] + [DataType(DataType.EmailAddress)] + public string Email { get; set; } + + [Required(ErrorMessage = "This field is required")] + [DataType(DataType.Password)] + public string Password { get; set; } + } +} diff --git a/MyWebsite/MyWebsite/Models/MetricsPackage.cs b/MyWebsite/MyWebsite/Models/MetricsPackage.cs deleted file mode 100644 index fbc9005..0000000 --- a/MyWebsite/MyWebsite/Models/MetricsPackage.cs +++ /dev/null @@ -1,12 +0,0 @@ -using System; - -namespace MyWebsite.Models -{ - public class MetricsPackage - { - public Guid Id { get; set; } - public string Title { get; set; } - public string Content { get; set; } - public string Version { get; set; } - } -} diff --git a/MyWebsite/MyWebsite/Models/Project.cs b/MyWebsite/MyWebsite/Models/Project.cs index ddd98e1..a5f17b0 100644 --- a/MyWebsite/MyWebsite/Models/Project.cs +++ b/MyWebsite/MyWebsite/Models/Project.cs @@ -1,17 +1,29 @@ using System; using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; namespace MyWebsite.Models { public class Project { [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] public Guid Id { get; set; } + + [Required] + [Column(TypeName = "varchar(100)")] public string Title { get; set; } + + [Column(TypeName = "text")] public string Description { get; set; } + + [Column(TypeName = "varchar(50)")] public string ImageName { get; set; } + [Column(TypeName = "varchar(50)")] public string Link { get; set; } + [Column(TypeName = "varchar(50)")] public string LinkCaption { get; set; } + [Column(TypeName = "varchar(100)")] public string Badges { get; set; } } } diff --git a/MyWebsite/MyWebsite/Models/Resume.cs b/MyWebsite/MyWebsite/Models/Resume.cs new file mode 100644 index 0000000..eb9d8f0 --- /dev/null +++ b/MyWebsite/MyWebsite/Models/Resume.cs @@ -0,0 +1,15 @@ +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace MyWebsite.Models +{ + public class Resume + { + [Key] + [Column(TypeName = "varchar(10)")] + public string Language { get; set; } + [Required] + [Column(TypeName = "text")] + public string Content { get; set; } + } +} \ No newline at end of file diff --git a/MyWebsite/MyWebsite/MyWebsite.csproj b/MyWebsite/MyWebsite/MyWebsite.csproj index 1c0139d..4421eac 100644 --- a/MyWebsite/MyWebsite/MyWebsite.csproj +++ b/MyWebsite/MyWebsite/MyWebsite.csproj @@ -4,11 +4,21 @@ netcoreapp3.1 + + + + + + + + + + diff --git a/MyWebsite/MyWebsite/MyWebsite.csproj.user b/MyWebsite/MyWebsite/MyWebsite.csproj.user index 79c2e03..d0b5b39 100644 --- a/MyWebsite/MyWebsite/MyWebsite.csproj.user +++ b/MyWebsite/MyWebsite/MyWebsite.csproj.user @@ -7,7 +7,8 @@ True False False - + + False 600 diff --git a/MyWebsite/MyWebsite/Startup.cs b/MyWebsite/MyWebsite/Startup.cs index 8673c47..89c0d9d 100644 --- a/MyWebsite/MyWebsite/Startup.cs +++ b/MyWebsite/MyWebsite/Startup.cs @@ -5,6 +5,9 @@ using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.EntityFrameworkCore; using MyWebsite.Models; +using MyWebsite.Areas.API.Models; +using Microsoft.AspNetCore.Authentication.Cookies; +using Microsoft.AspNetCore.Http; namespace MyWebsite { @@ -22,9 +25,14 @@ namespace MyWebsite // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { - string connection = Configuration.GetConnectionString("DefaultConnection"); services.AddDbContext(options => - options.UseSqlServer(connection)); + options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); + + services.AddDbContext(options => + options.UseSqlServer(Configuration.GetConnectionString("FoxTubeAPI"))); + + services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(options => + options.LoginPath = new PathString("/Admin/Login")); services.AddControllersWithViews(); } @@ -45,6 +53,7 @@ namespace MyWebsite app.UseRouting(); + app.UseAuthentication(); app.UseAuthorization(); app.UseEndpoints(endpoints => @@ -53,6 +62,10 @@ namespace MyWebsite name: "projects", areaName: "Projects", pattern: "Projects/{controller=Projects}/{action=Index}/{id?}"); + endpoints.MapAreaControllerRoute( + name: "admin", + areaName: "Admin", + pattern: "Admin/{controller}/{action=Index}/{id?}"); endpoints.MapAreaControllerRoute( name: "api", areaName: "API", diff --git a/MyWebsite/MyWebsite/Views/Admin/Index.cshtml b/MyWebsite/MyWebsite/Views/Admin/Index.cshtml new file mode 100644 index 0000000..0726ec4 --- /dev/null +++ b/MyWebsite/MyWebsite/Views/Admin/Index.cshtml @@ -0,0 +1,23 @@ + +@{ + ViewData["Title"] = "Admin panel"; +} + +
+

Administration

+
+ + + + \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Views/Admin/Login.cshtml b/MyWebsite/MyWebsite/Views/Admin/Login.cshtml new file mode 100644 index 0000000..c9f8811 --- /dev/null +++ b/MyWebsite/MyWebsite/Views/Admin/Login.cshtml @@ -0,0 +1,27 @@ +@model LoginModel; +@{ + ViewData["Title"] = "Login"; +} + +
+

Administration panel - Login

+
+ +
+
+
+
+ + + +
+
+ + + +
+ +
+
+ + \ No newline at end of file diff --git a/MyWebsite/MyWebsite/Views/Gallery/Details.cshtml b/MyWebsite/MyWebsite/Views/Gallery/Details.cshtml index 4542df5..6461231 100644 --- a/MyWebsite/MyWebsite/Views/Gallery/Details.cshtml +++ b/MyWebsite/MyWebsite/Views/Gallery/Details.cshtml @@ -1,4 +1,4 @@ -@model Image; +@model Image @{ ViewData["Title"] = Model.Title; } @@ -13,7 +13,9 @@

@Model.Title

Creation date: @Model.CreationDate.ToShortDateString() -

@Model.Description

+

+ @Html.Raw(Model.Description) +

diff --git a/MyWebsite/MyWebsite/Views/Gallery/Index.cshtml b/MyWebsite/MyWebsite/Views/Gallery/Index.cshtml index 2a56664..53cfa14 100644 --- a/MyWebsite/MyWebsite/Views/Gallery/Index.cshtml +++ b/MyWebsite/MyWebsite/Views/Gallery/Index.cshtml @@ -6,9 +6,9 @@

My arts

-