diff --git a/src/Nullinside.Api.Common.AspNetCore/Nullinside.Api.Common.AspNetCore.csproj b/src/Nullinside.Api.Common.AspNetCore/Nullinside.Api.Common.AspNetCore.csproj
index 83838e7..1749a67 100644
--- a/src/Nullinside.Api.Common.AspNetCore/Nullinside.Api.Common.AspNetCore.csproj
+++ b/src/Nullinside.Api.Common.AspNetCore/Nullinside.Api.Common.AspNetCore.csproj
@@ -20,8 +20,8 @@
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
+
+
diff --git a/src/Nullinside.Api.Common/Auth/AuthUtils.cs b/src/Nullinside.Api.Common/Auth/AuthUtils.cs
index 58d3fd6..f5c343a 100644
--- a/src/Nullinside.Api.Common/Auth/AuthUtils.cs
+++ b/src/Nullinside.Api.Common/Auth/AuthUtils.cs
@@ -3,7 +3,7 @@
namespace Nullinside.Api.Common.Auth;
///
-/// Random utilities for authentication.
+/// Random utilities for authentication.
///
public static class AuthUtils {
///
diff --git a/src/Nullinside.Api.Common/Desktop/GitHubUpdateManager.cs b/src/Nullinside.Api.Common/Desktop/GitHubUpdateManager.cs
index b3db17d..2b12728 100644
--- a/src/Nullinside.Api.Common/Desktop/GitHubUpdateManager.cs
+++ b/src/Nullinside.Api.Common/Desktop/GitHubUpdateManager.cs
@@ -7,7 +7,7 @@ namespace Nullinside.Api.Common.Desktop;
///
/// Handles checking for updates to the application via GitHub releases.
///
-public class GitHubUpdateManager {
+public static class GitHubUpdateManager {
///
/// Gets the latest version number of the release.
///
diff --git a/src/Nullinside.Api.Common/Docker/DockerProxy.cs b/src/Nullinside.Api.Common/Docker/DockerProxy.cs
index dbab87d..2113312 100644
--- a/src/Nullinside.Api.Common/Docker/DockerProxy.cs
+++ b/src/Nullinside.Api.Common/Docker/DockerProxy.cs
@@ -128,7 +128,14 @@ public async Task TurnOnOffDockerCompose(string name, bool turnOn, Cancell
(!turnOn && output.error.Contains("Stopped", StringComparison.InvariantCultureIgnoreCase));
}
- private async Task<(string output, string error)> ExecuteCommand(string command, CancellationToken token,
+ ///
+ /// Executes an SSH command in the given directory.
+ ///
+ /// The command to execute.
+ /// The cancellation token.
+ /// The directory to navigate to before executing the command.
+ /// The STDOUT and STDERR of the command's execution.
+ private async Task<(string output, string error)> ExecuteCommand(string command, CancellationToken token = new(),
string? dir = null) {
using SshClient client = new(_server!, _username!, _password!);
await client.ConnectAsync(token);
diff --git a/src/Nullinside.Api.Common/Nullinside.Api.Common.csproj b/src/Nullinside.Api.Common/Nullinside.Api.Common.csproj
index 829e3f0..6f1bfc2 100644
--- a/src/Nullinside.Api.Common/Nullinside.Api.Common.csproj
+++ b/src/Nullinside.Api.Common/Nullinside.Api.Common.csproj
@@ -16,10 +16,10 @@
-
-
+
+
-
+
diff --git a/src/Nullinside.Api.Common/SqlScripts.cs b/src/Nullinside.Api.Common/SqlScripts.cs
index 2236603..80299c8 100644
--- a/src/Nullinside.Api.Common/SqlScripts.cs
+++ b/src/Nullinside.Api.Common/SqlScripts.cs
@@ -71,7 +71,7 @@ public static class SqlScripts {
""";
///
- /// The inner function for the . Not meant to be called directly.
+ /// Removes .
///
public const string LEVENSHTEIN_DISTANCE_SEARCH_INNER_FUNCTION_REMOVE =
"""
diff --git a/src/Nullinside.Api.Common/Twitch/ITwitchApiProxy.cs b/src/Nullinside.Api.Common/Twitch/ITwitchApiProxy.cs
new file mode 100644
index 0000000..f5d651c
--- /dev/null
+++ b/src/Nullinside.Api.Common/Twitch/ITwitchApiProxy.cs
@@ -0,0 +1,107 @@
+using Nullinside.Api.Common.Twitch.Json;
+
+using TwitchLib.Api.Helix.Models.Chat.GetChatters;
+using TwitchLib.Api.Helix.Models.Moderation.BanUser;
+using TwitchLib.Api.Helix.Models.Moderation.GetModerators;
+
+namespace Nullinside.Api.Common.Twitch;
+
+///
+/// The proxy for handling communication with Twitch.
+///
+public interface ITwitchApiProxy {
+ ///
+ /// The Twitch access token. These are the credentials used for all requests.
+ ///
+ TwitchAccessToken? OAuth { get; set; }
+
+ ///
+ /// Creates a new access token from a code using Twitch's OAuth workflow.
+ ///
+ /// The code from twitch to send back to twitch to generate a new access token.
+ /// The cancellation token.
+ /// The object will have its updated with the new settings for the token.
+ /// The OAuth details if successful, null otherwise.
+ Task CreateAccessToken(string code, CancellationToken token = new());
+
+ ///
+ /// Refreshes the access token.
+ ///
+ /// The cancellation token.
+ /// The object will have its updated with the new settings for the token.
+ /// The OAuth details if successful, null otherwise.
+ Task RefreshAccessToken(CancellationToken token = new());
+
+ ///
+ /// Determines if the is valid.
+ ///
+ /// The cancellation token.
+ /// True if valid, false otherwise.
+ Task GetAccessTokenIsValid(CancellationToken token = new());
+
+ ///
+ /// Gets the twitch id and username of the owner of the .
+ ///
+ /// The cancellation token.
+ /// The twitch username if successful, null otherwise.
+ Task<(string? id, string? username)> GetUser(CancellationToken token = new());
+
+ ///
+ /// Gets the email address of the owner of the .
+ ///
+ /// The cancellation token.
+ /// The email address if successful, null otherwise.
+ Task GetUserEmail(CancellationToken token = new());
+
+ ///
+ /// Gets the list of channels the user moderates for.
+ ///
+ /// The twitch id to scan.
+ /// The list of channels the user moderates for.
+ Task> GetUserModChannels(string userId);
+
+ ///
+ /// Bans a list of users from a channel.
+ ///
+ /// The twitch id of the channel to ban the users from.
+ /// The twitch id of the bot user, the one banning the users.
+ /// The list of users to ban.
+ /// The reason for the ban.
+ /// The stopping token.
+ /// The users with confirmed bans.
+ Task> BanChannelUsers(string channelId, string botId,
+ IEnumerable<(string Id, string Username)> users, string reason, CancellationToken token = new());
+
+ ///
+ /// Gets the list of mods for the channel.
+ ///
+ /// The twitch id of the channel to get mods for.
+ /// The cancellation token.
+ /// The collection of moderators.
+ Task> GetChannelMods(string channelId, CancellationToken token = new());
+
+ ///
+ /// Gets the chatters currently in a channel.
+ ///
+ /// The twitch id of the channel that we are moderating.
+ /// The twitch id of the bot.
+ /// The cancellation token.
+ /// The collection of chatters.
+ Task> GetChannelUsers(string channelId, string botId, CancellationToken token = new());
+
+ ///
+ /// Checks if the supplied channels are live.
+ ///
+ /// The twitch ids of the channels.
+ /// The list of twitch channels that are currently live.
+ Task> GetChannelsLive(IEnumerable userIds);
+
+ ///
+ /// Makes a user a moderator in a channel.
+ ///
+ /// The twitch id of the channel to add the mod to.
+ /// The twitch id to give the moderator role.
+ /// The cancellation token.
+ /// True if successful, false otherwise.
+ Task AddChannelMod(string channelId, string userId, CancellationToken token = new());
+}
\ No newline at end of file
diff --git a/src/Nullinside.Api.Common/Twitch/TwitchAccessToken.cs b/src/Nullinside.Api.Common/Twitch/TwitchAccessToken.cs
new file mode 100644
index 0000000..30f317a
--- /dev/null
+++ b/src/Nullinside.Api.Common/Twitch/TwitchAccessToken.cs
@@ -0,0 +1,21 @@
+namespace Nullinside.Api.Common.Twitch;
+
+///
+/// Represents an OAuth token in the Twitch workflow.
+///
+public class TwitchAccessToken {
+ ///
+ /// The Twitch access token.
+ ///
+ public string? AccessToken { get; set; }
+
+ ///
+ /// The refresh token.
+ ///
+ public string? RefreshToken { get; set; }
+
+ ///
+ /// The UTC when the expires.
+ ///
+ public DateTime? ExpiresUtc { get; set; }
+}
\ No newline at end of file
diff --git a/src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs b/src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs
index e5a97cc..e720d30 100644
--- a/src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs
+++ b/src/Nullinside.Api.Common/Twitch/TwitchApiProxy.cs
@@ -20,209 +20,159 @@
namespace Nullinside.Api.Common.Twitch;
///
-/// A proxy for making twitch requests.
+/// The proxy for handling communication with Twitch.
///
-public class TwitchApiProxy {
+public class TwitchApiProxy : ITwitchApiProxy {
///
/// The logger.
///
private static readonly ILog Log = LogManager.GetLogger(typeof(TwitchApiProxy));
- ///
- /// Initializes a new instance of the class.
- ///
- public TwitchApiProxy() {
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The access token.
- /// The refresh token.
- /// When the token expires (utc).
- public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires) {
- AccessToken = token;
- RefreshToken = refreshToken;
- ExpiresUtc = tokenExpires;
- }
-
///
/// The, public, twitch client id.
///
- public static string ClientId { get; } = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_ID")!;
+ private static readonly string ClientId = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_ID")!;
///
/// The, private, twitch client secret.
///
- public static string ClientSecret { get; } = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_SECRET")!;
+ private static readonly string ClientSecret = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_SECRET")!;
///
/// The redirect url.
///
- public static string ClientRedirect { get; } = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_REDIRECT")!;
-
- ///
- /// The Twitch access token.
- ///
- public string? AccessToken { get; set; }
+ private static readonly string ClientRedirect = Environment.GetEnvironmentVariable("TWITCH_BOT_CLIENT_REDIRECT")!;
///
- /// The refresh token.
+ /// Initializes a new instance of the class.
///
- public string? RefreshToken { get; set; }
+ public TwitchApiProxy() {
+ }
///
- /// The UTC when the expires.
+ /// Initializes a new instance of the class.
///
- public DateTime? ExpiresUtc { get; set; }
+ /// The access token.
+ /// The refresh token.
+ /// When the token expires (utc).
+ public TwitchApiProxy(string token, string refreshToken, DateTime tokenExpires) {
+ OAuth = new TwitchAccessToken {
+ AccessToken = token,
+ RefreshToken = refreshToken,
+ ExpiresUtc = tokenExpires
+ };
+ }
///
/// The number of times to retry queries before giving up.
///
public int Retries { get; set; } = 3;
- ///
- /// Gets a new instance of the .
- ///
- /// A new instance of the .
- private TwitchAPI GetApi() {
- var api = new TwitchAPI {
- Settings = {
- ClientId = ClientId,
- AccessToken = AccessToken
- }
+ ///
+ public TwitchAccessToken? OAuth { get; set; }
+
+ ///
+ public async Task CreateAccessToken(string code, CancellationToken token = new()) {
+ TwitchAPI api = GetApi();
+ AuthCodeResponse? response = await api.Auth.GetAccessTokenFromCodeAsync(code, ClientSecret, ClientRedirect);
+ if (null == response) {
+ return null;
+ }
+
+ OAuth = new TwitchAccessToken {
+ AccessToken = response.AccessToken,
+ RefreshToken = response.RefreshToken,
+ ExpiresUtc = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn)
};
- return api;
+ return OAuth;
}
- ///
- /// Refreshes the access token.
- ///
- /// The cancellation token.
- /// The object will have it's properties updated with the new settings for the token.
- /// True if successful, false otherwise.
- public async Task RefreshTokenAsync(CancellationToken token = new()) {
+ ///
+ public async Task RefreshAccessToken(CancellationToken token = new()) {
try {
TwitchAPI api = GetApi();
- RefreshResponse? response = await api.Auth.RefreshAuthTokenAsync(RefreshToken, ClientSecret, ClientId);
+ RefreshResponse? response = await api.Auth.RefreshAuthTokenAsync(OAuth?.RefreshToken, ClientSecret, ClientId);
if (null == response) {
- return false;
+ return null;
}
- AccessToken = response.AccessToken;
- RefreshToken = response.RefreshToken;
- ExpiresUtc = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn);
- return true;
+ OAuth = new TwitchAccessToken {
+ AccessToken = response.AccessToken,
+ RefreshToken = response.RefreshToken,
+ ExpiresUtc = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn)
+ };
+ return OAuth;
}
catch (Exception) {
- return false;
+ return null;
}
}
- ///
- /// Gets a new access token.
- ///
- /// The code to send to twitch to generate a new access token.
- /// The cancellation token.
- /// The object will have it's properties updated with the new settings for the token.
- /// True if successful, false otherwise.
- public async Task GetAccessToken(string code, CancellationToken token = new()) {
- TwitchAPI api = GetApi();
- AuthCodeResponse? response = await api.Auth.GetAccessTokenFromCodeAsync(code, ClientSecret, ClientRedirect);
- if (null == response) {
- return false;
- }
-
- AccessToken = response.AccessToken;
- RefreshToken = response.RefreshToken;
- ExpiresUtc = DateTime.UtcNow + TimeSpan.FromSeconds(response.ExpiresIn);
- return true;
+ ///
+ public async Task GetAccessTokenIsValid(CancellationToken token = new()) {
+ return !string.IsNullOrWhiteSpace((await GetUser(token)).id);
}
- ///
- /// Gets the email address of the owner of the .
- ///
- /// The cancellation token.
- /// The email address if successful, null otherwise.
- public async Task GetUserEmail(CancellationToken token = new()) {
+ ///
+ public async Task<(string? id, string? username)> GetUser(CancellationToken token = new()) {
return await Retry.Execute(async () => {
TwitchAPI api = GetApi();
GetUsersResponse? response = await api.Helix.Users.GetUsersAsync();
if (null == response) {
- return null;
+ return (null, null);
}
- return response.Users.FirstOrDefault()?.Email;
+ User? user = response.Users.FirstOrDefault();
+ return (user?.Id, user?.Login);
}, Retries, token);
}
- ///
- /// Gets the twitch username of the owner of the .
- ///
- /// The cancellation token.
- /// The twitch username if successful, null otherwise.
- public async Task<(string? id, string? username)> GetTwitchUser(CancellationToken token = new()) {
+ ///
+ public async Task GetUserEmail(CancellationToken token = new()) {
return await Retry.Execute(async () => {
TwitchAPI api = GetApi();
GetUsersResponse? response = await api.Helix.Users.GetUsersAsync();
if (null == response) {
- return (null, null);
+ return null;
}
- User? user = response.Users.FirstOrDefault();
- return (user?.Id, user?.Login);
+ return response.Users.FirstOrDefault()?.Email;
}, Retries, token);
}
- ///
- /// Determines if the API has valid credentials.
- ///
- /// The cancellation token.
- /// True if successful, null otherwise.
- public async Task IsValid(CancellationToken token = new()) {
- return !string.IsNullOrWhiteSpace((await GetTwitchUser(token)).id);
- }
+ ///
+ public async Task> GetUserModChannels(string userId) {
+ using var client = new HttpClient();
- ///
- /// Gets the chatters in a channel.
- ///
- /// The id of the channel that we are moderating.
- /// The id of the bot channel.
- /// The cancellation token.
- /// The collection of chatters.
- public async Task> GetChattersInChannel(string channelId, string botId,
- CancellationToken token = new()) {
- return await Retry.Execute(async () => {
- TwitchAPI api = GetApi();
- var chatters = new List();
- string? cursor = null;
- int total = 0;
- do {
- GetChattersResponse? response = await api.Helix.Chat.GetChattersAsync(channelId, botId, 1000, cursor);
- if (null == response) {
- break;
- }
+ var ret = new List();
+ string? cursor = null;
+ do {
+ string url = $"https://api.twitch.tv/helix/moderation/channels?user_id={userId}&first=100";
+ if (null != cursor) {
+ url += $"&after={cursor}";
+ }
- chatters.AddRange(response.Data);
- cursor = response.Pagination.Cursor;
- total = response.Total;
- } while (null != cursor);
+ var request = new HttpRequestMessage(HttpMethod.Get, url);
+ request.Headers.Add("Authorization", $"Bearer {OAuth?.AccessToken}");
+ request.Headers.Add("Client-Id", ClientId);
- Debug.Assert(chatters.Count == total);
- return chatters;
- }, Retries, token);
+ using HttpResponseMessage response = await client.SendAsync(request);
+ response.EnsureSuccessStatusCode();
+ string responseBody = await response.Content.ReadAsStringAsync();
+ var moderatedChannels = JsonConvert.DeserializeObject(responseBody);
+ if (null == moderatedChannels) {
+ break;
+ }
+
+ ret.AddRange(moderatedChannels.data);
+ cursor = moderatedChannels.pagination.cursor;
+ } while (null != cursor);
+
+ return ret;
}
- ///
- /// Bans a list of users.
- ///
- /// The twitch account id of the channel to ban the users from.
- /// The twitch account id of the bot user.
- /// The list of users to ban.
- /// The reason for the ban.
- /// The stopping token.
- /// The users with confirmed bans.
- public async Task> BanUsers(string channelId, string botId,
+ ///
+ public async Task> BanChannelUsers(string channelId, string botId,
IEnumerable<(string Id, string Username)> users, string reason, CancellationToken token = new()) {
return await Retry.Execute(async () => {
TwitchAPI api = GetApi();
@@ -255,13 +205,56 @@ public async Task> BanUsers(string channelId, string bot
}, Retries, token);
}
- ///
- /// Gets the list of mods for the channel.
- ///
- /// The twitch id of the channel to get mods for.
- /// The cancellation token.
- /// The collection of moderators.
- public async Task> GetMods(string channelId, CancellationToken token = new()) {
+ ///
+ public async Task> GetChannelUsers(string channelId, string botId,
+ CancellationToken token = new()) {
+ return await Retry.Execute(async () => {
+ TwitchAPI api = GetApi();
+ var chatters = new List();
+ string? cursor = null;
+ int total = 0;
+ do {
+ GetChattersResponse? response = await api.Helix.Chat.GetChattersAsync(channelId, botId, 1000, cursor);
+ if (null == response) {
+ break;
+ }
+
+ chatters.AddRange(response.Data);
+ cursor = response.Pagination.Cursor;
+ total = response.Total;
+ } while (null != cursor);
+
+ Debug.Assert(chatters.Count == total);
+ return chatters;
+ }, Retries, token);
+ }
+
+ ///
+ public async Task> GetChannelsLive(IEnumerable userIds) {
+ TwitchAPI api = GetApi();
+
+ // We can only query 100 at a time, so throttle the search.
+ var liveUsers = new List();
+ string[] twitchIdsArray = userIds.ToArray();
+ for (int i = 0; i < twitchIdsArray.Length; i += 100) {
+ int lastIndex = i + 100;
+ if (lastIndex > twitchIdsArray.Length) {
+ lastIndex = twitchIdsArray.Length;
+ }
+
+ GetStreamsResponse? response =
+ await api.Helix.Streams.GetStreamsAsync(userIds: twitchIdsArray[i..lastIndex].ToList());
+ if (null != response) {
+ liveUsers.AddRange(response.Streams.Where(s =>
+ "live".Equals(s.Type, StringComparison.InvariantCultureIgnoreCase)));
+ }
+ }
+
+ return liveUsers.Select(l => l.UserId);
+ }
+
+ ///
+ public async Task> GetChannelMods(string channelId, CancellationToken token = new()) {
return await Retry.Execute(async () => {
TwitchAPI api = GetApi();
@@ -287,14 +280,8 @@ public async Task> BanUsers(string channelId, string bot
}, Retries, token);
}
- ///
- /// Makes the bot account a mod.
- ///
- /// The twitch id of the channel to add the mod to.
- /// The twitch user id to mod.
- /// The cancellation token.
- /// True if successful, false otherwise.
- public async Task ModAccount(string channelId, string userId, CancellationToken token = new()) {
+ ///
+ public async Task AddChannelMod(string channelId, string userId, CancellationToken token = new()) {
return await Retry.Execute(async () => {
TwitchAPI api = GetApi();
await api.Helix.Moderation.AddChannelModeratorAsync(channelId, userId);
@@ -303,65 +290,16 @@ public async Task> BanUsers(string channelId, string bot
}
///
- /// Gets the list of channels the supplied user moderates for.
- ///
- /// The user id to scan.
- /// The list of channels the supplied user moderates for.
- public async Task> GetChannelsWeMod(string userId) {
- using var client = new HttpClient();
-
- var ret = new List();
- string? cursor = null;
- do {
- string url = $"https://api.twitch.tv/helix/moderation/channels?user_id={userId}&first=100";
- if (null != cursor) {
- url += $"&after={cursor}";
- }
-
- var request = new HttpRequestMessage(HttpMethod.Get, url);
- request.Headers.Add("Authorization", $"Bearer {AccessToken}");
- request.Headers.Add("Client-Id", ClientId);
-
- using HttpResponseMessage response = await client.SendAsync(request);
- response.EnsureSuccessStatusCode();
- string responseBody = await response.Content.ReadAsStringAsync();
- var moderatedChannels = JsonConvert.DeserializeObject(responseBody);
- if (null == moderatedChannels) {
- break;
- }
-
- ret.AddRange(moderatedChannels.data);
- cursor = moderatedChannels.pagination.cursor;
- } while (null != cursor);
-
- return ret;
- }
-
- ///
- /// Checks if the supplied channels are live.
+ /// Gets a new instance of the .
///
- /// The twitch user ids.
- /// The list of twitch users that are currently live.
- public async Task> GetLiveChannels(IEnumerable userIds) {
- TwitchAPI api = GetApi();
-
- // We can only query 100 at a time, so throttle the search.
- var liveUsers = new List();
- string[] twitchIdsArray = userIds.ToArray();
- for (int i = 0; i < twitchIdsArray.Length; i += 100) {
- int lastIndex = i + 100;
- if (lastIndex > twitchIdsArray.Length) {
- lastIndex = twitchIdsArray.Length;
- }
-
- GetStreamsResponse? response =
- await api.Helix.Streams.GetStreamsAsync(userIds: twitchIdsArray[i..lastIndex].ToList());
- if (null != response) {
- liveUsers.AddRange(response.Streams.Where(s =>
- "live".Equals(s.Type, StringComparison.InvariantCultureIgnoreCase)));
+ /// A new instance of the .
+ private TwitchAPI GetApi() {
+ var api = new TwitchAPI {
+ Settings = {
+ ClientId = ClientId,
+ AccessToken = OAuth?.AccessToken
}
- }
-
- return liveUsers.Select(l => l.UserId);
+ };
+ return api;
}
}
\ No newline at end of file
diff --git a/src/Nullinside.Api.Model/INullinsideContext.cs b/src/Nullinside.Api.Model/INullinsideContext.cs
index c3a7d10..f75cacf 100644
--- a/src/Nullinside.Api.Model/INullinsideContext.cs
+++ b/src/Nullinside.Api.Model/INullinsideContext.cs
@@ -6,7 +6,7 @@
namespace Nullinside.Api.Model;
///
-/// Represents the nullinside database.
+/// Represents the nullinside database.
///
public interface INullinsideContext : IAsyncDisposable {
///
@@ -60,20 +60,20 @@ public interface INullinsideContext : IAsyncDisposable {
DatabaseFacade Database { get; }
///
- /// Saves all changes made in this context to the database.
+ /// Saves all changes made in this context to the database.
///
/// A to observe while waiting for the task to complete.
///
- /// A task that represents the asynchronous save operation. The task result contains the
- /// number of state entries written to the database.
+ /// A task that represents the asynchronous save operation. The task result contains the
+ /// number of state entries written to the database.
///
Task SaveChangesAsync(CancellationToken cancellationToken = default);
-
+
///
- /// Saves all changes made in this context to the database.
+ /// Saves all changes made in this context to the database.
///
///
- /// The number of state entries written to the database.
+ /// The number of state entries written to the database.
///
int SaveChanges();
}
\ No newline at end of file
diff --git a/src/Nullinside.Api.Model/Shared/UserHelpers.cs b/src/Nullinside.Api.Model/Shared/UserHelpers.cs
index e1a9f36..9a6eb70 100644
--- a/src/Nullinside.Api.Model/Shared/UserHelpers.cs
+++ b/src/Nullinside.Api.Model/Shared/UserHelpers.cs
@@ -1,5 +1,3 @@
-using System.Security.Cryptography;
-
using Microsoft.EntityFrameworkCore;
using Nullinside.Api.Common;
diff --git a/src/Nullinside.Api/Controllers/TwitchBotController.cs b/src/Nullinside.Api/Controllers/TwitchBotController.cs
index 8219839..38b0a62 100644
--- a/src/Nullinside.Api/Controllers/TwitchBotController.cs
+++ b/src/Nullinside.Api/Controllers/TwitchBotController.cs
@@ -46,6 +46,7 @@ public TwitchBotController(IConfiguration configuration, INullinsideContext dbCo
/// redirects users back to the nullinside website.
///
/// The credentials provided by twitch.
+ /// The twitch api.
/// The cancellation token.
///
/// A redirect to the nullinside website.
@@ -57,10 +58,10 @@ public TwitchBotController(IConfiguration configuration, INullinsideContext dbCo
[AllowAnonymous]
[HttpGet]
[Route("login")]
- public async Task TwitchLogin([FromQuery] string code, CancellationToken token) {
+ public async Task TwitchLogin([FromQuery] string code, [FromServices] ITwitchApiProxy api,
+ CancellationToken token) {
string? siteUrl = _configuration.GetValue("Api:SiteUrl");
- var api = new TwitchApiProxy();
- if (!await api.GetAccessToken(code, token)) {
+ if (null == await api.CreateAccessToken(code, token)) {
return Redirect($"{siteUrl}/twitch-bot/config?error={TwitchBotLoginErrors.TwitchErrorWithToken}");
}
@@ -69,13 +70,13 @@ public async Task TwitchLogin([FromQuery] string code, Cancellati
return Redirect($"{siteUrl}/twitch-bot/config?error={TwitchBotLoginErrors.TwitchAccountHasNoEmail}");
}
- (string? id, string? username) user = await api.GetTwitchUser(token);
+ (string? id, string? username) user = await api.GetUser(token);
if (string.IsNullOrWhiteSpace(user.username) || string.IsNullOrWhiteSpace(user.id)) {
return Redirect($"{siteUrl}/twitch-bot/config?error={TwitchBotLoginErrors.InternalError}");
}
- string? bearerToken = await UserHelpers.GetTokenAndSaveToDatabase(_dbContext, email, token, api.AccessToken,
- api.RefreshToken, api.ExpiresUtc, user.username, user.id);
+ string? bearerToken = await UserHelpers.GetTokenAndSaveToDatabase(_dbContext, email, token, api.OAuth?.AccessToken,
+ api.OAuth?.RefreshToken, api.OAuth?.ExpiresUtc, user.username, user.id);
if (string.IsNullOrWhiteSpace(bearerToken)) {
return Redirect($"{siteUrl}/twitch-bot/config?error={TwitchBotLoginErrors.InternalError}");
}
diff --git a/src/Nullinside.Api/Controllers/UserController.cs b/src/Nullinside.Api/Controllers/UserController.cs
index b38fcc2..50e2780 100644
--- a/src/Nullinside.Api/Controllers/UserController.cs
+++ b/src/Nullinside.Api/Controllers/UserController.cs
@@ -83,6 +83,7 @@ public async Task Login([FromForm] GoogleOpenIdToken creds, Cance
/// redirects users back to the nullinside website.
///
/// The credentials provided by twitch.
+ /// The twitch api.
/// The cancellation token.
///
/// A redirect to the nullinside website.
@@ -94,10 +95,10 @@ public async Task Login([FromForm] GoogleOpenIdToken creds, Cance
[AllowAnonymous]
[HttpGet]
[Route("twitch-login")]
- public async Task TwitchLogin([FromQuery] string code, CancellationToken token) {
+ public async Task TwitchLogin([FromQuery] string code, [FromServices] ITwitchApiProxy api,
+ CancellationToken token) {
string? siteUrl = _configuration.GetValue("Api:SiteUrl");
- var api = new TwitchApiProxy();
- if (!await api.GetAccessToken(code, token)) {
+ if (null == await api.CreateAccessToken(code, token)) {
return Redirect($"{siteUrl}/user/login?error=3");
}
diff --git a/src/Nullinside.Api/Nullinside.Api.csproj b/src/Nullinside.Api/Nullinside.Api.csproj
index 5533643..a5a12bb 100644
--- a/src/Nullinside.Api/Nullinside.Api.csproj
+++ b/src/Nullinside.Api/Nullinside.Api.csproj
@@ -21,16 +21,16 @@
-
+
runtime; build; native; contentfiles; analyzers; buildtransitive
all
-
-
-
+
+
+