mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-29 16:11:05 +01:00
@@ -189,6 +189,20 @@ We have a script to do this.
|
||||
1. Run `dotnet ef migrations add SL<NameOfYourMigration> --context SqliteDatabaseContext`.
|
||||
1. Follow the above steps.
|
||||
|
||||
## Adding OAuth Providers
|
||||
|
||||
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. 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.
|
||||
1. Add a null entry to the default [appsettings.json](../src/Tgstation.Server.Host/appsettings.json).
|
||||
1. Update the main [README.md](../README.md) to indicate the new provider.
|
||||
1. Update the [API documentation](../docs/API.dox) to indicate the new provider.
|
||||
|
||||
TGS should now be able to accept authentication response tokens from your provider.
|
||||
|
||||
### Important Note About the \[Required\] Attribute.
|
||||
|
||||
We use this attribute to ensure EFCore generated tables are not nullable for specific properties. They are valid to be null in API communication. Do not use this attribute expecting the model validator to prevent null data in API request.
|
||||
|
||||
@@ -11,6 +11,7 @@ on:
|
||||
- master
|
||||
|
||||
env:
|
||||
TGS4_DOTNET_VERSION: 3.1.x
|
||||
TGS4_TEST_DISCORD_CHANNEL: ${{ secrets.DISCORD_CHANNEL_ID }}
|
||||
TGS4_TEST_DISCORD_TOKEN: ${{ secrets.DISCORD_TOKEN }}
|
||||
TGS4_TEST_IRC_CHANNEL: ${{ secrets.IRC_CHANNEL }}
|
||||
@@ -23,7 +24,7 @@ jobs:
|
||||
name: Build DMAPI
|
||||
env:
|
||||
BYOND_MAJOR: 513
|
||||
BYOND_MINOR: 1527
|
||||
BYOND_MINOR: 1536
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Install x86 libc Dependencies
|
||||
@@ -130,10 +131,10 @@ jobs:
|
||||
with:
|
||||
node-version: 12.x
|
||||
|
||||
- name: Setup dotnet 3.1.X
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: 3.1.x
|
||||
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
@@ -157,9 +158,17 @@ jobs:
|
||||
configuration: [ 'Debug', 'Release' ]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Clean package cache as a temporary workaround for actions/setup-dotnet#155
|
||||
run: dotnet clean && dotnet nuget locals all --clear
|
||||
|
||||
- name: Build
|
||||
run: dotnet build -c ${{ matrix.configuration }}
|
||||
|
||||
@@ -184,9 +193,14 @@ jobs:
|
||||
configuration: [ 'Debug', 'Release' ]
|
||||
runs-on: windows-latest
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
|
||||
|
||||
- name: Set General__UseBasicWatchdog
|
||||
if: ${{ matrix.watchdog-type == 'Basic' }}
|
||||
run: echo "General__UseBasicWatchdog=true" >> $env:GITHUB_ENV
|
||||
run: echo "General__UseBasicWatchdog=true" >> $Env:GITHUB_ENV
|
||||
|
||||
- name: Set TGS4_TEST_CONNECTION_STRING
|
||||
shell: bash
|
||||
@@ -199,15 +213,21 @@ jobs:
|
||||
|
||||
- name: Set TGS4_TEST_PULL_REQUEST_NUMBER
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: echo "TGS4_TEST_PULL_REQUEST_NUMBER=${{ github.event.number }}" >> $env:GITHUB_ENV
|
||||
run: echo "TGS4_TEST_PULL_REQUEST_NUMBER=${{ github.event.number }}" >> $Env:GITHUB_ENV
|
||||
|
||||
- name: Set TGS4_GITHUB_REF for PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: echo "TGS4_GITHUB_REF=${{ github.event.base_ref }}" >> $env:GITHUB_ENV
|
||||
run: echo "TGS4_GITHUB_REF=${{ github.base_ref }}" >> $Env:GITHUB_ENV
|
||||
|
||||
- name: Set TGS4_GITHUB_REF for push
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: echo "TGS4_GITHUB_REF=${{ github.event.ref }}" >> $env:GITHUB_ENV
|
||||
shell: bash
|
||||
run: |
|
||||
TEMP_GITHUB_REF="${{ github.event.ref }}"
|
||||
echo "TGS4_GITHUB_REF=${TEMP_GITHUB_REF##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Clean package cache as a temporary workaround for actions/setup-dotnet#155
|
||||
run: dotnet clean && dotnet nuget locals all --clear
|
||||
|
||||
- name: Run Integration Test
|
||||
run: |
|
||||
@@ -285,7 +305,7 @@ jobs:
|
||||
matrix:
|
||||
database-type: [ 'Sqlite', 'PostgresSql', 'MariaDB', 'MySql' ]
|
||||
watchdog-type: [ 'Basic', 'System' ]
|
||||
configuration: [ 'Debug', 'Release' ]
|
||||
configuration: [ 'Release' ]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Disable ptrace_scope
|
||||
@@ -305,7 +325,7 @@ jobs:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: ${{ matrix.dotnet }}
|
||||
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
|
||||
|
||||
- name: Set Sqlite Connection Info
|
||||
if: ${{ matrix.database-type == 'Sqlite' }}
|
||||
@@ -345,17 +365,20 @@ jobs:
|
||||
|
||||
- name: Set TGS4_GITHUB_REF for PR
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
run: echo "TGS4_GITHUB_REF=${{ github.event.base_ref }}" >> $GITHUB_ENV
|
||||
run: echo "TGS4_GITHUB_REF=${{ github.base_ref }}" >> $GITHUB_ENV
|
||||
|
||||
- name: Set TGS4_GITHUB_REF for push
|
||||
if: ${{ github.event_name == 'push' }}
|
||||
run: echo "TGS4_GITHUB_REF=${{ github.event.ref }}" >> $GITHUB_ENV
|
||||
shell: bash
|
||||
run: |
|
||||
TEMP_GITHUB_REF="${{ github.event.ref }}"
|
||||
echo "TGS4_GITHUB_REF=${TEMP_GITHUB_REF##*/}" >> $GITHUB_ENV
|
||||
|
||||
- name: Run Integration Test
|
||||
run: |
|
||||
cd tests/Tgstation.Server.Tests
|
||||
sleep 10
|
||||
dotnet test -c ${{ matrix.configuration }} -l "console;verbosity=detailed;noprogress=true" --collect:"XPlat Code Coverage" --settings ../../build/coverlet.runsettings -r ./TestResults
|
||||
dotnet test -c ${{ matrix.configuration }}NoService -l "console;verbosity=detailed;noprogress=true" --collect:"XPlat Code Coverage" --settings ../../build/coverlet.runsettings -r ./TestResults
|
||||
|
||||
- name: Store Code Coverage
|
||||
uses: actions/upload-artifact@v2
|
||||
@@ -369,13 +392,13 @@ jobs:
|
||||
cd src/Tgstation.Server.Host.Console
|
||||
dotnet publish -c ${{ matrix.configuration }} -o ../../Artifacts/Console
|
||||
cd ../Tgstation.Server.Host
|
||||
dotnet publish -c ${{ matrix.configuration }} --no-build -o ../../Artifacts/Console/lib/Default
|
||||
dotnet publish -c ${{ matrix.configuration }}NoService --no-build -o ../../Artifacts/Console/lib/Default
|
||||
|
||||
- name: Package Server Update Package
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'System' && matrix.database-type == 'PostgresSql' }}
|
||||
run: |
|
||||
cd src/Tgstation.Server.Host
|
||||
dotnet publish -c ${{ matrix.configuration }} --no-build -o ../../Artifacts/ServerUpdate
|
||||
dotnet publish -c ${{ matrix.configuration }}NoService --no-build -o ../../Artifacts/ServerUpdate
|
||||
|
||||
- name: Store Server Console
|
||||
if: ${{ matrix.configuration == 'Release' && matrix.watchdog-type == 'System' && matrix.database-type == 'MariaDB' }}
|
||||
@@ -436,96 +459,48 @@ jobs:
|
||||
name: linux-unit-test-coverage-Release
|
||||
path: ./code_coverage/unit_tests/linux_unit_tests_release
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Debug, Basic, Sqlite)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Debug-Basic-Sqlite
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_debug_basic_sqlite
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Release, Basic, Sqlite)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Release-Basic-Sqlite
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_release_basic_sqlite
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Debug, System, Sqlite)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Debug-System-Sqlite
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_debug_system_sqlite
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Release, System, Sqlite)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Release-System-Sqlite
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_release_system_sqlite
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Debug, Basic, PostgresSql)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Debug-Basic-PostgresSql
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_debug_basic_postgressql
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Release, Basic, PostgresSql)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Release-Basic-PostgresSql
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_release_basic_postgressql
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Debug, System, PostgresSql)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Debug-System-PostgresSql
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_debug_system_postgressql
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Release, System, PostgresSql)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Release-System-PostgresSql
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_release_system_mariadb
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Debug, Basic, MariaDB)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Debug-Basic-MariaDB
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_debug_basic_mariadb
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Release, Basic, MariaDB)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Release-Basic-MariaDB
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_release_basic_mariadb
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Debug, System, MariaDB)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Debug-System-MariaDB
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_debug_system_mariadb
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Release, System, MariaDB)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Release-System-MariaDB
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_release_system_mysql
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Debug, Basic, MySql)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Debug-Basic-MySql
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_debug_basic_mysql
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Release, Basic, MySql)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Release-Basic-MySql
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_release_basic_mysql
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Debug, System, MySql)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
name: linux-integration-test-coverage-Debug-System-MySql
|
||||
path: ./code_coverage/integration_tests/linux_integration_tests_debug_system_mysql
|
||||
|
||||
- name: Retrieve Linux Integration Test Coverage (Release, System, MySql)
|
||||
uses: actions/download-artifact@v2
|
||||
with:
|
||||
@@ -586,7 +561,7 @@ jobs:
|
||||
name: Deploy HTTP API
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' && contains(github.event.head_commit.message, '[APIDeploy]')
|
||||
if: github.event_name == 'push' && contains(github.event.head_commit.message, '[APIDeploy]')
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
@@ -629,7 +604,7 @@ jobs:
|
||||
name: Deploy DreamMaker API
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: windows-latest
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' && contains(github.event.head_commit.message, '[DMDeploy]')
|
||||
if: github.event_name == 'push' && contains(github.event.head_commit.message, '[DMDeploy]')
|
||||
steps:
|
||||
|
||||
- name: Checkout
|
||||
@@ -672,8 +647,13 @@ jobs:
|
||||
name: Deploy Nuget Packages
|
||||
needs: [upload-code-coverage, validate-openapi-spec]
|
||||
runs-on: ubuntu-latest
|
||||
if: github.event_name == 'push' && github.event.ref == 'refs/heads/master' && contains(github.event.head_commit.message, '[NugetDeploy]')
|
||||
if: github.event_name == 'push' && contains(github.event.head_commit.message, '[NugetDeploy]')
|
||||
steps:
|
||||
- name: Setup dotnet
|
||||
uses: actions/setup-dotnet@v1
|
||||
with:
|
||||
dotnet-version: ${{ env.TGS4_DOTNET_VERSION }}
|
||||
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
|
||||
@@ -27,9 +27,6 @@ jobs:
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- run: git checkout HEAD^2
|
||||
if: ${{ github.event_name == 'pull_request' }}
|
||||
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v1
|
||||
with:
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# tgstation-server v4:
|
||||
|
||||
 [](https://travis-ci.org/tgstation/tgstation-server) [](https://codecov.io/gh/tgstation/tgstation-server)
|
||||
 [](https://codecov.io/gh/tgstation/tgstation-server)
|
||||
|
||||
[](LICENSE) [](http://isitmaintained.com/project/tgstation/tgstation-server "Average time to resolve an issue") [](https://www.nuget.org/packages/Tgstation.Server.Api) [](https://www.nuget.org/packages/Tgstation.Server.Client)
|
||||
|
||||
@@ -57,7 +57,7 @@ docker run \
|
||||
--name="tgs" \ # Name for the container
|
||||
--cap-add=sys_nice \ # Recommended, allows tgs to schedule DreamDaemon as a higher priority process
|
||||
--init \ #Highly recommended, reaps potential zombie processes
|
||||
-p <tgs port>:<port configured with setup wizard> \ # Port bridge for accessing TGS
|
||||
-p 5000:5000 \ # Port bridge for accessing TGS, you can change this if you need
|
||||
-p 0.0.0.0:<public game port>:<public game port> \ # Port bridge for accessing DreamDaemon
|
||||
-v /path/to/your/configfile/directory:/config_data \ # Recommended, create a volume mapping for server configuration
|
||||
-v /path/to/store/instances:/tgs4_instances \ # Recommended, create a volume mapping for server instances
|
||||
@@ -86,9 +86,19 @@ The first time you run TGS4 you should be prompted with a configuration wizard w
|
||||
|
||||
This wizard will, generally, run whenever the server is launched without detecting the config json. Follow the instructions below to perform this process manually.
|
||||
|
||||
#### Configuration Methods
|
||||
|
||||
There are 3 primary supported ways to configure TGS:
|
||||
|
||||
- Modify the `appsettings.Production.json` file (Recommended).
|
||||
- Set environment variables in the form `Section__Subsection=value` or `Section__ArraySubsection__0=value` for arrays.
|
||||
- Set command line arguments in the form `--Section:Subsection=value` or `--Section:ArraySubsection:0=value` for arrays.
|
||||
|
||||
The latter two are not recommended as they cannot be dynamically changed at runtime. See more on ASP.NET core configuration [here](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1).
|
||||
|
||||
#### Manual Configuration
|
||||
|
||||
Create an `appsettings.Production.json` file next to `appsettings.json`. This will override the default settings in appsettings.json with your production settings. There are a few keys meant to be changed by hosts. Modifying any config files while the server is running will trigger a safe restart (Keeps DreamDaemon's running). Note these are all case-sensitive:
|
||||
Create an `appsettings.Production.json` file next to `appsettings.json`. This will override the default settings in appsettings.json with your production settings. There are a few keys meant to be changed by hosts. Modifying any config files while the server is running will trigger a safe restart (Keeps DreamDaemon instances running). Note these are all case-sensitive:
|
||||
|
||||
- `General:ConfigVersion`: Suppresses warnings about out of date config versions. You should change this after updating TGS to one with a new config version. The current version can be found on the releases page for your server version (This field did not exist before v4.4.0).
|
||||
|
||||
@@ -120,6 +130,41 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi
|
||||
|
||||
- `ControlPanel:AllowedOrigins`: Set the Access-Control-Allow-Origin headers to this list of origins for all responses (also enables all headers and methods). This is overridden by `ControlPanel:AllowAnyOrigin`
|
||||
|
||||
- `Swarm`: This section should be left `null` unless using the server swarm system. If this is to happen, ensure all swarm servers are set to connect to the same database.
|
||||
|
||||
- `Swarm:PrivateKey`: Should be a secure string set identically on all swarmed servers.
|
||||
|
||||
- `Swarm:ControllerAddress`: Should be set on all swarmed servers that are **not** the controller server and should be an address the controller server may be reached at.
|
||||
|
||||
- `Swarm:Address`: Should be set on all swarmed servers. Should be an address the server can be reached at by other servers in the swarm.
|
||||
|
||||
- `Swarm:Identifier` should be set uniquely on all swarmed servers. Used to identify the current server. This is also used to select which instances exist on the current machine and should not be changed post-setup.
|
||||
|
||||
- `Security:OAuth:<Provider Name>`: Sets the OAuth client ID and secret for a given `<Provider Name>`. The currently supported providers are `GitHub`, `Discord`, and `TGForums`. Setting these fields to `null` disables logins with the provider, but does not stop users from associating their accounts using the API. Sample Entry:
|
||||
```json
|
||||
{
|
||||
"Security": {
|
||||
"OAuth": {
|
||||
"Keycloak": {
|
||||
"ClientId": "...",
|
||||
"ClientSecret": "...",
|
||||
"RedirectUrl": "...",
|
||||
"ServerUrl": "..."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
The following providers use the `RedirectUrl` setting:
|
||||
|
||||
- GitHub
|
||||
- TGForums
|
||||
- Keycloak
|
||||
|
||||
The following providers use the `ServerUrl` setting:
|
||||
|
||||
- Keycloak
|
||||
|
||||
### Database Configuration
|
||||
|
||||
If using a MariaDB/MySQL server, our client library [recommends you set 'utf8mb4' as your default charset](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1-recommended-server-charset) disregard at your own risk.
|
||||
@@ -281,6 +326,12 @@ Example VirtualHost Entry
|
||||
</IfModule>
|
||||
```
|
||||
|
||||
## Swarmed Servers
|
||||
|
||||
Multiple tgstation-servers can be linked together in a swarm. The main benefit of this is allowing for users, groups, and permissions to be shared across the servers. Servers in a swarm must connect to the same database, use the same tgstation-server version, and have their own unique names.
|
||||
|
||||
In a swarm, one server is designated the 'controller'. This is the server other 'node's in the swarm communicate with and coordinates group updates. Issuing an update command to one server in a swarm will update them all to the specified version.
|
||||
|
||||
## Usage
|
||||
|
||||
tgstation-server v4 is controlled via a RESTful HTTP json API. Documentation on this API can be found [here](https://tgstation.github.io/tgstation-server/api.html). This section serves to document the concepts of the server. The API is versioned separately from the release version. A specification for it can be found in the api-vX.X.X git releases/tags.
|
||||
@@ -371,11 +422,10 @@ TGS 4 can self update without stopping your DreamDaemon servers. Any V4 release
|
||||
|
||||
Here are tools for interacting with the TGS 4 web API
|
||||
|
||||
- [tgstation-server-control-panel]: Official client and included with the server (WIP). A react web app for using tgstation-server.
|
||||
- [Tgstation.Server.ControlPanel](https://github.com/tgstation/Tgstation.Server.ControlPanel): Official client. A cross platform GUI for using tgstation-server
|
||||
- [Tgstation.Server.Client](https://www.nuget.org/packages/Tgstation.Server.Client): A nuget .NET Standard 2.0 TAP based library for communicating with tgstation-server
|
||||
- [Tgstation.Server.Api](https://www.nuget.org/packages/Tgstation.Server.Api): A nuget .NET Standard 2.0 library containing API definitions for tgstation-server
|
||||
- [Postman](https://www.getpostman.com/): This repository contains [TGS.postman_collection.json](tools/TGS.postman_collection.json) which is used during development for testing. Contains example requests for all endpoints but takes some knowledge to use (Note that the pre-request script is configured to login the default admin user for every request)
|
||||
- [tgstation-server-webpanel](https://github.com/tgstation/tgstation-server-webpanel): Official client and included with the server (WIP). A react web app for using tgstation-server.
|
||||
- [Tgstation.Server.ControlPanel](https://github.com/tgstation/Tgstation.Server.ControlPanel): Official client. A cross platform GUI for using tgstation-server. Feature complete but lacks OAuth login options.
|
||||
- [Tgstation.Server.Client](https://www.nuget.org/packages/Tgstation.Server.Client): A nuget .NET Standard 2.0 TAP based library for communicating with tgstation-server. Feature complete.
|
||||
- [Tgstation.Server.Api](https://www.nuget.org/packages/Tgstation.Server.Api): A nuget .NET Standard 2.0 library containing API definitions for tgstation-server. Feature complete.
|
||||
|
||||
Contact project maintainers to get your client added to this list
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<PropertyGroup>
|
||||
<!-- This is in it's own file to help incremental building, changing it causes a complete rebuild of the web panel -->
|
||||
<TgsControlPanelVersion>1.0.1</TgsControlPanelVersion>
|
||||
<TgsControlPanelVersion>2.0.1</TgsControlPanelVersion>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
+9
-5
@@ -1,10 +1,10 @@
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-bionic AS build
|
||||
FROM mcr.microsoft.com/dotnet/core/sdk:3.1-buster AS build
|
||||
|
||||
# install node and npm
|
||||
# replace shell with bash so we can source files
|
||||
RUN curl --silent -o- https://raw.githubusercontent.com/creationix/nvm/v0.31.2/install.sh | sh
|
||||
|
||||
ENV NODE_VERSION=10.13.0
|
||||
ENV NODE_VERSION 10.13.0
|
||||
ENV NVM_DIR /root/.nvm
|
||||
|
||||
RUN . $NVM_DIR/nvm.sh \
|
||||
@@ -12,7 +12,7 @@ RUN . $NVM_DIR/nvm.sh \
|
||||
&& nvm use $NODE_VERSION \
|
||||
&& apt-get update \
|
||||
&& apt-get install -y \
|
||||
dos2unix \
|
||||
dos2unix \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV NODE_PATH $NVM_DIR/v$NODE_VERSION/lib/node_modules
|
||||
@@ -56,14 +56,18 @@ RUN dotnet publish -c Release -o /app
|
||||
WORKDIR /repo/src/Tgstation.Server.Host
|
||||
RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app
|
||||
|
||||
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-bionic
|
||||
FROM mcr.microsoft.com/dotnet/core/aspnet:3.1-buster-slim
|
||||
|
||||
#needed for byond
|
||||
RUN apt-get update \
|
||||
&& apt-get install -y \
|
||||
gcc-multilib \
|
||||
gdb \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
multiarch-support \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& curl http://security.debian.org/debian-security/pool/updates/main/o/openssl/libssl1.0.0_1.0.1t-1+deb8u12_amd64.deb --output libssl1.0.0.deb \
|
||||
&& dpkg -i libssl1.0.0.deb \
|
||||
&& rm libssl1.0.0.deb
|
||||
|
||||
EXPOSE 5000
|
||||
|
||||
|
||||
@@ -4,8 +4,9 @@
|
||||
"no_operation_id": "error",
|
||||
"operation_id_case_convention": "off",
|
||||
"no_summary": "error",
|
||||
"no_array_responses": "off",
|
||||
"no_array_responses": "error",
|
||||
"parameter_order": "error",
|
||||
"undefined_tag": "off",
|
||||
"unused_tag": "error",
|
||||
"operation_id_naming_convention": "off"
|
||||
},
|
||||
@@ -44,12 +45,15 @@
|
||||
"no_property_description": "off",
|
||||
"description_mentions_json": "error",
|
||||
"array_of_arrays": "error",
|
||||
"inconsistent_property_type": "error",
|
||||
"property_case_convention": "off",
|
||||
"enum_case_convention": "error"
|
||||
"property_case_collision": "error",
|
||||
"enum_case_convention": "off",
|
||||
"undefined_required_properties": "error"
|
||||
},
|
||||
"walker": {
|
||||
"no_empty_descriptions": "error",
|
||||
"has_circular_references": "off",
|
||||
"has_circular_references": "error",
|
||||
"$ref_siblings": "error",
|
||||
"duplicate_sibling_description": "error",
|
||||
"incorrect_ref_pattern": "error"
|
||||
@@ -76,7 +80,11 @@
|
||||
"responses": {
|
||||
"no_response_codes": "error",
|
||||
"no_success_response_codes": "error",
|
||||
"no_response_body": "error",
|
||||
"ibm_status_code_guidelines": "off"
|
||||
},
|
||||
"schemas": {
|
||||
"json_or_param_binary_string": "error"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+5
-5
@@ -3,11 +3,11 @@
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<Import Project="ControlPanelVersion.props" />
|
||||
<PropertyGroup>
|
||||
<TgsCoreVersion>4.6.3</TgsCoreVersion>
|
||||
<TgsConfigVersion>2.1.1</TgsConfigVersion>
|
||||
<TgsApiVersion>7.4.0</TgsApiVersion>
|
||||
<TgsClientVersion>8.4.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>5.2.9</TgsDmapiVersion>
|
||||
<TgsCoreVersion>4.7.0</TgsCoreVersion>
|
||||
<TgsConfigVersion>2.2.0</TgsConfigVersion>
|
||||
<TgsApiVersion>8.1.2</TgsApiVersion>
|
||||
<TgsClientVersion>9.1.1</TgsClientVersion>
|
||||
<TgsDmapiVersion>5.2.10</TgsDmapiVersion>
|
||||
<TgsHostWatchdogVersion>1.1.0</TgsHostWatchdogVersion>
|
||||
<TgsContainerScriptVersion>1.2.0</TgsContainerScriptVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
+64
-21
@@ -63,7 +63,7 @@ TGS will only every return the response codes listed here
|
||||
- 204: No Content. Identical to 200 with no response body.
|
||||
- 400: Bad Request. The response body will contain an @ref Tgstation.Server.Api.Models.ErrorMessage model detailing the error
|
||||
- 401: Unauthorized. Invalid or expired credentials were provided. Check rights APIs for updates. See @ref api_auth for details
|
||||
- 403: Forbidden. User tried to make a request they were not allowed to perform.
|
||||
- 403: Forbidden. User tried to make a request they were not allowed to perform.
|
||||
- 404: Not found. A resource was requested that had never existed. In the case of retrieving a resource by ID, it could potentially exist in the future
|
||||
- 406: Not Acceptable. Consequence of failing to provide an Accept header
|
||||
- 408: Request Timeout. The client took to long to continue a request
|
||||
@@ -77,9 +77,27 @@ TGS will only every return the response codes listed here
|
||||
- 501: Not implemented. Functionality not available in the current server version
|
||||
- 503: Service unavailable. The server is either starting up or shutting down and isn't ready to respond to requests. You can try again soon and a response/lack thereof will indicate which of the two events it was
|
||||
|
||||
@section api_ver Server Information
|
||||
|
||||
The versions of the TGS host can be retireved with this request.
|
||||
|
||||
Note: This endpoint does not require authentication
|
||||
|
||||
GET "/" => @ref Tgstation.Server.Api.Models.ServerInformation
|
||||
|
||||
The Version model fields are based on the C# one and looks like this:
|
||||
|
||||
@code{.json}
|
||||
{
|
||||
"version": "<major>.<minor>.<patch>.<revision>"
|
||||
}
|
||||
@endcode
|
||||
|
||||
Other fields may be present in the Version model but should be ignored. See a description of these version numbers <a href="https://github.com/tgstation/tgstation-server/blob/master/.github/CONTRIBUTING.md#versioning">here</a>.
|
||||
|
||||
@section api_auth Authentication
|
||||
|
||||
Every request made to TGS requires authentication. It is provided in the form of the Authorization header.
|
||||
Every request made to TGS requires authentication. It is provided in the form of the Authorization header.
|
||||
|
||||
The first request made to TGS must be to login the user
|
||||
|
||||
@@ -87,7 +105,7 @@ POST "/" => @ref Tgstation.Server.Api.Models.Token
|
||||
|
||||
Headers:
|
||||
|
||||
- Authorization:basic `<Base64 encoded credentials in the form username:password>`
|
||||
- Authorization:Basic `<Base64 encoded credentials in the form username:password>`
|
||||
|
||||
If the provided credentials are valid and your user account is enabled you will recieve a @ref Tgstation.Server.Api.Models.Token object
|
||||
@code{.json}
|
||||
@@ -100,10 +118,37 @@ If your account is disabled, you will recieve a 403 response.
|
||||
|
||||
You may recognize the bearer value as a <a href="https://jwt.io">Json Web Token</a>. This is a secure representation of your identity to the server. It expires after a set period of time or until your password changes. It must be present for requests made to all other APIs. To do so add the following header to your other requests
|
||||
|
||||
- Authorization:bearer `<your bearer token>`
|
||||
- Authorization:Bearer `<your bearer token>`
|
||||
|
||||
Continue to use this token until you begin to recieve 401 responses from the API. Then repeat the process to get a new one if your credentials are still valid
|
||||
|
||||
@subsection api_auth_o OAuth 2.0
|
||||
|
||||
TGS4 supports OAuth 2.0 with select providers for authentication.
|
||||
|
||||
The flow for this is as follows:
|
||||
|
||||
- Retrieve the @ref api_ver to find out which @ref Tgstation.Server.Api.Models.OAuthProvider are enabled and their respective information.
|
||||
- Send the user to the Authorization Request endpoint for the provider using the client ID from above. See https://tools.ietf.org/html/rfc6749#section-4.1.1. DO NOT specify a redirect URI, this should be configured in the provider.
|
||||
- Retrieve the authorization response code after successfully completing the authorize step above.
|
||||
- Perform the following request:
|
||||
|
||||
POST "/" => @ref Tgstation.Server.Api.Models.Token
|
||||
|
||||
Headers:
|
||||
|
||||
- Authorization:OAuth `OAuth Authorization Response Code`
|
||||
- OAuthProvider:<Provider Name>
|
||||
|
||||
You will be granted a bearer token as in basic auth. This will have an extended expiration to avoid repeating the entire process.
|
||||
|
||||
@subsubsection api_auth_o_providers Supported Providers
|
||||
|
||||
- GitHub: https://developer.github.com/apps/building-oauth-apps/authorizing-oauth-apps
|
||||
- Discord: https://discord.com/developers/docs/topics/oauth2
|
||||
- TGForums: https://tgstation13.org/phpBB/viewtopic.php?f=45&t=9922
|
||||
- Keycloak: https://plugins.miniorange.com/keycloak-single-sign-on-wordpress-sso-oauth-openid-connect
|
||||
|
||||
@section api_perms Permissions
|
||||
|
||||
Almost all actions available require some level of user permissions. Which are divided into two categories:
|
||||
@@ -118,6 +163,20 @@ I GET "/InstanceUser" => @ref Tgstation.Server.Api.Models.InstanceUser
|
||||
|
||||
See individual documentation of each permission enum for their usage
|
||||
|
||||
@section api_transfer File Transfers
|
||||
|
||||
Certain responses inherit from @ref Tgstation.Server.Api.Models.FileTicketResult. These are special in that, having that model's @ref Tgstation.Server.Api.Models.FileTicketResult.FileTicket field populated indicates there is a pending file download or upload to take place. These are handled in the "/Transfer" endpoint.
|
||||
|
||||
To perform a file download make the following request:
|
||||
|
||||
GET "/Transfer?ticket=<@ref Tgstation.Server.Api.Models.FileTicketResult.FileTicket>" => application/octet-stream
|
||||
|
||||
To perform a file upload make the following request:
|
||||
|
||||
PUT "/Transfer?ticket=<@ref Tgstation.Server.Api.Models.FileTicketResult.FileTicket>" application/octet-stream => OK
|
||||
|
||||
File tickets are only valid for a short time after the initial request is made and should be dealt with immediately. Ensure that the file tickets are properly URL encoded.
|
||||
|
||||
@section api_user User Management
|
||||
|
||||
TGS start with one user: "Admin". The password is "ISolemlySwearToDeleteTheDataDirectory"
|
||||
@@ -150,22 +209,6 @@ I DELETE "/InstanceUser/{UserId}" => OK
|
||||
|
||||
Users with the permission to modify @ref Tgstation.Server.Api.Models.Instance objects can also gain user editing rights for any Instance. See @ref api_instance
|
||||
|
||||
@section api_ver Version
|
||||
|
||||
The versions of the TGS host can be retireved with this request
|
||||
|
||||
GET "/" => @ref Tgstation.Server.Api.Models.ServerInformation
|
||||
|
||||
The Version model fields are based on the C# one and looks like this:
|
||||
|
||||
@code{.json}
|
||||
{
|
||||
"version": "<major>.<minor>.<patch>.<revision>"
|
||||
}
|
||||
@endcode
|
||||
|
||||
Other fields may be present in the Version model but should be ignored. See a description of these version numbers <a href="https://github.com/tgstation/tgstation-server/blob/master/.github/CONTRIBUTING.md#versioning">here</a>.
|
||||
|
||||
@section api_admin Server-wide Administrative Actions
|
||||
|
||||
You can retrieve server update information with this
|
||||
@@ -349,7 +392,7 @@ If the server detects a set of @ref Tgstation.Server.Api.Models.TestMergeParamet
|
||||
|
||||
@subsubsection api_repounsetauth Unsetting Authentication
|
||||
|
||||
The repository uses the @ref Tgstation.Server.Api.Models.Repository.AccessUser and @ref Tgstation.Server.Api.Models.Repository.AccessToken credentials to access the remote repository if these fields are set. To unset them you must set both of them to an empty string like so
|
||||
The repository uses the @ref Tgstation.Server.Api.Models.Repository.AccessUser and @ref Tgstation.Server.Api.Models.Repository.AccessToken credentials to access the remote repository if these fields are set. To unset them you must set both of them to an empty string like so
|
||||
|
||||
@code{.json}
|
||||
{
|
||||
|
||||
+10
-10
@@ -1,6 +1,6 @@
|
||||
// tgstation-server DMAPI
|
||||
|
||||
#define TGS_DMAPI_VERSION "5.2.9"
|
||||
#define TGS_DMAPI_VERSION "5.2.10"
|
||||
|
||||
// All functions and datums outside this document are subject to change with any version and should not be relied on.
|
||||
|
||||
@@ -67,7 +67,7 @@
|
||||
#define TGS_EVENT_REPO_CHECKOUT 1
|
||||
/// When the repository performs a fetch operation. No parameters
|
||||
#define TGS_EVENT_REPO_FETCH 2
|
||||
/// When the repository merges a pull request. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
|
||||
/// When the repository test merges. Parameters: PR Number, PR Sha, (Nullable) Comment made by TGS user
|
||||
#define TGS_EVENT_REPO_MERGE_PULL_REQUEST 3
|
||||
/// Before the repository makes a sychronize operation. Parameters: Absolute repostiory path
|
||||
#define TGS_EVENT_REPO_PRE_SYNCHRONIZE 4
|
||||
@@ -190,21 +190,21 @@
|
||||
|
||||
/// Represents a merge of a GitHub pull request.
|
||||
/datum/tgs_revision_information/test_merge
|
||||
/// The pull request number.
|
||||
/// The test merge number.
|
||||
var/number
|
||||
/// The pull request title when it was merged.
|
||||
/// The test merge source's title when it was merged.
|
||||
var/title
|
||||
/// The pull request body when it was merged.
|
||||
/// The test merge source's body when it was merged.
|
||||
var/body
|
||||
/// The GitHub username of the pull request's author.
|
||||
/// The Username of the test merge source's author.
|
||||
var/author
|
||||
/// An http URL to the pull request.
|
||||
/// An http URL to the test merge source.
|
||||
var/url
|
||||
/// The SHA of the pull request when that was merged.
|
||||
/// The SHA of the test merge when that was merged.
|
||||
var/pull_request_commit
|
||||
/// ISO 8601 timestamp of when the pull request was merged.
|
||||
/// ISO 8601 timestamp of when the test merge was created on TGS.
|
||||
var/time_merged
|
||||
/// (Nullable) Comment left by the TGS user who initiated the merge..
|
||||
/// Optional comment left by the TGS user who initiated the merge.
|
||||
var/comment
|
||||
|
||||
/// Represents a connected chat channel.
|
||||
|
||||
@@ -98,18 +98,19 @@
|
||||
return json_encode(response)
|
||||
|
||||
/datum/tgs_api/v5/OnTopic(T)
|
||||
if(!initialized)
|
||||
return FALSE //continue world/Topic
|
||||
|
||||
var/list/params = params2list(T)
|
||||
var/json = params[DMAPI5_TOPIC_DATA]
|
||||
if(!json)
|
||||
return FALSE
|
||||
return FALSE // continue to /world/Topic
|
||||
|
||||
var/list/topic_parameters = json_decode(json)
|
||||
if(!topic_parameters)
|
||||
return TopicResponse("Invalid topic parameters json!");
|
||||
|
||||
if(!initialized)
|
||||
TGS_WARNING_LOG("Missed topic due to not being initialized: [T]")
|
||||
return TRUE // too early to handle, but it's still our responsibility
|
||||
|
||||
var/their_sCK = topic_parameters[DMAPI5_PARAMETER_ACCESS_IDENTIFIER]
|
||||
if(their_sCK != access_identifier)
|
||||
return TopicResponse("Failed to decode [DMAPI5_PARAMETER_ACCESS_IDENTIFIER] from: [json]!");
|
||||
|
||||
@@ -8,6 +8,7 @@ using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
{
|
||||
@@ -17,24 +18,34 @@ namespace Tgstation.Server.Api
|
||||
public sealed class ApiHeaders
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ApiVersion"/> header key
|
||||
/// The <see cref="ApiVersion"/> header key.
|
||||
/// </summary>
|
||||
public const string ApiVersionHeader = "Api";
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceId"/> header key
|
||||
/// The <see cref="InstanceId"/> header key.
|
||||
/// </summary>
|
||||
public const string InstanceIdHeader = "Instance";
|
||||
|
||||
/// <summary>
|
||||
/// The JWT authentication header scheme
|
||||
/// The <see cref="OAuthProvider"/> header key.
|
||||
/// </summary>
|
||||
public const string JwtAuthenticationScheme = "bearer";
|
||||
public const string OAuthProviderHeader = "OAuthProvider";
|
||||
|
||||
/// <summary>
|
||||
/// The JWT authentication header scheme
|
||||
/// </summary>
|
||||
public const string BasicAuthenticationScheme = "basic";
|
||||
public const string BearerAuthenticationScheme = "Bearer";
|
||||
|
||||
/// <summary>
|
||||
/// The JWT authentication header scheme
|
||||
/// </summary>
|
||||
public const string BasicAuthenticationScheme = "Basic";
|
||||
|
||||
/// <summary>
|
||||
/// The JWT authentication header scheme
|
||||
/// </summary>
|
||||
public const string OAuthAuthenticationScheme = "OAuth";
|
||||
|
||||
/// <summary>
|
||||
/// The current <see cref="System.Reflection.AssemblyName"/>
|
||||
@@ -47,7 +58,7 @@ namespace Tgstation.Server.Api
|
||||
public static readonly Version Version = AssemblyName.Version.Semver();
|
||||
|
||||
/// <summary>
|
||||
/// The instance <see cref="Models.EntityId.Id"/> being accessed
|
||||
/// The instance <see cref="EntityId.Id"/> being accessed
|
||||
/// </summary>
|
||||
public long? InstanceId { get; set; }
|
||||
|
||||
@@ -82,9 +93,14 @@ namespace Tgstation.Server.Api
|
||||
public string? Password { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the header uses password or JWT authentication
|
||||
/// The <see cref="Models.OAuthProvider"/> the <see cref="Token"/> is for, if any.
|
||||
/// </summary>
|
||||
public bool IsTokenAuthentication => Token != null;
|
||||
public OAuthProvider? OAuthProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If the header uses password or TGS JWT authentication.
|
||||
/// </summary>
|
||||
public bool IsTokenAuthentication => Token != null && !OAuthProvider.HasValue;
|
||||
|
||||
/// <summary>
|
||||
/// Checks if a given <paramref name="otherVersion"/> is compatible with our own
|
||||
@@ -98,12 +114,15 @@ namespace Tgstation.Server.Api
|
||||
/// </summary>
|
||||
/// <param name="userAgent">The value of <see cref="UserAgent"/></param>
|
||||
/// <param name="token">The value of <see cref="Token"/></param>
|
||||
public ApiHeaders(ProductHeaderValue userAgent, string token) : this(userAgent, token, null, null)
|
||||
/// <param name="oauthProvider">The value of <see cref="OAuthProvider"/>.</param>
|
||||
public ApiHeaders(ProductHeaderValue userAgent, string token, OAuthProvider? oauthProvider = null) : this(userAgent, token, null, null)
|
||||
{
|
||||
if (userAgent == null)
|
||||
throw new ArgumentNullException(nameof(userAgent));
|
||||
if (token == null)
|
||||
throw new ArgumentNullException(nameof(token));
|
||||
|
||||
OAuthProvider = oauthProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -126,8 +145,10 @@ namespace Tgstation.Server.Api
|
||||
/// Construct and validates <see cref="ApiHeaders"/> from a set of <paramref name="requestHeaders"/>
|
||||
/// </summary>
|
||||
/// <param name="requestHeaders">The <see cref="RequestHeaders"/> containing the <see cref="ApiHeaders"/></param>
|
||||
/// <param name="ignoreMissingAuth">If a missing <see cref="HeaderNames.Authorization"/> should be ignored.</param>
|
||||
/// <exception cref="HeadersException">Thrown if the <paramref name="requestHeaders"/> constitue invalid <see cref="ApiHeaders"/>.</exception>
|
||||
public ApiHeaders(RequestHeaders requestHeaders)
|
||||
#pragma warning disable CA1502
|
||||
public ApiHeaders(RequestHeaders requestHeaders, bool ignoreMissingAuth = false)
|
||||
{
|
||||
if (requestHeaders == null)
|
||||
throw new ArgumentNullException(nameof(requestHeaders));
|
||||
@@ -164,7 +185,10 @@ namespace Tgstation.Server.Api
|
||||
AddError(HeaderTypes.Api, $"Malformed {ApiVersionHeader} header!");
|
||||
|
||||
if (!requestHeaders.Headers.TryGetValue(HeaderNames.Authorization, out StringValues authorization))
|
||||
AddError(HeaderTypes.Authorization, $"Missing {HeaderNames.Authorization} header!");
|
||||
{
|
||||
if (!ignoreMissingAuth)
|
||||
AddError(HeaderTypes.Authorization, $"Missing {HeaderNames.Authorization} header!");
|
||||
}
|
||||
else
|
||||
{
|
||||
var auth = authorization.First();
|
||||
@@ -187,11 +211,22 @@ namespace Tgstation.Server.Api
|
||||
InstanceId = instanceId;
|
||||
}
|
||||
|
||||
#pragma warning disable CA1308 // Normalize strings to uppercase
|
||||
switch (scheme.ToLowerInvariant())
|
||||
#pragma warning restore CA1308 // Normalize strings to uppercase
|
||||
switch (scheme)
|
||||
{
|
||||
case JwtAuthenticationScheme:
|
||||
case OAuthAuthenticationScheme:
|
||||
if (requestHeaders.Headers.TryGetValue(OAuthProviderHeader, out StringValues oauthProviderValues))
|
||||
{
|
||||
var oauthProviderString = oauthProviderValues.First();
|
||||
if (Enum.TryParse<OAuthProvider>(oauthProviderString, out var oauthProvider))
|
||||
OAuthProvider = oauthProvider;
|
||||
else
|
||||
AddError(HeaderTypes.OAuthProvider, "Invalid OAuth provider!");
|
||||
}
|
||||
else
|
||||
AddError(HeaderTypes.OAuthProvider, $"Missing {OAuthProviderHeader} header!");
|
||||
|
||||
goto case BearerAuthenticationScheme;
|
||||
case BearerAuthenticationScheme:
|
||||
Token = parameter;
|
||||
break;
|
||||
case BasicAuthenticationScheme:
|
||||
@@ -203,12 +238,12 @@ namespace Tgstation.Server.Api
|
||||
}
|
||||
catch
|
||||
{
|
||||
throw new InvalidOperationException("Invalid basic Authorization header!");
|
||||
throw new InvalidOperationException($"Invalid basic {HeaderNames.Authorization} header!");
|
||||
}
|
||||
|
||||
var basicAuthSplits = joinedString.Split(new char[] { ':' }, StringSplitOptions.RemoveEmptyEntries);
|
||||
if (basicAuthSplits.Length < 2)
|
||||
throw new InvalidOperationException("Invalid basic Authorization header!");
|
||||
throw new InvalidOperationException($"Invalid basic {HeaderNames.Authorization} header!");
|
||||
|
||||
Username = basicAuthSplits.First();
|
||||
Password = String.Concat(basicAuthSplits.Skip(1));
|
||||
@@ -226,6 +261,7 @@ namespace Tgstation.Server.Api
|
||||
|
||||
ApiVersion = apiVersion!.Semver();
|
||||
}
|
||||
#pragma warning restore CA1502
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="ApiHeaders"/>
|
||||
@@ -253,7 +289,7 @@ namespace Tgstation.Server.Api
|
||||
/// Set <see cref="HttpRequestHeaders"/> using the <see cref="ApiHeaders"/>. This initially clears <paramref name="headers"/>
|
||||
/// </summary>
|
||||
/// <param name="headers">The <see cref="HttpRequestHeaders"/> to set</param>
|
||||
/// <param name="instanceId">The instance <see cref="Models.EntityId.Id"/> for the request</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> for the request</param>
|
||||
public void SetRequestHeaders(HttpRequestHeaders headers, long? instanceId = null)
|
||||
{
|
||||
if (headers == null)
|
||||
@@ -263,12 +299,16 @@ namespace Tgstation.Server.Api
|
||||
|
||||
headers.Clear();
|
||||
headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Json));
|
||||
if (IsTokenAuthentication)
|
||||
headers.Authorization = new AuthenticationHeaderValue(JwtAuthenticationScheme, Token);
|
||||
else
|
||||
if (!IsTokenAuthentication)
|
||||
headers.Authorization = new AuthenticationHeaderValue(
|
||||
BasicAuthenticationScheme,
|
||||
Convert.ToBase64String(Encoding.UTF8.GetBytes($"{Username}:{Password}")));
|
||||
else
|
||||
{
|
||||
headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, Token);
|
||||
if (OAuthProvider.HasValue)
|
||||
headers.Add(OAuthProviderHeader, OAuthProvider.ToString());
|
||||
}
|
||||
|
||||
headers.UserAgent.Add(new ProductInfoHeaderValue(UserAgent));
|
||||
headers.Add(ApiVersionHeader, new ProductHeaderValue(AssemblyName.Name, ApiVersion.ToString()).ToString());
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api
|
||||
{
|
||||
@@ -24,13 +24,18 @@ namespace Tgstation.Server.Api
|
||||
Accept = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Api header.
|
||||
/// <see cref="ApiHeaders.ApiVersionHeader"/>.
|
||||
/// </summary>
|
||||
Api = 4,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="Microsoft.Net.Http.Headers.HeaderNames.Authorization"/>
|
||||
/// </summary>
|
||||
Authorization = 8
|
||||
Authorization = 8,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="ApiHeaders.OAuthProviderHeader"/>.
|
||||
/// </summary>
|
||||
OAuthProvider = 16,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
@@ -7,11 +7,6 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
public sealed class Administration
|
||||
{
|
||||
/// <summary>
|
||||
/// If the server is running on a windows operating system
|
||||
/// </summary>
|
||||
public bool WindowsHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The GitHub repository the server is built to recieve updates from
|
||||
/// </summary>
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a BYOND installation. <see cref="RawData.Content"/> is used to upload custom BYOND version zip files, though <see cref="Version"/> must still be set.
|
||||
/// Represents a BYOND installation. <see cref="FileTicketResult.FileTicket"/> is used to upload custom BYOND version zip files, though <see cref="Version"/> must still be set.
|
||||
/// </summary>
|
||||
public sealed class Byond : RawData
|
||||
public sealed class Byond : FileTicketResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="System.Version"/> of the <see cref="Byond"/> installation used for new compiles. Will be <see langword="null"/> if the user does not have permission to view it or there is no BYOND version installed. Only considers the <see cref="Version.Major"/> and <see cref="Version.Minor"/> numbers.
|
||||
@@ -17,5 +16,10 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The <see cref="Job"/> being used to install a new <see cref="Version"/>
|
||||
/// </summary>
|
||||
public Job? InstallJob { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If a custom BYOND version is to be uploaded.
|
||||
/// </summary>
|
||||
public bool? UploadCustomZip { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
@@ -11,7 +11,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The IRC channel name. Also potentially contains the channel passsword (if separated by a colon).
|
||||
/// If multiple copies of the same channel with different keys are added to the server, the one that will be used is undefined.
|
||||
/// </summary>
|
||||
[StringLength(Limits.MaximumIndexableStringLength)]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? IrcChannel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
@@ -19,5 +19,10 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The <see cref="Byond.Version"/> the <see cref="CompileJob"/> was made with
|
||||
/// </summary>
|
||||
public Version? ByondVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The origin <see cref="Uri"/> of the repository the compile job was built from.
|
||||
/// </summary>
|
||||
public Uri? RepositoryOrigin { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a game configuration file. Create and delete actions uncerimonuously overwrite/delete files
|
||||
/// </summary>
|
||||
public sealed class ConfigurationFile : RawData
|
||||
public sealed class ConfigurationFile : FileTicketResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The path to the <see cref="ConfigurationFile"/> file
|
||||
|
||||
@@ -64,10 +64,10 @@ namespace Tgstation.Server.Api.Models
|
||||
CannotChangeServerSuite,
|
||||
|
||||
/// <summary>
|
||||
/// A required GitHub API request failed.
|
||||
/// A required remote API request failed.
|
||||
/// </summary>
|
||||
[Description("A required GitHub request returned an API error!")]
|
||||
GitHubApiError,
|
||||
[Description("A required remote API request returned an error!")]
|
||||
RemoteApiError,
|
||||
|
||||
/// <summary>
|
||||
/// A server update was requested while another was in progress.
|
||||
@@ -76,7 +76,7 @@ namespace Tgstation.Server.Api.Models
|
||||
ServerUpdateInProgress,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to change something other than the capitalization of a <see cref="Internal.User.Name"/>.
|
||||
/// Attempted to change something other than the capitalization of a <see cref="Internal.UserBase.Name"/>.
|
||||
/// </summary>
|
||||
[Description("Can only change the capitalization of a user's name!")]
|
||||
UserNameChange,
|
||||
@@ -88,7 +88,7 @@ namespace Tgstation.Server.Api.Models
|
||||
UserSidChange,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to create a <see cref="User"/> with a <see cref="Internal.User.Name"/> and <see cref="Internal.User.SystemIdentifier"/>.
|
||||
/// Attempted to create a <see cref="User"/> with a <see cref="Internal.UserBase.Name"/> and <see cref="Internal.User.SystemIdentifier"/>.
|
||||
/// </summary>
|
||||
[Description("A user cannot have both a name and systemIdentifier!")]
|
||||
UserMismatchNameSid,
|
||||
@@ -106,13 +106,13 @@ namespace Tgstation.Server.Api.Models
|
||||
UserPasswordLength,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to create a <see cref="User"/> with a ':' in the <see cref="Internal.User.Name"/>.
|
||||
/// Attempted to create a <see cref="User"/> with a ':' in the <see cref="Internal.UserBase.Name"/>.
|
||||
/// </summary>
|
||||
[Description("User names cannot contain the ':' character!")]
|
||||
UserColonInName,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to create a <see cref="User"/> with a <see langword="null"/> or whitespace <see cref="Internal.User.Name"/>.
|
||||
/// Attempted to create a <see cref="User"/> with a <see langword="null"/> or whitespace <see cref="Internal.UserBase.Name"/>.
|
||||
/// </summary>
|
||||
[Description("User's name is missing or invalid whitespace!")]
|
||||
UserMissingName,
|
||||
@@ -166,7 +166,7 @@ namespace Tgstation.Server.Api.Models
|
||||
RequiresPosixSystemIdentity,
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ConfigurationFile"/> was attem updated
|
||||
/// A <see cref="ConfigurationFile"/> was updated.
|
||||
/// </summary>
|
||||
[Description("This existing file hash does not match, the file has beeen updated!")]
|
||||
ConfigurationFileUpdated,
|
||||
@@ -234,7 +234,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// <see cref="Repository.NewTestMerges"/> contained duplicate <see cref="TestMergeParameters.Number"/>s.
|
||||
/// </summary>
|
||||
[Description("The same pull request was present more than once in the test merge requests or is already merged!")]
|
||||
[Description("The same test merge was present more than once or is already merged!")]
|
||||
RepoDuplicateTestMerge,
|
||||
|
||||
/// <summary>
|
||||
@@ -250,18 +250,16 @@ namespace Tgstation.Server.Api.Models
|
||||
RepoWhitespaceCommitterEmail,
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated.
|
||||
/// A paginated request asked for too large a page.
|
||||
/// </summary>
|
||||
[Description("Deprecated error code.")]
|
||||
[Obsolete("With API v7 ", true)]
|
||||
DreamDaemonDuplicatePorts,
|
||||
[Description("Requested pageSize is too large!")]
|
||||
ApiPageTooLarge,
|
||||
|
||||
/// <summary>
|
||||
/// Deprecated.
|
||||
/// A paginated request asked for page 0.
|
||||
/// </summary>
|
||||
[Description("Deprecated error code.")]
|
||||
[Obsolete("With DMAPI-5.0.0, ultrasafe security is now supported.", true)]
|
||||
InvalidSecurityLevel,
|
||||
[Description("Cannot request page or pageSize <= 0.")]
|
||||
ApiInvalidPageOrPageSize,
|
||||
|
||||
/// <summary>
|
||||
/// A requested <see cref="ChatChannel"/>'s data does not match with its <see cref="Internal.ChatBot.Provider"/>.
|
||||
@@ -288,11 +286,10 @@ namespace Tgstation.Server.Api.Models
|
||||
ChatBotProviderMissing,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to update a <see cref="User"/> or <see cref="InstanceUser"/> without its ID.
|
||||
/// Tried to edit <see cref="UserGroup"/> membership using <see cref="Routes.UserGroup"/>.
|
||||
/// </summary>
|
||||
[Description("Missing user ID!")]
|
||||
[Obsolete("Deprecated in favor of code 2", true)]
|
||||
UserMissingId,
|
||||
[Description("The " + Routes.UserGroup + " endpoint cannot edit group members. Please update each member user individually.")]
|
||||
UserGroupControllerCantEditMembers,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to add a <see cref="ChatBot"/> when at or above the <see cref="Instance.ChatBotLimit"/> or it was set to something lower than the existing amount of <see cref="ChatBot"/>.
|
||||
@@ -337,11 +334,10 @@ namespace Tgstation.Server.Api.Models
|
||||
DreamMakerInvalidValidation,
|
||||
|
||||
/// <summary>
|
||||
/// DMAPI validation timeout.
|
||||
/// Tried to remove the last <see cref="OAuthConnection"/> for a passwordless <see cref="User"/>.
|
||||
/// </summary>
|
||||
[Description("The DreamDaemon startup timeout was hit before the DMAPI validated!")]
|
||||
[Obsolete("Deprecated in favor of error code 52", true)]
|
||||
DreamMakerValidationTimeout,
|
||||
[Description("This user is passwordless and removing their oAuthConnections would leave them with no authentication method!")]
|
||||
CannotRemoveLastAuthenticationOption,
|
||||
|
||||
/// <summary>
|
||||
/// No .dme could be found for deployment.
|
||||
@@ -460,7 +456,7 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// Encounted merge conflicts while test merging.
|
||||
/// </summary>
|
||||
[Description("Encountered merge conflicts while test merging one or more pull requests!")]
|
||||
[Description("Encountered merge conflicts while test merging one or more sources!")]
|
||||
RepoTestMergeConflict,
|
||||
|
||||
/// <summary>
|
||||
@@ -582,5 +578,47 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
[Description("The requested port is either already in use by TGS or could not be allocated!")]
|
||||
PortNotAvailable,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to set <see cref="User.OAuthConnections"/> for the admin user.
|
||||
/// </summary>
|
||||
[Description("The admin user cannot use OAuth connections!")]
|
||||
AdminUserCannotOAuth,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to login with a disabled OAuth provider.
|
||||
/// </summary>
|
||||
[Description("The requested OAuth provider is disabled via configuration!")]
|
||||
OAuthProviderDisabled,
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="Job"/> requiring a file upload did not receive it before timing out.
|
||||
/// </summary>
|
||||
[Description("The job did not receive a required upload before timing out!")]
|
||||
FileUploadExpired,
|
||||
|
||||
/// <summary>
|
||||
/// Tried to update a <see cref="User"/> to have both a <see cref="User.Group"/> and <see cref="User.PermissionSet"/>
|
||||
/// </summary>
|
||||
[Description("A user may not have both a permissionSet and group!")]
|
||||
UserGroupAndPermissionSet,
|
||||
|
||||
/// <summary>
|
||||
/// Tried to delete a non-empty <see cref="UserGroup"/>.
|
||||
/// </summary>
|
||||
[Description("Cannot delete the user group as it is not empty!")]
|
||||
UserGroupNotEmpty,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to create an <see cref="User"/> but the configured limit has been reached.
|
||||
/// </summary>
|
||||
[Description("The user cannot be created because the configured limit has been reached!")]
|
||||
UserLimitReached,
|
||||
|
||||
/// <summary>
|
||||
/// Attempted to create an <see cref="UserGroup"/> but the configured limit has been reached.
|
||||
/// </summary>
|
||||
[Description("The user group cannot be created because the configured limit has been reached!")]
|
||||
UserGroupLimitReached,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Response for when file transfers are necessary.
|
||||
/// </summary>
|
||||
public class FileTicketResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The ticket to use to access the <see cref="Routes.Transfer"/> controller.
|
||||
/// </summary>
|
||||
public string? FileTicket { get; set; }
|
||||
}
|
||||
}
|
||||
+13
-13
@@ -1,56 +1,56 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a <see cref="User"/>s permissions in an <see cref="Instance"/>
|
||||
/// Represents a <see cref="PermissionSet"/>s permissions in an <see cref="Instance"/>
|
||||
/// </summary>
|
||||
public class InstanceUser
|
||||
public class InstancePermissionSet
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Internal.User.Id"/> of the <see cref="User"/> the <see cref="InstanceUser"/> belongs to
|
||||
/// The <see cref="EntityId.Id"/> of the <see cref="PermissionSet"/> the <see cref="InstancePermissionSet"/> belongs to
|
||||
/// </summary>
|
||||
public long UserId { get; set; }
|
||||
public long PermissionSetId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.InstanceUserRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.InstancePermissionSetRights"/> of the <see cref="InstancePermissionSet"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public InstanceUserRights? InstanceUserRights { get; set; }
|
||||
public InstancePermissionSetRights? InstancePermissionSetRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ByondRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.ByondRights"/> of the <see cref="InstancePermissionSet"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ByondRights? ByondRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.DreamDaemonRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.DreamDaemonRights"/> of the <see cref="InstancePermissionSet"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DreamDaemonRights? DreamDaemonRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.DreamMakerRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.DreamMakerRights"/> of the <see cref="InstancePermissionSet"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public DreamMakerRights? DreamMakerRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.RepositoryRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.RepositoryRights"/> of the <see cref="InstancePermissionSet"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public RepositoryRights? RepositoryRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ChatBotRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.ChatBotRights"/> of the <see cref="InstancePermissionSet"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ChatBotRights? ChatBotRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.ConfigurationRights"/> of the <see cref="InstanceUser"/>
|
||||
/// The <see cref="Rights.ConfigurationRights"/> of the <see cref="InstancePermissionSet"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ConfigurationRights? ConfigurationRights { get; set; }
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
@@ -17,7 +17,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// The name of the connection
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumIndexableStringLength)]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Provides information about remote providers.
|
||||
/// </summary>
|
||||
public interface IGitRemoteInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Models.RemoteGitProvider"/> in use by the repository.
|
||||
/// </summary>
|
||||
public RemoteGitProvider? RemoteGitProvider { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="RemoteGitProvider"/> is not <see cref="RemoteGitProvider.Unknown"/> this will be set with the owner of the repository
|
||||
/// </summary>
|
||||
public string? RemoteRepositoryOwner { get; }
|
||||
|
||||
/// <summary>
|
||||
/// If <see cref="RemoteGitProvider"/> is not <see cref="RemoteGitProvider.Unknown"/> this will be set with the name of the repository
|
||||
/// </summary>
|
||||
public string? RemoteRepositoryName { get; }
|
||||
}
|
||||
}
|
||||
@@ -1,15 +0,0 @@
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents raw bytes.
|
||||
/// </summary>
|
||||
public abstract class RawData
|
||||
{
|
||||
/// <summary>
|
||||
/// The bytes of the <see cref="RawData"/>.
|
||||
/// </summary>
|
||||
#pragma warning disable CA1819, SA1011 // Properties should not return arrays, Closing square bracket should be followed by a space
|
||||
public byte[]? Content { get; set; }
|
||||
#pragma warning restore CA1819, SA1011 // Properties should not return arrays, Closing square bracket should be followed by a space
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
@@ -22,6 +22,11 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// </summary>
|
||||
public uint UserLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum number of <see cref="Models.UserGroup"/>s allowed.
|
||||
/// </summary>
|
||||
public uint UserGroupLimit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Limits the locations instances may be created or attached from.
|
||||
/// </summary>
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Information about a server in the swarm.
|
||||
/// </summary>
|
||||
public abstract class SwarmServer
|
||||
{
|
||||
/// <summary>
|
||||
/// The public address of the server.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public Uri? Address { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server's identifier.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string? Identifier { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a merge of a GitHub pull request
|
||||
/// Represents a test merge of a remote "pull request".
|
||||
/// </summary>
|
||||
public class TestMerge : TestMergeBase
|
||||
{
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
@@ -9,19 +9,19 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
public abstract class TestMergeBase : TestMergeParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The title of the pull request
|
||||
/// The title of the test merge source.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string? TitleAtMerge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The body of the pull request
|
||||
/// The body of the test merge source.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string? BodyAtMerge { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The URL of the pull request
|
||||
/// The URL of the test merge source.
|
||||
/// </summary>
|
||||
[Required]
|
||||
#pragma warning disable CA1056 // Uri properties should not be strings
|
||||
@@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
#pragma warning restore CA1056 // Uri properties should not be strings
|
||||
|
||||
/// <summary>
|
||||
/// The author of the pull request
|
||||
/// The author of the test merge source.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public string? Author { get; set; }
|
||||
@@ -51,7 +51,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
BodyAtMerge = copy.BodyAtMerge;
|
||||
Comment = copy.Comment;
|
||||
Number = copy.Number;
|
||||
PullRequestRevision = copy.PullRequestRevision;
|
||||
TargetCommitSha = copy.TargetCommitSha;
|
||||
TitleAtMerge = copy.TitleAtMerge;
|
||||
Url = copy.Url;
|
||||
}
|
||||
|
||||
@@ -1,20 +1,13 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a server <see cref="User"/>
|
||||
/// </summary>
|
||||
public class User
|
||||
public abstract class User : UserBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="User"/> is enabled since users cannot be deleted. System users cannot be disabled
|
||||
/// </summary>
|
||||
@@ -30,26 +23,7 @@ namespace Tgstation.Server.Api.Models.Internal
|
||||
/// <summary>
|
||||
/// The SID/UID of the <see cref="User"/> on Windows/POSIX respectively
|
||||
/// </summary>
|
||||
// No need for StringLength as the server MUST validate it.
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? SystemIdentifier { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string? Name { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.AdministrationRights"/> for the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public AdministrationRights? AdministrationRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.InstanceManagerRights"/> for the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public InstanceManagerRights? InstanceManagerRights { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for <see cref="User"/>.
|
||||
/// </summary>
|
||||
public class UserBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The name of the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a group of <see cref="Models.User"/>s.
|
||||
/// </summary>
|
||||
public class UserGroup : UserGroupBase
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Models.PermissionSet"/> of the <see cref="UserGroup"/>.
|
||||
/// </summary>
|
||||
public PermissionSet? PermissionSet { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models.Internal
|
||||
{
|
||||
/// <summary>
|
||||
/// Base class for <see cref="UserGroup"/>.
|
||||
/// </summary>
|
||||
public abstract class UserGroupBase : EntityId
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the <see cref="UserGroup"/>.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? Name { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
@@ -19,5 +21,10 @@ namespace Tgstation.Server.Api.Models
|
||||
/// Length limit for git commit SHAs.
|
||||
/// </summary>
|
||||
public const int MaximumCommitShaLength = 40;
|
||||
|
||||
/// <summary>
|
||||
/// The maximum size for file transfers.
|
||||
/// </summary>
|
||||
public const int MaximumFileTransferSize = Int32.MaxValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
using System;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a server log file.
|
||||
/// </summary>
|
||||
public sealed class LogFile : RawData
|
||||
public sealed class LogFile : FileTicketResult
|
||||
{
|
||||
/// <summary>
|
||||
/// The name of the log file.
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
using Newtonsoft.Json;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a valid OAuth connection.
|
||||
/// </summary>
|
||||
public class OAuthConnection
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="OAuthProvider"/> of the <see cref="OAuthConnection"/>.
|
||||
/// </summary>]
|
||||
[EnumDataType(typeof(OAuthProvider))]
|
||||
public OAuthProvider Provider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The ID of the user in the <see cref="Provider"/>.
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(Limits.MaximumIndexableStringLength, MinimumLength = 1)]
|
||||
public string? ExternalUserId { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// List of OAuth providers supported by TGS
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumConverter))]
|
||||
public enum OAuthProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// https://github.com
|
||||
/// </summary>
|
||||
GitHub,
|
||||
|
||||
/// <summary>
|
||||
/// https://discord.com
|
||||
/// </summary>
|
||||
Discord,
|
||||
|
||||
/// <summary>
|
||||
/// https://tgstation13.org
|
||||
/// </summary>
|
||||
TGForums,
|
||||
|
||||
/// <summary>
|
||||
/// https://www.keycloak.org
|
||||
/// </summary>
|
||||
Keycloak,
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
using System;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Public information about a given <see cref="OAuthProvider"/>.
|
||||
/// </summary>
|
||||
public sealed class OAuthProviderInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// The client ID.
|
||||
/// </summary>
|
||||
public string? ClientId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The redirect URL.
|
||||
/// </summary>
|
||||
public Uri? RedirectUri { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The server URL.
|
||||
/// </summary>
|
||||
public Uri? ServerUrl { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a paginated set of models.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The <see cref="System.Type"/> of the returned model.</typeparam>
|
||||
public sealed class Paginated<TModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ICollection{T}"/> of the returned <typeparamref name="TModel"/>s.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public ICollection<TModel>? Content { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The total number of pages in the query.
|
||||
/// </summary>
|
||||
public int TotalPages { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The current size of pages in the query.
|
||||
/// </summary>
|
||||
public int PageSize { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Rights;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a set of server permissions.
|
||||
/// </summary>
|
||||
public class PermissionSet
|
||||
{
|
||||
/// <summary>
|
||||
/// The ID of the <see cref="PermissionSet"/>.
|
||||
/// </summary>
|
||||
[Required]
|
||||
public long? Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.AdministrationRights"/> for the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public AdministrationRights? AdministrationRights { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Rights.InstanceManagerRights"/> for the <see cref="User"/>
|
||||
/// </summary>
|
||||
[Required]
|
||||
public InstanceManagerRights? InstanceManagerRights { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Indicates the remote git host.
|
||||
/// </summary>
|
||||
public enum RemoteGitProvider
|
||||
{
|
||||
/// <summary>
|
||||
/// Unknown remote git provider.
|
||||
/// </summary>
|
||||
Unknown,
|
||||
|
||||
/// <summary>
|
||||
/// Remote provider is GitHub.com
|
||||
/// </summary>
|
||||
GitHub,
|
||||
|
||||
/// <summary>
|
||||
/// Remote provider is GitLab.com
|
||||
/// </summary>
|
||||
GitLab,
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,19 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using Tgstation.Server.Api.Models.Internal;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <summary>
|
||||
/// Represents a git repository
|
||||
/// </summary>
|
||||
public sealed class Repository : Internal.RepositorySettings
|
||||
public sealed class Repository : RepositorySettings, IGitRemoteInformation
|
||||
{
|
||||
/// <summary>
|
||||
/// The origin URL. If <see langword="null"/>, the <see cref="Repository"/> does not exist
|
||||
/// </summary>
|
||||
public string? Origin { get; set; }
|
||||
public Uri? Origin { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If submodules should be recursively cloned.
|
||||
@@ -29,15 +31,14 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
public RevisionInformation? RevisionInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the repository was cloned from GitHub.com this will be set with the owner of the repository
|
||||
/// </summary>
|
||||
public string? GitHubOwner { get; set; }
|
||||
/// <inheritdoc />
|
||||
public RemoteGitProvider? RemoteGitProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the repository was cloned from GitHub.com this will be set with the name of the repository
|
||||
/// </summary>
|
||||
public string? GitHubName { get; set; }
|
||||
/// <inheritdoc />
|
||||
public string? RemoteRepositoryOwner { get; set; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public string? RemoteRepositoryName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/> started by the <see cref="Repository"/> if any
|
||||
@@ -60,4 +61,4 @@ namespace Tgstation.Server.Api.Models
|
||||
/// </summary>
|
||||
public ICollection<TestMergeParameters>? NewTestMerges { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
@@ -21,5 +22,25 @@ namespace Tgstation.Server.Api.Models
|
||||
/// The DMAPI version of the host.
|
||||
/// </summary>
|
||||
public Version? DMApiVersion { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If the server is running on a windows operating system.
|
||||
/// </summary>
|
||||
public bool WindowsHost { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// If there is a server update in progress.
|
||||
/// </summary>
|
||||
public bool UpdateInProgress { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A <see cref="ICollection{T}"/> of connected <see cref="SwarmServer"/>s.
|
||||
/// </summary>
|
||||
public ICollection<SwarmServer>? SwarmServers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Map of <see cref="OAuthProvider"/> to the <see cref="OAuthProviderInfo"/> for them.
|
||||
/// </summary>
|
||||
public IDictionary<OAuthProvider, OAuthProviderInfo>? OAuthProviderInfos { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class SwarmServer : Internal.SwarmServer
|
||||
{
|
||||
/// <summary>
|
||||
/// If the <see cref="SwarmServer"/> is the controller.
|
||||
/// </summary>
|
||||
public bool Controller { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
@@ -8,16 +8,16 @@ namespace Tgstation.Server.Api.Models
|
||||
public class TestMergeParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The number of the pull request
|
||||
/// The number of the test merge source.
|
||||
/// </summary>
|
||||
public int Number { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The sha of the pull request revision to merge. If not specified, the latest commit shall be used (semi-unsafe)
|
||||
/// The sha of the test merge revision to merge. If not specified, the latest commit shall be used (semi-unsafe)
|
||||
/// </summary>
|
||||
[Required]
|
||||
[StringLength(40)]
|
||||
public string? PullRequestRevision { get; set; }
|
||||
public virtual string? TargetCommitSha { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Optional comment about the test
|
||||
@@ -25,4 +25,4 @@ namespace Tgstation.Server.Api.Models
|
||||
[StringLength(Limits.MaximumStringLength)]
|
||||
public string? Comment { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
namespace Tgstation.Server.Api.Models
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public class User : Internal.User
|
||||
@@ -16,6 +19,22 @@
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> who created this <see cref="User"/>
|
||||
/// </summary>
|
||||
public Internal.User? CreatedBy { get; set; }
|
||||
[Required]
|
||||
public Internal.UserBase? CreatedBy { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// List of <see cref="OAuthConnection"/>s associated with the <see cref="User"/>.
|
||||
/// </summary>
|
||||
public ICollection<OAuthConnection>? OAuthConnections { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.PermissionSet"/> directly associated with the <see cref="User"/>.
|
||||
/// </summary>
|
||||
public PermissionSet? PermissionSet { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Internal.UserGroup"/> asociated with the <see cref="User"/>, if any.
|
||||
/// </summary>
|
||||
public Internal.UserGroup? Group { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace Tgstation.Server.Api.Models
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public sealed class UserGroup : Internal.UserGroup
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="User"/>s the <see cref="UserGroup"/> has.
|
||||
/// </summary>
|
||||
public ICollection<Internal.UserBase>? Users { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User can edit their and other <see cref="Models.User"/>s and create new ones.
|
||||
/// User has complete control over creating/editing <see cref="Models.User"/>s and <see cref="Models.UserGroup"/>s (and deleting in the case of the latter).
|
||||
/// </summary>
|
||||
WriteUsers = 1,
|
||||
|
||||
@@ -24,7 +24,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
RestartHost = 2,
|
||||
|
||||
/// <summary>
|
||||
/// User can upgrade or downgrade TGS through the API.
|
||||
/// User can read <see cref="Models.Administration"/> and upgrade/downgrade TGS through the API.
|
||||
/// </summary>
|
||||
ChangeVersion = 4,
|
||||
|
||||
@@ -42,5 +42,10 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// User can list and download <see cref="Models.LogFile"/>s.
|
||||
/// </summary>
|
||||
DownloadLogs = 32,
|
||||
|
||||
/// <summary>
|
||||
/// User can modify their own <see cref="Models.User.OAuthConnections"/>.
|
||||
/// </summary>
|
||||
EditOwnOAuthConnections = 64,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// User can view <see cref="Models.Instance"/>s which they have an <see cref="Models.InstanceUser"/> for.
|
||||
/// User can view <see cref="Models.Instance"/>s which they have an <see cref="Models.InstancePermissionSet"/> for.
|
||||
/// </summary>
|
||||
Read = 1,
|
||||
|
||||
@@ -64,7 +64,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
SetChatBotLimit = 512,
|
||||
|
||||
/// <summary>
|
||||
/// User can give themselves full <see cref="Models.InstanceUser"/> rights on ALL instances.
|
||||
/// User can give themselves or their group full <see cref="Models.InstancePermissionSet"/> rights on ALL instances.
|
||||
/// </summary>
|
||||
GrantPermissions = 1024,
|
||||
}
|
||||
|
||||
+7
-7
@@ -6,7 +6,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
/// Rights for an <see cref="Models.Instance"/>
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum InstanceUserRights : ulong
|
||||
public enum InstancePermissionSetRights : ulong
|
||||
{
|
||||
/// <summary>
|
||||
/// User has no rights/
|
||||
@@ -14,18 +14,18 @@ namespace Tgstation.Server.Api.Rights
|
||||
None = 0,
|
||||
|
||||
/// <summary>
|
||||
/// Allow read access to all <see cref="Models.InstanceUser"/>s in the <see cref="Models.Instance"/>.
|
||||
/// Allow read access to all <see cref="Models.InstancePermissionSet"/>s in the <see cref="Models.Instance"/>.
|
||||
/// </summary>
|
||||
ReadUsers = 1,
|
||||
Read = 1,
|
||||
|
||||
/// <summary>
|
||||
/// Allow write and delete access to all <see cref="Models.InstanceUser"/> for the <see cref="Models.Instance"/>.
|
||||
/// Allow write and delete access to all <see cref="Models.InstancePermissionSet"/> for the <see cref="Models.Instance"/>.
|
||||
/// </summary>
|
||||
WriteUsers = 2,
|
||||
Write = 2,
|
||||
|
||||
/// <summary>
|
||||
/// Allow adding additional <see cref="Models.InstanceUser"/>s to the <see cref="Models.Instance"/>.
|
||||
/// Allow adding additional <see cref="Models.InstancePermissionSet"/>s to the <see cref="Models.Instance"/>.
|
||||
/// </summary>
|
||||
CreateUsers = 4
|
||||
Create = 4
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
SetSha = 4,
|
||||
|
||||
/// <summary>
|
||||
/// User may fetch and merge GitHub pull requests.
|
||||
/// User may create <see cref="Models.TestMerge"/>s.
|
||||
/// </summary>
|
||||
MergePullRequest = 8,
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Tgstation.Server.Api.Rights
|
||||
{ RightsType.DreamDaemon, typeof(DreamDaemonRights) },
|
||||
{ RightsType.ChatBots, typeof(ChatBotRights) },
|
||||
{ RightsType.Configuration, typeof(ConfigurationRights) },
|
||||
{ RightsType.InstanceUser, typeof(InstanceUserRights) }
|
||||
{ RightsType.InstancePermissionSet, typeof(InstancePermissionSetRights) }
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace Tgstation.Server.Api.Rights
|
||||
namespace Tgstation.Server.Api.Rights
|
||||
{
|
||||
/// <summary>
|
||||
/// The type of rights a model uses
|
||||
@@ -46,8 +46,8 @@
|
||||
Configuration,
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="InstanceUserRights"/>
|
||||
/// <see cref="InstancePermissionSetRights"/>
|
||||
/// </summary>
|
||||
InstanceUser
|
||||
InstancePermissionSet
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,6 +28,11 @@ namespace Tgstation.Server.Api
|
||||
/// </summary>
|
||||
public const string User = Root + nameof(Models.User);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.UserGroup"/> controller.
|
||||
/// </summary>
|
||||
public const string UserGroup = Root + nameof(Models.UserGroup);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.Instance"/> controller
|
||||
/// </summary>
|
||||
@@ -69,9 +74,9 @@ namespace Tgstation.Server.Api
|
||||
public const string ConfigurationFile = Configuration + "/" + File;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.InstanceUser"/> controller
|
||||
/// The <see cref="Models.InstancePermissionSet"/> controller
|
||||
/// </summary>
|
||||
public const string InstanceUser = Root + nameof(Models.InstanceUser);
|
||||
public const string InstancePermissionSet = Root + nameof(Models.InstancePermissionSet);
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.ChatBot"/> controller
|
||||
@@ -88,6 +93,11 @@ namespace Tgstation.Server.Api
|
||||
/// </summary>
|
||||
public const string Jobs = Root + nameof(Models.Job);
|
||||
|
||||
/// <summary>
|
||||
/// The transfer controller.
|
||||
/// </summary>
|
||||
public const string Transfer = Root + "Transfer";
|
||||
|
||||
/// <summary>
|
||||
/// The postfix for list operations
|
||||
/// </summary>
|
||||
@@ -109,7 +119,7 @@ namespace Tgstation.Server.Api
|
||||
public static string ListRoute(string route) => String.Format(CultureInfo.InvariantCulture, "{0}/{1}", route, List);
|
||||
|
||||
/// <summary>
|
||||
/// Sanitize a <see cref="Models.Internal.RawData"/> path for use in a GET <see cref="Uri"/>.
|
||||
/// Sanitize a <see cref="Models.FileTicketResult"/> path for use in a GET <see cref="Uri"/>.
|
||||
/// </summary>
|
||||
/// <param name="path">The path to sanitize.</param>
|
||||
/// <returns>The sanitized path.</returns>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../../build/Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -23,6 +23,7 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<DocumentationFile>bin\$(Configuration)\netstandard2.1\Tgstation.Server.Api.xml</DocumentationFile>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
<NoWarn>CA1028</NoWarn>
|
||||
</PropertyGroup>
|
||||
|
||||
@@ -33,17 +34,14 @@
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.AspNetCore.Http.Extensions" Version="2.2.0" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="4.7.0" />
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="System.ComponentModel.Annotations" Version="5.0.0" />
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<AdditionalFiles Include="../../build/stylecop.json" />
|
||||
<None Include="../../LICENSE" Pack="true" PackagePath="" />
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
@@ -9,39 +10,49 @@ using Tgstation.Server.Api.Models;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class AdministrationClient : IAdministrationClient
|
||||
sealed class AdministrationClient : PaginatedClient, IAdministrationClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="apiClient"/> for the <see cref="AdministrationClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="AdministrationClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
public AdministrationClient(IApiClient apiClient)
|
||||
: base(apiClient)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Administration> Read(CancellationToken cancellationToken) => ApiClient.Read<Administration>(Routes.Administration, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Update(Administration administration, CancellationToken cancellationToken) => ApiClient.Update(Routes.Administration, administration ?? throw new ArgumentNullException(nameof(administration)), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Restart(CancellationToken cancellationToken) => ApiClient.Delete(Routes.Administration, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<LogFile>> ListLogs(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<LogFile>(paginationSettings, Routes.Logs, null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task<Tuple<LogFile, Stream>> GetLog(LogFile logFile, CancellationToken cancellationToken)
|
||||
{
|
||||
this.apiClient = apiClient;
|
||||
var resultFile = await ApiClient.Read<LogFile>(
|
||||
Routes.Logs + Routes.SanitizeGetPath(
|
||||
HttpUtility.UrlEncode(
|
||||
logFile?.Name ?? throw new ArgumentNullException(nameof(logFile)))),
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var stream = await ApiClient.Download(resultFile, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return Tuple.Create(resultFile, stream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
stream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Administration> Read(CancellationToken cancellationToken) => apiClient.Read<Administration>(Routes.Administration, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Update(Administration administration, CancellationToken cancellationToken) => apiClient.Update(Routes.Administration, administration ?? throw new ArgumentNullException(nameof(administration)), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Restart(CancellationToken cancellationToken) => apiClient.Delete(Routes.Administration, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<LogFile>> ListLogs(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<LogFile>>(Routes.Logs, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<LogFile> GetLog(LogFile logFile, CancellationToken cancellationToken) => apiClient.Read<LogFile>(
|
||||
Routes.Logs + Routes.SanitizeGetPath(
|
||||
HttpUtility.UrlEncode(
|
||||
logFile?.Name ?? throw new ArgumentNullException(nameof(logFile)))),
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json;
|
||||
using Newtonsoft.Json.Converters;
|
||||
using Newtonsoft.Json.Serialization;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Net;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Mime;
|
||||
using System.Text;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using System.Web;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
@@ -36,7 +39,7 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="HttpClient"/> for the <see cref="ApiClient"/>
|
||||
/// The <see cref="HttpClientImplementation"/> for the <see cref="ApiClient"/>
|
||||
/// </summary>
|
||||
readonly IHttpClient httpClient;
|
||||
|
||||
@@ -135,7 +138,8 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Main request method
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The resulting POCO type</typeparam>
|
||||
/// <typeparam name="TBody">The body <see cref="Type"/>.</typeparam>
|
||||
/// <typeparam name="TResult">The resulting POCO type.</typeparam>
|
||||
/// <param name="route">The route to run</param>
|
||||
/// <param name="body">The body of the request</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
@@ -143,38 +147,93 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="tokenRefresh">If this is a token refresh operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success</returns>
|
||||
async Task<TResult> RunRequest<TResult>(string route, object? body, HttpMethod method, long? instanceId, bool tokenRefresh, CancellationToken cancellationToken)
|
||||
Task<TResult> RunRequest<TBody, TResult>(
|
||||
string route,
|
||||
TBody? body,
|
||||
HttpMethod method,
|
||||
long? instanceId,
|
||||
bool tokenRefresh,
|
||||
CancellationToken cancellationToken)
|
||||
where TBody : class
|
||||
{
|
||||
HttpContent? content = null;
|
||||
if(body != null)
|
||||
content = new StringContent(
|
||||
JsonConvert.SerializeObject(body, typeof(TBody), Formatting.None, GetSerializerSettings()),
|
||||
Encoding.UTF8,
|
||||
MediaTypeNames.Application.Json);
|
||||
|
||||
return RunRequest<TResult>(
|
||||
route,
|
||||
content,
|
||||
method,
|
||||
instanceId,
|
||||
tokenRefresh,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Main request method
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The resulting POCO type</typeparam>
|
||||
/// <param name="route">The route to run</param>
|
||||
/// <param name="content">The <see cref="HttpContent"/> of the request if any.</param>
|
||||
/// <param name="method">The method of the request</param>
|
||||
/// <param name="instanceId">The optional instance <see cref="EntityId.Id"/> for the request</param>
|
||||
/// <param name="tokenRefresh">If this is a token refresh operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response on success</returns>
|
||||
async Task<TResult> RunRequest<TResult>(
|
||||
string route,
|
||||
HttpContent? content,
|
||||
HttpMethod method,
|
||||
long? instanceId,
|
||||
bool tokenRefresh,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (route == null)
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
if (method == null)
|
||||
throw new ArgumentNullException(nameof(method));
|
||||
if (body == null && (method == HttpMethod.Post || method == HttpMethod.Put))
|
||||
throw new InvalidOperationException("Body cannot be null for POST or PUT!");
|
||||
if (content == null && (method == HttpMethod.Post || method == HttpMethod.Put))
|
||||
throw new InvalidOperationException("content cannot be null for POST or PUT!");
|
||||
|
||||
HttpResponseMessage response;
|
||||
var fullUri = new Uri(Url, route);
|
||||
var serializerSettings = GetSerializerSettings();
|
||||
var fileDownload = typeof(TResult) == typeof(Stream);
|
||||
using (var request = new HttpRequestMessage(method, fullUri))
|
||||
{
|
||||
if (body != null)
|
||||
request.Content = new StringContent(
|
||||
JsonConvert.SerializeObject(body, serializerSettings),
|
||||
Encoding.UTF8,
|
||||
MediaTypeNames.Application.Json);
|
||||
if (content != null)
|
||||
request.Content = content;
|
||||
|
||||
var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
|
||||
headersToUse.SetRequestHeaders(request.Headers, instanceId);
|
||||
|
||||
if (fileDownload)
|
||||
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MediaTypeNames.Application.Octet));
|
||||
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using (response)
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogResponse(response, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
// just stream
|
||||
if (fileDownload && response.IsSuccessStatusCode)
|
||||
return (TResult)(object)await CachedResponseStream.Create(response).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
response.Dispose();
|
||||
throw;
|
||||
}
|
||||
|
||||
using (response)
|
||||
{
|
||||
var json = await response.Content.ReadAsStringAsync().ConfigureAwait(false);
|
||||
|
||||
if (!response.IsSuccessStatusCode)
|
||||
@@ -182,7 +241,7 @@ namespace Tgstation.Server.Client
|
||||
if (!tokenRefresh
|
||||
&& response.StatusCode == HttpStatusCode.Unauthorized
|
||||
&& await RefreshToken(cancellationToken).ConfigureAwait(false))
|
||||
return await RunRequest<TResult>(route, body, method, instanceId, false, cancellationToken).ConfigureAwait(false);
|
||||
return await RunRequest<TResult>(route, content, method, instanceId, false, cancellationToken).ConfigureAwait(false);
|
||||
HandleBadResponse(response, json);
|
||||
}
|
||||
|
||||
@@ -212,7 +271,7 @@ namespace Tgstation.Server.Client
|
||||
if (startingToken != headers.Token)
|
||||
return true;
|
||||
|
||||
var token = await RunRequest<Token>(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken);
|
||||
var token = await RunRequest<object, Token>(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken);
|
||||
headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
|
||||
}
|
||||
catch (ClientException)
|
||||
@@ -228,54 +287,101 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, null, false, cancellationToken);
|
||||
public Task<TResult> Create<TResult>(string route, CancellationToken cancellationToken) => RunRequest<object, TResult>(route, new object(), HttpMethod.Put, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, null, false, cancellationToken);
|
||||
public Task<TResult> Read<TResult>(string route, CancellationToken cancellationToken) => RunRequest<object, TResult>(route, null, HttpMethod.Get, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Post, null, false, cancellationToken);
|
||||
public Task<TResult> Update<TResult>(string route, CancellationToken cancellationToken) => RunRequest<object, TResult>(route, new object(), HttpMethod.Post, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, null, false, cancellationToken);
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) where TBody : class => RunRequest<TBody, TResult>(route, body, HttpMethod.Post, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Patch(string route, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Patch, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Post, null, false, cancellationToken);
|
||||
public Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) where TBody : class => RunRequest<TBody, object>(route, body, HttpMethod.Post, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, null, false, cancellationToken);
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) where TBody : class => RunRequest<TBody, TResult>(route, body, HttpMethod.Put, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(string route, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, null, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Put, instanceId, false, cancellationToken);
|
||||
public Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) where TBody : class => RunRequest<TBody, TResult>(route, body, HttpMethod.Put, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Get, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, body, HttpMethod.Post, instanceId, false, cancellationToken);
|
||||
public Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken) where TBody : class => RunRequest<TBody, TResult>(route, body, HttpMethod.Post, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Delete, instanceId, false, cancellationToken);
|
||||
public Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken) where TBody : class => RunRequest<TBody, object>(route, body, HttpMethod.Delete, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Delete, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Put, instanceId, false, cancellationToken);
|
||||
public Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object, TResult>(route, new object(), HttpMethod.Put, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, new object(), HttpMethod.Patch, instanceId, false, cancellationToken);
|
||||
public Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object, TResult>(route, new object(), HttpMethod.Patch, instanceId, false, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddRequestLogger(IRequestLogger requestLogger) => requestLoggers.Add(requestLogger ?? throw new ArgumentNullException(nameof(requestLogger)));
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Stream> Download(FileTicketResult ticket, CancellationToken cancellationToken)
|
||||
{
|
||||
if (ticket == null)
|
||||
throw new ArgumentNullException(nameof(ticket));
|
||||
|
||||
return RunRequest<Stream>(
|
||||
$"{Routes.Transfer}?ticket={HttpUtility.UrlEncode(ticket.FileTicket)}",
|
||||
null,
|
||||
HttpMethod.Get,
|
||||
null,
|
||||
false,
|
||||
cancellationToken);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public async Task Upload(FileTicketResult ticket, Stream? uploadStream, CancellationToken cancellationToken)
|
||||
{
|
||||
if (ticket == null)
|
||||
throw new ArgumentNullException(nameof(ticket));
|
||||
|
||||
MemoryStream? memoryStream = null;
|
||||
if (uploadStream == null)
|
||||
memoryStream = new MemoryStream();
|
||||
|
||||
using (memoryStream)
|
||||
{
|
||||
var streamContent = new StreamContent(uploadStream ?? memoryStream);
|
||||
try
|
||||
{
|
||||
await RunRequest<object>(
|
||||
$"{Routes.Transfer}?ticket={HttpUtility.UrlEncode(ticket.FileTicket)}",
|
||||
streamContent,
|
||||
HttpMethod.Put,
|
||||
null,
|
||||
false,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
streamContent = null; //CA2000
|
||||
}
|
||||
finally
|
||||
{
|
||||
streamContent?.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,6 @@ namespace Tgstation.Server.Client
|
||||
sealed class ApiClientFactory : IApiClientFactory
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders) => new ApiClient(new HttpClient(), url, apiHeaders, tokenRefreshHeaders);
|
||||
public IApiClient CreateApiClient(Uri url, ApiHeaders apiHeaders, ApiHeaders? tokenRefreshHeaders) => new ApiClient(new HttpClientImplementation(), url, apiHeaders, tokenRefreshHeaders);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
@@ -31,7 +31,7 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/>.</param>
|
||||
protected ApiException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(
|
||||
responseMessage,
|
||||
errorMessage?.Message ?? $"HTTP {responseMessage.StatusCode}. Unknown API error, ErrorMessage payload not present!")
|
||||
errorMessage?.Message ?? $"HTTP {responseMessage?.StatusCode ?? throw new ArgumentNullException(nameof(responseMessage))}. Unknown API error, ErrorMessage payload not present!")
|
||||
{
|
||||
ServerApiVersion = errorMessage?.ServerApiVersion;
|
||||
AdditionalServerData = errorMessage?.AdditionalData;
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
using System.IO;
|
||||
using System.Net.Http;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Caches the <see cref="Stream"/> from a <see cref="HttpResponseMessage"/> for later use.
|
||||
/// </summary>
|
||||
sealed class CachedResponseStream : Stream
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="HttpResponseMessage"/> for the <see cref="CachedResponseStream"/>.
|
||||
/// </summary>
|
||||
readonly HttpResponseMessage response;
|
||||
|
||||
/// <summary>
|
||||
/// The reponse content <see cref="Stream"/>.
|
||||
/// </summary>
|
||||
readonly Stream responseStream;
|
||||
|
||||
/// <summary>
|
||||
/// Asyncronously creates a new <see cref="CachedResponseStream"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The <see cref="HttpResponseMessage"/> to build from.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a new <see cref="CachedResponseStream"/>.</returns>
|
||||
public static async Task<CachedResponseStream> Create(HttpResponseMessage response)
|
||||
{
|
||||
var stream = await response.Content.ReadAsStreamAsync().ConfigureAwait(false);
|
||||
return new CachedResponseStream(response, stream);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="CachedResponseStream"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="response">The value of <see cref="response"/>.</param>
|
||||
/// <param name="responseStream">The value of <see cref="responseStream"/>.</param>
|
||||
CachedResponseStream(HttpResponseMessage response, Stream responseStream)
|
||||
{
|
||||
this.response = response;
|
||||
this.responseStream = responseStream;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Dispose(bool disposing)
|
||||
{
|
||||
base.Dispose(disposing);
|
||||
if (!disposing)
|
||||
return;
|
||||
responseStream.Dispose();
|
||||
response.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async ValueTask DisposeAsync()
|
||||
{
|
||||
await base.DisposeAsync().ConfigureAwait(false);
|
||||
await responseStream.DisposeAsync().ConfigureAwait(false);
|
||||
response.Dispose();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanRead => responseStream.CanRead;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanSeek => responseStream.CanSeek;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override bool CanWrite => responseStream.CanWrite;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Length => responseStream.Length;
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Position
|
||||
{
|
||||
get => responseStream.Position;
|
||||
set => responseStream.Position = value;
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Flush() => responseStream.Flush();
|
||||
|
||||
/// <inheritdoc />
|
||||
public override int Read(byte[] buffer, int offset, int count) => responseStream.Read(buffer, offset, count);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override long Seek(long offset, SeekOrigin origin) => responseStream.Seek(offset, origin);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void SetLength(long value) => responseStream.SetLength(value);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override void Write(byte[] buffer, int offset, int count) => responseStream.Write(buffer, offset, count);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
@@ -8,13 +9,8 @@ using Tgstation.Server.Api.Models;
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ByondClient : IByondClient
|
||||
sealed class ByondClient : PaginatedClient, IByondClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ByondClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ByondClient"/>
|
||||
/// </summary>
|
||||
@@ -23,21 +19,35 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ByondClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
/// <param name="instance">The value of <see cref="Instance"/></param>
|
||||
public ByondClient(IApiClient apiClient, Instance instance)
|
||||
: base(apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Byond> ActiveVersion(CancellationToken cancellationToken) => apiClient.Read<Byond>(Routes.Byond, instance.Id, cancellationToken);
|
||||
public Task<Byond> ActiveVersion(CancellationToken cancellationToken) => ApiClient.Read<Byond>(Routes.Byond, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<Byond>> InstalledVersions(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<Byond>>(Routes.ListRoute(Routes.Byond), instance.Id, cancellationToken);
|
||||
public Task<IReadOnlyList<Byond>> InstalledVersions(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<Byond>(paginationSettings, Routes.ListRoute(Routes.Byond), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Byond> SetActiveVersion(Byond byond, CancellationToken cancellationToken) => apiClient.Update<Byond, Byond>(Routes.Byond, byond ?? throw new ArgumentNullException(nameof(byond)), instance.Id, cancellationToken);
|
||||
public async Task<Byond> SetActiveVersion(Byond byond, Stream zipFileStream, CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await ApiClient.Update<Byond, Byond>(
|
||||
Routes.Byond,
|
||||
byond ?? throw new ArgumentNullException(nameof(byond)),
|
||||
instance.Id,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (byond.UploadCustomZip == true)
|
||||
await ApiClient.Upload(result, zipFileStream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return result;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -8,13 +8,8 @@ using Tgstation.Server.Api.Models;
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ChatBotsClient : IChatBotsClient
|
||||
sealed class ChatBotsClient : PaginatedClient, IChatBotsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ChatBotsClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ChatBotsClient"/>
|
||||
/// </summary>
|
||||
@@ -23,27 +18,28 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ChatBotsClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public ChatBotsClient(IApiClient apiClient, Instance instance)
|
||||
: base(apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ChatBot> Create(ChatBot settings, CancellationToken cancellationToken) => apiClient.Create<ChatBot, ChatBot>(Routes.Chat, settings ?? throw new ArgumentNullException(nameof(settings)), instance.Id, cancellationToken);
|
||||
public Task<ChatBot> Create(ChatBot settings, CancellationToken cancellationToken) => ApiClient.Create<ChatBot, ChatBot>(Routes.Chat, settings ?? throw new ArgumentNullException(nameof(settings)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(ChatBot settings, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.Chat, settings?.Id ?? throw new ArgumentNullException(nameof(settings))), instance.Id, cancellationToken);
|
||||
public Task Delete(ChatBot settings, CancellationToken cancellationToken) => ApiClient.Delete(Routes.SetID(Routes.Chat, settings?.Id ?? throw new ArgumentNullException(nameof(settings))), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<ChatBot>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<ChatBot>>(Routes.ListRoute(Routes.Chat), instance.Id, cancellationToken);
|
||||
public Task<IReadOnlyList<ChatBot>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<ChatBot>(paginationSettings, Routes.ListRoute(Routes.Chat), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ChatBot> Update(ChatBot settings, CancellationToken cancellationToken) => apiClient.Update<ChatBot, ChatBot>(Routes.Chat, settings ?? throw new ArgumentNullException(nameof(settings)), instance.Id, cancellationToken);
|
||||
public Task<ChatBot> Update(ChatBot settings, CancellationToken cancellationToken) => ApiClient.Update<ChatBot, ChatBot>(Routes.Chat, settings ?? throw new ArgumentNullException(nameof(settings)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ChatBot> GetId(ChatBot settings, CancellationToken cancellationToken) => apiClient.Read<ChatBot>(Routes.SetID(Routes.Chat, (settings ?? throw new ArgumentNullException(nameof(settings))).Id), instance.Id, cancellationToken);
|
||||
public Task<ChatBot> GetId(ChatBot settings, CancellationToken cancellationToken) => ApiClient.Read<ChatBot>(Routes.SetID(Routes.Chat, (settings ?? throw new ArgumentNullException(nameof(settings))).Id), instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
@@ -8,13 +9,8 @@ using Tgstation.Server.Api.Models;
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class ConfigurationClient : IConfigurationClient
|
||||
sealed class ConfigurationClient : PaginatedClient, IConfigurationClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ConfigurationClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="ConfigurationClient"/>
|
||||
/// </summary>
|
||||
@@ -23,35 +19,82 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// Construct a <see cref="ConfigurationClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public ConfigurationClient(IApiClient apiClient, Instance instance)
|
||||
: base(apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken);
|
||||
public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => ApiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ConfigurationFile> CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Create<ConfigurationFile, ConfigurationFile>(Routes.Configuration, directory, instance.Id, cancellationToken);
|
||||
public Task<ConfigurationFile> CreateDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => ApiClient.Create<ConfigurationFile, ConfigurationFile>(Routes.Configuration, directory, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<ConfigurationFile>> List(string directory, CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<ConfigurationFile>>(Routes.ListRoute(Routes.Configuration) + Routes.SanitizeGetPath(directory), instance.Id, cancellationToken);
|
||||
public Task<IReadOnlyList<ConfigurationFile>> List(
|
||||
PaginationSettings? paginationSettings,
|
||||
string directory,
|
||||
CancellationToken cancellationToken)
|
||||
=> ReadPaged<ConfigurationFile>(
|
||||
paginationSettings,
|
||||
Routes.ListRoute(Routes.Configuration) + Routes.SanitizeGetPath(directory),
|
||||
instance.Id,
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ConfigurationFile> Read(ConfigurationFile file, CancellationToken cancellationToken)
|
||||
public async Task<Tuple<ConfigurationFile, Stream>> Read(ConfigurationFile file, CancellationToken cancellationToken)
|
||||
{
|
||||
if (file == null)
|
||||
throw new ArgumentNullException(nameof(file));
|
||||
return apiClient.Read<ConfigurationFile>(
|
||||
var configFile = await ApiClient.Read<ConfigurationFile>(
|
||||
Routes.ConfigurationFile + Routes.SanitizeGetPath(file.Path ?? throw new ArgumentException("file.Path should not be null!", nameof(file))),
|
||||
instance.Id,
|
||||
cancellationToken);
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var downloadStream = await ApiClient.Download(configFile, cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
return Tuple.Create(configFile, downloadStream);
|
||||
}
|
||||
catch
|
||||
{
|
||||
downloadStream.Dispose();
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update<ConfigurationFile, ConfigurationFile>(Routes.Configuration, file ?? throw new ArgumentNullException(nameof(file)), instance.Id, cancellationToken);
|
||||
public async Task<ConfigurationFile> Write(ConfigurationFile file, Stream uploadStream, CancellationToken cancellationToken)
|
||||
{
|
||||
long initialStreamPosition = 0;
|
||||
MemoryStream? memoryStream = null;
|
||||
if (uploadStream?.CanSeek == false)
|
||||
memoryStream = new MemoryStream();
|
||||
else if (uploadStream != null)
|
||||
initialStreamPosition = uploadStream.Position;
|
||||
|
||||
using (memoryStream)
|
||||
{
|
||||
var configFileTask = ApiClient.Update<ConfigurationFile, ConfigurationFile>(
|
||||
Routes.Configuration,
|
||||
file ?? throw new ArgumentNullException(nameof(file)),
|
||||
instance.Id,
|
||||
cancellationToken);
|
||||
|
||||
if (memoryStream != null)
|
||||
await uploadStream!.CopyToAsync(memoryStream, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var configFile = await configFileTask.ConfigureAwait(false);
|
||||
|
||||
var streamUsed = memoryStream ?? uploadStream;
|
||||
streamUsed?.Seek(initialStreamPosition, SeekOrigin.Begin);
|
||||
await ApiClient.Upload(configFile, streamUsed, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return configFile;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -8,13 +8,8 @@ using Tgstation.Server.Api.Models;
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class DreamMakerClient : IDreamMakerClient
|
||||
sealed class DreamMakerClient : PaginatedClient, IDreamMakerClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="DreamMakerClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="DreamMakerClient"/>
|
||||
/// </summary>
|
||||
@@ -23,27 +18,28 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DreamMakerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
/// <param name="instance">The value of <see cref="Instance"/></param>
|
||||
public DreamMakerClient(IApiClient apiClient, Instance instance)
|
||||
: base(apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Job> Compile(CancellationToken cancellationToken) => apiClient.Create<Job>(Routes.DreamMaker, instance.Id, cancellationToken);
|
||||
public Task<Job> Compile(CancellationToken cancellationToken) => ApiClient.Create<Job>(Routes.DreamMaker, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<CompileJob> GetCompileJob(EntityId compileJob, CancellationToken cancellationToken) => apiClient.Read<CompileJob>(Routes.SetID(Routes.DreamMaker, compileJob?.Id ?? throw new ArgumentNullException(nameof(compileJob))), instance.Id, cancellationToken);
|
||||
public Task<CompileJob> GetCompileJob(EntityId compileJob, CancellationToken cancellationToken) => ApiClient.Read<CompileJob>(Routes.SetID(Routes.DreamMaker, compileJob?.Id ?? throw new ArgumentNullException(nameof(compileJob))), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<EntityId>> GetJobIds(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<EntityId>>(Routes.ListRoute(Routes.DreamMaker), instance.Id, cancellationToken);
|
||||
public Task<IReadOnlyList<EntityId>> ListCompileJobs(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<EntityId>(paginationSettings, Routes.ListRoute(Routes.DreamMaker), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DreamMaker> Read(CancellationToken cancellationToken) => apiClient.Read<DreamMaker>(Routes.DreamMaker, instance.Id, cancellationToken);
|
||||
public Task<DreamMaker> Read(CancellationToken cancellationToken) => ApiClient.Read<DreamMaker>(Routes.DreamMaker, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<DreamMaker> Update(DreamMaker dreamMaker, CancellationToken cancellationToken) => apiClient.Update<DreamMaker, DreamMaker>(Routes.DreamMaker, dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)), instance.Id, cancellationToken);
|
||||
public Task<DreamMaker> Update(DreamMaker dreamMaker, CancellationToken cancellationToken) => ApiClient.Update<DreamMaker, DreamMaker>(Routes.DreamMaker, dreamMaker ?? throw new ArgumentNullException(nameof(dreamMaker)), instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -20,16 +21,18 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// Get all installed <see cref="Byond"/> <see cref="System.Version"/>s
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in an <see cref="IReadOnlyList{T}"/> of installed <see cref="Byond"/> <see cref="System.Version"/>s</returns>
|
||||
Task<IReadOnlyList<Byond>> InstalledVersions(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<Byond>> InstalledVersions(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Updates the <see cref="Byond"/> information
|
||||
/// </summary>
|
||||
/// <param name="byond">The <see cref="Byond"/> information to update</param>
|
||||
/// <param name="zipFileStream">The <see cref="Stream"/> for the .zip file if <see cref="Byond.UploadCustomZip"/> is <see langword="true"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the updated <see cref="Byond"/> information</returns>
|
||||
Task<Byond> SetActiveVersion(Byond byond, CancellationToken cancellationToken);
|
||||
Task<Byond> SetActiveVersion(Byond byond, Stream zipFileStream, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -13,9 +13,10 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// List the <see cref="ChatBot"/>s
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of the <see cref="ChatBot"/> of the server</returns>
|
||||
Task<IReadOnlyList<ChatBot>> List(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<ChatBot>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <see cref="ChatBot"/>
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -13,26 +15,31 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// List configuration files
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="directory">The path to the directory to list files in</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="IReadOnlyList{T}"/> of <see cref="ConfigurationFile"/>s in the <paramref name="directory"/></returns>
|
||||
Task<IReadOnlyList<ConfigurationFile>> List(string directory, CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<ConfigurationFile>> List(
|
||||
PaginationSettings? paginationSettings,
|
||||
string directory,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Read a <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
/// <param name="file">The <see cref="ConfigurationFile"/> file to read</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task<ConfigurationFile> Read(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task"/> resulting in a <see cref="Tuple{T1, T2}"/> containing the <see cref="ConfigurationFile"/> and downloaded <see cref="FileTicketResult"/> <see cref="Stream"/>.</returns>
|
||||
Task<Tuple<ConfigurationFile, Stream>> Read(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Overwrite a <see cref="ConfigurationFile"/> file
|
||||
/// </summary>
|
||||
/// <param name="file">The <see cref="ConfigurationFile"/> file to write</param>
|
||||
/// <param name="uploadStream">The <see cref="Stream"/> of uploaded data. If <see langword="null"/>, a delete will be attempted.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="ConfigurationFile"/></returns>
|
||||
Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="ConfigurationFile"/>.</returns>
|
||||
Task<ConfigurationFile> Write(ConfigurationFile file, Stream uploadStream, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Delete an empty <paramref name="directory"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -33,11 +33,12 @@ namespace Tgstation.Server.Client.Components
|
||||
Task<Job> Compile(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Gets the <see cref="EntityId"/>s of all <see cref="CompileJob"/>s for the instance
|
||||
/// Gets the <see cref="CompileJob"/>s for the instance
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of <see cref="CompileJob"/> <see cref="EntityId"/>s.</returns>
|
||||
Task<IReadOnlyList<EntityId>> GetJobIds(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<EntityId>> ListCompileJobs(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <paramref name="compileJob"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Tgstation.Server.Api.Models;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
@@ -33,9 +33,9 @@ namespace Tgstation.Server.Client.Components
|
||||
IConfigurationClient Configuration { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IInstanceUserClient"/>
|
||||
/// Access the <see cref="IInstancePermissionSetClient"/>.
|
||||
/// </summary>
|
||||
IInstanceUserClient Users { get; }
|
||||
IInstancePermissionSetClient PermissionSets { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Access the <see cref="IChatBotsClient"/>
|
||||
@@ -52,4 +52,4 @@ namespace Tgstation.Server.Client.Components
|
||||
/// </summary>
|
||||
IJobsClient Jobs { get; }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="InstancePermissionSet"/>s
|
||||
/// </summary>
|
||||
public interface IInstancePermissionSetClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="InstancePermissionSet"/> associated with the logged on user
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="InstancePermissionSet"/> associated with the logged on user</returns>
|
||||
Task<InstancePermissionSet> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific <paramref name="instancePermissionSet"/>
|
||||
/// </summary>
|
||||
/// <param name="instancePermissionSet">The <see cref="InstancePermissionSet"/> to get</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the requested <paramref name="instancePermissionSet"/></returns>
|
||||
Task<InstancePermissionSet> GetId(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="InstancePermissionSet"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of <see cref="InstancePermissionSet"/>s in the instance</returns>
|
||||
Task<IReadOnlyList<InstancePermissionSet>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <paramref name="instancePermissionSet"/>
|
||||
/// </summary>
|
||||
/// <param name="instancePermissionSet">The <see cref="InstancePermissionSet"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task<InstancePermissionSet> Update(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <paramref name="instancePermissionSet"/>
|
||||
/// </summary>
|
||||
/// <param name="instancePermissionSet">The <see cref="InstancePermissionSet"/> to create</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> reulting in the new <see cref="InstancePermissionSet"/></returns>
|
||||
Task<InstancePermissionSet> Create(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <paramref name="instancePermissionSet"/>
|
||||
/// </summary>
|
||||
/// <param name="instancePermissionSet">The <see cref="InstancePermissionSet"/> to delete</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="InstanceUser"/>s
|
||||
/// </summary>
|
||||
public interface IInstanceUserClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get the <see cref="InstanceUser"/> associated with the logged on user
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="InstanceUser"/> associated with the logged on user</returns>
|
||||
Task<InstanceUser> Read(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get a specific <paramref name="instanceUser"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceUser">The <see cref="InstanceUser"/> to get</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the requested <paramref name="instanceUser"/></returns>
|
||||
Task<InstanceUser> GetId(InstanceUser instanceUser, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get the <see cref="InstanceUser"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of <see cref="InstanceUser"/>s in the instance</returns>
|
||||
Task<IReadOnlyList<InstanceUser>> List(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <paramref name="instanceUser"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceUser">The <see cref="InstanceUser"/> to update</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task<InstanceUser> Update(InstanceUser instanceUser, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a <paramref name="instanceUser"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceUser">The <see cref="InstanceUser"/> to create</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> reulting in the new <see cref="InstanceUser"/></returns>
|
||||
Task<InstanceUser> Create(InstanceUser instanceUser, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Delete a <paramref name="instanceUser"/>
|
||||
/// </summary>
|
||||
/// <param name="instanceUser">The <see cref="InstanceUser"/> to delete</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -11,18 +11,20 @@ namespace Tgstation.Server.Client.Components
|
||||
public interface IJobsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// List the <see cref="Job"/> <see cref="EntityId"/>s in the <see cref="Instance"/>
|
||||
/// List the <see cref="Job"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of the <see cref="Job"/> <see cref="EntityId"/>s in the <see cref="Instance"/></returns>
|
||||
Task<IReadOnlyList<EntityId>> List(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<Job>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// List the active <see cref="Job"/>s in the <see cref="Instance"/>
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of the active <see cref="Job"/>s in the <see cref="Instance"/></returns>
|
||||
Task<IReadOnlyList<Job>> ListActive(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<Job>> ListActive(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Get a <paramref name="job"/>
|
||||
|
||||
@@ -22,7 +22,7 @@ namespace Tgstation.Server.Client.Components
|
||||
public IConfigurationClient Configuration { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstanceUserClient Users { get; }
|
||||
public IInstancePermissionSetClient PermissionSets { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IChatBotsClient ChatBots { get; }
|
||||
@@ -49,7 +49,7 @@ namespace Tgstation.Server.Client.Components
|
||||
Repository = new RepositoryClient(apiClient, instance);
|
||||
DreamDaemon = new DreamDaemonClient(apiClient, instance);
|
||||
Configuration = new ConfigurationClient(apiClient, instance);
|
||||
Users = new InstanceUserClient(apiClient, instance);
|
||||
PermissionSets = new InstancePermissionSetClient(apiClient, instance);
|
||||
ChatBots = new ChatBotsClient(apiClient, instance);
|
||||
DreamMaker = new DreamMakerClient(apiClient, instance);
|
||||
Jobs = new JobsClient(apiClient, instance);
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class InstancePermissionSetClient : PaginatedClient, IInstancePermissionSetClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="InstancePermissionSetClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstancePermissionSetClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public InstancePermissionSetClient(IApiClient apiClient, Instance instance)
|
||||
: base(apiClient)
|
||||
{
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstancePermissionSet> Create(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken) => ApiClient.Create<InstancePermissionSet, InstancePermissionSet>(Routes.InstancePermissionSet, instancePermissionSet ?? throw new ArgumentNullException(nameof(instancePermissionSet)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken) => ApiClient.Delete(
|
||||
Routes.SetID(
|
||||
Routes.InstancePermissionSet,
|
||||
instancePermissionSet.PermissionSetId),
|
||||
instance.Id,
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstancePermissionSet> Read(CancellationToken cancellationToken) => ApiClient.Read<InstancePermissionSet>(Routes.InstancePermissionSet, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstancePermissionSet> Update(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken) => ApiClient.Update<InstancePermissionSet, InstancePermissionSet>(Routes.InstancePermissionSet, instancePermissionSet ?? throw new ArgumentNullException(nameof(instancePermissionSet)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<InstancePermissionSet>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<InstancePermissionSet>(
|
||||
paginationSettings,
|
||||
Routes.ListRoute(Routes.InstancePermissionSet),
|
||||
instance.Id,
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstancePermissionSet> GetId(InstancePermissionSet instancePermissionSet, CancellationToken cancellationToken) => ApiClient.Read<InstancePermissionSet>(Routes.SetID(Routes.InstancePermissionSet, instancePermissionSet?.PermissionSetId ?? throw new ArgumentNullException(nameof(instancePermissionSet))), instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class InstanceUserClient : IInstanceUserClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="InstanceUserClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="InstanceUserClient"/>
|
||||
/// </summary>
|
||||
readonly Instance instance;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceUserClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="instance">The value of <see cref="instance"/></param>
|
||||
public InstanceUserClient(IApiClient apiClient, Instance instance)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> Create(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Create<InstanceUser, InstanceUser>(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Delete(
|
||||
Routes.SetID(
|
||||
Routes.InstanceUser,
|
||||
instanceUser.UserId),
|
||||
instance.Id,
|
||||
cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> Read(CancellationToken cancellationToken) => apiClient.Read<InstanceUser>(Routes.InstanceUser, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> Update(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Update<InstanceUser, InstanceUser>(Routes.InstanceUser, instanceUser ?? throw new ArgumentNullException(nameof(instanceUser)), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<InstanceUser>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<InstanceUser>>(Routes.ListRoute(Routes.InstanceUser), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<InstanceUser> GetId(InstanceUser instanceUser, CancellationToken cancellationToken) => apiClient.Read<InstanceUser>(Routes.SetID(Routes.InstanceUser, instanceUser?.UserId ?? throw new ArgumentNullException(nameof(instanceUser))), instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -8,13 +8,8 @@ using Tgstation.Server.Api.Models;
|
||||
namespace Tgstation.Server.Client.Components
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class JobsClient : IJobsClient
|
||||
sealed class JobsClient : PaginatedClient, IJobsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="JobsClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/> for the <see cref="JobsClient"/>
|
||||
/// </summary>
|
||||
@@ -23,24 +18,26 @@ namespace Tgstation.Server.Client.Components
|
||||
/// <summary>
|
||||
/// Construct a <see cref="JobsClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
/// <param name="instance">The value of <see cref="Instance"/></param>
|
||||
public JobsClient(IApiClient apiClient, Instance instance)
|
||||
: base(apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Cancel(Job job, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.Jobs, job?.Id ?? throw new ArgumentNullException(nameof(job))), instance.Id, cancellationToken);
|
||||
public Task Cancel(Job job, CancellationToken cancellationToken) => ApiClient.Delete(Routes.SetID(Routes.Jobs, job?.Id ?? throw new ArgumentNullException(nameof(job))), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<EntityId>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<EntityId>>(Routes.ListRoute(Routes.Jobs), instance.Id, cancellationToken);
|
||||
public Task<IReadOnlyList<Job>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<Job>(paginationSettings, Routes.ListRoute(Routes.Jobs), instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<Job>> ListActive(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<Job>>(Routes.Jobs, instance.Id, cancellationToken);
|
||||
public Task<IReadOnlyList<Job>> ListActive(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<Job>(paginationSettings, Routes.Jobs, instance.Id, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Job> GetId(EntityId job, CancellationToken cancellationToken) => apiClient.Read<Job>(Routes.SetID(Routes.Jobs, job?.Id ?? throw new ArgumentNullException(nameof(job))), instance.Id, cancellationToken);
|
||||
public Task<Job> GetId(EntityId job, CancellationToken cancellationToken) => ApiClient.Read<Job>(Routes.SetID(Routes.Jobs, job?.Id ?? throw new ArgumentNullException(nameof(job))), instance.Id, cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+7
-7
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -6,7 +6,7 @@ using System.Threading.Tasks;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class HttpClient : IHttpClient
|
||||
sealed class HttpClientImplementation : IHttpClient
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public TimeSpan Timeout
|
||||
@@ -16,16 +16,16 @@ namespace Tgstation.Server.Client
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The real <see cref="System.Net.Http.HttpClient"/>
|
||||
/// The real <see cref="HttpClient"/>
|
||||
/// </summary>
|
||||
readonly System.Net.Http.HttpClient httpClient;
|
||||
readonly HttpClient httpClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="HttpClient"/>
|
||||
/// Construct an <see cref="HttpClientImplementation"/>
|
||||
/// </summary>
|
||||
public HttpClient()
|
||||
public HttpClientImplementation()
|
||||
{
|
||||
httpClient = new System.Net.Http.HttpClient();
|
||||
httpClient = new HttpClient();
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
@@ -1,4 +1,6 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -35,16 +37,17 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Lists the log files available for download.
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in an <see cref="IReadOnlyList{T}"/> of <see cref="LogFile"/> metadata.</returns>
|
||||
Task<IReadOnlyList<LogFile>> ListLogs(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<LogFile>> ListLogs(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Download a given <paramref name="logFile"/>.
|
||||
/// </summary>
|
||||
/// <param name="logFile">The <see cref="LogFile"/> to download.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the downloaded <see cref="LogFile"/>.</returns>
|
||||
Task<LogFile> GetLog(LogFile logFile, CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting a <see cref="Tuple{T1, T2}"/> containing the downloaded <see cref="LogFile"/> and associated <see cref="Stream"/>.</returns>
|
||||
Task<Tuple<LogFile, Stream>> GetLog(LogFile logFile, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
@@ -40,7 +42,7 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken);
|
||||
Task<TResult> Create<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) where TBody : class;
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PUT request
|
||||
@@ -69,7 +71,7 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken);
|
||||
Task<TResult> Update<TBody, TResult>(string route, TBody body, CancellationToken cancellationToken) where TBody : class;
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP POST request
|
||||
@@ -88,7 +90,7 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken);
|
||||
Task Update<TBody>(string route, TBody body, CancellationToken cancellationToken) where TBody : class;
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PATCH request.
|
||||
@@ -113,17 +115,22 @@ namespace Tgstation.Server.Client
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
Task<TResult> Create<TBody, TResult>(
|
||||
string route,
|
||||
TBody body,
|
||||
long instanceId,
|
||||
CancellationToken cancellationToken)
|
||||
where TBody : class;
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP PUT request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Create<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -133,7 +140,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Patch<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -143,7 +150,7 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -155,16 +162,21 @@ namespace Tgstation.Server.Client
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
Task<TResult> Update<TBody, TResult>(
|
||||
string route,
|
||||
TBody body,
|
||||
long instanceId,
|
||||
CancellationToken cancellationToken)
|
||||
where TBody : class;
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP DELETE request
|
||||
/// </summary>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete(string route, long instanceId, CancellationToken cancellationToken);
|
||||
@@ -175,19 +187,36 @@ namespace Tgstation.Server.Client
|
||||
/// <typeparam name="TBody">The type to of the request body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="body">The request body</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
|
||||
Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken) where TBody : class;
|
||||
|
||||
/// <summary>
|
||||
/// Run an HTTP DELETE request
|
||||
/// </summary>
|
||||
/// <typeparam name="TResult">The type of the response body</typeparam>
|
||||
/// <param name="route">The server route to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="Api.Models.EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="instanceId">The instance <see cref="EntityId.Id"/> to make the request to</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the response body as a <typeparamref name="TResult"/></returns>
|
||||
Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Downloads a file <see cref="Stream"/> for a given <paramref name="ticket"/>.
|
||||
/// </summary>
|
||||
/// <param name="ticket">The <see cref="FileTicketResult"/> to download.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the downloaded <see cref="Stream"/>.</returns>
|
||||
Task<Stream> Download(FileTicketResult ticket, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Uploads a given <paramref name="uploadStream"/> for a given <paramref name="ticket"/>.
|
||||
/// </summary>
|
||||
/// <param name="ticket">The <see cref="FileTicketResult"/> to download.</param>
|
||||
/// <param name="uploadStream">The <see cref="Stream"/> to upload. <see langword="null"/> represents an empty file.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task Upload(FileTicketResult ticket, Stream? uploadStream, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -14,9 +14,10 @@ namespace Tgstation.Server.Client
|
||||
/// <summary>
|
||||
/// Get all <see cref="IInstanceClient"/>s for <see cref="Instance"/>s the user can view
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of all <see cref="Instance"/>s the user can view</returns>
|
||||
Task<IReadOnlyList<Instance>> List(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<Instance>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create or attach an <paramref name="instance"/>
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -41,11 +41,16 @@ namespace Tgstation.Server.Client
|
||||
IUsersClient Users { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ServerInformation"/> of the <see cref="IServerClient"/>
|
||||
/// Access the <see cref="IUserGroupsClient"/>.
|
||||
/// </summary>
|
||||
IUserGroupsClient Groups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Api.Models.ServerInformation"/> of the <see cref="IServerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="ServerInformation"/> of the target server</returns>
|
||||
Task<ServerInformation> Version(CancellationToken cancellationToken);
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="Api.Models.ServerInformation"/> of the target server</returns>
|
||||
Task<ServerInformation> ServerInformation(CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Adds a <paramref name="requestLogger"/> to the request pipeline
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// For managing <see cref="UserGroup"/>s.
|
||||
/// </summary>
|
||||
public interface IUserGroupsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Get a specific <paramref name="group"/>.
|
||||
/// </summary>
|
||||
/// <param name="group">The <see cref="EntityId"/> of the <see cref="UserGroup"/> to get.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the requested <paramref name="group"/>.</returns>
|
||||
Task<UserGroup> GetId(EntityId group, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// List all <see cref="UserGroup"/>s.
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of all <see cref="UserGroup"/>s.</returns>
|
||||
Task<IReadOnlyList<UserGroup>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <paramref name="group"/>.
|
||||
/// </summary>
|
||||
/// <param name="group">The <see cref="UserGroup"/> to create.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The new <see cref="User"/>.</returns>
|
||||
Task<UserGroup> Create(UserGroup group, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Update a <paramref name="group"/>.
|
||||
/// </summary>
|
||||
/// <param name="group">The updated <see cref="UserGroup"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>The updated <see cref="User"/>.</returns>
|
||||
Task<UserGroup> Update(UserGroup group, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Deletes a <paramref name="group"/>.
|
||||
/// </summary>
|
||||
/// <param name="group">The <see cref="EntityId"/> of the <see cref="UserGroup"/> to delete.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task Delete(EntityId group, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
@@ -23,14 +23,15 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="user">The <see cref="Api.Models.Internal.User"/> to get.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the requested <paramref name="user"/></returns>
|
||||
Task<User> GetId(Api.Models.Internal.User user, CancellationToken cancellationToken);
|
||||
Task<User> GetId(Api.Models.Internal.UserBase user, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// List all <see cref="User"/>s
|
||||
/// </summary>
|
||||
/// <param name="paginationSettings">The optional <see cref="PaginationSettings"/> for the operation.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="IReadOnlyList{T}"/> of all <see cref="User"/>s</returns>
|
||||
Task<IReadOnlyList<User>> List(CancellationToken cancellationToken);
|
||||
Task<IReadOnlyList<User>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Create a new <paramref name="user"/>
|
||||
@@ -48,4 +49,4 @@ namespace Tgstation.Server.Client
|
||||
/// <returns>The updated <see cref="User"/></returns>
|
||||
Task<User> Update(UserUpdate user, CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -9,41 +9,36 @@ using Tgstation.Server.Client.Components;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class InstanceManagerClient : IInstanceManagerClient
|
||||
sealed class InstanceManagerClient : PaginatedClient, IInstanceManagerClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="InstanceManagerClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InstanceManagerClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
public InstanceManagerClient(IApiClient apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
}
|
||||
: base(apiClient)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Instance> CreateOrAttach(Instance instance, CancellationToken cancellationToken) => apiClient.Create<Instance, Instance>(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken);
|
||||
public Task<Instance> CreateOrAttach(Instance instance, CancellationToken cancellationToken) => ApiClient.Create<Instance, Instance>(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Detach(Instance instance, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
|
||||
public Task Detach(Instance instance, CancellationToken cancellationToken) => ApiClient.Delete(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<Instance>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<Instance>>(Routes.ListRoute(Routes.InstanceManager), cancellationToken);
|
||||
public Task<IReadOnlyList<Instance>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<Instance>(paginationSettings, Routes.ListRoute(Routes.InstanceManager), null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Instance> Update(Instance instance, CancellationToken cancellationToken) => apiClient.Update<Instance, Instance>(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken);
|
||||
public Task<Instance> Update(Instance instance, CancellationToken cancellationToken) => ApiClient.Update<Instance, Instance>(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<Instance> GetId(Instance instance, CancellationToken cancellationToken) => apiClient.Read<Instance>(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
|
||||
public Task<Instance> GetId(Instance instance, CancellationToken cancellationToken) => ApiClient.Read<Instance>(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task GrantPermissions(Instance instance, CancellationToken cancellationToken) => apiClient.Patch(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
|
||||
public Task GrantPermissions(Instance instance, CancellationToken cancellationToken) => ApiClient.Patch(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public IInstanceClient CreateClient(Instance instance) => new InstanceClient(apiClient, instance);
|
||||
public IInstanceClient CreateClient(Instance instance) => new InstanceClient(ApiClient, instance);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
@@ -17,6 +17,11 @@ namespace Tgstation.Server.Client
|
||||
"The current user has insufficient permissions to perform the requested operation!")
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Intializes a new instance of the <see cref="InsufficientPermissionsException"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
public InsufficientPermissionsException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="InsufficientPermissionsException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
@@ -30,4 +35,4 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public InsufficientPermissionsException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
@@ -17,6 +17,11 @@ namespace Tgstation.Server.Client
|
||||
public MethodNotSupportedException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Intializes a new instance of the <see cref="MethodNotSupportedException"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
public MethodNotSupportedException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="MethodNotSupportedException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
@@ -30,4 +35,4 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public MethodNotSupportedException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Client that deals with getting paginated results.
|
||||
/// </summary>
|
||||
abstract class PaginatedClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>
|
||||
/// </summary>
|
||||
protected IApiClient ApiClient { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="PaginatedClient"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="ApiClient"/>.</param>
|
||||
public PaginatedClient(IApiClient apiClient)
|
||||
{
|
||||
ApiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Reads a given <paramref name="route"/> with paged results.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The <see cref="Type"/> of result model.</typeparam>
|
||||
/// <param name="paginationSettings">The <see cref="PaginationSettings"/> if any.</param>
|
||||
/// <param name="route">The route.</param>
|
||||
/// <param name="instanceId">The optional <see cref="Instance"/> <see cref="EntityId.Id"/>.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in an <see cref="IReadOnlyList{T}"/> of the paginated <typeparamref name="TModel"/>s.</returns>
|
||||
protected async Task<IReadOnlyList<TModel>> ReadPaged<TModel>(
|
||||
PaginationSettings? paginationSettings,
|
||||
string route,
|
||||
long? instanceId,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
if (route == null)
|
||||
throw new ArgumentNullException(nameof(route));
|
||||
|
||||
var routeFormatter = $"{route}?page={{0}}";
|
||||
var currentPage = 1;
|
||||
if (paginationSettings != null)
|
||||
{
|
||||
if (paginationSettings.RetrieveCount == 0)
|
||||
return new List<TModel>(); // that was easy
|
||||
else if (paginationSettings.RetrieveCount < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(paginationSettings), "RetrieveCount cannot be less than 0!");
|
||||
|
||||
int? pageSize = null;
|
||||
if (paginationSettings.PageSize.HasValue)
|
||||
{
|
||||
// pagesize validates itself on first request
|
||||
pageSize = paginationSettings.PageSize.Value;
|
||||
routeFormatter += $"&pageSize={pageSize}";
|
||||
}
|
||||
|
||||
if (paginationSettings.Offset.HasValue)
|
||||
{
|
||||
if(paginationSettings.Offset.Value < 0)
|
||||
throw new ArgumentOutOfRangeException(nameof(paginationSettings), "Offset cannot be less than 0!");
|
||||
|
||||
pageSize ??= paginationSettings.Offset.Value;
|
||||
currentPage = (paginationSettings.Offset.Value / pageSize.Value) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
Task<Paginated<TModel>> GetPage() => instanceId.HasValue
|
||||
? ApiClient.Read<Paginated<TModel>>(
|
||||
String.Format(CultureInfo.InvariantCulture, routeFormatter, currentPage),
|
||||
instanceId.Value,
|
||||
cancellationToken)
|
||||
: ApiClient.Read<Paginated<TModel>>(
|
||||
String.Format(CultureInfo.InvariantCulture, routeFormatter, currentPage),
|
||||
cancellationToken);
|
||||
|
||||
var firstPage = await GetPage().ConfigureAwait(false);
|
||||
|
||||
var totalAvailable = firstPage.TotalPages * firstPage.PageSize;
|
||||
var maximumItems = paginationSettings?.RetrieveCount.HasValue == true
|
||||
? Math.Min(paginationSettings.RetrieveCount!.Value, totalAvailable)
|
||||
: totalAvailable;
|
||||
|
||||
var results = new List<TModel>(maximumItems);
|
||||
var currentResults = firstPage;
|
||||
do
|
||||
{
|
||||
// check if first page
|
||||
if(currentPage > 1)
|
||||
currentResults = await GetPage().ConfigureAwait(false);
|
||||
|
||||
if (currentResults.Content == null)
|
||||
throw new ApiConflictException("Paginated results missing content!");
|
||||
|
||||
IEnumerable<TModel> rangeToAdd = currentResults.Content;
|
||||
if (paginationSettings?.Offset.HasValue == true)
|
||||
{
|
||||
rangeToAdd = rangeToAdd
|
||||
.Skip(paginationSettings.Offset!.Value % currentResults.PageSize);
|
||||
}
|
||||
|
||||
var itemsAvailableInPage = rangeToAdd.Count();
|
||||
var itemsStillRequired = maximumItems - results.Count;
|
||||
if (itemsAvailableInPage > itemsStillRequired)
|
||||
rangeToAdd = rangeToAdd
|
||||
.Take(itemsStillRequired);
|
||||
|
||||
results.AddRange(rangeToAdd);
|
||||
++currentPage;
|
||||
}
|
||||
while (results.Count < maximumItems && currentPage <= currentResults.TotalPages);
|
||||
|
||||
return results;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <summary>
|
||||
/// Settings for a paginated request.
|
||||
/// </summary>
|
||||
public sealed class PaginationSettings
|
||||
{
|
||||
/// <summary>
|
||||
/// The size of a page. Defaults to server settings.
|
||||
/// </summary>
|
||||
public int? PageSize { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The offset to take from. Default 0.
|
||||
/// </summary>
|
||||
public int? Offset { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The maximum amount of items to retrieve. Default everything.
|
||||
/// </summary>
|
||||
/// <remarks>Results will be truncated if overflow occurs due to <see cref="PageSize"/>.</remarks>
|
||||
public int? RetrieveCount { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
[assembly: InternalsVisibleTo("Tgstation.Server.Tests")]
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using Microsoft.Net.Http.Headers;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Net.Http;
|
||||
@@ -23,12 +23,15 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="responseMessage">The <see cref="HttpResponseMessage"/> for the <see cref="ClientException"/>.</param>
|
||||
public RateLimitException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{
|
||||
if (responseMessage == null)
|
||||
throw new ArgumentNullException(nameof(responseMessage));
|
||||
|
||||
if (!responseMessage.Headers.TryGetValues(HeaderNames.RetryAfter, out var values))
|
||||
return;
|
||||
|
||||
var secondsString = values.FirstOrDefault();
|
||||
if (UInt32.TryParse(secondsString, out var seconds))
|
||||
RetryAfter = DateTimeOffset.Now.AddSeconds(seconds);
|
||||
RetryAfter = DateTimeOffset.UtcNow.AddSeconds(seconds);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -36,6 +39,12 @@ namespace Tgstation.Server.Client
|
||||
/// </summary>
|
||||
public RateLimitException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="RateLimitException"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/>.</param>
|
||||
public RateLimitException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="RateLimitException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
@@ -43,4 +52,4 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public RateLimitException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
@@ -15,6 +15,11 @@ namespace Tgstation.Server.Client
|
||||
public RequestTimeoutException(HttpResponseMessage responseMessage) : base(responseMessage, "The request timed out!")
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Intializes a new instance of the <see cref="RequestTimeoutException"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
public RequestTimeoutException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="RequestTimeoutException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
@@ -28,4 +33,4 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public RequestTimeoutException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
@@ -39,6 +39,9 @@ namespace Tgstation.Server.Client
|
||||
/// <inheritdoc />
|
||||
public IUsersClient Users { get; }
|
||||
|
||||
/// <inheritdoc />
|
||||
public IUserGroupsClient Groups { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IApiClient"/> for the <see cref="ServerClient"/>
|
||||
/// </summary>
|
||||
@@ -65,15 +68,16 @@ namespace Tgstation.Server.Client
|
||||
Instances = new InstanceManagerClient(apiClient);
|
||||
Users = new UsersClient(apiClient);
|
||||
Administration = new AdministrationClient(apiClient);
|
||||
Groups = new UserGroupsClient(apiClient);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public void Dispose() => apiClient.Dispose();
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<ServerInformation> Version(CancellationToken cancellationToken) => apiClient.Read<ServerInformation>(Routes.Root, cancellationToken);
|
||||
public Task<ServerInformation> ServerInformation(CancellationToken cancellationToken) => apiClient.Read<ServerInformation>(Routes.Root, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public void AddRequestLogger(IRequestLogger requestLogger) => apiClient.AddRequestLogger(requestLogger);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Net.Http.Headers;
|
||||
@@ -38,7 +38,7 @@ namespace Tgstation.Server.Client
|
||||
string password,
|
||||
IEnumerable<IRequestLogger>? requestLoggers = null,
|
||||
TimeSpan? timeout = null,
|
||||
bool attemptRefreshLogin = true,
|
||||
bool attemptLoginRefresh = true,
|
||||
CancellationToken cancellationToken = default)
|
||||
{
|
||||
if (host == null)
|
||||
@@ -62,6 +62,9 @@ namespace Tgstation.Server.Client
|
||||
token = await api.Update<Token>(Routes.Root, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (!attemptLoginRefresh)
|
||||
loginHeaders = null;
|
||||
|
||||
var apiHeaders = new ApiHeaders(productHeaderValue, token.Bearer!);
|
||||
|
||||
var client = new ServerClient(ApiClientFactory.CreateApiClient(host, apiHeaders, loginHeaders), token);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
@@ -23,6 +23,12 @@ namespace Tgstation.Server.Client
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerErrorException"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="message">The message for the <see cref="Exception"/>.</param>
|
||||
public ServerErrorException(string message) : base(message) { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ServerErrorException"/> with a <paramref name="message"/> and <paramref name="innerException"/>
|
||||
/// </summary>
|
||||
@@ -30,4 +36,4 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public ServerErrorException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
@@ -15,6 +15,11 @@ namespace Tgstation.Server.Client
|
||||
public ServiceUnavailableException(HttpResponseMessage responseMessage) : base(responseMessage, "The service is unavailable!")
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Intializes a new instance of the <see cref="ServiceUnavailableException"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
public ServiceUnavailableException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="ServiceUnavailableException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
@@ -28,4 +33,4 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public ServiceUnavailableException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<Import Project="../../build/Version.props" />
|
||||
|
||||
<PropertyGroup>
|
||||
@@ -23,18 +23,18 @@
|
||||
<LangVersion>latest</LangVersion>
|
||||
<Nullable>enable</Nullable>
|
||||
<DocumentationFile>bin\$(Configuration)\netstandard2.1\Tgstation.Server.Client.xml</DocumentationFile>
|
||||
<EnableNETAnalyzers>true</EnableNETAnalyzers>
|
||||
</PropertyGroup>
|
||||
|
||||
<PropertyGroup Condition="'$(Configuration)'=='Release'">
|
||||
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
|
||||
<WarningsAsErrors />
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
|
||||
<PackageReference Include="StyleCop.Analyzers" Version="1.1.118">
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="5.0.1">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
</ItemGroup>
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Net.Http;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
@@ -17,6 +17,11 @@ namespace Tgstation.Server.Client
|
||||
public UnauthorizedException(ErrorMessage? errorMessage, HttpResponseMessage responseMessage) : base(errorMessage, responseMessage)
|
||||
{ }
|
||||
|
||||
/// <summary>
|
||||
/// Intializes a new instance of the <see cref="UnauthorizedException"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
public UnauthorizedException() { }
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="UnauthorizedException"/> with a <paramref name="message"/>
|
||||
/// </summary>
|
||||
@@ -30,4 +35,4 @@ namespace Tgstation.Server.Client
|
||||
/// <param name="innerException">The inner <see cref="Exception"/> for the base <see cref="Exception"/></param>
|
||||
public UnauthorizedException(string message, Exception innerException) : base(message, innerException) { }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Api;
|
||||
using Tgstation.Server.Api.Models;
|
||||
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class UserGroupsClient : PaginatedClient, IUserGroupsClient
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="UserGroupsClient"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
public UserGroupsClient(IApiClient apiClient)
|
||||
: base(apiClient)
|
||||
{
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<UserGroup> Create(UserGroup group, CancellationToken cancellationToken) => ApiClient.Create<UserGroup, UserGroup>(Routes.UserGroup, group ?? throw new ArgumentNullException(nameof(group)), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<UserGroup> GetId(EntityId group, CancellationToken cancellationToken) => ApiClient.Read<UserGroup>(Routes.SetID(Routes.UserGroup, group?.Id ?? throw new ArgumentNullException(nameof(group))), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<UserGroup>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<UserGroup>(paginationSettings, Routes.ListRoute(Routes.UserGroup), null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<UserGroup> Update(UserGroup group, CancellationToken cancellationToken) => ApiClient.Update<UserGroup, UserGroup>(Routes.UserGroup, group ?? throw new ArgumentNullException(nameof(group)), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task Delete(EntityId group, CancellationToken cancellationToken) => ApiClient.Delete(Routes.SetID(Routes.UserGroup, group?.Id ?? throw new ArgumentNullException(nameof(group))), cancellationToken);
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
using System;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@@ -8,35 +8,31 @@ using Tgstation.Server.Api.Models;
|
||||
namespace Tgstation.Server.Client
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class UsersClient : IUsersClient
|
||||
sealed class UsersClient : PaginatedClient, IUsersClient
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="apiClient"/> for the <see cref="UsersClient"/>
|
||||
/// </summary>
|
||||
readonly IApiClient apiClient;
|
||||
|
||||
/// <summary>
|
||||
/// Construct an <see cref="UsersClient"/>
|
||||
/// </summary>
|
||||
/// <param name="apiClient">The value of <see cref="apiClient"/></param>
|
||||
/// <param name="apiClient">The <see cref="IApiClient"/> for the <see cref="PaginatedClient"/>.</param>
|
||||
public UsersClient(IApiClient apiClient)
|
||||
: base(apiClient)
|
||||
{
|
||||
this.apiClient = apiClient ?? throw new ArgumentNullException(nameof(apiClient));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<User> Create(UserUpdate user, CancellationToken cancellationToken) => apiClient.Create<UserUpdate, User>(Routes.User, user ?? throw new ArgumentNullException(nameof(user)), cancellationToken);
|
||||
public Task<User> Create(UserUpdate user, CancellationToken cancellationToken) => ApiClient.Create<UserUpdate, User>(Routes.User, user ?? throw new ArgumentNullException(nameof(user)), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<User> GetId(Api.Models.Internal.User user, CancellationToken cancellationToken) => apiClient.Read<User>(Routes.SetID(Routes.User, user?.Id ?? throw new ArgumentNullException(nameof(user))), cancellationToken);
|
||||
public Task<User> GetId(Api.Models.Internal.UserBase user, CancellationToken cancellationToken) => ApiClient.Read<User>(Routes.SetID(Routes.User, user?.Id ?? throw new ArgumentNullException(nameof(user))), cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<IReadOnlyList<User>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<User>>(Routes.ListRoute(Routes.User), cancellationToken);
|
||||
public Task<IReadOnlyList<User>> List(PaginationSettings? paginationSettings, CancellationToken cancellationToken)
|
||||
=> ReadPaged<User>(paginationSettings, Routes.ListRoute(Routes.User), null, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<User> Read(CancellationToken cancellationToken) => apiClient.Read<User>(Routes.User, cancellationToken);
|
||||
public Task<User> Read(CancellationToken cancellationToken) => ApiClient.Read<User>(Routes.User, cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<User> Update(UserUpdate user, CancellationToken cancellationToken) => apiClient.Update<UserUpdate, User>(Routes.User, user ?? throw new ArgumentNullException(nameof(user)), cancellationToken);
|
||||
public Task<User> Update(UserUpdate user, CancellationToken cancellationToken) => ApiClient.Update<UserUpdate, User>(Routes.User, user ?? throw new ArgumentNullException(nameof(user)), cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user