Added Ellie.VotesApi
This commit is contained in:
parent
6e293408ce
commit
92925d2d0e
22 changed files with 599 additions and 8 deletions
24
Ellie.sln
24
Ellie.sln
|
@ -6,13 +6,14 @@ MinimumVisualStudioVersion = 10.0.40219.1
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0B2F1537-4BF0-422B-A0DD-8F9CCEFB340F}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{0B2F1537-4BF0-422B-A0DD-8F9CCEFB340F}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
CHANGELOG.md = CHANGELOG.md
|
CHANGELOG.md = CHANGELOG.md
|
||||||
LICENSE.md = LICENSE.md
|
Dockerfile = Dockerfile
|
||||||
README.md = README.md
|
LICENSE.md = LICENSE.md
|
||||||
Dockerfile = Dockerfile
|
NuGet.Config = NuGet.Config
|
||||||
NuGet.Config = NuGet.Config
|
README.md = README.md
|
||||||
EndProjectSection
|
EndProjectSection
|
||||||
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie", "src\Ellie\Ellie.csproj", "{2BAF005E-781D-45FF-B218-E6361F5E8CD4}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie", "src\Ellie\Ellie.csproj", "{2BAF005E-781D-45FF-B218-E6361F5E8CD4}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ayu", "ayu", "{5284415D-A43F-4539-9483-410124199743}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ayu", "ayu", "{5284415D-A43F-4539-9483-410124199743}"
|
||||||
|
@ -25,7 +26,9 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie.Marmalade", "src\Elli
|
||||||
EndProject
|
EndProject
|
||||||
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie.Coordinator", "src\Ellie.Coordinator\Ellie.Coordinator.csproj", "{44BE7271-BABE-46BE-BB41-A5B6F1116C21}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie.Coordinator", "src\Ellie.Coordinator\Ellie.Coordinator.csproj", "{44BE7271-BABE-46BE-BB41-A5B6F1116C21}"
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ellie.Bot.Generators.Strings", "src\Ellie.Bot.Generators.Strings\Ellie.Bot.Generators.Strings.csproj", "{11DE9EB6-2793-4540-BE66-701D2D02903A}"
|
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Ellie.Bot.Generators.Strings", "src\Ellie.Bot.Generators.Strings\Ellie.Bot.Generators.Strings.csproj", "{11DE9EB6-2793-4540-BE66-701D2D02903A}"
|
||||||
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Ellie.VotesApi", "src\Ellie.VotesApi\Ellie.VotesApi.csproj", "{8D996036-52D1-4B11-B7D7-6F853A907EDD}"
|
||||||
EndProject
|
EndProject
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -57,6 +60,10 @@ Global
|
||||||
{11DE9EB6-2793-4540-BE66-701D2D02903A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{11DE9EB6-2793-4540-BE66-701D2D02903A}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{11DE9EB6-2793-4540-BE66-701D2D02903A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{11DE9EB6-2793-4540-BE66-701D2D02903A}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{11DE9EB6-2793-4540-BE66-701D2D02903A}.Release|Any CPU.Build.0 = Release|Any CPU
|
{11DE9EB6-2793-4540-BE66-701D2D02903A}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{8D996036-52D1-4B11-B7D7-6F853A907EDD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{8D996036-52D1-4B11-B7D7-6F853A907EDD}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{8D996036-52D1-4B11-B7D7-6F853A907EDD}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{8D996036-52D1-4B11-B7D7-6F853A907EDD}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(SolutionProperties) = preSolution
|
GlobalSection(SolutionProperties) = preSolution
|
||||||
HideSolutionNode = FALSE
|
HideSolutionNode = FALSE
|
||||||
|
@ -69,6 +76,7 @@ Global
|
||||||
{D6CF9ABE-205E-4699-90CA-0F18ED236490} = {C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}
|
{D6CF9ABE-205E-4699-90CA-0F18ED236490} = {C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}
|
||||||
{44BE7271-BABE-46BE-BB41-A5B6F1116C21} = {C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}
|
{44BE7271-BABE-46BE-BB41-A5B6F1116C21} = {C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}
|
||||||
{11DE9EB6-2793-4540-BE66-701D2D02903A} = {C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}
|
{11DE9EB6-2793-4540-BE66-701D2D02903A} = {C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}
|
||||||
|
{8D996036-52D1-4B11-B7D7-6F853A907EDD} = {C5E3EF2E-72CF-41BB-B0C5-EB4C08403E67}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||||
SolutionGuid = {878761F1-C7B5-4D38-A00D-3377D703EBBA}
|
SolutionGuid = {878761F1-C7B5-4D38-A00D-3377D703EBBA}
|
||||||
|
|
25
src/Ellie.VotesApi/.dockerignore
Normal file
25
src/Ellie.VotesApi/.dockerignore
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
**/.dockerignore
|
||||||
|
**/.env
|
||||||
|
**/.git
|
||||||
|
**/.gitignore
|
||||||
|
**/.project
|
||||||
|
**/.settings
|
||||||
|
**/.toolstarget
|
||||||
|
**/.vs
|
||||||
|
**/.vscode
|
||||||
|
**/.idea
|
||||||
|
**/*.*proj.user
|
||||||
|
**/*.dbmdl
|
||||||
|
**/*.jfm
|
||||||
|
**/azds.yaml
|
||||||
|
**/bin
|
||||||
|
**/charts
|
||||||
|
**/docker-compose*
|
||||||
|
**/Dockerfile*
|
||||||
|
**/node_modules
|
||||||
|
**/npm-debug.log
|
||||||
|
**/obj
|
||||||
|
**/secrets.dev.yaml
|
||||||
|
**/values.dev.yaml
|
||||||
|
LICENSE
|
||||||
|
README.md
|
1
src/Ellie.VotesApi/.gitignore
vendored
Normal file
1
src/Ellie.VotesApi/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
store/
|
41
src/Ellie.VotesApi/Common/AuthHandler.cs
Normal file
41
src/Ellie.VotesApi/Common/AuthHandler.cs
Normal file
|
@ -0,0 +1,41 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Security.Claims;
|
||||||
|
using System.Text.Encodings.Web;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authentication;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Microsoft.Extensions.Options;
|
||||||
|
|
||||||
|
namespace Ellie.VotesApi
|
||||||
|
{
|
||||||
|
public class AuthHandler : AuthenticationHandler<AuthenticationSchemeOptions>
|
||||||
|
{
|
||||||
|
public const string SchemeName = "AUTHORIZATION_SCHEME";
|
||||||
|
public const string DiscordsClaim = "DISCORDS_CLAIM";
|
||||||
|
public const string TopggClaim = "TOPGG_CLAIM";
|
||||||
|
|
||||||
|
private readonly IConfiguration _conf;
|
||||||
|
|
||||||
|
public AuthHandler(IOptionsMonitor<AuthenticationSchemeOptions> options,
|
||||||
|
ILoggerFactory logger,
|
||||||
|
UrlEncoder encoder,
|
||||||
|
ISystemClock clock,
|
||||||
|
IConfiguration conf)
|
||||||
|
: base(options, logger, encoder, clock)
|
||||||
|
=> _conf = conf;
|
||||||
|
|
||||||
|
protected override Task<AuthenticateResult> HandleAuthenticateAsync()
|
||||||
|
{
|
||||||
|
var claims = new List<Claim>();
|
||||||
|
|
||||||
|
if (_conf[ConfKeys.DISCORDS_KEY].Trim() == Request.Headers["Authorization"].ToString().Trim())
|
||||||
|
claims.Add(new(DiscordsClaim, "true"));
|
||||||
|
|
||||||
|
if (_conf[ConfKeys.TOPGG_KEY] == Request.Headers["Authorization"].ToString().Trim())
|
||||||
|
claims.Add(new Claim(TopggClaim, "true"));
|
||||||
|
|
||||||
|
return Task.FromResult(AuthenticateResult.Success(new(new(new ClaimsIdentity(claims)), SchemeName)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
8
src/Ellie.VotesApi/Common/ConfKeys.cs
Normal file
8
src/Ellie.VotesApi/Common/ConfKeys.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ellie.VotesApi
|
||||||
|
{
|
||||||
|
public static class ConfKeys
|
||||||
|
{
|
||||||
|
public const string DISCORDS_KEY = "DiscordsKey";
|
||||||
|
public const string TOPGG_KEY = "TopGGKey";
|
||||||
|
}
|
||||||
|
}
|
26
src/Ellie.VotesApi/Common/DiscordsVoteWebhookModel.cs
Normal file
26
src/Ellie.VotesApi/Common/DiscordsVoteWebhookModel.cs
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
namespace Ellie.VotesApi
|
||||||
|
{
|
||||||
|
public class DiscordsVoteWebhookModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the user who voted
|
||||||
|
/// </summary>
|
||||||
|
public string User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The ID of the bot which recieved the vote
|
||||||
|
/// </summary>
|
||||||
|
public string Bot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Contains totalVotes, votesMonth, votes24, hasVoted - a list of IDs of users who have voted this month, and
|
||||||
|
/// Voted24 - a list of IDs of users who have voted today
|
||||||
|
/// </summary>
|
||||||
|
public string Votes { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of event, whether it is a vote event or test event
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; }
|
||||||
|
}
|
||||||
|
}
|
8
src/Ellie.VotesApi/Common/Policies.cs
Normal file
8
src/Ellie.VotesApi/Common/Policies.cs
Normal file
|
@ -0,0 +1,8 @@
|
||||||
|
namespace Ellie.VotesApi
|
||||||
|
{
|
||||||
|
public static class Policies
|
||||||
|
{
|
||||||
|
public const string DiscordsAuth = "DiscordsAuth";
|
||||||
|
public const string TopggAuth = "TopggAuth";
|
||||||
|
}
|
||||||
|
}
|
30
src/Ellie.VotesApi/Common/ToppVoteWebhookModel.cs
Normal file
30
src/Ellie.VotesApi/Common/ToppVoteWebhookModel.cs
Normal file
|
@ -0,0 +1,30 @@
|
||||||
|
namespace Ellie.VotesApi
|
||||||
|
{
|
||||||
|
public class TopggVoteWebhookModel
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Discord ID of the bot that received a vote.
|
||||||
|
/// </summary>
|
||||||
|
public string Bot { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Discord ID of the user who voted.
|
||||||
|
/// </summary>
|
||||||
|
public string User { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// The type of the vote (should always be "upvote" except when using the test button it's "test").
|
||||||
|
/// </summary>
|
||||||
|
public string Type { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether the weekend multiplier is in effect, meaning users votes count as two.
|
||||||
|
/// </summary>
|
||||||
|
public bool Weekend { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Query string params found on the /bot/:ID/vote page. Example: ?a=1&b=2.
|
||||||
|
/// </summary>
|
||||||
|
public string Query { get; set; }
|
||||||
|
}
|
||||||
|
}
|
33
src/Ellie.VotesApi/Controllers/DiscordsController.cs
Normal file
33
src/Ellie.VotesApi/Controllers/DiscordsController.cs
Normal file
|
@ -0,0 +1,33 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ellie.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace Ellie.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class DiscordsController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<DiscordsController> _logger;
|
||||||
|
private readonly IVotesCache _cache;
|
||||||
|
|
||||||
|
public DiscordsController(ILogger<DiscordsController> logger, IVotesCache cache)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("new")]
|
||||||
|
[Authorize(Policy = Policies.DiscordsAuth)]
|
||||||
|
public async Task<IEnumerable<Vote>> New()
|
||||||
|
{
|
||||||
|
var votes = await _cache.GetNewDiscordsVotesAsync();
|
||||||
|
if(votes.Count > 0)
|
||||||
|
_logger.LogInformation("Sending {NewDiscordsVotes} new discords votes", votes.Count);
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
34
src/Ellie.VotesApi/Controllers/TopGgController.cs
Normal file
34
src/Ellie.VotesApi/Controllers/TopGgController.cs
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ellie.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace Ellie.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
[Route("[controller]")]
|
||||||
|
public class TopGgController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<TopGgController> _logger;
|
||||||
|
private readonly IVotesCache _cache;
|
||||||
|
|
||||||
|
public TopGgController(ILogger<TopGgController> logger, IVotesCache cache)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_cache = cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("new")]
|
||||||
|
[Authorize(Policy = Policies.TopggAuth)]
|
||||||
|
public async Task<IEnumerable<Vote>> New()
|
||||||
|
{
|
||||||
|
var votes = await _cache.GetNewTopGgVotesAsync();
|
||||||
|
if (votes.Count > 0)
|
||||||
|
_logger.LogInformation("Sending {NewTopggVotes} new topgg votes", votes.Count);
|
||||||
|
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
48
src/Ellie.VotesApi/Controllers/WebhookController.cs
Normal file
48
src/Ellie.VotesApi/Controllers/WebhookController.cs
Normal file
|
@ -0,0 +1,48 @@
|
||||||
|
using System.Threading.Tasks;
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
|
using Ellie.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace Ellie.VotesApi.Controllers
|
||||||
|
{
|
||||||
|
[ApiController]
|
||||||
|
public class WebhookController : ControllerBase
|
||||||
|
{
|
||||||
|
private readonly ILogger<WebhookController> _logger;
|
||||||
|
private readonly IVotesCache _votesCache;
|
||||||
|
|
||||||
|
public WebhookController(ILogger<WebhookController> logger, IVotesCache votesCache)
|
||||||
|
{
|
||||||
|
_logger = logger;
|
||||||
|
_votesCache = votesCache;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("/discordswebhook")]
|
||||||
|
[Authorize(Policy = Policies.DiscordsAuth)]
|
||||||
|
public async Task<IActionResult> DiscordsWebhook([FromBody] DiscordsVoteWebhookModel data)
|
||||||
|
{
|
||||||
|
|
||||||
|
_logger.LogInformation("User {UserId} has voted for Bot {BotId} on {Platform}",
|
||||||
|
data.User,
|
||||||
|
data.Bot,
|
||||||
|
"discords.com");
|
||||||
|
|
||||||
|
await _votesCache.AddNewDiscordsVote(data.User);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpPost("/topggwebhook")]
|
||||||
|
[Authorize(Policy = Policies.TopggAuth)]
|
||||||
|
public async Task<IActionResult> TopggWebhook([FromBody] TopggVoteWebhookModel data)
|
||||||
|
{
|
||||||
|
_logger.LogInformation("User {UserId} has voted for Bot {BotId} on {Platform}",
|
||||||
|
data.User,
|
||||||
|
data.Bot,
|
||||||
|
"top.gg");
|
||||||
|
|
||||||
|
await _votesCache.AddNewTopggVote(data.User);
|
||||||
|
return Ok();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
20
src/Ellie.VotesApi/Dockerfile
Normal file
20
src/Ellie.VotesApi/Dockerfile
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
FROM mcr.microsoft.com/dotnet/aspnet:5.0 AS base
|
||||||
|
WORKDIR /app
|
||||||
|
EXPOSE 80
|
||||||
|
EXPOSE 443
|
||||||
|
|
||||||
|
FROM mcr.microsoft.com/dotnet/sdk:5.0 AS build
|
||||||
|
WORKDIR /src
|
||||||
|
COPY ["src/Ellie.VotesApi/Ellie.VotesApi.csproj", "Ellie.VotesApi/"]
|
||||||
|
RUN dotnet restore "src/Ellie.VotesApi/Ellie.VotesApi.csproj"
|
||||||
|
COPY . .
|
||||||
|
WORKDIR "/src/Ellie.VotesApi"
|
||||||
|
RUN dotnet build "Ellie.VotesApi.csproj" -c Release -o /app/build
|
||||||
|
|
||||||
|
FROM build AS publish
|
||||||
|
RUN dotnet publish "Ellie.VotesApi.csproj" -c Release -o /app/publish
|
||||||
|
|
||||||
|
FROM base AS final
|
||||||
|
WORKDIR /app
|
||||||
|
COPY --from=publish /app/publish .
|
||||||
|
ENTRYPOINT ["dotnet", "Ellie.VotesApi.dll"]
|
13
src/Ellie.VotesApi/Ellie.VotesApi.csproj
Normal file
13
src/Ellie.VotesApi/Ellie.VotesApi.csproj
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk.Web">
|
||||||
|
|
||||||
|
<PropertyGroup>
|
||||||
|
<TargetFramework>net7.0</TargetFramework>
|
||||||
|
<DockerDefaultTargetOS>Linux</DockerDefaultTargetOS>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MorseCode.ITask" Version="2.0.3" />
|
||||||
|
<PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
</Project>
|
9
src/Ellie.VotesApi/Program.cs
Normal file
9
src/Ellie.VotesApi/Program.cs
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Ellie.VotesApi;
|
||||||
|
|
||||||
|
CreateHostBuilder(args).Build().Run();
|
||||||
|
|
||||||
|
static IHostBuilder CreateHostBuilder(string[] args) =>
|
||||||
|
Host.CreateDefaultBuilder(args)
|
||||||
|
.ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup<Startup>(); });
|
31
src/Ellie.VotesApi/Properties/launchSettings.json
Normal file
31
src/Ellie.VotesApi/Properties/launchSettings.json
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"$schema": "http://json.schemastore.org/launchsettings.json",
|
||||||
|
"iisSettings": {
|
||||||
|
"windowsAuthentication": false,
|
||||||
|
"anonymousAuthentication": true,
|
||||||
|
"iisExpress": {
|
||||||
|
"applicationUrl": "http://localhost:16451",
|
||||||
|
"sslPort": 44323
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"profiles": {
|
||||||
|
"IIS Express": {
|
||||||
|
"commandName": "IISExpress",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"Ellie.VotesApi": {
|
||||||
|
"commandName": "Project",
|
||||||
|
"dotnetRunMessages": "true",
|
||||||
|
"launchBrowser": true,
|
||||||
|
"launchUrl": "swagger",
|
||||||
|
"applicationUrl": "https://localhost:5001;http://localhost:5000",
|
||||||
|
"environmentVariables": {
|
||||||
|
"ASPNETCORE_ENVIRONMENT": "Development"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
46
src/Ellie.VotesApi/README.md
Normal file
46
src/Ellie.VotesApi/README.md
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
## Votes Api
|
||||||
|
|
||||||
|
This api is used if you want your bot to be able to reward users who vote for it on discords.com or top.gg
|
||||||
|
|
||||||
|
#### [GET] `/discords/new`
|
||||||
|
Get the discords votes received after previous call to this endpoint.
|
||||||
|
Input full url of this endpoint in your creds.yml file under Discords url field.
|
||||||
|
For example "https://api.my.cool.bot/discords/new"
|
||||||
|
#### [GET] `/topgg/new`
|
||||||
|
Get the topgg votes received after previous call to this endpoint.
|
||||||
|
Input full url of this endpoint in your creds.yml file under Topgg url field.
|
||||||
|
For example "https://api.my.cool.bot/topgg/new"
|
||||||
|
|
||||||
|
#### [POST] `/discordswebhook`
|
||||||
|
Input this endpoint as the webhook on discords.com bot edit page
|
||||||
|
model: https://docs.botsfordiscord.com/methods/receiving-votes
|
||||||
|
For example "https://api.my.cool.bot/topggwebhook"
|
||||||
|
#### [POST] `/topggwebhook`
|
||||||
|
Input this endpoint as the webhook https://top.gg/bot/:your-bot-id/webhooks (replace :your-bot-id with your bot's id)
|
||||||
|
model: https://docs.top.gg/resources/webhooks/#schema
|
||||||
|
For example "https://api.my.cool.bot/discordswebhook"
|
||||||
|
|
||||||
|
Input your super-secret header value in appsettings.json's DiscordsKey and TopGGKey fields
|
||||||
|
They must match your DiscordsKey and TopGG key respectively, as well as your secrets in the discords.com and top.gg webhook setup pages
|
||||||
|
|
||||||
|
Full Example:
|
||||||
|
|
||||||
|
⚠ Change TopggKey and DiscordsKey to a secure long string
|
||||||
|
⚠ You can use https://www.random.org/strings/?num=1&len=20&digits=on&upperalpha=on&loweralpha=on&unique=on&format=html&rnd=new to generate it
|
||||||
|
|
||||||
|
`creds.yml`
|
||||||
|
```yml
|
||||||
|
votes:
|
||||||
|
TopggServiceUrl: "https://api.my.cool.bot/topgg"
|
||||||
|
TopggKey: "my_topgg_key"
|
||||||
|
DiscordsServiceUrl: "https://api.my.cool.bot/discords"
|
||||||
|
DiscordsKey: "my_discords_key"
|
||||||
|
```
|
||||||
|
|
||||||
|
`appsettings.json`
|
||||||
|
```json
|
||||||
|
...
|
||||||
|
"DiscordsKey": "my_discords_key",
|
||||||
|
"TopGGKey": "my_topgg_key",
|
||||||
|
...
|
||||||
|
```
|
100
src/Ellie.VotesApi/Services/FileVotesCache.cs
Normal file
100
src/Ellie.VotesApi/Services/FileVotesCache.cs
Normal file
|
@ -0,0 +1,100 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.IO;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Text.Json;
|
||||||
|
using System.Threading;
|
||||||
|
using MorseCode.ITask;
|
||||||
|
|
||||||
|
namespace Ellie.VotesApi.Services
|
||||||
|
{
|
||||||
|
public class FileVotesCache : IVotesCache
|
||||||
|
{
|
||||||
|
// private const string STATS_FILE = "store/stats.json";
|
||||||
|
private const string TOPGG_FILE = "store/topgg.json";
|
||||||
|
private const string DISCORDS_FILE = "store/discords.json";
|
||||||
|
|
||||||
|
private readonly SemaphoreSlim _locker = new SemaphoreSlim(1, 1);
|
||||||
|
|
||||||
|
public FileVotesCache()
|
||||||
|
{
|
||||||
|
if (!Directory.Exists("store"))
|
||||||
|
Directory.CreateDirectory("store");
|
||||||
|
|
||||||
|
if (!File.Exists(TOPGG_FILE))
|
||||||
|
File.WriteAllText(TOPGG_FILE, "[]");
|
||||||
|
|
||||||
|
if (!File.Exists(DISCORDS_FILE))
|
||||||
|
File.WriteAllText(DISCORDS_FILE, "[]");
|
||||||
|
}
|
||||||
|
|
||||||
|
public ITask AddNewTopggVote(string userId)
|
||||||
|
=> AddNewVote(TOPGG_FILE, userId);
|
||||||
|
|
||||||
|
public ITask AddNewDiscordsVote(string userId)
|
||||||
|
=> AddNewVote(DISCORDS_FILE, userId);
|
||||||
|
|
||||||
|
private async ITask AddNewVote(string file, string userId)
|
||||||
|
{
|
||||||
|
await _locker.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
var votes = await GetVotesAsync(file);
|
||||||
|
votes.Add(userId);
|
||||||
|
await File.WriteAllTextAsync(file, JsonSerializer.Serialize(votes));
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_locker.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ITask<IList<Vote>> GetNewTopGgVotesAsync()
|
||||||
|
{
|
||||||
|
var votes = await EvictTopggVotes();
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async ITask<IList<Vote>> GetNewDiscordsVotesAsync()
|
||||||
|
{
|
||||||
|
var votes = await EvictDiscordsVotes();
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ITask<List<Vote>> EvictTopggVotes()
|
||||||
|
=> EvictVotes(TOPGG_FILE);
|
||||||
|
|
||||||
|
private ITask<List<Vote>> EvictDiscordsVotes()
|
||||||
|
=> EvictVotes(DISCORDS_FILE);
|
||||||
|
|
||||||
|
private async ITask<List<Vote>> EvictVotes(string file)
|
||||||
|
{
|
||||||
|
await _locker.WaitAsync();
|
||||||
|
try
|
||||||
|
{
|
||||||
|
|
||||||
|
var ids = await GetVotesAsync(file);
|
||||||
|
await File.WriteAllTextAsync(file, "[]");
|
||||||
|
|
||||||
|
return ids?
|
||||||
|
.Select(x => (Ok: ulong.TryParse(x, out var r), Id: r))
|
||||||
|
.Where(x => x.Ok)
|
||||||
|
.Select(x => new Vote
|
||||||
|
{
|
||||||
|
UserId = x.Id
|
||||||
|
})
|
||||||
|
.ToList();
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
_locker.Release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async ITask<IList<string>> GetVotesAsync(string file)
|
||||||
|
{
|
||||||
|
await using var fs = File.Open(file, FileMode.Open);
|
||||||
|
var votes = await JsonSerializer.DeserializeAsync<List<string>>(fs);
|
||||||
|
return votes;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
13
src/Ellie.VotesApi/Services/IVotesCache.cs
Normal file
13
src/Ellie.VotesApi/Services/IVotesCache.cs
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using MorseCode.ITask;
|
||||||
|
|
||||||
|
namespace Ellie.VotesApi.Services
|
||||||
|
{
|
||||||
|
public interface IVotesCache
|
||||||
|
{
|
||||||
|
ITask<IList<Vote>> GetNewTopGgVotesAsync();
|
||||||
|
ITask<IList<Vote>> GetNewDiscordsVotesAsync();
|
||||||
|
ITask AddNewTopggVote(string userId);
|
||||||
|
ITask AddNewDiscordsVote(string userId);
|
||||||
|
}
|
||||||
|
}
|
68
src/Ellie.VotesApi/Startup.cs
Normal file
68
src/Ellie.VotesApi/Startup.cs
Normal file
|
@ -0,0 +1,68 @@
|
||||||
|
using Microsoft.AspNetCore.Authorization;
|
||||||
|
using Microsoft.AspNetCore.Builder;
|
||||||
|
using Microsoft.AspNetCore.Hosting;
|
||||||
|
using Microsoft.Extensions.Configuration;
|
||||||
|
using Microsoft.Extensions.DependencyInjection;
|
||||||
|
using Microsoft.Extensions.Hosting;
|
||||||
|
using Microsoft.OpenApi.Models;
|
||||||
|
using Ellie.VotesApi.Services;
|
||||||
|
|
||||||
|
namespace Ellie.VotesApi
|
||||||
|
{
|
||||||
|
public class Startup
|
||||||
|
{
|
||||||
|
public IConfiguration Configuration { get; }
|
||||||
|
|
||||||
|
public Startup(IConfiguration configuration)
|
||||||
|
=> Configuration = configuration;
|
||||||
|
|
||||||
|
|
||||||
|
// This method get called by the runtime. Use this method to add services tp the container.
|
||||||
|
public void ConfigureServices(IServiceCollection services)
|
||||||
|
{
|
||||||
|
services.AddControllers();
|
||||||
|
services.AddSingleton<IVotesCache, FileVotesCache>();
|
||||||
|
services.AddSwaggerGen(static c =>
|
||||||
|
{
|
||||||
|
c.SwaggerDoc("v1", new OpenApiInfo { Title = "Ellie.VotesApi", Version = "v1" });
|
||||||
|
});
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddAuthentication(opts =>
|
||||||
|
{
|
||||||
|
opts.DefaultScheme = AuthHandler.SchemeName;
|
||||||
|
opts.AddScheme<AuthHandler>(AuthHandler.SchemeName, AuthHandler.SchemeName);
|
||||||
|
});
|
||||||
|
|
||||||
|
services
|
||||||
|
.AddAuthorization(static opts =>
|
||||||
|
{
|
||||||
|
opts.DefaultPolicy = new AuthorizationPolicyBuilder(AuthHandler.SchemeName)
|
||||||
|
.RequireAssertion(static _ => false)
|
||||||
|
.Build();
|
||||||
|
opts.AddPolicy(Policies.DiscordsAuth, static policy => policy.RequireClaim(AuthHandler.DiscordsClaim));
|
||||||
|
opts.AddPolicy(Policies.TopggAuth, static policy => policy.RequireClaim(AuthHandler.TopggClaim));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
|
||||||
|
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
|
||||||
|
{
|
||||||
|
if (env.IsDevelopment())
|
||||||
|
{
|
||||||
|
app.UseDeveloperExceptionPage();
|
||||||
|
app.UseSwagger();
|
||||||
|
app.UseSwaggerUI(static c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "Ellie.VotesApi v1"));
|
||||||
|
}
|
||||||
|
|
||||||
|
app.UseHttpsRedirection();
|
||||||
|
|
||||||
|
app.UseRouting();
|
||||||
|
|
||||||
|
app.UseAuthentication();
|
||||||
|
app.UseAuthorization();
|
||||||
|
|
||||||
|
app.UseEndpoints(static endpoints => { endpoints.MapControllers(); });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
7
src/Ellie.VotesApi/WeatherForecast.cs
Normal file
7
src/Ellie.VotesApi/WeatherForecast.cs
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
namespace Ellie.VotesApi
|
||||||
|
{
|
||||||
|
public class Vote
|
||||||
|
{
|
||||||
|
public ulong UserId { get; set; }
|
||||||
|
}
|
||||||
|
}
|
10
src/Ellie.VotesApi/appsettings.Development.json
Normal file
10
src/Ellie.VotesApi/appsettings.Development.json
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"DetailedErrors": true,
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft.AspNetCore": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
12
src/Ellie.VotesApi/appsettings.json
Normal file
12
src/Ellie.VotesApi/appsettings.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"Logging": {
|
||||||
|
"LogLevel": {
|
||||||
|
"Default": "Information",
|
||||||
|
"Microsoft": "Warning",
|
||||||
|
"Microsoft.Hosting.Lifetime": "Information"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"DiscordsKey": "my_discords_key",
|
||||||
|
"TopGGKey": "my_topgg_key",
|
||||||
|
"AllowedHosts": "*"
|
||||||
|
}
|
Loading…
Reference in a new issue