mirror of
https://github.com/tgstation/tgstation-server.git
synced 2026-08-25 05:56:58 +01:00
+91
-45
@@ -11,6 +11,97 @@ branches:
|
||||
|
||||
jobs:
|
||||
include:
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
- DMAPI=false
|
||||
- CONFIG=Release
|
||||
- TGS4_TEST_DATABASE_TYPE=MySql
|
||||
- TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test"
|
||||
name: "MySql Integration Test"
|
||||
language: csharp
|
||||
mono: none
|
||||
services:
|
||||
- mysql
|
||||
dotnet: 3.1
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.nuget/packages:
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
- DMAPI=false
|
||||
- CONFIG=Release
|
||||
- TGS4_TEST_DATABASE_TYPE=Sqlite
|
||||
- TGS4_TEST_CONNECTION_STRING="Data Source=TravisTestDB.sqlite3;Mode=ReadWriteCreate"
|
||||
name: "Sqlite Integration Test"
|
||||
language: csharp
|
||||
mono: none
|
||||
dotnet: 3.1
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.nuget/packages:
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
# - env:
|
||||
# - DoxGeneration=false
|
||||
# - DockerBuild=false
|
||||
# - DMAPI=false
|
||||
# - CONFIG=Release
|
||||
# - TGS4_TEST_DATABASE_TYPE=PostgresSql
|
||||
# - TGS4_TEST_CONNECTION_STRING="Application Name=tgstation-server;Host=127.0.0.1;Username=postgres;Password=;Database=TGS_Test"
|
||||
# name: "PostgresSql Integration Test"
|
||||
# language: csharp
|
||||
# mono: none
|
||||
# dotnet: 3.1
|
||||
# services:
|
||||
# - postgresql
|
||||
# cache:
|
||||
# directories:
|
||||
# - $HOME/.nuget/packages:
|
||||
# addons:
|
||||
# apt:
|
||||
# packages:
|
||||
# - libc6-i386
|
||||
# - libstdc++6:i386
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
- DMAPI=false
|
||||
- CONFIG=Debug
|
||||
name: "Debug Unit Tests"
|
||||
language: csharp
|
||||
mono: none
|
||||
dotnet: 3.1
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.nuget/packages:
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
- DMAPI=false
|
||||
- CONFIG=Release
|
||||
name: "Release Unit Tests"
|
||||
language: csharp
|
||||
mono: none
|
||||
dotnet: 3.1
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.nuget/packages:
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=true
|
||||
name: "Docker Build"
|
||||
services:
|
||||
- docker
|
||||
- env:
|
||||
- DoxGeneration=true
|
||||
name: "Dox Generation"
|
||||
@@ -30,56 +121,11 @@ jobs:
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
|
||||
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
- DMAPI=false
|
||||
- CONFIG=Debug
|
||||
name: "Test Server Debug"
|
||||
language: csharp
|
||||
mono: none
|
||||
dotnet: 3.1
|
||||
services:
|
||||
- mysql
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.nuget/packages:
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=false
|
||||
- DMAPI=false
|
||||
- CONFIG=Release
|
||||
name: "Test Server Release"
|
||||
language: csharp
|
||||
mono: none
|
||||
dotnet: 3.1
|
||||
services:
|
||||
- mysql
|
||||
cache:
|
||||
directories:
|
||||
- $HOME/.nuget/packages:
|
||||
addons:
|
||||
apt:
|
||||
packages:
|
||||
- libc6-i386
|
||||
- libstdc++6:i386
|
||||
- env:
|
||||
- DoxGeneration=false
|
||||
- DockerBuild=true
|
||||
name: "Docker Build"
|
||||
services:
|
||||
- docker
|
||||
|
||||
install:
|
||||
- if [ $DoxGeneration = false ] && [ $DockerBuild = false ] && [ $DMAPI = true ]; then build/install_byond.sh; fi
|
||||
|
||||
+1
-1
@@ -73,7 +73,7 @@ test_script:
|
||||
- OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Host.Watchdog.Tests/Tgstation.Server.Host.Watchdog.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Watchdog.Tests*]*" -output:".\watchdog_coverage.xml" -oldstyle
|
||||
- ps: $wc = New-Object 'System.Net.WebClient'
|
||||
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Host.Watchdog.Tests\TestResults\results.trx))
|
||||
- OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations..*" -output:".\server_coverage.xml" -oldstyle
|
||||
- OpenCover.Console.exe -returntargetcode -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx --logger:console;noprogress=true /p:DebugType=full tests/Tgstation.Server.Tests/Tgstation.Server.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations..*" -output:".\server_coverage.xml" -oldstyle
|
||||
- ps: $wc = New-Object 'System.Net.WebClient'
|
||||
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\tests\Tgstation.Server.Tests\TestResults\results.trx))
|
||||
- lint-openapi -p -c build/OpenApiValidationSettings.json C:/swagger.json
|
||||
|
||||
+4
-4
@@ -2,10 +2,10 @@
|
||||
<PropertyGroup>
|
||||
<!-- This is the authorative version list -->
|
||||
<!-- Integration tests will ensure they match across the board -->
|
||||
<TgsCoreVersion>4.2.8</TgsCoreVersion>
|
||||
<TgsApiVersion>6.4.1</TgsApiVersion>
|
||||
<TgsClientVersion>7.0.1</TgsClientVersion>
|
||||
<TgsDmapiVersion>5.2.1</TgsDmapiVersion>
|
||||
<TgsCoreVersion>4.3.0</TgsCoreVersion>
|
||||
<TgsApiVersion>6.5.0</TgsApiVersion>
|
||||
<TgsClientVersion>7.1.0</TgsClientVersion>
|
||||
<TgsDmapiVersion>5.2.2</TgsDmapiVersion>
|
||||
<TgsControlPanelVersion>0.4.0</TgsControlPanelVersion>
|
||||
<TgsHostWatchdogVersion>1.1.0</TgsHostWatchdogVersion>
|
||||
</PropertyGroup>
|
||||
|
||||
Executable
+17
@@ -0,0 +1,17 @@
|
||||
#!/bin/bash
|
||||
set -e
|
||||
|
||||
export TGS4_TEST_DISCORD_CHANNEL=493119635319947269
|
||||
export TGS4_TEST_IRC_CHANNEL=\#botbus
|
||||
export TGS4_TEST_TEMP_DIRECTORY=~/tgs4_test
|
||||
#token set in CI settings
|
||||
|
||||
cd tests/Tgstation.Server.Tests
|
||||
|
||||
dotnet build -c $CONFIG
|
||||
|
||||
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp3.1/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build --logger:\"console;noprogress=true\"" --format opencover --output "../../TestResults/integration_test.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations.*"
|
||||
|
||||
cd ../../TestResults
|
||||
|
||||
bash <(curl -s https://codecov.io/bash) -f integration_test.xml -F integration
|
||||
+5
-18
@@ -7,6 +7,11 @@ mkdir TestResults
|
||||
|
||||
source ~/.nvm/nvm.sh && nvm install 10
|
||||
|
||||
if [[ ! -z "${TGS4_TEST_CONNECTION_STRING}" ]]; then
|
||||
build/integration_test.sh
|
||||
exit
|
||||
fi
|
||||
|
||||
cd tests/Tgstation.Server.Api.Tests
|
||||
|
||||
dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
|
||||
@@ -32,22 +37,6 @@ cd ../Tgstation.Server.Host.Console.Tests
|
||||
dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
|
||||
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp3.1/Tgstation.Server.Host.Console.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/console.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Console.Tests*]*"
|
||||
|
||||
cd ../Tgstation.Server.Tests
|
||||
export TGS4_TEST_DATABASE_TYPE=MySql
|
||||
export TGS4_TEST_DISCORD_CHANNEL=493119635319947269
|
||||
export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test"
|
||||
export TGS4_TEST_IRC_CHANNEL=\#botbus
|
||||
export TGS4_TEST_TEMP_DIRECTORY=~/tgs4_test
|
||||
#token set in CI settings
|
||||
dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
|
||||
|
||||
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp3.1/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/servermy.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations.*"
|
||||
|
||||
#Run again for Sqlite
|
||||
export TGS4_TEST_DATABASE_TYPE=Sqlite
|
||||
export TGS4_TEST_CONNECTION_STRING="Data Source=TravisTestDB.sqlite3;Mode=ReadWriteCreate"
|
||||
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp3.1/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/serversl.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations.*"
|
||||
|
||||
cd ../../TestResults
|
||||
|
||||
bash <(curl -s https://codecov.io/bash) -f api.xml -F unittests
|
||||
@@ -55,5 +44,3 @@ bash <(curl -s https://codecov.io/bash) -f client.xml -F unittests
|
||||
bash <(curl -s https://codecov.io/bash) -f host.xml -F unittests
|
||||
bash <(curl -s https://codecov.io/bash) -f watchdog.xml -F unittests
|
||||
bash <(curl -s https://codecov.io/bash) -f console.xml -F unittests
|
||||
bash <(curl -s https://codecov.io/bash) -f servermy.xml -F integration
|
||||
bash <(curl -s https://codecov.io/bash) -f serversl.xml -F integration
|
||||
|
||||
+2
-1
@@ -1,6 +1,6 @@
|
||||
// tgstation-server DMAPI
|
||||
|
||||
#define TGS_DMAPI_VERSION "5.2.1"
|
||||
#define TGS_DMAPI_VERSION "5.2.2"
|
||||
|
||||
// All functions and datums outside this document are subject to change with any version and should not be relied on.
|
||||
|
||||
@@ -127,6 +127,7 @@
|
||||
* Call this when your initializations are complete and your game is ready to play before any player interactions happen.
|
||||
*
|
||||
* This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running.
|
||||
* Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184
|
||||
* Before this point, note that any static files or directories may be in use by another server. Your code should account for this.
|
||||
* This function should not be called before ..() in [/world/proc/New].
|
||||
*/
|
||||
|
||||
@@ -114,12 +114,14 @@
|
||||
/datum/tgs_api/v4/OnInitializationComplete()
|
||||
Export(TGS4_COMM_SERVER_PRIMED)
|
||||
|
||||
var/tgs4_secret_sleep_offline_sauce = 24051994
|
||||
var/tgs4_secret_sleep_offline_sauce = 29051994
|
||||
var/old_sleep_offline = world.sleep_offline
|
||||
world.sleep_offline = tgs4_secret_sleep_offline_sauce
|
||||
sleep(1)
|
||||
if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it
|
||||
world.sleep_offline = old_sleep_offline
|
||||
else
|
||||
TGS_WARNING_LOG("world.sleep_offline unexpectedly changed!")
|
||||
|
||||
/datum/tgs_api/v4/OnTopic(T)
|
||||
var/list/params = params2list(T)
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
var/list/chat_channels
|
||||
|
||||
/datum/tgs_api/v5/ApiVersion()
|
||||
return new /datum/tgs_version("5.2.1")
|
||||
return new /datum/tgs_version("5.2.2")
|
||||
|
||||
/datum/tgs_api/v5/OnWorldNew(minimum_required_security_level)
|
||||
server_port = world.params[DMAPI5_PARAM_SERVER_PORT]
|
||||
@@ -94,6 +94,8 @@
|
||||
sleep(1)
|
||||
if(world.sleep_offline == tgs4_secret_sleep_offline_sauce) //if not someone changed it
|
||||
world.sleep_offline = old_sleep_offline
|
||||
else
|
||||
TGS_WARNING_LOG("world.sleep_offline unexpectedly changed!")
|
||||
|
||||
/datum/tgs_api/v5/proc/TopicResponse(error_message = null)
|
||||
var/list/response = list()
|
||||
|
||||
@@ -18,6 +18,6 @@ namespace Tgstation.Server.Api.Models
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/>s made from the <see cref="RevisionInformation"/>
|
||||
/// </summary>
|
||||
public ICollection<CompileJob>? CompileJobs { get; set; }
|
||||
public ICollection<EntityId>? CompileJobs { get; set; }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,6 @@
|
||||
/// <summary>
|
||||
/// The <see cref="User"/> who created this <see cref="User"/>
|
||||
/// </summary>
|
||||
public User? CreatedBy { get; set; }
|
||||
public Internal.User? CreatedBy { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -162,22 +162,9 @@ namespace Tgstation.Server.Client
|
||||
var headersToUse = tokenRefresh ? tokenRefreshHeaders! : headers;
|
||||
headersToUse.SetRequestHeaders(request.Headers, instanceId);
|
||||
|
||||
// This is meant to be a gate against token refresh operations
|
||||
await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
if(!tokenRefresh)
|
||||
semaphoreSlim.Release();
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(requestLoggers.Select(x => x.LogRequest(request, cancellationToken))).ConfigureAwait(false);
|
||||
|
||||
response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (tokenRefresh)
|
||||
semaphoreSlim.Release();
|
||||
}
|
||||
response = await httpClient.SendAsync(request, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
using (response)
|
||||
@@ -214,8 +201,13 @@ namespace Tgstation.Server.Client
|
||||
if (tokenRefreshHeaders == null)
|
||||
return false;
|
||||
|
||||
var startingToken = headers.Token;
|
||||
await semaphoreSlim.WaitAsync(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (startingToken != headers.Token)
|
||||
return true;
|
||||
|
||||
var token = await RunRequest<Token>(Routes.Root, new object(), HttpMethod.Post, null, true, cancellationToken);
|
||||
headers = new ApiHeaders(headers.UserAgent!, token.Bearer!);
|
||||
}
|
||||
@@ -223,6 +215,10 @@ namespace Tgstation.Server.Client
|
||||
{
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
semaphoreSlim.Release();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
"isRoot": true,
|
||||
"tools": {
|
||||
"dotnet-ef": {
|
||||
"version": "3.1.3",
|
||||
"version": "3.1.4",
|
||||
"commands": [
|
||||
"dotnet-ef"
|
||||
]
|
||||
|
||||
@@ -620,13 +620,61 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task SendUpdateMessage(string message, CancellationToken cancellationToken)
|
||||
public async Task<Func<string, string, Task>> SendDeploymentMessage(
|
||||
Models.RevisionInformation revisionInformation,
|
||||
Version byondVersion,
|
||||
DateTimeOffset? estimatedCompletionTime,
|
||||
string gitHubOwner,
|
||||
string gitHubRepo,
|
||||
bool localCommitPushed,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
List<ulong> wdChannels;
|
||||
message = String.Format(CultureInfo.InvariantCulture, "DM: {0}", message);
|
||||
lock (mappedChannels) // so it doesn't change while we're using it
|
||||
wdChannels = mappedChannels.Where(x => x.Value.IsUpdatesChannel).Select(x => x.Key).ToList();
|
||||
return SendMessage(message, wdChannels, cancellationToken);
|
||||
|
||||
logger.LogTrace("Sending deployment message for RevisionInformation: {0}", revisionInformation.Id);
|
||||
|
||||
var callbacks = new List<Func<string, string, Task>>();
|
||||
|
||||
await Task.WhenAll(
|
||||
wdChannels.Select(
|
||||
async x =>
|
||||
{
|
||||
ChannelMapping channelMapping;
|
||||
lock (mappedChannels)
|
||||
if (!mappedChannels.TryGetValue(x, out channelMapping))
|
||||
return;
|
||||
IProvider provider;
|
||||
lock (providers)
|
||||
if (!providers.TryGetValue(channelMapping.ProviderId, out provider))
|
||||
return;
|
||||
try
|
||||
{
|
||||
var callback = await provider.SendUpdateMessage(
|
||||
revisionInformation,
|
||||
byondVersion,
|
||||
estimatedCompletionTime,
|
||||
gitHubOwner,
|
||||
gitHubRepo,
|
||||
channelMapping.ProviderChannelId,
|
||||
localCommitPushed,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
callbacks.Add(callback);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
logger.LogWarning(
|
||||
"Error sending deploy message to provider {0}! Exception: {1}",
|
||||
channelMapping.ProviderId,
|
||||
ex);
|
||||
}
|
||||
}))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return (errorMessage, dreamMakerOutput) => Task.WhenAll(callbacks.Select(x => x(errorMessage, dreamMakerOutput)));
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -76,14 +76,21 @@ namespace Tgstation.Server.Host.Components.Chat.Commands
|
||||
head = repo.Head;
|
||||
}
|
||||
|
||||
await databaseContextFactory.UseContext(async db => results = await db.RevisionInformations.Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
|
||||
.SelectMany(x => x.ActiveTestMerges)
|
||||
.Select(x => x.TestMerge)
|
||||
.Select(x => new Models.TestMerge
|
||||
{
|
||||
Number = x.Number,
|
||||
PullRequestRevision = x.PullRequestRevision
|
||||
}).ToListAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false);
|
||||
await databaseContextFactory.UseContext(
|
||||
async db => results = await db
|
||||
.RevisionInformations
|
||||
.AsQueryable()
|
||||
.Where(x => x.Instance.Id == instance.Id && x.CommitSha == head)
|
||||
.SelectMany(x => x.ActiveTestMerges)
|
||||
.Select(x => x.TestMerge)
|
||||
.Select(x => new Models.TestMerge
|
||||
{
|
||||
Number = x.Number,
|
||||
PullRequestRevision = x.PullRequestRevision
|
||||
})
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
@@ -62,12 +62,24 @@ namespace Tgstation.Server.Host.Components.Chat
|
||||
Task SendWatchdogMessage(string message, bool adminOnly, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Send a chat <paramref name="message"/> to configured update channels
|
||||
/// Send the message for a deployment to configured deployment channels.
|
||||
/// </summary>
|
||||
/// <param name="message">The message being sent</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
Task SendUpdateMessage(string message, CancellationToken cancellationToken);
|
||||
/// <param name="revisionInformation">The <see cref="RevisionInformation"/> of the deployment.</param>
|
||||
/// <param name="byondVersion">The BYOND <see cref="Version"/> of the deployment.</param>
|
||||
/// <param name="estimatedCompletionTime">The optional <see cref="DateTimeOffset"/> the deployment is expected to be completed at.</param>
|
||||
/// <param name="gitHubOwner">The repository GitHub owner, if any.</param>
|
||||
/// <param name="gitHubRepo">The repository GitHub name, if any.</param>
|
||||
/// <param name="localCommitPushed"><see langword="true"/> if the local deployment commit was pushed to the remote repository.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Func{T1, T2, TResult}"/> to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any.</returns>
|
||||
Task<Func<string, string, Task>> SendDeploymentMessage(
|
||||
Models.RevisionInformation revisionInformation,
|
||||
Version byondVersion,
|
||||
DateTimeOffset? estimatedCompletionTime,
|
||||
string gitHubOwner,
|
||||
string gitHubRepo,
|
||||
bool localCommitPushed,
|
||||
CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Start tracking <see cref="Commands.CustomCommand"/>s and <see cref="ChannelRepresentation"/>s.
|
||||
|
||||
@@ -6,6 +6,8 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
@@ -28,6 +30,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IAssemblyInformationProvider"/> for the <see cref="DiscordProvider"/>.
|
||||
/// </summary>
|
||||
readonly IAssemblyInformationProvider assemblyInformationProvider;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DiscordSocketClient"/> for the <see cref="DiscordProvider"/>
|
||||
/// </summary>
|
||||
@@ -53,12 +60,18 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DiscordProvider"/>
|
||||
/// </summary>
|
||||
/// <param name="assemblyInformationProvider">The value of <see cref="assemblyInformationProvider"/>.</param>
|
||||
/// <param name="logger">The value of <see cref="Logger"/></param>
|
||||
/// <param name="botToken">The value of <see cref="botToken"/></param>
|
||||
/// <param name="reconnectInterval">The initial reconnect interval in minutes.</param>
|
||||
public DiscordProvider(ILogger<DiscordProvider> logger, string botToken, uint reconnectInterval)
|
||||
public DiscordProvider(
|
||||
IAssemblyInformationProvider assemblyInformationProvider,
|
||||
ILogger<DiscordProvider> logger,
|
||||
string botToken,
|
||||
uint reconnectInterval)
|
||||
: base(logger, reconnectInterval)
|
||||
{
|
||||
this.assemblyInformationProvider = assemblyInformationProvider ?? throw new ArgumentNullException(nameof(assemblyInformationProvider));
|
||||
this.botToken = botToken ?? throw new ArgumentNullException(nameof(botToken));
|
||||
client = new DiscordSocketClient();
|
||||
client.MessageReceived += Client_MessageReceived;
|
||||
@@ -260,5 +273,138 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Logger.LogWarning("Error sending discord message: {0}", e);
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Func<string, string, Task>> SendUpdateMessage(
|
||||
RevisionInformation revisionInformation,
|
||||
Version byondVersion,
|
||||
DateTimeOffset? estimatedCompletionTime,
|
||||
string gitHubOwner,
|
||||
string gitHubRepo,
|
||||
ulong channelId,
|
||||
bool localCommitPushed,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
bool gitHub = gitHubOwner != null && gitHubRepo != null;
|
||||
|
||||
localCommitPushed |= revisionInformation.CommitSha == revisionInformation.OriginCommitSha;
|
||||
|
||||
var fields = new List<EmbedFieldBuilder>
|
||||
{
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "BYOND Version",
|
||||
Value = $"{byondVersion.Major}.{byondVersion.Minor}",
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Local Commit",
|
||||
Value = localCommitPushed && gitHub
|
||||
? $"[{revisionInformation.CommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.CommitSha})"
|
||||
: revisionInformation.CommitSha.Substring(0, 7),
|
||||
IsInline = true
|
||||
},
|
||||
new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Branch Commit",
|
||||
Value = gitHub
|
||||
? $"[{revisionInformation.OriginCommitSha.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{revisionInformation.OriginCommitSha})"
|
||||
: revisionInformation.OriginCommitSha.Substring(0, 7),
|
||||
IsInline = true
|
||||
}
|
||||
};
|
||||
|
||||
fields.AddRange((revisionInformation.ActiveTestMerges ?? Enumerable.Empty<RevInfoTestMerge>())
|
||||
.Select(x => x.TestMerge)
|
||||
.Select(x => new EmbedFieldBuilder
|
||||
{
|
||||
Name = $"#{x.Number}",
|
||||
Value = $"[{x.TitleAtMerge}]({x.Url}) by _[@{x.Author}](https://github.com/{x.Author})_{Environment.NewLine}Commit: [{x.PullRequestRevision.Substring(0, 7)}](https://github.com/{gitHubOwner}/{gitHubRepo}/commit/{x.PullRequestRevision}){(String.IsNullOrWhiteSpace(x.Comment) ? String.Empty : $"{Environment.NewLine}_**{x.Comment}**_")}"
|
||||
}));
|
||||
|
||||
var builder = new EmbedBuilder
|
||||
{
|
||||
Author = new EmbedAuthorBuilder
|
||||
{
|
||||
Name = assemblyInformationProvider.VersionPrefix,
|
||||
Url = "https://github.com/tgstation/tgstation-server",
|
||||
IconUrl = "https://avatars0.githubusercontent.com/u/1363778?s=280&v=4"
|
||||
},
|
||||
Color = Color.Gold,
|
||||
Description = "TGS has begun deploying active repository code to production.",
|
||||
Fields = fields,
|
||||
Title = "Code Deployment",
|
||||
Footer = new EmbedFooterBuilder
|
||||
{
|
||||
Text = "In progress... ETA"
|
||||
},
|
||||
Timestamp = estimatedCompletionTime
|
||||
};
|
||||
|
||||
Logger.LogTrace("Attempting to post deploy embed to channel {0}...", channelId);
|
||||
if (!(client.GetChannel(channelId) is IMessageChannel channel))
|
||||
{
|
||||
Logger.LogTrace("Channel ID {0} does not exist or is not an IMessageChannel!", channelId);
|
||||
return (errorMessage, dreamMakerOutput) => Task.CompletedTask;
|
||||
}
|
||||
|
||||
var message = await channel.SendMessageAsync(
|
||||
String.Empty,
|
||||
false,
|
||||
builder.Build(),
|
||||
new RequestOptions
|
||||
{
|
||||
CancelToken = cancellationToken
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return async (errorMessage, dreamMakerOutput) =>
|
||||
{
|
||||
builder.Footer.Text = errorMessage == null ? "Succeeded" : "Failed";
|
||||
builder.Color = errorMessage == null ? Color.Green : Color.Red;
|
||||
builder.Timestamp = DateTimeOffset.Now;
|
||||
builder.Description = errorMessage == null
|
||||
? "The deployment completed successfully and will be available at the next server reboot."
|
||||
: "The deployment failed.";
|
||||
|
||||
if (dreamMakerOutput != null)
|
||||
builder.AddField(new EmbedFieldBuilder
|
||||
{
|
||||
Name = "DreamMaker Output",
|
||||
Value = $"```{Environment.NewLine}{dreamMakerOutput}{Environment.NewLine}```"
|
||||
});
|
||||
|
||||
if (errorMessage != null)
|
||||
builder.AddField(new EmbedFieldBuilder
|
||||
{
|
||||
Name = "Error Message",
|
||||
Value = errorMessage
|
||||
});
|
||||
|
||||
try
|
||||
{
|
||||
await message.ModifyAsync(
|
||||
props => props.Embed = builder.Build())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.LogWarning("Updating deploy embed {0} failed, attempting new post! Exception: {1}", message.Id, ex);
|
||||
try
|
||||
{
|
||||
await channel.SendMessageAsync(
|
||||
String.Empty,
|
||||
false,
|
||||
builder.Build())
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex2)
|
||||
{
|
||||
Logger.LogWarning("Posting completion deploy embed failed! Exception: {0}", ex2);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
@@ -65,5 +66,27 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
/// <param name="reconnectInterval">The reconnection interval in minutes.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task SetReconnectInterval(uint reconnectInterval);
|
||||
|
||||
/// <summary>
|
||||
/// Send the message for a deployment.
|
||||
/// </summary>
|
||||
/// <param name="revisionInformation">The <see cref="RevisionInformation"/> of the deployment.</param>
|
||||
/// <param name="byondVersion">The BYOND <see cref="Version"/> of the deployment.</param>
|
||||
/// <param name="estimatedCompletionTime">The optional <see cref="DateTimeOffset"/> the deployment is expected to be completed at.</param>
|
||||
/// <param name="gitHubOwner">The repository GitHub owner, if any.</param>
|
||||
/// <param name="gitHubRepo">The repository GitHub name, if any.</param>
|
||||
/// <param name="channelId">The <see cref="ChannelRepresentation.RealId"/> to send to</param>
|
||||
/// <param name="localCommitPushed"><see langword="true"/> if the local deployment commit was pushed to the remote repository.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="Func{T1, T2, TResult}"/> to call to update the message at the deployment's conclusion. Parameters: Error message if any, DreamMaker output if any.</returns>
|
||||
Task<Func<string, string, Task>> SendUpdateMessage(
|
||||
RevisionInformation revisionInformation,
|
||||
Version byondVersion,
|
||||
DateTimeOffset? estimatedCompletionTime,
|
||||
string gitHubOwner,
|
||||
string gitHubRepo,
|
||||
ulong channelId,
|
||||
bool localCommitPushed,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -467,5 +467,55 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
Logger.LogWarning("Unable to send to channel: {0}", e);
|
||||
}
|
||||
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
|
||||
|
||||
/// <inheritdoc />
|
||||
public override async Task<Func<string, string, Task>> SendUpdateMessage(
|
||||
Models.RevisionInformation revisionInformation,
|
||||
Version byondVersion,
|
||||
DateTimeOffset? estimatedCompletionTime,
|
||||
string gitHubOwner,
|
||||
string gitHubRepo,
|
||||
ulong channelId,
|
||||
bool localCommitPushed,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
var commitInsert = revisionInformation.CommitSha.Substring(0, 7);
|
||||
string remoteCommitInsert;
|
||||
if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha)
|
||||
{
|
||||
commitInsert = String.Format(CultureInfo.InvariantCulture, localCommitPushed ? "^{0}" : "{0}", commitInsert);
|
||||
remoteCommitInsert = String.Empty;
|
||||
}
|
||||
else
|
||||
remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7));
|
||||
|
||||
var testmergeInsert = (revisionInformation.ActiveTestMerges?.Count ?? 0) == 0 ? String.Empty : String.Format(CultureInfo.InvariantCulture, " (Test Merges: {0})",
|
||||
String.Join(", ", revisionInformation.ActiveTestMerges.Select(x => x.TestMerge).Select(x =>
|
||||
{
|
||||
var result = String.Format(CultureInfo.InvariantCulture, "#{0} at {1}", x.Number, x.PullRequestRevision.Substring(0, 7));
|
||||
if (x.Comment != null)
|
||||
result += String.Format(CultureInfo.InvariantCulture, " ({0})", x.Comment);
|
||||
return result;
|
||||
})));
|
||||
|
||||
await SendMessage(
|
||||
channelId,
|
||||
String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"DM: Deploying revision: {0}{1}{2} BYOND Version: {3}{4}",
|
||||
commitInsert,
|
||||
testmergeInsert,
|
||||
remoteCommitInsert,
|
||||
byondVersion,
|
||||
estimatedCompletionTime.HasValue
|
||||
? $" ETA: {estimatedCompletionTime - DateTimeOffset.Now}"
|
||||
: String.Empty),
|
||||
cancellationToken).ConfigureAwait(false);
|
||||
|
||||
return (errorMessage, dreamMakerOutput) => SendMessage(
|
||||
channelId,
|
||||
$"DM: Deployment {(errorMessage == null ? "complete" : "failed")}!",
|
||||
cancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Models;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
{
|
||||
@@ -180,5 +181,16 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task SendMessage(ulong channelId, string message, CancellationToken cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
public abstract Task<Func<string, string, Task>> SendUpdateMessage(
|
||||
RevisionInformation revisionInformation,
|
||||
Version byondVersion,
|
||||
DateTimeOffset? estimatedCompletionTime,
|
||||
string gitHubOwner,
|
||||
string gitHubRepo,
|
||||
ulong channelId,
|
||||
bool localCommitPushed,
|
||||
CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,7 +56,11 @@ namespace Tgstation.Server.Host.Components.Chat.Providers
|
||||
return new IrcProvider(assemblyInformationProvider, asyncDelayer, loggerFactory.CreateLogger<IrcProvider>(), ircBuilder.Address, ircBuilder.Port.Value, ircBuilder.Nickname, ircBuilder.Password, ircBuilder.PasswordType, settings.ReconnectionInterval.Value, ircBuilder.UseSsl.Value);
|
||||
case ChatProvider.Discord:
|
||||
var discordBuilder = (DiscordConnectionStringBuilder)builder;
|
||||
return new DiscordProvider(loggerFactory.CreateLogger<DiscordProvider>(), discordBuilder.BotToken, settings.ReconnectionInterval.Value);
|
||||
return new DiscordProvider(
|
||||
assemblyInformationProvider,
|
||||
loggerFactory.CreateLogger<DiscordProvider>(),
|
||||
discordBuilder.BotToken,
|
||||
settings.ReconnectionInterval.Value);
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid ChatProvider: {0}", settings.Provider));
|
||||
}
|
||||
|
||||
@@ -197,11 +197,17 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
|
||||
// ensure we have the entire compile job tree
|
||||
logger.LogTrace("Loading compile job {0}...", compileJob.Id);
|
||||
await databaseContextFactory.UseContext(async db => compileJob = await db.CompileJobs.Where(x => x.Id == compileJob.Id)
|
||||
.Include(x => x.Job).ThenInclude(x => x.StartedBy)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
|
||||
.FirstAsync(cancellationToken).ConfigureAwait(false)).ConfigureAwait(false); // can't wait to see that query
|
||||
await databaseContextFactory.UseContext(
|
||||
async db => compileJob = await db
|
||||
.CompileJobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == compileJob.Id)
|
||||
.Include(x => x.Job).ThenInclude(x => x.StartedBy)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge).ThenInclude(x => x.MergedBy)
|
||||
.FirstAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
.ConfigureAwait(false); // can't wait to see that query
|
||||
|
||||
if (!compileJob.Job.StoppedAt.HasValue)
|
||||
{
|
||||
@@ -269,8 +275,12 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
// find the uids of locked directories
|
||||
await databaseContextFactory.UseContext(async db =>
|
||||
{
|
||||
jobUidsToNotErase = (await db.CompileJobs.Where(
|
||||
x => x.Job.Instance.Id == instance.Id && jobIdsToSkip.Contains(x.Id))
|
||||
jobUidsToNotErase = (await db
|
||||
.CompileJobs
|
||||
.AsQueryable()
|
||||
.Where(
|
||||
x => x.Job.Instance.Id == instance.Id
|
||||
&& jobIdsToSkip.Contains(x.Id))
|
||||
.Select(x => x.DirectoryName.Value)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
|
||||
@@ -14,7 +14,6 @@ using Tgstation.Server.Host.Components.Byond;
|
||||
using Tgstation.Server.Host.Components.Chat;
|
||||
using Tgstation.Server.Host.Components.Repository;
|
||||
using Tgstation.Server.Host.Components.Session;
|
||||
using Tgstation.Server.Host.Components.Watchdog;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Database;
|
||||
using Tgstation.Server.Host.IO;
|
||||
@@ -82,11 +81,6 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// </summary>
|
||||
readonly IProcessExecutor processExecutor;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IWatchdog"/> for <see cref="DreamMaker"/>
|
||||
/// </summary>
|
||||
readonly IWatchdog watchdog;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IRepositoryManager"/> for <see cref="DreamMaker"/>.
|
||||
/// </summary>
|
||||
@@ -113,14 +107,18 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
readonly Api.Models.Instance metadata;
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="lock"/> <see cref="object"/> for <see cref="compiling"/>.
|
||||
/// <see langword="lock"/> <see cref="object"/> for <see cref="deploying"/>.
|
||||
/// </summary>
|
||||
readonly object compilingLock;
|
||||
readonly object deploymentLock;
|
||||
|
||||
Func<string, string, Task> currentChatCallback;
|
||||
|
||||
string currentDreamMakerOutput;
|
||||
|
||||
/// <summary>
|
||||
/// If a compile job is running
|
||||
/// </summary>
|
||||
bool compiling;
|
||||
bool deploying;
|
||||
|
||||
/// <summary>
|
||||
/// Construct <see cref="DreamMaker"/>
|
||||
@@ -132,7 +130,6 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// <param name="eventConsumer">The value of <see cref="eventConsumer"/></param>
|
||||
/// <param name="chatManager">The value of <see cref="chatManager"/></param>
|
||||
/// <param name="processExecutor">The value of <see cref="processExecutor"/></param>
|
||||
/// <param name="watchdog">The value of <see cref="watchdog"/></param>
|
||||
/// <param name="gitHubClientFactory">The value of <see cref="gitHubClientFactory"/>.</param>
|
||||
/// <param name="compileJobConsumer">The value of <see cref="compileJobConsumer"/>.</param>
|
||||
/// <param name="repositoryManager">The value of <see cref="repositoryManager"/>.</param>
|
||||
@@ -146,7 +143,6 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
IEventConsumer eventConsumer,
|
||||
IChatManager chatManager,
|
||||
IProcessExecutor processExecutor,
|
||||
IWatchdog watchdog,
|
||||
IGitHubClientFactory gitHubClientFactory,
|
||||
ICompileJobSink compileJobConsumer,
|
||||
IRepositoryManager repositoryManager,
|
||||
@@ -160,14 +156,13 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer));
|
||||
this.chatManager = chatManager ?? throw new ArgumentNullException(nameof(chatManager));
|
||||
this.processExecutor = processExecutor ?? throw new ArgumentNullException(nameof(processExecutor));
|
||||
this.watchdog = watchdog ?? throw new ArgumentNullException(nameof(watchdog));
|
||||
this.gitHubClientFactory = gitHubClientFactory ?? throw new ArgumentNullException(nameof(gitHubClientFactory));
|
||||
this.compileJobConsumer = compileJobConsumer ?? throw new ArgumentNullException(nameof(compileJobConsumer));
|
||||
this.repositoryManager = repositoryManager ?? throw new ArgumentNullException(nameof(repositoryManager));
|
||||
this.logger = logger ?? throw new ArgumentNullException(nameof(logger));
|
||||
this.metadata = metadata ?? throw new ArgumentNullException(nameof(metadata));
|
||||
|
||||
compilingLock = new object();
|
||||
deploymentLock = new object();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -289,7 +284,7 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
cancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
logger.LogDebug("DreamMaker exit code: {0}", exitCode);
|
||||
job.Output = dm.GetCombinedOutput();
|
||||
currentDreamMakerOutput = job.Output = dm.GetCombinedOutput();
|
||||
logger.LogDebug("DreamMaker output: {0}{1}", Environment.NewLine, job.Output);
|
||||
return exitCode;
|
||||
}
|
||||
@@ -352,13 +347,10 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// Cleans up a failed compile <paramref name="job"/>
|
||||
/// </summary>
|
||||
/// <param name="job">The running <see cref="CompileJob"/></param>
|
||||
/// <param name="cancelled">If the <paramref name="job"/> was cancelled</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task CleanupFailedCompile(Models.CompileJob job, bool cancelled, CancellationToken cancellationToken)
|
||||
async Task CleanupFailedCompile(Models.CompileJob job)
|
||||
{
|
||||
logger.LogTrace("Cleaning compile directory...");
|
||||
var chatTask = chatManager.SendUpdateMessage(cancelled ? "Deploy cancelled!" : "Deploy failed!", cancellationToken);
|
||||
var jobPath = job.DirectoryName.ToString();
|
||||
try
|
||||
{
|
||||
@@ -368,66 +360,6 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
{
|
||||
logger.LogWarning("Error cleaning up compile directory {0}! Exception: {1}", ioManager.ResolvePath(jobPath), e);
|
||||
}
|
||||
|
||||
await chatTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Send a message to <see cref="chatManager"/> about a deployment
|
||||
/// </summary>
|
||||
/// <param name="revisionInformation">The <see cref="RevisionInformation"/> for the deployment</param>
|
||||
/// <param name="byondLock">The <see cref="IByondExecutableLock"/> for the deployment</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation</returns>
|
||||
async Task SendDeploymentMessage(Models.RevisionInformation revisionInformation, IByondExecutableLock byondLock, CancellationToken cancellationToken)
|
||||
{
|
||||
var commitInsert = revisionInformation.CommitSha.Substring(0, 7);
|
||||
string remoteCommitInsert;
|
||||
if (revisionInformation.CommitSha == revisionInformation.OriginCommitSha)
|
||||
{
|
||||
commitInsert = String.Format(CultureInfo.InvariantCulture, "^{0}", commitInsert);
|
||||
remoteCommitInsert = String.Empty;
|
||||
}
|
||||
else
|
||||
remoteCommitInsert = String.Format(CultureInfo.InvariantCulture, ". Remote commit: ^{0}", revisionInformation.OriginCommitSha.Substring(0, 7));
|
||||
|
||||
var testmergeInsert = (revisionInformation.ActiveTestMerges?.Count ?? 0) == 0
|
||||
? String.Empty
|
||||
: String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"{0}Test Merges:{1}",
|
||||
Environment.NewLine,
|
||||
String.Join(
|
||||
Environment.NewLine,
|
||||
revisionInformation
|
||||
.ActiveTestMerges
|
||||
.Select(x => x.TestMerge)
|
||||
.Select(x =>
|
||||
{
|
||||
var result = String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"- #{0} at {1}",
|
||||
x.Number,
|
||||
x.PullRequestRevision.Substring(0, 7));
|
||||
|
||||
if (x.Comment != null)
|
||||
result += $": {x.Comment}";
|
||||
|
||||
return result;
|
||||
})));
|
||||
|
||||
await chatManager.SendUpdateMessage(
|
||||
String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"*Deployment Triggered*{0}Revision: {1}{2}{3}{0}BYOND Version: {4}.{5}",
|
||||
Environment.NewLine,
|
||||
commitInsert,
|
||||
testmergeInsert,
|
||||
remoteCommitInsert,
|
||||
byondLock.Version.Major,
|
||||
byondLock.Version.Minor),
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -523,9 +455,9 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
|
||||
logger.LogDebug("Compile complete!");
|
||||
}
|
||||
catch (Exception e)
|
||||
catch
|
||||
{
|
||||
await CleanupFailedCompile(job, e is OperationCanceledException, cancellationToken).ConfigureAwait(false);
|
||||
await CleanupFailedCompile(job).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
@@ -547,174 +479,209 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
if (progressReporter == null)
|
||||
throw new ArgumentNullException(nameof(progressReporter));
|
||||
|
||||
string repoOwner = null;
|
||||
string repoName = null;
|
||||
TimeSpan? averageSpan = null;
|
||||
Models.RepositorySettings repositorySettings = null;
|
||||
Models.DreamDaemonSettings ddSettings = null;
|
||||
DreamMakerSettings dreamMakerSettings = null;
|
||||
IRepository repo = null;
|
||||
lock (deploymentLock)
|
||||
{
|
||||
if (deploying)
|
||||
throw new JobException(ErrorCode.DreamMakerCompileJobInProgress);
|
||||
deploying = true;
|
||||
}
|
||||
|
||||
currentChatCallback = null;
|
||||
currentDreamMakerOutput = null;
|
||||
Models.CompileJob compileJob = null;
|
||||
Models.RevisionInformation revInfo = null;
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
averageSpan = await CalculateExpectedDeploymentTime(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
ddSettings = await databaseContext
|
||||
.DreamDaemonSettings
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.Select(x => new Models.DreamDaemonSettings
|
||||
{
|
||||
StartupTimeout = x.StartupTimeout,
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (ddSettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingDreamDaemonSettings);
|
||||
|
||||
dreamMakerSettings = await databaseContext.DreamMakerSettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (dreamMakerSettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingDreamMakerSettings);
|
||||
|
||||
repositorySettings = await databaseContext
|
||||
.RepositorySettings
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.Select(x => new Models.RepositorySettings
|
||||
{
|
||||
AccessToken = x.AccessToken,
|
||||
ShowTestMergeCommitters = x.ShowTestMergeCommitters,
|
||||
PushTestMergeCommits = x.PushTestMergeCommits,
|
||||
PostTestMergeComment = x.PostTestMergeComment
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (repositorySettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingRepositorySettings);
|
||||
|
||||
repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
if (repo == null)
|
||||
throw new JobException(ErrorCode.RepoMissing);
|
||||
|
||||
if (repo.IsGitHubRepository)
|
||||
{
|
||||
repoOwner = repo.GitHubOwner;
|
||||
repoName = repo.GitHubRepoName;
|
||||
}
|
||||
|
||||
var repoSha = repo.Head;
|
||||
revInfo = await databaseContext
|
||||
.RevisionInformations
|
||||
.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id)
|
||||
.Include(x => x.ActiveTestMerges)
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ThenInclude(x => x.MergedBy)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (revInfo == default)
|
||||
{
|
||||
revInfo = new Models.RevisionInformation
|
||||
{
|
||||
CommitSha = repoSha,
|
||||
OriginCommitSha = repoSha,
|
||||
Instance = new Models.Instance
|
||||
{
|
||||
Id = metadata.Id
|
||||
},
|
||||
ActiveTestMerges = new List<RevInfoTestMerge>()
|
||||
};
|
||||
|
||||
logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
|
||||
databaseContext.Instances.Attach(revInfo.Instance);
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
repo.Dispose();
|
||||
throw;
|
||||
}
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
using (repo)
|
||||
compileJob = await Compile(
|
||||
revInfo,
|
||||
dreamMakerSettings,
|
||||
ddSettings.StartupTimeout.Value,
|
||||
repo,
|
||||
progressReporter,
|
||||
averageSpan,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var activeCompileJob = compileJobConsumer.LatestCompileJob();
|
||||
try
|
||||
{
|
||||
string repoOwner = null;
|
||||
string repoName = null;
|
||||
TimeSpan? averageSpan = null;
|
||||
Models.RepositorySettings repositorySettings = null;
|
||||
Models.DreamDaemonSettings ddSettings = null;
|
||||
DreamMakerSettings dreamMakerSettings = null;
|
||||
IRepository repo = null;
|
||||
Models.RevisionInformation revInfo = null;
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
compileJob.Job = new Models.Job
|
||||
{
|
||||
Id = job.Id
|
||||
};
|
||||
compileJob.RevisionInformation = new Models.RevisionInformation
|
||||
{
|
||||
Id = revInfo.Id
|
||||
};
|
||||
averageSpan = await CalculateExpectedDeploymentTime(databaseContext, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
databaseContext.Jobs.Attach(compileJob.Job);
|
||||
databaseContext.RevisionInformations.Attach(compileJob.RevisionInformation);
|
||||
databaseContext.CompileJobs.Add(compileJob);
|
||||
ddSettings = await databaseContext
|
||||
.DreamDaemonSettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.Select(x => new Models.DreamDaemonSettings
|
||||
{
|
||||
StartupTimeout = x.StartupTimeout,
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (ddSettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingDreamDaemonSettings);
|
||||
|
||||
// The difficulty with compile jobs is they have a two part commit
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
dreamMakerSettings = await databaseContext
|
||||
.DreamMakerSettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.FirstAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (dreamMakerSettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingDreamMakerSettings);
|
||||
|
||||
repositorySettings = await databaseContext
|
||||
.RepositorySettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.Select(x => new Models.RepositorySettings
|
||||
{
|
||||
AccessToken = x.AccessToken,
|
||||
ShowTestMergeCommitters = x.ShowTestMergeCommitters,
|
||||
PushTestMergeCommits = x.PushTestMergeCommits,
|
||||
PostTestMergeComment = x.PostTestMergeComment
|
||||
})
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (repositorySettings == default)
|
||||
throw new JobException(ErrorCode.InstanceMissingRepositorySettings);
|
||||
|
||||
repo = await repositoryManager.LoadRepository(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false);
|
||||
if (repo == null)
|
||||
throw new JobException(ErrorCode.RepoMissing);
|
||||
|
||||
if (repo.IsGitHubRepository)
|
||||
{
|
||||
repoOwner = repo.GitHubOwner;
|
||||
repoName = repo.GitHubRepoName;
|
||||
}
|
||||
|
||||
var repoSha = repo.Head;
|
||||
revInfo = await databaseContext
|
||||
.RevisionInformations
|
||||
.AsQueryable()
|
||||
.Where(x => x.CommitSha == repoSha && x.Instance.Id == metadata.Id)
|
||||
.Include(x => x.ActiveTestMerges)
|
||||
.ThenInclude(x => x.TestMerge)
|
||||
.ThenInclude(x => x.MergedBy)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (revInfo == default)
|
||||
{
|
||||
revInfo = new Models.RevisionInformation
|
||||
{
|
||||
CommitSha = repoSha,
|
||||
OriginCommitSha = repoSha,
|
||||
Instance = new Models.Instance
|
||||
{
|
||||
Id = metadata.Id
|
||||
},
|
||||
ActiveTestMerges = new List<RevInfoTestMerge>()
|
||||
};
|
||||
|
||||
logger.LogWarning(Repository.Repository.OriginTrackingErrorTemplate, repoSha);
|
||||
databaseContext.Instances.Attach(revInfo.Instance);
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// So we need to un-commit the compile job if the above throws
|
||||
databaseContext.CompileJobs.Remove(compileJob);
|
||||
await databaseContext.Save(default).ConfigureAwait(false);
|
||||
repo.Dispose();
|
||||
throw;
|
||||
}
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var likelyPushedTestMergeCommit =
|
||||
repositorySettings.PushTestMergeCommits.Value
|
||||
&& repositorySettings.AccessToken != null
|
||||
&& repositorySettings.AccessUser != null;
|
||||
using (repo)
|
||||
compileJob = await Compile(
|
||||
revInfo,
|
||||
dreamMakerSettings,
|
||||
ddSettings.StartupTimeout.Value,
|
||||
repo,
|
||||
progressReporter,
|
||||
averageSpan,
|
||||
likelyPushedTestMergeCommit,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var activeCompileJob = compileJobConsumer.LatestCompileJob();
|
||||
try
|
||||
{
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
compileJob.Job = new Models.Job
|
||||
{
|
||||
Id = job.Id
|
||||
};
|
||||
compileJob.RevisionInformation = new Models.RevisionInformation
|
||||
{
|
||||
Id = revInfo.Id
|
||||
};
|
||||
|
||||
databaseContext.Jobs.Attach(compileJob.Job);
|
||||
databaseContext.RevisionInformations.Attach(compileJob.RevisionInformation);
|
||||
databaseContext.CompileJobs.Add(compileJob);
|
||||
|
||||
// The difficulty with compile jobs is they have a two part commit
|
||||
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
|
||||
try
|
||||
{
|
||||
await compileJobConsumer.LoadCompileJob(compileJob, cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// So we need to un-commit the compile job if the above throws
|
||||
databaseContext.CompileJobs.Remove(compileJob);
|
||||
await databaseContext.Save(default).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
})
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
await CleanupFailedCompile(compileJob).ConfigureAwait(false);
|
||||
throw;
|
||||
}
|
||||
|
||||
var commentsTask = PostDeploymentComments(
|
||||
revInfo,
|
||||
activeCompileJob?.RevisionInformation,
|
||||
repositorySettings,
|
||||
repoOwner,
|
||||
repoName);
|
||||
|
||||
var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, null, cancellationToken);
|
||||
|
||||
var chatTask = currentChatCallback(null, compileJob.Output);
|
||||
currentChatCallback = null;
|
||||
|
||||
try
|
||||
{
|
||||
await Task.WhenAll(commentsTask, eventTask, chatTask).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JobException(ErrorCode.PostDeployFailure, ex);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
await CleanupFailedCompile(compileJob, ex is OperationCanceledException, default).ConfigureAwait(false);
|
||||
if (currentChatCallback != null)
|
||||
await currentChatCallback(
|
||||
ex.Message,
|
||||
currentDreamMakerOutput)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
var commentsTask = PostDeploymentComments(
|
||||
revInfo,
|
||||
activeCompileJob?.RevisionInformation,
|
||||
repositorySettings,
|
||||
repoOwner,
|
||||
repoName);
|
||||
|
||||
var eventTask = eventConsumer.HandleEvent(EventType.DeploymentComplete, null, cancellationToken);
|
||||
|
||||
var chatTask = chatManager.SendUpdateMessage(
|
||||
String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Deployment complete! Changes will be applied when DreamDaemon {0}.",
|
||||
watchdog.Running ? "reboots" : "is launched"),
|
||||
cancellationToken);
|
||||
|
||||
try
|
||||
finally
|
||||
{
|
||||
await Task.WhenAll(commentsTask, eventTask, chatTask).ConfigureAwait(false);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
throw new JobException(ErrorCode.PostDeployFailure, ex);
|
||||
deploying = false;
|
||||
}
|
||||
}
|
||||
#pragma warning restore CA1506
|
||||
@@ -727,7 +694,9 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the average <see cref="TimeSpan"/> of the 10 previous deployments or <see langword="null"/> if there are none.</returns>
|
||||
async Task<TimeSpan?> CalculateExpectedDeploymentTime(IDatabaseContext databaseContext, CancellationToken cancellationToken)
|
||||
{
|
||||
var previousCompileJobs = await databaseContext.CompileJobs
|
||||
var previousCompileJobs = await databaseContext
|
||||
.CompileJobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Job.Instance.Id == metadata.Id)
|
||||
.OrderByDescending(x => x.Job.StoppedAt)
|
||||
.Take(10)
|
||||
@@ -751,23 +720,32 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
return averageSpan;
|
||||
}
|
||||
|
||||
async Task<Models.CompileJob> Compile(Models.RevisionInformation revisionInformation, Api.Models.DreamMaker dreamMakerSettings, uint apiValidateTimeout, IRepository repository, Action<int> progressReporter, TimeSpan? estimatedDuration, CancellationToken cancellationToken)
|
||||
async Task<Models.CompileJob> Compile(
|
||||
Models.RevisionInformation revisionInformation,
|
||||
Api.Models.DreamMaker dreamMakerSettings,
|
||||
uint apiValidateTimeout,
|
||||
IRepository repository,
|
||||
Action<int> progressReporter,
|
||||
TimeSpan? estimatedDuration,
|
||||
bool localCommitExistsOnRemote,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
logger.LogTrace("Begin Compile");
|
||||
|
||||
lock (compilingLock)
|
||||
{
|
||||
if (compiling)
|
||||
throw new JobException(ErrorCode.DreamMakerCompileJobInProgress);
|
||||
compiling = true;
|
||||
}
|
||||
|
||||
using var progressCts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
|
||||
var progressTask = estimatedDuration.HasValue ? ProgressTask(progressReporter, estimatedDuration.Value, progressCts.Token) : Task.CompletedTask;
|
||||
try
|
||||
{
|
||||
using var byondLock = await byond.UseExecutables(null, cancellationToken).ConfigureAwait(false);
|
||||
await SendDeploymentMessage(revisionInformation, byondLock, cancellationToken).ConfigureAwait(false);
|
||||
currentChatCallback = await chatManager.SendDeploymentMessage(
|
||||
revisionInformation,
|
||||
byondLock.Version,
|
||||
DateTimeOffset.Now + estimatedDuration,
|
||||
repository.GitHubOwner,
|
||||
repository.GitHubRepoName,
|
||||
localCommitExistsOnRemote,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
var job = new Models.CompileJob
|
||||
{
|
||||
@@ -788,7 +766,6 @@ namespace Tgstation.Server.Host.Components.Deployment
|
||||
}
|
||||
finally
|
||||
{
|
||||
compiling = false;
|
||||
progressCts.Cancel();
|
||||
await progressTask.ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -162,6 +162,7 @@ namespace Tgstation.Server.Host.Components
|
||||
await databaseContextFactory.UseContext(
|
||||
async (db) => user = await db
|
||||
.Users
|
||||
.AsQueryable()
|
||||
.Where(x => x.CanonicalName == User.CanonicalizeName(Api.Models.User.AdminName))
|
||||
.FirstAsync(cancellationToken)
|
||||
.ConfigureAwait(false))
|
||||
@@ -189,7 +190,11 @@ namespace Tgstation.Server.Host.Components
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
{
|
||||
var repositorySettingsTask = databaseContext.RepositorySettings.Where(x => x.InstanceId == metadata.Id).FirstAsync(jobCancellationToken);
|
||||
var repositorySettingsTask = databaseContext
|
||||
.RepositorySettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.FirstAsync(jobCancellationToken);
|
||||
|
||||
const int NumSteps = 3;
|
||||
var doneSteps = 0;
|
||||
@@ -225,6 +230,7 @@ namespace Tgstation.Server.Host.Components
|
||||
bool hasDbChanges = false;
|
||||
|
||||
Task<RevisionInformation> LoadRevInfo() => databaseContext.RevisionInformations
|
||||
.AsQueryable()
|
||||
.Where(x => x.CommitSha == startSha && x.Instance.Id == metadata.Id)
|
||||
.Include(x => x.ActiveTestMerges).ThenInclude(x => x.TestMerge)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
@@ -294,6 +300,7 @@ namespace Tgstation.Server.Host.Components
|
||||
var currentHead = repo.Head;
|
||||
|
||||
currentRevInfo = await databaseContext.RevisionInformations
|
||||
.AsQueryable()
|
||||
.Where(x => x.CommitSha == currentHead && x.Instance.Id == metadata.Id)
|
||||
.FirstOrDefaultAsync(jobCancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -262,7 +262,6 @@ namespace Tgstation.Server.Host.Components
|
||||
eventConsumer,
|
||||
chatManager,
|
||||
processExecutor,
|
||||
watchdog,
|
||||
gitHubClientFactory,
|
||||
dmbFactory,
|
||||
repoManager,
|
||||
|
||||
@@ -213,10 +213,14 @@ namespace Tgstation.Server.Host.Components
|
||||
var tasks = new List<Task>();
|
||||
await databaseContextFactory.UseContext(async db =>
|
||||
{
|
||||
var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x => new Models.Job
|
||||
{
|
||||
Id = x.Id
|
||||
}).ToAsyncEnumerable();
|
||||
var jobs = db
|
||||
.Jobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Instance.Id == metadata.Id)
|
||||
.Select(x => new Models.Job
|
||||
{
|
||||
Id = x.Id
|
||||
});
|
||||
await jobs.ForEachAsync(job =>
|
||||
{
|
||||
lock (tasks)
|
||||
@@ -271,7 +275,10 @@ namespace Tgstation.Server.Host.Components
|
||||
var factoryStartup = instanceFactory.StartAsync(cancellationToken);
|
||||
await databaseContext.Initialize(cancellationToken).ConfigureAwait(false);
|
||||
await jobManager.StartAsync(cancellationToken).ConfigureAwait(false);
|
||||
var dbInstances = databaseContext.Instances.Where(x => x.Online.Value)
|
||||
var dbInstances = databaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Online.Value)
|
||||
.Include(x => x.RepositorySettings)
|
||||
.Include(x => x.ChatSettings)
|
||||
.ThenInclude(x => x.Channels)
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace Tgstation.Server.Host.Components.Interop
|
||||
/// <summary>
|
||||
/// The DMAPI <see cref="Version"/> being used.
|
||||
/// </summary>
|
||||
public static readonly Version Version = new Version(5, 2, 1);
|
||||
public static readonly Version Version = new Version(5, 2, 2);
|
||||
|
||||
/// <summary>
|
||||
/// <see cref="JsonSerializerSettings"/> for use when communicating with the DMAPI.
|
||||
|
||||
@@ -56,7 +56,11 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
|
||||
logger.LogDebug("Saving reattach information: {0}...", reattachInformation);
|
||||
|
||||
var deleteTask = db.WatchdogReattachInformations.Where(x => x.InstanceId == metadata.Id).DeleteAsync(cancellationToken);
|
||||
var deleteTask = db
|
||||
.WatchdogReattachInformations
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == metadata.Id)
|
||||
.DeleteAsync(cancellationToken);
|
||||
|
||||
Models.ReattachInformation ConvertReattachInfo(ReattachInformation wdInfo)
|
||||
{
|
||||
@@ -93,7 +97,9 @@ namespace Tgstation.Server.Host.Components.Session
|
||||
Models.DualReattachInformation result = null;
|
||||
await databaseContextFactory.UseContext(async (db) =>
|
||||
{
|
||||
var instance = await db.Instances.Where(x => x.Id == metadata.Id)
|
||||
var instance = await db.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == metadata.Id)
|
||||
.Include(x => x.WatchdogReattachInformation).ThenInclude(x => x.Alpha).ThenInclude(x => x.CompileJob)
|
||||
.Include(x => x.WatchdogReattachInformation).ThenInclude(x => x.Bravo).ThenInclude(x => x.CompileJob)
|
||||
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -192,7 +192,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
};
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DisposeAndNullControllers()
|
||||
protected override void DisposeAndNullControllersImpl()
|
||||
{
|
||||
Server?.Dispose();
|
||||
Server = null;
|
||||
|
||||
@@ -392,10 +392,8 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
#pragma warning restore CA1502
|
||||
|
||||
/// <summary>
|
||||
/// Call <see cref="IDisposable.Dispose"/> on <see cref="alphaServer"/> and <see cref="bravoServer"/> and set them to <see langword="null"/>
|
||||
/// </summary>
|
||||
protected override void DisposeAndNullControllers()
|
||||
/// <inheritdoc />
|
||||
protected override void DisposeAndNullControllersImpl()
|
||||
{
|
||||
alphaServer?.Dispose();
|
||||
alphaServer = null;
|
||||
|
||||
@@ -112,6 +112,11 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// </summary>
|
||||
readonly IRestartRegistration restartRegistration;
|
||||
|
||||
/// <summary>
|
||||
/// <see langword="lock"/> <see cref="object"/> used for <see cref="DisposeAndNullControllers"/>.
|
||||
/// </summary>
|
||||
readonly object controllerDisposeLock;
|
||||
|
||||
/// <summary>
|
||||
/// If the <see cref="WatchdogBase"/> should <see cref="LaunchImplNoLock(bool, bool, DualReattachInformation, CancellationToken)"/> in <see cref="StartAsync(CancellationToken)"/>
|
||||
/// </summary>
|
||||
@@ -201,6 +206,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
ActiveLaunchParameters = initialLaunchParameters;
|
||||
releaseServers = false;
|
||||
ActiveParametersUpdated = new TaskCompletionSource<object>();
|
||||
controllerDisposeLock = new object();
|
||||
|
||||
restartRegistration = serverControl.RegisterForRestart(this);
|
||||
try
|
||||
@@ -463,7 +469,16 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
/// <summary>
|
||||
/// Call <see cref="IDisposable.Dispose"/> and null the fields for all <see cref="ISessionController"/>s and set <see cref="Running"/> to <see langword="false"/>.
|
||||
/// </summary>
|
||||
protected abstract void DisposeAndNullControllers();
|
||||
protected abstract void DisposeAndNullControllersImpl();
|
||||
|
||||
/// <summary>
|
||||
/// Wrapper for <see cref="DisposeAndNullControllersImpl"/> under a locked context.
|
||||
/// </summary>
|
||||
protected void DisposeAndNullControllers()
|
||||
{
|
||||
lock (controllerDisposeLock)
|
||||
DisposeAndNullControllersImpl();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the active <see cref="ISessionController"/>.
|
||||
@@ -857,6 +872,7 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
await databaseContextFactory.UseContext(
|
||||
async db => adminUserId = await db
|
||||
.Users
|
||||
.AsQueryable()
|
||||
.Where(x => x.CanonicalName == Models.User.CanonicalizeName(Api.Models.User.AdminName))
|
||||
.Select(x => x.Id)
|
||||
.FirstAsync(cancellationToken)
|
||||
|
||||
@@ -101,9 +101,9 @@ namespace Tgstation.Server.Host.Components.Watchdog
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void DisposeAndNullControllers()
|
||||
protected override void DisposeAndNullControllersImpl()
|
||||
{
|
||||
base.DisposeAndNullControllers();
|
||||
base.DisposeAndNullControllersImpl();
|
||||
|
||||
// If we reach this point, we can guarantee PrepServerForLaunch will be called before starting again.
|
||||
activeSwappable = null;
|
||||
|
||||
@@ -4,7 +4,7 @@ using Newtonsoft.Json.Converters;
|
||||
namespace Tgstation.Server.Host.Configuration
|
||||
{
|
||||
/// <summary>
|
||||
/// Configuration options for the <see cref="Database.DatabaseContext{TParentContext}"/>
|
||||
/// Configuration options for the <see cref="Database.DatabaseContext"/>
|
||||
/// </summary>
|
||||
sealed class DatabaseConfiguration
|
||||
{
|
||||
|
||||
@@ -24,5 +24,10 @@
|
||||
/// Use Sqlite
|
||||
/// </summary>
|
||||
Sqlite,
|
||||
|
||||
/// <summary>
|
||||
/// Use PostgresSql
|
||||
/// </summary>
|
||||
PostgresSql,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,11 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// </summary>
|
||||
public const string Section = "General";
|
||||
|
||||
/// <summary>
|
||||
/// The default value of <see cref="ApiPort"/>.
|
||||
/// </summary>
|
||||
public const ushort DefaultApiPort = 5000;
|
||||
|
||||
/// <summary>
|
||||
/// The default value for <see cref="ServerInformation.MinimumPasswordLength"/>.
|
||||
/// </summary>
|
||||
@@ -37,7 +42,12 @@ namespace Tgstation.Server.Host.Configuration
|
||||
/// <summary>
|
||||
/// The default value for <see cref="RestartTimeout"/>
|
||||
/// </summary>
|
||||
const int DefaultRestartTimeout = 10000;
|
||||
const int DefaultRestartTimeout = 60000;
|
||||
|
||||
/// <summary>
|
||||
/// The port the TGS API listens on.
|
||||
/// </summary>
|
||||
public ushort ApiPort { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// A GitHub personal access token to use for bypassing rate limits on requests. Requires no scopes
|
||||
|
||||
@@ -163,7 +163,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (ApiHeaders != null)
|
||||
Logger.LogDebug(
|
||||
"Request made by User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}",
|
||||
"Request details: User ID {0}. Api version: {1}. User-Agent: {2}. Type: {3}. Route {4}{5} to Instance {6}",
|
||||
AuthenticationContext?.User.Id.Value.ToString(CultureInfo.InvariantCulture),
|
||||
ApiHeaders.ApiVersion.Semver(),
|
||||
ApiHeaders.RawUserAgent,
|
||||
|
||||
@@ -80,6 +80,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var countOfExistingBotsInInstance = await DatabaseContext
|
||||
.ChatBots
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.CountAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -140,7 +141,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
public async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
await Task.WhenAll(instance.Chat.DeleteConnection(id, cancellationToken), DatabaseContext.ChatBots.Where(x => x.Id == id).DeleteAsync(cancellationToken)).ConfigureAwait(false);
|
||||
await Task.WhenAll(
|
||||
instance.Chat.DeleteConnection(id, cancellationToken),
|
||||
DatabaseContext
|
||||
.ChatBots
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id)
|
||||
.DeleteAsync(cancellationToken))
|
||||
.ConfigureAwait(false);
|
||||
|
||||
return Ok();
|
||||
}
|
||||
@@ -156,7 +164,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(typeof(IEnumerable<Api.Models.ChatBot>), 200)]
|
||||
public async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
var query = DatabaseContext.ChatBots.Where(x => x.InstanceId == Instance.Id).Include(x => x.Channels);
|
||||
var query = DatabaseContext
|
||||
.ChatBots
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.Include(x => x.Channels);
|
||||
|
||||
var results = await query.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -183,7 +195,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(410)]
|
||||
public async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
var query = DatabaseContext.ChatBots.Where(x => x.Id == id).Include(x => x.Channels);
|
||||
var query = DatabaseContext.ChatBots
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id)
|
||||
.Include(x => x.Channels);
|
||||
|
||||
var results = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
if (results == default)
|
||||
@@ -221,7 +236,11 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (earlyOut != null)
|
||||
return earlyOut;
|
||||
|
||||
var query = DatabaseContext.ChatBots.Where(x => x.InstanceId == Instance.Id && x.Id == model.Id).Include(x => x.Channels);
|
||||
var query = DatabaseContext
|
||||
.ChatBots
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id && x.Id == model.Id)
|
||||
.Include(x => x.Channels);
|
||||
|
||||
var current = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
|
||||
@@ -109,7 +109,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
if (settings == null)
|
||||
{
|
||||
settings = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
settings = await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == Instance.Id)
|
||||
.Select(x => x.DreamDaemonSettings)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (settings == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
}
|
||||
@@ -191,7 +197,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return BadRequest(new ErrorMessage(ErrorCode.DreamDaemonDoubleSoft));
|
||||
|
||||
// alias for changing DD settings
|
||||
var current = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).Select(x => x.DreamDaemonSettings).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var current = await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == Instance.Id)
|
||||
.Select(x => x.DreamDaemonSettings)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (current == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
|
||||
@@ -61,7 +61,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
public async Task<IActionResult> Read(CancellationToken cancellationToken)
|
||||
{
|
||||
var instance = instanceManager.GetInstance(Instance);
|
||||
var dreamMakerSettings = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var dreamMakerSettings = await DatabaseContext
|
||||
.DreamMakerSettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return Json(dreamMakerSettings.ToApi());
|
||||
}
|
||||
|
||||
@@ -79,7 +84,9 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(404)]
|
||||
public async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
var compileJob = await DatabaseContext.CompileJobs
|
||||
var compileJob = await DatabaseContext
|
||||
.CompileJobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id && x.Job.Instance.Id == Instance.Id)
|
||||
.Include(x => x.Job).ThenInclude(x => x.StartedBy)
|
||||
.Include(x => x.RevisionInformation).ThenInclude(x => x.PrimaryTestMerge).ThenInclude(x => x.MergedBy)
|
||||
@@ -101,10 +108,17 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(typeof(List<EntityId>), 200)]
|
||||
public async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
var compileJobs = await DatabaseContext.CompileJobs.Where(x => x.Job.Instance.Id == Instance.Id).OrderByDescending(x => x.Job.StoppedAt).Select(x => new EntityId
|
||||
{
|
||||
Id = x.Id
|
||||
}).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
var compileJobs = await DatabaseContext
|
||||
.CompileJobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Job.Instance.Id == Instance.Id)
|
||||
.OrderByDescending(x => x.Job.StoppedAt)
|
||||
.Select(x => new EntityId
|
||||
{
|
||||
Id = x.Id
|
||||
})
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return Json(compileJobs);
|
||||
}
|
||||
|
||||
@@ -159,7 +173,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (model.ApiValidationPort == 0)
|
||||
throw new InvalidOperationException("ApiValidationPort cannot be 0!");
|
||||
|
||||
var hostModel = await DatabaseContext.DreamMakerSettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var hostModel = await DatabaseContext
|
||||
.DreamMakerSettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (hostModel == null)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
|
||||
|
||||
@@ -196,12 +196,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
using (systemIdentity)
|
||||
{
|
||||
// Get the user from the database
|
||||
IQueryable<User> query;
|
||||
IQueryable<User> query = DatabaseContext.Users.AsQueryable();
|
||||
string canonicalName = Models.User.CanonicalizeName(ApiHeaders.Username);
|
||||
if (systemIdentity == null)
|
||||
query = DatabaseContext.Users.Where(x => x.CanonicalName == canonicalName);
|
||||
query = query.Where(x => x.CanonicalName == canonicalName);
|
||||
else
|
||||
query = DatabaseContext.Users.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid);
|
||||
query = query.Where(x => x.CanonicalName == canonicalName || x.SystemIdentifier == systemIdentity.Uid);
|
||||
var users = await query.Select(x => new User
|
||||
{
|
||||
Id = x.Id,
|
||||
|
||||
@@ -174,18 +174,25 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var newCancellationToken = cts.Token;
|
||||
try
|
||||
{
|
||||
await DatabaseContext.Instances.ForEachAsync(
|
||||
otherInstance =>
|
||||
await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Select(x => new Models.Instance
|
||||
{
|
||||
if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
|
||||
earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached));
|
||||
else if (InstanceIsChildOf(otherInstance.Path))
|
||||
earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
|
||||
Path = x.Path
|
||||
})
|
||||
.ForEachAsync(
|
||||
otherInstance =>
|
||||
{
|
||||
if (++countOfOtherInstances >= generalConfiguration.InstanceLimit)
|
||||
earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceLimitReached));
|
||||
else if (InstanceIsChildOf(otherInstance.Path))
|
||||
earlyOut ??= Conflict(new ErrorMessage(ErrorCode.InstanceAtConflictingPath));
|
||||
|
||||
if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
|
||||
cts.Cancel();
|
||||
},
|
||||
newCancellationToken)
|
||||
if (earlyOut != null && !newCancellationToken.IsCancellationRequested)
|
||||
cts.Cancel();
|
||||
},
|
||||
newCancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
@@ -312,7 +319,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(410)]
|
||||
public async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
var originalModel = await DatabaseContext.Instances.Where(x => x.Id == id)
|
||||
var originalModel = await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id)
|
||||
.Include(x => x.WatchdogReattachInformation)
|
||||
.Include(x => x.WatchdogReattachInformation.Alpha)
|
||||
.Include(x => x.WatchdogReattachInformation.Bravo)
|
||||
@@ -358,7 +368,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (model == null)
|
||||
throw new ArgumentNullException(nameof(model));
|
||||
|
||||
IQueryable<Models.Instance> InstanceQuery() => DatabaseContext.Instances.Where(x => x.Id == model.Id);
|
||||
IQueryable<Models.Instance> InstanceQuery() => DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == model.Id);
|
||||
|
||||
var moveJob = await InstanceQuery()
|
||||
.SelectMany(x => x.Jobs).
|
||||
@@ -435,6 +448,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
{
|
||||
var countOfExistingChatBots = await DatabaseContext
|
||||
.ChatBots
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == originalModel.Id)
|
||||
.CountAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -582,7 +596,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
var cantList = !AuthenticationContext.User.InstanceManagerRights.Value.HasFlag(InstanceManagerRights.List);
|
||||
IQueryable<Models.Instance> QueryForUser()
|
||||
{
|
||||
var query = DatabaseContext.Instances.Where(x => x.Id == id);
|
||||
var query = DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id);
|
||||
|
||||
if (cantList)
|
||||
query = query.Include(x => x.InstanceUsers);
|
||||
|
||||
@@ -101,7 +101,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (earlyOut != null)
|
||||
return earlyOut;
|
||||
|
||||
var originalUser = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == model.UserId).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var originalUser = await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == Instance.Id)
|
||||
.SelectMany(x => x.InstanceUsers)
|
||||
.Where(x => x.UserId == model.UserId)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (originalUser == null)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
|
||||
@@ -141,7 +148,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(typeof(IEnumerable<Api.Models.InstanceUser>), 200)]
|
||||
public async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
var users = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
var users = await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == Instance.Id)
|
||||
.SelectMany(x => x.InstanceUsers)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return Json(users.Select(x => x.ToApi()));
|
||||
}
|
||||
|
||||
@@ -160,7 +173,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
public async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
// this functions as userId
|
||||
var user = await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var user = await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == Instance.Id)
|
||||
.SelectMany(x => x.InstanceUsers)
|
||||
.Where(x => x.UserId == id)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (user == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
return Json(user.ToApi());
|
||||
@@ -178,7 +198,14 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(204)]
|
||||
public async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
await DatabaseContext.Instances.Where(x => x.Id == Instance.Id).SelectMany(x => x.InstanceUsers).Where(x => x.UserId == id).DeleteAsync(cancellationToken).ConfigureAwait(false);
|
||||
await DatabaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == Instance.Id)
|
||||
.SelectMany(x => x.InstanceUsers)
|
||||
.Where(x => x.UserId == id)
|
||||
.DeleteAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return NoContent();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(typeof(IEnumerable<Api.Models.Job>), 200)]
|
||||
public async Task<IActionResult> Read(CancellationToken cancellationToken)
|
||||
{
|
||||
var result = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id && !x.StoppedAt.HasValue).OrderByDescending(x => x.StartedAt).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
var result = await DatabaseContext
|
||||
.Jobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Instance.Id == Instance.Id && !x.StoppedAt.HasValue)
|
||||
.OrderByDescending(x => x.StartedAt)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return Json(result.Select(x => x.ToApi()));
|
||||
}
|
||||
|
||||
@@ -65,10 +71,17 @@ namespace Tgstation.Server.Host.Controllers
|
||||
public async Task<IActionResult> List(CancellationToken cancellationToken)
|
||||
{
|
||||
// you KNOW this will need pagination eventually right?
|
||||
var jobs = await DatabaseContext.Jobs.Where(x => x.Instance.Id == Instance.Id).OrderByDescending(x => x.StartedAt).Select(x => new Api.Models.EntityId
|
||||
{
|
||||
Id = x.Id
|
||||
}).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
var jobs = await DatabaseContext
|
||||
.Jobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Instance.Id == Instance.Id)
|
||||
.OrderByDescending(x => x.StartedAt)
|
||||
.Select(x => new Api.Models.EntityId
|
||||
{
|
||||
Id = x.Id
|
||||
})
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
return Json(jobs);
|
||||
}
|
||||
|
||||
@@ -89,7 +102,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
public async Task<IActionResult> Delete(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
// don't care if an instance post or not at this point
|
||||
var job = await DatabaseContext.Jobs.Where(x => x.Id == id && x.Instance.Id == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var job = await DatabaseContext
|
||||
.Jobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id && x.Instance.Id == Instance.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (job == default(Job))
|
||||
return NotFound();
|
||||
|
||||
@@ -117,7 +135,13 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(404)]
|
||||
public async Task<IActionResult> GetId(long id, CancellationToken cancellationToken)
|
||||
{
|
||||
var job = await DatabaseContext.Jobs.Where(x => x.Id == id && x.Instance.Id == Instance.Id).Include(x => x.StartedBy).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var job = await DatabaseContext
|
||||
.Jobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id && x.Instance.Id == Instance.Id)
|
||||
.Include(x => x.StartedBy)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (job == default)
|
||||
return NotFound();
|
||||
var api = job.ToApi();
|
||||
|
||||
@@ -156,7 +156,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
if (model.AccessUser == null ^ model.AccessToken == null)
|
||||
return BadRequest(ErrorCode.RepoMismatchUserAndAccessToken);
|
||||
|
||||
var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var currentModel = await DatabaseContext
|
||||
.RepositorySettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (currentModel == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
@@ -236,7 +241,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(410)]
|
||||
public async Task<IActionResult> Delete(CancellationToken cancellationToken)
|
||||
{
|
||||
var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var currentModel = await DatabaseContext
|
||||
.RepositorySettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (currentModel == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone);
|
||||
@@ -275,7 +285,12 @@ namespace Tgstation.Server.Host.Controllers
|
||||
[ProducesResponseType(410)]
|
||||
public async Task<IActionResult> Read(CancellationToken cancellationToken)
|
||||
{
|
||||
var currentModel = await DatabaseContext.RepositorySettings.Where(x => x.InstanceId == Instance.Id).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
var currentModel = await DatabaseContext
|
||||
.RepositorySettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (currentModel == default)
|
||||
return StatusCode((int)HttpStatusCode.Gone, new ErrorMessage(ErrorCode.RepoMissing));
|
||||
@@ -345,6 +360,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var currentModel = await DatabaseContext
|
||||
.RepositorySettings
|
||||
.AsQueryable()
|
||||
.Where(x => x.InstanceId == Instance.Id)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -653,6 +669,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
await databaseContextFactory.UseContext(
|
||||
async databaseContext =>
|
||||
dbPull = await databaseContext.RevisionInformations
|
||||
.AsQueryable()
|
||||
.Where(x => x.Instance.Id == Instance.Id
|
||||
&& x.OriginCommitSha == lastRevisionInfo.OriginCommitSha
|
||||
&& x.ActiveTestMerges.Count <= model.NewTestMerges.Count
|
||||
|
||||
@@ -192,7 +192,10 @@ namespace Tgstation.Server.Host.Controllers
|
||||
|
||||
var originalUser = passwordEditOnly
|
||||
? AuthenticationContext.User
|
||||
: await DatabaseContext.Users.Where(x => x.Id == model.Id)
|
||||
: await DatabaseContext
|
||||
.Users
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == model.Id)
|
||||
.Include(x => x.CreatedBy)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -301,6 +304,7 @@ namespace Tgstation.Server.Host.Controllers
|
||||
return Forbid();
|
||||
|
||||
var user = await DatabaseContext.Users
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == id)
|
||||
.Include(x => x.CreatedBy)
|
||||
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
||||
@@ -202,7 +202,7 @@ namespace Tgstation.Server.Host.Core
|
||||
// CORS conditionally enabled later
|
||||
services.AddCors();
|
||||
|
||||
void AddTypedContext<TContext>() where TContext : DatabaseContext<TContext>
|
||||
void AddTypedContext<TContext>() where TContext : DatabaseContext
|
||||
{
|
||||
services.AddDbContext<TContext>(builder =>
|
||||
{
|
||||
@@ -226,6 +226,9 @@ namespace Tgstation.Server.Host.Core
|
||||
case DatabaseType.Sqlite:
|
||||
AddTypedContext<SqliteDatabaseContext>();
|
||||
break;
|
||||
case DatabaseType.PostgresSql:
|
||||
AddTypedContext<PostgresSqlDatabaseContext>();
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType));
|
||||
}
|
||||
@@ -354,6 +357,8 @@ namespace Tgstation.Server.Host.Core
|
||||
// Final point where we wrap exceptions in a 500 (ErrorMessage) response
|
||||
applicationBuilder.UseServerErrorHandling();
|
||||
|
||||
applicationBuilder.UseRequestCounting();
|
||||
|
||||
// 503 requests made while the application is starting
|
||||
applicationBuilder.UseAsyncInitialization(async (cancellationToken) =>
|
||||
{
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Core
|
||||
{
|
||||
@@ -8,14 +11,25 @@ namespace Tgstation.Server.Host.Core
|
||||
sealed class ServerPortProivder : IServerPortProvider
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public ushort HttpApiPort { get; }
|
||||
public ushort HttpApiPort => generalConfiguration.ApiPort;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="GeneralConfiguration"/> for the <see cref="ServerPortProivder"/>.
|
||||
/// </summary>
|
||||
readonly GeneralConfiguration generalConfiguration;
|
||||
|
||||
/// <summary>
|
||||
/// Initializes a new instance of the <see cref="ServerPortProivder"/> <see langword="class"/>.
|
||||
/// </summary>
|
||||
/// <param name="generalConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="generalConfiguration"/>.</param>
|
||||
/// <param name="configuration">The <see cref="IConfiguration"/> to use.</param>
|
||||
public ServerPortProivder(IConfiguration configuration)
|
||||
/// <param name="logger">The <see cref="ILogger"/> to use.</param>
|
||||
public ServerPortProivder(
|
||||
IOptions<GeneralConfiguration> generalConfigurationOptions,
|
||||
IConfiguration configuration,
|
||||
ILogger<ServerPortProivder> logger)
|
||||
{
|
||||
generalConfiguration = generalConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(generalConfigurationOptions));
|
||||
if (configuration == null)
|
||||
throw new ArgumentNullException(nameof(configuration));
|
||||
|
||||
@@ -26,8 +40,13 @@ namespace Tgstation.Server.Host.Core
|
||||
.GetSection("Url")
|
||||
.Value;
|
||||
|
||||
if (httpEndpoint == null)
|
||||
throw new InvalidOperationException("Missing required configuration option Kestrel:EndPoints:Http:Url!");
|
||||
if (generalConfiguration.ApiPort == default && httpEndpoint == null)
|
||||
throw new InvalidOperationException("Missing required configuration option General:ApiPort!");
|
||||
|
||||
if (generalConfiguration.ApiPort != default)
|
||||
return;
|
||||
|
||||
logger.LogWarning("The \"Kestrel\" configuration section is deprecated! Please set your API port using the \"General:ApiPort\" configuration option!");
|
||||
|
||||
var splits = httpEndpoint.Split(":", StringSplitOptions.RemoveEmptyEntries);
|
||||
var portString = splits.Last();
|
||||
@@ -36,7 +55,7 @@ namespace Tgstation.Server.Host.Core
|
||||
if (!UInt16.TryParse(portString, out var result))
|
||||
throw new InvalidOperationException($"Failed to parse HTTP EndPoint port: {httpEndpoint}");
|
||||
|
||||
HttpApiPort = result;
|
||||
generalConfiguration.ApiPort = result;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -133,6 +133,14 @@ namespace Tgstation.Server.Host.Core
|
||||
swaggerGenOptions.DocumentFilter<SwaggerConfiguration>();
|
||||
swaggerGenOptions.SchemaFilter<SwaggerConfiguration>();
|
||||
|
||||
swaggerGenOptions.CustomSchemaIds(type =>
|
||||
{
|
||||
if (type == typeof(Api.Models.Internal.User))
|
||||
return "ShallowUser";
|
||||
|
||||
return type.Name;
|
||||
});
|
||||
|
||||
swaggerGenOptions.AddSecurityDefinition(PasswordSecuritySchemeId, new OpenApiSecurityScheme
|
||||
{
|
||||
In = ParameterLocation.Header,
|
||||
|
||||
@@ -5,7 +5,6 @@ using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Linq.Expressions;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
@@ -48,9 +47,7 @@ namespace Tgstation.Server.Host.Database
|
||||
public void Attach(TModel model) => dbSet.Attach(model);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task ForEachAsync(Action<TModel> action, CancellationToken cancellationToken) => dbSet
|
||||
.AsAsyncEnumerable()
|
||||
.ForEachAsync(action, cancellationToken);
|
||||
public IAsyncEnumerator<TModel> GetAsyncEnumerator(CancellationToken cancellationToken = default) => dbSet.AsAsyncEnumerable().GetAsyncEnumerator();
|
||||
|
||||
/// <inheritdoc />
|
||||
public IEnumerator<TModel> GetEnumerator() => dbSet.AsQueryable().GetEnumerator();
|
||||
@@ -61,9 +58,6 @@ namespace Tgstation.Server.Host.Database
|
||||
/// <inheritdoc />
|
||||
public void RemoveRange(IEnumerable<TModel> models) => dbSet.RemoveRange(models);
|
||||
|
||||
/// <inheritdoc />
|
||||
public Task<List<TModel>> ToListAsync(CancellationToken cancellationToken) => dbSet.AsQueryable().ToListAsync(cancellationToken);
|
||||
|
||||
/// <inheritdoc />
|
||||
IEnumerator IEnumerable.GetEnumerator() => dbSet.AsQueryable().GetEnumerator();
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Data.Common;
|
||||
using System.Data.SqlClient;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Npgsql;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Database
|
||||
@@ -34,6 +35,11 @@ namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
ConnectionString = connectionString
|
||||
};
|
||||
case DatabaseType.PostgresSql:
|
||||
return new NpgsqlConnection
|
||||
{
|
||||
ConnectionString = connectionString
|
||||
};
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, "Invalid DatabaseType!");
|
||||
}
|
||||
|
||||
@@ -18,95 +18,94 @@ namespace Tgstation.Server.Host.Database
|
||||
/// <summary>
|
||||
/// Backend abstract implementation of <see cref="IDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <typeparam name="TParentContext">The child <see cref="Type"/> used to implement a backend.</typeparam>
|
||||
#pragma warning disable CA1506 // TODO: Decomplexify
|
||||
abstract class DatabaseContext<TParentContext> : DbContext, IDatabaseContext where TParentContext : DbContext
|
||||
abstract class DatabaseContext : DbContext, IDatabaseContext
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public DatabaseType DatabaseType => DatabaseConfiguration.DatabaseType;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="User"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="User"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<User> Users { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Instance"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="Instance"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<Instance> Instances { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="CompileJob"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="CompileJob"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<CompileJob> CompileJobs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RevisionInformation"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="RevisionInformation"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<RevisionInformation> RevisionInformations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamMakerSettings"/> in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="Models.DreamMakerSettings"/> in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<DreamMakerSettings> DreamMakerSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ChatBot"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="ChatBot"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<ChatBot> ChatBots { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.DreamDaemonSettings"/> in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="Models.DreamDaemonSettings"/> in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<DreamDaemonSettings> DreamDaemonSettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Models.RepositorySettings"/> in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="Models.RepositorySettings"/> in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<RepositorySettings> RepositorySettings { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="InstanceUser"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="InstanceUser"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<InstanceUser> InstanceUsers { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ChatChannel"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="ChatChannel"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<ChatChannel> ChatChannels { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="Job"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="Job"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<Job> Jobs { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ReattachInformation"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="ReattachInformation"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<ReattachInformation> ReattachInformations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DualReattachInformation"/>s in the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="DualReattachInformation"/>s in the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
public DbSet<DualReattachInformation> WatchdogReattachInformations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="TestMerge"/>s in the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// The <see cref="TestMerge"/>s in the <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
public DbSet<TestMerge> TestMerges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="RevInfoTestMerge"/>s om the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// The <see cref="RevInfoTestMerge"/>s om the <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
public DbSet<RevInfoTestMerge> RevInfoTestMerges { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="ILogger"/> for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// The <see cref="ILogger"/> for the <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
protected ILogger Logger { get; }
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// The <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
protected DatabaseConfiguration DatabaseConfiguration { get; }
|
||||
|
||||
@@ -150,7 +149,7 @@ namespace Tgstation.Server.Host.Database
|
||||
IDatabaseCollection<DualReattachInformation> IDatabaseContext.WatchdogReattachInformations => watchdogReattachInformationsCollection;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
readonly IDatabaseSeeder databaseSeeder;
|
||||
|
||||
@@ -220,13 +219,13 @@ namespace Tgstation.Server.Host.Database
|
||||
readonly IDatabaseCollection<DualReattachInformation> watchdogReattachInformationsCollection;
|
||||
|
||||
/// <summary>
|
||||
/// Construct a <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// Construct a <see cref="DatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TParentContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions"/> for the <see cref="DatabaseContext"/>.</param>
|
||||
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="DatabaseConfiguration"/></param>
|
||||
/// <param name="databaseSeeder">The value of <see cref="databaseSeeder"/></param>
|
||||
/// <param name="logger">The value of <see cref="Logger"/></param>
|
||||
public DatabaseContext(DbContextOptions<TParentContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions)
|
||||
public DatabaseContext(DbContextOptions dbContextOptions, IOptions<DatabaseConfiguration> databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions)
|
||||
{
|
||||
DatabaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
|
||||
this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder));
|
||||
@@ -354,9 +353,14 @@ namespace Tgstation.Server.Host.Database
|
||||
|
||||
// Update this with new migrations as they are made
|
||||
string targetMigration = null;
|
||||
|
||||
if (DatabaseType == DatabaseType.PostgresSql && version < new Version(4, 3, 0))
|
||||
throw new NotSupportedException("Cannot migrate below version 4.3.0 with PostgresSql!");
|
||||
|
||||
if (version < new Version(4, 1, 0))
|
||||
throw new NotSupportedException("Cannot migrate below version 4.1.0!");
|
||||
else if (version < new Version(4, 2, 0))
|
||||
|
||||
if (version < new Version(4, 2, 0))
|
||||
targetMigration = DatabaseType == DatabaseType.Sqlite ? nameof(SLRebuild) : nameof(MSFixCascadingDelete);
|
||||
|
||||
if (targetMigration == null)
|
||||
@@ -379,6 +383,9 @@ namespace Tgstation.Server.Host.Database
|
||||
case DatabaseType.Sqlite:
|
||||
migrationSubstitution = "SL{0}";
|
||||
break;
|
||||
case DatabaseType.PostgresSql:
|
||||
migrationSubstitution = "PG{0}";
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException($"Invalid DatabaseType: {DatabaseType}");
|
||||
}
|
||||
@@ -402,7 +409,7 @@ namespace Tgstation.Server.Host.Database
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ensure the <see cref="DatabaseType"/> is correct for the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// Ensure the <see cref="DatabaseType"/> is correct for the <see cref="DatabaseContext"/>.
|
||||
/// </summary>
|
||||
protected abstract void ValidateDatabaseType();
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ namespace Tgstation.Server.Host.Database
|
||||
sealed class DatabaseSeeder : IDatabaseSeeder
|
||||
{
|
||||
/// <summary>
|
||||
/// The <see cref="ICryptographySuite"/> for the <see cref="DatabaseContext{TParentContext}"/>
|
||||
/// The <see cref="ICryptographySuite"/> for the <see cref="DatabaseSeeder"/>
|
||||
/// </summary>
|
||||
readonly ICryptographySuite cryptographySuite;
|
||||
|
||||
/// <summary>
|
||||
/// The <see cref="IPlatformIdentifier"/> for the <see cref="DatabaseContext{TParentContext}"/>.
|
||||
/// The <see cref="IPlatformIdentifier"/> for the <see cref="DatabaseSeeder"/>.
|
||||
/// </summary>
|
||||
readonly IPlatformIdentifier platformIdentifier;
|
||||
|
||||
@@ -76,7 +76,11 @@ namespace Tgstation.Server.Host.Database
|
||||
if (platformIdentifier.IsWindows)
|
||||
{
|
||||
// normalize backslashes to forward slashes
|
||||
var allInstances = await databaseContext.Instances.ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
var allInstances = await databaseContext
|
||||
.Instances
|
||||
.AsQueryable()
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
foreach (var instance in allInstances)
|
||||
instance.Path = instance.Path.Replace('\\', '/');
|
||||
}
|
||||
@@ -107,6 +111,7 @@ namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
var admin = await databaseContext
|
||||
.Users
|
||||
.AsQueryable()
|
||||
.Where(x => x.CanonicalName == User.CanonicalizeName(Api.Models.User.AdminName))
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
@@ -4,7 +4,7 @@ using Tgstation.Server.Host.Configuration;
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
/// <summary>
|
||||
/// Contains helpers for creating design time <see cref="DatabaseContext{TParentContext}"/>s
|
||||
/// Contains helpers for creating design time <see cref="DatabaseContext"/>s
|
||||
/// </summary>
|
||||
static class DesignTimeDbContextFactoryHelpers
|
||||
{
|
||||
@@ -14,7 +14,7 @@ namespace Tgstation.Server.Host.Database.Design
|
||||
/// <param name="databaseType">The <see cref="DatabaseConfiguration.DatabaseType"/>.</param>
|
||||
/// <param name="connectionString">The <see cref="DatabaseConfiguration.ConnectionString"/>.</param>
|
||||
/// <returns>The <see cref="IOptions{TOptions}"/> for the <see cref="DatabaseConfiguration"/></returns>
|
||||
public static IOptions<DatabaseConfiguration> GetDbContextOptions(DatabaseType databaseType, string connectionString)
|
||||
public static IOptions<DatabaseConfiguration> GetDatabaseConfiguration(DatabaseType databaseType, string connectionString)
|
||||
{
|
||||
var dbConfig = new DatabaseConfiguration
|
||||
{
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Database.Design
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
return new MySqlDatabaseContext(
|
||||
new DbContextOptions<MySqlDatabaseContext>(),
|
||||
DesignTimeDbContextFactoryHelpers.GetDbContextOptions(
|
||||
DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
|
||||
DatabaseType.MariaDB,
|
||||
"Server=127.0.0.1;User Id=root;Password=fake;Database=TGS_Design"),
|
||||
new DatabaseSeeder(
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Design;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
using Tgstation.Server.Host.Models;
|
||||
using Tgstation.Server.Host.Security;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Design
|
||||
{
|
||||
/// <inheritdoc />
|
||||
sealed class PostgresSqlDesignTimeDbContextFactory : IDesignTimeDbContextFactory<PostgresSqlDatabaseContext>
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public PostgresSqlDatabaseContext CreateDbContext(string[] args)
|
||||
{
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
return new PostgresSqlDatabaseContext(
|
||||
new DbContextOptions<PostgresSqlDatabaseContext>(),
|
||||
DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
|
||||
DatabaseType.PostgresSql,
|
||||
"Application Name=tgstation-server;Host=127.0.0.1;Password=qCkWimNgLfWwpr7TnUHs;Username=postgres;Database=TGS_Design"),
|
||||
new DatabaseSeeder(
|
||||
new CryptographySuite(
|
||||
new PasswordHasher<User>()),
|
||||
new PlatformIdentifier()),
|
||||
loggerFactory.CreateLogger<PostgresSqlDatabaseContext>());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Database.Design
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
return new SqlServerDatabaseContext(
|
||||
new DbContextOptions<SqlServerDatabaseContext>(),
|
||||
DesignTimeDbContextFactoryHelpers.GetDbContextOptions(
|
||||
DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
|
||||
DatabaseType.SqlServer,
|
||||
"Data Source=fake;Initial Catalog=TGS_Design;Integrated Security=True;Application Name=tgstation-server"),
|
||||
new DatabaseSeeder(
|
||||
|
||||
@@ -20,7 +20,7 @@ namespace Tgstation.Server.Host.Database.Design
|
||||
using var loggerFactory = new LoggerFactory();
|
||||
return new SqliteDatabaseContext(
|
||||
new DbContextOptions<SqliteDatabaseContext>(),
|
||||
DesignTimeDbContextFactoryHelpers.GetDbContextOptions(
|
||||
DesignTimeDbContextFactoryHelpers.GetDatabaseConfiguration(
|
||||
DatabaseType.Sqlite,
|
||||
"Data Source=tgs_design.sqlite3;Mode=ReadWriteCreate"),
|
||||
new DatabaseSeeder(
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
@@ -10,7 +7,7 @@ namespace Tgstation.Server.Host.Database
|
||||
/// Represents a database table.
|
||||
/// </summary>
|
||||
/// <typeparam name="TModel">The type of model.</typeparam>
|
||||
public interface IDatabaseCollection<TModel> : IQueryable<TModel>
|
||||
public interface IDatabaseCollection<TModel> : IQueryable<TModel>, IAsyncEnumerable<TModel>
|
||||
{
|
||||
/// <summary>
|
||||
/// An <see cref="IEnumerable{T}"/> of <typeparamref name="TModel"/>s prioritizing in the working set.
|
||||
@@ -46,20 +43,5 @@ namespace Tgstation.Server.Host.Database
|
||||
/// </summary>
|
||||
/// <param name="models">An <see cref="IEnumerable{T}"/> of <typeparamref name="TModel"/>s to remove.</param>
|
||||
void RemoveRange(IEnumerable<TModel> models);
|
||||
|
||||
/// <summary>
|
||||
/// Asyncronously run a given <paramref name="action"/> on the <see cref="IDatabaseCollection{TModel}"/>.
|
||||
/// </summary>
|
||||
/// <param name="action">The <see cref="Action{T}"/> to run.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
|
||||
Task ForEachAsync(Action<TModel> action, CancellationToken cancellationToken);
|
||||
|
||||
/// <summary>
|
||||
/// Retrieve all the <typeparamref name="TModel"/>s in the table.
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in a <see cref="List{T}"/> of all <typeparamref name="TModel"/> in the table.</returns>
|
||||
Task<List<TModel>> ToListAsync(CancellationToken cancellationToken);
|
||||
}
|
||||
}
|
||||
|
||||
+816
@@ -0,0 +1,816 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Migrations
|
||||
{
|
||||
[DbContext(typeof(PostgresSqlDatabaseContext))]
|
||||
[Migration("20200516111712_PGCreate")]
|
||||
partial class PGCreate
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
|
||||
.HasAnnotation("ProductVersion", "3.1.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ChannelLimit")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConnectionString")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<bool?>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<int>("Provider")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("ReconnectionInterval")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId", "Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ChatBots");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<long>("ChatSettingsId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<decimal?>("DiscordChannelId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("IrcChannel")
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<bool?>("IsAdminChannel")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("IsUpdatesChannel")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("IsWatchdogChannel")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Tag")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChatSettingsId", "DiscordChannelId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ChatSettingsId", "IrcChannel")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ChatChannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("ByondVersion")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("DMApiMajorVersion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("DMApiMinorVersion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("DMApiPatchVersion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid?>("DirectoryName")
|
||||
.IsRequired()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DmeName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("JobId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("MinimumSecurityLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Output")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("RevisionInformationId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DirectoryName");
|
||||
|
||||
b.HasIndex("JobId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RevisionInformationId");
|
||||
|
||||
b.ToTable("CompileJobs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<bool?>("AllowWebClient")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("AutoStart")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<long>("HeartbeatSeconds")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("PrimaryPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SecondaryPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SecurityLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("StartupTimeout")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DreamDaemonSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiValidationPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("ApiValidationSecurityLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ProjectName")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DreamMakerSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<long?>("AlphaId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("AlphaIsActive")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<long?>("BravoId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AlphaId");
|
||||
|
||||
b.HasIndex("BravoId");
|
||||
|
||||
b.HasIndex("InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("WatchdogReattachInformations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<long>("AutoUpdateInterval")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("ChatBotLimit")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("ConfigurationType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<bool?>("Online")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Instances");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<decimal>("ByondRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("ChatBotRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("ConfigurationRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("DreamDaemonRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("DreamMakerRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<decimal>("InstanceUserRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("RepositoryRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId");
|
||||
|
||||
b.HasIndex("UserId", "InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("InstanceUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<decimal?>("CancelRight")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal?>("CancelRightsType")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<bool?>("Cancelled")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<long?>("CancelledById")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long?>("ErrorCode")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ExceptionDetails")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTimeOffset?>("StartedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long>("StartedById")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTimeOffset?>("StoppedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CancelledById");
|
||||
|
||||
b.HasIndex("InstanceId");
|
||||
|
||||
b.HasIndex("StartedById");
|
||||
|
||||
b.ToTable("Jobs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("AccessIdentifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("CompileJobId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("IsPrimary")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("LaunchSecurityLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Port")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("ProcessId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("RebootState")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CompileJobId");
|
||||
|
||||
b.ToTable("ReattachInformations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("AccessToken")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<string>("AccessUser")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<bool?>("AutoUpdatesKeepTestMerges")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("AutoUpdatesSynchronize")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("CommitterEmail")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<string>("CommitterName")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool?>("PostTestMergeComment")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("PushTestMergeCommits")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("ShowTestMergeCommitters")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RepositorySettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<long>("RevisionInformationId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("TestMergeId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RevisionInformationId");
|
||||
|
||||
b.HasIndex("TestMergeId");
|
||||
|
||||
b.ToTable("RevInfoTestMerges");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("CommitSha")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasMaxLength(40);
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("OriginCommitSha")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasMaxLength(40);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId", "CommitSha")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RevisionInformations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("BodyAtMerge")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<DateTimeOffset>("MergedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long>("MergedById")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long?>("PrimaryRevisionInformationId")
|
||||
.IsRequired()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("PullRequestRevision")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasMaxLength(40);
|
||||
|
||||
b.Property<string>("TitleAtMerge")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MergedById");
|
||||
|
||||
b.HasIndex("PrimaryRevisionInformationId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("TestMerges");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
|
||||
{
|
||||
b.Property<long?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<decimal>("AdministrationRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("CanonicalName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long?>("CreatedById")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool?>("Enabled")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal>("InstanceManagerRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<DateTimeOffset?>("LastPasswordUpdate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("SystemIdentifier")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CanonicalName")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("SystemIdentifier")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithMany("ChatSettings")
|
||||
.HasForeignKey("InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
|
||||
.WithMany("Channels")
|
||||
.HasForeignKey("ChatSettingsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
|
||||
.WithOne()
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
|
||||
.WithMany("CompileJobs")
|
||||
.HasForeignKey("RevisionInformationId")
|
||||
.OnDelete(DeleteBehavior.ClientNoAction)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithOne("DreamDaemonSettings")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithOne("DreamMakerSettings")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha")
|
||||
.WithMany()
|
||||
.HasForeignKey("AlphaId");
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo")
|
||||
.WithMany()
|
||||
.HasForeignKey("BravoId");
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", null)
|
||||
.WithOne("WatchdogReattachInformation")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithMany("InstanceUsers")
|
||||
.HasForeignKey("InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", null)
|
||||
.WithMany("InstanceUsers")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CancelledById");
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithMany("Jobs")
|
||||
.HasForeignKey("InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("StartedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
|
||||
.WithMany()
|
||||
.HasForeignKey("CompileJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithOne("RepositorySettings")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
|
||||
.WithMany("ActiveTestMerges")
|
||||
.HasForeignKey("RevisionInformationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
|
||||
.WithMany("RevisonInformations")
|
||||
.HasForeignKey("TestMergeId")
|
||||
.OnDelete(DeleteBehavior.ClientNoAction)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithMany("RevisionInformations")
|
||||
.HasForeignKey("InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
|
||||
.WithMany("TestMerges")
|
||||
.HasForeignKey("MergedById")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
|
||||
.WithOne("PrimaryTestMerge")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
|
||||
.WithMany("CreatedUsers")
|
||||
.HasForeignKey("CreatedById");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,638 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
|
||||
namespace Tgstation.Server.Host.Database.Migrations
|
||||
{
|
||||
/// <summary>
|
||||
/// Create initial schema for PostgreSQL.
|
||||
/// </summary>
|
||||
public partial class PGCreate : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder == null)
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Instances",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(maxLength: 10000, nullable: false),
|
||||
Path = table.Column<string>(nullable: false),
|
||||
Online = table.Column<bool>(nullable: false),
|
||||
ConfigurationType = table.Column<int>(nullable: false),
|
||||
AutoUpdateInterval = table.Column<long>(nullable: false),
|
||||
ChatBotLimit = table.Column<int>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Instances", x => x.Id);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Users",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Enabled = table.Column<bool>(nullable: false),
|
||||
CreatedAt = table.Column<DateTimeOffset>(nullable: false),
|
||||
SystemIdentifier = table.Column<string>(nullable: true),
|
||||
Name = table.Column<string>(maxLength: 10000, nullable: false),
|
||||
AdministrationRights = table.Column<decimal>(nullable: false),
|
||||
InstanceManagerRights = table.Column<decimal>(nullable: false),
|
||||
PasswordHash = table.Column<string>(nullable: true),
|
||||
CreatedById = table.Column<long>(nullable: true),
|
||||
CanonicalName = table.Column<string>(nullable: false),
|
||||
LastPasswordUpdate = table.Column<DateTimeOffset>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Users", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Users_Users_CreatedById",
|
||||
column: x => x.CreatedById,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChatBots",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Name = table.Column<string>(maxLength: 100, nullable: false),
|
||||
Enabled = table.Column<bool>(nullable: true),
|
||||
ReconnectionInterval = table.Column<long>(nullable: false),
|
||||
ChannelLimit = table.Column<int>(nullable: false),
|
||||
Provider = table.Column<int>(nullable: false),
|
||||
ConnectionString = table.Column<string>(maxLength: 10000, nullable: false),
|
||||
InstanceId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ChatBots", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ChatBots_Instances_InstanceId",
|
||||
column: x => x.InstanceId,
|
||||
principalTable: "Instances",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DreamDaemonSettings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
AllowWebClient = table.Column<bool>(nullable: false),
|
||||
SecurityLevel = table.Column<int>(nullable: false),
|
||||
PrimaryPort = table.Column<int>(nullable: false),
|
||||
SecondaryPort = table.Column<int>(nullable: false),
|
||||
StartupTimeout = table.Column<long>(nullable: false),
|
||||
HeartbeatSeconds = table.Column<long>(nullable: false),
|
||||
AutoStart = table.Column<bool>(nullable: false),
|
||||
InstanceId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DreamDaemonSettings", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DreamDaemonSettings_Instances_InstanceId",
|
||||
column: x => x.InstanceId,
|
||||
principalTable: "Instances",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "DreamMakerSettings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
ProjectName = table.Column<string>(maxLength: 10000, nullable: true),
|
||||
ApiValidationPort = table.Column<int>(nullable: false),
|
||||
ApiValidationSecurityLevel = table.Column<int>(nullable: false),
|
||||
InstanceId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_DreamMakerSettings", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_DreamMakerSettings_Instances_InstanceId",
|
||||
column: x => x.InstanceId,
|
||||
principalTable: "Instances",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RepositorySettings",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
CommitterName = table.Column<string>(maxLength: 10000, nullable: false),
|
||||
CommitterEmail = table.Column<string>(maxLength: 10000, nullable: false),
|
||||
AccessUser = table.Column<string>(maxLength: 10000, nullable: true),
|
||||
AccessToken = table.Column<string>(maxLength: 10000, nullable: true),
|
||||
PushTestMergeCommits = table.Column<bool>(nullable: false),
|
||||
ShowTestMergeCommitters = table.Column<bool>(nullable: false),
|
||||
AutoUpdatesKeepTestMerges = table.Column<bool>(nullable: false),
|
||||
AutoUpdatesSynchronize = table.Column<bool>(nullable: false),
|
||||
PostTestMergeComment = table.Column<bool>(nullable: false),
|
||||
InstanceId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RepositorySettings", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RepositorySettings_Instances_InstanceId",
|
||||
column: x => x.InstanceId,
|
||||
principalTable: "Instances",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RevisionInformations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
CommitSha = table.Column<string>(maxLength: 40, nullable: false),
|
||||
OriginCommitSha = table.Column<string>(maxLength: 40, nullable: false),
|
||||
InstanceId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RevisionInformations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RevisionInformations_Instances_InstanceId",
|
||||
column: x => x.InstanceId,
|
||||
principalTable: "Instances",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "InstanceUsers",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
UserId = table.Column<long>(nullable: false),
|
||||
InstanceUserRights = table.Column<decimal>(nullable: false),
|
||||
ByondRights = table.Column<decimal>(nullable: false),
|
||||
DreamDaemonRights = table.Column<decimal>(nullable: false),
|
||||
DreamMakerRights = table.Column<decimal>(nullable: false),
|
||||
RepositoryRights = table.Column<decimal>(nullable: false),
|
||||
ChatBotRights = table.Column<decimal>(nullable: false),
|
||||
ConfigurationRights = table.Column<decimal>(nullable: false),
|
||||
InstanceId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_InstanceUsers", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_InstanceUsers_Instances_InstanceId",
|
||||
column: x => x.InstanceId,
|
||||
principalTable: "Instances",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_InstanceUsers_Users_UserId",
|
||||
column: x => x.UserId,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "Jobs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Description = table.Column<string>(nullable: false),
|
||||
ErrorCode = table.Column<long>(nullable: true),
|
||||
ExceptionDetails = table.Column<string>(nullable: true),
|
||||
StartedAt = table.Column<DateTimeOffset>(nullable: false),
|
||||
StoppedAt = table.Column<DateTimeOffset>(nullable: true),
|
||||
Cancelled = table.Column<bool>(nullable: false),
|
||||
CancelRightsType = table.Column<decimal>(nullable: true),
|
||||
CancelRight = table.Column<decimal>(nullable: true),
|
||||
StartedById = table.Column<long>(nullable: false),
|
||||
CancelledById = table.Column<long>(nullable: true),
|
||||
InstanceId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_Jobs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_Jobs_Users_CancelledById",
|
||||
column: x => x.CancelledById,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_Jobs_Instances_InstanceId",
|
||||
column: x => x.InstanceId,
|
||||
principalTable: "Instances",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_Jobs_Users_StartedById",
|
||||
column: x => x.StartedById,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ChatChannels",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
IrcChannel = table.Column<string>(maxLength: 100, nullable: true),
|
||||
DiscordChannelId = table.Column<decimal>(nullable: true),
|
||||
IsAdminChannel = table.Column<bool>(nullable: false),
|
||||
IsWatchdogChannel = table.Column<bool>(nullable: false),
|
||||
IsUpdatesChannel = table.Column<bool>(nullable: false),
|
||||
Tag = table.Column<string>(maxLength: 10000, nullable: true),
|
||||
ChatSettingsId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ChatChannels", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ChatChannels_ChatBots_ChatSettingsId",
|
||||
column: x => x.ChatSettingsId,
|
||||
principalTable: "ChatBots",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "TestMerges",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
Number = table.Column<int>(nullable: false),
|
||||
PullRequestRevision = table.Column<string>(maxLength: 40, nullable: false),
|
||||
Comment = table.Column<string>(maxLength: 10000, nullable: true),
|
||||
TitleAtMerge = table.Column<string>(nullable: false),
|
||||
BodyAtMerge = table.Column<string>(nullable: false),
|
||||
Url = table.Column<string>(nullable: false),
|
||||
Author = table.Column<string>(nullable: false),
|
||||
MergedAt = table.Column<DateTimeOffset>(nullable: false),
|
||||
MergedById = table.Column<long>(nullable: false),
|
||||
PrimaryRevisionInformationId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_TestMerges", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_TestMerges_Users_MergedById",
|
||||
column: x => x.MergedById,
|
||||
principalTable: "Users",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_TestMerges_RevisionInformations_PrimaryRevisionInformationId",
|
||||
column: x => x.PrimaryRevisionInformationId,
|
||||
principalTable: "RevisionInformations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "CompileJobs",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
DmeName = table.Column<string>(nullable: false),
|
||||
Output = table.Column<string>(nullable: false),
|
||||
DirectoryName = table.Column<Guid>(nullable: false),
|
||||
MinimumSecurityLevel = table.Column<int>(nullable: false),
|
||||
JobId = table.Column<long>(nullable: false),
|
||||
RevisionInformationId = table.Column<long>(nullable: false),
|
||||
ByondVersion = table.Column<string>(nullable: false),
|
||||
DMApiMajorVersion = table.Column<int>(nullable: true),
|
||||
DMApiMinorVersion = table.Column<int>(nullable: true),
|
||||
DMApiPatchVersion = table.Column<int>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_CompileJobs", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_CompileJobs_Jobs_JobId",
|
||||
column: x => x.JobId,
|
||||
principalTable: "Jobs",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_CompileJobs_RevisionInformations_RevisionInformationId",
|
||||
column: x => x.RevisionInformationId,
|
||||
principalTable: "RevisionInformations",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "RevInfoTestMerges",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
TestMergeId = table.Column<long>(nullable: false),
|
||||
RevisionInformationId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_RevInfoTestMerges", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_RevInfoTestMerges_RevisionInformations_RevisionInformationId",
|
||||
column: x => x.RevisionInformationId,
|
||||
principalTable: "RevisionInformations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
table.ForeignKey(
|
||||
name: "FK_RevInfoTestMerges_TestMerges_TestMergeId",
|
||||
column: x => x.TestMergeId,
|
||||
principalTable: "TestMerges",
|
||||
principalColumn: "Id");
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "ReattachInformations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
AccessIdentifier = table.Column<string>(nullable: false),
|
||||
ProcessId = table.Column<int>(nullable: false),
|
||||
IsPrimary = table.Column<bool>(nullable: false),
|
||||
Port = table.Column<int>(nullable: false),
|
||||
RebootState = table.Column<int>(nullable: false),
|
||||
LaunchSecurityLevel = table.Column<int>(nullable: false),
|
||||
CompileJobId = table.Column<long>(nullable: false)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_ReattachInformations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
|
||||
column: x => x.CompileJobId,
|
||||
principalTable: "CompileJobs",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateTable(
|
||||
name: "WatchdogReattachInformations",
|
||||
columns: table => new
|
||||
{
|
||||
Id = table.Column<long>(nullable: false)
|
||||
.Annotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn),
|
||||
AlphaIsActive = table.Column<bool>(nullable: false),
|
||||
InstanceId = table.Column<long>(nullable: false),
|
||||
AlphaId = table.Column<long>(nullable: true),
|
||||
BravoId = table.Column<long>(nullable: true)
|
||||
},
|
||||
constraints: table =>
|
||||
{
|
||||
table.PrimaryKey("PK_WatchdogReattachInformations", x => x.Id);
|
||||
table.ForeignKey(
|
||||
name: "FK_WatchdogReattachInformations_ReattachInformations_AlphaId",
|
||||
column: x => x.AlphaId,
|
||||
principalTable: "ReattachInformations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_WatchdogReattachInformations_ReattachInformations_BravoId",
|
||||
column: x => x.BravoId,
|
||||
principalTable: "ReattachInformations",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Restrict);
|
||||
table.ForeignKey(
|
||||
name: "FK_WatchdogReattachInformations_Instances_InstanceId",
|
||||
column: x => x.InstanceId,
|
||||
principalTable: "Instances",
|
||||
principalColumn: "Id",
|
||||
onDelete: ReferentialAction.Cascade);
|
||||
});
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ChatBots_InstanceId_Name",
|
||||
table: "ChatBots",
|
||||
columns: new[] { "InstanceId", "Name" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ChatChannels_ChatSettingsId_DiscordChannelId",
|
||||
table: "ChatChannels",
|
||||
columns: new[] { "ChatSettingsId", "DiscordChannelId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ChatChannels_ChatSettingsId_IrcChannel",
|
||||
table: "ChatChannels",
|
||||
columns: new[] { "ChatSettingsId", "IrcChannel" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CompileJobs_DirectoryName",
|
||||
table: "CompileJobs",
|
||||
column: "DirectoryName");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CompileJobs_JobId",
|
||||
table: "CompileJobs",
|
||||
column: "JobId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_CompileJobs_RevisionInformationId",
|
||||
table: "CompileJobs",
|
||||
column: "RevisionInformationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DreamDaemonSettings_InstanceId",
|
||||
table: "DreamDaemonSettings",
|
||||
column: "InstanceId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_DreamMakerSettings_InstanceId",
|
||||
table: "DreamMakerSettings",
|
||||
column: "InstanceId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Instances_Path",
|
||||
table: "Instances",
|
||||
column: "Path",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InstanceUsers_InstanceId",
|
||||
table: "InstanceUsers",
|
||||
column: "InstanceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_InstanceUsers_UserId_InstanceId",
|
||||
table: "InstanceUsers",
|
||||
columns: new[] { "UserId", "InstanceId" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Jobs_CancelledById",
|
||||
table: "Jobs",
|
||||
column: "CancelledById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Jobs_InstanceId",
|
||||
table: "Jobs",
|
||||
column: "InstanceId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Jobs_StartedById",
|
||||
table: "Jobs",
|
||||
column: "StartedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_ReattachInformations_CompileJobId",
|
||||
table: "ReattachInformations",
|
||||
column: "CompileJobId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RepositorySettings_InstanceId",
|
||||
table: "RepositorySettings",
|
||||
column: "InstanceId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RevInfoTestMerges_RevisionInformationId",
|
||||
table: "RevInfoTestMerges",
|
||||
column: "RevisionInformationId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RevInfoTestMerges_TestMergeId",
|
||||
table: "RevInfoTestMerges",
|
||||
column: "TestMergeId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_RevisionInformations_InstanceId_CommitSha",
|
||||
table: "RevisionInformations",
|
||||
columns: new[] { "InstanceId", "CommitSha" },
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TestMerges_MergedById",
|
||||
table: "TestMerges",
|
||||
column: "MergedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_TestMerges_PrimaryRevisionInformationId",
|
||||
table: "TestMerges",
|
||||
column: "PrimaryRevisionInformationId",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_CanonicalName",
|
||||
table: "Users",
|
||||
column: "CanonicalName",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_CreatedById",
|
||||
table: "Users",
|
||||
column: "CreatedById");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_SystemIdentifier",
|
||||
table: "Users",
|
||||
column: "SystemIdentifier",
|
||||
unique: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WatchdogReattachInformations_AlphaId",
|
||||
table: "WatchdogReattachInformations",
|
||||
column: "AlphaId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WatchdogReattachInformations_BravoId",
|
||||
table: "WatchdogReattachInformations",
|
||||
column: "BravoId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_WatchdogReattachInformations_InstanceId",
|
||||
table: "WatchdogReattachInformations",
|
||||
column: "InstanceId",
|
||||
unique: true);
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
if (migrationBuilder == null)
|
||||
throw new ArgumentNullException(nameof(migrationBuilder));
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChatChannels");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "DreamDaemonSettings");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "DreamMakerSettings");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "InstanceUsers");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RepositorySettings");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RevInfoTestMerges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "WatchdogReattachInformations");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ChatBots");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "TestMerges");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "ReattachInformations");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "CompileJobs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Jobs");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "RevisionInformations");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Users");
|
||||
|
||||
migrationBuilder.DropTable(
|
||||
name: "Instances");
|
||||
}
|
||||
}
|
||||
}
|
||||
+814
@@ -0,0 +1,814 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata;
|
||||
using Tgstation.Server.Host.Database;
|
||||
|
||||
namespace Tgstation.Server.Host.Migrations
|
||||
{
|
||||
[DbContext(typeof(PostgresSqlDatabaseContext))]
|
||||
partial class PostgresSqlDatabaseContextModelSnapshot : ModelSnapshot
|
||||
{
|
||||
protected override void BuildModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn)
|
||||
.HasAnnotation("ProductVersion", "3.1.4")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 63);
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ChannelLimit")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("ConnectionString")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<bool?>("Enabled")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<int>("Provider")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("ReconnectionInterval")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId", "Name")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ChatBots");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<long>("ChatSettingsId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<decimal?>("DiscordChannelId")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("IrcChannel")
|
||||
.HasColumnType("character varying(100)")
|
||||
.HasMaxLength(100);
|
||||
|
||||
b.Property<bool?>("IsAdminChannel")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("IsUpdatesChannel")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("IsWatchdogChannel")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Tag")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("ChatSettingsId", "DiscordChannelId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("ChatSettingsId", "IrcChannel")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("ChatChannels");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("ByondVersion")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<int?>("DMApiMajorVersion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("DMApiMinorVersion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int?>("DMApiPatchVersion")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<Guid?>("DirectoryName")
|
||||
.IsRequired()
|
||||
.HasColumnType("uuid");
|
||||
|
||||
b.Property<string>("DmeName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("JobId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("MinimumSecurityLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Output")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("RevisionInformationId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("DirectoryName");
|
||||
|
||||
b.HasIndex("JobId")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("RevisionInformationId");
|
||||
|
||||
b.ToTable("CompileJobs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<bool?>("AllowWebClient")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("AutoStart")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<long>("HeartbeatSeconds")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("PrimaryPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SecondaryPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("SecurityLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("StartupTimeout")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DreamDaemonSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<int>("ApiValidationPort")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("ApiValidationSecurityLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ProjectName")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("DreamMakerSettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<long?>("AlphaId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("AlphaIsActive")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<long?>("BravoId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("AlphaId");
|
||||
|
||||
b.HasIndex("BravoId");
|
||||
|
||||
b.HasIndex("InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("WatchdogReattachInformations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<long>("AutoUpdateInterval")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("ChatBotLimit")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("ConfigurationType")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<bool?>("Online")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("Path")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Path")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Instances");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<decimal>("ByondRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("ChatBotRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("ConfigurationRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("DreamDaemonRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("DreamMakerRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<decimal>("InstanceUserRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal>("RepositoryRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<long?>("UserId")
|
||||
.IsRequired()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId");
|
||||
|
||||
b.HasIndex("UserId", "InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("InstanceUsers");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<decimal?>("CancelRight")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<decimal?>("CancelRightsType")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<bool?>("Cancelled")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<long?>("CancelledById")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long?>("ErrorCode")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("ExceptionDetails")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTimeOffset?>("StartedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long>("StartedById")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<DateTimeOffset?>("StoppedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CancelledById");
|
||||
|
||||
b.HasIndex("InstanceId");
|
||||
|
||||
b.HasIndex("StartedById");
|
||||
|
||||
b.ToTable("Jobs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("AccessIdentifier")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<long>("CompileJobId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool>("IsPrimary")
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<int>("LaunchSecurityLevel")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("Port")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("ProcessId")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<int>("RebootState")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CompileJobId");
|
||||
|
||||
b.ToTable("ReattachInformations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("AccessToken")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<string>("AccessUser")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<bool?>("AutoUpdatesKeepTestMerges")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("AutoUpdatesSynchronize")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<string>("CommitterEmail")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<string>("CommitterName")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool?>("PostTestMergeComment")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("PushTestMergeCommits")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<bool?>("ShowTestMergeCommitters")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RepositorySettings");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<long>("RevisionInformationId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<long>("TestMergeId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RevisionInformationId");
|
||||
|
||||
b.HasIndex("TestMergeId");
|
||||
|
||||
b.ToTable("RevInfoTestMerges");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("CommitSha")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasMaxLength(40);
|
||||
|
||||
b.Property<long>("InstanceId")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("OriginCommitSha")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasMaxLength(40);
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("InstanceId", "CommitSha")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("RevisionInformations");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
|
||||
{
|
||||
b.Property<long>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<string>("Author")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("BodyAtMerge")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Comment")
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<DateTimeOffset>("MergedAt")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long>("MergedById")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<int>("Number")
|
||||
.HasColumnType("integer");
|
||||
|
||||
b.Property<long?>("PrimaryRevisionInformationId")
|
||||
.IsRequired()
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<string>("PullRequestRevision")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(40)")
|
||||
.HasMaxLength(40);
|
||||
|
||||
b.Property<string>("TitleAtMerge")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("Url")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("MergedById");
|
||||
|
||||
b.HasIndex("PrimaryRevisionInformationId")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("TestMerges");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
|
||||
{
|
||||
b.Property<long?>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("bigint")
|
||||
.HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn);
|
||||
|
||||
b.Property<decimal>("AdministrationRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<string>("CanonicalName")
|
||||
.IsRequired()
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<DateTimeOffset?>("CreatedAt")
|
||||
.IsRequired()
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<long?>("CreatedById")
|
||||
.HasColumnType("bigint");
|
||||
|
||||
b.Property<bool?>("Enabled")
|
||||
.IsRequired()
|
||||
.HasColumnType("boolean");
|
||||
|
||||
b.Property<decimal>("InstanceManagerRights")
|
||||
.HasColumnType("numeric(20,0)");
|
||||
|
||||
b.Property<DateTimeOffset?>("LastPasswordUpdate")
|
||||
.HasColumnType("timestamp with time zone");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.IsRequired()
|
||||
.HasColumnType("character varying(10000)")
|
||||
.HasMaxLength(10000);
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.Property<string>("SystemIdentifier")
|
||||
.HasColumnType("text");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CanonicalName")
|
||||
.IsUnique();
|
||||
|
||||
b.HasIndex("CreatedById");
|
||||
|
||||
b.HasIndex("SystemIdentifier")
|
||||
.IsUnique();
|
||||
|
||||
b.ToTable("Users");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithMany("ChatSettings")
|
||||
.HasForeignKey("InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
|
||||
.WithMany("Channels")
|
||||
.HasForeignKey("ChatSettingsId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Job", "Job")
|
||||
.WithOne()
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
|
||||
.WithMany("CompileJobs")
|
||||
.HasForeignKey("RevisionInformationId")
|
||||
.OnDelete(DeleteBehavior.ClientNoAction)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithOne("DreamDaemonSettings")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithOne("DreamMakerSettings")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.DualReattachInformation", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Alpha")
|
||||
.WithMany()
|
||||
.HasForeignKey("AlphaId");
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.ReattachInformation", "Bravo")
|
||||
.WithMany()
|
||||
.HasForeignKey("BravoId");
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", null)
|
||||
.WithOne("WatchdogReattachInformation")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.DualReattachInformation", "InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithMany("InstanceUsers")
|
||||
.HasForeignKey("InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", null)
|
||||
.WithMany("InstanceUsers")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("CancelledById");
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithMany("Jobs")
|
||||
.HasForeignKey("InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
|
||||
.WithMany()
|
||||
.HasForeignKey("StartedById")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
|
||||
.WithMany()
|
||||
.HasForeignKey("CompileJobId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithOne("RepositorySettings")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
|
||||
.WithMany("ActiveTestMerges")
|
||||
.HasForeignKey("RevisionInformationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
|
||||
.WithMany("RevisonInformations")
|
||||
.HasForeignKey("TestMergeId")
|
||||
.OnDelete(DeleteBehavior.ClientNoAction)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
|
||||
.WithMany("RevisionInformations")
|
||||
.HasForeignKey("InstanceId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
|
||||
.WithMany("TestMerges")
|
||||
.HasForeignKey("MergedById")
|
||||
.OnDelete(DeleteBehavior.Restrict)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
|
||||
.WithOne("PrimaryTestMerge")
|
||||
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
|
||||
{
|
||||
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
|
||||
.WithMany("CreatedUsers")
|
||||
.HasForeignKey("CreatedById");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -9,17 +9,17 @@ using Tgstation.Server.Host.Configuration;
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext{TParentContext}"/> for MySQL
|
||||
/// <see cref="DatabaseContext"/> for MySQL
|
||||
/// </summary>
|
||||
sealed class MySqlDatabaseContext : DatabaseContext<MySqlDatabaseContext>
|
||||
sealed class MySqlDatabaseContext : DatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a <see cref="MySqlDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext"/></param>
|
||||
public MySqlDatabaseContext(DbContextOptions<MySqlDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger<MySqlDatabaseContext> logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
|
||||
{ }
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using Tgstation.Server.Host.Configuration;
|
||||
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext"/> for PostgresSQL.
|
||||
/// </summary>
|
||||
sealed class PostgresSqlDatabaseContext : DatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a <see cref="SqlServerDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext"/></param>
|
||||
public PostgresSqlDatabaseContext(
|
||||
DbContextOptions<PostgresSqlDatabaseContext> dbContextOptions,
|
||||
IOptions<DatabaseConfiguration> databaseConfiguration,
|
||||
IDatabaseSeeder databaseSeeder,
|
||||
ILogger<PostgresSqlDatabaseContext> logger)
|
||||
: base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
|
||||
{ }
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
options.UseNpgsql(DatabaseConfiguration.ConnectionString, x => x.EnableRetryOnFailure());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void ValidateDatabaseType()
|
||||
{
|
||||
if (!Debugger.IsAttached)
|
||||
throw new NotImplementedException("PostgresSQL implementation is not complete yet!");
|
||||
|
||||
if (DatabaseType != DatabaseType.PostgresSql)
|
||||
throw new InvalidOperationException("Invalid DatabaseType for PostgresSqlDatabaseContext!");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,17 +7,17 @@ using Tgstation.Server.Host.Configuration;
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext{TParentContext}"/> for Sqlserver
|
||||
/// <see cref="DatabaseContext"/> for Sqlserver
|
||||
/// </summary>
|
||||
sealed class SqlServerDatabaseContext : DatabaseContext<SqlServerDatabaseContext>
|
||||
sealed class SqlServerDatabaseContext : DatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a <see cref="SqlServerDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext"/></param>
|
||||
public SqlServerDatabaseContext(DbContextOptions<SqlServerDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger<SqlServerDatabaseContext> logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
|
||||
{ }
|
||||
|
||||
@@ -25,7 +25,7 @@ namespace Tgstation.Server.Host.Database
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder options)
|
||||
{
|
||||
base.OnConfiguring(options);
|
||||
options.UseSqlServer(DatabaseConfiguration.ConnectionString);
|
||||
options.UseSqlServer(DatabaseConfiguration.ConnectionString, x => x.EnableRetryOnFailure());
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
|
||||
@@ -9,17 +9,17 @@ using Tgstation.Server.Host.Configuration;
|
||||
namespace Tgstation.Server.Host.Database
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="DatabaseContext{TParentContext}"/> for MySQL
|
||||
/// <see cref="DatabaseContext"/> for MySQL
|
||||
/// </summary>
|
||||
sealed class SqliteDatabaseContext : DatabaseContext<SqliteDatabaseContext>
|
||||
sealed class SqliteDatabaseContext : DatabaseContext
|
||||
{
|
||||
/// <summary>
|
||||
/// Construct a <see cref="MySqlDatabaseContext"/>
|
||||
/// </summary>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
|
||||
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TContext}"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseConfiguration">The <see cref="IOptions{TOptions}"/> of <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="databaseSeeder">The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext"/></param>
|
||||
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="DatabaseContext"/></param>
|
||||
public SqliteDatabaseContext(DbContextOptions<SqliteDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger<SqliteDatabaseContext> logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
|
||||
{ }
|
||||
|
||||
|
||||
@@ -107,5 +107,32 @@ namespace Tgstation.Server.Host.Extensions
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Add middleware for logging the request number.
|
||||
/// </summary>
|
||||
/// <param name="applicationBuilder">The <see cref="IApplicationBuilder"/> to configure.</param>
|
||||
public static void UseRequestCounting(this IApplicationBuilder applicationBuilder)
|
||||
{
|
||||
if (applicationBuilder == null)
|
||||
throw new ArgumentNullException(nameof(applicationBuilder));
|
||||
|
||||
ulong requestCounter = 0;
|
||||
|
||||
applicationBuilder.Use(async (context, next) =>
|
||||
{
|
||||
var logger = GetLogger(context);
|
||||
var requestNumber = ++requestCounter;
|
||||
logger.LogTrace("Starting request #{0}...", requestNumber);
|
||||
try
|
||||
{
|
||||
await next().ConfigureAwait(false);
|
||||
}
|
||||
finally
|
||||
{
|
||||
logger.LogTrace("Finished request #{0}", requestNumber);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,6 +30,7 @@ namespace Tgstation.Server.Host.Extensions
|
||||
|
||||
return databaseContext
|
||||
.CompileJobs
|
||||
.AsQueryable()
|
||||
.Where(x => x.Job.Instance.Id == instance.Id)
|
||||
.OrderByDescending(x => x.Job.StoppedAt)
|
||||
.FirstOrDefaultAsync(cancellationToken);
|
||||
|
||||
@@ -186,7 +186,13 @@ namespace Tgstation.Server.Host.Jobs
|
||||
await databaseContextFactory.UseContext(async databaseContext =>
|
||||
{
|
||||
// mark all jobs as cancelled
|
||||
var badJobs = await databaseContext.Jobs.Where(y => !y.StoppedAt.HasValue).Select(y => y.Id).ToListAsync(cancellationToken).ConfigureAwait(false);
|
||||
var badJobs = await databaseContext
|
||||
.Jobs
|
||||
.AsQueryable()
|
||||
.Where(y => !y.StoppedAt.HasValue)
|
||||
.Select(y => y.Id)
|
||||
.ToListAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
if (badJobs.Count > 0)
|
||||
{
|
||||
logger.LogTrace("Cleaning {0} unfinished jobs...", badJobs.Count);
|
||||
|
||||
@@ -48,9 +48,9 @@ namespace Tgstation.Server.Host.Models
|
||||
OriginCommitSha = OriginCommitSha,
|
||||
PrimaryTestMerge = PrimaryTestMerge?.ToApi(),
|
||||
ActiveTestMerges = ActiveTestMerges.Select(x => x.TestMerge.ToApi()).ToList(),
|
||||
CompileJobs = CompileJobs.Select(x => new Api.Models.CompileJob
|
||||
CompileJobs = CompileJobs.Select(x => new Api.Models.EntityId
|
||||
{
|
||||
Id = x.Id // anti recursion measure
|
||||
Id = x.Id
|
||||
}).ToList()
|
||||
};
|
||||
}
|
||||
|
||||
@@ -55,7 +55,10 @@ namespace Tgstation.Server.Host.Security
|
||||
if (CurrentAuthenticationContext != null)
|
||||
throw new InvalidOperationException("Authentication context has already been loaded");
|
||||
|
||||
var user = await databaseContext.Users.Where(x => x.Id == userId)
|
||||
var user = await databaseContext
|
||||
.Users
|
||||
.AsQueryable()
|
||||
.Where(x => x.Id == userId)
|
||||
.Include(x => x.CreatedBy)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
@@ -85,6 +88,7 @@ namespace Tgstation.Server.Host.Security
|
||||
if (instanceId.HasValue)
|
||||
{
|
||||
instanceUser = await databaseContext.InstanceUsers
|
||||
.AsQueryable()
|
||||
.Where(x => x.UserId == userId && x.InstanceId == instanceId && x.Instance.Online.Value)
|
||||
.Include(x => x.Instance)
|
||||
.FirstOrDefaultAsync(cancellationToken)
|
||||
|
||||
@@ -299,7 +299,10 @@ namespace Tgstation.Server.Host
|
||||
{
|
||||
await eventsTask.ConfigureAwait(false);
|
||||
}
|
||||
catch (OperationCanceledException) { }
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
logger.LogError("Restart timeout hit! Existing DreamDaemon processes will be lost and must be killed manually before being restarted with TGS!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
logger.LogError("Restart handlers error! Exception: {0}", e);
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.Core;
|
||||
using Tgstation.Server.Host.Extensions;
|
||||
using Tgstation.Server.Host.IO;
|
||||
using Tgstation.Server.Host.Setup;
|
||||
@@ -58,8 +60,17 @@ namespace Tgstation.Server.Host
|
||||
}
|
||||
|
||||
var hostBuilder = CreateDefaultBuilder()
|
||||
.ConfigureWebHostDefaults(webHostBuilder =>
|
||||
.ConfigureWebHost(webHostBuilder =>
|
||||
webHostBuilder
|
||||
.UseKestrel(kestrelOptions =>
|
||||
{
|
||||
var serverPortProvider = kestrelOptions.ApplicationServices.GetRequiredService<IServerPortProvider>();
|
||||
kestrelOptions.ListenAnyIP(
|
||||
serverPortProvider.HttpApiPort,
|
||||
listenOptions => listenOptions.Protocols = HttpProtocols.Http1AndHttp2);
|
||||
})
|
||||
.UseIIS()
|
||||
.UseIISIntegration()
|
||||
.UseApplication(postSetupServices)
|
||||
.SuppressStatusMessages(true)
|
||||
.UseShutdownTimeout(TimeSpan.FromMinutes(1)));
|
||||
|
||||
@@ -4,6 +4,7 @@ using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using MySql.Data.MySqlClient;
|
||||
using Newtonsoft.Json;
|
||||
using Npgsql;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Data.Common;
|
||||
@@ -140,7 +141,11 @@ namespace Tgstation.Server.Host.Setup
|
||||
|
||||
do
|
||||
{
|
||||
await console.WriteAsync("API Port (leave blank for default): ", false, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
$"API Port (leave blank for default of {GeneralConfiguration.DefaultApiPort}): ",
|
||||
false,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
var portString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
|
||||
if (String.IsNullOrWhiteSpace(portString))
|
||||
return null;
|
||||
@@ -275,34 +280,38 @@ namespace Tgstation.Server.Host.Setup
|
||||
/// <summary>
|
||||
/// Prompt the user for the <see cref="DatabaseType"/>.
|
||||
/// </summary>
|
||||
/// <param name="firstTime">If this is the user's first time here.</param>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the input <see cref="DatabaseType"/>.</returns>
|
||||
async Task<DatabaseType> PromptDatabaseType(CancellationToken cancellationToken)
|
||||
async Task<DatabaseType> PromptDatabaseType(bool firstTime, CancellationToken cancellationToken)
|
||||
{
|
||||
await console.WriteAsync(String.Empty, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
"NOTE: It is HIGHLY reccommended that TGS runs on a complete relational database, specfically *NOT* Sqlite.",
|
||||
true,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
"Sqlite, by nature cannot perform several DDL operations. Because of this future compatiblility cannot be guaranteed.",
|
||||
true,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
"This means that you may not be able to update to the next minor version of TGS4 without a clean re-installation!",
|
||||
true,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
"Please consider taking the time to set up a relational database if this is meant to be a long-standing server.",
|
||||
true,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await console.WriteAsync(String.Empty, true, cancellationToken).ConfigureAwait(false);
|
||||
if (firstTime)
|
||||
{
|
||||
await console.WriteAsync(String.Empty, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
"NOTE: It is HIGHLY reccommended that TGS runs on a complete relational database, specfically *NOT* Sqlite.",
|
||||
true,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
"Sqlite, by nature cannot perform several DDL operations. Because of this future compatiblility cannot be guaranteed.",
|
||||
true,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
"This means that you may not be able to update to the next minor version of TGS4 without a clean re-installation!",
|
||||
true,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await console.WriteAsync(
|
||||
"Please consider taking the time to set up a relational database if this is meant to be a long-standing server.",
|
||||
true,
|
||||
cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
await console.WriteAsync(String.Empty, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
await asyncDelayer.Delay(TimeSpan.FromSeconds(3), cancellationToken).ConfigureAwait(false);
|
||||
await asyncDelayer.Delay(TimeSpan.FromSeconds(3), cancellationToken).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
await console.WriteAsync("What SQL database type will you be using?", true, cancellationToken).ConfigureAwait(false);
|
||||
do
|
||||
@@ -310,10 +319,13 @@ namespace Tgstation.Server.Host.Setup
|
||||
await console.WriteAsync(
|
||||
String.Format(
|
||||
CultureInfo.InvariantCulture,
|
||||
"Please enter one of {0}, {1}, {2} or {3}: ",
|
||||
"Please enter one of {0}, {1}, {2}, or {3}: ",
|
||||
DatabaseType.MariaDB,
|
||||
DatabaseType.MySql,
|
||||
#pragma warning disable SA1515 // Single-line comment should be preceded by blank line
|
||||
// DatabaseType.PostgresSql,
|
||||
DatabaseType.SqlServer,
|
||||
#pragma warning restore SA1515 // Single-line comment should be preceded by blank line
|
||||
DatabaseType.Sqlite),
|
||||
false,
|
||||
cancellationToken)
|
||||
@@ -332,19 +344,22 @@ namespace Tgstation.Server.Host.Setup
|
||||
/// </summary>
|
||||
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
|
||||
/// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="DatabaseConfiguration"/></returns>
|
||||
#pragma warning disable CA1502 // TODO: Decomplexify
|
||||
async Task<DatabaseConfiguration> ConfigureDatabase(CancellationToken cancellationToken)
|
||||
{
|
||||
bool firstTime = true;
|
||||
do
|
||||
{
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
var databaseConfiguration = new DatabaseConfiguration
|
||||
{
|
||||
DatabaseType = await PromptDatabaseType(cancellationToken).ConfigureAwait(false)
|
||||
DatabaseType = await PromptDatabaseType(firstTime, cancellationToken).ConfigureAwait(false)
|
||||
};
|
||||
firstTime = false;
|
||||
|
||||
string serverAddress = null;
|
||||
uint? mySQLServerPort = null;
|
||||
ushort? serverPort = null;
|
||||
|
||||
bool isSqliteDB = databaseConfiguration.DatabaseType == DatabaseType.Sqlite;
|
||||
if (!isSqliteDB)
|
||||
@@ -353,15 +368,17 @@ namespace Tgstation.Server.Host.Setup
|
||||
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
|
||||
await console.WriteAsync("Enter the server's address and port [<server>:<port> or <server>] (blank for local): ", false, cancellationToken).ConfigureAwait(false);
|
||||
serverAddress = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
|
||||
if (!String.IsNullOrWhiteSpace(serverAddress) && databaseConfiguration.DatabaseType == DatabaseType.SqlServer)
|
||||
if (String.IsNullOrWhiteSpace(serverAddress))
|
||||
serverAddress = null;
|
||||
else if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer)
|
||||
{
|
||||
var match = Regex.Match(serverAddress, @"^(?<server>.+):(?<port>.+)$");
|
||||
if (match.Success)
|
||||
{
|
||||
serverAddress = match.Groups["server"].Value;
|
||||
var portString = match.Groups["port"].Value;
|
||||
if (uint.TryParse(portString, out uint port))
|
||||
mySQLServerPort = port;
|
||||
if (UInt16.TryParse(portString, out var port))
|
||||
serverPort = port;
|
||||
else
|
||||
{
|
||||
await console.WriteAsync($"Failed to parse port \"{portString}\", please try again.", true, cancellationToken).ConfigureAwait(false);
|
||||
@@ -434,53 +451,84 @@ namespace Tgstation.Server.Host.Setup
|
||||
connectionString,
|
||||
databaseConfiguration.DatabaseType);
|
||||
|
||||
if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer)
|
||||
switch (databaseConfiguration.DatabaseType)
|
||||
{
|
||||
var csb = new SqlConnectionStringBuilder
|
||||
{
|
||||
ApplicationName = assemblyInformationProvider.VersionPrefix,
|
||||
DataSource = serverAddress ?? "(local)"
|
||||
};
|
||||
case DatabaseType.SqlServer:
|
||||
{
|
||||
var csb = new SqlConnectionStringBuilder
|
||||
{
|
||||
ApplicationName = assemblyInformationProvider.VersionPrefix,
|
||||
DataSource = serverAddress ?? "(local)"
|
||||
};
|
||||
|
||||
if (useWinAuth)
|
||||
csb.IntegratedSecurity = true;
|
||||
else
|
||||
{
|
||||
csb.UserID = username;
|
||||
csb.Password = password;
|
||||
}
|
||||
if (useWinAuth)
|
||||
csb.IntegratedSecurity = true;
|
||||
else
|
||||
{
|
||||
csb.UserID = username;
|
||||
csb.Password = password;
|
||||
}
|
||||
|
||||
CreateTestConnection(csb.ConnectionString);
|
||||
csb.InitialCatalog = databaseName;
|
||||
databaseConfiguration.ConnectionString = csb.ConnectionString;
|
||||
}
|
||||
else if(databaseConfiguration.DatabaseType == DatabaseType.Sqlite)
|
||||
{
|
||||
var csb = new SqliteConnectionStringBuilder
|
||||
{
|
||||
DataSource = databaseName,
|
||||
Mode = dbExists ? SqliteOpenMode.ReadOnly : SqliteOpenMode.ReadWriteCreate
|
||||
};
|
||||
CreateTestConnection(csb.ConnectionString);
|
||||
csb.InitialCatalog = databaseName;
|
||||
databaseConfiguration.ConnectionString = csb.ConnectionString;
|
||||
}
|
||||
|
||||
CreateTestConnection(csb.ConnectionString);
|
||||
databaseConfiguration.ConnectionString = csb.ConnectionString;
|
||||
}
|
||||
else
|
||||
{
|
||||
// MySQL/MariaDB
|
||||
var csb = new MySqlConnectionStringBuilder
|
||||
{
|
||||
Server = serverAddress ?? "127.0.0.1",
|
||||
UserID = username,
|
||||
Password = password
|
||||
};
|
||||
break;
|
||||
case DatabaseType.MariaDB:
|
||||
case DatabaseType.MySql:
|
||||
{
|
||||
// MySQL/MariaDB
|
||||
var csb = new MySqlConnectionStringBuilder
|
||||
{
|
||||
Server = serverAddress ?? "127.0.0.1",
|
||||
UserID = username,
|
||||
Password = password
|
||||
};
|
||||
|
||||
if (mySQLServerPort.HasValue)
|
||||
csb.Port = mySQLServerPort.Value;
|
||||
if (serverPort.HasValue)
|
||||
csb.Port = serverPort.Value;
|
||||
|
||||
CreateTestConnection(csb.ConnectionString);
|
||||
csb.Database = databaseName;
|
||||
databaseConfiguration.ConnectionString = csb.ConnectionString;
|
||||
CreateTestConnection(csb.ConnectionString);
|
||||
csb.Database = databaseName;
|
||||
databaseConfiguration.ConnectionString = csb.ConnectionString;
|
||||
}
|
||||
|
||||
break;
|
||||
case DatabaseType.Sqlite:
|
||||
{
|
||||
var csb = new SqliteConnectionStringBuilder
|
||||
{
|
||||
DataSource = databaseName,
|
||||
Mode = dbExists ? SqliteOpenMode.ReadOnly : SqliteOpenMode.ReadWriteCreate
|
||||
};
|
||||
|
||||
CreateTestConnection(csb.ConnectionString);
|
||||
databaseConfiguration.ConnectionString = csb.ConnectionString;
|
||||
}
|
||||
|
||||
break;
|
||||
case DatabaseType.PostgresSql:
|
||||
{
|
||||
var csb = new NpgsqlConnectionStringBuilder
|
||||
{
|
||||
ApplicationName = assemblyInformationProvider.VersionPrefix,
|
||||
Host = serverAddress ?? "127.0.0.1",
|
||||
Password = password,
|
||||
Username = username
|
||||
};
|
||||
|
||||
if (serverPort.HasValue)
|
||||
csb.Port = serverPort.Value;
|
||||
|
||||
CreateTestConnection(csb.ConnectionString);
|
||||
csb.Database = databaseName;
|
||||
databaseConfiguration.ConnectionString = csb.ConnectionString;
|
||||
}
|
||||
|
||||
break;
|
||||
default:
|
||||
throw new InvalidOperationException("Invalid DatabaseType!");
|
||||
}
|
||||
|
||||
try
|
||||
@@ -502,6 +550,7 @@ namespace Tgstation.Server.Host.Setup
|
||||
}
|
||||
while (true);
|
||||
}
|
||||
#pragma warning restore CA1502
|
||||
|
||||
/// <summary>
|
||||
/// Prompts the user to create a <see cref="GeneralConfiguration"/>
|
||||
@@ -687,6 +736,7 @@ namespace Tgstation.Server.Host.Setup
|
||||
{
|
||||
await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Configuration complete! Saving to {0}", userConfigFileName), true, cancellationToken).ConfigureAwait(false);
|
||||
|
||||
newGeneralConfiguration.ApiPort = hostingPort ?? GeneralConfiguration.DefaultApiPort;
|
||||
var map = new Dictionary<string, object>()
|
||||
{
|
||||
{ DatabaseConfiguration.Section, databaseConfiguration },
|
||||
@@ -695,18 +745,6 @@ namespace Tgstation.Server.Host.Setup
|
||||
{ ControlPanelConfiguration.Section, controlPanelConfiguration }
|
||||
};
|
||||
|
||||
if (hostingPort.HasValue)
|
||||
map.Add("Kestrel", new
|
||||
{
|
||||
EndPoints = new
|
||||
{
|
||||
Http = new
|
||||
{
|
||||
Url = String.Format(CultureInfo.InvariantCulture, "http://0.0.0.0:{0}", hostingPort)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
var json = JsonConvert.SerializeObject(map, Formatting.Indented);
|
||||
var configBytes = Encoding.UTF8.GetBytes(json);
|
||||
|
||||
|
||||
@@ -48,25 +48,26 @@
|
||||
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.6" />
|
||||
<PackageReference Include="Discord.Net.WebSocket" Version="2.2.0" />
|
||||
<PackageReference Include="LibGit2Sharp" Version="0.27.0-preview-0034" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="3.0.0">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.3">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="3.1.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.3" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.3">
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.4" />
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="3.1.4">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.VisualStudio.Azure.Containers.Tools.Targets" Version="1.10.8" />
|
||||
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
|
||||
<PackageReference Include="Npgsql.EntityFrameworkCore.PostgreSQL" Version="3.1.3" />
|
||||
<PackageReference Include="Octokit" Version="0.47.0" />
|
||||
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="3.1.1" />
|
||||
<!-- If this is updated, be sure to update the reference in the README.md -->
|
||||
@@ -82,10 +83,10 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Newtonsoft" Version="5.4.1" />
|
||||
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
|
||||
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="4.7.0" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.5.1" />
|
||||
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.6.0" />
|
||||
<PackageReference Include="System.Management" Version="4.7.0" />
|
||||
<PackageReference Include="Wangkanai.Detection.Browser" Version="2.0.0" />
|
||||
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="3.0.49" />
|
||||
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="3.0.50" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{
|
||||
"General": {
|
||||
"ApiPort": 5000,
|
||||
"MinimumPasswordLength": 15,
|
||||
"GitHubAccessToken": null,
|
||||
"SetupWizardMode": "AutoDetect",
|
||||
"ByondTopicTimeout": 5000,
|
||||
"RestartTimeout": 10000,
|
||||
"RestartTimeout": 60000,
|
||||
"UseExperimentalWatchdog": false,
|
||||
"UseBasicWatchdogOnWindows": false,
|
||||
"UserLimit": 100,
|
||||
@@ -17,13 +18,6 @@
|
||||
"LogLevel": "Debug",
|
||||
"MicrosoftLogLevel": "Warning"
|
||||
},
|
||||
"Kestrel": {
|
||||
"EndPoints": {
|
||||
"Http": {
|
||||
"Url": "http://0.0.0.0:5000"
|
||||
}
|
||||
}
|
||||
},
|
||||
"Logging": {
|
||||
"IncludeScopes": false,
|
||||
"Debug": {
|
||||
|
||||
@@ -25,6 +25,10 @@
|
||||
|
||||
world.log << "You really shouldn't be able to read this"
|
||||
|
||||
/world/Export(url)
|
||||
log << "Export: [url]"
|
||||
return ..()
|
||||
|
||||
/world/Topic(T, Addr, Master, Keys)
|
||||
world.log << "Topic: [T]"
|
||||
. = HandleTopic(T)
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
/world
|
||||
sleep_offline = FALSE
|
||||
|
||||
/world/New()
|
||||
log << "Initial value of sleep_offline: [sleep_offline]"
|
||||
TgsNew(new /datum/tgs_event_handler/impl, TGS_SECURITY_ULTRASAFE)
|
||||
StartAsync()
|
||||
|
||||
@@ -9,17 +13,16 @@
|
||||
/proc/Run()
|
||||
sleep(60)
|
||||
world.TgsChatBroadcast("World Initialized")
|
||||
world.TgsInitializationComplete()
|
||||
// world.TgsInitializationComplete()
|
||||
|
||||
/world/Topic(T, Addr, Master, Keys)
|
||||
world.log << "Topic: [T]"
|
||||
log << "Topic: [T]"
|
||||
. = HandleTopic(T)
|
||||
world.log << "Response: [.]"
|
||||
log << "Response: [.]"
|
||||
|
||||
/world/proc/HandleTopic(T)
|
||||
TGS_TOPIC
|
||||
|
||||
world.sleep_offline = FALSE
|
||||
TgsChatBroadcast("Recieved non-tgs topic: [T]")
|
||||
|
||||
var/list/data = params2list(T)
|
||||
@@ -40,9 +43,14 @@
|
||||
|
||||
world.TgsChatBroadcast("Recieved event: [json_encode(args)]")
|
||||
|
||||
/world/Export(url)
|
||||
log << "Export: [url]"
|
||||
return ..()
|
||||
|
||||
/proc/RebootAsync()
|
||||
set waitfor = FALSE
|
||||
world.sleep_offline = FALSE
|
||||
world.TgsChatBroadcast("Rebooting after 3 seconds");
|
||||
world.log << "About to sleep. sleep_offline: [world.sleep_offline]"
|
||||
sleep(30)
|
||||
world.log << "Done sleep, calling Reboot"
|
||||
world.Reboot()
|
||||
|
||||
+34
-38
@@ -4,6 +4,7 @@ using Moq;
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Tgstation.Server.Host.System;
|
||||
|
||||
namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
|
||||
{
|
||||
@@ -21,23 +22,23 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
|
||||
[TestMethod]
|
||||
public void TestConstructionAndDisposal()
|
||||
{
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(null, null, 1));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(null, null, null, 1));
|
||||
var mockAss = new Mock<IAssemblyInformationProvider>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockAss.Object, null, null, 1));
|
||||
var mockLogger = new Mock<ILogger<DiscordProvider>>();
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockLogger.Object, null, 1));
|
||||
Assert.ThrowsException<ArgumentNullException>(() => new DiscordProvider(mockAss.Object, mockLogger.Object, null, 1));
|
||||
var mockToken = "asdf";
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new DiscordProvider(mockLogger.Object, mockToken, 0));
|
||||
new DiscordProvider(mockLogger.Object, mockToken, 1).Dispose();
|
||||
Assert.ThrowsException<ArgumentOutOfRangeException>(() => new DiscordProvider(mockAss.Object, mockLogger.Object, mockToken, 0));
|
||||
new DiscordProvider(mockAss.Object, mockLogger.Object, mockToken, 1).Dispose();
|
||||
}
|
||||
|
||||
[TestMethod]
|
||||
public async Task TestConnectWithFakeTokenFails()
|
||||
{
|
||||
var mockLogger = new Mock<ILogger<DiscordProvider>>();
|
||||
using (var provider = new DiscordProvider(mockLogger.Object, "asdf", 1))
|
||||
{
|
||||
Assert.IsFalse(await provider.Connect(default).ConfigureAwait(false));
|
||||
Assert.IsFalse(provider.Connected);
|
||||
}
|
||||
using var provider = new DiscordProvider(Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, "asdf", 1);
|
||||
Assert.IsFalse(await provider.Connect(default).ConfigureAwait(false));
|
||||
Assert.IsFalse(provider.Connected);
|
||||
}
|
||||
|
||||
[Ignore("Broken due to dependency issues after first call to .Connect()")]
|
||||
@@ -49,37 +50,32 @@ namespace Tgstation.Server.Host.Components.Chat.Providers.Tests
|
||||
|
||||
|
||||
var mockLogger = new Mock<ILogger<DiscordProvider>>();
|
||||
using (var provider = new DiscordProvider(mockLogger.Object, testToken1, 1))
|
||||
{
|
||||
Assert.IsFalse(provider.Connected);
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
Assert.IsTrue(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
Assert.IsTrue(provider.Connected);
|
||||
using var provider = new DiscordProvider(Mock.Of<IAssemblyInformationProvider>(), mockLogger.Object, testToken1, 1);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
Assert.IsTrue(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
Assert.IsTrue(provider.Connected);
|
||||
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
|
||||
//now try it with cancellationTokens
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.Cancel();
|
||||
var cancellationToken = cts.Token;
|
||||
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => provider.Connect(cancellationToken)).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
Assert.IsTrue(provider.Connected);
|
||||
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => provider.Disconnect(cancellationToken)).ConfigureAwait(false);
|
||||
Assert.IsTrue(provider.Connected);
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
}
|
||||
|
||||
}
|
||||
//now try it with cancellationTokens
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
var cancellationToken = cts.Token;
|
||||
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => provider.Connect(cancellationToken)).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
Assert.IsTrue(await provider.Connect(default).ConfigureAwait(false));
|
||||
Assert.IsTrue(provider.Connected);
|
||||
await Assert.ThrowsExceptionAsync<OperationCanceledException>(() => provider.Disconnect(cancellationToken)).ConfigureAwait(false);
|
||||
Assert.IsTrue(provider.Connected);
|
||||
await provider.Disconnect(default).ConfigureAwait(false);
|
||||
Assert.IsFalse(provider.Connected);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,14 +48,12 @@ namespace Tgstation.Server.Host.Components.Repository.Tests
|
||||
tempDir,
|
||||
default);
|
||||
|
||||
using (var repo = await TestRepoLoading(tempDir))
|
||||
{
|
||||
var gitObject = repo.Lookup("f636418bf47d238d33b0e4a34f0072b23a8aad0e");
|
||||
Assert.IsNotNull(gitObject);
|
||||
var commit = gitObject.Peel<Commit>();
|
||||
using var repo = await TestRepoLoading(tempDir);
|
||||
var gitObject = repo.Lookup("f636418bf47d238d33b0e4a34f0072b23a8aad0e");
|
||||
Assert.IsNotNull(gitObject);
|
||||
var commit = gitObject.Peel<Commit>();
|
||||
|
||||
Assert.AreEqual("Update Test.md", commit.Message);
|
||||
}
|
||||
Assert.AreEqual("Update Test.md", commit.Message);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
||||
@@ -13,7 +13,7 @@ namespace Tgstation.Server.Host.Core.Tests
|
||||
{
|
||||
var delayer = new AsyncDelayer();
|
||||
var startDelay = delayer.Delay(TimeSpan.FromSeconds(1), default);
|
||||
var checkDelay = Task.Delay(TimeSpan.FromSeconds(1) - TimeSpan.FromMilliseconds(10), default);
|
||||
var checkDelay = Task.Delay(TimeSpan.FromSeconds(1) - TimeSpan.FromMilliseconds(100), default);
|
||||
await startDelay.ConfigureAwait(false);
|
||||
Assert.IsTrue(checkDelay.IsCompleted);
|
||||
}
|
||||
@@ -22,11 +22,9 @@ namespace Tgstation.Server.Host.Core.Tests
|
||||
public async Task TestCancel()
|
||||
{
|
||||
var delayer = new AsyncDelayer();
|
||||
using (var cts = new CancellationTokenSource())
|
||||
{
|
||||
cts.Cancel();
|
||||
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => delayer.Delay(TimeSpan.FromSeconds(1), cts.Token)).ConfigureAwait(false);
|
||||
}
|
||||
using var cts = new CancellationTokenSource();
|
||||
cts.Cancel();
|
||||
await Assert.ThrowsExceptionAsync<TaskCanceledException>(() => delayer.Delay(TimeSpan.FromSeconds(1), cts.Token)).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -127,7 +127,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
new ChatChannel
|
||||
{
|
||||
IsAdminChannel = true,
|
||||
IsUpdatesChannel = false,
|
||||
IsUpdatesChannel = true,
|
||||
IsWatchdogChannel = true,
|
||||
Tag = "butt",
|
||||
DiscordChannelId = channelId
|
||||
@@ -140,7 +140,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
Assert.IsNotNull(updatedBot.Channels);
|
||||
Assert.AreEqual(1, updatedBot.Channels.Count);
|
||||
Assert.AreEqual(true, updatedBot.Channels.First().IsAdminChannel);
|
||||
Assert.AreEqual(false, updatedBot.Channels.First().IsUpdatesChannel);
|
||||
Assert.AreEqual(true, updatedBot.Channels.First().IsUpdatesChannel);
|
||||
Assert.AreEqual(true, updatedBot.Channels.First().IsWatchdogChannel);
|
||||
Assert.AreEqual("butt", updatedBot.Channels.First().Tag);
|
||||
Assert.AreEqual(channelId, updatedBot.Channels.First().DiscordChannelId);
|
||||
|
||||
@@ -29,6 +29,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
public async Task Run(CancellationToken cancellationToken)
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: START WATCHDOG TESTS");
|
||||
// Increase startup timeout, disable heartbeats
|
||||
await instanceClient.DreamDaemon.Update(new DreamDaemon
|
||||
{
|
||||
@@ -46,17 +47,18 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
// await RunLongRunningTestThenUpdate(cancellationToken);
|
||||
// await RunLongRunningTestThenUpdateWithByondVersionSwitch(cancellationToken);
|
||||
|
||||
// Remove this deploy when the above tests are reenabled
|
||||
await DeployTestDme("LongRunning/long_running_test", DreamDaemonSecurity.Trusted, cancellationToken);
|
||||
|
||||
await RunHeartbeatTest(cancellationToken);
|
||||
|
||||
await StartAndLeaveRunning(cancellationToken);
|
||||
global::System.Console.WriteLine("TEST: END WATCHDOG TESTS");
|
||||
}
|
||||
|
||||
async Task RunBasicTest(CancellationToken cancellationToken)
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: WATCHDOG BASIC TEST");
|
||||
var daemonStatus = await DeployTestDme("BasicOperation/basic_operation_test", DreamDaemonSecurity.Ultrasafe, cancellationToken);
|
||||
|
||||
Assert.IsFalse(daemonStatus.Running.Value);
|
||||
@@ -84,6 +86,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
async Task RunHeartbeatTest(CancellationToken cancellationToken)
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: WATCHDOG HEARTBEAT TEST");
|
||||
// enable heartbeats
|
||||
await instanceClient.DreamDaemon.Update(new DreamDaemon
|
||||
{
|
||||
@@ -144,6 +147,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
async Task RunLongRunningTestThenUpdate(CancellationToken cancellationToken)
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: WATCHDOG LONG RUNNING TEST");
|
||||
const string DmeName = "LongRunning/long_running_test";
|
||||
|
||||
var daemonStatus = await DeployTestDme(DmeName, DreamDaemonSecurity.Trusted, cancellationToken);
|
||||
@@ -184,6 +188,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
async Task RunLongRunningTestThenUpdateWithByondVersionSwitch(CancellationToken cancellationToken)
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: WATCHDOG BYOND VERSION UPDATE TEST");
|
||||
var versionToInstall = new Version(511, 1384, 0);
|
||||
var byondInstallJobTask = instanceClient.Byond.SetActiveVersion(
|
||||
new Api.Models.Byond
|
||||
@@ -232,6 +237,7 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
public async Task StartAndLeaveRunning(CancellationToken cancellationToken)
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: WATCHDOG ENDLESS TEST");
|
||||
var dd = await instanceClient.DreamDaemon.Read(cancellationToken);
|
||||
if(dd.ActiveCompileJob == null)
|
||||
await DeployTestDme("LongRunning/long_running_test", DreamDaemonSecurity.Trusted, cancellationToken);
|
||||
@@ -253,18 +259,16 @@ namespace Tgstation.Server.Tests.Instance
|
||||
|
||||
try
|
||||
{
|
||||
global::System.Console.WriteLine("TEST: Sending world reboot topic...");
|
||||
var result = await bts.SendTopic(IPAddress.Loopback, "tgs_integration_test_special_tactics=1", 1337, cancellationToken);
|
||||
Assert.AreEqual("ack", result.StringData);
|
||||
|
||||
await Task.Delay(7000, cancellationToken);
|
||||
await Task.Delay(10000, cancellationToken);
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
throw;
|
||||
}
|
||||
catch
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
async Task<DreamDaemon> DeployTestDme(string dmeName, DreamDaemonSecurity deploymentSecurity, CancellationToken cancellationToken)
|
||||
|
||||
@@ -118,7 +118,7 @@ namespace Tgstation.Server.Tests
|
||||
using var hardTimeoutCts = new CancellationTokenSource();
|
||||
hardTimeoutCts.CancelAfter(new TimeSpan(0, 9, 45));
|
||||
var hardTimeoutCancellationToken = hardTimeoutCts.Token;
|
||||
hardTimeoutCancellationToken.Register(() => Console.WriteLine("TEST TIMEOUT HARD!"));
|
||||
hardTimeoutCancellationToken.Register(() => Console.WriteLine($"[{DateTimeOffset.Now}] TEST TIMEOUT HARD!"));
|
||||
|
||||
using var softTimeoutCts = CancellationTokenSource.CreateLinkedTokenSource(hardTimeoutCancellationToken);
|
||||
softTimeoutCts.CancelAfter(new TimeSpan(0, 9, 15));
|
||||
@@ -127,7 +127,7 @@ namespace Tgstation.Server.Tests
|
||||
softTimeoutCancellationToken.Register(() =>
|
||||
{
|
||||
if (!tooLateForSoftTimeout)
|
||||
Console.WriteLine("TEST TIMEOUT SOFT!");
|
||||
Console.WriteLine($"[{DateTimeOffset.Now}] TEST TIMEOUT SOFT!");
|
||||
});
|
||||
|
||||
using var serverCts = CancellationTokenSource.CreateLinkedTokenSource(softTimeoutCancellationToken);
|
||||
@@ -297,7 +297,7 @@ namespace Tgstation.Server.Tests
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"TEST ERROR: {ex.GetType()} in flight!");
|
||||
Console.WriteLine($"[{DateTimeOffset.Now}] TEST ERROR: {ex}");
|
||||
throw;
|
||||
}
|
||||
finally
|
||||
|
||||
@@ -72,7 +72,7 @@ namespace Tgstation.Server.Tests
|
||||
var args = new List<string>()
|
||||
{
|
||||
String.Format(CultureInfo.InvariantCulture, "Database:DropDatabase={0}", true),
|
||||
String.Format(CultureInfo.InvariantCulture, "Kestrel:EndPoints:Http:Url={0}", UrlString),
|
||||
String.Format(CultureInfo.InvariantCulture, "General:ApiPort={0}", 5010),
|
||||
String.Format(CultureInfo.InvariantCulture, "Database:DatabaseType={0}", DatabaseType),
|
||||
String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString={0}", connectionString),
|
||||
String.Format(CultureInfo.InvariantCulture, "General:SetupWizardMode={0}", SetupWizardMode.Never),
|
||||
|
||||
Reference in New Issue
Block a user