Skip to content

Commit

Permalink
Added GitHub linkage
Browse files Browse the repository at this point in the history
  • Loading branch information
Aragas committed Dec 26, 2023
1 parent 26188c2 commit 8154b1c
Show file tree
Hide file tree
Showing 28 changed files with 2,439 additions and 0 deletions.
1 change: 1 addition & 0 deletions src/BUTR.Site.NexusMods.Client/Models/DemoUser.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public static class DemoUser
steamUserId: null,
gogUserId: null,
discordUserId: null,
gitHubUserId: null,
hasTenantGame: true,
availableTenants: new List<ProfileTenantModel> { new(tenantId: 1, name: "Bannerlord") });
private static readonly List<NexusModsModModel> _mods = new()
Expand Down
26 changes: 26 additions & 0 deletions src/BUTR.Site.NexusMods.Client/Models/GitHubUserInfo2.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
using BUTR.Site.NexusMods.ServerClient;

namespace BUTR.Site.NexusMods.Client.Models
{
public sealed record GitHubUserInfo2
{
public string Url { get; init; }
public string Name { get; init; }
public bool NeedsRelink { get; init; }

public GitHubUserInfo2(GitHubUserInfo? userInfo)
{
if (userInfo is null)
{
NeedsRelink = true;
Url = string.Empty;
Name = string.Empty;
}
else
{
Url = $"https://github.com/{userInfo.Login}";
Name = userInfo.Login;
}
}
};
}
33 changes: 33 additions & 0 deletions src/BUTR.Site.NexusMods.Client/Pages/Basic/Profile.razor
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
@inject IDiscordClient _discordClient
@inject ISteamClient _steamClient
@inject IGOGClient _gogClient
@inject IGitHubClient _gitHubClient

@if (_user is null)
{
Expand Down Expand Up @@ -90,6 +91,28 @@ else
<Column ColumnSize="@ColumnSize.Is9" TextColor="@TextColor.Secondary">@user.Email</Column>
</Row>
<Divider/>
<Row>
<Column ColumnSize="@ColumnSize.Is3" TextWeight="@TextWeight.Bold">GitHub</Column>
<Column ColumnSize="@ColumnSize.Is9" TextColor="@TextColor.Secondary">
@if (_user!.GitHubUserId is not null && _gitHubUserInfo is null)
{
<Text>...loading</Text>
}
else if (_user.GitHubUserId is not null && _gitHubUserInfo!.NeedsRelink)
{
<Anchor Style="text-decoration: none" To="github-linked-role" Target="@Target.Blank">Needs Relinking</Anchor>
}
else if (_user.GitHubUserId is not null)
{
<Anchor Style="text-decoration: none" To="@_gitHubUserInfo!.Url" Target="@Target.Blank">@_gitHubUserInfo.Name</Anchor>
}
else
{
<Anchor Style="text-decoration: none" To="github-linked-role" Target="@Target.Blank">Not Linked</Anchor>
}
</Column>
</Row>
<Divider/>
<Row>
<Column ColumnSize="@ColumnSize.Is3" TextWeight="@TextWeight.Bold">Discord</Column>
<Column ColumnSize="@ColumnSize.Is9" TextColor="@TextColor.Secondary">
Expand Down Expand Up @@ -159,6 +182,7 @@ else
</Card>;

private ProfileModel? _user;
private GitHubUserInfo2? _gitHubUserInfo;
private DiscordUserInfo2? _discordUser;
private SteamUserInfo2? _steamUser;
private GOGUserInfo2? _gogUser;
Expand All @@ -172,6 +196,15 @@ else
var userResponse = await _userClient.ProfileAsync();
_user = userResponse.Value;

if (_user?.GitHubUserId is not null)
{
_ = _gitHubClient.GetUserInfoAsync().ContinueWith(x =>
{
_gitHubUserInfo = new(x.Result.Value);
StateHasChanged();
});
}

if (_user?.DiscordUserId is not null)
{
Expand Down
25 changes: 25 additions & 0 deletions src/BUTR.Site.NexusMods.Client/Pages/GitHub/LinkedRole.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
@attribute [Authorize]
@page "/github-linked-role"

@inject IGitHubClient _gitHubClient;
@inject ILocalStorageService _localStorage;
@inject NavigationManager _navigationManager;

@code {

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

var response = await _gitHubClient.GetOAuthUrlAsync();
if (response.Value?.Url is null)
{
_navigationManager.NavigateTo("profile");
return;
}

await _localStorage.SetItemAsync("github_state", response.Value.State);
_navigationManager.NavigateTo(response.Value.Url);
}

}
127 changes: 127 additions & 0 deletions src/BUTR.Site.NexusMods.Client/Pages/GitHub/OAuthCallback.razor
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
@attribute [Authorize]
@page "/github-oauth-callback"

@inject NavigationManager _navigationManager;
@inject ILocalStorageService _localStorage;
@inject AuthenticationProvider _authenticationProvider;
@inject IGitHubClient _gitHubClient;

<Container>
<Row Flex="@Flex.JustifyContent.Center">
<Column ColumnSize="@ColumnSize.Is7.OnWidescreen.IsAuto.OnDesktop">
<Card Margin="@Margin.Is5.OnDesktop.Is4.OnTablet.Is3.OnMobile" Border="@Border.Is0.Rounded" Shadow="@Shadow.Small">
<CardBody>
<Heading Padding="@Padding.Is3" Size="@HeadingSize.Is5" TextAlignment="@TextAlignment.Center" TextTransform="@TextTransform.Uppercase" TextWeight="@TextWeight.Bold">@_status</Heading>
<Divider/>
@if (_userInfo is not null)
{
<Row Margin="@Margin.Is2">
<Span><Anchor Style="text-decoration: none" To="@_userInfo.Url" Target="@Target.Blank">@_userInfo.Name</Anchor> was successfully linked with the BUTR Site!</Span>
</Row>
}
else
{
<Row Margin="@Margin.Is2">
<Spinner/>
</Row>
}

@if (!string.IsNullOrEmpty(_message))
{
<Row Margin="@Margin.Is2">
<Span>@_message</Span>
</Row>
}

@if (!string.IsNullOrEmpty(_image))
{
<Row Margin="@Margin.Is2">
<FigureImage Margin="@Margin.Is0" Source="@_image" AlternateText="A meme image displaying success or failure. Success is Brent Rambo giving a thumbs up. Failure is a horse failing to play with a gymnastics by kinda lying onto it ball and falling."></FigureImage>
</Row>
}

<Row Margin="@Margin.Is2">
<Span>Use the "Linked Roles" option in servers with the BUTR Discord bot to claim your roles.</Span>
</Row>
<Row Margin="@Margin.Is2">
<Button Border="@Border.RoundedPill"
TextTransform="@TextTransform.Uppercase"
Padding="@Padding.Is2"
TextWeight="@TextWeight.Bold"
Color="@Color.Primary"
Type="@ButtonType.Link"
To="discord://-/"
rel="noreferrer"
Target="@Target.Blank">
<Figure Margin="@Margin.Is0" Size="@FigureSize.Is32x32">
<FigureImage Margin="@Margin.Is0" AlternateText="Discord app icon." Source="images/discord.svg"/>
</Figure>
Back to Discord
</Button>
</Row>
</CardBody>
</Card>
</Column>
</Row>
</Container>

@code {

private const string Success = "images/success.gif";
private const string Failure = "images/failure.gif";

private string _status = string.Empty;
private string _message = string.Empty;
private string _image = string.Empty;
private GitHubUserInfo2? _userInfo;

protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();

if (!await _localStorage.ContainKeyAsync("github_state"))
{
_status = "FAILURE";
_message = "State verification failed.";
_image = Failure;
return;
}

var queries = _navigationManager.QueryString();
var queryStatRaw = queries["state"];
var queryCode = queries["code"];

try
{
var state = await _localStorage.GetItemAsync<Guid>("github_state");
if (!Guid.TryParse(queryStatRaw, out var queryState) || state != queryState)
{
_status = "FAILURE";
_message = "State verification failed.";
_image = Failure;
return;
}

await _gitHubClient.LinkAsync(code: queryCode);
_ = await _authenticationProvider.ValidateAsync();

if (await _gitHubClient.GetUserInfoAsync() is { Value: var userInfo })
{
_userInfo = new GitHubUserInfo2(userInfo);
_status = "SUCCESS";
_image = Success;
}
else
{
_status = "FAILURE";
_message = "Failed to link!";
_image = Failure;
}
}
finally
{
await _localStorage.RemoveItemAsync("github_state");
}
}

}
1 change: 1 addition & 0 deletions src/BUTR.Site.NexusMods.Client/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@ public static WebAssemblyHostBuilder CreateHostBuilder(string[] args)
services.AddTransient<IStatisticsClient, StatisticsClient>(sp => ConfigureClient(sp, (http, opt) => new StatisticsClient(http, opt)));
services.AddTransient<IQuartzClient, QuartzClient>(sp => ConfigureClient(sp, (http, opt) => new QuartzClient(http, opt)));
services.AddTransient<IRecreateStacktraceClient, RecreateStacktraceClient>(sp => ConfigureClient(sp, (http, opt) => new RecreateStacktraceClient(http, opt)));
services.AddTransient<IGitHubClient, GitHubClient>(sp => ConfigureClient(sp, (http, opt) => new GitHubClient(http, opt)));

services.AddScoped<TenantProvider>();

Expand Down
4 changes: 4 additions & 0 deletions src/BUTR.Site.NexusMods.Server/Contexts/BaseAppDbContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,12 @@ public class BaseAppDbContext : DbContext
public DbSet<NexusModsUserToNexusModsModEntity> NexusModsUserToNexusModsMods { get; set; } = default!;
public DbSet<NexusModsUserToModuleEntity> NexusModsUserToModules { get; set; } = default!;

public DbSet<NexusModsUserToIntegrationGitHubEntity> NexusModsUserToGitHub { get; set; } = default!;
public DbSet<NexusModsUserToIntegrationDiscordEntity> NexusModsUserToDiscord { get; set; } = default!;
public DbSet<NexusModsUserToIntegrationGOGEntity> NexusModsUserToGOG { get; set; } = default!;
public DbSet<NexusModsUserToIntegrationSteamEntity> NexusModsUserToSteam { get; set; } = default!;

public DbSet<IntegrationGitHubTokensEntity> IntegrationGitHubTokens { get; set; } = default!;
public DbSet<IntegrationDiscordTokensEntity> IntegrationDiscordTokens { get; set; } = default!;
public DbSet<IntegrationGOGTokensEntity> IntegrationGOGTokens { get; set; } = default!;
public DbSet<IntegrationGOGToOwnedTenantEntity> IntegrationGOGToOwnedTenants { get; set; } = default!;
Expand Down Expand Up @@ -96,6 +98,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

_entityConfigurationFactory.ApplyConfigurationWithTenant<ExceptionTypeEntity>(modelBuilder);

_entityConfigurationFactory.ApplyConfiguration<IntegrationGitHubTokensEntity>(modelBuilder);
_entityConfigurationFactory.ApplyConfiguration<IntegrationDiscordTokensEntity>(modelBuilder);
_entityConfigurationFactory.ApplyConfiguration<IntegrationGOGTokensEntity>(modelBuilder);
_entityConfigurationFactory.ApplyConfiguration<IntegrationGOGToOwnedTenantEntity>(modelBuilder);
Expand All @@ -115,6 +118,7 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)

_entityConfigurationFactory.ApplyConfiguration<NexusModsUserEntity>(modelBuilder);
_entityConfigurationFactory.ApplyConfigurationWithTenant<NexusModsUserToCrashReportEntity>(modelBuilder);
_entityConfigurationFactory.ApplyConfiguration<NexusModsUserToIntegrationGitHubEntity>(modelBuilder);
_entityConfigurationFactory.ApplyConfiguration<NexusModsUserToIntegrationDiscordEntity>(modelBuilder);
_entityConfigurationFactory.ApplyConfiguration<NexusModsUserToIntegrationGOGEntity>(modelBuilder);
_entityConfigurationFactory.ApplyConfigurationWithTenant<NexusModsUserToModuleEntity>(modelBuilder);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
using BUTR.Site.NexusMods.Server.Models;
using BUTR.Site.NexusMods.Server.Models.Database;

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace BUTR.Site.NexusMods.Server.Contexts.Configs;

public class IntegrationGitHubTokensEntityConfiguration : BaseEntityConfiguration<IntegrationGitHubTokensEntity>
{
protected override void ConfigureModel(EntityTypeBuilder<IntegrationGitHubTokensEntity> builder)
{
builder.Property<NexusModsUserId>(nameof(NexusModsUserEntity.NexusModsUserId)).HasColumnName("integration_github_tokens_id").HasValueObjectConversion().ValueGeneratedNever();
builder.Property(x => x.GitHubUserId).HasColumnName("github_user_id");
builder.Property(x => x.AccessToken).HasColumnName("access_token");
builder.ToTable("integration_github_tokens", "integration").HasKey(nameof(NexusModsUserEntity.NexusModsUserId));

builder.HasOne(x => x.NexusModsUser)
.WithOne()
.HasForeignKey<IntegrationGitHubTokensEntity>(nameof(NexusModsUserEntity.NexusModsUserId))
.HasPrincipalKey<NexusModsUserEntity>(x => x.NexusModsUserId)
.OnDelete(DeleteBehavior.Cascade);

builder.HasOne(x => x.UserToGitHub)
.WithOne(x => x.ToTokens)
.HasForeignKey<IntegrationGitHubTokensEntity>(x => x.GitHubUserId)
.HasPrincipalKey<NexusModsUserToIntegrationGitHubEntity>(x => x.GitHubUserId)
.OnDelete(DeleteBehavior.Cascade);

builder.Navigation(x => x.NexusModsUser).AutoInclude();

base.ConfigureModel(builder);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
using BUTR.Site.NexusMods.Server.Models;
using BUTR.Site.NexusMods.Server.Models.Database;

using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;

namespace BUTR.Site.NexusMods.Server.Contexts.Configs;

public class NexusModsUserToIntegrationGitHubEntityConfiguration : BaseEntityConfiguration<NexusModsUserToIntegrationGitHubEntity>
{
protected override void ConfigureModel(EntityTypeBuilder<NexusModsUserToIntegrationGitHubEntity> builder)
{
builder.Property<NexusModsUserId>(nameof(NexusModsUserEntity.NexusModsUserId)).HasColumnName("nexusmods_user_to_github_id").HasValueObjectConversion().ValueGeneratedNever();
builder.Property(x => x.GitHubUserId).HasColumnName("github_user_id");
builder.ToTable("nexusmods_user_to_integration_github", "nexusmods_user").HasKey(nameof(NexusModsUserEntity.NexusModsUserId));

builder.HasOne(x => x.NexusModsUser)
.WithOne(x => x.ToGitHub)
.HasForeignKey<NexusModsUserToIntegrationGitHubEntity>(nameof(NexusModsUserEntity.NexusModsUserId))
.HasPrincipalKey<NexusModsUserEntity>(x => x.NexusModsUserId)
.OnDelete(DeleteBehavior.Cascade);

builder.Navigation(x => x.NexusModsUser).AutoInclude();

base.ConfigureModel(builder);
}
}
Loading

0 comments on commit 8154b1c

Please sign in to comment.