diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index e1eca34032..f116b2259d 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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. diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index a71922c1df..518a6e5b61 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -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.) diff --git a/.github/workflows/ci-pipeline.yml b/.github/workflows/ci-pipeline.yml index afef6086dd..cc4afdcb38 100644 --- a/.github/workflows/ci-pipeline.yml +++ b/.github/workflows/ci-pipeline.yml @@ -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: diff --git a/build/Version.props b/build/Version.props index e1726021b4..a741ed3363 100644 --- a/build/Version.props +++ b/build/Version.props @@ -5,7 +5,8 @@ 6.10.0 5.3.0 - 10.10.0 + 10.10.0 + 0.1.0 7.0.0 16.0.0 19.0.0 diff --git a/src/README.md b/src/README.md index 76cb6bdf5d..c05d859bf2 100644 --- a/src/README.md +++ b/src/README.md @@ -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. diff --git a/src/Tgstation.Server.Api/ApiHeaders.cs b/src/Tgstation.Server.Api/ApiHeaders.cs index acc673bf07..b023727bed 100644 --- a/src/Tgstation.Server.Api/ApiHeaders.cs +++ b/src/Tgstation.Server.Api/ApiHeaders.cs @@ -269,7 +269,7 @@ void AddError(HeaderErrorTypes headerType, string message) if (requestHeaders.Headers.TryGetValue(OAuthProviderHeader, out StringValues oauthProviderValues)) { var oauthProviderString = oauthProviderValues.First(); - if (Enum.TryParse(oauthProviderString, out var oauthProvider)) + if (Enum.TryParse(oauthProviderString, true, out var oauthProvider)) OAuthProvider = oauthProvider; else AddError(HeaderErrorTypes.OAuthProvider, "Invalid OAuth provider!"); diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index 2617a2a566..129035ad21 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -14,7 +14,7 @@ - <_Parameter1>$(TgsApiVersion) + <_Parameter1>$(TgsRestVersion) diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/OAuthInformation.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/OAuthInformation.graphql deleted file mode 100644 index e32a409b60..0000000000 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/OAuthInformation.graphql +++ /dev/null @@ -1,18 +0,0 @@ -query OAuthInformation { - swarm { - currentNode { - gateway { - information { - oAuthProviderInfos { - key - value { - clientId - redirectUri - serverUrl - } - } - } - } - } - } -} diff --git a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/UnauthenticatedServerInformation.graphql b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/UnauthenticatedServerInformation.graphql index 44bff278f0..be535b84a0 100644 --- a/src/Tgstation.Server.Client.GraphQL/GQL/Queries/UnauthenticatedServerInformation.graphql +++ b/src/Tgstation.Server.Client.GraphQL/GQL/Queries/UnauthenticatedServerInformation.graphql @@ -3,15 +3,7 @@ query UnauthenticatedServerInformation { currentNode { gateway { information { - majorApiVersion - oAuthProviderInfos { - key - value { - clientId - redirectUri - serverUrl - } - } + majorGraphQLApiVersion } } } diff --git a/src/Tgstation.Server.Client.GraphQL/Tgstation.Server.Client.GraphQL.csproj b/src/Tgstation.Server.Client.GraphQL/Tgstation.Server.Client.GraphQL.csproj index 22f0529c5c..780cbbcd7c 100644 --- a/src/Tgstation.Server.Client.GraphQL/Tgstation.Server.Client.GraphQL.csproj +++ b/src/Tgstation.Server.Client.GraphQL/Tgstation.Server.Client.GraphQL.csproj @@ -3,7 +3,7 @@ $(TgsFrameworkVersion) - $(TgsApiVersion) + enable diff --git a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs index b0a67681cd..9e2a37da59 100644 --- a/src/Tgstation.Server.Host/Authority/IUserAuthority.cs +++ b/src/Tgstation.Server.Host/Authority/IUserAuthority.cs @@ -36,12 +36,12 @@ public interface IUserAuthority : IAuthority ValueTask> GetId(long id, bool includeJoins, bool allowSystemUser, CancellationToken cancellationToken); /// - /// Gets the s for the with a given . + /// Gets the s for the with a given . /// /// The of the . /// The for the operation. - /// A resulting in an of . - ValueTask> OAuthConnections(long userId, CancellationToken cancellationToken); + /// A resulting in an of . + ValueTask> OAuthConnections(long userId, CancellationToken cancellationToken); /// /// Gets all registered s. diff --git a/src/Tgstation.Server.Host/Authority/UserAuthority.cs b/src/Tgstation.Server.Host/Authority/UserAuthority.cs index 753a180d30..686eb10f47 100644 --- a/src/Tgstation.Server.Host/Authority/UserAuthority.cs +++ b/src/Tgstation.Server.Host/Authority/UserAuthority.cs @@ -102,7 +102,7 @@ public static Task> GetUsers( /// The for the operation. /// A resulting in a of the requested s. [DataLoader] - public static async ValueTask> GetOAuthConnections( + public static async ValueTask> GetOAuthConnections( IReadOnlyList userIds, IDatabaseContext databaseContext, CancellationToken cancellationToken) @@ -118,7 +118,7 @@ public static Task> GetUsers( 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)); } /// @@ -285,8 +285,8 @@ public IQueryable Queryable(bool includeJoins) => Queryable(includeJoins, false); /// - public async ValueTask> OAuthConnections(long userId, CancellationToken cancellationToken) - => new AuthorityResponse( + public async ValueTask> OAuthConnections(long userId, CancellationToken cancellationToken) + => new AuthorityResponse( await oAuthConnectionsDataLoader.LoadRequiredAsync(userId, cancellationToken)); /// diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index aa7753c05c..3a310a7ec9 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -57,10 +57,10 @@ 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; diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Interceptors/RightsTypeInterceptor.cs b/src/Tgstation.Server.Host/GraphQL/Interceptors/RightsTypeInterceptor.cs similarity index 97% rename from src/Tgstation.Server.Host/GraphQL/Types/Interceptors/RightsTypeInterceptor.cs rename to src/Tgstation.Server.Host/GraphQL/Interceptors/RightsTypeInterceptor.cs index 538c32b3ad..5fea9058e3 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/Interceptors/RightsTypeInterceptor.cs +++ b/src/Tgstation.Server.Host/GraphQL/Interceptors/RightsTypeInterceptor.cs @@ -6,10 +6,10 @@ 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 { /// /// Fixes the names used for the default flags types in API rights. diff --git a/src/Tgstation.Server.Host/GraphQL/Mutation.cs b/src/Tgstation.Server.Host/GraphQL/Mutation.cs index 4b68aa9b89..5e6fa1d10c 100644 --- a/src/Tgstation.Server.Host/GraphQL/Mutation.cs +++ b/src/Tgstation.Server.Host/GraphQL/Mutation.cs @@ -14,8 +14,14 @@ namespace Tgstation.Server.Host.GraphQL /// Root type for GraphQL mutations. /// /// Intentionally left mostly empty, use type extensions to properly scope operations to domains. + [GraphQLDescription(GraphQLDescription)] public sealed class Mutation { + /// + /// Description to show on the type. + /// + public const string GraphQLDescription = "Root Mutation type"; + /// /// Generate a JWT for authenticating with server. This is the only operation that accepts and required basic authentication. /// diff --git a/src/Tgstation.Server.Host/GraphQL/Mutations/Payloads/LoginPayload.cs b/src/Tgstation.Server.Host/GraphQL/Mutations/Payloads/LoginPayload.cs index 8d5cd5e683..407ebab3b7 100644 --- a/src/Tgstation.Server.Host/GraphQL/Mutations/Payloads/LoginPayload.cs +++ b/src/Tgstation.Server.Host/GraphQL/Mutations/Payloads/LoginPayload.cs @@ -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 diff --git a/src/Tgstation.Server.Host/GraphQL/Mutations/UserGroupMutations.cs b/src/Tgstation.Server.Host/GraphQL/Mutations/UserGroupMutations.cs index 92ef77d288..7f27d8ea76 100644 --- a/src/Tgstation.Server.Host/GraphQL/Mutations/UserGroupMutations.cs +++ b/src/Tgstation.Server.Host/GraphQL/Mutations/UserGroupMutations.cs @@ -18,6 +18,7 @@ namespace Tgstation.Server.Host.GraphQL.Mutations /// related s. /// [ExtendObjectType(typeof(Mutation))] + [GraphQLDescription(Mutation.GraphQLDescription)] public sealed class UserGroupMutations { /// diff --git a/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs b/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs index 64c671a7cb..77fc7e3445 100644 --- a/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs +++ b/src/Tgstation.Server.Host/GraphQL/Mutations/UserMutations.cs @@ -13,6 +13,7 @@ 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 /// related s. /// [ExtendObjectType(typeof(Mutation))] + [GraphQLDescription(Mutation.GraphQLDescription)] public sealed class UserMutations { /// diff --git a/src/Tgstation.Server.Host/GraphQL/Query.cs b/src/Tgstation.Server.Host/GraphQL/Query.cs index ca9fed764f..660040d41d 100644 --- a/src/Tgstation.Server.Host/GraphQL/Query.cs +++ b/src/Tgstation.Server.Host/GraphQL/Query.cs @@ -1,10 +1,13 @@ #pragma warning disable CA1724 // Dumb conflict with Microsoft.EntityFrameworkCore.Query +using HotChocolate; + namespace Tgstation.Server.Host.GraphQL { /// /// GraphQL query . /// + [GraphQLDescription("Root Query type")] public sealed class Query { /// diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Scalars/JwtType.cs b/src/Tgstation.Server.Host/GraphQL/Scalars/JwtType.cs similarity index 94% rename from src/Tgstation.Server.Host/GraphQL/Types/Scalars/JwtType.cs rename to src/Tgstation.Server.Host/GraphQL/Scalars/JwtType.cs index 47f10ccde3..170a744a42 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/Scalars/JwtType.cs +++ b/src/Tgstation.Server.Host/GraphQL/Scalars/JwtType.cs @@ -3,7 +3,7 @@ using HotChocolate.Language; using HotChocolate.Types; -namespace Tgstation.Server.Host.GraphQL.Types.Scalars +namespace Tgstation.Server.Host.GraphQL.Scalars { /// /// A for encoded JSON Web Tokens. diff --git a/src/Tgstation.Server.Host/GraphQL/Types/Scalars/SemverType.cs b/src/Tgstation.Server.Host/GraphQL/Scalars/SemverType.cs similarity index 97% rename from src/Tgstation.Server.Host/GraphQL/Types/Scalars/SemverType.cs rename to src/Tgstation.Server.Host/GraphQL/Scalars/SemverType.cs index dce31b4751..de8c1e530a 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/Scalars/SemverType.cs +++ b/src/Tgstation.Server.Host/GraphQL/Scalars/SemverType.cs @@ -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 { /// /// A for semantic s. diff --git a/src/Tgstation.Server.Host/GraphQL/Subscription.cs b/src/Tgstation.Server.Host/GraphQL/Subscription.cs index 114a7622ad..170dceb49a 100644 --- a/src/Tgstation.Server.Host/GraphQL/Subscription.cs +++ b/src/Tgstation.Server.Host/GraphQL/Subscription.cs @@ -16,8 +16,14 @@ namespace Tgstation.Server.Host.GraphQL /// Root type for GraphQL subscriptions. /// /// Intentionally left mostly empty, use type extensions to properly scope operations to domains. + [GraphQLDescription(GraphQLDescription)] public sealed class Subscription { + /// + /// Description to show on the type. + /// + public const string GraphQLDescription = "Root Subscription type. Note that subscriptions are performed over Server Sent events."; + /// /// Gets the topic name for the login session represented by a given . /// diff --git a/src/Tgstation.Server.Host/GraphQL/Subscriptions/UserSubscriptions.cs b/src/Tgstation.Server.Host/GraphQL/Subscriptions/UserSubscriptions.cs index 2369df8ab4..35be564c52 100644 --- a/src/Tgstation.Server.Host/GraphQL/Subscriptions/UserSubscriptions.cs +++ b/src/Tgstation.Server.Host/GraphQL/Subscriptions/UserSubscriptions.cs @@ -18,6 +18,7 @@ namespace Tgstation.Server.Host.GraphQL.Subscriptions /// Subscriptions for . /// [ExtendObjectType(typeof(Subscription))] + [GraphQLDescription(Subscription.GraphQLDescription)] public sealed class UserSubscriptions { /// diff --git a/src/Tgstation.Server.Host/GraphQL/Types/GatewayInformation.cs b/src/Tgstation.Server.Host/GraphQL/Types/GatewayInformation.cs index aefc2562bb..9a1aee31cf 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/GatewayInformation.cs +++ b/src/Tgstation.Server.Host/GraphQL/Types/GatewayInformation.cs @@ -10,6 +10,7 @@ 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 @@ public sealed class GatewayInformation /// The containing the . /// A specifying the minimumn valid password length for TGS users. [TgsGraphQLAuthorize(AdministrationRights.WriteUsers | AdministrationRights.EditOwnPassword)] - public uint? MinimumPasswordLength( + public uint MinimumPasswordLength( [Service] IOptionsSnapshot generalConfigurationOptions) { ArgumentNullException.ThrowIfNull(generalConfigurationOptions); @@ -41,7 +42,7 @@ public sealed class GatewayInformation /// The containing the . /// A specifying the maximum allowed attached instances for the . [TgsGraphQLAuthorize(InstanceManagerRights.Create)] - public uint? InstanceLimit( + public uint InstanceLimit( [Service] IOptionsSnapshot generalConfigurationOptions) { ArgumentNullException.ThrowIfNull(generalConfigurationOptions); @@ -55,7 +56,7 @@ public sealed class GatewayInformation /// A specifying the maximum allowed registered users for the . /// This limit only applies to user creation attempts made via the current . [TgsGraphQLAuthorize(AdministrationRights.WriteUsers)] - public uint? UserLimit( + public uint UserLimit( [Service] IOptionsSnapshot generalConfigurationOptions) { ArgumentNullException.ThrowIfNull(generalConfigurationOptions); @@ -69,7 +70,7 @@ public sealed class GatewayInformation /// A specifying the maximum allowed registered s for the . /// This limit only applies to creation attempts made via the current . [TgsGraphQLAuthorize(AdministrationRights.WriteUsers)] - public uint? UserGroupLimit( + public uint UserGroupLimit( [Service] IOptionsSnapshot generalConfigurationOptions) { ArgumentNullException.ThrowIfNull(generalConfigurationOptions); @@ -95,7 +96,7 @@ public sealed class GatewayInformation /// The to use. /// if the runs on a Windows operating system, otherwise. [TgsGraphQLAuthorize] - public bool? WindowsHost( + public bool WindowsHost( [Service] IPlatformIdentifier platformIdentifier) { ArgumentNullException.ThrowIfNull(platformIdentifier); @@ -106,7 +107,7 @@ public sealed class GatewayInformation /// Gets the swarm protocol . /// [TgsGraphQLAuthorize] - public Version? SwarmProtocolVersion => global::System.Version.Parse(MasterVersionsAttribute.Instance.RawSwarmProtocolVersion); + public Version SwarmProtocolVersion => global::System.Version.Parse(MasterVersionsAttribute.Instance.RawSwarmProtocolVersion); /// /// Gets the of tgstation-server the is running. @@ -114,7 +115,7 @@ public sealed class GatewayInformation /// The to use. /// The of tgstation-server the is running. [TgsGraphQLAuthorize] - public Version? Version( + public Version Version( [Service] IAssemblyInformationProvider assemblyInformationProvider) { ArgumentNullException.ThrowIfNull(assemblyInformationProvider); @@ -122,32 +123,38 @@ public sealed class GatewayInformation } /// - /// Gets the major HTTP API number of the . + /// Gets the major GraphQL API number of the . /// - public int MajorApiVersion => ApiHeaders.Version.Major; + public int MajorGraphQLApiVersion => GraphQLApiVersion.Major; /// - /// Gets the HTTP API of the . + /// Gets the GraphQL API of the . /// [TgsGraphQLAuthorize] - public Version? ApiVersion => ApiHeaders.Version; + public Version GraphQLApiVersion => global::System.Version.Parse(MasterVersionsAttribute.Instance.RawGraphQLVersion); + + /// + /// Gets the REST API of the . + /// + [TgsGraphQLAuthorize] + public Version ApiVersion => ApiHeaders.Version; /// /// Gets the DMAPI interop the uses. /// [TgsGraphQLAuthorize] - public Version? DMApiVersion => DMApiConstants.InteropVersion; + public Version DMApiVersion => DMApiConstants.InteropVersion; /// /// Gets the information needed to perform open authentication with the . /// /// The to use. - /// A map of enabled s to their . - public IReadOnlyDictionary OAuthProviderInfos( + /// A map of enabled s to their . + public OAuthProviderInfos OAuthProviderInfos( [Service] IOAuthProviders oAuthProviders) { ArgumentNullException.ThrowIfNull(oAuthProviders); - return oAuthProviders.ProviderInfos(); + return new OAuthProviderInfos(oAuthProviders); } } } diff --git a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/BasicOAuthProviderInfo.cs b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/BasicOAuthProviderInfo.cs new file mode 100644 index 0000000000..f503d7adc8 --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/BasicOAuthProviderInfo.cs @@ -0,0 +1,28 @@ +using System; + +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.GraphQL.Types.OAuth +{ + /// + /// Basic OAuth provider info. + /// + public class BasicOAuthProviderInfo + { + /// + /// The client ID. + /// + public string ClientID { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The to build from. + public BasicOAuthProviderInfo(OAuthProviderInfo providerInfo) + { + ArgumentNullException.ThrowIfNull(providerInfo); + + ClientID = providerInfo.ClientId ?? throw new InvalidOperationException("ClientID not set!"); + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/FullOAuthProviderInfo.cs b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/FullOAuthProviderInfo.cs new file mode 100644 index 0000000000..cd750f665b --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/FullOAuthProviderInfo.cs @@ -0,0 +1,29 @@ +using System; + +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.GraphQL.Types.OAuth +{ + /// + /// OAuth provider info with a and . + /// + public sealed class FullOAuthProviderInfo : RedirectOAuthProviderInfo + { + /// + /// The remote service URL. + /// + public Uri ServerUrl { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The to build from. + public FullOAuthProviderInfo(OAuthProviderInfo providerInfo) + : base(providerInfo) + { + ArgumentNullException.ThrowIfNull(providerInfo); + + ServerUrl = providerInfo.ServerUrl ?? throw new InvalidOperationException("Missing OAuthProviderInfo ServerUrl!"); + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Types/OAuthConnection.cs b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthConnection.cs similarity index 94% rename from src/Tgstation.Server.Host/GraphQL/Types/OAuthConnection.cs rename to src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthConnection.cs index 7b0732fe92..b073446c66 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/OAuthConnection.cs +++ b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthConnection.cs @@ -2,7 +2,7 @@ using Tgstation.Server.Api.Models; -namespace Tgstation.Server.Host.GraphQL.Types +namespace Tgstation.Server.Host.GraphQL.Types.OAuth { /// /// Represents a valid OAuth connection. diff --git a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthProviderInfos.cs b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthProviderInfos.cs new file mode 100644 index 0000000000..da9bffe4b3 --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/OAuthProviderInfos.cs @@ -0,0 +1,66 @@ +using System; + +using Tgstation.Server.Api.Models; +using Tgstation.Server.Host.Security.OAuth; + +namespace Tgstation.Server.Host.GraphQL.Types.OAuth +{ + /// + /// Description of configured OAuth services. + /// + public sealed class OAuthProviderInfos + { + /// + /// https://discord.com. + /// + public BasicOAuthProviderInfo? Discord { get; } + + /// + /// https://github.com. + /// + public RedirectOAuthProviderInfo? GitHub { get; } + + /// + /// https://tgstation13.org. + /// + public RedirectOAuthProviderInfo? TGForums { get; } + + /// + /// https://www.keycloak.org. + /// + public FullOAuthProviderInfo? Keycloak { get; } + + /// + /// https://invisioncommunity.com. + /// + public FullOAuthProviderInfo? InvisionCommunity { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The to get data from. + public OAuthProviderInfos(IOAuthProviders oAuthProviders) + { + ArgumentNullException.ThrowIfNull(oAuthProviders); + + var dic = oAuthProviders.ProviderInfos(); + + TProviderInfo? TryBuild(OAuthProvider oAuthProvider, Func 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)); + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/RedirectOAuthProviderInfo.cs b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/RedirectOAuthProviderInfo.cs new file mode 100644 index 0000000000..ec0b00f6a0 --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/RedirectOAuthProviderInfo.cs @@ -0,0 +1,27 @@ +using System; + +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.GraphQL.Types.OAuth +{ + /// + /// OAuth provider info with a . + /// + public class RedirectOAuthProviderInfo : BasicOAuthProviderInfo + { + /// + /// The authentication server URL. + /// + public Uri RedirectUri { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The to build from. + public RedirectOAuthProviderInfo(OAuthProviderInfo providerInfo) + : base(providerInfo) + { + RedirectUri = providerInfo!.RedirectUri ?? throw new InvalidOperationException("RedirectUri not set!"); + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Types/OAuth/ServerUrlOAuthProviderInfo.cs b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/ServerUrlOAuthProviderInfo.cs new file mode 100644 index 0000000000..2add1c2bae --- /dev/null +++ b/src/Tgstation.Server.Host/GraphQL/Types/OAuth/ServerUrlOAuthProviderInfo.cs @@ -0,0 +1,27 @@ +using System; + +using Tgstation.Server.Api.Models; + +namespace Tgstation.Server.Host.GraphQL.Types.OAuth +{ + /// + /// OAuth provider info with a . + /// + public sealed class ServerUrlOAuthProviderInfo : BasicOAuthProviderInfo + { + /// + /// The remote service URL. + /// + public Uri ServerUrl { get; } + + /// + /// Initializes a new instance of the class. + /// + /// The to build from. + public ServerUrlOAuthProviderInfo(OAuthProviderInfo providerInfo) + : base(providerInfo) + { + ServerUrl = providerInfo!.ServerUrl ?? throw new InvalidOperationException("ServerUrl not set!"); + } + } +} diff --git a/src/Tgstation.Server.Host/GraphQL/Types/User.cs b/src/Tgstation.Server.Host/GraphQL/Types/User.cs index 7e993fd358..721fbe9eef 100644 --- a/src/Tgstation.Server.Host/GraphQL/Types/User.cs +++ b/src/Tgstation.Server.Host/GraphQL/Types/User.cs @@ -7,6 +7,7 @@ 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; diff --git a/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs b/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs index 8212c4ffee..efef0144bc 100644 --- a/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs +++ b/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs @@ -32,7 +32,7 @@ sealed class MasterVersionsAttribute : Attribute public string RawWebpanelVersion { get; } /// - /// The of the control panel version built. + /// The of the host watchdog that was built alongside this TGS version. /// public string RawHostWatchdogVersion { get; } @@ -42,10 +42,15 @@ sealed class MasterVersionsAttribute : Attribute public string RawMariaDBRedistVersion { get; } /// - /// The of the MariaDB server bundled with TGS installs. + /// The of the TGS swarm protocol. /// public string RawSwarmProtocolVersion { get; } + /// + /// The of the TGS GraphQL API. + /// + public string RawGraphQLVersion { get; } + /// /// Initializes a new instance of the class. /// @@ -55,13 +60,15 @@ sealed class MasterVersionsAttribute : Attribute /// The value of . /// The value of . /// The value of . + /// The value of . 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 @@ public MasterVersionsAttribute( 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)); } } } diff --git a/src/Tgstation.Server.Host/README.md b/src/Tgstation.Server.Host/README.md index 1f8df0feee..9dd86b2d63 100644 --- a/src/Tgstation.Server.Host/README.md +++ b/src/Tgstation.Server.Host/README.md @@ -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. diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 35f03b7fd0..c97953fa70 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -57,6 +57,7 @@ <_Parameter4>$(TgsHostWatchdogVersion) <_Parameter5>$(TgsMariaDBRedistVersion) <_Parameter6>$(TgsSwarmProtocolVersion) + <_Parameter7>$(TgsGraphQLVersion) diff --git a/src/Tgstation.Server.Host/appsettings.yml b/src/Tgstation.Server.Host/appsettings.yml index 353104e6de..b965a326ed 100644 --- a/src/Tgstation.Server.Host/appsettings.yml +++ b/src/Tgstation.Server.Host/appsettings.yml @@ -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` diff --git a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs index ea2bf9311f..2092e0819f 100644 --- a/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs +++ b/tests/Tgstation.Server.Tests/Live/TestLiveServer.cs @@ -1402,24 +1402,8 @@ async Task TestTgsInternal(bool openDreamOnly, CancellationToken hardCancellatio // 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>(); diff --git a/tests/Tgstation.Server.Tests/TestVersions.cs b/tests/Tgstation.Server.Tests/TestVersions.cs index 6da26320aa..a0809b78f6 100644 --- a/tests/Tgstation.Server.Tests/TestVersions.cs +++ b/tests/Tgstation.Server.Tests/TestVersions.cs @@ -32,7 +32,7 @@ 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 @@ public void TestConfigVersion() } [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() { diff --git a/tools/Tgstation.Server.ReleaseNotes/Component.cs b/tools/Tgstation.Server.ReleaseNotes/Component.cs index d1baa3fcda..ac2d1aa84b 100644 --- a/tools/Tgstation.Server.ReleaseNotes/Component.cs +++ b/tools/Tgstation.Server.ReleaseNotes/Component.cs @@ -1,4 +1,6 @@ -namespace Tgstation.Server.ReleaseNotes +using YamlDotNet.Serialization; + +namespace Tgstation.Server.ReleaseNotes { enum Component { @@ -6,7 +8,8 @@ enum Component Core, HostWatchdog, WebControlPanel, - HttpApi, + HttpApi, // can't be properly renamed due to changelog + GraphQLApi, DreamMakerApi, InteropApi, NugetCommon, diff --git a/tools/Tgstation.Server.ReleaseNotes/Program.cs b/tools/Tgstation.Server.ReleaseNotes/Program.cs index 801d10e779..bd2f868e20 100644 --- a/tools/Tgstation.Server.ReleaseNotes/Program.cs +++ b/tools/Tgstation.Server.ReleaseNotes/Program.cs @@ -91,9 +91,12 @@ static async Task Main(string[] args) 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 @@ static async Task Main(string[] args) 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 @@ static async Task Main(string[] args) 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 @@ async ValueTask DeleteMilestone(Milestone milestoneToDelete, int moveToMilestone var componentVersionDict = new Dictionary { { 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 @@ async ValueTask DeleteMilestone(Milestone milestoneToDelete, int moveToMilestone 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 @@ Version Parse(string elemName, bool controlPanel = false) var dict = new Dictionary { { Component.Core, Parse("TgsCoreVersion") }, - { Component.HttpApi, Parse("TgsApiVersion") }, + { Component.HttpApi, Parse("TgsRestVersion") }, + { Component.GraphQLApi, Parse("TgsGraphQLVersion") }, { Component.DreamMakerApi, Parse("TgsDmapiVersion") }, }; @@ -720,7 +727,8 @@ async Task CommitNotes(Component component, List notes) 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 @@ static async Task GenerateNotes(IGitHubClient client, Dictionary> { { 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 },