Invented a new variety of spaghetti //

Made a form for adding  and modifying flags.
dotnetflags
C-xC-c 4 years ago
parent 851337c018
commit ebb982a00b

2
.gitignore vendored

@ -32,7 +32,7 @@ bld/
# Visual Studio 2015/2017 cache/options directory
.vs/
# Uncomment if you have tasks that create the project's static files in wwwroot
/BantFlags/wwwroot/flags/*
/BantFlags/wwwroot/flags/*.png
# Visual Studio 2017 auto generated files
Generated\ Files/

@ -6,20 +6,18 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Magick.NET-Q8-AnyCPU" Version="7.14.5" />
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Razor" Version="2.2.0" />
<PackageReference Include="Microsoft.AspNetCore.StaticFiles" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.0.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.0.0" />
<PackageReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Design" Version="3.1.0" />
<PackageReference Include="MySql.Data" Version="8.0.18" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="Nito.AsyncEx" Version="5.0.0" />
</ItemGroup>
<ItemGroup>
<Folder Include="wwwroot\flags\" />
</ItemGroup>
</Project>

@ -3,7 +3,6 @@ using BantFlags.Data.Database;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using System;
using System.Collections.Generic;
using System.Data.Common;
using System.Linq;
using System.Threading.Tasks;
@ -16,36 +15,31 @@ namespace BantFlags.Controllers
{
private DatabaseService Database { get; }
private string FlagList { get; set; }
private HashSet<string> DatabaseFlags { get; set; }
public FlagsController(DatabaseService db)
{
Database = db;
// 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();
}
/// <summary>
/// Retrives flags from the database from the posts sent in post_nrs
/// </summary>
/// <param name="post_nrs">The comma seperated list of post numbers from the thread.</param>
/// <param name="board">Currently should only be /bant/. Not checked here because we don't need to care what they send.</param>
/// <param name="version">The version of the userscript.</param>
[HttpPost]
[Route("get")]
[Consumes("application/x-www-form-urlencoded")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Get([FromForm]string post_nrs, [FromForm]string board, [FromForm]string version)
{ // TODO: version can be an int?.
// int ver = verson ?? 0
try
public async Task<IActionResult> Get([FromForm]string post_nrs, [FromForm]string board, [FromForm]int? version)
{
try // We only care if the post if valid.
{
int ver = int.TryParse(version, out int x) ? x : 0;
int ver = version ?? 0;
if (ver > 1)
{
// Improved flag sending, see Docs/GetPosts
// Improved data structuring, see Docs/GetPosts
return Json(await Database.GetPosts_V2(post_nrs));
}
else
@ -63,15 +57,16 @@ namespace BantFlags.Controllers
/// Posts flags in the database.
/// </summary>
/// <param name="post_nr">The post number to associate the flags to.</param>
/// <param name="board">/bant/.</param>
/// <param name="board">Currently should only be /bant/.</param>
/// <param name="regions">List of flags to associate with the post. Split by "||" in API V1 and "," in V2.</param>
/// <param name="version">The version of the userscript.</param>
[HttpPost]
[Route("post")]
[Consumes("application/x-www-form-urlencoded")]
[ProducesResponseType(StatusCodes.Status200OK)]
[ProducesResponseType(StatusCodes.Status400BadRequest)]
public async Task<IActionResult> Post([FromForm]string post_nr, [FromForm]string board, [FromForm]string regions, [FromForm]int? version)
{ // Should we rename regions? It'd be more idomatic for it to be flags or something but then we have to introduce a new variable that does the same thing.
{
try // We only care if the post if valid.
{
string[] flags;
@ -87,7 +82,20 @@ namespace BantFlags.Controllers
}
// TODO: Currently we skip over invalid flags. Should we error instead?
var validFlags = flags.Where(x => DatabaseFlags.Contains(x));
// We can't easily format it like in the current bantflags - we really should continue to
// return "empty, or there were errors. Re-set your flags.", for compatibility, but we'd
// have to store that as a flag in the database and perform an expensive string comparison
// to stop people selecting it.
// Do we care if people select the broken flag?
var validFlags = flags.Where(x => Database.KnownFlags().Contains(x));
for (int i = 0; i < flags.Length; i++)
{
if (!Database.KnownFlags().Contains(flags[i]))
{
flags[i] = "empty, or there were errors. Re-set your flags.";
}
}
var numberOfFlags = validFlags.Count();
if (numberOfFlags <= 0 || numberOfFlags > 25)
@ -115,7 +123,7 @@ namespace BantFlags.Controllers
[HttpGet]
[Route("flags")]
[ProducesResponseType(StatusCodes.Status200OK)]
public IActionResult Flags() => Ok(FlagList);
public IActionResult Flags() => Ok(Database.FlagList());
/// <summary>
/// Creates an error mesage to send in case of 400 bad request, without giving away too much information.
@ -127,7 +135,7 @@ namespace BantFlags.Controllers
NullReferenceException _ => "Some data wasn't initialised. Are you sending everything?",
DbException _ => "Internal database error.",
ArgumentNullException _ => "No regions sent",
ArgumentException e => e.Message,
ArgumentException e => e.Message, // We create all arguement exceptions here, so we can pass the message on.
Exception e => e.Message, // Don't do this.
_ => "how in the hell"
}; // This needs more testing.

@ -1,4 +1,5 @@
using System.Data;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Threading.Tasks;
@ -11,9 +12,53 @@ namespace BantFlags.Data.Database
{
private MySqlConnectionPool ConnectionPool { get; }
private string Flags { get; set; }
private HashSet<string> FlagsHash { get; set; }
public DatabaseService(DatabaseServiceConfig dbConfig)
{
ConnectionPool = new MySqlConnectionPool(dbConfig.ConnectionString, dbConfig.PoolSize);
var flags = GetFlags().Result; // It's okay to error here since it's only initialised at startup.
Flags = string.Join("\n", flags);
FlagsHash = flags.ToHashSet();
}
public string FlagList() => Flags;
public HashSet<string> KnownFlags() => FlagsHash;
public async Task UpdateKnownFlags()
{
var flags = await GetFlags();
Flags = string.Join("\n", flags);
FlagsHash = flags.ToHashSet();
}
public async Task DeleteFlagsAsync(List<FormFlag> flags)
{
using var rentedConnection = await ConnectionPool.RentConnectionAsync();
using var query = rentedConnection.Object.UseStoredProcedure("delete_flag");
flags.ForEach(async f =>
await query.SetParam("@flag", f.Name)
.ExecuteNonQueryAsync(reuse: true));
return;
}
public async Task RenameFlagsAsync(List<RenameFlag> flags)
{
using var rentedConnection = await ConnectionPool.RentConnectionAsync();
using var query = rentedConnection.Object.UseStoredProcedure("rename_flag");
flags.ForEach(async flag =>
await query.SetParam("@old", flag.Name)
.SetParam("@new", flag.NewName)
.ExecuteNonQueryAsync(reuse: true));
}
public async Task InsertPost(FlagModel post)
@ -41,7 +86,7 @@ namespace BantFlags.Data.Database
/// <summary>
/// Returns all of the flags that we support.
/// </summary>
public async Task<EnumerableRowCollection<string>> GetFlags()
public async Task<List<string>> GetFlags()
{
using var rentedConnected = await ConnectionPool.RentConnectionAsync();
@ -49,7 +94,20 @@ namespace BantFlags.Data.Database
.ExecuteTableAsync();
return table.AsEnumerable()
.Select(x => x.GetValue<string>("flag"));
.Select(x => x.GetValue<string>("flag"))
.ToList();
}
public async Task InsertFlagsAsync(List<FormFlag> flags)
{
using var rentedConnection = await ConnectionPool.RentConnectionAsync();
using var query = rentedConnection.Object.UseStoredProcedure("insert_flag");
flags.ForEach(async f =>
await query.SetParam("@flag", f.Name)
.ExecuteNonQueryAsync(reuse: true));
return;
}
}

@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Generic;
namespace BantFlags.Data
{

@ -0,0 +1,56 @@
using System.Collections.Generic;
namespace BantFlags.Data
{
/// <summary>
/// Singleton class comprised of objects used in/Upload.
/// It's a fucking mess and I hate it so much.
/// </summary>
public class Staging
{
public List<RenameFlag> RenamedFlags { get; set; }
public List<FormFlag> DeletedFlags { get; set; }
public List<FormFlag> AddedFlags { get; set; }
public List<string> Flags { get; set; }
public string Password { get; }
public Staging(string password)
{
RenamedFlags = new List<RenameFlag>();
DeletedFlags = new List<FormFlag>();
AddedFlags = new List<FormFlag>();
Password = password;
}
public void Clear()
{
RenamedFlags = new List<RenameFlag>();
DeletedFlags = new List<FormFlag>();
AddedFlags = new List<FormFlag>();
}
}
public class FormFlag
{
public string Name { get; set; }
public bool IsChecked { get; set; }
public Method FormMethod { get; set; }
}
public class RenameFlag : FormFlag
{
public string NewName { get; set; }
}
public enum Method
{
Delete,
Rename,
Add
}
}

@ -6,4 +6,8 @@
}
<h1>/bant/ Flags</h1>
<p>E<span lang="jp">メール</span>: boku (at) plum (dot) moe</p>
<p>E<span lang="jp">メール</span>: boku (at) plum (dot) moe</p>
<a href="~/bantflags.user.js">Install Bantflags</a>
<br />
<a href="https://nineball.party/srsbsn/3521">Official Thread</a>

@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using Microsoft.AspNetCore.Mvc.RazorPages;
namespace BantFlags.Pages
{
@ -11,7 +6,6 @@ namespace BantFlags.Pages
{
public void OnGet()
{
}
}
}

@ -8,5 +8,6 @@
</head>
<body style="text-align: center;">
@RenderBody()
@RenderSection("Scripts", required: false)
</body>
</html>

@ -0,0 +1,131 @@
@page
@using BantFlags.Data
@model BantFlags.UploadModel
@{
ViewData["Title"] = "Upload";
Layout = "~/Pages/Shared/_Layout.cshtml";
}
<h1>Upload</h1>
<img src="~/montage.png" />
@Model.Message
<h2>Add a Flag</h2>
<form method="post" asp-page-handler="Add" enctype="multipart/form-data">
<input type="file" asp-for="Upload" />
@Html.CheckBoxFor(model => model.ShouldGloss)<span>Apply Gloss?</span>
<br />
<br />
<input type="submit" value="Upload Flag" />
</form>
<h2>Manage Existing Flags</h2>
<form method="post" enctype="multipart/form-data">
<label>Flag:</label>
<select name="flag">
@foreach (string s in Model.staging.Flags)
{
<option>@s</option>
}
</select>
<label>New Name:</label>
<input type="text" name="newName" />
<br />
<br />
<button asp-page-handler="Delete" type="submit">Delete Flag</button>
<button asp-page-handler="Rename" type="submit">Rename Flag</button>
</form>
<h2>Commit Changes</h2>
<form method="post" asp-page-handler="Commit" enctype="multipart/form-data">
<label>Password:</label>
<input type="text" name="password" />
<br />
<br />
<input type="submit" value="Commit Changes" />
</form>
<h2>Pending Changes</h2>
<form method="post" asp-page-handler="Unstage">
<label>Password:</label>
<input type="text" name="password" />
<button type="submit" onclick="window.confirm('Really unstage the selected flags?')">unstage</button>
@if (Model.staging.AddedFlags.Any())
{
<h3>New Flags</h3>
<div class="flag-container">
@foreach (FormFlag flag in Model.staging.AddedFlags)
{
<div class="flag">
<img src="~/flags/staging/@(flag.Name).png" />
<br />
<span>@flag.Name</span>
<input type="hidden" name="addedAndDeletedFlags.Index" value="@flag.Name" />
<input type="hidden" name="addedAndDeletedFlags[@flag.Name].Name" value="@flag.Name" />
<input type="hidden" name="addedAndDeletedFlags[@flag.Name].Method" value="@Method.Add" />
<br />
<input type="checkbox" name="addedAndDeletedFlags[@flag.Name].IsChecked" value="true" />
</div>
}
</div>
}
@if (Model.staging.DeletedFlags.Any())
{
<h3>Deleted Flags</h3>
<div class="flag-container">
@foreach (FormFlag flag in Model.staging.DeletedFlags)
{
<div class="flag">
<img src="~/flags/@(flag.Name).png" />
<br />
<span>@flag.Name</span>
<input type="hidden" name="addedAndDeletedFlags.Index" value="@flag.Name" />
<input type="hidden" name="addedAndDeletedFlags[@flag.Name].Name" value="@flag.Name" />
<input type="hidden" name="addedAndDeletedFlags[@flag.Name].Method" value="@Method.Delete" />
<br />
<input type="checkbox" name="addedAndDeletedFlags[@flag.Name].IsChecked" value="true" />
</div>
}
</div>
}
@if (Model.staging.RenamedFlags.Any())
{
<h3>Renamed Flags</h3>
<div class="flag-container">
@foreach (RenameFlag flag in Model.staging.RenamedFlags)
{
<div class="flag">
<img src="~/flags/@(flag.Name).png" />
<br />
<span>@flag.NewName</span>
<input type="hidden" name="renamedFlags.Index" value="@flag.Name" />
<input type="hidden" name="renamedFlags[@flag.Name].Name" value="@flag.Name" />
<input type="hidden" name="renamedFlags[@flag.Name].Method" value="@Method.Rename" />
<input type="hidden" name="renamedFlags[@flag.Name].NewName" value="@flag.NewName" />
<br />
<input type="checkbox" name="renamedFlags[@flag.Name].IsChecked" value="true" />
</div>
}
</div>
}
</form>
@section Scripts {
@* Place flag image inside the <select> because ASP removes "invalid HTML" *@
<script>
let x = document.getElementsByTagName('select')[0].children
Array.prototype.slice.call(x).forEach(function (e) {
var name = e.innerHTML;
e.innerHTML = "<img src=\"https://flags.plum.moe/flags/" + name + ".png\">" + name
});
</script>
}
@section Head {
<link rel="stylesheet" href="~/form.css" />
}

@ -0,0 +1,275 @@
using BantFlags.Data;
using BantFlags.Data.Database;
using ImageMagick;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.RazorPages;
using System;
using System.Collections.Generic;
using System.Data;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace BantFlags
{
[RequestFormLimits(ValueCountLimit = 5000)]
[IgnoreAntiforgeryToken(Order = 2000)]
public class UploadModel : PageModel
{
private IWebHostEnvironment Env { get; }
private DatabaseService Database { get; set; }
private readonly byte[] pngHeader = new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A };
private string FlagsPath { get; set; }
public Staging staging { get; set; }
public UploadModel(IWebHostEnvironment env, DatabaseService db, Staging s)
{
Env = env;
Database = db;
staging = s;
FlagsPath = Env.WebRootPath + "/flags/";
}
public string Message { get; private set; }
// TODO: These bound properties should be inlined.
[BindProperty]
public IFormFile Upload { get; set; }
[BindProperty]
public bool ShouldGloss { get; set; }
public async void OnGet()
{
if (staging.Flags == null)
{
staging.Flags = await Database.GetFlags(); // Because we can't populate Flags in the constructor.
}
}
public IActionResult OnPostUnstage(List<FormFlag> addedAndDeletedFlags, List<RenameFlag> renamedFlags, string password)
{
if (password != staging.Password) // TODO: Maybe we should hash this?
{
Message = "Wrong Password";
return Page();
}
try // Haha I can't program
{
var addedAndDeleted = addedAndDeletedFlags.Where(x => x.IsChecked);
var renamed = renamedFlags.Where(x => x.IsChecked);
addedAndDeleted.ForEach(x =>
_ = x.FormMethod switch
{
// Using an enum seems kinda redundant here.
Method.Delete => staging.DeletedFlags.Remove(staging.DeletedFlags.First(y => y.Name == x.Name)),
Method.Add => staging.AddedFlags.Remove(staging.AddedFlags.First(y => y.Name == x.Name)),
_ => throw new Exception()
});
renamed.ForEach(x => staging.RenamedFlags.Remove(staging.RenamedFlags.First(y => y.Name == x.Name))); // These can be reworked to use asp-for and then we can work off the original objects.
addedAndDeleted.ForEach(x => staging.Flags.Add(x.Name));
renamed.ForEach(x => staging.Flags.Add(x.Name));
staging.Flags = staging.Flags.Distinct().OrderBy(x => x).ToList();
Message = $"Successfully unstaged flags";
}
catch
{
Message = "Something went very wrong";
}
return Page();
}
public async Task<IActionResult> OnPostCommitAsync(string password)
{
if (password != staging.Password)
{
Message = "Wrong Password";
return Page();
}
try
{
// TODO: This needs to be rewritten / burnt / both
if (staging.DeletedFlags.Any())
{
await Database.DeleteFlagsAsync(staging.DeletedFlags);
staging.DeletedFlags
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, flag.Name + ".png"), Path.Combine(FlagsPath, "dead/", flag.Name + ".png")));
}
if (staging.AddedFlags.Any())
{
await Database.InsertFlagsAsync(staging.AddedFlags);
staging.AddedFlags
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, "staging/", flag.Name + ".png"), Path.Combine(FlagsPath, flag.Name + ".png")));
}
if (staging.RenamedFlags.Any())
{
await Database.RenameFlagsAsync(staging.RenamedFlags);
staging.RenamedFlags
.ForEach(flag => System.IO.File.Copy(Path.Combine(FlagsPath, flag.Name + ".png"), Path.Combine(FlagsPath, flag.NewName + ".png")));
}
await Database.UpdateKnownFlags();
staging.Flags = await Database.GetFlags();
staging.Clear();
Message = "Changes Commited successfully";
}
catch (Exception e)
{
Message = "Something went bang\n" + e.Message;
}
return Page();
}
public IActionResult OnPostDelete(string flag)
{
staging.DeletedFlags.Add(new FormFlag
{
Name = flag
});
staging.Flags.Remove(flag);
return Page();
}
public IActionResult OnPostRename(string flag, string newName)
{
if (!(FileNameIsValid(newName)))
{
Message = "Invalid Filename.";
return Page();
}
staging.RenamedFlags.Add(new RenameFlag
{
Name = flag,
NewName = newName
});
staging.Flags.Remove(flag);
return Page();
}
public async Task<IActionResult> OnPostAddAsync()
{
try
{
if (!(await ValidateImageAsync()))
{
Message = "Invalid Image.";
return Page();
}
// TODO: maybe there's something no releasing memory here.
using var memoryStream = new MemoryStream();
await Upload.CopyToAsync(memoryStream);
memoryStream.Position = 0;
// Magic.NET is a huge dependency to be used like this
// Maybe we should switch to a Process and expect to have
// ImageMagick installed on the target machine.
using var image = new MagickImage(memoryStream);
if (ShouldGloss)
{
using var gloss = new MagickImage(Env.WebRootPath + "/gloss.png");
gloss.Composite(image, new PointD(0, 0), CompositeOperator.Over);
}
image.Write(FlagsPath + "staging/" + Upload.FileName);
staging.AddedFlags.Add(new FormFlag
{
Name = Path.GetFileNameWithoutExtension(Upload.FileName)
});
Message = "Flag uploaded successfully!";
return Page();
}
catch (Exception e)
{
Message = $"Something went bang.\n{e.Message}";
return Page();
}
}
// TODO: hash images and check against for duplicates.
// or is that going too far?
/// <summary>
/// Rigorously validates an image to ensure it's a flag.
/// </summary>
public async Task<bool> ValidateImageAsync()
{
var fileName = Path.GetFileNameWithoutExtension(Upload.FileName);
if (!(FileNameIsValid(fileName))
|| Upload.Length > 15 * 1024 // 15KB
|| Upload.ContentType.ToLower() != "image/png")
{
return false;
}
using (var memoryStream = new MemoryStream())
{
await Upload.CopyToAsync(memoryStream);
memoryStream.Position = 0;
using (var image = new MagickImage(memoryStream))
{
if (image.Width != 16 || image.Height != 11)
{
return false;
}
}
using (var reader = new BinaryReader(memoryStream))
{
reader.BaseStream.Position = 0;
return reader.ReadBytes(pngHeader.Length).SequenceEqual(pngHeader);
}
}
}
/// <summary>
/// Matches bad things we don't want in filenames.
/// Inverts the result - returns false on a match.
/// </summary>
/// <param name="fileName">The name of the file to validate.</param>
/// <returns></returns>
private bool FileNameIsValid(string fileName) =>
!(fileName == null
|| fileName.Contains("||")
|| Database.KnownFlags().Contains(fileName)
|| staging.AddedFlags.Select(x => x.Name).Contains(fileName)
|| fileName.Length > 100);
}
}

@ -0,0 +1,3 @@
@using BantFlags
@namespace BantFlags.Pages
@addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers

@ -0,0 +1,3 @@
@{
Layout = "_Layout";
}

@ -1,13 +1,13 @@
{
"$schema": "http://json.schemastore.org/launchsettings.json",
{
"iisSettings": {
"windowsAuthentication": false,
"anonymousAuthentication": true,
"iisExpress": {
"applicationUrl": "http://localhost:11673",
"applicationUrl": "http://localhost:17777",
"sslPort": 44366
}
},
"$schema": "http://json.schemastore.org/launchsettings.json",
"profiles": {
"IIS Express": {
"commandName": "IISExpress",
@ -21,10 +21,10 @@
"commandName": "Project",
"launchBrowser": true,
"launchUrl": "weatherforecast",
"applicationUrl": "https://localhost:5001;http://localhost:5000",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
},
"applicationUrl": "https://localhost:5001;http://localhost:5000"
}
}
}
}

@ -1,13 +1,16 @@
using BantFlags.Data;
using BantFlags.Data.Database;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.FileProviders;
using Microsoft.Extensions.Hosting;
using System.IO;
using System.Runtime.InteropServices;
namespace BantFlags
{
@ -28,7 +31,15 @@ namespace BantFlags
services.AddRazorPages();
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) // For image upload during production.
{
services.AddDataProtection()
.SetApplicationName("BantFlags")
.PersistKeysToFileSystem(new DirectoryInfo(@"/var/www/dotnet/bantflags/wtf-keys/"));
}
services.AddSingleton(new DatabaseService(Configuration.GetSection("dbconfig").Get<DatabaseServiceConfig>()));
services.AddSingleton(new Staging(Configuration.GetValue<string>("staging-password")));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
@ -61,8 +72,8 @@ namespace BantFlags
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapRazorPages();
endpoints.MapControllers();
});
}
}

@ -13,5 +13,6 @@
}
},
"webroot": ""
"webroot": "/var/www/html",
"staging-password": "supersecretpassword"
}

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

@ -0,0 +1,4 @@
# Ignore everything in this directory
*
# Except this file
!.gitignore

@ -0,0 +1,24 @@
body {
background: #ffe url(fade.png) top center repeat-x;
}
h1, h2, h3 {
color: maroon;
}
h3 {
clear: both;
padding-top: 20px;
}
.flag-container {
width: 60%;
margin: auto;
}
.flag {
margin: auto;
width: 12%;
display: block;
padding-bottom: 10px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

@ -6,6 +6,7 @@ CREATE USER IF NOT EXISTS flags@localhost IDENTIFIED BY 'default';
GRANT ALL PRIVILEGES ON bantflags.* TO flags@localhost;
FLUSH PRIVILEGES;
CREATE TABLE IF NOT EXISTS `flags` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`flag` VARCHAR(100) NOT NULL DEFAULT '0',
@ -17,6 +18,7 @@ ENGINE=InnoDB
AUTO_INCREMENT=0
;
CREATE TABLE IF NOT EXISTS `posts` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`post_nr` INT(10) NOT NULL DEFAULT '0',
@ -29,6 +31,7 @@ ENGINE=InnoDB
AUTO_INCREMENT=0
;
CREATE TABLE IF NOT EXISTS `postflags` (
`id` INT(10) NOT NULL AUTO_INCREMENT,
`post_nr` INT(10) NOT NULL DEFAULT '0',
@ -36,7 +39,7 @@ CREATE TABLE IF NOT EXISTS `postflags` (
PRIMARY KEY (`id`),
INDEX `flag` (`flag`),
INDEX `post_nr` (`post_nr`),
CONSTRAINT `flag` FOREIGN KEY (`flag`) REFERENCES `flags` (`id`),
CONSTRAINT `flag` FOREIGN KEY (`flag`) REFERENCES `flags` (`id`) ON DELETE CASCADE,
CONSTRAINT `post_nr` FOREIGN KEY (`post_nr`) REFERENCES `posts` (`id`)
)
COLLATE='utf8_general_ci'
@ -44,6 +47,7 @@ ENGINE=InnoDB
AUTO_INCREMENT=0
;
DROP PROCEDURE IF EXISTS insert_post;
DELIMITER $$
CREATE DEFINER=`flags`@`localhost` PROCEDURE `insert_post`(
@ -61,6 +65,7 @@ END
$$
DELIMITER ;
DROP PROCEDURE IF EXISTS insert_post_flags;
DELIMITER $$
CREATE DEFINER=`flags`@`localhost` PROCEDURE `insert_post_flags`(
@ -80,3 +85,59 @@ insert into postflags (post_nr, flag) VALUES (
END
$$
DELIMITER ;
DROP PROCEDURE IF EXISTS rename_flag;
DELIMITER $$
CREATE DEFINER=`flags`@`localhost` PROCEDURE `rename_flag`(
IN `@old` VARCHAR(100),
IN `@new` VARCHAR(100)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
UPDATE flags SET flags.flag = `@new` WHERE flags.flag = `@old`;
END
$$
DELIMITER ;
DROP PROCEDURE IF EXISTS delete_flag;
DELIMITER $$
CREATE DEFINER=`flags`@`localhost` PROCEDURE `delete_flag`(
IN `@flag` VARCHAR(100)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
DELETE flags.* FROM flags WHERE flags.flag = `@flag`;
END
$$
DELIMITER ;
DROP PROCEDURE IF EXISTS insert_flag;
DELIMITER $$
CREATE DEFINER=`flags`@`localhost` PROCEDURE `insert_flag`(
IN `@flag` VARCHAR(100)
)
LANGUAGE SQL
NOT DETERMINISTIC
CONTAINS SQL
SQL SECURITY DEFINER
COMMENT ''
BEGIN
INSERT INTO `flags` (`flag`) VALUES (`@flag`);
END
$$
DELIMITER ;

@ -35,6 +35,8 @@ declarations. Update your hecking browser.
- Microsoft.AspNetCore.StaticFiles
- Microsoft.EntityFrameworkCore.SqlServer
- Microsoft.EntityFrameworkCore.Tools
- Microsoft.VisualStudio.Web.CodeGeneration.Design
- Magick.NET-Q8-AnyCPU
*** Setup
1. Install .NET
@ -42,8 +44,8 @@ declarations. Update your hecking browser.
3. Create the database using [[https://github.com/C-xC-c/BantFlags/blob/master/Environment/database.sql][database.sql]].
+ *Change the password*.
4. configure =BantFlags/appsettings.example.json= with your connection
string and webroot (the working directory / where you'll serve the
flags from) and rename it to =appsettings.json=
string and webroot (where you'll serve the flags from *without a
trailing /*) and rename it to =appsettings.json=
+ The location of the BantFlags application and the served content
are not necessarly the same. If you leave it empty, or provide a
nonexistant path the application will look for the =wwwroot=
@ -53,9 +55,9 @@ declarations. Update your hecking browser.
=/var/www/html= and bantflags will server static content from
that directory, or leave =webroot= empty and bantflags will look
for static content in =/etc/bantflags/wwwroot=
5. Add flags to the backend (currently only possible by querying the
database directly), and place image *with the same name* in
={webroot}/flags/=.
5. Add flags to the backend either by querying the database directly
and placing images *with the same name* in ={webroot}/flags/= or
by using /upload.
6. Configure your webserver of choice to forward requests to kestral
+ [[https://github.com/C-xC-c/BantFlags/blob/master/Environment/nginx.conf][Example nginx config.]]
7. Run with =dotnet BantFlags.dll= or create a service to run it as a
@ -66,6 +68,7 @@ declarations. Update your hecking browser.
*** Database
Tables look like this:
*posts*
| id | post_nr | board |
| 1 | 12345 | bant |
@ -97,4 +100,4 @@ The API is 1:1 compatable with all previous versions of bantflags, but
also encodes a new =version= variable when getting flags which allows
for breaking changes in the script while the backend only sends data
it knows is parsable. See [[https://github.com/C-xC-c/BantFlags/tree/master/Docs/][Docs/{endpoint}]]
for version compatibility.
for version compatibility.
Loading…
Cancel
Save