diff --git a/BantFlags/Controllers/FlagsController.cs b/BantFlags/Controllers/FlagsController.cs index 214a402..51cb739 100644 --- a/BantFlags/Controllers/FlagsController.cs +++ b/BantFlags/Controllers/FlagsController.cs @@ -1,7 +1,7 @@ using BantFlags.Data; +using BantFlags.Data.Database; using Microsoft.AspNetCore.Http; using Microsoft.AspNetCore.Mvc; -using Microsoft.Extensions.Logging; using System; using System.Collections.Generic; using System.Data.Common; @@ -16,21 +16,16 @@ namespace BantFlags.Controllers { private DatabaseService Database { get; } - private ILogger Logger { get; } - private string FlagList { get; set; } private HashSet DatabaseFlags { get; set; } - public FlagsController(DatabaseService db, ILogger logger) + public FlagsController(DatabaseService db) { Database = db; - Logger = logger; - // During initialisation we get the current list of flags for - // resolving supported flags and preventing duplicate flags from - // being created - List flags = Database.GetFlags().Result; + // During initialisation we get the current list of flags for resolving supported flags and preventing duplicate flags from being created + var flags = Database.GetFlags().Result; // If this fails the program should exit anyway. FlagList = string.Join("\n", flags); DatabaseFlags = flags.ToHashSet(); @@ -45,13 +40,21 @@ namespace BantFlags.Controllers { try { - var posts = await Database.GetPosts(post_nrs); + int ver = int.TryParse(version, out int x) ? x : 0; - return Json(posts); + if (ver > 1) + { + // Improved flag sending, see Docs/GetPosts + return Json(await Database.GetPosts_V2(post_nrs)); + } + else + { + return Json(await Database.GetPosts_V1(post_nrs)); + } } catch (Exception e) { - return Problem(e.Message, statusCode: StatusCodes.Status400BadRequest); // TODO: We shouldn't send the exception message + return Problem(ErrorMessage(e), statusCode: StatusCodes.Status400BadRequest); } } @@ -86,8 +89,13 @@ namespace BantFlags.Controllers [HttpGet] [Route("flags")] + [ProducesResponseType(StatusCodes.Status200OK)] public IActionResult Flags() => Ok(FlagList); + /// + /// Creates an error mesage to send in case of 400 bad request, without giving away too much information. + /// + /// Raw exception to be filtered. private string ErrorMessage(Exception exception) => exception switch { diff --git a/BantFlags/Data/Database/DatabaseService.cs b/BantFlags/Data/Database/DatabaseService.cs new file mode 100644 index 0000000..627f85c --- /dev/null +++ b/BantFlags/Data/Database/DatabaseService.cs @@ -0,0 +1,65 @@ +using System.Data; +using System.Linq; +using System.Threading.Tasks; + +namespace BantFlags.Data.Database +{ + /// + /// Functions for interacting with the database. + /// + public partial class DatabaseService + { + private MySqlConnectionPool ConnectionPool { get; } + + public DatabaseService(DatabaseServiceConfig dbConfig) + { + ConnectionPool = new MySqlConnectionPool(dbConfig.ConnectionString, dbConfig.PoolSize); + } + + public async Task InsertPost(FlagModel post) + { + using (var rentedConnection = await ConnectionPool.RentConnectionAsync()) + { + await rentedConnection.Object.UseStoredProcedure("insert_post") + .SetParam("@post_nr", post.PostNumber) + .SetParam("@board", post.Board) + .ExecuteNonQueryAsync(); + + using (var query = rentedConnection.Object.UseStoredProcedure("insert_post_flags")) + { + query.SetParam("@post_nr", post.PostNumber); + + post.Flags.ForEach(async f => + await query.SetParam("@flag", f) + .ExecuteNonQueryAsync(reuse: true)); + } + } + + return; + } + + /// + /// Returns all of the flags that we support. + /// + public async Task> GetFlags() + { + using var rentedConnected = await ConnectionPool.RentConnectionAsync(); + + DataTable table = await rentedConnected.Object.CreateQuery("SELECT flags.flag FROM flags") + .ExecuteTableAsync(); + + return table.AsEnumerable() + .Select(x => x.GetValue("flag")); + } + } + + /// + /// Configuration data passed by appsettings. + /// + public class DatabaseServiceConfig + { + public string ConnectionString { get; set; } + + public int PoolSize { get; set; } + } +} \ No newline at end of file diff --git a/BantFlags/Data/Database/GetPosts.cs b/BantFlags/Data/Database/GetPosts.cs new file mode 100644 index 0000000..ac5ed10 --- /dev/null +++ b/BantFlags/Data/Database/GetPosts.cs @@ -0,0 +1,54 @@ +using System.Collections.Generic; +using System.Data; +using System.Linq; +using System.Threading.Tasks; + +namespace BantFlags.Data.Database +{ + public partial class DatabaseService + { + private readonly string GetPostsQuery = @"SELECT posts.post_nr, flags.flag FROM flags LEFT JOIN (postflags) ON (postflags.flag = flags.id) LEFT JOIN (posts) ON (postflags.post_nr = posts.id) WHERE FIND_IN_SET(posts.post_nr, (@posts))"; + + /// + /// Returns the post numbers and their flags from the post numbers in the input. + /// + /// List of post numbers on the page. + /// + public async Task>> GetPosts(string input) + { + using var rentedConnection = await ConnectionPool.RentConnectionAsync(); + + DataTable table = await rentedConnection.Object.CreateQuery(GetPostsQuery) + .SetParam("@posts", input) + .ExecuteTableAsync(); + + return table.AsEnumerable() + .GroupBy(x => x.GetValue("post_nr")); + } + + public async Task>> GetPosts_V1(string input) + { + List> posts = new List>(); + + var x = await GetPosts(input); + + x.ForEach(x => posts.Add(new Dictionary + { + {"post_nr", x.Key.ToString() }, + {"region", string.Join("||", x.AsEnumerable().Select(y => y.GetValue("flag")))} + })); + + return posts; + } + + public async Task>> GetPosts_V2(string input) + { + var posts = await GetPosts(input); + return posts + .ToDictionary( + x => x.Key, + x => x.AsEnumerable().Select(x => x.GetValue("flag")) + ); + } + } +} \ No newline at end of file diff --git a/BantFlags/Data/DatabaseService.cs b/BantFlags/Data/DatabaseService.cs deleted file mode 100644 index 69c03a1..0000000 --- a/BantFlags/Data/DatabaseService.cs +++ /dev/null @@ -1,95 +0,0 @@ -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Threading.Tasks; - -namespace BantFlags.Data -{ - public class DatabaseService - { - private MySqlConnectionPool ConnectionPool { get; } - - public DatabaseService(DatabaseServiceConfig dbConfig) - { - ConnectionPool = new MySqlConnectionPool(dbConfig.ConnectionString, dbConfig.PoolSize); - } - - private readonly string SelectQuery = @"SELECT posts.post_nr, flags.flag FROM flags LEFT JOIN (postflags) ON (postflags.flag = flags.id) LEFT JOIN (posts) ON (postflags.post_nr = posts.id) WHERE FIND_IN_SET(posts.post_nr, (@posts))"; - - public async Task>> GetPosts(string input) - { - List> posts = new List>(); - - using (var rentedConnection = await ConnectionPool.RentConnectionAsync()) - { - DataTable table = await rentedConnection.Object.CreateQuery(SelectQuery) - .SetParam("@posts", input) - .ExecuteTableAsync(); - - // TODO: rework this. - // Once the majority are on the new script we can do the below - // and return Dictionary> - // instead of rewriting the flags each time. - var groupedPosts = table.AsEnumerable() - .GroupBy(x => x.GetValue("post_nr")); - - //.ToDictionary( - // x => x.Key, - // x => x.AsEnumerable().Select(x => x.GetValue("flag")); - - groupedPosts.ForEach(x => posts.Add( - new Dictionary - { - {"post_nr", x.Key.ToString() }, - // This is a lot of work, it'll be nice to get rid of it. - {"region", string.Join("||", x.AsEnumerable().Select(y => y.GetValue("flag")))} - } - )); - - return posts; - } - } - - public async Task InsertPost(FlagModel post) - { - using (var rentedConnection = await ConnectionPool.RentConnectionAsync()) - { - await rentedConnection.Object.UseStoredProcedure("insert_post") - .SetParam("@post_nr", post.PostNumber) - .SetParam("@board", post.Board) - .ExecuteNonQueryAsync(); - - using (var query = rentedConnection.Object.UseStoredProcedure("insert_post_flags")) - { - query.SetParam("@post_nr", post.PostNumber); - - post.Flags.ForEach(async f => - await query.SetParam("@flag", f) - .ExecuteNonQueryAsync(reuse: true)); - } - } - - return; - } - - public async Task> GetFlags() - { - using (var rentedConnected = await ConnectionPool.RentConnectionAsync()) - { - DataTable table = await rentedConnected.Object.CreateQuery("SELECT flags.flag FROM flags") - .ExecuteTableAsync(); - - return table.AsEnumerable() - .Select(x => x.GetValue("flag")) - .ToList(); - } - } - } - - public class DatabaseServiceConfig - { - public string ConnectionString { get; set; } - - public int PoolSize { get; set; } - } -} \ No newline at end of file diff --git a/BantFlags/Program.cs b/BantFlags/Program.cs index ac6aecc..332b060 100644 --- a/BantFlags/Program.cs +++ b/BantFlags/Program.cs @@ -1,7 +1,6 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using Microsoft.Extensions.Logging; using System; using System.IO; diff --git a/BantFlags/Startup.cs b/BantFlags/Startup.cs index eb2f61c..1a83050 100644 --- a/BantFlags/Startup.cs +++ b/BantFlags/Startup.cs @@ -1,4 +1,5 @@ using BantFlags.Data; +using BantFlags.Data.Database; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.HttpOverrides; @@ -6,6 +7,7 @@ using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.FileProviders; using Microsoft.Extensions.Hosting; +using System.IO; namespace BantFlags { @@ -33,7 +35,7 @@ namespace BantFlags public void Configure(IApplicationBuilder app, IWebHostEnvironment env) { string webroot = Configuration.GetValue("webroot"); - if (webroot != null) + if (Directory.Exists(webroot)) { env.WebRootPath = webroot; env.WebRootFileProvider = new PhysicalFileProvider(env.WebRootPath);