Improve GetPosts API

dotnetflags
C-xC-c 4 years ago
parent 6aaf3af929
commit 9ccff23251

@ -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<string> DatabaseFlags { get; set; }
public FlagsController(DatabaseService db, ILogger<FlagsController> 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<string> 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);
/// <summary>
/// Creates an error mesage to send in case of 400 bad request, without giving away too much information.
/// </summary>
/// <param name="exception">Raw exception to be filtered.</param>
private string ErrorMessage(Exception exception) =>
exception switch
{

@ -0,0 +1,65 @@
using System.Data;
using System.Linq;
using System.Threading.Tasks;
namespace BantFlags.Data.Database
{
/// <summary>
/// Functions for interacting with the database.
/// </summary>
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;
}
/// <summary>
/// Returns all of the flags that we support.
/// </summary>
public async Task<EnumerableRowCollection<string>> 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<string>("flag"));
}
}
/// <summary>
/// Configuration data passed by appsettings.
/// </summary>
public class DatabaseServiceConfig
{
public string ConnectionString { get; set; }
public int PoolSize { get; set; }
}
}

@ -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))";
/// <summary>
/// Returns the post numbers and their flags from the post numbers in the input.
/// </summary>
/// <param name="input">List of post numbers on the page.</param>
/// <returns></returns>
public async Task<IEnumerable<IGrouping<int, DataRow>>> 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<int>("post_nr"));
}
public async Task<List<Dictionary<string, string>>> GetPosts_V1(string input)
{
List<Dictionary<string, string>> posts = new List<Dictionary<string, string>>();
var x = await GetPosts(input);
x.ForEach(x => posts.Add(new Dictionary<string, string>
{
{"post_nr", x.Key.ToString() },
{"region", string.Join("||", x.AsEnumerable().Select(y => y.GetValue<string>("flag")))}
}));
return posts;
}
public async Task<Dictionary<int, IEnumerable<string>>> GetPosts_V2(string input)
{
var posts = await GetPosts(input);
return posts
.ToDictionary(
x => x.Key,
x => x.AsEnumerable().Select(x => x.GetValue<string>("flag"))
);
}
}
}

@ -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<List<Dictionary<string, string>>> GetPosts(string input)
{
List<Dictionary<string, string>> posts = new List<Dictionary<string, string>>();
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<int, IEnumerable<string>>
// instead of rewriting the flags each time.
var groupedPosts = table.AsEnumerable()
.GroupBy(x => x.GetValue<int>("post_nr"));
//.ToDictionary(
// x => x.Key,
// x => x.AsEnumerable().Select(x => x.GetValue<string>("flag"));
groupedPosts.ForEach(x => posts.Add(
new Dictionary<string, string>
{
{"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<string>("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<List<string>> 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<string>("flag"))
.ToList();
}
}
}
public class DatabaseServiceConfig
{
public string ConnectionString { get; set; }
public int PoolSize { get; set; }
}
}

@ -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;

@ -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<string>("webroot");
if (webroot != null)
if (Directory.Exists(webroot))
{
env.WebRootPath = webroot;
env.WebRootFileProvider = new PhysicalFileProvider(env.WebRootPath);

Loading…
Cancel
Save