Merge pull request #1954 from tgstation/GraphQLGitHub [RESTDeploy][GQLDeploy]

Initial GraphQL API release. Also releasing REST API v10.10
This commit is contained in:
Jordan Dominion
2024-10-06 17:54:39 -04:00
committed by GitHub
39 changed files with 423 additions and 117 deletions
+5 -4
View File
@@ -191,7 +191,7 @@ This prevents nesting levels from getting deeper then they need to be.
- Some terminology to help understand the architecture:
- An instance can be thought of as a separate server. It has a separate directory, repository, set of byond installations, etc... The only thing shared amongst instances is API surface, users, global configuration, the active tgstation-server version, and the host machine.
- API refers to the HTTP API unless otherwise specified.
- API refers to the REST API unless otherwise specified.
- The entirety of server functionality resides in the host (Tgstation.Server.Host) project.
- A Component is a service running in tgstation-server to help with instance functionality. These can only be communicated with via the HTTP or DM APIs.
- There is a difference between Watchdog and Host Watchdog. The former monitors DreamDaemon uptime, the latter handles updating tgstation-server.
@@ -247,7 +247,7 @@ Warning: You may need to temporarily set valid MySql credentials in [MySqlDesign
OAuth providers are hardcoded but it is fairly easy to add new ones. The flow doesn't need to be strict OAuth either (r.e. /tg/ forums). Follow the following steps:
1. Add the name to the [Tgstation.Server.Api.Models.OAuthProviders](../src/Tgstation.Server.Api/Models/OAuthProviders.cs) enum (Also necessitates a minor HTTP API version bump).
1. Add the name to the [Tgstation.Server.Api.Models.OAuthProviders](../src/Tgstation.Server.Api/Models/OAuthProviders.cs) enum (Also necessitates a minor API version bump to the HTTP APIs (REST/GraphQL)).
1. Create an implementation of [IOAuthValidator](../src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs).
- Most providers can simply override the [GenericOAuthValidator](../src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs).
1. Construct the implementation in the [OAuthProviders](../src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs) class.
@@ -275,7 +275,8 @@ Major changes should be committed to the `VX` branch created when the time for a
We have several subcomponent APIs we ship with the core server that have their own versions.
- HTTP API
- REST API
- GraphQL API
- DreamMaker API
- Interop API
- Configuration File
@@ -308,7 +309,7 @@ Word commit names descriptively. Only submit work through pull requests (With th
At the time of this writing, the repository is configured to automate much of the deployment/release process.
When the new API, client, or DMAPI is ready to be released, update the `Version.props` file appropriately and merge the pull request with the text `[APIDeploy]`, `[NuGetDeploy]`, or `[DMDeploy]` respectively in the commit message (or all three!). The release will be published automatically.
When the new API, client, or DMAPI is ready to be released, update the `Version.props` file appropriately and merge the pull request with the text `[RESTDeploy]`, `[GQLDeploy]`, `[NugetDeploy]`, or `[DMDeploy]` respectively in the commit message (or all three!). The release will be published automatically.
That step should be taken for the latest API and client before releasing the core version that uses them if applicable.
+1 -1
View File
@@ -16,7 +16,7 @@ They will be amalgamated together in the end.
Categories are used by [the release notes tool](../tools/Tgstation.Server.ReleaseNotes) to generate formatted changelists used in releases.
The default category is Core.
Only one category may be specified for a 🆑 block.
Valid categories are Core, DreamMaker API, HTTP API, Host Watchdog, Web Control Panel, Configuration, Nuget: Api, Nuget: Client, and Nuget: Common.
Valid categories are Core, DreamMaker API, REST API, GraphQL API, Host Watchdog, Web Control Panel, Configuration, Nuget: Api, Nuget: Client, and Nuget: Common.
/🆑
[Why]: # (If this does not close or work on an existing GitHub issue, please add a short description [two lines down] of why you think these changes would benefit the server. If you can't justify it in words, it might not be worth adding.)
+114 -10
View File
@@ -10,7 +10,8 @@
# - Checks commit tags for deployment intents
# - Deploys DreamMaker API zip [DMDeploy] (dev/master)
# - Deploys Nuget Packages [NugetDeploy] (dev/master)
# - Deploys HTTP API swagger.json [APIDeploy] (dev/master)
# - Deploys HTTP API swagger.json [RESTDeploy] (dev/master)
# - Deploys GraphQL API schema.graphql [GQLDeploy] (dev/master)
# - Deploys tgstation-server [TGSDeploy] (master)
# - GitHub Releases: https://github.com/tgstation/tgstation-server/releases
# - Docker: https://hub.docker.com/r/tgstation/server
@@ -741,6 +742,13 @@ jobs:
name: openapi-spec
path: C:/tgs_api.json
- name: Store GraphQL Schema
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Advanced' && matrix.database-type == 'SqlServer' }}
uses: actions/upload-artifact@v4
with:
name: graphql-schema
path: ./artifacts/tgs-api.graphql
- name: Package Server Service
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'Basic' && matrix.database-type == 'PostgresSql' }}
run: |
@@ -1611,11 +1619,11 @@ jobs:
- name: GitHub Requires at Least One Step for a Job
run: exit 0
deploy-http:
name: Deploy HTTP API
deploy-rest:
name: Deploy REST API
needs: deployment-gate
runs-on: windows-latest
if: contains(github.event.head_commit.message, '[APIDeploy]')
if: contains(github.event.head_commit.message, '[RESTDeploy]')
steps:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
@@ -1630,7 +1638,7 @@ jobs:
shell: powershell
run: |
[XML]$versionXML = Get-Content build/Version.props
$apiVersion = $versionXML.Project.PropertyGroup.TgsApiVersion
$apiVersion = $versionXML.Project.PropertyGroup.TgsRestVersion
echo "TGS_API_VERSION=$apiVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
- name: Retrieve OpenAPI Spec
@@ -1661,7 +1669,7 @@ jobs:
- name: Generate Release Notes
env:
TGS_RELEASE_NOTES_TOKEN: ${{ steps.app-token-generation.outputs.token }}
run: dotnet release_notes_bins/Tgstation.Server.ReleaseNotes.dll ${{ env.TGS_API_VERSION }} --httpapi
run: dotnet release_notes_bins/Tgstation.Server.ReleaseNotes.dll ${{ env.TGS_API_VERSION }} --restapi
- name: Create GitHub Release
uses: actions/create-release@v1
@@ -1670,7 +1678,7 @@ jobs:
GITHUB_TOKEN: ${{ steps.app-token-generation.outputs.token }}
with:
tag_name: api-v${{ env.TGS_API_VERSION }}
release_name: tgstation-server API v${{ env.TGS_API_VERSION }}
release_name: tgstation-server REST API v${{ env.TGS_API_VERSION }}
body_path: release_notes.md
commitish: ${{ github.event.head_commit.id }}
@@ -1684,6 +1692,86 @@ jobs:
asset_name: swagger.json
asset_content_type: application/json
deploy-gql:
name: Deploy GraphQL API
needs: deployment-gate
runs-on: windows-latest
if: contains(github.event.head_commit.message, '[GQLDeploy]')
steps:
- name: Setup dotnet
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.TGS_DOTNET_VERSION }}.0.x
dotnet-quality: ${{ env.TGS_DOTNET_QUALITY }}
- name: Checkout
uses: actions/checkout@v4
- name: Parse API version
shell: powershell
run: |
[XML]$versionXML = Get-Content build/Version.props
$apiVersion = $versionXML.Project.PropertyGroup.TgsGraphQLVersion
echo "TGS_API_VERSION=$apiVersion" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
[Version]$parsedVersion = $apiVersion
if ($parsedVersion.Major -eq 0) {
echo "TGS_GRAPHQL_PRERELEASE=true" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
} else {
echo "TGS_GRAPHQL_PRERELEASE=false" | Out-File -FilePath $Env:GITHUB_ENV -Encoding utf8 -Append
}
- name: Retrieve GraphQL Schema
uses: actions/download-artifact@v4
with:
name: graphql-schema
path: schema
- name: Grab Most Recent Changelog
shell: powershell
run: |
$ProgressPreference = 'SilentlyContinue'
Invoke-WebRequest -Uri https://raw.githubusercontent.com/tgstation/tgstation-server/gh-pages/changelog.yml -OutFile changelog.yml
- name: Retrieve ReleaseNotes Binaries
uses: actions/download-artifact@v4
with:
name: release_notes_bins
path: release_notes_bins
- name: Generate App Token
id: app-token-generation
uses: actions/create-github-app-token@v1
with:
app-id: ${{ secrets.APP_ID }}
private-key: ${{ secrets.APP_PRIVATE_KEY }}
- name: Generate Release Notes
env:
TGS_RELEASE_NOTES_TOKEN: ${{ steps.app-token-generation.outputs.token }}
run: dotnet release_notes_bins/Tgstation.Server.ReleaseNotes.dll ${{ env.TGS_API_VERSION }} --graphqlapi
- name: Create GitHub Release
uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ steps.app-token-generation.outputs.token }}
with:
tag_name: graphql-v${{ env.TGS_API_VERSION }}
release_name: tgstation-server GraphQL API v${{ env.TGS_API_VERSION }}
body_path: release_notes.md
commitish: ${{ github.event.head_commit.id }}
prerelease: ${{ env.TGS_GRAPHQL_PRERELEASE }}
- name: Upload GraphQL Schema
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ steps.app-token-generation.outputs.token }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./schema/tgs-api.graphql
asset_name: tgs-api.graphql
asset_content_type: text/plain
deploy-dm:
name: Deploy DreamMaker API
needs: deployment-gate
@@ -1818,7 +1906,7 @@ jobs:
ensure-release:
name: Ensure TGS Release is Latest GitHub Release
needs: [deploy-dm, deploy-http]
needs: [deploy-dm, deploy-rest, deploy-gql]
runs-on: ubuntu-latest
if: (!(cancelled() || failure())) && (!contains(github.event.head_commit.message, '[TGSDeploy]')) && (needs.deploy-dm.result == 'success' || needs.deploy-http.result == 'success')
steps:
@@ -1848,7 +1936,7 @@ jobs:
deploy-tgs:
name: Deploy TGS
needs: [deploy-dm, deploy-http, deployment-gate]
needs: [deploy-dm, deploy-rest, deploy-gql, deployment-gate]
runs-on: windows-latest
if: (!(cancelled() || failure())) && github.event.ref == 'refs/heads/master' && contains(github.event.head_commit.message, '[TGSDeploy]') && needs.deployment-gate.result == 'success'
env:
@@ -1940,6 +2028,12 @@ jobs:
name: openapi-spec
path: swagger
- name: Retrieve GraphQL Schema
uses: actions/download-artifact@v4
with:
name: graphql-schema
path: schema
- name: Retrieve Debian Packaging Archive
uses: actions/download-artifact@v4
with:
@@ -2035,7 +2129,7 @@ jobs:
asset_name: DMAPI.zip
asset_content_type: application/zip
- name: Upload OpenApi Spec Artifact
- name: Upload REST API Artifact
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ steps.app-token-generation.outputs.token }}
@@ -2045,6 +2139,16 @@ jobs:
asset_name: swagger.json
asset_content_type: application/json
- name: Upload GraphQL API Artifact
uses: actions/upload-release-asset@v1
env:
GITHUB_TOKEN: ${{ steps.app-token-generation.outputs.token }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./schema/tgs-api.graphql
asset_name: tgs-api.graphql
asset_content_type: text/plain
- name: Upload Server Update Package Artifact
uses: actions/upload-release-asset@v1
env:
+2 -1
View File
@@ -5,7 +5,8 @@
<PropertyGroup>
<TgsCoreVersion>6.10.0</TgsCoreVersion>
<TgsConfigVersion>5.3.0</TgsConfigVersion>
<TgsApiVersion>10.10.0</TgsApiVersion>
<TgsRestVersion>10.10.0</TgsRestVersion>
<TgsGraphQLVersion>0.1.0</TgsGraphQLVersion>
<TgsCommonLibraryVersion>7.0.0</TgsCommonLibraryVersion>
<TgsApiLibraryVersion>16.0.0</TgsApiLibraryVersion>
<TgsClientVersion>19.0.0</TgsClientVersion>
+1 -1
View File
@@ -5,7 +5,7 @@ This is a series of README.md files aimed at directing people around the codebas
- To explore the server code navigate to [Tgstation.Server.Host](./Tgstation.Server.Host).
- To explore the DreamMaker public API navigate to [DMAPI/tgs.dm](./DMAPI/tgs.dm).
- To explore the DMAPI internals, navigate to [DMAPI/tgs](./DMAPI/tgs).
- To explore the HTTP API definitions navigate to [Tgstation.Server.Api](./Tgstation.Server.Api).
- To explore the REST API definitions navigate to [Tgstation.Server.Api](./Tgstation.Server.Api).
- To explore the C# client code navigate to [Tgstation.Server.Client](./Tgstation.Server.Client).
[Tgstation.Server.Host.Watchdog](./Tgstation.Server.Host.Watchdog), [Tgstation.Server.Host.Service](./Tgstation.Server.Host.Service), and [Tgstation.Server.Host.Console](./Tgstation.Server.Host.Console) are related to the Service/Console runners which have the simple task of executing Tgstation.Server.Host and updating it when requested.
+1 -1
View File
@@ -269,7 +269,7 @@ namespace Tgstation.Server.Api
if (requestHeaders.Headers.TryGetValue(OAuthProviderHeader, out StringValues oauthProviderValues))
{
var oauthProviderString = oauthProviderValues.First();
if (Enum.TryParse<OAuthProvider>(oauthProviderString, out var oauthProvider))
if (Enum.TryParse<OAuthProvider>(oauthProviderString, true, out var oauthProvider))
OAuthProvider = oauthProvider;
else
AddError(HeaderErrorTypes.OAuthProvider, "Invalid OAuth provider!");
@@ -14,7 +14,7 @@
<Target Name="ApplyApiVersionAttribute" BeforeTargets="CoreCompile">
<ItemGroup>
<AssemblyAttributes Include="Tgstation.Server.Api.Properties.ApiVersionAttribute">
<_Parameter1>$(TgsApiVersion)</_Parameter1>
<_Parameter1>$(TgsRestVersion)</_Parameter1>
</AssemblyAttributes>
</ItemGroup>
@@ -1,18 +0,0 @@
query OAuthInformation {
swarm {
currentNode {
gateway {
information {
oAuthProviderInfos {
key
value {
clientId
redirectUri
serverUrl
}
}
}
}
}
}
}
@@ -3,15 +3,7 @@ query UnauthenticatedServerInformation {
currentNode {
gateway {
information {
majorApiVersion
oAuthProviderInfos {
key
value {
clientId
redirectUri
serverUrl
}
}
majorGraphQLApiVersion
}
}
}
@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>$(TgsFrameworkVersion)</TargetFramework>
<Version>$(TgsApiVersion)</Version>
<!-- Opting to not version this because as far as I can tell, it'll only be used locally -->
<Nullable>enable</Nullable>
</PropertyGroup>
@@ -36,12 +36,12 @@ namespace Tgstation.Server.Host.Authority
ValueTask<AuthorityResponse<User>> GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken);
/// <summary>
/// Gets the <see cref="OAuthConnection"/>s for the <see cref="User"/> with a given <paramref name="userId"/>.
/// Gets the <see cref="GraphQL.Types.OAuth.OAuthConnection"/>s for the <see cref="User"/> with a given <paramref name="userId"/>.
/// </summary>
/// <param name="userId">The <see cref="EntityId.Id"/> of the <see cref="User"/>.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an <see cref="global::System.Array"/> of <see cref="GraphQL.Types.OAuthConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
ValueTask<AuthorityResponse<GraphQL.Types.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken);
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in an <see cref="global::System.Array"/> of <see cref="GraphQL.Types.OAuth.OAuthConnection"/> <see cref="AuthorityResponse{TResult}"/>.</returns>
ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken);
/// <summary>
/// Gets all registered <see cref="User"/>s.
@@ -102,7 +102,7 @@ namespace Tgstation.Server.Host.Authority
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> resulting in a <see cref="Dictionary{TKey, TValue}"/> of the requested <see cref="User"/>s.</returns>
[DataLoader]
public static async ValueTask<ILookup<long, GraphQL.Types.OAuthConnection>> GetOAuthConnections(
public static async ValueTask<ILookup<long, GraphQL.Types.OAuth.OAuthConnection>> GetOAuthConnections(
IReadOnlyList<long> userIds,
IDatabaseContext databaseContext,
CancellationToken cancellationToken)
@@ -118,7 +118,7 @@ namespace Tgstation.Server.Host.Authority
return list.ToLookup(
oauthConnection => oauthConnection.UserId,
x => new GraphQL.Types.OAuthConnection(x.ExternalUserId!, x.Provider));
x => new GraphQL.Types.OAuth.OAuthConnection(x.ExternalUserId!, x.Provider));
}
/// <summary>
@@ -285,8 +285,8 @@ namespace Tgstation.Server.Host.Authority
=> Queryable(includeJoins, false);
/// <inheritdoc />
public async ValueTask<AuthorityResponse<GraphQL.Types.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken)
=> new AuthorityResponse<GraphQL.Types.OAuthConnection[]>(
public async ValueTask<AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>> OAuthConnections(long userId, CancellationToken cancellationToken)
=> new AuthorityResponse<GraphQL.Types.OAuth.OAuthConnection[]>(
await oAuthConnectionsDataLoader.LoadRequiredAsync(userId, cancellationToken));
/// <inheritdoc />
@@ -57,10 +57,10 @@ using Tgstation.Server.Host.Controllers.Results;
using Tgstation.Server.Host.Database;
using Tgstation.Server.Host.Extensions;
using Tgstation.Server.Host.GraphQL;
using Tgstation.Server.Host.GraphQL.Interceptors;
using Tgstation.Server.Host.GraphQL.Scalars;
using Tgstation.Server.Host.GraphQL.Subscriptions;
using Tgstation.Server.Host.GraphQL.Types;
using Tgstation.Server.Host.GraphQL.Types.Interceptors;
using Tgstation.Server.Host.GraphQL.Types.Scalars;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Jobs;
using Tgstation.Server.Host.Properties;
@@ -6,10 +6,10 @@ using System.Reflection;
using HotChocolate.Configuration;
using HotChocolate.Types;
using HotChocolate.Types.Descriptors.Definitions;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.GraphQL.Types;
namespace Tgstation.Server.Host.GraphQL.Types.Interceptors
namespace Tgstation.Server.Host.GraphQL.Interceptors
{
/// <summary>
/// Fixes the names used for the default flags types in API rights.
@@ -14,8 +14,14 @@ namespace Tgstation.Server.Host.GraphQL
/// Root type for GraphQL mutations.
/// </summary>
/// <remarks>Intentionally left mostly empty, use type extensions to properly scope operations to domains.</remarks>
[GraphQLDescription(GraphQLDescription)]
public sealed class Mutation
{
/// <summary>
/// Description to show on the <see cref="Mutation"/> type.
/// </summary>
public const string GraphQLDescription = "Root Mutation type";
/// <summary>
/// Generate a JWT for authenticating with server. This is the only operation that accepts and required basic authentication.
/// </summary>
@@ -1,6 +1,6 @@
using HotChocolate;
using Tgstation.Server.Api.Models.Response;
using Tgstation.Server.Host.GraphQL.Types.Scalars;
using Tgstation.Server.Host.GraphQL.Scalars;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.GraphQL.Mutations.Payloads
@@ -18,6 +18,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
/// <see cref="IUserGroupAuthority"/> related <see cref="Mutation"/>s.
/// </summary>
[ExtendObjectType(typeof(Mutation))]
[GraphQLDescription(Mutation.GraphQLDescription)]
public sealed class UserGroupMutations
{
/// <summary>
@@ -13,6 +13,7 @@ using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.GraphQL.Mutations.Payloads;
using Tgstation.Server.Host.GraphQL.Types;
using Tgstation.Server.Host.GraphQL.Types.OAuth;
using Tgstation.Server.Host.Models.Transformers;
using Tgstation.Server.Host.Security;
@@ -22,6 +23,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations
/// <see cref="IUserAuthority"/> related <see cref="Mutation"/>s.
/// </summary>
[ExtendObjectType(typeof(Mutation))]
[GraphQLDescription(Mutation.GraphQLDescription)]
public sealed class UserMutations
{
/// <summary>
@@ -1,10 +1,13 @@
#pragma warning disable CA1724 // Dumb conflict with Microsoft.EntityFrameworkCore.Query
using HotChocolate;
namespace Tgstation.Server.Host.GraphQL
{
/// <summary>
/// GraphQL query <see cref="global::System.Type"/>.
/// </summary>
[GraphQLDescription("Root Query type")]
public sealed class Query
{
/// <summary>
@@ -3,7 +3,7 @@
using HotChocolate.Language;
using HotChocolate.Types;
namespace Tgstation.Server.Host.GraphQL.Types.Scalars
namespace Tgstation.Server.Host.GraphQL.Scalars
{
/// <summary>
/// A <see cref="ScalarType{TRuntimeType, TLiteral}"/> for encoded JSON Web Tokens.
@@ -2,10 +2,9 @@
using HotChocolate.Language;
using HotChocolate.Types;
using Tgstation.Server.Common.Extensions;
namespace Tgstation.Server.Host.GraphQL.Types.Scalars
namespace Tgstation.Server.Host.GraphQL.Scalars
{
/// <summary>
/// A <see cref="ScalarType{TRuntimeType, TLiteral}"/> for semantic <see cref="Version"/>s.
@@ -16,8 +16,14 @@ namespace Tgstation.Server.Host.GraphQL
/// Root type for GraphQL subscriptions.
/// </summary>
/// <remarks>Intentionally left mostly empty, use type extensions to properly scope operations to domains.</remarks>
[GraphQLDescription(GraphQLDescription)]
public sealed class Subscription
{
/// <summary>
/// Description to show on the <see cref="Subscription"/> type.
/// </summary>
public const string GraphQLDescription = "Root Subscription type. Note that subscriptions are performed over Server Sent events.";
/// <summary>
/// Gets the topic name for the login session represented by a given <paramref name="authenticationContext"/>.
/// </summary>
@@ -18,6 +18,7 @@ namespace Tgstation.Server.Host.GraphQL.Subscriptions
/// Subscriptions for <see cref="User"/>.
/// </summary>
[ExtendObjectType(typeof(Subscription))]
[GraphQLDescription(Subscription.GraphQLDescription)]
public sealed class UserSubscriptions
{
/// <summary>
@@ -10,6 +10,7 @@ using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components.Interop;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.GraphQL.Types.OAuth;
using Tgstation.Server.Host.Properties;
using Tgstation.Server.Host.Security;
using Tgstation.Server.Host.Security.OAuth;
@@ -28,7 +29,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// <param name="generalConfigurationOptions">The <see cref="IOptionsSnapshot{TOptions}"/> containing the <see cref="GeneralConfiguration"/>.</param>
/// <returns>A <see cref="uint"/> specifying the minimumn valid password length for TGS users.</returns>
[TgsGraphQLAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword)]
public uint? MinimumPasswordLength(
public uint MinimumPasswordLength(
[Service] IOptionsSnapshot<GeneralConfiguration> generalConfigurationOptions)
{
ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
@@ -41,7 +42,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// <param name="generalConfigurationOptions">The <see cref="IOptionsSnapshot{TOptions}"/> containing the <see cref="GeneralConfiguration"/>.</param>
/// <returns>A <see cref="uint"/> specifying the maximum allowed attached instances for the <see cref="SwarmNode"/>.</returns>
[TgsGraphQLAuthorize(InstanceManagerRights.Create)]
public uint? InstanceLimit(
public uint InstanceLimit(
[Service] IOptionsSnapshot<GeneralConfiguration> generalConfigurationOptions)
{
ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
@@ -55,7 +56,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// <returns>A <see cref="uint"/> specifying the maximum allowed registered users for the <see cref="ServerSwarm"/>.</returns>
/// <remarks>This limit only applies to user creation attempts made via the current <see cref="SwarmNode"/>.</remarks>
[TgsGraphQLAuthorize(AdministrationRights.WriteUsers)]
public uint? UserLimit(
public uint UserLimit(
[Service] IOptionsSnapshot<GeneralConfiguration> generalConfigurationOptions)
{
ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
@@ -69,7 +70,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// <returns>A <see cref="uint"/> specifying the maximum allowed registered <see cref="UserGroup"/>s for the <see cref="ServerSwarm"/>.</returns>
/// <remarks>This limit only applies to <see cref="UserGroup"/> creation attempts made via the current <see cref="SwarmNode"/>.</remarks>
[TgsGraphQLAuthorize(AdministrationRights.WriteUsers)]
public uint? UserGroupLimit(
public uint UserGroupLimit(
[Service] IOptionsSnapshot<GeneralConfiguration> generalConfigurationOptions)
{
ArgumentNullException.ThrowIfNull(generalConfigurationOptions);
@@ -95,7 +96,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// <param name="platformIdentifier">The <see cref="IPlatformIdentifier"/> to use.</param>
/// <returns><see langword="true"/> if the <see cref="SwarmNode"/> runs on a Windows operating system, <see langword="false"/> otherwise.</returns>
[TgsGraphQLAuthorize]
public bool? WindowsHost(
public bool WindowsHost(
[Service] IPlatformIdentifier platformIdentifier)
{
ArgumentNullException.ThrowIfNull(platformIdentifier);
@@ -106,7 +107,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// Gets the swarm protocol <see cref="Version"/>.
/// </summary>
[TgsGraphQLAuthorize]
public Version? SwarmProtocolVersion => global::System.Version.Parse(MasterVersionsAttribute.Instance.RawSwarmProtocolVersion);
public Version SwarmProtocolVersion => global::System.Version.Parse(MasterVersionsAttribute.Instance.RawSwarmProtocolVersion);
/// <summary>
/// Gets the <see cref="global::System.Version"/> of tgstation-server the <see cref="SwarmNode"/> is running.
@@ -114,7 +115,7 @@ namespace Tgstation.Server.Host.GraphQL.Types
/// <param name="assemblyInformationProvider">The <see cref="IAssemblyInformationProvider"/> to use.</param>
/// <returns>The <see cref="global::System.Version"/> of tgstation-server the <see cref="SwarmNode"/> is running.</returns>
[TgsGraphQLAuthorize]
public Version? Version(
public Version Version(
[Service] IAssemblyInformationProvider assemblyInformationProvider)
{
ArgumentNullException.ThrowIfNull(assemblyInformationProvider);
@@ -122,32 +123,38 @@ namespace Tgstation.Server.Host.GraphQL.Types
}
/// <summary>
/// Gets the major HTTP API <see cref="global::System.Version"/> number of the <see cref="SwarmNode"/>.
/// Gets the major GraphQL API <see cref="global::System.Version"/> number of the <see cref="SwarmNode"/>.
/// </summary>
public int MajorApiVersion => ApiHeaders.Version.Major;
public int MajorGraphQLApiVersion => GraphQLApiVersion.Major;
/// <summary>
/// Gets the HTTP API <see cref="global::System.Version"/> of the <see cref="SwarmNode"/>.
/// Gets the GraphQL API <see cref="global::System.Version"/> of the <see cref="SwarmNode"/>.
/// </summary>
[TgsGraphQLAuthorize]
public Version? ApiVersion => ApiHeaders.Version;
public Version GraphQLApiVersion => global::System.Version.Parse(MasterVersionsAttribute.Instance.RawGraphQLVersion);
/// <summary>
/// Gets the REST API <see cref="global::System.Version"/> of the <see cref="SwarmNode"/>.
/// </summary>
[TgsGraphQLAuthorize]
public Version ApiVersion => ApiHeaders.Version;
/// <summary>
/// Gets the DMAPI interop <see cref="global::System.Version"/> the <see cref="SwarmNode"/> uses.
/// </summary>
[TgsGraphQLAuthorize]
public Version? DMApiVersion => DMApiConstants.InteropVersion;
public Version DMApiVersion => DMApiConstants.InteropVersion;
/// <summary>
/// Gets the information needed to perform open authentication with the <see cref="SwarmNode"/>.
/// </summary>
/// <param name="oAuthProviders">The <see cref="IOAuthProviders"/> to use.</param>
/// <returns>A map of enabled <see cref="OAuthProvider"/>s to their <see cref="OAuthProviderInfo"/>.</returns>
public IReadOnlyDictionary<OAuthProvider, OAuthProviderInfo> OAuthProviderInfos(
/// <returns>A map of enabled <see cref="OAuthProvider"/>s to their <see cref="FullOAuthProviderInfo"/>.</returns>
public OAuthProviderInfos OAuthProviderInfos(
[Service] IOAuthProviders oAuthProviders)
{
ArgumentNullException.ThrowIfNull(oAuthProviders);
return oAuthProviders.ProviderInfos();
return new OAuthProviderInfos(oAuthProviders);
}
}
}
@@ -0,0 +1,28 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// Basic OAuth provider info.
/// </summary>
public class BasicOAuthProviderInfo
{
/// <summary>
/// The client ID.
/// </summary>
public string ClientID { get; }
/// <summary>
/// Initializes a new instance of the <see cref="BasicOAuthProviderInfo"/> class.
/// </summary>
/// <param name="providerInfo">The <see cref="OAuthProviderInfo"/> to build from.</param>
public BasicOAuthProviderInfo(OAuthProviderInfo providerInfo)
{
ArgumentNullException.ThrowIfNull(providerInfo);
ClientID = providerInfo.ClientId ?? throw new InvalidOperationException("ClientID not set!");
}
}
}
@@ -0,0 +1,29 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// OAuth provider info with a <see cref="RedirectOAuthProviderInfo.RedirectUri"/> and <see cref="ServerUrl"/>.
/// </summary>
public sealed class FullOAuthProviderInfo : RedirectOAuthProviderInfo
{
/// <summary>
/// The remote service URL.
/// </summary>
public Uri ServerUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="FullOAuthProviderInfo"/> class.
/// </summary>
/// <param name="providerInfo">The <see cref="OAuthProviderInfo"/> to build from.</param>
public FullOAuthProviderInfo(OAuthProviderInfo providerInfo)
: base(providerInfo)
{
ArgumentNullException.ThrowIfNull(providerInfo);
ServerUrl = providerInfo.ServerUrl ?? throw new InvalidOperationException("Missing OAuthProviderInfo ServerUrl!");
}
}
}
@@ -2,7 +2,7 @@
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// Represents a valid OAuth connection.
@@ -0,0 +1,66 @@
using System;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Host.Security.OAuth;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// Description of configured OAuth services.
/// </summary>
public sealed class OAuthProviderInfos
{
/// <summary>
/// https://discord.com.
/// </summary>
public BasicOAuthProviderInfo? Discord { get; }
/// <summary>
/// https://github.com.
/// </summary>
public RedirectOAuthProviderInfo? GitHub { get; }
/// <summary>
/// https://tgstation13.org.
/// </summary>
public RedirectOAuthProviderInfo? TGForums { get; }
/// <summary>
/// https://www.keycloak.org.
/// </summary>
public FullOAuthProviderInfo? Keycloak { get; }
/// <summary>
/// https://invisioncommunity.com.
/// </summary>
public FullOAuthProviderInfo? InvisionCommunity { get; }
/// <summary>
/// Initializes a new instance of the <see cref="OAuthProviderInfos"/> class.
/// </summary>
/// <param name="oAuthProviders">The <see cref="IOAuthProviders"/> to get data from.</param>
public OAuthProviderInfos(IOAuthProviders oAuthProviders)
{
ArgumentNullException.ThrowIfNull(oAuthProviders);
var dic = oAuthProviders.ProviderInfos();
TProviderInfo? TryBuild<TProviderInfo>(OAuthProvider oAuthProvider, Func<OAuthProviderInfo, TProviderInfo> contructor)
where TProviderInfo : BasicOAuthProviderInfo
{
if (dic.TryGetValue(oAuthProvider, out var providerInfo))
{
return contructor(providerInfo);
}
return null;
}
Discord = TryBuild(OAuthProvider.Discord, info => new BasicOAuthProviderInfo(info));
GitHub = TryBuild(OAuthProvider.GitHub, info => new RedirectOAuthProviderInfo(info));
TGForums = TryBuild(OAuthProvider.TGForums, info => new RedirectOAuthProviderInfo(info));
Keycloak = TryBuild(OAuthProvider.Keycloak, info => new FullOAuthProviderInfo(info));
InvisionCommunity = TryBuild(OAuthProvider.InvisionCommunity, info => new FullOAuthProviderInfo(info));
}
}
}
@@ -0,0 +1,27 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// OAuth provider info with a <see cref="RedirectUri"/>.
/// </summary>
public class RedirectOAuthProviderInfo : BasicOAuthProviderInfo
{
/// <summary>
/// The authentication server URL.
/// </summary>
public Uri RedirectUri { get; }
/// <summary>
/// Initializes a new instance of the <see cref="RedirectOAuthProviderInfo"/> class.
/// </summary>
/// <param name="providerInfo">The <see cref="OAuthProviderInfo"/> to build from.</param>
public RedirectOAuthProviderInfo(OAuthProviderInfo providerInfo)
: base(providerInfo)
{
RedirectUri = providerInfo!.RedirectUri ?? throw new InvalidOperationException("RedirectUri not set!");
}
}
}
@@ -0,0 +1,27 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.GraphQL.Types.OAuth
{
/// <summary>
/// OAuth provider info with a <see cref="ServerUrl"/>.
/// </summary>
public sealed class ServerUrlOAuthProviderInfo : BasicOAuthProviderInfo
{
/// <summary>
/// The remote service URL.
/// </summary>
public Uri ServerUrl { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ServerUrlOAuthProviderInfo"/> class.
/// </summary>
/// <param name="providerInfo">The <see cref="OAuthProviderInfo"/> to build from.</param>
public ServerUrlOAuthProviderInfo(OAuthProviderInfo providerInfo)
: base(providerInfo)
{
ServerUrl = providerInfo!.ServerUrl ?? throw new InvalidOperationException("ServerUrl not set!");
}
}
}
@@ -7,6 +7,7 @@ using HotChocolate.Types.Relay;
using Tgstation.Server.Host.Authority;
using Tgstation.Server.Host.GraphQL.Interfaces;
using Tgstation.Server.Host.GraphQL.Types.OAuth;
using Tgstation.Server.Host.Models.Transformers;
using Tgstation.Server.Host.Security;
@@ -32,7 +32,7 @@ namespace Tgstation.Server.Host.Properties
public string RawWebpanelVersion { get; }
/// <summary>
/// The <see cref="Version"/> <see cref="string"/> of the control panel version built.
/// The <see cref="Version"/> <see cref="string"/> of the host watchdog that was built alongside this TGS version.
/// </summary>
public string RawHostWatchdogVersion { get; }
@@ -42,10 +42,15 @@ namespace Tgstation.Server.Host.Properties
public string RawMariaDBRedistVersion { get; }
/// <summary>
/// The <see cref="Version"/> <see cref="string"/> of the MariaDB server bundled with TGS installs.
/// The <see cref="Version"/> <see cref="string"/> of the TGS swarm protocol.
/// </summary>
public string RawSwarmProtocolVersion { get; }
/// <summary>
/// The <see cref="Version"/> <see cref="string"/> of the TGS GraphQL API.
/// </summary>
public string RawGraphQLVersion { get; }
/// <summary>
/// Initializes a new instance of the <see cref="MasterVersionsAttribute"/> class.
/// </summary>
@@ -55,13 +60,15 @@ namespace Tgstation.Server.Host.Properties
/// <param name="rawHostWatchdogVersion">The value of <see cref="RawHostWatchdogVersion"/>.</param>
/// <param name="rawMariaDBRedistVersion">The value of <see cref="RawMariaDBRedistVersion"/>.</param>
/// <param name="rawSwarmProtocolVersion">The value of <see cref="RawSwarmProtocolVersion"/>.</param>
/// <param name="rawGraphQLVersion">The value of <see cref="RawGraphQLVersion"/>.</param>
public MasterVersionsAttribute(
string rawConfigurationVersion,
string rawInteropVersion,
string rawWebpanelVersion,
string rawHostWatchdogVersion,
string rawMariaDBRedistVersion,
string rawSwarmProtocolVersion)
string rawSwarmProtocolVersion,
string rawGraphQLVersion)
{
RawConfigurationVersion = rawConfigurationVersion ?? throw new ArgumentNullException(nameof(rawConfigurationVersion));
RawInteropVersion = rawInteropVersion ?? throw new ArgumentNullException(nameof(rawInteropVersion));
@@ -69,6 +76,7 @@ namespace Tgstation.Server.Host.Properties
RawHostWatchdogVersion = rawHostWatchdogVersion ?? throw new ArgumentNullException(nameof(rawHostWatchdogVersion));
RawMariaDBRedistVersion = rawMariaDBRedistVersion ?? throw new ArgumentNullException(nameof(rawMariaDBRedistVersion));
RawSwarmProtocolVersion = rawSwarmProtocolVersion ?? throw new ArgumentNullException(nameof(rawSwarmProtocolVersion));
RawGraphQLVersion = rawGraphQLVersion ?? throw new ArgumentNullException(nameof(rawGraphQLVersion));
}
}
}
+3 -1
View File
@@ -23,13 +23,15 @@ Server startup can be a bit complicated so here's a walkthrough
Here's a breakdown of things in this directory
- [.config](./.config) contains the dotnet-tools.json. At the time of writing, this is only used to set the version of the [dotnet ef tools](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/) used to create database migrations.
- [Authority](./Authority) is code that contains the buisiness logic that bridges the HTTP APIs with component code and the database.
- [ClientApp](./ClientApp) contains scripts to build and deploy the web control panel with TGS.
- [Components](./Components) is where the bulk of the TGS implementation lives.
- [Configuration](./Configuration) contains classes that partly make up the configuration yaml files (i.e. [appsettings.yml](./appsettings.yml)).
- [Controllers](./Controllers) is where HTTP API code lives and bridges it with component code.
- [Controllers](./Controllers) is where REST API code lives.
- [Core](./Core) contains [Application.cs](./Core/Application.cs) and other classes related to the overrarching server process.
- [Database](./Database) contains all database related code.
- [Extensions](./Extensions) contains helper functions implemented as C# extension methods.
- [GraphQL](./GraqhQL) is where GraphQL API code lives.
- [IO](./IO) contains classes related to interacting with the filesystem.
- [Jobs](./Jobs) contains the job manager code.
- [Models](./Models) contains ORM models used to interact with the database.
@@ -57,6 +57,7 @@
<_Parameter4>$(TgsHostWatchdogVersion)</_Parameter4>
<_Parameter5>$(TgsMariaDBRedistVersion)</_Parameter5>
<_Parameter6>$(TgsSwarmProtocolVersion)</_Parameter6>
<_Parameter7>$(TgsGraphQLVersion)</_Parameter7>
</MasterVersionAssemblyAttributes>
</ItemGroup>
<WriteCodeFragment AssemblyAttributes="@(MasterVersionAssemblyAttributes)" Language="C#" OutputDirectory="$(IntermediateOutputPath)" OutputFile="MasterVersionsAssemblyInfo.cs">
+2 -2
View File
@@ -13,7 +13,7 @@ General:
UserGroupLimit: 25 # Maximum number of allowed groups
InstanceLimit: 10 # Maximum number of allowed instances
ValidInstancePaths: # An array of directories instances may be created in (either directly or as a subdirectory). null removes the restriction
HostApiDocumentation: false # Make HTTP API documentation available at /api/doc/tgs_api.json
HostApiDocumentation: false # Make HTTP API documentation available at /api/doc/tgs_api.json and /api/graphql
SkipAddingByondFirewallException: false # Windows Only: Prevent running netsh.exe to add a firewall exception for installed engine binaries
DeploymentDirectoryCopyTasksPerCore: 100 # Maximum number of concurrent file copy operations PER available CPU core
OpenDreamGitUrl: https://github.com/OpenDreamProject/OpenDream # The repository to retrieve OpenDream from
@@ -40,7 +40,7 @@ Logging:
Microsoft: Warning # Level of stdout verbosity from Microsoft dependecies (i.e. webserver, database object relational mapper, etc...). Can be one of Trace, Debug, Information, Warning, Error, or Critical
ControlPanel:
Enable: false # If the web control panel is hosted alongside the server
Channel: https://tgstation.github.io/tgstation-server-webpanel/api/${Major}.${Minor}.${Patch} # Channel for live web control panel updates. The tokens ${Major}, ${Minor}, and ${Patch} are substituted for the version of the HTTP API TGS was built with
Channel: https://tgstation.github.io/tgstation-server-webpanel/api/${Major}.${Minor}.${Patch} # Channel for live web control panel updates. The tokens ${Major}, ${Minor}, and ${Patch} are substituted for the version of the REST API TGS was built with
AllowAnyOrigin: false # Enable the `Access-Control-Allow-Origin: *` header for HTTP responses
AllowedOrigins: [] # Explict list of origins for `Access-Control-Allow-Origin:` HTTP header. Ignored if AllowAnyOrigin is true
PublicPath: # Used if TGS is not hosted at the website root. If TGS is hosted under `https://domain.com/tgs`, this should be `/tgs`
@@ -1402,24 +1402,8 @@ namespace Tgstation.Server.Tests.Live
// test getting server info
var unAuthedMultiClient = new MultiServerClient(firstAdminRestClient, unauthenticatedGraphQLClient);
await unAuthedMultiClient.ExecuteReadOnlyConfirmEquivalence(
restClient => restClient.ServerInformation(cancellationToken),
await unauthenticatedGraphQLClient.RunQueryEnsureNoErrors(
gqlClient => gqlClient.UnauthenticatedServerInformation.ExecuteAsync(cancellationToken),
(restServerInfo, gqlServerInfo) =>
{
var result = restServerInfo.ApiVersion.Major == gqlServerInfo.Swarm.CurrentNode.Gateway.Information.MajorApiVersion
&& (restServerInfo.OAuthProviderInfos == gqlServerInfo.Swarm.CurrentNode.Gateway.Information.OAuthProviderInfos
|| restServerInfo.OAuthProviderInfos.All(kvp =>
{
var info = gqlServerInfo.Swarm.CurrentNode.Gateway.Information.OAuthProviderInfos.FirstOrDefault(x => (int)x.Key == (int)kvp.Key);
return info != null
&& info.Value.ServerUrl == kvp.Value.ServerUrl
&& info.Value.ClientId == kvp.Value.ClientId
&& info.Value.RedirectUri == kvp.Value.RedirectUri;
}));
return result;
},
cancellationToken);
var testObserver = new HoldLastObserver<IOperationResult<ISessionInvalidationResult>>();
+12 -3
View File
@@ -32,7 +32,7 @@ using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.System;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Tests.Live;
using Tgstation.Server.Host.Utils;
using Tgstation.Server.Host.Properties;
namespace Tgstation.Server.Tests
{
@@ -75,14 +75,23 @@ namespace Tgstation.Server.Tests
}
[TestMethod]
public void TestApiVersion()
public void TestRestVersion()
{
var versionString = versionsPropertyGroup.Element(xmlNamespace + "TgsApiVersion").Value;
var versionString = versionsPropertyGroup.Element(xmlNamespace + "TgsRestVersion").Value;
Assert.IsNotNull(versionString);
Assert.IsTrue(Version.TryParse(versionString, out var expected));
Assert.AreEqual(expected, ApiHeaders.Version);
}
[TestMethod]
public void TestGraphQLVersion()
{
var versionString = versionsPropertyGroup.Element(xmlNamespace + "TgsGraphQLVersion").Value;
Assert.IsNotNull(versionString);
Assert.IsTrue(Version.TryParse(versionString, out var expected));
Assert.AreEqual(expected, Version.Parse(MasterVersionsAttribute.Instance.RawGraphQLVersion));
}
[TestMethod]
public void TestApiLibraryVersion()
{
@@ -1,4 +1,6 @@
namespace Tgstation.Server.ReleaseNotes
using YamlDotNet.Serialization;
namespace Tgstation.Server.ReleaseNotes
{
enum Component
{
@@ -6,7 +8,8 @@
Core,
HostWatchdog,
WebControlPanel,
HttpApi,
HttpApi, // can't be properly renamed due to changelog
GraphQLApi,
DreamMakerApi,
InteropApi,
NugetCommon,
+27 -11
View File
@@ -91,9 +91,12 @@ namespace Tgstation.Server.ReleaseNotes
case "--NO-CLOSE":
doNotCloseMilestone = true;
break;
case "--HTTPAPI":
case "--RESTAPI":
componentRelease = Component.HttpApi;
break;
case "--GRAPHQLAPI":
componentRelease = Component.GraphQLApi;
break;
case "--INTEROPAPI":
componentRelease = Component.InteropApi;
break;
@@ -245,7 +248,8 @@ namespace Tgstation.Server.ReleaseNotes
return 10;
}
var apiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsApiVersion").Value);
var restVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsRestVersion").Value);
var graphQLVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsGraphQLVersion").Value);
var configVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsConfigVersion").Value);
var dmApiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsDmapiVersion").Value);
var interopVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsInteropVersion").Value);
@@ -255,7 +259,7 @@ namespace Tgstation.Server.ReleaseNotes
if (webControlVersion.Major == 0)
postControlPanelMessage = true;
prefix = $"Please refer to the [README](https://github.com/tgstation/tgstation-server#setup) for setup instructions. Full changelog can be found [here](https://raw.githubusercontent.com/tgstation/tgstation-server/gh-pages/changelog.yml).{Environment.NewLine}{Environment.NewLine}#### Component Versions\nCore: {coreVersion}\nConfiguration: {configVersion}\nHTTP API: {apiVersion}\nDreamMaker API: {dmApiVersion} (Interop: {interopVersion})\n[Web Control Panel](https://github.com/tgstation/tgstation-server-webpanel): {webControlVersion}\nHost Watchdog: {hostWatchdogVersion}";
prefix = $"Please refer to the [README](https://github.com/tgstation/tgstation-server#setup) for setup instructions. Full changelog can be found [here](https://raw.githubusercontent.com/tgstation/tgstation-server/gh-pages/changelog.yml).{Environment.NewLine}{Environment.NewLine}#### Component Versions\nCore: {coreVersion}\nConfiguration: {configVersion}\nREST API: {restVersion}\nGraphQL API{(graphQLVersion.Major < 1 ? " (Pre-release)" : String.Empty)}: {graphQLVersion}\nDreamMaker API: {dmApiVersion} (Interop: {interopVersion})\n[Web Control Panel](https://github.com/tgstation/tgstation-server-webpanel): {webControlVersion}\nHost Watchdog: {hostWatchdogVersion}";
var newNotes = new StringBuilder(prefix);
if (postControlPanelMessage)
@@ -409,7 +413,8 @@ namespace Tgstation.Server.ReleaseNotes
var componentVersionDict = new Dictionary<Component, Version>
{
{ Component.Configuration, configVersion },
{ Component.HttpApi, apiVersion },
{ Component.HttpApi, restVersion },
{ Component.GraphQLApi, graphQLVersion },
{ Component.DreamMakerApi, dmApiVersion },
{ Component.InteropApi, interopVersion },
{ Component.WebControlPanel, webControlVersion },
@@ -508,7 +513,8 @@ namespace Tgstation.Server.ReleaseNotes
static string GetComponentDisplayName(Component component, bool debian) => component switch
{
Component.HttpApi => debian ? "the HTTP API" : "HTTP API",
Component.HttpApi => debian ? "the REST API" : "REST API",
Component.GraphQLApi => debian ? "the GraphQL API" : "GraphQL API",
Component.InteropApi => debian ? "the Interop API" : "Interop API",
Component.Configuration => debian ? "the TGS configuration" : "**Configuration**",
Component.DreamMakerApi => debian ? "the DreamMaker API" : "DreamMaker API",
@@ -607,7 +613,8 @@ namespace Tgstation.Server.ReleaseNotes
var dict = new Dictionary<Component, Version>
{
{ Component.Core, Parse("TgsCoreVersion") },
{ Component.HttpApi, Parse("TgsApiVersion") },
{ Component.HttpApi, Parse("TgsRestVersion") },
{ Component.GraphQLApi, Parse("TgsGraphQLVersion") },
{ Component.DreamMakerApi, Parse("TgsDmapiVersion") },
};
@@ -720,7 +727,8 @@ namespace Tgstation.Server.ReleaseNotes
component = targetComponent.ToUpperInvariant() switch
{
"**CONFIGURATION**" or "CONFIGURATION" or "CONFIG" => Component.Configuration,
"HTTP API" => Component.HttpApi,
"HTTP API" or "REST API" => Component.HttpApi,
"GQL API" or "GRAPHQL API" or "GQL" or "GRAPHQL" => Component.GraphQLApi,
"WEB CONTROL PANEL" => Component.WebControlPanel,
"DMAPI" or "DREAMMAKER API" => Component.DreamMakerApi,
"INTEROP API" => Component.InteropApi,
@@ -1160,15 +1168,23 @@ Note: `<path>` is the directory's name containing the manifest you're submitting
var nugetApiVersions = EnumerateNugetVersions("Tgstation.Server.Api");
var nugetClientVersions = EnumerateNugetVersions("Tgstation.Server.Client");
const string ApiTagPrefix = "api-v";
const string GraphQLTagPrefix = "graphql-v";
const string DMApiTagPrefix = "dmapi-v";
var newDic = new Dictionary<Component, IReadOnlySet<Version>> {
{ Component.HttpApi, releases
.Where(x => x.TagName.StartsWith("api-v"))
.Select(x => Version.Parse(x.TagName[5..]))
.Where(x => x.TagName.StartsWith(ApiTagPrefix))
.Select(x => Version.Parse(x.TagName[ApiTagPrefix.Length..]))
.OrderBy(x => x)
.ToHashSet() },
{ Component.GraphQLApi, releases
.Where(x => x.TagName.StartsWith(GraphQLTagPrefix))
.Select(x => Version.Parse(x.TagName[GraphQLTagPrefix.Length..]))
.OrderBy(x => x)
.ToHashSet() },
{ Component.DreamMakerApi, releases
.Where(x => x.TagName.StartsWith("dmapi-v"))
.Select(x => Version.Parse(x.TagName[7..]))
.Where(x => x.TagName.StartsWith(DMApiTagPrefix))
.Select(x => Version.Parse(x.TagName[DMApiTagPrefix.Length..]))
.OrderBy(x => x)
.ToHashSet() },
{ Component.NugetCommon, await nugetCommonVersions },