From eeba115cc2158a77517753cb383b17d74886279e Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Sat, 22 Jan 2022 04:15:43 -0500 Subject: [PATCH 01/16] fixed issues with cleaning data --- .../Controllers/EncylopediaController.cs | 24 ++-- Library.Encyclopedia.API/appsettings.json | 1 + .../DataAccess/MainDataAccess.cs | 121 +++++++++++++----- .../Interfaces/IMainDataAccess.cs | 2 +- .../Models/External/MainMinimizedExternal.cs | 3 +- .../Models/External/MainUpdateModel.cs | 3 +- Library.Encyclopedia.Entity/Models/Links.cs | 2 + Library.Encyclopedia.Entity/Models/Main.cs | 68 +++++++++- 8 files changed, 174 insertions(+), 50 deletions(-) diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index 8d264b4..70a47ba 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -6,6 +6,7 @@ using Library.Encyclopedia.Entity.Models.External; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -28,10 +29,10 @@ public class EncylopediaController : ControllerBase /// /// /// - public EncylopediaController(ILogger logger, IApplicationDbContext dbContext) + public EncylopediaController(ILogger logger, IApplicationDbContext dbContext, IConfiguration configuration) { _logger = logger; - this.mainDataAccess = new MainDataAccess(dbContext); + this.mainDataAccess = new MainDataAccess(dbContext, configuration); } /// @@ -39,18 +40,19 @@ public EncylopediaController(ILogger logger, IApplication /// /// /// - /// + /// /// /// [HttpGet] public async Task Get(string query, int offset = 0, - int size = 10, + int limit = 10, + int previewSize = 50, bool asc = true) { try { - var response = await mainDataAccess.GetAsync(query, offset, size, asc); + var response = await mainDataAccess.GetAsync(query, offset, limit, previewSize, asc); if (response == null) { return StatusCode(204); @@ -72,18 +74,18 @@ public async Task Get(string query, /// /// /// - /// + /// /// /// [HttpGet("category")] public async Task GetByCategory(string category, int offset = 0, - int size = 10, + int limit = 10, bool asc = true) { try { - var response = await mainDataAccess.GetByCategoryAsync(category, offset, size, asc); + var response = await mainDataAccess.GetByCategoryAsync(category, offset, limit, asc); if (response == null) { @@ -106,18 +108,18 @@ public async Task GetByCategory(string category, /// /// /// - /// + /// /// /// [HttpGet("alphabet")] public async Task GetByStartingAlphabet(char alphabet, int offset = 0, - int size = 10, + int limit = 10, bool asc = true) { try { - var response = await mainDataAccess.GetByAlphabetAsync(alphabet, offset, size, asc); + var response = await mainDataAccess.GetByAlphabetAsync(alphabet, offset, limit, asc); if (response == null) { diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index 8d02394..7b695ce 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -6,6 +6,7 @@ "Microsoft.Hosting.Lifetime": "Information" } }, + "App-Base-Url": "http://localhost:4200", "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index 3374177..dba1a0b 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -3,6 +3,7 @@ using Library.Encyclopedia.Entity.Models; using Library.Encyclopedia.Entity.Models.External; using Microsoft.EntityFrameworkCore; +using Microsoft.Extensions.Configuration; using System; using System.Collections.Generic; using System.IO; @@ -10,41 +11,48 @@ using System.Text; using System.Text.RegularExpressions; using System.Threading.Tasks; +using System.Web; namespace Library.Encyclopedia.DataAccess.DataAccess { public class MainDataAccess : IMainDataAccess { private IApplicationDbContext _dbcontext; - public MainDataAccess(IApplicationDbContext dbcontext) + private string APP_BASE_URL; + + public MainDataAccess(IApplicationDbContext dbcontext, IConfiguration configuration) { + APP_BASE_URL = configuration.GetSection("App-Base-Url").Value; _dbcontext = dbcontext; } #region GET - async Task IMainDataAccess.GetAsync(string query, int offset, int pagesize, bool ascending) + async Task IMainDataAccess.GetAsync(string query, int offset, int pagesize, int previewSize, bool ascending) { - query = query.ToLower(); - var temp = _dbcontext.Main.Where(s => s.Description.ToLower().Contains(query) || s.Title.ToLower().Contains(query)) + // random cleanup + //await CleanUpData(); + + query = query != null ? query.ToLower() : string.Empty; + var temp = _dbcontext.Main.Where(s => s.RawDescription.ToLower().Contains(query) || s.Title.ToLower().Contains(query)) .Skip(offset) - .Take(pagesize); + .Take(pagesize) + .Include(s => s.Links); IEnumerable
data; if (ascending) data = await temp.OrderBy(s => s.Title) - .ThenBy(s => s.Description) + .ThenBy(s => s.RawDescription) .ToListAsync(); else data = await temp.OrderByDescending(s => s.Title) - .ThenByDescending(s => s.Description) + .ThenByDescending(s => s.RawDescription) .ToListAsync(); - var total = await _dbcontext.Main.CountAsync(s => s.Description.ToLower().Contains(query) || s.Title.ToLower().Contains(query)); + var total = await _dbcontext.Main.CountAsync(s => s.RawDescription.ToLower().Contains(query) || s.Title.ToLower().Contains(query)); + + MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.MinimizeWithQuery(query, previewSize), total); - MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(), total); - // random cleanup - await CleanUpData(); return result; } @@ -60,10 +68,10 @@ async Task IMainDataAccess.GetByCategoryAsync(s IEnumerable
data; if (ascending) data = temp.OrderBy(s => s.Title) - .ThenBy(s => s.Description); + .ThenBy(s => s.RawDescription); else data = temp.OrderByDescending(s => s.Title) - .ThenByDescending(s => s.Description); + .ThenByDescending(s => s.RawDescription); var total = rawData.Count(s => s.Category.ToLower().Split(',', StringSplitOptions.None).Contains(category)); @@ -82,11 +90,11 @@ async Task IMainDataAccess.GetByAlphabetAsync(c IEnumerable
data; if (ascending) data = await temp.OrderBy(s => s.Title) - .ThenBy(s => s.Description) + .ThenBy(s => s.RawDescription) .ToListAsync(); else data = await temp.OrderByDescending(s => s.Title) - .ThenByDescending(s => s.Description) + .ThenByDescending(s => s.RawDescription) .ToListAsync(); var total = await _dbcontext.Main.CountAsync(s => s.Title.ToLower().StartsWith(alph)); @@ -98,7 +106,20 @@ async Task IMainDataAccess.GetByAlphabetAsync(c async Task
IMainDataAccess.GetAsync(Guid id) { - return await _dbcontext.Main.Include(s => s.Files).Include(s => s.Links).FirstOrDefaultAsync(s => s.Id == id); + Main item = await _dbcontext.Main.Include(s => s.Files).Include(s => s.Links).FirstOrDefaultAsync(s => s.Id == id); + + // replace links in item + while (item.RichDescription.IndexOf("$$$") != -1) + { + var startIndex = item.RichDescription.IndexOf("$$$") + 3; + var endIndex = item.RichDescription.Substring(startIndex).IndexOf("$$$"); + + var referenceid = item.RichDescription.Substring(startIndex, endIndex); + + item.RichDescription= item.RichDescription.Replace($"$$${referenceid}$$$", $"{item.Links.FirstOrDefault(s => s.ReferenceId.ToString() == referenceid).Description}"); + } + + return item; } #endregion @@ -124,7 +145,10 @@ public async Task UpdateAsync(Guid id, MainUpdateModel model) if (entry != null) { entry.Title = model.Title; - entry.Description = model.Description; + entry.RichDescription = model.RichDescription; + + // TODO : do conversion on raw description + entry.Category = model.Category; _dbcontext.Main.Update(entry); @@ -172,19 +196,18 @@ public async Task CleanUpData() { string searchStartString = ""; - var links = new List(); + var allLinks = new List(); var allData = await _dbcontext.Main.ToListAsync(); - //using StreamWriter file = new StreamWriter(@"C:\temp\temp.txt", append: true); foreach (var item in allData) { - if (item.Description != null) + if (item.RichDescription != null) { // Extract start indices List startIndexes = new List(); for (int index = 0; ; index += searchStartString.Length) { - index = item.Description.IndexOf(searchStartString, index); + index = item.RichDescription.IndexOf(searchStartString, index); if (index != -1) startIndexes.Add(index); else break; @@ -194,32 +217,32 @@ public async Task CleanUpData() List endIndexes = new List(); for (int index = 0; ; index += searchEndString.Length) { - index = item.Description.IndexOf(searchEndString, index); + index = item.RichDescription.IndexOf(searchEndString, index); if (index != -1) endIndexes.Add(index); else break; } // - var newDesc = new string(item.Description); + var newDesc = new string(item.RichDescription); + var links = new List(); + for (int i = 0; i < startIndexes.Count; i++) { - string value = item.Description.Substring(startIndexes[i], endIndexes[i] - startIndexes[i] + searchEndString.Length); + string value = item.RichDescription.Substring(startIndexes[i], endIndexes[i] - startIndexes[i] + searchEndString.Length); if (value.Contains("http://")) { var id = value.Substring(value.IndexOf("id=") + 3, 36); var desc = value.Substring(value.IndexOf(">") + 1, value.IndexOf("<", value.IndexOf(">")) - value.IndexOf(">") - 1); - //await file.WriteLineAsync($"id = {id} | Description = {desc}"); - newDesc = newDesc.Replace(value, $"$$${id}$$$"); - //await file.WriteLineAsync($"new description = {newDesc}"); // add to links list links.Add(new Links { Id = Guid.NewGuid(), - MainId = Guid.Parse(id), + MainId = item.Id, + ReferenceId = Guid.Parse(id), Link = value, Description = desc, IsInternal = true @@ -228,18 +251,54 @@ public async Task CleanUpData() } // update the description - item.Description = newDesc; + item.RichDescription = newDesc; + // update the links array + item.Links = links; + allLinks.AddRange(links); } } // add to the links table - await _dbcontext.Links.AddRangeAsync(links); - + await _dbcontext.Links.AddRangeAsync(allLinks); // update the main data _dbcontext.Main.UpdateRange(allData); + await _dbcontext.SaveChanges(); + allData = await _dbcontext.Main.Include(s => s.Links).ToListAsync(); + + foreach (var item in allData) + { + var temp = new string(item.RichDescription); + + if (!string.IsNullOrEmpty(temp)) + { + // replace the links + if (item.Links != null && item.Links.Any()) + { + foreach (var link in item.Links) + { + var identifier = $"$$${link.ReferenceId}$$$"; + temp = temp.Replace(identifier, link.Description); + } + } + + item.RawDescription = temp; + + // replace the HTML tags + item.RawDescription = StripHTML(item.RawDescription, true); + } + } + // update the main data + _dbcontext.Main.UpdateRange(allData); await _dbcontext.SaveChanges(); } + + public static string StripHTML(string HTMLText, bool decode = true) + { + Regex reg = new Regex("<[^>]+>", RegexOptions.IgnoreCase); + var stripped = reg.Replace(HTMLText, ""); + return decode ? HttpUtility.HtmlDecode(stripped) : stripped; + } #endregion } } \ No newline at end of file diff --git a/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs b/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs index cc8c6eb..e50686d 100644 --- a/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs +++ b/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs @@ -9,7 +9,7 @@ namespace Library.Encyclopedia.Entity.Interfaces public interface IMainDataAccess { #region GET - public Task GetAsync(string query, int offset, int pagesize, bool ascending); + public Task GetAsync(string query, int offset, int pagesize, int previewSize, bool ascending); public Task GetByCategoryAsync(string category, int offset, int pagesize, bool ascending); public Task GetByAlphabetAsync(char startingAlphabet, int offset, int pagesize, bool ascending); public Task
GetAsync(Guid id); diff --git a/Library.Encyclopedia.Entity/Models/External/MainMinimizedExternal.cs b/Library.Encyclopedia.Entity/Models/External/MainMinimizedExternal.cs index 7aca354..df21a9e 100644 --- a/Library.Encyclopedia.Entity/Models/External/MainMinimizedExternal.cs +++ b/Library.Encyclopedia.Entity/Models/External/MainMinimizedExternal.cs @@ -8,8 +8,7 @@ public class MainMinimizedExternal { public Guid Id { get; set; } public string Title { get; set; } - public string Description { get; set; } - public string Category { get; set; } + public string Preview { get; set; } } public class MainMinimizedExternalCollection : QueryExternalModel { diff --git a/Library.Encyclopedia.Entity/Models/External/MainUpdateModel.cs b/Library.Encyclopedia.Entity/Models/External/MainUpdateModel.cs index 225404b..90bbab1 100644 --- a/Library.Encyclopedia.Entity/Models/External/MainUpdateModel.cs +++ b/Library.Encyclopedia.Entity/Models/External/MainUpdateModel.cs @@ -3,7 +3,8 @@ public class MainUpdateModel { public string Title { get; set; } - public string Description { get; set; } + public string RawDescription { get; set; } + public string RichDescription { get; set; } public string Category { get; set; } } } \ No newline at end of file diff --git a/Library.Encyclopedia.Entity/Models/Links.cs b/Library.Encyclopedia.Entity/Models/Links.cs index 0d2eb11..d75f55d 100644 --- a/Library.Encyclopedia.Entity/Models/Links.cs +++ b/Library.Encyclopedia.Entity/Models/Links.cs @@ -10,6 +10,8 @@ public class Links [Required] public Guid MainId { get; set; } [Required] + public Guid ReferenceId { get; set; } + [Required] public string Link { get; set; } public string Description { get; set; } [Required] diff --git a/Library.Encyclopedia.Entity/Models/Main.cs b/Library.Encyclopedia.Entity/Models/Main.cs index bc6bf11..9109bca 100644 --- a/Library.Encyclopedia.Entity/Models/Main.cs +++ b/Library.Encyclopedia.Entity/Models/Main.cs @@ -12,7 +12,9 @@ public class Main [Required] [StringLength(1024)] public string Title { get; set; } - public string Description { get; set; } + public string RichDescription { get; set; } + public string RawDescription { get; set; } + [StringLength(256)] public string Category { get; set; } @@ -22,15 +24,73 @@ public class Main public static class MainExtensions { + public static MainMinimizedExternal MinimizeWithQuery(this Main main, string query, int previewText_maxlength) + { + if (!string.IsNullOrEmpty(query)) + { + int indexOfQuery = main.RawDescription.IndexOf(query, StringComparison.OrdinalIgnoreCase); + + // find the complete word + if (indexOfQuery != -1) + { + for (int i = indexOfQuery; i >= 0; i--) + { + if (char.IsSeparator(main.RawDescription[i])) + { + indexOfQuery = i; + break; + } + } + } + + int lengthOfPreviewText = indexOfQuery == -1 + ? Math.Min(previewText_maxlength, main.RawDescription.Length) + : Math.Min(previewText_maxlength, main.RawDescription.Length - indexOfQuery); + + return new MainMinimizedExternal + { + Id = main.Id, + Preview = indexOfQuery == -1 + ? main.RawDescription.Length > previewText_maxlength + ? main.RawDescription.Substring(0, lengthOfPreviewText) + " ..." + : main.RawDescription.Substring(0, lengthOfPreviewText) + : main.RawDescription.Length - indexOfQuery > previewText_maxlength + ? " ... " + main.RawDescription.Substring(indexOfQuery, lengthOfPreviewText) + " ..." + : main.RawDescription.Length > previewText_maxlength + ? " ... " + main.RawDescription.Substring(main.RawDescription.Length - previewText_maxlength, previewText_maxlength) + : main.RawDescription, + Title = main.Title + }; + } + else + { + int lengthOfPreviewText = Math.Min(previewText_maxlength, main.RawDescription.Length); + return new MainMinimizedExternal + { + Id = main.Id, + Preview = main.RawDescription.Length > previewText_maxlength + ? main.RawDescription.Substring(0, lengthOfPreviewText) + " ..." + : main.RawDescription.Substring(0, lengthOfPreviewText), + Title = main.Title + }; + } + } public static MainMinimizedExternal Minimize(this Main main) { - return new MainMinimizedExternal { + return new MainMinimizedExternal + { Id = main.Id, - Category = main.Category, - Description = main.Description, + Preview = main.RawDescription, Title = main.Title }; } + public static IEnumerable MinimizeWithQuery(this IEnumerable
collection, string query, int previewText_maxlength) + { + foreach (var item in collection) + { + yield return MinimizeWithQuery(item, query, previewText_maxlength); + } + } public static IEnumerable Minimize(this IEnumerable
collection) { foreach (var item in collection) From 768a66fb45d1b91eb5d304d920ab422c326553f9 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Sun, 23 Jan 2022 02:28:29 -0500 Subject: [PATCH 02/16] working website --- .../Controllers/EncylopediaController.cs | 21 +-- .../Properties/launchSettings.json | 2 +- Library.Encyclopedia.API/Startup.cs | 12 +- .../DataAccess/MainDataAccess.cs | 156 +++++++++++++++--- .../Interfaces/IMainDataAccess.cs | 4 +- .../Models/External/MainMinimizedExternal.cs | 6 + Library.Encyclopedia.Entity/Models/Main.cs | 92 ++++++----- 7 files changed, 205 insertions(+), 88 deletions(-) diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index 70a47ba..e7c80ac 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -80,12 +80,13 @@ public async Task Get(string query, [HttpGet("category")] public async Task GetByCategory(string category, int offset = 0, - int limit = 10, + int limit = 10, int previewSize = 50, + bool asc = true) { try { - var response = await mainDataAccess.GetByCategoryAsync(category, offset, limit, asc); + var response = await mainDataAccess.GetByCategoryAsync(category, offset, limit,previewSize, asc); if (response == null) { @@ -114,12 +115,12 @@ public async Task GetByCategory(string category, [HttpGet("alphabet")] public async Task GetByStartingAlphabet(char alphabet, int offset = 0, - int limit = 10, + int limit = 10, int previewSize = 50, bool asc = true) { try { - var response = await mainDataAccess.GetByAlphabetAsync(alphabet, offset, limit, asc); + var response = await mainDataAccess.GetByAlphabetAsync(alphabet, offset, limit,previewSize, asc); if (response == null) { @@ -169,8 +170,8 @@ public async Task Get(Guid id) /// /// [HttpPost] - [ApiKey] - //[Authorize] + //[ApiKey] + [Authorize(Roles = @"EncyclopediaAdministrators")] public async Task Create([FromBody]Main model) { try @@ -198,8 +199,8 @@ public async Task Create([FromBody]Main model) /// /// [HttpPut("{id}")] - [ApiKey] - //[Authorize] + //[ApiKey] + [Authorize(Roles = @"EncyclopediaAdministrators")] public async Task Update(Guid id, [FromBody]MainUpdateModel model) { try @@ -220,8 +221,8 @@ public async Task Update(Guid id, [FromBody]MainUpdateModel model /// /// [HttpDelete("{id}")] - [ApiKey] - //[Authorize] + //[ApiKey] + [Authorize(Roles = @"EncyclopediaAdministrators")] public async Task Delete(Guid id) { try diff --git a/Library.Encyclopedia.API/Properties/launchSettings.json b/Library.Encyclopedia.API/Properties/launchSettings.json index 3984a6a..95445d5 100644 --- a/Library.Encyclopedia.API/Properties/launchSettings.json +++ b/Library.Encyclopedia.API/Properties/launchSettings.json @@ -1,6 +1,6 @@ { "iisSettings": { - "windowsAuthentication": false, + "windowsAuthentication": true, "anonymousAuthentication": true, "iisExpress": { "applicationUrl": "http://localhost:63327", diff --git a/Library.Encyclopedia.API/Startup.cs b/Library.Encyclopedia.API/Startup.cs index 5510e7b..07b2104 100644 --- a/Library.Encyclopedia.API/Startup.cs +++ b/Library.Encyclopedia.API/Startup.cs @@ -3,6 +3,7 @@ using Library.Encyclopedia.Entity.Interfaces; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; @@ -28,12 +29,13 @@ public void ConfigureServices(IServiceCollection services) options.AddPolicy(name: MyAllowSpecificOrigins, builder => { - builder.WithOrigins("http://localhost:4200").AllowAnyHeader(); - builder.WithOrigins("https://tools.library.pfw.edu").AllowAnyHeader(); + builder.WithOrigins("http://localhost:4200").AllowAnyHeader().AllowCredentials().AllowAnyMethod(); + builder.WithOrigins("https://tools.library.pfw.edu").AllowAnyHeader().AllowCredentials().AllowAnyMethod(); }); }); - //services.AddAuthentication(IISDefaults.AuthenticationScheme); + services.AddAuthentication(IISDefaults.AuthenticationScheme); + services.AddControllers(); string url = Configuration.GetValue("FTPSettings:url"); @@ -66,8 +68,8 @@ public void Configure(IApplicationBuilder app, IWebHostEnvironment env) app.UseStaticFiles(); app.UseRouting(); - //app.UseAuthentication(); - //app.UseAuthorization(); + app.UseAuthentication(); + app.UseAuthorization(); app.UseEndpoints(endpoints => { endpoints.MapControllerRoute( diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index dba1a0b..ff8696f 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -57,35 +57,70 @@ async Task IMainDataAccess.GetAsync(string quer return result; } - async Task IMainDataAccess.GetByCategoryAsync(string category, int offset, int pagesize, bool ascending) + async Task IMainDataAccess.GetByCategoryAsync(string category, int offset, int pagesize, int previewSize, bool ascending) { - category = category.ToLower(); - List
rawData = await _dbcontext.Main.ToListAsync(); - var temp = rawData.Where(s => s.Category.ToLower().Split(',', StringSplitOptions.None).Contains(category)) - .Skip(offset) - .Take(pagesize); + if (!string.IsNullOrEmpty(category)) + { + category = category.ToLower(); + List
rawData = await _dbcontext.Main.ToListAsync(); + var temp = rawData.Where(s => s.Category != null).Where(s => s.Category.ToLower().Split(',', StringSplitOptions.None).Contains(category)) + .Skip(offset) + .Take(pagesize); - IEnumerable
data; - if (ascending) - data = temp.OrderBy(s => s.Title) - .ThenBy(s => s.RawDescription); - else - data = temp.OrderByDescending(s => s.Title) - .ThenByDescending(s => s.RawDescription); + IEnumerable
data; + if (ascending) + data = temp.OrderBy(s => s.Title) + .ThenBy(s => s.RawDescription); + else + data = temp.OrderByDescending(s => s.Title) + .ThenByDescending(s => s.RawDescription); - var total = rawData.Count(s => s.Category.ToLower().Split(',', StringSplitOptions.None).Contains(category)); + var total = rawData.Where(s => s.Category != null).Count(s => s.Category.ToLower().Split(',', StringSplitOptions.None).Contains(category)); - MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(), total); + MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(previewSize), total); - return result; + return result; + } + else return new MainMinimizedExternalCollection(); } - async Task IMainDataAccess.GetByAlphabetAsync(char startingAlphabet, int offset, int pagesize, bool ascending) + async Task IMainDataAccess.GetByAlphabetAsync(char startingAlphabet, int offset, int pagesize, int previewSize, bool ascending) { - var alph = startingAlphabet.ToString().ToLower(); - var temp = _dbcontext.Main.Where(s => s.Title.ToLower().StartsWith(alph)) - .Skip(offset) - .Take(pagesize); + IQueryable
temp; + int total; + + if (startingAlphabet == '#') + { + temp = _dbcontext.Main.Where(s => s.Title.StartsWith("0") || + s.Title.StartsWith("1") || + s.Title.StartsWith("2") || + s.Title.StartsWith("3") || + s.Title.StartsWith("4") || + s.Title.StartsWith("5") || + s.Title.StartsWith("6") || + s.Title.StartsWith("7") || + s.Title.StartsWith("8") || + s.Title.StartsWith("9")) + .Skip(offset) + .Take(pagesize); + total = await _dbcontext.Main.CountAsync(s => s.Title.StartsWith("0") || + s.Title.StartsWith("1") || + s.Title.StartsWith("2") || + s.Title.StartsWith("3") || + s.Title.StartsWith("4") || + s.Title.StartsWith("5") || + s.Title.StartsWith("6") || + s.Title.StartsWith("7") || + s.Title.StartsWith("8") || + s.Title.StartsWith("9")); + } + else + { + temp = _dbcontext.Main.Where(s => s.Title.ToLower().StartsWith(startingAlphabet.ToString().ToLower())) + .Skip(offset) + .Take(pagesize); + total = await _dbcontext.Main.CountAsync(s => s.Title.ToLower().StartsWith(startingAlphabet.ToString().ToLower())); + } IEnumerable
data; if (ascending) @@ -97,9 +132,7 @@ async Task IMainDataAccess.GetByAlphabetAsync(c .ThenByDescending(s => s.RawDescription) .ToListAsync(); - var total = await _dbcontext.Main.CountAsync(s => s.Title.ToLower().StartsWith(alph)); - - MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(), total); + MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(previewSize), total); return result; } @@ -116,7 +149,7 @@ async Task
IMainDataAccess.GetAsync(Guid id) var referenceid = item.RichDescription.Substring(startIndex, endIndex); - item.RichDescription= item.RichDescription.Replace($"$$${referenceid}$$$", $"{item.Links.FirstOrDefault(s => s.ReferenceId.ToString() == referenceid).Description}"); + item.RichDescription = item.RichDescription.Replace($"$$${referenceid}$$$", $"{item.Links.FirstOrDefault(s => s.ReferenceId.ToString() == referenceid).Description}"); } return item; @@ -130,10 +163,76 @@ public async Task CreateAsync(Main model) model.Id = Guid.NewGuid(); + List links = ReplaceInternalLinks(model); + + //santize data + model.RawDescription = StripHTML(model.RichDescription); + await _dbcontext.Main.AddAsync(model); + await _dbcontext.Links.AddRangeAsync(links); + await _dbcontext.SaveChanges(); return model.Id; } + + private List ReplaceInternalLinks(Main model) + { + string searchStartString = ""; + var links = new List(); + + if (model.RichDescription != null) + { + // Extract start indices + List startIndexes = new List(); + for (int index = 0; ; index += searchStartString.Length) + { + index = model.RichDescription.IndexOf(searchStartString, index); + if (index != -1) + startIndexes.Add(index); + else break; + } + + // Extract end indices + List endIndexes = new List(); + for (int index = 0; ; index += searchEndString.Length) + { + index = model.RichDescription.IndexOf(searchEndString, index); + if (index != -1) + endIndexes.Add(index); + else break; + } + + var newDesc = new string(model.RichDescription); + + for (int i = 0; i < startIndexes.Count; i++) + { + string value = model.RichDescription.Substring(startIndexes[i], endIndexes[i] - startIndexes[i] + searchEndString.Length); + if (value.Contains(APP_BASE_URL)) + { + var id = value.Substring(value.IndexOf(APP_BASE_URL) + APP_BASE_URL.Length + 1, 36); + var desc = value.Substring(value.IndexOf(">") + 1, value.IndexOf("<", value.IndexOf(">")) - value.IndexOf(">") - 1); + + newDesc = newDesc.Replace(value, $"$$${id}$$$"); + + // add to links list + links.Add(new Links + { + Id = Guid.NewGuid(), + MainId = model.Id, + ReferenceId = Guid.Parse(id), + Link = value, + Description = desc, + IsInternal = true + }); + } + } + + model.RichDescription = newDesc; + } + + return links; + } #endregion #region UPDATE @@ -182,8 +281,13 @@ public async Task> GetAllCategoriesAsync() var result = new List(); data.ForEach(item => { - result.AddRange(item.Split(',')); + if (!string.IsNullOrEmpty(item)) + { + result.AddRange(item.Split(',')); + } }); + + result.RemoveAll(s => s == string.Empty); return result.Distinct(); } diff --git a/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs b/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs index e50686d..d00e9e8 100644 --- a/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs +++ b/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs @@ -10,8 +10,8 @@ public interface IMainDataAccess { #region GET public Task GetAsync(string query, int offset, int pagesize, int previewSize, bool ascending); - public Task GetByCategoryAsync(string category, int offset, int pagesize, bool ascending); - public Task GetByAlphabetAsync(char startingAlphabet, int offset, int pagesize, bool ascending); + public Task GetByCategoryAsync(string category, int offset, int pagesize, int previewSize, bool ascending); + public Task GetByAlphabetAsync(char startingAlphabet, int offset, int pagesize, int previewSize, bool ascending); public Task
GetAsync(Guid id); #endregion diff --git a/Library.Encyclopedia.Entity/Models/External/MainMinimizedExternal.cs b/Library.Encyclopedia.Entity/Models/External/MainMinimizedExternal.cs index df21a9e..3e36cbf 100644 --- a/Library.Encyclopedia.Entity/Models/External/MainMinimizedExternal.cs +++ b/Library.Encyclopedia.Entity/Models/External/MainMinimizedExternal.cs @@ -12,6 +12,12 @@ public class MainMinimizedExternal } public class MainMinimizedExternalCollection : QueryExternalModel { + public MainMinimizedExternalCollection() + { + this.Count = 0; + this.Result = new List(); + this.Total = 0; + } public MainMinimizedExternalCollection(IEnumerable collection, int total) { this.Result = collection; diff --git a/Library.Encyclopedia.Entity/Models/Main.cs b/Library.Encyclopedia.Entity/Models/Main.cs index 9109bca..ac92950 100644 --- a/Library.Encyclopedia.Entity/Models/Main.cs +++ b/Library.Encyclopedia.Entity/Models/Main.cs @@ -26,63 +26,67 @@ public static class MainExtensions { public static MainMinimizedExternal MinimizeWithQuery(this Main main, string query, int previewText_maxlength) { - if (!string.IsNullOrEmpty(query)) + if (!string.IsNullOrEmpty(main.RawDescription)) { - int indexOfQuery = main.RawDescription.IndexOf(query, StringComparison.OrdinalIgnoreCase); - - // find the complete word - if (indexOfQuery != -1) + if (!string.IsNullOrEmpty(query)) { - for (int i = indexOfQuery; i >= 0; i--) + int indexOfQuery = main.RawDescription.IndexOf(query, StringComparison.OrdinalIgnoreCase); + + // find the complete word + if (indexOfQuery != -1) { - if (char.IsSeparator(main.RawDescription[i])) + for (int i = indexOfQuery; i >= 0; i--) { - indexOfQuery = i; - break; + if (char.IsSeparator(main.RawDescription[i])) + { + indexOfQuery = i; + break; + } } } - } - int lengthOfPreviewText = indexOfQuery == -1 - ? Math.Min(previewText_maxlength, main.RawDescription.Length) - : Math.Min(previewText_maxlength, main.RawDescription.Length - indexOfQuery); + int lengthOfPreviewText = indexOfQuery == -1 + ? Math.Min(previewText_maxlength, main.RawDescription.Length) + : Math.Min(previewText_maxlength, main.RawDescription.Length - indexOfQuery); - return new MainMinimizedExternal + return new MainMinimizedExternal + { + Id = main.Id, + Preview = indexOfQuery == -1 + ? main.RawDescription.Length > previewText_maxlength + ? main.RawDescription.Substring(0, lengthOfPreviewText) + " ..." + : main.RawDescription.Substring(0, lengthOfPreviewText) + : main.RawDescription.Length - indexOfQuery > previewText_maxlength + ? " ... " + main.RawDescription.Substring(indexOfQuery, lengthOfPreviewText) + " ..." + : main.RawDescription.Length > previewText_maxlength + ? " ... " + main.RawDescription.Substring(main.RawDescription.Length - previewText_maxlength, previewText_maxlength) + : main.RawDescription, + Title = main.Title + }; + } + else { - Id = main.Id, - Preview = indexOfQuery == -1 - ? main.RawDescription.Length > previewText_maxlength - ? main.RawDescription.Substring(0, lengthOfPreviewText) + " ..." - : main.RawDescription.Substring(0, lengthOfPreviewText) - : main.RawDescription.Length - indexOfQuery > previewText_maxlength - ? " ... " + main.RawDescription.Substring(indexOfQuery, lengthOfPreviewText) + " ..." - : main.RawDescription.Length > previewText_maxlength - ? " ... " + main.RawDescription.Substring(main.RawDescription.Length - previewText_maxlength, previewText_maxlength) - : main.RawDescription, - Title = main.Title - }; + int lengthOfPreviewText = Math.Min(previewText_maxlength, main.RawDescription.Length); + return new MainMinimizedExternal + { + Id = main.Id, + Preview = main.RawDescription.Length > previewText_maxlength + ? main.RawDescription.Substring(0, lengthOfPreviewText) + " ..." + : main.RawDescription.Substring(0, lengthOfPreviewText), + Title = main.Title + }; + } } else - { - int lengthOfPreviewText = Math.Min(previewText_maxlength, main.RawDescription.Length); - return new MainMinimizedExternal - { + return new MainMinimizedExternal { Id = main.Id, - Preview = main.RawDescription.Length > previewText_maxlength - ? main.RawDescription.Substring(0, lengthOfPreviewText) + " ..." - : main.RawDescription.Substring(0, lengthOfPreviewText), + Preview = string.Empty, Title = main.Title - }; - } + }; } - public static MainMinimizedExternal Minimize(this Main main) + public static MainMinimizedExternal Minimize(this Main main, int previewText_maxlength) { - return new MainMinimizedExternal - { - Id = main.Id, - Preview = main.RawDescription, - Title = main.Title - }; + return main.MinimizeWithQuery(null, previewText_maxlength); } public static IEnumerable MinimizeWithQuery(this IEnumerable
collection, string query, int previewText_maxlength) { @@ -91,11 +95,11 @@ public static IEnumerable MinimizeWithQuery(this IEnumera yield return MinimizeWithQuery(item, query, previewText_maxlength); } } - public static IEnumerable Minimize(this IEnumerable
collection) + public static IEnumerable Minimize(this IEnumerable
collection, int previewText_maxlength) { foreach (var item in collection) { - yield return Minimize(item); + yield return Minimize(item, previewText_maxlength); } } } From 859585cf6d942468d30802cd99dfc8ab1fe15306 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Mon, 24 Jan 2022 02:58:35 -0500 Subject: [PATCH 03/16] Fixed major bugs --- .../.config/dotnet-tools.json | 12 ++ Library.Encyclopedia.API/appsettings.json | 4 +- .../DataAccess/MainDataAccess.cs | 115 ++++++++++-------- 3 files changed, 82 insertions(+), 49 deletions(-) create mode 100644 Library.Encyclopedia.API/.config/dotnet-tools.json diff --git a/Library.Encyclopedia.API/.config/dotnet-tools.json b/Library.Encyclopedia.API/.config/dotnet-tools.json new file mode 100644 index 0000000..0d1da73 --- /dev/null +++ b/Library.Encyclopedia.API/.config/dotnet-tools.json @@ -0,0 +1,12 @@ +{ + "version": 1, + "isRoot": true, + "tools": { + "dotnet-ef": { + "version": "6.0.1", + "commands": [ + "dotnet-ef" + ] + } + } +} \ No newline at end of file diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index 7b695ce..aa3949b 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -6,10 +6,12 @@ "Microsoft.Hosting.Lifetime": "Information" } }, - "App-Base-Url": "http://localhost:4200", + //"App-Base-Url": "http://localhost:4200", + "App-Base-Url": "https://tools.library.pfw.edu/encyclopedia", "AllowedHosts": "*", "ConnectionStrings": { "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" + //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" }, "ApiKey": "5929b003-8895-4fb3-bbb0-2eb101c48f66", "FTPSettings": { diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index ff8696f..b405de9 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -29,31 +29,30 @@ public MainDataAccess(IApplicationDbContext dbcontext, IConfiguration configurat #region GET async Task IMainDataAccess.GetAsync(string query, int offset, int pagesize, int previewSize, bool ascending) { - // random cleanup - //await CleanUpData(); - query = query != null ? query.ToLower() : string.Empty; - var temp = _dbcontext.Main.Where(s => s.RawDescription.ToLower().Contains(query) || s.Title.ToLower().Contains(query)) - .Skip(offset) - .Take(pagesize) - .Include(s => s.Links); - IEnumerable
data; + if (ascending) - data = await temp.OrderBy(s => s.Title) - .ThenBy(s => s.RawDescription) - .ToListAsync(); + data = await _dbcontext.Main.Where(s => s.RawDescription.ToLower().Contains(query) || s.Title.ToLower().Contains(query)) + .OrderBy(s => s.Title.ToLower()) + .ThenBy(s => s.RawDescription.ToLower()) + .Skip(offset) + .Take(pagesize) + .Include(s => s.Links) + .ToListAsync(); else - data = await temp.OrderByDescending(s => s.Title) - .ThenByDescending(s => s.RawDescription) - .ToListAsync(); + data = await _dbcontext.Main.Where(s => s.RawDescription.ToLower().Contains(query) || s.Title.ToLower().Contains(query)) + .OrderByDescending(s => s.Title.ToLower()) + .ThenByDescending(s => s.RawDescription.ToLower()) + .Skip(offset) + .Take(pagesize) + .Include(s => s.Links) + .ToListAsync(); var total = await _dbcontext.Main.CountAsync(s => s.RawDescription.ToLower().Contains(query) || s.Title.ToLower().Contains(query)); MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.MinimizeWithQuery(query, previewSize), total); - - return result; } @@ -62,20 +61,26 @@ async Task IMainDataAccess.GetByCategoryAsync(s if (!string.IsNullOrEmpty(category)) { category = category.ToLower(); - List
rawData = await _dbcontext.Main.ToListAsync(); - var temp = rawData.Where(s => s.Category != null).Where(s => s.Category.ToLower().Split(',', StringSplitOptions.None).Contains(category)) - .Skip(offset) - .Take(pagesize); IEnumerable
data; if (ascending) - data = temp.OrderBy(s => s.Title) - .ThenBy(s => s.RawDescription); + data = await _dbcontext.Main.Where(s => s.Category != null) + .Where(s => s.Category.ToLower().Contains(category + ",") || s.Category.ToLower().Contains("," + category) || s.Category.ToLower() == category) + .OrderBy(s => s.Title.ToLower()) + .ThenBy(s => s.RawDescription.ToLower()) + .Skip(offset) + .Take(pagesize) + .ToListAsync(); else - data = temp.OrderByDescending(s => s.Title) - .ThenByDescending(s => s.RawDescription); + data = await _dbcontext.Main.Where(s => s.Category != null) + .Where(s => s.Category.ToLower().Contains(category + ",") || s.Category.ToLower().Contains("," + category) || s.Category.ToLower() == category) + .OrderByDescending(s => s.Title.ToLower()) + .ThenByDescending(s => s.RawDescription.ToLower()) + .Skip(offset) + .Take(pagesize) + .ToListAsync(); - var total = rawData.Where(s => s.Category != null).Count(s => s.Category.ToLower().Split(',', StringSplitOptions.None).Contains(category)); + var total = await _dbcontext.Main.Where(s => s.Category != null).CountAsync(s => s.Category.ToLower().Contains(category + ",") || s.Category.ToLower().Contains("," + category) || s.Category.ToLower() == category); MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(previewSize), total); @@ -100,9 +105,8 @@ async Task IMainDataAccess.GetByAlphabetAsync(c s.Title.StartsWith("6") || s.Title.StartsWith("7") || s.Title.StartsWith("8") || - s.Title.StartsWith("9")) - .Skip(offset) - .Take(pagesize); + s.Title.StartsWith("9")); + total = await _dbcontext.Main.CountAsync(s => s.Title.StartsWith("0") || s.Title.StartsWith("1") || s.Title.StartsWith("2") || @@ -116,20 +120,22 @@ async Task IMainDataAccess.GetByAlphabetAsync(c } else { - temp = _dbcontext.Main.Where(s => s.Title.ToLower().StartsWith(startingAlphabet.ToString().ToLower())) - .Skip(offset) - .Take(pagesize); + temp = _dbcontext.Main.Where(s => s.Title.ToLower().StartsWith(startingAlphabet.ToString().ToLower())); total = await _dbcontext.Main.CountAsync(s => s.Title.ToLower().StartsWith(startingAlphabet.ToString().ToLower())); } IEnumerable
data; if (ascending) - data = await temp.OrderBy(s => s.Title) - .ThenBy(s => s.RawDescription) + data = await temp.OrderBy(s => s.Title.ToLower()) + .ThenBy(s => s.RawDescription.ToLower()) + .Skip(offset) + .Take(pagesize) .ToListAsync(); else - data = await temp.OrderByDescending(s => s.Title) - .ThenByDescending(s => s.RawDescription) + data = await temp.OrderByDescending(s => s.Title.ToLower()) + .ThenByDescending(s => s.RawDescription.ToLower()) + .Skip(offset) + .Take(pagesize) .ToListAsync(); MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(previewSize), total); @@ -163,23 +169,23 @@ public async Task CreateAsync(Main model) model.Id = Guid.NewGuid(); - List links = ReplaceInternalLinks(model); + model = ReplaceInternalLinks(model); //santize data - model.RawDescription = StripHTML(model.RichDescription); + model.RawDescription = StripHTML(model.RawDescription); await _dbcontext.Main.AddAsync(model); - await _dbcontext.Links.AddRangeAsync(links); + await _dbcontext.Links.AddRangeAsync(model.Links); await _dbcontext.SaveChanges(); return model.Id; } - private List ReplaceInternalLinks(Main model) + private Main ReplaceInternalLinks(Main model) { string searchStartString = ""; - var links = new List(); + model.Links = new List(); if (model.RichDescription != null) { @@ -204,6 +210,7 @@ private List ReplaceInternalLinks(Main model) } var newDesc = new string(model.RichDescription); + var newDesc2 = new string(model.RichDescription); for (int i = 0; i < startIndexes.Count; i++) { @@ -214,9 +221,10 @@ private List ReplaceInternalLinks(Main model) var desc = value.Substring(value.IndexOf(">") + 1, value.IndexOf("<", value.IndexOf(">")) - value.IndexOf(">") - 1); newDesc = newDesc.Replace(value, $"$$${id}$$$"); + newDesc2 = newDesc2.Replace(value, desc); // add to links list - links.Add(new Links + model.Links.Add(new Links { Id = Guid.NewGuid(), MainId = model.Id, @@ -229,9 +237,10 @@ private List ReplaceInternalLinks(Main model) } model.RichDescription = newDesc; + model.RawDescription = newDesc2; } - return links; + return model; } #endregion @@ -243,15 +252,25 @@ public async Task UpdateAsync(Guid id, MainUpdateModel model) if (entry != null) { - entry.Title = model.Title; - entry.RichDescription = model.RichDescription; + if (model != null) + { + entry.Title = model.Title ?? entry.Title; - // TODO : do conversion on raw description + if (model.RichDescription != null) + { + entry.RichDescription = model.RichDescription; - entry.Category = model.Category; + entry = ReplaceInternalLinks(entry); - _dbcontext.Main.Update(entry); - await _dbcontext.SaveChanges(); + //santize data + entry.RawDescription = StripHTML(entry.RawDescription); + } + + entry.Category = model.Category ?? entry.Category; + + _dbcontext.Main.Update(entry); + await _dbcontext.SaveChanges(); + } } else throw new UpdateFailedException(UpdateFailErrorCode.EntryNotFound); From c23186763d54b89e0e085952d633c76a3026f02a Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Mon, 24 Jan 2022 14:02:45 -0500 Subject: [PATCH 04/16] Fixed a UI bug --- .../Controllers/EncylopediaController.cs | 11 +++- Library.Encyclopedia.API/appsettings.json | 4 +- .../DataAccess/MainDataAccess.cs | 55 ++++++++++++++++++- .../Interfaces/IMainDataAccess.cs | 2 +- 4 files changed, 64 insertions(+), 8 deletions(-) diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index e7c80ac..143c547 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -205,9 +205,16 @@ public async Task Update(Guid id, [FromBody]MainUpdateModel model { try { - await mainDataAccess.UpdateAsync(id, model); + var response = await mainDataAccess.UpdateAsync(id, model); - return Ok(); + if (response == null) + { + return StatusCode(500); + } + else + { + return Ok(response); + } } catch (Exception ex) { diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index aa3949b..9b70064 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -10,8 +10,8 @@ "App-Base-Url": "https://tools.library.pfw.edu/encyclopedia", "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" - //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" + //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" + "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" }, "ApiKey": "5929b003-8895-4fb3-bbb0-2eb101c48f66", "FTPSettings": { diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index b405de9..0145e33 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -168,8 +168,8 @@ public async Task CreateAsync(Main model) // assign a random id model.Id = Guid.NewGuid(); - model = ReplaceInternalLinks(model); + model = ReplaceLineBreaks(model); //santize data model.RawDescription = StripHTML(model.RawDescription); @@ -181,6 +181,51 @@ public async Task CreateAsync(Main model) return model.Id; } + private Main ReplaceLineBreaks(Main model) + { + string searchStartString = "

"; + string searchEndString = "

"; + + if (model.RichDescription != null) + { + // Extract start indices + List startIndexes = new List(); + for (int index = 0; ; index += searchStartString.Length) + { + index = model.RichDescription.IndexOf(searchStartString, index); + if (index != -1) + startIndexes.Add(index); + else break; + } + + // Extract end indices + List endIndexes = new List(); + for (int index = 0; ; index += searchEndString.Length) + { + index = model.RichDescription.IndexOf(searchEndString, index); + if (index != -1) + endIndexes.Add(index); + else break; + } + + var newDesc = new string(model.RichDescription); + + for (int i = 0; i < startIndexes.Count; i++) + { + string tag = model.RichDescription.Substring(startIndexes[i], endIndexes[i] - startIndexes[i] + searchEndString.Length); + string value = model.RichDescription.Substring(startIndexes[i] + searchStartString.Length, endIndexes[i] - startIndexes[i] - +searchStartString.Length); + if (string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value)) + { + newDesc = newDesc.Replace(tag, "
"); + } + } + + model.RichDescription = newDesc; + } + + return model; + } + private Main ReplaceInternalLinks(Main model) { string searchStartString = "
CreateAsync(Main model) private Main ReplaceLineBreaks(Main model) { - string searchStartString = "

"; + string searchStartString = "') + 1); + if (string.IsNullOrEmpty(value) || string.IsNullOrWhiteSpace(value)) { newDesc = newDesc.Replace(tag, "
"); @@ -303,6 +309,9 @@ public async Task

UpdateAsync(Guid id, MainUpdateModel model) if (model.RichDescription != null) { + if (model.RichDescription!=null && model.RichDescription.Contains("$$$")) + throw new UpdateFailedException(UpdateFailErrorCode.INVALID_INPUT_SEQUENCE); + entry.RichDescription = model.RichDescription; entry = ReplaceInternalLinks(entry); diff --git a/Library.Encyclopedia.Entity/Exceptions/CreateFailedException.cs b/Library.Encyclopedia.Entity/Exceptions/CreateFailedException.cs new file mode 100644 index 0000000..1fd9049 --- /dev/null +++ b/Library.Encyclopedia.Entity/Exceptions/CreateFailedException.cs @@ -0,0 +1,19 @@ +using EnumsNET; +using System; +using System.ComponentModel; + +namespace Library.Encyclopedia.Entity.Exceptions +{ + public class CreateFailedException : Exception + { + public CreateFailedException(CreateFailErrorCode code) : base(((CreateFailErrorCode)code).AsString(EnumFormat.Description)) + { + } + } + + public enum CreateFailErrorCode + { + [Description("$$$ is not an accepted input")] + INVALID_INPUT_SEQUENCE + } +} \ No newline at end of file diff --git a/Library.Encyclopedia.Entity/Exceptions/UpdateFailedException.cs b/Library.Encyclopedia.Entity/Exceptions/UpdateFailedException.cs index 7bd1aa6..eb04a1e 100644 --- a/Library.Encyclopedia.Entity/Exceptions/UpdateFailedException.cs +++ b/Library.Encyclopedia.Entity/Exceptions/UpdateFailedException.cs @@ -14,6 +14,8 @@ public UpdateFailedException(UpdateFailErrorCode code) : base(((UpdateFailErrorC public enum UpdateFailErrorCode { [Description("Entry not found!")] - EntryNotFound + EntryNotFound, + [Description("$$$ is not an accepted input")] + INVALID_INPUT_SEQUENCE } } From 1f26368913c03159360cad2e01d12ec191d2a900 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Mon, 24 Jan 2022 20:59:19 -0500 Subject: [PATCH 06/16] Added delete. --- .../DataAccess/MainDataAccess.cs | 25 +++++++++++++------ .../Exceptions/GetFailedException.cs | 18 +++++++++++++ 2 files changed, 35 insertions(+), 8 deletions(-) create mode 100644 Library.Encyclopedia.Entity/Exceptions/GetFailedException.cs diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index bdc7d83..3f7d650 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -147,15 +147,23 @@ async Task
IMainDataAccess.GetAsync(Guid id) { Main item = await _dbcontext.Main.Include(s => s.Files).Include(s => s.Links).FirstOrDefaultAsync(s => s.Id == id); - // replace links in item - while (item.RichDescription.IndexOf("$$$") != -1) + if (item != null) { - var startIndex = item.RichDescription.IndexOf("$$$") + 3; - var endIndex = item.RichDescription.Substring(startIndex).IndexOf("$$$"); + if (item.RichDescription != null) + { + // replace links in item + while (item.RichDescription.IndexOf("$$$") != -1) + { + var startIndex = item.RichDescription.IndexOf("$$$") + 3; + var endIndex = item.RichDescription.Substring(startIndex).IndexOf("$$$"); - var referenceid = item.RichDescription.Substring(startIndex, endIndex); + var referenceid = item.RichDescription.Substring(startIndex, endIndex); - item.RichDescription = item.RichDescription.Replace($"$$${referenceid}$$$", $"{item.Links.FirstOrDefault(s => s.ReferenceId.ToString() == referenceid).Description}"); + item.RichDescription = item.RichDescription.Replace($"$$${referenceid}$$$", $"{item.Links.FirstOrDefault(s => s.ReferenceId.ToString() == referenceid).Description}"); + } + } + + return item; } return item; @@ -309,7 +317,7 @@ public async Task
UpdateAsync(Guid id, MainUpdateModel model) if (model.RichDescription != null) { - if (model.RichDescription!=null && model.RichDescription.Contains("$$$")) + if (model.RichDescription != null && model.RichDescription.Contains("$$$")) throw new UpdateFailedException(UpdateFailErrorCode.INVALID_INPUT_SEQUENCE); entry.RichDescription = model.RichDescription; @@ -339,10 +347,11 @@ public async Task
UpdateAsync(Guid id, MainUpdateModel model) public async Task DeleteAsync(Guid id) { // find the entry - var entry = await _dbcontext.Main.FirstOrDefaultAsync(s => s.Id == id); + var entry = await _dbcontext.Main.Include(s => s.Links).FirstOrDefaultAsync(s => s.Id == id); if (entry != null) { + if (entry.Links != null && entry.Links.Any()) _dbcontext.Links.RemoveRange(entry.Links); _dbcontext.Main.Remove(entry); await _dbcontext.SaveChanges(); } diff --git a/Library.Encyclopedia.Entity/Exceptions/GetFailedException.cs b/Library.Encyclopedia.Entity/Exceptions/GetFailedException.cs new file mode 100644 index 0000000..8fdf675 --- /dev/null +++ b/Library.Encyclopedia.Entity/Exceptions/GetFailedException.cs @@ -0,0 +1,18 @@ +using EnumsNET; +using System; +using System.ComponentModel; + +namespace Library.Encyclopedia.Entity.Exceptions +{ + public class GetFailedException : Exception + { + public GetFailedException(GetFailErrorCode code) : base(((GetFailErrorCode)code).AsString(EnumFormat.Description)) + { + } + } + + public enum GetFailErrorCode + { + + } +} \ No newline at end of file From 5610b646ff8778ed32e594056851ff915b9380e8 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Tue, 25 Jan 2022 14:11:02 -0500 Subject: [PATCH 07/16] Added login api --- .../Controllers/EncylopediaController.cs | 25 +++++++++++++++++-- .../Library.Encyclopedia.API.csproj | 4 +++ .../DataAccess/MainDataAccess.cs | 3 ++- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index 143c547..1338864 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -4,12 +4,14 @@ using Library.Encyclopedia.Entity.Interfaces; using Library.Encyclopedia.Entity.Models; using Library.Encyclopedia.Entity.Models.External; +using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; +using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; namespace Library.Encyclopedia.Controllers @@ -35,6 +37,25 @@ public EncylopediaController(ILogger logger, IApplication this.mainDataAccess = new MainDataAccess(dbContext, configuration); } + [HttpGet("Login")] + [Authorize(Roles = @"EncyclopediaAdministrators")] + public IActionResult Login() + { + try + { + using (var context = new PrincipalContext(ContextType.Domain | ContextType.Machine)) + { + var usr = UserPrincipal.FindByIdentity(context, this.HttpContext.User.Identity.Name); + return Ok(usr.DisplayName); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + /// /// Get all items based on search query /// @@ -86,7 +107,7 @@ public async Task GetByCategory(string category, { try { - var response = await mainDataAccess.GetByCategoryAsync(category, offset, limit,previewSize, asc); + var response = await mainDataAccess.GetByCategoryAsync(category, offset, limit, previewSize, asc); if (response == null) { @@ -120,7 +141,7 @@ public async Task GetByStartingAlphabet(char alphabet, { try { - var response = await mainDataAccess.GetByAlphabetAsync(alphabet, offset, limit,previewSize, asc); + var response = await mainDataAccess.GetByAlphabetAsync(alphabet, offset, limit, previewSize, asc); if (response == null) { diff --git a/Library.Encyclopedia.API/Library.Encyclopedia.API.csproj b/Library.Encyclopedia.API/Library.Encyclopedia.API.csproj index 5216164..37b1980 100644 --- a/Library.Encyclopedia.API/Library.Encyclopedia.API.csproj +++ b/Library.Encyclopedia.API/Library.Encyclopedia.API.csproj @@ -4,6 +4,10 @@ netcoreapp3.1 + + + + diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index 3f7d650..20fa987 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -178,6 +178,7 @@ public async Task CreateAsync(Main model) // assign a random id model.Id = Guid.NewGuid(); + model.Category = model.Category == string.Empty ? null : model.Category; model = ReplaceInternalLinks(model); model = ReplaceLineBreaks(model); @@ -329,7 +330,7 @@ public async Task
UpdateAsync(Guid id, MainUpdateModel model) entry.RawDescription = StripHTML(entry.RawDescription); } - entry.Category = model.Category ?? entry.Category; + entry.Category = model.Category == null ? entry.Category : model.Category == string.Empty ? null : model.Category; _dbcontext.Main.Update(entry); await _dbcontext.SaveChanges(); From ba2625f87b3ba7e57d63a4374903f4138bd4acbe Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Wed, 26 Jan 2022 18:44:56 -0500 Subject: [PATCH 08/16] File upload implemented --- .../Controllers/EncylopediaController.cs | 6 +- .../Controllers/FilesController.cs | 49 ++++++------- Library.Encyclopedia.API/Startup.cs | 12 +++- Library.Encyclopedia.API/appsettings.json | 14 ++-- Library.Encyclopedia.API/web.config | 13 ++++ .../DataAccess/MainDataAccess.cs | 62 +++++++++++++---- .../FileAccess/FTPAdapter.cs | 69 ------------------- .../FileAccess/WindowsFileSaveAdapter.cs | 53 ++++++++++++++ .../Interfaces/IFilesAdapter.cs | 5 +- 9 files changed, 162 insertions(+), 121 deletions(-) create mode 100644 Library.Encyclopedia.API/web.config delete mode 100644 Library.Encyclopedia.DataAccess/FileAccess/FTPAdapter.cs create mode 100644 Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index 1338864..fab97c7 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -1,16 +1,13 @@ -using Library.Encyclopedia.API.Attributes; -using Library.Encyclopedia.DataAccess; +using Library.Encyclopedia.DataAccess; using Library.Encyclopedia.DataAccess.DataAccess; using Library.Encyclopedia.Entity.Interfaces; using Library.Encyclopedia.Entity.Models; using Library.Encyclopedia.Entity.Models.External; -using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; -using System.Collections.Generic; using System.DirectoryServices.AccountManagement; using System.Threading.Tasks; @@ -45,6 +42,7 @@ public IActionResult Login() { using (var context = new PrincipalContext(ContextType.Domain | ContextType.Machine)) { + var usr = UserPrincipal.FindByIdentity(context, this.HttpContext.User.Identity.Name); return Ok(usr.DisplayName); } diff --git a/Library.Encyclopedia.API/Controllers/FilesController.cs b/Library.Encyclopedia.API/Controllers/FilesController.cs index a98ffcb..ce3a146 100644 --- a/Library.Encyclopedia.API/Controllers/FilesController.cs +++ b/Library.Encyclopedia.API/Controllers/FilesController.cs @@ -3,6 +3,7 @@ using Library.Encyclopedia.DataAccess.DataAccess; using Library.Encyclopedia.Entity.Interfaces; using Library.Encyclopedia.Entity.Models; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; @@ -38,38 +39,34 @@ public FilesController(ILogger logger, IApplicationDbContext db /// /// [HttpPost("{id}")] - [ApiKey] - public async Task Post(Guid id, List files, [FromForm] Dictionary descriptions) + [Authorize(Roles = @"EncyclopediaAdministrators")] + public async Task Post(Guid id, IFormFile file, string description) { try { // Store files at remote location - var responses = filesAdapter.UploadFiles(id, files); + var response = await filesAdapter.UploadFile(id, file); - List fileResponses = new List(); - - // filter out the failed uploads - for (int i = 0; i < responses.Count(); i++) + if (response) { - if (responses.ToList()[i]) + string fileNameWithoutExt = Path.GetFileNameWithoutExtension(file.FileName); + Files file_entry = new Files { - string fileNameWithoutExt = Path.GetFileNameWithoutExtension(files[i].FileName); - fileResponses.Add(new Files - { - Id = Guid.NewGuid(), - MainId = id, - FileName = files[i].FileName, - FilePath = $@"/encyclopedia-media/{id}/{files[i].FileName}", - FileType = files[i].ContentType, - FileDescription = descriptions.ContainsKey(fileNameWithoutExt) ? descriptions[fileNameWithoutExt] : null - }); - } - } + Id = Guid.NewGuid(), + MainId = id, + FileName = file.FileName, + FilePath = $@"/{id}/{file.FileName}", + FileType = file.ContentType, + FileDescription = description + }; - // Save to database - await filesDataAccess.AddFiles(fileResponses); + // Save to database + await filesDataAccess.AddFiles(new List { file_entry }); - return Ok(fileResponses); + return Ok(file_entry); + } + else + return BadRequest(); } catch (Exception ex) { @@ -87,14 +84,12 @@ public async Task Post(Guid id, List files, [FromForm] /// /// [HttpGet("{id}")] - [ApiKey] + [Authorize(Roles = @"EncyclopediaAdministrators")] public async Task Get(Guid id, string directory) { try { - var files = await filesDataAccess.GetFiles(id); - var fileResponses = filesAdapter.DownloadFiles(files, directory); - return Ok(fileResponses); + return Ok(); } catch (Exception ex) { diff --git a/Library.Encyclopedia.API/Startup.cs b/Library.Encyclopedia.API/Startup.cs index 07b2104..80d2d73 100644 --- a/Library.Encyclopedia.API/Startup.cs +++ b/Library.Encyclopedia.API/Startup.cs @@ -3,6 +3,7 @@ using Library.Encyclopedia.Entity.Interfaces; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http.Features; using Microsoft.AspNetCore.Server.IISIntegration; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; @@ -38,14 +39,21 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); - string url = Configuration.GetValue("FTPSettings:url"); + string url = Configuration.GetValue("FTPSettings:filePath"); string userName = Configuration.GetValue("FTPSettings:username"); string password = Configuration.GetValue("FTPSettings:password"); services.AddScoped(s => new ApplicationDbContext(Configuration.GetConnectionString("DefaultConnection"))); services.AddScoped(s => { - return new FTPAdapter(url, new System.Net.NetworkCredential(userName, password)); + return new WindowsFileSaveAdapter(url); + }); + + services.Configure(x => + { + x.ValueLengthLimit = int.MaxValue; + x.MultipartBodyLengthLimit = int.MaxValue; + x.MultipartHeadersLengthLimit = int.MaxValue; }); } diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index 9b70064..abc7927 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -10,13 +10,17 @@ "App-Base-Url": "https://tools.library.pfw.edu/encyclopedia", "AllowedHosts": "*", "ConnectionStrings": { - //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" - "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" + "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" + //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" }, "ApiKey": "5929b003-8895-4fb3-bbb0-2eb101c48f66", "FTPSettings": { - "url": "10.161.100.82", - "username": "mazus01", - "password": "" + // For Linux + //"url": "10.161.100.82", + //"username": "mazus01", + //"password": "" + + // For Windows + "filePath": "O:\\Library\\Departments\\Applications\\PFW Encyclopedia Files\\" } } \ No newline at end of file diff --git a/Library.Encyclopedia.API/web.config b/Library.Encyclopedia.API/web.config new file mode 100644 index 0000000..a991ee8 --- /dev/null +++ b/Library.Encyclopedia.API/web.config @@ -0,0 +1,13 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index 20fa987..e3241b8 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -241,11 +241,13 @@ private Main ReplaceLineBreaks(Main model) return model; } - private Main ReplaceInternalLinks(Main model) + private Main ReplaceInternalLinks(Main model, bool isUpdate = false) { string searchStartString = ""; - model.Links = new List(); + model.Links ??= new List(); + var newCreatedLinks = new List(); + var updatedLinks = new List(); if (model.RichDescription != null) { @@ -283,21 +285,42 @@ private Main ReplaceInternalLinks(Main model) newDesc = newDesc.Replace(value, $"$$${id}$$$"); newDesc2 = newDesc2.Replace(value, desc); - // add to links list - model.Links.Add(new Links + if (!model.Links.Any(s => s.ReferenceId == Guid.Parse(id))) { - Id = Guid.NewGuid(), - MainId = model.Id, - ReferenceId = Guid.Parse(id), - Link = value, - Description = desc, - IsInternal = true - }); + // add to links list + Links item = new Links + { + Id = Guid.NewGuid(), + MainId = model.Id, + ReferenceId = Guid.Parse(id), + Link = value, + Description = desc, + IsInternal = true + }; + model.Links.Add(item); + + newCreatedLinks.Add(item); + } + else + { + model.Links.FirstOrDefault(s => s.ReferenceId == Guid.Parse(id)).Link = value; + model.Links.FirstOrDefault(s => s.ReferenceId == Guid.Parse(id)).Description = desc; + updatedLinks.Add(model.Links.FirstOrDefault(s => s.ReferenceId == Guid.Parse(id))); + } + } } model.RichDescription = newDesc; model.RawDescription = newDesc2; + + if (isUpdate) + { + _dbcontext.Links.AddRangeAsync(newCreatedLinks); + _dbcontext.Links.UpdateRange(updatedLinks); + _dbcontext.SaveChanges().Wait(); + } + } return model; @@ -323,7 +346,7 @@ public async Task
UpdateAsync(Guid id, MainUpdateModel model) entry.RichDescription = model.RichDescription; - entry = ReplaceInternalLinks(entry); + entry = ReplaceInternalLinks(entry, true); entry = ReplaceLineBreaks(entry); //santize data @@ -334,6 +357,21 @@ public async Task
UpdateAsync(Guid id, MainUpdateModel model) _dbcontext.Main.Update(entry); await _dbcontext.SaveChanges(); + + if (entry.RichDescription != null) + { + // replace links in item + while (entry.RichDescription.IndexOf("$$$") != -1) + { + var startIndex = entry.RichDescription.IndexOf("$$$") + 3; + var endIndex = entry.RichDescription.Substring(startIndex).IndexOf("$$$"); + + var referenceid = entry.RichDescription.Substring(startIndex, endIndex); + + entry.RichDescription = entry.RichDescription.Replace($"$$${referenceid}$$$", $"{entry.Links.FirstOrDefault(s => s.ReferenceId.ToString() == referenceid).Description}"); + } + } + return entry; } } diff --git a/Library.Encyclopedia.DataAccess/FileAccess/FTPAdapter.cs b/Library.Encyclopedia.DataAccess/FileAccess/FTPAdapter.cs deleted file mode 100644 index 1e1cae2..0000000 --- a/Library.Encyclopedia.DataAccess/FileAccess/FTPAdapter.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Library.Encyclopedia.Entity.Interfaces; -using Library.Encyclopedia.Entity.Models; -using Microsoft.AspNetCore.Http; -using Renci.SshNet; -using System; -using System.Collections.Generic; -using System.IO; -using System.Net; -using System.Text; - -namespace Library.Encyclopedia.DataAccess.FileAccess -{ - public class FTPAdapter : IFilesAdapter - { - private readonly string url; - private readonly NetworkCredential networkCredential; - - public FTPAdapter(string url, NetworkCredential networkCredential) - { - this.url = url; - this.networkCredential = networkCredential; - } - - public IEnumerable DownloadFiles(IEnumerable files, string directory) - { - List responses = new List(); - var customUrl = $@"/home/{networkCredential.UserName}"; - - using (SftpClient client = new SftpClient(url, 22, networkCredential.UserName, networkCredential.Password)) - { - client.Connect(); - foreach (var file in files) - { - using (var stream = File.OpenWrite(Path.Combine(directory, file.FileName))) - { - client.DownloadFile(customUrl + file.FilePath, stream); - responses.Add(true); - } - } - } - - return responses; - } - - public IEnumerable UploadFiles(Guid id, List files) - { - List responses = new List(); - var customUrl = $@"/home/{networkCredential.UserName}/encyclopedia-media/{id}/"; - - using (SftpClient client = new SftpClient(url, 22, networkCredential.UserName, networkCredential.Password)) - { - client.Connect(); - if (!client.Exists(customUrl)) - client.CreateDirectory(customUrl); - - foreach (var file in files) - { - if (client.Exists(customUrl + file.FileName)) - client.DeleteFile(customUrl + file.FileName); - - client.UploadFile(file.OpenReadStream(), customUrl + file.FileName); - responses.Add(true); - } - } - - return responses; - } - } -} diff --git a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs new file mode 100644 index 0000000..9b408c8 --- /dev/null +++ b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs @@ -0,0 +1,53 @@ +using Library.Encyclopedia.Entity.Interfaces; +using Library.Encyclopedia.Entity.Models; +using Microsoft.AspNetCore.Http; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.DataAccess.FileAccess +{ + public class WindowsFileSaveAdapter : IFilesAdapter + { + private readonly string networkPath; + + public WindowsFileSaveAdapter(string networkPath) + { + this.networkPath = networkPath; + } + public bool DownloadFile(Files file, string directory) + { + throw new NotImplementedException(); + } + + public async Task UploadFile(Guid id, IFormFile file) + { + try + { + var path = Path.Combine(networkPath, id.ToString(), file.FileName); + if (File.Exists(path)) + using (var stream = File.OpenWrite(path)) + { + await file.CopyToAsync(stream); + } + else + { + Directory.CreateDirectory(Path.GetDirectoryName(path)); + using (var stream = File.Create(path)) + { + await file.CopyToAsync(stream); + } + } + + return true; + } + catch (Exception ex) + { + return false; + } + } + } +} diff --git a/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs b/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs index 95bfb79..5dd34ef 100644 --- a/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs +++ b/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs @@ -2,12 +2,13 @@ using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; +using System.Threading.Tasks; namespace Library.Encyclopedia.Entity.Interfaces { public interface IFilesAdapter { - IEnumerable UploadFiles(Guid id, List files); - IEnumerable DownloadFiles(IEnumerable files, string directory); + public Task UploadFile(Guid id, IFormFile file); + bool DownloadFile(Files file, string directory); } } \ No newline at end of file From dd232b0bf126423789b825a46aa0848f91ae8d26 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Fri, 28 Jan 2022 05:31:02 -0500 Subject: [PATCH 09/16] Implemented File Service --- .../Controllers/EncylopediaController.cs | 4 +- .../Controllers/FilesController.cs | 29 ++++++++++++- .../DataAccess/MainDataAccess.cs | 41 ++++++++++++++++--- .../FileAccess/WindowsFileSaveAdapter.cs | 19 ++++++++- .../Interfaces/IFilesAdapter.cs | 5 ++- Library.Encyclopedia.Entity/Models/Files.cs | 2 + 6 files changed, 89 insertions(+), 11 deletions(-) diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index fab97c7..68b3d27 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -28,10 +28,10 @@ public class EncylopediaController : ControllerBase /// /// /// - public EncylopediaController(ILogger logger, IApplicationDbContext dbContext, IConfiguration configuration) + public EncylopediaController(ILogger logger, IApplicationDbContext dbContext, IConfiguration configuration, IFilesAdapter filesAdapter) { _logger = logger; - this.mainDataAccess = new MainDataAccess(dbContext, configuration); + this.mainDataAccess = new MainDataAccess(dbContext, configuration, filesAdapter); } [HttpGet("Login")] diff --git a/Library.Encyclopedia.API/Controllers/FilesController.cs b/Library.Encyclopedia.API/Controllers/FilesController.cs index ce3a146..acfcb17 100644 --- a/Library.Encyclopedia.API/Controllers/FilesController.cs +++ b/Library.Encyclopedia.API/Controllers/FilesController.cs @@ -56,6 +56,7 @@ public async Task Post(Guid id, IFormFile file, string descriptio MainId = id, FileName = file.FileName, FilePath = $@"/{id}/{file.FileName}", + FileSize = file.Length, FileType = file.ContentType, FileDescription = description }; @@ -85,10 +86,36 @@ public async Task Post(Guid id, IFormFile file, string descriptio /// [HttpGet("{id}")] [Authorize(Roles = @"EncyclopediaAdministrators")] - public async Task Get(Guid id, string directory) + public IActionResult Get(string id, string name, string contentType) { try { + var stream = this.filesAdapter.DownloadFile(id, name); + return File(stream, contentType); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + /// + /// Delete File + /// + /// + /// + /// + /// + /// + [HttpDelete("{id}")] + [Authorize(Roles = @"EncyclopediaAdministrators")] + public async Task Delete(string id, string fileName, string fileId) + { + try + { + this.filesAdapter.DeleteFile(id, fileName); + await filesDataAccess.RemoveFiles(new List { Guid.Parse(fileId) }); return Ok(); } catch (Exception ex) diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index e3241b8..64895af 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -18,12 +18,14 @@ namespace Library.Encyclopedia.DataAccess.DataAccess public class MainDataAccess : IMainDataAccess { private IApplicationDbContext _dbcontext; + private readonly IFilesAdapter filesAdapter; private string APP_BASE_URL; - public MainDataAccess(IApplicationDbContext dbcontext, IConfiguration configuration) + public MainDataAccess(IApplicationDbContext dbcontext, IConfiguration configuration, IFilesAdapter filesAdapter) { APP_BASE_URL = configuration.GetSection("App-Base-Url").Value; _dbcontext = dbcontext; + this.filesAdapter = filesAdapter; } #region GET @@ -86,7 +88,31 @@ async Task IMainDataAccess.GetByCategoryAsync(s return result; } - else return new MainMinimizedExternalCollection(); + else + { + IEnumerable
data; + if (ascending) + data = await _dbcontext.Main.Where(s => s.Category == null) + .OrderBy(s => s.Title.ToLower()) + .ThenBy(s => s.RawDescription.ToLower()) + .Skip(offset) + .Take(pagesize) + .ToListAsync(); + else + data = await _dbcontext.Main.Where(s => s.Category == null) + + .OrderByDescending(s => s.Title.ToLower()) + .ThenByDescending(s => s.RawDescription.ToLower()) + .Skip(offset) + .Take(pagesize) + .ToListAsync(); + + var total = await _dbcontext.Main.CountAsync(s => s.Category == null); + + MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(previewSize), total); + + return result; + } } async Task IMainDataAccess.GetByAlphabetAsync(char startingAlphabet, int offset, int pagesize, int previewSize, bool ascending) @@ -145,7 +171,7 @@ async Task IMainDataAccess.GetByAlphabetAsync(c async Task
IMainDataAccess.GetAsync(Guid id) { - Main item = await _dbcontext.Main.Include(s => s.Files).Include(s => s.Links).FirstOrDefaultAsync(s => s.Id == id); + Main item = await _dbcontext.Main.Include(s => s.Files).Include(s => s.Links).Include(s => s.Files).FirstOrDefaultAsync(s => s.Id == id); if (item != null) { @@ -331,7 +357,7 @@ private Main ReplaceInternalLinks(Main model, bool isUpdate = false) public async Task
UpdateAsync(Guid id, MainUpdateModel model) { // find the entry - var entry = await _dbcontext.Main.Include(s => s.Links).FirstOrDefaultAsync(s => s.Id == id); + var entry = await _dbcontext.Main.Include(s => s.Links).Include(s => s.Files).FirstOrDefaultAsync(s => s.Id == id); if (entry != null) { @@ -386,11 +412,16 @@ public async Task
UpdateAsync(Guid id, MainUpdateModel model) public async Task DeleteAsync(Guid id) { // find the entry - var entry = await _dbcontext.Main.Include(s => s.Links).FirstOrDefaultAsync(s => s.Id == id); + var entry = await _dbcontext.Main.Include(s => s.Links).Include(s => s.Files).FirstOrDefaultAsync(s => s.Id == id); if (entry != null) { if (entry.Links != null && entry.Links.Any()) _dbcontext.Links.RemoveRange(entry.Links); + if (entry.Files != null && entry.Files.Any()) + { + _dbcontext.Files.RemoveRange(entry.Files); + this.filesAdapter.DeleteFiles(entry.Id.ToString()); + } _dbcontext.Main.Remove(entry); await _dbcontext.SaveChanges(); } diff --git a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs index 9b408c8..9f45c17 100644 --- a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs +++ b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs @@ -18,9 +18,24 @@ public WindowsFileSaveAdapter(string networkPath) { this.networkPath = networkPath; } - public bool DownloadFile(Files file, string directory) + + public void DeleteFile(string id, string name) + { + File.Delete(Path.Combine(networkPath, id, name)); + } + + public void DeleteFiles(string id) + { + foreach (var file in Directory.GetFiles(Path.Combine(networkPath, id))) + { + File.Delete(file); + } + Directory.Delete(Path.Combine(networkPath, id)); + } + + public FileStream DownloadFile(string id, string name) { - throw new NotImplementedException(); + return File.OpenRead(Path.Combine(networkPath, id, name)); } public async Task UploadFile(Guid id, IFormFile file) diff --git a/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs b/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs index 5dd34ef..78f2d9b 100644 --- a/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs +++ b/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs @@ -2,6 +2,7 @@ using Microsoft.AspNetCore.Http; using System; using System.Collections.Generic; +using System.IO; using System.Threading.Tasks; namespace Library.Encyclopedia.Entity.Interfaces @@ -9,6 +10,8 @@ namespace Library.Encyclopedia.Entity.Interfaces public interface IFilesAdapter { public Task UploadFile(Guid id, IFormFile file); - bool DownloadFile(Files file, string directory); + FileStream DownloadFile(string id, string name); + public void DeleteFiles(string id); + public void DeleteFile(string id, string name); } } \ No newline at end of file diff --git a/Library.Encyclopedia.Entity/Models/Files.cs b/Library.Encyclopedia.Entity/Models/Files.cs index 3c981cf..f862f9d 100644 --- a/Library.Encyclopedia.Entity/Models/Files.cs +++ b/Library.Encyclopedia.Entity/Models/Files.cs @@ -13,6 +13,8 @@ public class Files public string FileName { get; set; } [Required] public string FileType { get; set; } + [Required] + public long FileSize { get; set; } public string FileDescription { get; set; } [Required] public string FilePath { get; set; } From b9b2f423d60b2f2aa5d8c2ef3ffe29eaf019ec1e Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Fri, 28 Jan 2022 16:59:45 -0500 Subject: [PATCH 10/16] Implemented Linux FTP adapter --- Library.Encyclopedia.API/Program.cs | 12 ++- Library.Encyclopedia.API/Startup.cs | 29 +++++-- Library.Encyclopedia.API/appsettings.json | 18 ++-- .../FileAccess/LinuxFileSaveAdapter.cs | 86 +++++++++++++++++++ .../FileAccess/WindowsFileSaveAdapter.cs | 2 +- .../Interfaces/IFilesAdapter.cs | 2 +- 6 files changed, 132 insertions(+), 17 deletions(-) create mode 100644 Library.Encyclopedia.DataAccess/FileAccess/LinuxFileSaveAdapter.cs diff --git a/Library.Encyclopedia.API/Program.cs b/Library.Encyclopedia.API/Program.cs index 8b36f8c..abca967 100644 --- a/Library.Encyclopedia.API/Program.cs +++ b/Library.Encyclopedia.API/Program.cs @@ -21,6 +21,16 @@ public static IHostBuilder CreateHostBuilder(string[] args) => .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); - }); + }) + .ConfigureLogging((logging)=> { + // clear default logging providers + logging.ClearProviders(); + + // add built-in providers manually, as needed + logging.AddConsole(); + logging.AddDebug(); + logging.AddEventLog(); + logging.AddEventSourceLogger(); + }); } } diff --git a/Library.Encyclopedia.API/Startup.cs b/Library.Encyclopedia.API/Startup.cs index 80d2d73..c90cbf6 100644 --- a/Library.Encyclopedia.API/Startup.cs +++ b/Library.Encyclopedia.API/Startup.cs @@ -25,6 +25,7 @@ public Startup(IConfiguration configuration) // This method gets called by the runtime. Use this method to add services to the container. public void ConfigureServices(IServiceCollection services) { + services.AddLogging(); services.AddCors(options => { options.AddPolicy(name: MyAllowSpecificOrigins, @@ -39,16 +40,30 @@ public void ConfigureServices(IServiceCollection services) services.AddControllers(); - string url = Configuration.GetValue("FTPSettings:filePath"); - string userName = Configuration.GetValue("FTPSettings:username"); - string password = Configuration.GetValue("FTPSettings:password"); + if (Configuration.GetValue("FileAdapterSettings:type").Equals("linux", System.StringComparison.OrdinalIgnoreCase)) + { + string url = Configuration.GetValue("FileAdapterSettings:url"); + string userName = Configuration.GetValue("FileAdapterSettings:username"); + string password = Configuration.GetValue("FileAdapterSettings:password"); + string basePath = Configuration.GetValue("FileAdapterSettings:basePath"); - services.AddScoped(s => new ApplicationDbContext(Configuration.GetConnectionString("DefaultConnection"))); - services.AddScoped(s => + services.AddSingleton(s => + { + return new LinuxFileSaveAdapter(url, userName, password, basePath); + }); + } + else if(Configuration.GetValue("FileAdapterSettings:type").Equals("windows", System.StringComparison.OrdinalIgnoreCase)) { - return new WindowsFileSaveAdapter(url); - }); + string filePath = Configuration.GetValue("FileAdapterSettings:filePath"); + services.AddSingleton(s => + { + return new WindowsFileSaveAdapter(filePath); + }); + } + + services.AddScoped(s => new ApplicationDbContext(Configuration.GetConnectionString("DefaultConnection"))); + services.Configure(x => { x.ValueLengthLimit = int.MaxValue; diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index abc7927..50bf267 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -10,17 +10,21 @@ "App-Base-Url": "https://tools.library.pfw.edu/encyclopedia", "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" - //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" + //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" + "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" }, "ApiKey": "5929b003-8895-4fb3-bbb0-2eb101c48f66", - "FTPSettings": { + "FileAdapterSettings": { + // linux or windows + "type": "linux", + // For Linux - //"url": "10.161.100.82", - //"username": "mazus01", - //"password": "" + "url": "10.161.100.82", + "username": "serviceuser", + "password": "P!ssword+007", + "basePath": "Applications/PFW Encyclopedia Files" // For Windows - "filePath": "O:\\Library\\Departments\\Applications\\PFW Encyclopedia Files\\" + //"filePath": "C:\\inetpub\\wwwroot\\PFW Encyclopedia Files\\" } } \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/FileAccess/LinuxFileSaveAdapter.cs b/Library.Encyclopedia.DataAccess/FileAccess/LinuxFileSaveAdapter.cs new file mode 100644 index 0000000..b1b3222 --- /dev/null +++ b/Library.Encyclopedia.DataAccess/FileAccess/LinuxFileSaveAdapter.cs @@ -0,0 +1,86 @@ +using Library.Encyclopedia.Entity.Interfaces; +using Microsoft.AspNetCore.Http; +using Renci.SshNet; +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.DataAccess.FileAccess +{ + public class LinuxFileSaveAdapter : IFilesAdapter, IDisposable + { + private readonly string url; + private readonly string basePath; + private readonly NetworkCredential networkCredential; + private SftpClient client; + + public LinuxFileSaveAdapter(string url, string userName, string password, string basePath) + { + this.url = url; + this.basePath = basePath; + this.networkCredential = new NetworkCredential(userName, password); + + client = new SftpClient(url, 22, networkCredential.UserName, networkCredential.Password); + client.Connect(); + } + + public void DeleteFile(string id, string name) + { + var completeUrl = $@"/home/{networkCredential.UserName}/" + basePath + '/' + id.ToString() + '/' + name; + + + if (client.Exists(completeUrl)) + client.DeleteFile(completeUrl); + } + + public void DeleteFiles(string id) + { + var completeUrl = $@"/home/{networkCredential.UserName}/" + basePath + '/' + id.ToString(); + + if (client.Exists(completeUrl)) + { + IEnumerable files = client.ListDirectory(completeUrl); + foreach (var file in files) + { + if (file.Name != "." && file.Name != "..") + { + client.DeleteFile(file.FullName); + } + } + + client.DeleteDirectory(completeUrl); + } + } + + public void Dispose() + { + this.client.Disconnect(); + this.client.Dispose(); + } + + public Stream DownloadFile(string id, string name) + { + var completeUrl = $@"/home/{networkCredential.UserName}/" + basePath + '/' + id.ToString() + '/' + name; + return client.OpenRead(completeUrl); + } + + public Task UploadFile(Guid id, IFormFile file) + { + var completeUrl = $@"/home/{networkCredential.UserName}/" + basePath + '/' + id.ToString() + '/' + file.FileName; + + if (!client.Exists($@"/home/{networkCredential.UserName}/" + basePath + '/' + id.ToString())) + client.CreateDirectory($@"/home/{networkCredential.UserName}/" + basePath + '/' + id.ToString()); + + if (client.Exists(completeUrl)) + client.DeleteFile(completeUrl); + + client.Create(completeUrl); + client.UploadFile(file.OpenReadStream(), completeUrl, true); + + return Task.FromResult(true); + } + } +} diff --git a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs index 9f45c17..c2ae715 100644 --- a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs +++ b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs @@ -33,7 +33,7 @@ public void DeleteFiles(string id) Directory.Delete(Path.Combine(networkPath, id)); } - public FileStream DownloadFile(string id, string name) + public Stream DownloadFile(string id, string name) { return File.OpenRead(Path.Combine(networkPath, id, name)); } diff --git a/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs b/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs index 78f2d9b..e65813a 100644 --- a/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs +++ b/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs @@ -10,7 +10,7 @@ namespace Library.Encyclopedia.Entity.Interfaces public interface IFilesAdapter { public Task UploadFile(Guid id, IFormFile file); - FileStream DownloadFile(string id, string name); + Stream DownloadFile(string id, string name); public void DeleteFiles(string id); public void DeleteFile(string id, string name); } From d58d681fdf6df5f9f6e66c79c51c05672458b60e Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Tue, 1 Feb 2022 11:52:49 -0500 Subject: [PATCH 11/16] Preview Files --- .../Attributes/ApiKeyAttribute.cs | 4 + .../Controllers/EncylopediaController.cs | 84 ++++++++++++------- .../Controllers/FilesController.cs | 56 ++++++++++++- Library.Encyclopedia.API/Startup.cs | 10 ++- Library.Encyclopedia.API/appsettings.json | 14 ++-- .../FileAccess/LinuxFileSaveAdapter.cs | 23 ++++- .../FileAccess/WindowsFileSaveAdapter.cs | 19 ++++- .../Interfaces/IFilesAdapter.cs | 3 + 8 files changed, 165 insertions(+), 48 deletions(-) diff --git a/Library.Encyclopedia.API/Attributes/ApiKeyAttribute.cs b/Library.Encyclopedia.API/Attributes/ApiKeyAttribute.cs index 924a61a..a3ee159 100644 --- a/Library.Encyclopedia.API/Attributes/ApiKeyAttribute.cs +++ b/Library.Encyclopedia.API/Attributes/ApiKeyAttribute.cs @@ -7,6 +7,10 @@ namespace Library.Encyclopedia.API.Attributes { + /// + /// This is a custom attribute for API Kay validation + /// Extracts API key from the header and then checks it with the API KEY hardcoded in appsettings + /// [AttributeUsage(validOn: AttributeTargets.Class | AttributeTargets.Method)] public class ApiKeyAttribute : Attribute, IAsyncActionFilter { diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index 68b3d27..023d81a 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -14,26 +14,37 @@ namespace Library.Encyclopedia.Controllers { /// - /// CRUD operations on Main Table + /// Controller to perform CRUD operations on Main Table /// [ApiController] [Route("[controller]")] public class EncylopediaController : ControllerBase { + #region PRIVATE FIELDS private readonly ILogger _logger; private readonly IMainDataAccess mainDataAccess; + #endregion + #region CONSTRUCTOR /// - /// Constructor + /// Contructor for EncylopediaController /// - /// - /// + /// logger instance + /// db instance + /// appsettings configuration + /// file upload/download/delete layer public EncylopediaController(ILogger logger, IApplicationDbContext dbContext, IConfiguration configuration, IFilesAdapter filesAdapter) { _logger = logger; this.mainDataAccess = new MainDataAccess(dbContext, configuration, filesAdapter); - } + } + #endregion + #region LOGIN + /// + /// Login to perform POST/PUT/DELETE operations on the database + /// + /// [HttpGet("Login")] [Authorize(Roles = @"EncyclopediaAdministrators")] public IActionResult Login() @@ -53,7 +64,9 @@ public IActionResult Login() throw; } } + #endregion + #region GET /// /// Get all items based on search query /// @@ -185,21 +198,19 @@ public async Task Get(Guid id) } /// - /// Create new entry + /// Get all categories /// /// - [HttpPost] - //[ApiKey] - [Authorize(Roles = @"EncyclopediaAdministrators")] - public async Task Create([FromBody]Main model) + [HttpGet("fetchallcategories")] + public async Task GetAllCategories() { try { - var response = await mainDataAccess.CreateAsync(model); + var response = await mainDataAccess.GetAllCategoriesAsync(); if (response == null) { - return StatusCode(500); + return StatusCode(204); } else { @@ -212,19 +223,21 @@ public async Task Create([FromBody]Main model) throw; } } + #endregion + #region POST /// /// Create new entry /// /// - [HttpPut("{id}")] + [HttpPost] //[ApiKey] [Authorize(Roles = @"EncyclopediaAdministrators")] - public async Task Update(Guid id, [FromBody]MainUpdateModel model) + public async Task Create([FromBody]Main model) { try { - var response = await mainDataAccess.UpdateAsync(id, model); + var response = await mainDataAccess.CreateAsync(model); if (response == null) { @@ -241,21 +254,30 @@ public async Task Update(Guid id, [FromBody]MainUpdateModel model throw; } } + #endregion + #region PUT /// /// Create new entry /// /// - [HttpDelete("{id}")] + [HttpPut("{id}")] //[ApiKey] [Authorize(Roles = @"EncyclopediaAdministrators")] - public async Task Delete(Guid id) + public async Task Update(Guid id, [FromBody]MainUpdateModel model) { try { - await mainDataAccess.DeleteAsync(id); + var response = await mainDataAccess.UpdateAsync(id, model); - return Ok(); + if (response == null) + { + return StatusCode(500); + } + else + { + return Ok(response); + } } catch (Exception ex) { @@ -263,32 +285,30 @@ public async Task Delete(Guid id) throw; } } + #endregion + #region DELETE /// - /// Get all categories + /// Create new entry /// /// - [HttpGet("fetchallcategories")] - public async Task GetAllCategories() + [HttpDelete("{id}")] + //[ApiKey] + [Authorize(Roles = @"EncyclopediaAdministrators")] + public async Task Delete(Guid id) { try { - var response = await mainDataAccess.GetAllCategoriesAsync(); + await mainDataAccess.DeleteAsync(id); - if (response == null) - { - return StatusCode(204); - } - else - { - return Ok(response); - } + return Ok(); } catch (Exception ex) { _logger.LogError(ex, $"an error has occured {ex.Message}"); throw; } - } + } + #endregion } } \ No newline at end of file diff --git a/Library.Encyclopedia.API/Controllers/FilesController.cs b/Library.Encyclopedia.API/Controllers/FilesController.cs index acfcb17..83c2610 100644 --- a/Library.Encyclopedia.API/Controllers/FilesController.cs +++ b/Library.Encyclopedia.API/Controllers/FilesController.cs @@ -6,6 +6,7 @@ using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; @@ -21,12 +22,14 @@ public class FilesController : ControllerBase { private readonly ILogger _logger; private readonly IFilesAdapter filesAdapter; + private readonly IConfiguration configuration; private readonly IFilesDataAccess filesDataAccess; - public FilesController(ILogger logger, IApplicationDbContext dbContext, IFilesAdapter filesAdapter) + public FilesController(ILogger logger, IApplicationDbContext dbContext, IFilesAdapter filesAdapter, IConfiguration configuration) { _logger = logger; this.filesAdapter = filesAdapter; + this.configuration = configuration; this.filesDataAccess = new FilesDataAccess(dbContext); } @@ -77,7 +80,7 @@ public async Task Post(Guid id, IFormFile file, string descriptio } /// - /// Upload Files + /// Download Files /// /// /// @@ -100,6 +103,55 @@ public IActionResult Get(string id, string name, string contentType) } } + /// + /// Create Viewable link + /// + /// + /// + /// + /// + /// + [HttpGet("CreateViewableLink/{id}")] + [Authorize(Roles = @"EncyclopediaAdministrators")] + public IActionResult CreateViewableLink(string id, string name) + { + try + { + this.filesAdapter.CreateViewableLink(id, name); + var baseUrl = configuration.GetSection("TempAssetsBaseUrl").Value; + return Ok(baseUrl + '/' + id + '/' + name); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + /// + /// Destroy Viewable link + /// + /// + /// + /// + /// + /// + [HttpDelete("DestroyViewableLink/{id}")] + [Authorize(Roles = @"EncyclopediaAdministrators")] + public IActionResult DestroyViewableLink(string id, string name) + { + try + { + this.filesAdapter.DestroyViewableLink(id, name); + return Ok(); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + /// /// Delete File /// diff --git a/Library.Encyclopedia.API/Startup.cs b/Library.Encyclopedia.API/Startup.cs index c90cbf6..a87594b 100644 --- a/Library.Encyclopedia.API/Startup.cs +++ b/Library.Encyclopedia.API/Startup.cs @@ -46,24 +46,26 @@ public void ConfigureServices(IServiceCollection services) string userName = Configuration.GetValue("FileAdapterSettings:username"); string password = Configuration.GetValue("FileAdapterSettings:password"); string basePath = Configuration.GetValue("FileAdapterSettings:basePath"); + string tempAssetsFilePath = Configuration.GetValue("TempAssetsLocation"); services.AddSingleton(s => { - return new LinuxFileSaveAdapter(url, userName, password, basePath); + return new LinuxFileSaveAdapter(url, userName, password, basePath, tempAssetsFilePath); }); } - else if(Configuration.GetValue("FileAdapterSettings:type").Equals("windows", System.StringComparison.OrdinalIgnoreCase)) + else if (Configuration.GetValue("FileAdapterSettings:type").Equals("windows", System.StringComparison.OrdinalIgnoreCase)) { string filePath = Configuration.GetValue("FileAdapterSettings:filePath"); + string tempAssetsFilePath = Configuration.GetValue("TempAssetsLocation"); services.AddSingleton(s => { - return new WindowsFileSaveAdapter(filePath); + return new WindowsFileSaveAdapter(filePath, tempAssetsFilePath); }); } services.AddScoped(s => new ApplicationDbContext(Configuration.GetConnectionString("DefaultConnection"))); - + services.Configure(x => { x.ValueLengthLimit = int.MaxValue; diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index 50bf267..bfac7f4 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -14,17 +14,19 @@ "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" }, "ApiKey": "5929b003-8895-4fb3-bbb0-2eb101c48f66", + "TempAssetsBaseUrl": "https://tools.library.pfw.edu/encyclopedia/pfwencyclopediaassets", + "TempAssetsLocation": "C:\\inetpub\\wwwroot\\pfwencyclopediaassets", "FileAdapterSettings": { // linux or windows - "type": "linux", + "type": "windows", // For Linux - "url": "10.161.100.82", - "username": "serviceuser", - "password": "P!ssword+007", - "basePath": "Applications/PFW Encyclopedia Files" + //"url": "10.161.100.82", + //"username": "serviceuser", + //"password": "P!ssword+007", + //"basePath": "Applications/PFW Encyclopedia Files" // For Windows - //"filePath": "C:\\inetpub\\wwwroot\\PFW Encyclopedia Files\\" + "filePath": "C:\\inetpub\\wwwroot\\PFW Encyclopedia Files\\" } } \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/FileAccess/LinuxFileSaveAdapter.cs b/Library.Encyclopedia.DataAccess/FileAccess/LinuxFileSaveAdapter.cs index b1b3222..7eddfd7 100644 --- a/Library.Encyclopedia.DataAccess/FileAccess/LinuxFileSaveAdapter.cs +++ b/Library.Encyclopedia.DataAccess/FileAccess/LinuxFileSaveAdapter.cs @@ -14,19 +14,33 @@ public class LinuxFileSaveAdapter : IFilesAdapter, IDisposable { private readonly string url; private readonly string basePath; + private readonly string tempAssetsFilePath; private readonly NetworkCredential networkCredential; private SftpClient client; - public LinuxFileSaveAdapter(string url, string userName, string password, string basePath) + public LinuxFileSaveAdapter(string url, string userName, string password, string basePath, string tempAssetsFilePath) { this.url = url; this.basePath = basePath; + this.tempAssetsFilePath = tempAssetsFilePath; this.networkCredential = new NetworkCredential(userName, password); client = new SftpClient(url, 22, networkCredential.UserName, networkCredential.Password); client.Connect(); } + public void CreateViewableLink(string id, string name) + { + if (!Directory.Exists(Path.Combine(tempAssetsFilePath, id))) + Directory.CreateDirectory(Path.Combine(tempAssetsFilePath, id)); + + var remotePath = $@"/home/{networkCredential.UserName}/" + basePath + '/' + id.ToString() + '/' + name; + using (Stream fileStream = File.Create(Path.Combine(tempAssetsFilePath, id, name))) + { + client.DownloadFile(remotePath, fileStream); + } + } + public void DeleteFile(string id, string name) { var completeUrl = $@"/home/{networkCredential.UserName}/" + basePath + '/' + id.ToString() + '/' + name; @@ -47,7 +61,7 @@ public void DeleteFiles(string id) { if (file.Name != "." && file.Name != "..") { - client.DeleteFile(file.FullName); + client.DeleteFile(file.FullName); } } @@ -55,6 +69,11 @@ public void DeleteFiles(string id) } } + public void DestroyViewableLink(string id, string name) + { + File.Delete(Path.Combine(tempAssetsFilePath, id, name)); + } + public void Dispose() { this.client.Disconnect(); diff --git a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs index c2ae715..7ac6be8 100644 --- a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs +++ b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs @@ -13,10 +13,20 @@ namespace Library.Encyclopedia.DataAccess.FileAccess public class WindowsFileSaveAdapter : IFilesAdapter { private readonly string networkPath; + private readonly string tempAssetFileLocation; - public WindowsFileSaveAdapter(string networkPath) + public WindowsFileSaveAdapter(string networkPath, string tempAssetFileLocation) { this.networkPath = networkPath; + this.tempAssetFileLocation = tempAssetFileLocation; + } + + public void CreateViewableLink(string id, string name) + { + if (!Directory.Exists(Path.Combine(tempAssetFileLocation, id))) + Directory.CreateDirectory(Path.Combine(tempAssetFileLocation, id)); + + File.Copy(Path.Combine(networkPath, id, name), Path.Combine(tempAssetFileLocation, id, name)); } public void DeleteFile(string id, string name) @@ -33,6 +43,11 @@ public void DeleteFiles(string id) Directory.Delete(Path.Combine(networkPath, id)); } + public void DestroyViewableLink(string id, string name) + { + File.Delete(Path.Combine(tempAssetFileLocation, id, name)); + } + public Stream DownloadFile(string id, string name) { return File.OpenRead(Path.Combine(networkPath, id, name)); @@ -59,7 +74,7 @@ public async Task UploadFile(Guid id, IFormFile file) return true; } - catch (Exception ex) + catch (Exception) { return false; } diff --git a/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs b/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs index e65813a..afbc621 100644 --- a/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs +++ b/Library.Encyclopedia.Entity/Interfaces/IFilesAdapter.cs @@ -13,5 +13,8 @@ public interface IFilesAdapter Stream DownloadFile(string id, string name); public void DeleteFiles(string id); public void DeleteFile(string id, string name); + + public void CreateViewableLink(string id, string name); + public void DestroyViewableLink(string id, string name); } } \ No newline at end of file From ee51c9073f968cb4354dcb52ff37ca463fca4716 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Tue, 1 Feb 2022 20:00:59 -0500 Subject: [PATCH 12/16] Fixed issue with doc viewer --- Library.Encyclopedia.API/Controllers/FilesController.cs | 3 --- Library.Encyclopedia.API/appsettings.json | 6 +++--- .../FileAccess/WindowsFileSaveAdapter.cs | 2 +- 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/Library.Encyclopedia.API/Controllers/FilesController.cs b/Library.Encyclopedia.API/Controllers/FilesController.cs index 83c2610..7a711fa 100644 --- a/Library.Encyclopedia.API/Controllers/FilesController.cs +++ b/Library.Encyclopedia.API/Controllers/FilesController.cs @@ -88,7 +88,6 @@ public async Task Post(Guid id, IFormFile file, string descriptio /// /// [HttpGet("{id}")] - [Authorize(Roles = @"EncyclopediaAdministrators")] public IActionResult Get(string id, string name, string contentType) { try @@ -112,7 +111,6 @@ public IActionResult Get(string id, string name, string contentType) /// /// [HttpGet("CreateViewableLink/{id}")] - [Authorize(Roles = @"EncyclopediaAdministrators")] public IActionResult CreateViewableLink(string id, string name) { try @@ -137,7 +135,6 @@ public IActionResult CreateViewableLink(string id, string name) /// /// [HttpDelete("DestroyViewableLink/{id}")] - [Authorize(Roles = @"EncyclopediaAdministrators")] public IActionResult DestroyViewableLink(string id, string name) { try diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index bfac7f4..b0058e8 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -10,11 +10,11 @@ "App-Base-Url": "https://tools.library.pfw.edu/encyclopedia", "AllowedHosts": "*", "ConnectionStrings": { - //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" - "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" + "DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=root" + //"DefaultConnection": "Server=localhost;Database=Encyclopedia;User=root;Password=RW_qh+-ta5hW*2s" }, "ApiKey": "5929b003-8895-4fb3-bbb0-2eb101c48f66", - "TempAssetsBaseUrl": "https://tools.library.pfw.edu/encyclopedia/pfwencyclopediaassets", + "TempAssetsBaseUrl": "https://tools.library.pfw.edu/pfwencyclopediaassets", "TempAssetsLocation": "C:\\inetpub\\wwwroot\\pfwencyclopediaassets", "FileAdapterSettings": { // linux or windows diff --git a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs index 7ac6be8..2782462 100644 --- a/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs +++ b/Library.Encyclopedia.DataAccess/FileAccess/WindowsFileSaveAdapter.cs @@ -26,7 +26,7 @@ public void CreateViewableLink(string id, string name) if (!Directory.Exists(Path.Combine(tempAssetFileLocation, id))) Directory.CreateDirectory(Path.Combine(tempAssetFileLocation, id)); - File.Copy(Path.Combine(networkPath, id, name), Path.Combine(tempAssetFileLocation, id, name)); + File.Copy(Path.Combine(networkPath, id, name), Path.Combine(tempAssetFileLocation, id, name), true); } public void DeleteFile(string id, string name) From dc9ce870a7fe11fadb5248417c9147c82b4a30d3 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Wed, 2 Feb 2022 19:44:52 -0500 Subject: [PATCH 13/16] Implemented Word Cloud --- .../Controllers/EncylopediaController.cs | 1 + .../Controllers/StatisticsController.cs | 68 +++++++ Library.Encyclopedia.API/Startup.cs | 4 +- .../ApplicationDbContext.cs | 11 +- .../DataAccess/MainDataAccess.cs | 11 +- .../IApplicationDbContext.cs | 3 + .../Library.Encyclopedia.DataAccess.csproj | 3 + .../CommonlyOccuringWordsAdapter.cs | 175 ++++++++++++++++++ .../QueryStatsAccess/QueryStatsAdapter.cs | 31 ++++ .../Models/QueryStats.cs | 13 ++ 10 files changed, 316 insertions(+), 4 deletions(-) create mode 100644 Library.Encyclopedia.API/Controllers/StatisticsController.cs create mode 100644 Library.Encyclopedia.DataAccess/QueryStatsAccess/CommonlyOccuringWordsAdapter.cs create mode 100644 Library.Encyclopedia.DataAccess/QueryStatsAccess/QueryStatsAdapter.cs create mode 100644 Library.Encyclopedia.Entity/Models/QueryStats.cs diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index 023d81a..ca21e77 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -1,5 +1,6 @@ using Library.Encyclopedia.DataAccess; using Library.Encyclopedia.DataAccess.DataAccess; +using Library.Encyclopedia.DataAccess.QueryStatsAccess; using Library.Encyclopedia.Entity.Interfaces; using Library.Encyclopedia.Entity.Models; using Library.Encyclopedia.Entity.Models.External; diff --git a/Library.Encyclopedia.API/Controllers/StatisticsController.cs b/Library.Encyclopedia.API/Controllers/StatisticsController.cs new file mode 100644 index 0000000..8d5c0e9 --- /dev/null +++ b/Library.Encyclopedia.API/Controllers/StatisticsController.cs @@ -0,0 +1,68 @@ +using Library.Encyclopedia.DataAccess; +using Library.Encyclopedia.DataAccess.QueryStatsAccess; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Caching.Memory; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.API.Controllers +{ + [ApiController] + [Route("[controller]")] + public class StatisticsController : ControllerBase + { + private readonly ILogger _logger; + private readonly CommonlyOccuringWordsAdapter commonlyOccuringWordsAdapter; + + public StatisticsController(ILogger logger, IApplicationDbContext applicationDbContext, IMemoryCache memoryCache) + { + _logger = logger; + this.commonlyOccuringWordsAdapter = new CommonlyOccuringWordsAdapter(applicationDbContext, memoryCache); + } + + [HttpGet("GetRecommendedWords")] + public IActionResult GetCommonlyOccuringWordsStats(int count = 10) + { + try + { + return Ok(commonlyOccuringWordsAdapter.GetCommonlyUsedWords(count)); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + [HttpGet("GetPopularWords")] + public IActionResult GetMostSearchedWordStats(int count = 10) + { + try + { + return Ok(commonlyOccuringWordsAdapter.GetCommonlySearchedWords(count)); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + [HttpGet("GetNLPResult")] + public IActionResult GetNLPResult() + { + try + { + return Ok(commonlyOccuringWordsAdapter.GetNLPResult()); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + } +} diff --git a/Library.Encyclopedia.API/Startup.cs b/Library.Encyclopedia.API/Startup.cs index a87594b..96f30d8 100644 --- a/Library.Encyclopedia.API/Startup.cs +++ b/Library.Encyclopedia.API/Startup.cs @@ -1,5 +1,6 @@ using Library.Encyclopedia.DataAccess; using Library.Encyclopedia.DataAccess.FileAccess; +using Library.Encyclopedia.DataAccess.QueryStatsAccess; using Library.Encyclopedia.Entity.Interfaces; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; @@ -64,7 +65,8 @@ public void ConfigureServices(IServiceCollection services) }); } - services.AddScoped(s => new ApplicationDbContext(Configuration.GetConnectionString("DefaultConnection"))); + services.AddSingleton(s => new ApplicationDbContext(Configuration.GetConnectionString("DefaultConnection"))); + services.AddMemoryCache(); services.Configure(x => { diff --git a/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs b/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs index 574dc9d..0c6069e 100644 --- a/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs +++ b/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs @@ -1,5 +1,6 @@ using Library.Encyclopedia.Entity.Models; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using System; using System.Threading.Tasks; @@ -16,16 +17,24 @@ public ApplicationDbContext(string connectionString) protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { var serverVersion = new MySqlServerVersion(new Version(8, 0, 18)); - optionsBuilder.UseMySql(connectionString, serverVersion); + optionsBuilder.UseMySql(connectionString, serverVersion, (mySqlOptions)=> { + mySqlOptions.EnableRetryOnFailure(); + }); } public DbSet
Main { get; set; } public DbSet Files { get; set; } public DbSet Links { get; set; } + public DbSet QueryStats { get; set; } public new async Task SaveChanges() { return await base.SaveChangesAsync(); } + + public async Task BeginTransactionAsync() + { + return await base.Database.BeginTransactionAsync(); + } } } \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index 64895af..95ccfe9 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -1,4 +1,5 @@ -using Library.Encyclopedia.Entity.Exceptions; +using Library.Encyclopedia.DataAccess.QueryStatsAccess; +using Library.Encyclopedia.Entity.Exceptions; using Library.Encyclopedia.Entity.Interfaces; using Library.Encyclopedia.Entity.Models; using Library.Encyclopedia.Entity.Models.External; @@ -10,6 +11,7 @@ using System.Linq; using System.Text; using System.Text.RegularExpressions; +using System.Threading; using System.Threading.Tasks; using System.Web; @@ -19,6 +21,7 @@ public class MainDataAccess : IMainDataAccess { private IApplicationDbContext _dbcontext; private readonly IFilesAdapter filesAdapter; + private readonly QueryStatsAdapter queryStatsAdapter; private string APP_BASE_URL; public MainDataAccess(IApplicationDbContext dbcontext, IConfiguration configuration, IFilesAdapter filesAdapter) @@ -26,6 +29,7 @@ public MainDataAccess(IApplicationDbContext dbcontext, IConfiguration configurat APP_BASE_URL = configuration.GetSection("App-Base-Url").Value; _dbcontext = dbcontext; this.filesAdapter = filesAdapter; + this.queryStatsAdapter = new QueryStatsAdapter(dbcontext); } #region GET @@ -55,6 +59,9 @@ async Task IMainDataAccess.GetAsync(string quer MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.MinimizeWithQuery(query, previewSize), total); + if (!string.IsNullOrEmpty(query) && !string.IsNullOrWhiteSpace(query) && total > 0 && query.Length >= 3) + this.queryStatsAdapter.AddQuery(query); + return result; } @@ -100,7 +107,7 @@ async Task IMainDataAccess.GetByCategoryAsync(s .ToListAsync(); else data = await _dbcontext.Main.Where(s => s.Category == null) - + .OrderByDescending(s => s.Title.ToLower()) .ThenByDescending(s => s.RawDescription.ToLower()) .Skip(offset) diff --git a/Library.Encyclopedia.DataAccess/IApplicationDbContext.cs b/Library.Encyclopedia.DataAccess/IApplicationDbContext.cs index 4c1fcf0..fb924c7 100644 --- a/Library.Encyclopedia.DataAccess/IApplicationDbContext.cs +++ b/Library.Encyclopedia.DataAccess/IApplicationDbContext.cs @@ -1,5 +1,6 @@ using Library.Encyclopedia.Entity.Models; using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Storage; using System.Threading.Tasks; namespace Library.Encyclopedia.DataAccess @@ -9,6 +10,8 @@ public interface IApplicationDbContext DbSet
Main { get; set; } DbSet Files { get; set; } DbSet Links { get; set; } + DbSet QueryStats { get; set; } Task SaveChanges(); + Task BeginTransactionAsync(); } } \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/Library.Encyclopedia.DataAccess.csproj b/Library.Encyclopedia.DataAccess/Library.Encyclopedia.DataAccess.csproj index e53781f..c75a2ba 100644 --- a/Library.Encyclopedia.DataAccess/Library.Encyclopedia.DataAccess.csproj +++ b/Library.Encyclopedia.DataAccess/Library.Encyclopedia.DataAccess.csproj @@ -5,6 +5,8 @@ + + @@ -18,6 +20,7 @@ + diff --git a/Library.Encyclopedia.DataAccess/QueryStatsAccess/CommonlyOccuringWordsAdapter.cs b/Library.Encyclopedia.DataAccess/QueryStatsAccess/CommonlyOccuringWordsAdapter.cs new file mode 100644 index 0000000..681bd64 --- /dev/null +++ b/Library.Encyclopedia.DataAccess/QueryStatsAccess/CommonlyOccuringWordsAdapter.cs @@ -0,0 +1,175 @@ +using Catalyst; +using Catalyst.Models; +using Library.Encyclopedia.Entity.Models; +using Microsoft.Extensions.Caching.Memory; +using Mosaik.Core; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading; + +namespace Library.Encyclopedia.DataAccess.QueryStatsAccess +{ + public class CommonlyOccuringWordsAdapter + { + private readonly IApplicationDbContext applicationDbContext; + private Dictionary commonlyOccuringWords; + private Dictionary> forReference; + private IMemoryCache _cache; + + public CommonlyOccuringWordsAdapter(IApplicationDbContext applicationDbContext, IMemoryCache cache) + { + commonlyOccuringWords = new Dictionary(); + forReference = new Dictionary>(); + this.applicationDbContext = applicationDbContext; + this._cache = cache; + } + + private void PopulateRecommendedWords() + { + commonlyOccuringWords = new Dictionary(); + forReference = new Dictionary>(); + + var allData = applicationDbContext.Main.ToList(); + + //BasicParser(allData); + AdvancedParser(allData); + + var cacheEntryOptions = new MemoryCacheEntryOptions() + .SetSlidingExpiration(TimeSpan.FromHours(1)); + + if (commonlyOccuringWords != null) + { + _cache.Set("commonlyOccuringWords", commonlyOccuringWords, cacheEntryOptions); + } + } + + private void AdvancedParser(List
allData) + { + English.Register(); + Storage.Current = new DiskStorage("catalyst-models"); + var nlp = Pipeline.For(Language.English); + + foreach (var item in allData) + { + ProcessText(nlp, item.Title, item.Id); + ProcessText(nlp, item.RawDescription, item.Id); + } + } + + private void ProcessText(Pipeline nlp, string text, Guid id) + { + var doc = new Document(text, Language.English); + nlp.ProcessSingle(doc); + + foreach (var sentence in doc) + { + foreach (var token in sentence) + { + switch (token.POS) + { + case PartOfSpeech.PROPN: + case PartOfSpeech.NOUN: + case PartOfSpeech.ADJ: + case PartOfSpeech.ADV: + case PartOfSpeech.NUM: + if (token.Value.Length >= 3) + { + if (commonlyOccuringWords.ContainsKey(token.Value)) + commonlyOccuringWords[token.Value]++; + else + commonlyOccuringWords.Add(token.Value, 1); + } + break; + case PartOfSpeech.NONE: + case PartOfSpeech.ADP: + case PartOfSpeech.AUX: + case PartOfSpeech.CCONJ: + case PartOfSpeech.DET: + case PartOfSpeech.INTJ: + case PartOfSpeech.PART: + case PartOfSpeech.PRON: + case PartOfSpeech.PUNCT: + case PartOfSpeech.SCONJ: + case PartOfSpeech.SYM: + case PartOfSpeech.VERB: + case PartOfSpeech.X: + default: + break; + } + + if (!forReference.ContainsKey(token.POS)) + forReference.Add(token.POS, new List() { token.Value.ToLower() }); + else + { + if (!forReference[token.POS].Contains(token.Value.ToLower())) + forReference[token.POS].Add(token.Value.ToLower()); + } + } + } + } + + private void BasicParser(List
allData) + { + foreach (var item in allData) + { + string[] seperators = { ", ", ". ", "! ", "? ", ": ", "; ", " " }; + + string v1 = item.Title.Replace('(', ' ') + .Replace(')', ' ') + .Replace('{', ' ') + .Replace('}', ' ') + .Replace('[', ' ') + .Replace(']', ' '); + foreach (var word in v1.Split(seperators, StringSplitOptions.RemoveEmptyEntries)) + { + if (!string.IsNullOrEmpty(word) && !string.IsNullOrWhiteSpace(word) && !word.ToCharArray().All(s => !char.IsLetterOrDigit(s))) + { + if (commonlyOccuringWords.ContainsKey(word)) + commonlyOccuringWords[word]++; + else + commonlyOccuringWords.Add(word, 1); + } + } + + string v2 = item.RawDescription.Replace('(', ' ') + .Replace(')', ' ') + .Replace('{', ' ') + .Replace('}', ' ') + .Replace('[', ' ') + .Replace(']', ' '); + + foreach (var word in v2.Split(seperators, StringSplitOptions.RemoveEmptyEntries)) + { + if (!string.IsNullOrEmpty(word) && !string.IsNullOrWhiteSpace(word) && !word.ToCharArray().All(s => !char.IsLetterOrDigit(s))) + { + if (commonlyOccuringWords.ContainsKey(word)) + commonlyOccuringWords[word]++; + else + commonlyOccuringWords.Add(word, 1); + } + } + } + } + + public Dictionary GetCommonlyUsedWords(int count = 0) + { + bool resultCacheValueFlag = _cache.TryGetValue("commonlyOccuringWords", out object value); + if (!resultCacheValueFlag || value == null) + PopulateRecommendedWords(); + + return _cache.Get>("commonlyOccuringWords").OrderByDescending(s => s.Value).Take(count).ToDictionary(x => x.Key, x => x.Value); + } + + public Dictionary GetCommonlySearchedWords(int count = 0) + { + IQueryable queryable = applicationDbContext.QueryStats.OrderByDescending(s => s.Count).Take(count); + return queryable.ToDictionary(s => s.Query, s => s.Count); + } + + public Dictionary> GetNLPResult() + { + return forReference.ToDictionary(s => s.Key.ToString(), s => s.Value); + } + } +} diff --git a/Library.Encyclopedia.DataAccess/QueryStatsAccess/QueryStatsAdapter.cs b/Library.Encyclopedia.DataAccess/QueryStatsAccess/QueryStatsAdapter.cs new file mode 100644 index 0000000..a659441 --- /dev/null +++ b/Library.Encyclopedia.DataAccess/QueryStatsAccess/QueryStatsAdapter.cs @@ -0,0 +1,31 @@ +using Library.Encyclopedia.Entity.Models; +using Microsoft.EntityFrameworkCore; +using System.Collections.Concurrent; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.DataAccess.QueryStatsAccess +{ + public class QueryStatsAdapter + { + private readonly IApplicationDbContext _dbcontext; + + public QueryStatsAdapter(IApplicationDbContext applicationDbContext) + { + this._dbcontext = applicationDbContext; + } + + public async Task AddQuery(string query) + { + QueryStats queryStats = await _dbcontext.QueryStats.FirstOrDefaultAsync(s => s.Query == query); + if (queryStats == null) + await _dbcontext.QueryStats.AddAsync(new QueryStats { Query = query, Count = 1 }); + else + { + queryStats.Count++; + _dbcontext.QueryStats.Update(queryStats); + } + + await _dbcontext.SaveChanges(); + } + } +} diff --git a/Library.Encyclopedia.Entity/Models/QueryStats.cs b/Library.Encyclopedia.Entity/Models/QueryStats.cs new file mode 100644 index 0000000..52d304f --- /dev/null +++ b/Library.Encyclopedia.Entity/Models/QueryStats.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Library.Encyclopedia.Entity.Models +{ + public class QueryStats + { + public long Id { get; set; } + public string Query { get; set; } + public long Count { get; set; } + } +} From d37d4a40bc5dd18c6a0aad470720c00d9278c7a2 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Thu, 3 Feb 2022 07:01:00 -0500 Subject: [PATCH 14/16] Implemented comments and suggestions --- .../Controllers/CommentController.cs | 197 ++++++++++++++++++ .../ApplicationDbContext.cs | 7 +- .../DataAccess/CommentsDataAccess.cs | 106 ++++++++++ .../DataAccess/LikesDataAccess.cs | 54 +++++ .../DataAccess/MainDataAccess.cs | 9 +- .../IApplicationDbContext.cs | 4 +- .../Interfaces/ICommentsDataAccess.cs | 19 ++ .../Interfaces/ILikesDataAccess.cs | 14 ++ .../Models/Comments.cs | 19 ++ .../External/CommentsExternalCollection.cs | 24 +++ Library.Encyclopedia.Entity/Models/Likes.cs | 13 ++ 11 files changed, 459 insertions(+), 7 deletions(-) create mode 100644 Library.Encyclopedia.API/Controllers/CommentController.cs create mode 100644 Library.Encyclopedia.DataAccess/DataAccess/CommentsDataAccess.cs create mode 100644 Library.Encyclopedia.DataAccess/DataAccess/LikesDataAccess.cs create mode 100644 Library.Encyclopedia.Entity/Interfaces/ICommentsDataAccess.cs create mode 100644 Library.Encyclopedia.Entity/Interfaces/ILikesDataAccess.cs create mode 100644 Library.Encyclopedia.Entity/Models/Comments.cs create mode 100644 Library.Encyclopedia.Entity/Models/External/CommentsExternalCollection.cs create mode 100644 Library.Encyclopedia.Entity/Models/Likes.cs diff --git a/Library.Encyclopedia.API/Controllers/CommentController.cs b/Library.Encyclopedia.API/Controllers/CommentController.cs new file mode 100644 index 0000000..7152f3d --- /dev/null +++ b/Library.Encyclopedia.API/Controllers/CommentController.cs @@ -0,0 +1,197 @@ +using Library.Encyclopedia.DataAccess; +using Library.Encyclopedia.DataAccess.DataAccess; +using Library.Encyclopedia.Entity.Interfaces; +using Library.Encyclopedia.Entity.Models; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.DirectoryServices.AccountManagement; +using System.Linq; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.API.Controllers +{ + [ApiController] + [Route("[controller]")] + public class CommentController : ControllerBase + { + private readonly ILogger _logger; + private readonly ICommentsDataAccess dataAccess; + private readonly ILikesDataAccess likesDataAccess; + + public CommentController(ILogger logger, IApplicationDbContext dbContext) + { + this._logger = logger; + dataAccess = new CommentsDataAccess(dbContext); + likesDataAccess = new LikesDataAccess(dbContext); + } + + [HttpGet] + [Authorize(Roles = @"EncyclopediaAdministrators")] + public async Task Get(int offset = 0, int limit = 10, bool asc = false) + { + try + { + var result = await dataAccess.Get(offset, limit, asc); + var likes = await likesDataAccess.GetLikes(result.Result.Select(s => s.Id).ToList()); + + if (likes != null) + { + foreach (var item in result.Result) + item.Likes = likes.ContainsKey(item.Id) ? likes[item.Id] : 0; + } + + if (result != null) + return Ok(result); + else + return StatusCode(500); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + [HttpPost("Resolve/{id}")] + [Authorize(Roles = @"EncyclopediaAdministrators")] + public async Task ResolveComment(Guid id, bool flag = true) + { + try + { + await dataAccess.Resolve(id, flag); + + return Ok(); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + [HttpGet("{id}")] + public async Task Get(Guid id) + { + try + { + var result = await dataAccess.Get(id); + var likes = await likesDataAccess.GetLikes(result.Select(s => s.Id).ToList()); + + if (likes != null) + { + foreach (var item in result) + item.Likes = likes.ContainsKey(item.Id) ? likes[item.Id] : 0; + } + + if (result != null) + return Ok(result); + else + return StatusCode(500); + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + [HttpPost] + [Authorize] + public async Task Post([FromBody]Comments comments) + { + try + { + using (var context = new PrincipalContext(ContextType.Domain | ContextType.Machine)) + { + var usr = UserPrincipal.FindByIdentity(context, this.HttpContext.User.Identity.Name); + comments.UserName = usr.DisplayName; + comments.UserSid = usr.Sid.Value; + + var result = await dataAccess.CreateComment(comments); + + if (result != null) + return Ok(result); + else + return StatusCode(500); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + [HttpDelete("{id}")] + [Authorize] + public async Task Delete(Guid id) + { + try + { + using (var context = new PrincipalContext(ContextType.Domain | ContextType.Machine)) + { + var usr = UserPrincipal.FindByIdentity(context, this.HttpContext.User.Identity.Name); + + if (usr.GetAuthorizationGroups().Select(s => s.Name).Contains("EncyclopediaAdministrators")) + await dataAccess.DeleteComment(id, string.Empty, true); + else + await dataAccess.DeleteComment(id, usr.Sid.Value); + + return Ok(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + [HttpPut("{id}")] + [Authorize] + public async Task Update(Guid id, string comment) + { + try + { + using (var context = new PrincipalContext(ContextType.Domain | ContextType.Machine)) + { + var usr = UserPrincipal.FindByIdentity(context, this.HttpContext.User.Identity.Name); + + await dataAccess.UpdateComment(id, comment, usr.Sid.Value); + + return Ok(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + + [HttpPost("Like/{id}")] + [Authorize] + public async Task LikeComment(Guid id, bool flag = true) + { + try + { + using (var context = new PrincipalContext(ContextType.Domain | ContextType.Machine)) + { + var usr = UserPrincipal.FindByIdentity(context, this.HttpContext.User.Identity.Name); + + await likesDataAccess.Like(id, usr.Sid.Value, flag); + + return Ok(); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + } +} diff --git a/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs b/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs index 0c6069e..35eb7ec 100644 --- a/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs +++ b/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs @@ -26,15 +26,12 @@ protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) public DbSet Files { get; set; } public DbSet Links { get; set; } public DbSet QueryStats { get; set; } + public DbSet Comments { get; set; } + public DbSet Likes { get; set; } public new async Task SaveChanges() { return await base.SaveChangesAsync(); } - - public async Task BeginTransactionAsync() - { - return await base.Database.BeginTransactionAsync(); - } } } \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/DataAccess/CommentsDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/CommentsDataAccess.cs new file mode 100644 index 0000000..73aca2c --- /dev/null +++ b/Library.Encyclopedia.DataAccess/DataAccess/CommentsDataAccess.cs @@ -0,0 +1,106 @@ +using Library.Encyclopedia.Entity.Interfaces; +using Library.Encyclopedia.Entity.Models; +using Library.Encyclopedia.Entity.Models.External; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.DataAccess.DataAccess +{ + public class CommentsDataAccess : ICommentsDataAccess + { + private readonly IApplicationDbContext dbContext; + + public CommentsDataAccess(IApplicationDbContext dbContext) + { + this.dbContext = dbContext; + } + + public async Task CreateComment(Comments comments) + { + comments.Id = Guid.NewGuid(); + comments.PostedTime = DateTime.Now; + + await dbContext.Comments.AddAsync(comments); + await dbContext.SaveChanges(); + + return comments; + } + + public async Task DeleteComment(Guid id, string userSid, bool isAdmin = false) + { + var item = await dbContext.Comments.FirstOrDefaultAsync(s => s.Id == id); + + if (item != null) + { + if (!isAdmin) + { + if (item.UserSid == userSid) + { + dbContext.Comments.Remove(item); + dbContext.Likes.RemoveRange(await dbContext.Likes.Where(s=>s.CommentId == item.Id).ToListAsync()); + await dbContext.SaveChanges(); + } + else + throw new Exception("This user did not post this comment!"); + } + else + { + dbContext.Comments.Remove(item); + dbContext.Likes.RemoveRange(await dbContext.Likes.Where(s => s.CommentId == item.Id).ToListAsync()); + await dbContext.SaveChanges(); + } + } + else + throw new Exception("Comment not found!"); + } + + public async Task Get(int offset = 0, int limit = 10, bool asc = false) + { + if(asc) + return new CommentsExternalCollection(await dbContext.Comments.OrderBy(s => s.PostedTime).Skip(offset).Take(limit).ToListAsync(), await dbContext.Comments.CountAsync()); + else + return new CommentsExternalCollection(await dbContext.Comments.OrderByDescending(s => s.PostedTime).Skip(offset).Take(limit).ToListAsync(), await dbContext.Comments.CountAsync()); + } + + public async Task> Get(Guid mainId) + { + return await dbContext.Comments.Where(s => s.MainId == mainId).OrderByDescending(s => s.PostedTime).ToListAsync(); + } + + public async Task Resolve(Guid id, bool flag = true) + { + var comment = await dbContext.Comments.FirstOrDefaultAsync(s => s.Id == id); + + if(comment != null) + { + comment.IsResolved = flag; + dbContext.Comments.Update(comment); + await dbContext.SaveChanges(); + } + } + + public async Task UpdateComment(Guid id, string comment, string userSid) + { + var commentObj = await dbContext.Comments.FirstOrDefaultAsync(s => s.Id == id); + + if (commentObj != null) + { + if (commentObj.UserSid == userSid) + { + commentObj.Comment = comment; + dbContext.Comments.Update(commentObj); + await dbContext.SaveChanges(); + } + else + throw new Exception("This user did not post this comment!"); + } + else + throw new Exception("Comment not found!"); + + } + } +} diff --git a/Library.Encyclopedia.DataAccess/DataAccess/LikesDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/LikesDataAccess.cs new file mode 100644 index 0000000..d5760b5 --- /dev/null +++ b/Library.Encyclopedia.DataAccess/DataAccess/LikesDataAccess.cs @@ -0,0 +1,54 @@ +using Library.Encyclopedia.Entity.Interfaces; +using Library.Encyclopedia.Entity.Models; +using Microsoft.EntityFrameworkCore; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.DataAccess.DataAccess +{ + public class LikesDataAccess : ILikesDataAccess + { + private readonly IApplicationDbContext dbContext; + + public LikesDataAccess(IApplicationDbContext dbContext) + { + this.dbContext = dbContext; + } + + public Task DeleteLikes(List commentIds) + { + throw new NotImplementedException(); + } + + public async Task> GetLikes(List commentIds) + { + return (await dbContext.Likes.Where(s => commentIds.Contains(s.CommentId)).ToListAsync()).GroupBy(s => s.CommentId).ToDictionary(s => s.Key, s => s.LongCount()); + } + + public async Task Like(Guid commentId, string userSid, bool flag = true) + { + var result = await dbContext.Likes.FirstOrDefaultAsync(s => s.CommentId == commentId && s.UserSid == userSid); + if (result != null) + { + // if disliked + if (!flag) + { + dbContext.Likes.Remove(result); + await dbContext.SaveChanges(); + } + else throw new Exception("Already Liked"); + } + else + { + if (flag) + { + await dbContext.Likes.AddAsync(new Likes { CommentId = commentId, UserSid = userSid }); + await dbContext.SaveChanges(); + } + } + } + } +} diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index 95ccfe9..4eae680 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -60,7 +60,7 @@ async Task IMainDataAccess.GetAsync(string quer MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.MinimizeWithQuery(query, previewSize), total); if (!string.IsNullOrEmpty(query) && !string.IsNullOrWhiteSpace(query) && total > 0 && query.Length >= 3) - this.queryStatsAdapter.AddQuery(query); + await this.queryStatsAdapter.AddQuery(query); return result; } @@ -430,6 +430,13 @@ public async Task DeleteAsync(Guid id) this.filesAdapter.DeleteFiles(entry.Id.ToString()); } _dbcontext.Main.Remove(entry); + + // delete all comments and likes + var allComments = await _dbcontext.Comments.Where(s => s.MainId == entry.Id).ToListAsync(); + _dbcontext.Comments.RemoveRange(allComments); + var allCommentIds = allComments.Select(s => s.Id); + _dbcontext.Likes.RemoveRange(await _dbcontext.Likes.Where(s => allCommentIds.Contains(s.CommentId)).ToListAsync()); + await _dbcontext.SaveChanges(); } else diff --git a/Library.Encyclopedia.DataAccess/IApplicationDbContext.cs b/Library.Encyclopedia.DataAccess/IApplicationDbContext.cs index fb924c7..562475d 100644 --- a/Library.Encyclopedia.DataAccess/IApplicationDbContext.cs +++ b/Library.Encyclopedia.DataAccess/IApplicationDbContext.cs @@ -11,7 +11,9 @@ public interface IApplicationDbContext DbSet Files { get; set; } DbSet Links { get; set; } DbSet QueryStats { get; set; } + DbSet Comments { get; set; } + DbSet Likes { get; set; } + Task SaveChanges(); - Task BeginTransactionAsync(); } } \ No newline at end of file diff --git a/Library.Encyclopedia.Entity/Interfaces/ICommentsDataAccess.cs b/Library.Encyclopedia.Entity/Interfaces/ICommentsDataAccess.cs new file mode 100644 index 0000000..6275b6f --- /dev/null +++ b/Library.Encyclopedia.Entity/Interfaces/ICommentsDataAccess.cs @@ -0,0 +1,19 @@ +using Library.Encyclopedia.Entity.Models; +using Library.Encyclopedia.Entity.Models.External; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.Entity.Interfaces +{ + public interface ICommentsDataAccess + { + Task Get(int offset = 0, int limit = 10, bool asc = false); + Task> Get(Guid mainId); + Task CreateComment(Comments comments); + Task UpdateComment(Guid id, string comment, string userSid); + Task DeleteComment(Guid id, string userSid, bool isAdmin = false); + Task Resolve(Guid id, bool flag = true); + } +} diff --git a/Library.Encyclopedia.Entity/Interfaces/ILikesDataAccess.cs b/Library.Encyclopedia.Entity/Interfaces/ILikesDataAccess.cs new file mode 100644 index 0000000..ffe27c9 --- /dev/null +++ b/Library.Encyclopedia.Entity/Interfaces/ILikesDataAccess.cs @@ -0,0 +1,14 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.Entity.Interfaces +{ + public interface ILikesDataAccess + { + Task Like(Guid commentId, string userSid, bool flag = true); + Task> GetLikes(List commentIds); + Task DeleteLikes(List commentIds); + } +} diff --git a/Library.Encyclopedia.Entity/Models/Comments.cs b/Library.Encyclopedia.Entity/Models/Comments.cs new file mode 100644 index 0000000..a1f17c1 --- /dev/null +++ b/Library.Encyclopedia.Entity/Models/Comments.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Text; + +namespace Library.Encyclopedia.Entity.Models +{ + public class Comments + { + public Guid Id { get; set; } + public Guid MainId { get; set; } + public string Comment { get; set; } + public DateTime PostedTime { get; set; } + public string UserName { get; set; } + public string UserSid { get; set; } + public long Likes { get; set; } + public bool IsResolved { get; set; } + } +} diff --git a/Library.Encyclopedia.Entity/Models/External/CommentsExternalCollection.cs b/Library.Encyclopedia.Entity/Models/External/CommentsExternalCollection.cs new file mode 100644 index 0000000..3d2dbec --- /dev/null +++ b/Library.Encyclopedia.Entity/Models/External/CommentsExternalCollection.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace Library.Encyclopedia.Entity.Models.External +{ + + public class CommentsExternalCollection : QueryExternalModel + { + public CommentsExternalCollection() + { + this.Count = 0; + this.Result = new List(); + this.Total = 0; + } + public CommentsExternalCollection(List collection, int total) + { + this.Result = collection; + this.Count = collection.Count(); + this.Total = total; + } + } +} diff --git a/Library.Encyclopedia.Entity/Models/Likes.cs b/Library.Encyclopedia.Entity/Models/Likes.cs new file mode 100644 index 0000000..eab4562 --- /dev/null +++ b/Library.Encyclopedia.Entity/Models/Likes.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Library.Encyclopedia.Entity.Models +{ + public class Likes + { + public long Id { get; set; } + public Guid CommentId { get; set; } + public string UserSid { get; set; } + } +} From 6670b1a44564cc11884b3a467eccc1a455d0047b Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Thu, 3 Feb 2022 07:52:45 -0500 Subject: [PATCH 15/16] added author feature --- .../Controllers/EncylopediaController.cs | 22 ++++++++++++------- Library.Encyclopedia.Entity/Models/Main.cs | 1 + 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index ca21e77..360e975 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -238,15 +238,21 @@ public async Task Create([FromBody]Main model) { try { - var response = await mainDataAccess.CreateAsync(model); - - if (response == null) - { - return StatusCode(500); - } - else + using (var context = new PrincipalContext(ContextType.Domain | ContextType.Machine)) { - return Ok(response); + var usr = UserPrincipal.FindByIdentity(context, this.HttpContext.User.Identity.Name); + model.Author = usr.DisplayName; + + var response = await mainDataAccess.CreateAsync(model); + + if (response == null) + { + return StatusCode(500); + } + else + { + return Ok(response); + } } } catch (Exception ex) diff --git a/Library.Encyclopedia.Entity/Models/Main.cs b/Library.Encyclopedia.Entity/Models/Main.cs index ac92950..9e81b7f 100644 --- a/Library.Encyclopedia.Entity/Models/Main.cs +++ b/Library.Encyclopedia.Entity/Models/Main.cs @@ -17,6 +17,7 @@ public class Main [StringLength(256)] public string Category { get; set; } + public string Author { get; set; } public ICollection Files { get; set; } public ICollection Links { get; set; } From df635d79c04e0c870f1a1742d95e7d703b7d8be8 Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Fri, 4 Feb 2022 05:13:48 -0500 Subject: [PATCH 16/16] Implemented feedback feature --- .../Controllers/EmailController.cs | 48 +++++++++++++++++ Library.Encyclopedia.API/appsettings.json | 7 ++- .../Email/EmailAdapter.cs | 52 +++++++++++++++++++ .../Library.Encyclopedia.DataAccess.csproj | 2 + .../Library.Encyclopedia.Entity.csproj | 1 + .../Models/EmailPropertiesV1.cs | 17 ++++++ 6 files changed, 126 insertions(+), 1 deletion(-) create mode 100644 Library.Encyclopedia.API/Controllers/EmailController.cs create mode 100644 Library.Encyclopedia.DataAccess/Email/EmailAdapter.cs create mode 100644 Library.Encyclopedia.Entity/Models/EmailPropertiesV1.cs diff --git a/Library.Encyclopedia.API/Controllers/EmailController.cs b/Library.Encyclopedia.API/Controllers/EmailController.cs new file mode 100644 index 0000000..f4fd07b --- /dev/null +++ b/Library.Encyclopedia.API/Controllers/EmailController.cs @@ -0,0 +1,48 @@ +using Library.Encyclopedia.DataAccess.Email; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.DirectoryServices.AccountManagement; +using System.Linq; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.API.Controllers +{ + [ApiController] + [Route("[controller]")] + public class EmailController : ControllerBase + { + public readonly EmailAdapter adapter; + private readonly ILogger logger; + + public EmailController(ILogger logger, IConfiguration configuration) + { + adapter = new EmailAdapter(configuration); + this.logger = logger; + } + + [HttpPost] + [Authorize] + public async Task SendEmail([FromBody]string content) + { + try + { + using (var context = new PrincipalContext(ContextType.Domain | ContextType.Machine)) + { + var usr = UserPrincipal.FindByIdentity(context, this.HttpContext.User.Identity.Name); + await adapter.SendEmail(usr.DisplayName, usr.EmailAddress, content); + + return Ok(); + } + } + catch (Exception ex) + { + logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } + } +} diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index b0058e8..d8b1515 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -28,5 +28,10 @@ // For Windows "filePath": "C:\\inetpub\\wwwroot\\PFW Encyclopedia Files\\" - } + }, + "SendGrid-APIKEY": "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", + "Template-Id": "d-af8da9debd13445898a1d82e2674e329", + "Admin-Email": "mazus01@pfw.edu", + "Admin-Username": "Souvik Mazumder", + "PFW-EmailList-Group": "Test" } \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/Email/EmailAdapter.cs b/Library.Encyclopedia.DataAccess/Email/EmailAdapter.cs new file mode 100644 index 0000000..8378950 --- /dev/null +++ b/Library.Encyclopedia.DataAccess/Email/EmailAdapter.cs @@ -0,0 +1,52 @@ +using Library.Encyclopedia.Entity.Models; +using Microsoft.Extensions.Configuration; +using SendGrid; +using SendGrid.Helpers.Mail; +using System; +using System.Collections.Generic; +using System.DirectoryServices.AccountManagement; +using System.Text; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.DataAccess.Email +{ + public class EmailAdapter + { + private readonly IConfiguration configuration; + + public EmailAdapter(IConfiguration configuration) + { + this.configuration = configuration; + } + + public async Task SendEmail(string userName, string email, string content) + { + var apiKey = configuration.GetSection("SendGrid-APIKEY").Value; + var client = new SendGridClient(apiKey); + var from = new EmailAddress(configuration.GetSection("Admin-Email").Value, configuration.GetSection("Admin-Username").Value); + + var to = new List(); + + using (var context = new PrincipalContext(ContextType.Machine)) + { + using (var group = GroupPrincipal.FindByIdentity(context, configuration.GetSection("PFW-EmailList-Group").Value)) + { + if (group != null) + { + var members = group.GetMembers(true); + foreach (UserPrincipal user in members) + { + to.Add(new EmailAddress { Email = user.EmailAddress, Name = user.DisplayName }); + } + } + } + } + + var msg = MailHelper.CreateSingleTemplateEmailToMultipleRecipients(from, to, configuration.GetSection("Template-Id").Value, new EmailPropertiesV1 { Content = content, Name = userName, Email = email }); + var response = await client.SendEmailAsync(msg); + + if (!response.IsSuccessStatusCode) + throw new Exception("Email not sent!"); + } + } +} diff --git a/Library.Encyclopedia.DataAccess/Library.Encyclopedia.DataAccess.csproj b/Library.Encyclopedia.DataAccess/Library.Encyclopedia.DataAccess.csproj index c75a2ba..1b6b377 100644 --- a/Library.Encyclopedia.DataAccess/Library.Encyclopedia.DataAccess.csproj +++ b/Library.Encyclopedia.DataAccess/Library.Encyclopedia.DataAccess.csproj @@ -19,7 +19,9 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + + diff --git a/Library.Encyclopedia.Entity/Library.Encyclopedia.Entity.csproj b/Library.Encyclopedia.Entity/Library.Encyclopedia.Entity.csproj index f6f8e6a..66d87ed 100644 --- a/Library.Encyclopedia.Entity/Library.Encyclopedia.Entity.csproj +++ b/Library.Encyclopedia.Entity/Library.Encyclopedia.Entity.csproj @@ -7,6 +7,7 @@ + diff --git a/Library.Encyclopedia.Entity/Models/EmailPropertiesV1.cs b/Library.Encyclopedia.Entity/Models/EmailPropertiesV1.cs new file mode 100644 index 0000000..c50a039 --- /dev/null +++ b/Library.Encyclopedia.Entity/Models/EmailPropertiesV1.cs @@ -0,0 +1,17 @@ +using Newtonsoft.Json; +using System; +using System.Collections.Generic; +using System.Text; + +namespace Library.Encyclopedia.Entity.Models +{ + public class EmailPropertiesV1 + { + [JsonProperty("content")] + public string Content { get; set; } + [JsonProperty("name")] + public string Name { get; set; } + [JsonProperty("email")] + public string Email { get; set; } + } +}