Skip to content

Commit

Permalink
Merge pull request #1936 from tgstation/graphql
Browse files Browse the repository at this point in the history
More GraphQL Work
  • Loading branch information
Cyberboss authored Sep 26, 2024
2 parents f5a4458 + a979c49 commit 61a7844
Show file tree
Hide file tree
Showing 178 changed files with 8,128 additions and 1,743 deletions.
6 changes: 5 additions & 1 deletion .github/workflows/ci-pipeline.yml
Original file line number Diff line number Diff line change
Expand Up @@ -578,7 +578,6 @@ jobs:
configuration: ["Debug", "Release"]
env:
TGS_TELEMETRY_KEY_FILE: C:/tgs_telemetry_key.txt
TGS_TEST_GRAPHQL: true
runs-on: windows-latest
steps:
- name: Setup dotnet
Expand Down Expand Up @@ -627,6 +626,7 @@ jobs:
run: |
echo "TGS_TEST_DATABASE_TYPE=PostgresSql" >> $GITHUB_ENV
echo "TGS_TEST_CONNECTION_STRING=Application Name=tgstation-server;Host=127.0.0.1;Username=$USER;Database=TGS__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
echo "TGS_TEST_GRAPHQL=true" >> $GITHUB_ENV
- name: Setup MariaDB
uses: ankane/setup-mariadb@v1
Expand All @@ -638,6 +638,7 @@ jobs:
run: |
echo "TGS_TEST_DATABASE_TYPE=MariaDB" >> $GITHUB_ENV
echo "TGS_TEST_CONNECTION_STRING=Server=127.0.0.1;uid=root;database=tgs__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
echo "TGS_TEST_GRAPHQL=true" >> $GITHUB_ENV
- name: Setup MySQL
uses: ankane/setup-mysql@v1
Expand All @@ -657,6 +658,7 @@ jobs:
TGS_CONNSTRING_VALUE="Server=(localdb)\MSSQLLocalDB;Encrypt=false;Integrated Security=true;Initial Catalog=TGS_${{ matrix.watchdog-type }}_${{ matrix.configuration }};Application Name=tgstation-server"
echo "TGS_TEST_CONNECTION_STRING=$(echo $TGS_CONNSTRING_VALUE)" >> $GITHUB_ENV
echo "TGS_TEST_DATABASE_TYPE=SqlServer" >> $GITHUB_ENV
echo "TGS_TEST_GRAPHQL=true" >> $GITHUB_ENV
- name: Checkout (Branch)
uses: actions/checkout@v4
Expand Down Expand Up @@ -855,6 +857,7 @@ jobs:
run: |
echo "TGS_TEST_DATABASE_TYPE=Sqlite" >> $GITHUB_ENV
echo "TGS_TEST_CONNECTION_STRING=Data Source=TGS_${{ matrix.watchdog-type }}_${{ matrix.configuration }}.sqlite3;Mode=ReadWriteCreate" >> $GITHUB_ENV
echo "TGS_TEST_GRAPHQL=true" >> $GITHUB_ENV
- name: Set PostgresSql Connection Info
if: ${{ matrix.database-type == 'PostgresSql' }}
Expand All @@ -874,6 +877,7 @@ jobs:
echo "TGS_TEST_DATABASE_TYPE=MySql" >> $GITHUB_ENV
echo "TGS_TEST_CONNECTION_STRING=Server=127.0.0.1;Port=3307;uid=root;pwd=mysql;database=tgs__${{ matrix.watchdog-type }}_${{ matrix.configuration }}" >> $GITHUB_ENV
echo "Database__ServerVersion=5.7.31" >> $GITHUB_ENV
echo "TGS_TEST_GRAPHQL=true" >> $GITHUB_ENV
- name: Set General__UseBasicWatchdog
if: ${{ matrix.watchdog-type == 'Basic' }}
Expand Down
6 changes: 3 additions & 3 deletions build/Version.props
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@
<PropertyGroup>
<TgsCoreVersion>6.10.0</TgsCoreVersion>
<TgsConfigVersion>5.2.0</TgsConfigVersion>
<TgsApiVersion>10.9.0</TgsApiVersion>
<TgsApiVersion>10.10.0</TgsApiVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>15.0.0</TgsApiLibraryVersion>
<TgsClientVersion>18.0.0</TgsClientVersion>
<TgsApiLibraryVersion>16.0.0</TgsApiLibraryVersion>
<TgsClientVersion>19.0.0</TgsClientVersion>
<TgsDmapiVersion>7.3.0</TgsDmapiVersion>
<TgsInteropVersion>5.10.0</TgsInteropVersion>
<TgsHostWatchdogVersion>1.5.0</TgsHostWatchdogVersion>
Expand Down
3 changes: 1 addition & 2 deletions build/analyzers.ruleset
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<?xml version="1.0" encoding="utf-8"?>
<RuleSet Name="myrules" Description="My rule set" ToolsVersion="17.0">
<Rules AnalyzerId="AsyncUsageAnalyzers" RuleNamespace="AsyncUsageAnalyzers">
<Rule Id="UseConfigureAwait" Action="Warning" />
</Rules>
<Rules AnalyzerId="Microsoft.Analyzers.ManagedCodeAnalysis" RuleNamespace="Microsoft.Rules.Managed">
<Rule Id="CA1000" Action="Warning" />
<Rule Id="CA1001" Action="Warning" />
<Rule Id="CA1002" Action="None" />
<Rule Id="CA1003" Action="Warning" />
<Rule Id="CA1004" Action="Warning" />
<Rule Id="CA1005" Action="Warning" />
Expand Down
2 changes: 1 addition & 1 deletion src/Tgstation.Server.Api/ApiHeaders.cs
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ public sealed class ApiHeaders
/// <summary>
/// A <see cref="char"/> <see cref="Array"/> containing the ':' <see cref="char"/>.
/// </summary>
static readonly char[] ColonSeparator = new char[] { ':' };
static readonly char[] ColonSeparator = [':'];

/// <summary>
/// The instance <see cref="EntityId.Id"/> being accessed.
Expand Down
6 changes: 6 additions & 0 deletions src/Tgstation.Server.Api/Models/ErrorCode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -663,5 +663,11 @@ public enum ErrorCode : uint
/// </summary>
[Description("Provided repository username doesn't match the user of the corresponding access token!")]
RepoTokenUsernameMismatch,

/// <summary>
/// Attempted to make a cross swarm server request using the GraphQL API.
/// </summary>
[Description("GraphQL swarm remote gateways not implemented!")]
RemoteGatewaysNotImplemented,
}
}
20 changes: 0 additions & 20 deletions src/Tgstation.Server.Api/Models/Internal/LocalServerInformation.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,6 @@ public abstract class ServerInformationBase
/// Limits the locations instances may be created or attached from.
/// </summary>
[ResponseOptions]
public ICollection<string>? ValidInstancePaths { get; set; }
public List<string>? ValidInstancePaths { get; set; }
}
}
9 changes: 1 addition & 8 deletions src/Tgstation.Server.Api/Models/Internal/SwarmServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models.Internal
/// <summary>
/// Information about a server in the swarm.
/// </summary>
public abstract class SwarmServer : IEquatable<SwarmServer>
public abstract class SwarmServer
{
/// <summary>
/// The public address of the server.
Expand Down Expand Up @@ -47,12 +47,5 @@ protected SwarmServer(SwarmServer copy)
PublicAddress = copy.PublicAddress;
Identifier = copy.Identifier;
}

/// <inheritdoc />
public bool Equals(SwarmServer other)
=> other != null
&& other.Identifier == Identifier
&& other.PublicAddress == PublicAddress
&& other.Address == Address;
}
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,11 @@
using System;

using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Api.Models.Response;

namespace Tgstation.Server.Api.Models.Internal
{
/// <summary>
/// Represents information about a running <see cref="SwarmServer"/>.
/// </summary>
public class SwarmServerInformation : SwarmServer, IEquatable<SwarmServerInformation>
public class SwarmServerInformation : SwarmServer
{
/// <summary>
/// If the <see cref="SwarmServerResponse"/> is the controller.
Expand All @@ -30,10 +28,5 @@ public SwarmServerInformation(SwarmServerInformation copy)
{
Controller = copy.Controller;
}

/// <inheritdoc />
public bool Equals(SwarmServerInformation other)
=> base.Equals(other)
&& other.Controller == Controller;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Models.Response
/// <summary>
/// Represents basic server information.
/// </summary>
public sealed class ServerInformationResponse : Internal.LocalServerInformation
public sealed class ServerInformationResponse : Internal.ServerInformationBase
{
/// <summary>
/// The version of the host.
Expand All @@ -23,6 +23,16 @@ public sealed class ServerInformationResponse : Internal.LocalServerInformation
/// </summary>
public Version? DMApiVersion { get; set; }

/// <summary>
/// If the server is running on a windows operating system.
/// </summary>
public bool WindowsHost { get; set; }

/// <summary>
/// Map of <see cref="OAuthProvider"/> to the <see cref="OAuthProviderInfo"/> for them.
/// </summary>
public Dictionary<OAuthProvider, OAuthProviderInfo>? OAuthProviderInfos { get; set; }

/// <summary>
/// If there is a server update in progress.
/// </summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Tgstation.Server.Api/Models/UserName.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public override string? Name
/// <typeparam name="TResultType">The child of <see cref="UserName"/> to create.</typeparam>
/// <returns>A new <typeparamref name="TResultType"/> copied from <see langword="this"/>.</returns>
protected virtual TResultType CreateUserName<TResultType>()
where TResultType : UserName, new() => new TResultType
where TResultType : UserName, new() => new()
{
Id = Id,
Name = Name,
Expand Down
16 changes: 5 additions & 11 deletions src/Tgstation.Server.Api/Rights/RightsHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,19 +47,13 @@ public static RightsType TypeToRight<TRight>()
/// </summary>
/// <typeparam name="TRight">The <see cref="RightsType"/>.</typeparam>
/// <param name="right">The <typeparamref name="TRight"/>.</param>
/// <returns>A <see cref="string"/> representing the claim role name.</returns>
public static string RoleNames<TRight>(TRight right)
/// <returns>Am <see cref="IEnumerable{T}"/> of <see cref="string"/>s representing the claim role names.</returns>
public static IEnumerable<string> RoleNames<TRight>(TRight right)
where TRight : Enum
{
IEnumerable<string> GetRoleNames()
{
foreach (Enum rightValue in Enum.GetValues(right.GetType()))
if (Convert.ToInt32(rightValue, CultureInfo.InvariantCulture) != 0 && right.HasFlag(rightValue))
yield return String.Concat(typeof(TRight).Name, '.', rightValue.ToString());
}

var names = GetRoleNames();
return String.Join(",", names);
foreach (Enum rightValue in Enum.GetValues(right.GetType()))
if (Convert.ToInt32(rightValue, CultureInfo.InvariantCulture) != 0 && right.HasFlag(rightValue))
yield return String.Concat(typeof(TRight).Name, '.', rightValue.ToString());
}

/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Tgstation.Server.Client.GraphQL/.graphqlrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"transportProfiles": [
{
"default": "Http",
"subscription": "WebSocket"
"subscription": "Http"
}
]
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
using System;
using System.Net.Http.Headers;
using System.Threading.Tasks;

using Microsoft.Extensions.Logging;

using StrawberryShake;

using Tgstation.Server.Common.Extensions;

namespace Tgstation.Server.Client.GraphQL
{
/// <inheritdoc cref="IAuthenticatedGraphQLServerClient" />
sealed class AuthenticatedGraphQLServerClient : GraphQLServerClient, IAuthenticatedGraphQLServerClient
{
/// <inheritdoc />
public ITransferClient TransferClient => restClient!.Transfer;

/// <summary>
/// A <see cref="Func{T, TResult}"/> that takes a bearer token as input and outputs a <see cref="ITransferClient"/> that uses it.
/// </summary>
readonly Func<string, IRestServerClient>? getRestClientForToken;

/// <summary>
/// The current <see cref="IRestServerClient"/>.
/// </summary>
IRestServerClient? restClient;

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticatedGraphQLServerClient"/> class.
/// </summary>
/// <param name="graphQLClient">The <see cref="IGraphQLClient"/> to use.</param>
/// <param name="serviceProvider">The <see cref="IAsyncDisposable"/> to use.</param>
/// <param name="logger">The <see cref="ILogger"/> to use.</param>
/// <param name="restClient">The value of <see cref="restClient"/>.</param>
public AuthenticatedGraphQLServerClient(
IGraphQLClient graphQLClient,
IAsyncDisposable serviceProvider,
ILogger<GraphQLServerClient> logger,
IRestServerClient restClient)
: base(graphQLClient, serviceProvider, logger)
{
this.restClient = restClient ?? throw new ArgumentNullException(nameof(restClient));
}

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticatedGraphQLServerClient"/> class.
/// </summary>
/// <param name="graphQLClient">The <see cref="IGraphQLClient"/> to use.</param>
/// <param name="serviceProvider">The <see cref="IAsyncDisposable"/> to use.</param>
/// <param name="logger">The <see cref="ILogger"/> to use.</param>
/// <param name="setAuthenticationHeader">The <see cref="Action{T}"/> to call to set the async local <see cref="AuthenticationHeaderValue"/> for requests.</param>
/// <param name="basicCredentialsHeader">The basic <see cref="AuthenticationHeaderValue"/> to use for reauthentication.</param>
/// <param name="loginResult">The <see cref="ILoginResult"/> <see cref="IOperationResult{TResultData}"/> containing the initial JWT to use.</param>
/// <param name="getRestClientForToken">The value of <see cref="getRestClientForToken"/>.</param>
public AuthenticatedGraphQLServerClient(
IGraphQLClient graphQLClient,
IAsyncDisposable serviceProvider,
ILogger<GraphQLServerClient> logger,
Action<AuthenticationHeaderValue> setAuthenticationHeader,
AuthenticationHeaderValue? basicCredentialsHeader,
IOperationResult<ILoginResult> loginResult,
Func<string, IRestServerClient> getRestClientForToken)
: base(
graphQLClient,
serviceProvider,
logger,
setAuthenticationHeader,
basicCredentialsHeader,
loginResult)
{
this.getRestClientForToken = getRestClientForToken ?? throw new ArgumentNullException(nameof(getRestClientForToken));
restClient = getRestClientForToken(loginResult.Data!.Login.Bearer!.EncodedToken);
}

/// <inheritdoc />
public sealed override ValueTask DisposeAsync()
#pragma warning disable CA2012 // Use ValueTasks correctly
=> ValueTaskExtensions.WhenAll(
base.DisposeAsync(),
restClient!.DisposeAsync());
#pragma warning restore CA2012 // Use ValueTasks correctly

/// <inheritdoc />
protected sealed override async ValueTask<AuthenticationHeaderValue> CreateUpdatedAuthenticationHeader(string bearer)
{
var baseTask = base.CreateUpdatedAuthenticationHeader(bearer);
if (restClient != null)
await restClient.DisposeAsync().ConfigureAwait(false);

if (getRestClientForToken != null)
restClient = getRestClientForToken(bearer);

return await baseTask.ConfigureAwait(false);
}
}
}
51 changes: 51 additions & 0 deletions src/Tgstation.Server.Client.GraphQL/AuthenticationException.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
using System;

namespace Tgstation.Server.Client.GraphQL
{
/// <summary>
/// <see cref="Exception"/> thrown when automatic <see cref="IGraphQLServerClient"/> authentication fails.
/// </summary>
public sealed class AuthenticationException : Exception
{
/// <summary>
/// The <see cref="ILogin_Login_Errors_ErrorMessageError"/>.
/// </summary>
public ILogin_Login_Errors_ErrorMessageError? ErrorMessage { get; }

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
/// </summary>
/// <param name="errorMessage">The value of <see cref="ErrorMessage"/>.</param>
public AuthenticationException(ILogin_Login_Errors_ErrorMessageError errorMessage)
: base(errorMessage?.Message)
{
ErrorMessage = errorMessage ?? throw new ArgumentNullException(nameof(errorMessage));
}

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
/// </summary>
public AuthenticationException()
{
}

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
/// </summary>
/// <param name="message">The <see cref="Exception.Message"/>.</param>
public AuthenticationException(string message)
: base(message)
{
}

/// <summary>
/// Initializes a new instance of the <see cref="AuthenticationException"/> class.
/// </summary>
/// <param name="message">The <see cref="Exception.Message"/>.</param>
/// <param name="innerException">The <see cref="Exception.InnerException"/>.</param>
public AuthenticationException(string message, Exception innerException)
: base(message, innerException)
{
}
}
}
Loading

0 comments on commit 61a7844

Please sign in to comment.