From 088ff782ce4ca142d9ba4b6905d59859e250cb2c Mon Sep 17 00:00:00 2001 From: Souvik Mazumder Date: Wed, 1 Dec 2021 14:47:27 -0500 Subject: [PATCH] Added API Authentication Added File Data Access TODO : FTP Layer --- .../Attributes/ApiKeyAttribute.cs | 43 ++++++++++++++ .../Controllers/EncylopediaController.cs | 33 ++++++++++- .../Controllers/FilesController.cs | 59 +++++++++++++++++++ .../Library.Encyclopedia.API.csproj | 1 + .../Properties/launchSettings.json | 2 - Library.Encyclopedia.API/Startup.cs | 2 +- Library.Encyclopedia.API/appsettings.json | 5 +- .../ApplicationDbContext.cs | 6 +- .../DataAccess/FilesDataAccess.cs | 35 +++++++++++ .../DataAccess/MainDataAccess.cs | 45 ++++++++++---- .../Interfaces/IFilesDataAccess.cs | 14 +++++ .../Interfaces/IMainDataAccess.cs | 2 + 12 files changed, 227 insertions(+), 20 deletions(-) create mode 100644 Library.Encyclopedia.API/Attributes/ApiKeyAttribute.cs create mode 100644 Library.Encyclopedia.API/Controllers/FilesController.cs create mode 100644 Library.Encyclopedia.DataAccess/DataAccess/FilesDataAccess.cs create mode 100644 Library.Encyclopedia.Entity/Interfaces/IFilesDataAccess.cs diff --git a/Library.Encyclopedia.API/Attributes/ApiKeyAttribute.cs b/Library.Encyclopedia.API/Attributes/ApiKeyAttribute.cs new file mode 100644 index 0000000..924a61a --- /dev/null +++ b/Library.Encyclopedia.API/Attributes/ApiKeyAttribute.cs @@ -0,0 +1,43 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.DependencyInjection; +using System; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.API.Attributes +{ + [AttributeUsage(validOn: AttributeTargets.Class | AttributeTargets.Method)] + public class ApiKeyAttribute : Attribute, IAsyncActionFilter + { + private const string APIKEYNAME = "ApiKey"; + public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next) + { + if (!context.HttpContext.Request.Headers.TryGetValue(APIKEYNAME, out var extractedApiKey)) + { + context.Result = new ContentResult() + { + StatusCode = 401, + Content = "Api Key was not provided" + }; + return; + } + + var appSettings = context.HttpContext.RequestServices.GetRequiredService(); + + var apiKey = appSettings.GetValue(APIKEYNAME); + + if (!apiKey.Equals(extractedApiKey)) + { + context.Result = new ContentResult() + { + StatusCode = 401, + Content = "Api Key is not valid" + }; + return; + } + + await next(); + } + } +} \ No newline at end of file diff --git a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs index 31c1445..4ea4134 100644 --- a/Library.Encyclopedia.API/Controllers/EncylopediaController.cs +++ b/Library.Encyclopedia.API/Controllers/EncylopediaController.cs @@ -1,4 +1,5 @@ -using Library.Encyclopedia.DataAccess; +using Library.Encyclopedia.API.Attributes; +using Library.Encyclopedia.DataAccess; using Library.Encyclopedia.DataAccess.DataAccess; using Library.Encyclopedia.Entity.Interfaces; using Library.Encyclopedia.Entity.Models; @@ -166,6 +167,7 @@ public async Task Get(Guid id) /// /// [HttpPost] + [ApiKey] public async Task Create([FromBody]Main model) { try @@ -193,6 +195,7 @@ public async Task Create([FromBody]Main model) /// /// [HttpPut("{id}")] + [ApiKey] public async Task Update(Guid id, [FromBody]MainUpdateModel model) { try @@ -213,6 +216,7 @@ public async Task Update(Guid id, [FromBody]MainUpdateModel model /// /// [HttpDelete("{id}")] + [ApiKey] public async Task Delete(Guid id) { try @@ -227,5 +231,32 @@ public async Task Delete(Guid id) throw; } } + + /// + /// Get all categories + /// + /// + [HttpGet("fetchallcategories")] + public async Task GetAllCategories() + { + try + { + var response = await mainDataAccess.GetAllCategoriesAsync(); + + if (response == null) + { + return StatusCode(204); + } + else + { + return Ok(response); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"an error has occured {ex.Message}"); + throw; + } + } } } \ No newline at end of file diff --git a/Library.Encyclopedia.API/Controllers/FilesController.cs b/Library.Encyclopedia.API/Controllers/FilesController.cs new file mode 100644 index 0000000..6eb1979 --- /dev/null +++ b/Library.Encyclopedia.API/Controllers/FilesController.cs @@ -0,0 +1,59 @@ +using Library.Encyclopedia.API.Attributes; +using Library.Encyclopedia.DataAccess; +using Library.Encyclopedia.DataAccess.DataAccess; +using Library.Encyclopedia.Entity.Interfaces; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.API.Controllers +{ + [ApiController] + [Route("[controller]")] + public class FilesController : ControllerBase + { + private readonly ILogger _logger; + private readonly IFilesDataAccess filesDataAccess; + + public FilesController(ILogger logger, IApplicationDbContext dbContext) + { + _logger = logger; + this.filesDataAccess = new FilesDataAccess(dbContext); + } + + /// + /// Upload Files + /// + /// + /// + /// + /// + /// + //[HttpPost("{id}")] + //[ApiKey] + //public async Task Get(Guid id, List files) + //{ + // try + // { + // ////var response = await filesDataAccess.AddFiles(id, files); + + // //if (response == null) + // //{ + // // return StatusCode(204); + // //} + // //else + // //{ + // // return Ok(response); + // //} + // } + // catch (Exception ex) + // { + // _logger.LogError(ex, $"an error has occured {ex.Message}"); + // throw; + // } + //} + } +} \ No newline at end of file diff --git a/Library.Encyclopedia.API/Library.Encyclopedia.API.csproj b/Library.Encyclopedia.API/Library.Encyclopedia.API.csproj index 10210cb..69e09fa 100644 --- a/Library.Encyclopedia.API/Library.Encyclopedia.API.csproj +++ b/Library.Encyclopedia.API/Library.Encyclopedia.API.csproj @@ -18,6 +18,7 @@ + diff --git a/Library.Encyclopedia.API/Properties/launchSettings.json b/Library.Encyclopedia.API/Properties/launchSettings.json index 3535896..6d1e37b 100644 --- a/Library.Encyclopedia.API/Properties/launchSettings.json +++ b/Library.Encyclopedia.API/Properties/launchSettings.json @@ -11,7 +11,6 @@ "IIS Express": { "commandName": "IISExpress", "launchBrowser": true, - "launchUrl": "swagger", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" } @@ -19,7 +18,6 @@ "Library.Encyclopedia": { "commandName": "Project", "launchBrowser": true, - "launchUrl": "swagger", "applicationUrl": "https://localhost:5001;http://localhost:5000", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development" diff --git a/Library.Encyclopedia.API/Startup.cs b/Library.Encyclopedia.API/Startup.cs index b123ba6..d0b4867 100644 --- a/Library.Encyclopedia.API/Startup.cs +++ b/Library.Encyclopedia.API/Startup.cs @@ -27,7 +27,7 @@ public void ConfigureServices(IServiceCollection services) configuration.RootPath = "ClientApp/dist"; }); - services.AddScoped(); + services.AddScoped(s => new ApplicationDbContext(Configuration.GetConnectionString("DefaultConnection"))); } // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. diff --git a/Library.Encyclopedia.API/appsettings.json b/Library.Encyclopedia.API/appsettings.json index 71db15b..0c4f199 100644 --- a/Library.Encyclopedia.API/appsettings.json +++ b/Library.Encyclopedia.API/appsettings.json @@ -8,6 +8,7 @@ }, "AllowedHosts": "*", "ConnectionStrings": { - "DefaultConnection": "Server=FW-LIBR-LB430\\SQLEXPRESS;Database=Encyclopedia;Trusted_Connection=True;MultipleActiveResultSets=true" - } + "DefaultConnection": "Server=localhost\\SQLEXPRESS01;Database=Encyclopedia;Trusted_Connection=True;" + }, + "ApiKey": "5929b003-8895-4fb3-bbb0-2eb101c48f66" } \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs b/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs index b17ae27..41f6d96 100644 --- a/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs +++ b/Library.Encyclopedia.DataAccess/ApplicationDbContext.cs @@ -6,7 +6,11 @@ namespace Library.Encyclopedia.DataAccess { public class ApplicationDbContext : DbContext, IApplicationDbContext { - private const string connectionString = "Server=localhost\\SQLEXPRESS01;Database=Encyclopedia;Trusted_Connection=True;"; + private string connectionString; + public ApplicationDbContext(string connectionString) + { + this.connectionString = connectionString; + } protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { diff --git a/Library.Encyclopedia.DataAccess/DataAccess/FilesDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/FilesDataAccess.cs new file mode 100644 index 0000000..5ac9236 --- /dev/null +++ b/Library.Encyclopedia.DataAccess/DataAccess/FilesDataAccess.cs @@ -0,0 +1,35 @@ +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 FilesDataAccess : IFilesDataAccess + { + private IApplicationDbContext _dbcontext; + public FilesDataAccess(IApplicationDbContext dbcontext) + { + _dbcontext = dbcontext; + } + + public async Task AddFiles(IEnumerable files) + { + await _dbcontext.Files.AddRangeAsync(files); + await _dbcontext.SaveChanges(); + } + + public async Task RemoveFiles(IEnumerable ids) + { + //find file entities + var files = await _dbcontext.Files.Where(s => ids.Contains(s.Id)).ToListAsync(); + + _dbcontext.Files.RemoveRange(files); + await _dbcontext.SaveChanges(); + } + } +} \ No newline at end of file diff --git a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs index 993de8c..8bf15de 100644 --- a/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs +++ b/Library.Encyclopedia.DataAccess/DataAccess/MainDataAccess.cs @@ -22,7 +22,8 @@ public MainDataAccess(IApplicationDbContext dbcontext) #region GET async Task IMainDataAccess.GetAsync(string query, int offset, int pagesize, bool ascending) { - var temp = _dbcontext.Main.Where(s => s.Description.Contains(query) || s.Title.Contains(query)) + query = query.ToLower(); + var temp = _dbcontext.Main.Where(s => s.Description.ToLower().Contains(query) || s.Title.ToLower().Contains(query)) .Skip(offset) .Take(pagesize); @@ -36,7 +37,7 @@ async Task IMainDataAccess.GetAsync(string quer .ThenByDescending(s => s.Description) .ToListAsync(); - var total = await _dbcontext.Main.CountAsync(s => s.Description.Contains(query) || s.Title.Contains(query)); + var total = await _dbcontext.Main.CountAsync(s => s.Description.ToLower().Contains(query) || s.Title.ToLower().Contains(query)); MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(), total); @@ -45,21 +46,21 @@ async Task IMainDataAccess.GetAsync(string quer async Task IMainDataAccess.GetByCategoryAsync(string category, int offset, int pagesize, bool ascending) { - var temp = _dbcontext.Main.Where(s => s.Category.Equals(category, StringComparison.OrdinalIgnoreCase)) + 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); IEnumerable
data; if (ascending) - data = await temp.OrderBy(s => s.Title) - .ThenBy(s => s.Description) - .ToListAsync(); + data = temp.OrderBy(s => s.Title) + .ThenBy(s => s.Description); else - data = await temp.OrderByDescending(s => s.Title) - .ThenByDescending(s => s.Description) - .ToListAsync(); + data = temp.OrderByDescending(s => s.Title) + .ThenByDescending(s => s.Description); - var total = await _dbcontext.Main.CountAsync(s => s.Category.Equals(category, StringComparison.OrdinalIgnoreCase)); + var total = rawData.Count(s => s.Category.ToLower().Split(',', StringSplitOptions.None).Contains(category)); MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(), total); @@ -68,7 +69,8 @@ async Task IMainDataAccess.GetByCategoryAsync(s async Task IMainDataAccess.GetByAlphabetAsync(char startingAlphabet, int offset, int pagesize, bool ascending) { - var temp = _dbcontext.Main.Where(s => s.Title.StartsWith(startingAlphabet.ToString(), StringComparison.OrdinalIgnoreCase)) + var alph = startingAlphabet.ToString().ToLower(); + var temp = _dbcontext.Main.Where(s => s.Title.ToLower().StartsWith(alph)) .Skip(offset) .Take(pagesize); @@ -82,7 +84,7 @@ async Task IMainDataAccess.GetByAlphabetAsync(c .ThenByDescending(s => s.Description) .ToListAsync(); - var total = await _dbcontext.Main.CountAsync(s => s.Title.StartsWith(startingAlphabet.ToString(), StringComparison.OrdinalIgnoreCase)); + var total = await _dbcontext.Main.CountAsync(s => s.Title.ToLower().StartsWith(alph)); MainMinimizedExternalCollection result = new MainMinimizedExternalCollection(data.Minimize(), total); @@ -91,7 +93,7 @@ async Task IMainDataAccess.GetByAlphabetAsync(c async Task
IMainDataAccess.GetAsync(Guid id) { - return await _dbcontext.Main.FirstOrDefaultAsync(s => s.Id == id); + return await _dbcontext.Main.Include(s => s.Files).Include(s => s.Links).FirstOrDefaultAsync(s => s.Id == id); } #endregion @@ -121,6 +123,7 @@ public async Task UpdateAsync(Guid id, MainUpdateModel model) entry.Category = model.Category; _dbcontext.Main.Update(entry); + await _dbcontext.SaveChanges(); } else throw new UpdateFailedException(UpdateFailErrorCode.EntryNotFound); @@ -134,10 +137,26 @@ public async Task DeleteAsync(Guid id) var entry = await _dbcontext.Main.FirstOrDefaultAsync(s => s.Id == id); if (entry != null) + { _dbcontext.Main.Remove(entry); + await _dbcontext.SaveChanges(); + } else throw new DeleteFailedException(DeleteFailErrorCode.EntryNotFound); } #endregion + + #region UTILS + public async Task> GetAllCategoriesAsync() + { + var data = await _dbcontext.Main.Select(s => s.Category).OrderBy(s => s).ToListAsync(); + var result = new List(); + data.ForEach(item => + { + result.AddRange(item.Split(',')); + }); + return result.Distinct(); + } + #endregion } } \ No newline at end of file diff --git a/Library.Encyclopedia.Entity/Interfaces/IFilesDataAccess.cs b/Library.Encyclopedia.Entity/Interfaces/IFilesDataAccess.cs new file mode 100644 index 0000000..061d2af --- /dev/null +++ b/Library.Encyclopedia.Entity/Interfaces/IFilesDataAccess.cs @@ -0,0 +1,14 @@ +using Library.Encyclopedia.Entity.Models; +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading.Tasks; + +namespace Library.Encyclopedia.Entity.Interfaces +{ + public interface IFilesDataAccess + { + Task AddFiles(IEnumerable files); + Task RemoveFiles(IEnumerable ids); + } +} diff --git a/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs b/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs index 555c3e8..cc8c6eb 100644 --- a/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs +++ b/Library.Encyclopedia.Entity/Interfaces/IMainDataAccess.cs @@ -19,5 +19,7 @@ public interface IMainDataAccess public Task CreateAsync(Main main); public Task UpdateAsync(Guid id, MainUpdateModel model); public Task DeleteAsync(Guid id); + + public Task> GetAllCategoriesAsync(); } } \ No newline at end of file