Merge pull request #904 from tgstation/885-SqliteSupport

Add SQLite support
This commit is contained in:
Jordan Brown
2020-03-25 00:24:17 -04:00
committed by GitHub
48 changed files with 2456 additions and 186 deletions
+1
View File
@@ -15,6 +15,7 @@ artifacts/
/tests/DMAPI/travistester.int
/tests/DMAPI/travistester.dmb
/src/Tgstation.Server.Host/appsettings.Development.json
/src/Tgstation.Server.Host/LocalTestDB.sqlite3
/src/Tgstation.Server.Host/wwwroot
/src/Tgstation.Server.Host/ClientApp/node_modules
/src/Tgstation.Server.Host/ClientApp/build
+2 -2
View File
@@ -51,7 +51,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.Client.Tests/Tgstation.Server.Client.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Client.Tests*]*" -output:".\client_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.Client.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.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations*" -output:".\host_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 /p:DebugType=full tests/Tgstation.Server.Host.Tests/Tgstation.Server.Host.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Tests*]* -[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations*" -output:".\host_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.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.Host.Console.Tests/Tgstation.Server.Host.Console.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Host.Console.Tests*]*" -output:".\console_coverage.xml" -oldstyle
@@ -64,7 +64,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.Models.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 /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 -c build/OpenApiValidationSettings.json C:/swagger.json
+11 -3
View File
@@ -20,7 +20,7 @@ $HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Client.T
cd ../Tgstation.Server.Host.Tests
dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/host.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*"
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Host.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/host.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Host.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Database.Migrations.*"
cd ../Tgstation.Server.Host.Watchdog.Tests
@@ -37,7 +37,14 @@ export TGS4_TEST_DATABASE_TYPE=MySql
export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test"
#token set in CI settings
dotnet build -c $CONFIG /p:CopyLocalLockFileAssemblies=true
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.1/Tgstation.Server.Tests.dll --target "dotnet" --targetargs "test -c $CONFIG --no-build" --format opencover --output "../../TestResults/server.xml" --include "[Tgstation.Server*]*" --exclude "[Tgstation.Server.Tests*]*" --exclude "[Tgstation.Server.Host]Tgstation.Server.Host.Models.Migrations.*"
$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.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"
#Disabled due to upstream issues
#$HOME/.dotnet/tools/coverlet bin/$CONFIG/netcoreapp2.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
@@ -46,4 +53,5 @@ 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 server.xml -F integration
bash <(curl -s https://codecov.io/bash) -f servermy.xml -F integration
# bash <(curl -s https://codecov.io/bash) -f serversl.xml -F integration
@@ -18,6 +18,11 @@
/// <summary>
/// Use MariaDB
/// </summary>
MariaDB
MariaDB,
/// <summary>
/// Use Sqlite
/// </summary>
Sqlite,
}
}
@@ -293,6 +293,9 @@ namespace Tgstation.Server.Host.Core
case DatabaseType.SqlServer:
AddTypedContext<SqlServerDatabaseContext>();
break;
case DatabaseType.Sqlite:
AddTypedContext<SqliteDatabaseContext>();
break;
default:
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType));
}
+228 -117
View File
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Hosting;
using Microsoft.Data.Sqlite;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using MySql.Data.MySqlClient;
@@ -8,6 +9,7 @@ using System.Collections.Generic;
using System.Data.Common;
using System.Data.SqlClient;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
@@ -148,6 +150,157 @@ namespace Tgstation.Server.Host.Core
while (true);
}
/// <summary>
/// Ensure a given <paramref name="testConnection"/> works.
/// </summary>
/// <param name="testConnection">The test <see cref="DbConnection"/>.</param>
/// <param name="databaseConfiguration">The <see cref="DatabaseConfiguration"/> may have derived data populated.</param>
/// <param name="databaseName">The database name (or path in the case of a <see cref="DatabaseType.Sqlite"/> database).</param>
/// <param name="dbExists">Whether or not the database exists.</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation.</param>
/// <returns>A <see cref="Task"/> representing the running operation.</returns>
async Task TestDatabaseConnection(
DbConnection testConnection,
DatabaseConfiguration databaseConfiguration,
string databaseName,
bool dbExists,
CancellationToken cancellationToken)
{
bool isSqliteDB = databaseConfiguration.DatabaseType == DatabaseType.Sqlite;
using (testConnection)
{
await console.WriteAsync("Testing connection...", true, cancellationToken).ConfigureAwait(false);
await testConnection.OpenAsync(cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Connection successful!", true, cancellationToken).ConfigureAwait(false);
if (databaseConfiguration.DatabaseType == DatabaseType.MariaDB
|| databaseConfiguration.DatabaseType == DatabaseType.MySql)
{
await console.WriteAsync("Checking MySQL/MariaDB version...", true, cancellationToken).ConfigureAwait(false);
using (var command = testConnection.CreateCommand())
{
command.CommandText = "SELECT VERSION()";
var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false);
var splits = fullVersion.Split('-');
databaseConfiguration.MySqlServerVersion = splits.First();
}
}
if (!isSqliteDB && !dbExists)
{
await console.WriteAsync("Testing create DB permission...", true, cancellationToken).ConfigureAwait(false);
using (var command = testConnection.CreateCommand())
{
// I really don't care about user sanitization here, they want to fuck their own DB? so be it
#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
command.CommandText = $"CREATE DATABASE {databaseName}";
#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
await console.WriteAsync("Success!", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false);
using (var command = testConnection.CreateCommand())
{
#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
command.CommandText = $"DROP DATABASE {databaseName}";
#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
try
{
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception e)
{
await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("This should be okay, but you may want to manually drop the database before continuing!", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Press any key to continue...", true, cancellationToken).ConfigureAwait(false);
await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
}
}
}
}
if (isSqliteDB && !dbExists)
await Task.WhenAll(
console.WriteAsync("Deleting test database file...", true, cancellationToken),
ioManager.DeleteFile(databaseName, cancellationToken)).ConfigureAwait(false);
}
async Task<string> ValidateNonExistantSqliteDBName(string databaseName, CancellationToken cancellationToken)
{
var resolvedPath = ioManager.ResolvePath(databaseName);
try
{
var directoryName = ioManager.GetDirectoryName(resolvedPath);
bool directoryExisted = await ioManager.DirectoryExists(directoryName, cancellationToken).ConfigureAwait(false);
await ioManager.CreateDirectory(directoryName, cancellationToken).ConfigureAwait(false);
try
{
await ioManager.WriteAllBytes(resolvedPath, Array.Empty<byte>(), cancellationToken).ConfigureAwait(false);
}
catch
{
if (!directoryExisted)
await ioManager.DeleteDirectory(directoryName, cancellationToken).ConfigureAwait(false);
throw;
}
}
catch (IOException)
{
return null;
}
if (!Path.IsPathRooted(databaseName))
{
await console.WriteAsync("Note, this relative path (currently) resolves to the following:", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync(resolvedPath, true, cancellationToken).ConfigureAwait(false);
bool writeResolved = await PromptYesNo(
"Would you like to save the relative path in the configuration? If not, the full path will be saved. (y/n): ",
cancellationToken)
.ConfigureAwait(false);
if (writeResolved)
databaseName = resolvedPath;
}
await ioManager.DeleteFile(databaseName, cancellationToken).ConfigureAwait(false);
return databaseName;
}
/// <summary>
/// Prompt the user for the <see cref="DatabaseType"/>.
/// </summary>
/// <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)
{
do
{
await console.WriteAsync(
String.Format(
CultureInfo.InvariantCulture,
"Please enter one of {0}, {1}, or {2}: ",
DatabaseType.MariaDB,
DatabaseType.MySql,
DatabaseType.SqlServer),
false,
cancellationToken)
.ConfigureAwait(false);
var databaseTypeString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
if (Enum.TryParse<DatabaseType>(databaseTypeString, out var databaseType))
return databaseType;
await console.WriteAsync("Invalid database type!", true, cancellationToken).ConfigureAwait(false);
}
while (true);
}
/// <summary>
/// Prompts the user to create a <see cref="DatabaseConfiguration"/>
/// </summary>
@@ -160,69 +313,69 @@ namespace Tgstation.Server.Host.Core
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("What SQL database type will you be using?", true, cancellationToken).ConfigureAwait(false);
var databaseConfiguration = new DatabaseConfiguration();
do
var databaseConfiguration = new DatabaseConfiguration
{
await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Please enter one of {0}, {1}, or {2}: ", DatabaseType.MariaDB, DatabaseType.SqlServer, DatabaseType.MySql), false, cancellationToken).ConfigureAwait(false);
var databaseTypeString = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
if (Enum.TryParse<DatabaseType>(databaseTypeString, out var databaseType))
{
databaseConfiguration.DatabaseType = databaseType;
break;
}
DatabaseType = await PromptDatabaseType(cancellationToken).ConfigureAwait(false)
};
await console.WriteAsync("Invalid database type!", true, cancellationToken).ConfigureAwait(false);
}
while (true);
string serverAddress;
string serverAddress = null;
uint? mySQLServerPort = null;
do
{
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))
bool isSqliteDB = databaseConfiguration.DatabaseType == DatabaseType.Sqlite;
if (!isSqliteDB)
do
{
serverAddress = null;
break;
}
else if (databaseConfiguration.DatabaseType != DatabaseType.SqlServer)
{
var m = Regex.Match(serverAddress, @"^(?<server>.+):(?<port>.+)$");
if (m.Success)
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)
{
serverAddress = m.Groups["server"].Value;
if (uint.TryParse(m.Groups["port"].Value, out uint port))
var match = Regex.Match(serverAddress, @"^(?<server>.+):(?<port>.+)$");
if (match.Success)
{
mySQLServerPort = port;
break;
}
else
{
await console.WriteAsync($@"Failed to parse port ""{m.Groups["port"].Value}"", please try again.", true, cancellationToken).ConfigureAwait(false);
serverAddress = match.Groups["server"].Value;
var portString = match.Groups["port"].Value;
if (uint.TryParse(portString, out uint port))
mySQLServerPort = port;
else
{
await console.WriteAsync($"Failed to parse port \"{portString}\", please try again.", true, cancellationToken).ConfigureAwait(false);
continue;
}
}
}
else break;
break;
}
else break;
}
while (true);
while (true);
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Enter the database name (Can be from previous installation. Otherwise, should not exist): ", false, cancellationToken).ConfigureAwait(false);
await console.WriteAsync($"Enter the database {(isSqliteDB ? "file path" : "name")} (Can be from previous installation. Otherwise, should not exist): ", false, cancellationToken).ConfigureAwait(false);
string databaseName;
bool dbExists = false;
do
{
databaseName = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
if (!String.IsNullOrWhiteSpace(databaseName))
{
if (isSqliteDB)
{
dbExists = await ioManager.FileExists(databaseName, cancellationToken).ConfigureAwait(false);
if (!dbExists)
databaseName = await ValidateNonExistantSqliteDBName(databaseName, cancellationToken).ConfigureAwait(false);
}
else
await PromptYesNo("Does this database already exist? (y/n): ", cancellationToken).ConfigureAwait(false);
}
if (String.IsNullOrWhiteSpace(databaseName))
await console.WriteAsync("Invalid database name!", true, cancellationToken).ConfigureAwait(false);
else
break;
await console.WriteAsync("Invalid database name!", true, cancellationToken).ConfigureAwait(false);
}
while (true);
var dbExists = await PromptYesNo("Does this database already exist? (y/n): ", cancellationToken).ConfigureAwait(false);
bool useWinAuth;
if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer && platformIdentifier.IsWindows)
useWinAuth = await PromptYesNo("Use Windows Authentication? (y/n): ", cancellationToken).ConfigureAwait(false);
@@ -233,27 +386,28 @@ namespace Tgstation.Server.Host.Core
string username = null;
string password = null;
if (!useWinAuth)
{
await console.WriteAsync("Enter username: ", false, cancellationToken).ConfigureAwait(false);
username = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Enter password: ", false, cancellationToken).ConfigureAwait(false);
password = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
}
else
{
await console.WriteAsync("IMPORTANT: If using the service runner, ensure this computer's LocalSystem account has CREATE DATABASE permissions on the target server!", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("The account it uses in MSSQL is usually \"NT AUTHORITY\\SYSTEM\" and the role it needs is usually \"dbcreator\".", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("We'll run a sanity test here, but it won't be indicative of the service's permissions if that is the case", true, cancellationToken).ConfigureAwait(false);
}
if (!isSqliteDB)
if (!useWinAuth)
{
await console.WriteAsync("Enter username: ", false, cancellationToken).ConfigureAwait(false);
username = await console.ReadLineAsync(false, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Enter password: ", false, cancellationToken).ConfigureAwait(false);
password = await console.ReadLineAsync(true, cancellationToken).ConfigureAwait(false);
}
else
{
await console.WriteAsync("IMPORTANT: If using the service runner, ensure this computer's LocalSystem account has CREATE DATABASE permissions on the target server!", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("The account it uses in MSSQL is usually \"NT AUTHORITY\\SYSTEM\" and the role it needs is usually \"dbcreator\".", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("We'll run a sanity test here, but it won't be indicative of the service's permissions if that is the case", true, cancellationToken).ConfigureAwait(false);
}
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
DbConnection testConnection;
void CreateTestConnection(string connectionString)
{
testConnection = dbConnectionFactory.CreateConnection(connectionString, databaseConfiguration.DatabaseType);
}
void CreateTestConnection(string connectionString) =>
testConnection = dbConnectionFactory.CreateConnection(
connectionString,
databaseConfiguration.DatabaseType);
if (databaseConfiguration.DatabaseType == DatabaseType.SqlServer)
{
@@ -262,6 +416,7 @@ namespace Tgstation.Server.Host.Core
ApplicationName = application.VersionPrefix,
DataSource = serverAddress ?? "(local)"
};
if (useWinAuth)
csb.IntegratedSecurity = true;
else
@@ -274,8 +429,20 @@ namespace Tgstation.Server.Host.Core
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);
databaseConfiguration.ConnectionString = csb.ConnectionString;
}
else
{
// MySQL/MariaDB
var csb = new MySqlConnectionStringBuilder
{
Server = serverAddress ?? "127.0.0.1",
@@ -293,63 +460,7 @@ namespace Tgstation.Server.Host.Core
try
{
using (testConnection)
{
await console.WriteAsync("Testing connection...", true, cancellationToken).ConfigureAwait(false);
await testConnection.OpenAsync(cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Connection successful!", true, cancellationToken).ConfigureAwait(false);
if (databaseConfiguration.DatabaseType != DatabaseType.SqlServer)
{
await console.WriteAsync("Checking MySQL/MariaDB version...", true, cancellationToken).ConfigureAwait(false);
using (var command = testConnection.CreateCommand())
{
command.CommandText = "SELECT VERSION()";
var fullVersion = (string)await command.ExecuteScalarAsync(cancellationToken).ConfigureAwait(false);
await console.WriteAsync(String.Format(CultureInfo.InvariantCulture, "Found {0}", fullVersion), true, cancellationToken).ConfigureAwait(false);
var splits = fullVersion.Split('-');
databaseConfiguration.MySqlServerVersion = splits[0];
}
}
if (!dbExists)
{
await console.WriteAsync("Testing create DB permission...", true, cancellationToken).ConfigureAwait(false);
using (var command = testConnection.CreateCommand())
{
// I really don't care about user sanitization here, they want to fuck their own DB? so be it
#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
command.CommandText = $"CREATE DATABASE {databaseName}";
#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
await console.WriteAsync("Success!", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Dropping test database...", true, cancellationToken).ConfigureAwait(false);
using (var command = testConnection.CreateCommand())
{
#pragma warning disable CA2100 // Review SQL queries for security vulnerabilities
command.CommandText = $"DROP DATABASE {databaseName}";
#pragma warning restore CA2100 // Review SQL queries for security vulnerabilities
try
{
await command.ExecuteNonQueryAsync(cancellationToken).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
throw;
}
catch (Exception e)
{
await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("This should be okay, but you may want to manually drop the database before continuing!", true, cancellationToken).ConfigureAwait(false);
await console.WriteAsync("Press any key to continue...", true, cancellationToken).ConfigureAwait(false);
await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false);
}
}
}
}
await TestDatabaseConnection(testConnection, databaseConfiguration, databaseName, dbExists, cancellationToken).ConfigureAwait(false);
return databaseConfiguration;
}
@@ -1,7 +1,8 @@
using MySql.Data.MySqlClient;
using System;
using System;
using System.Data.Common;
using System.Data.SqlClient;
using Microsoft.Data.Sqlite;
using MySql.Data.MySqlClient;
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Database
@@ -28,6 +29,11 @@ namespace Tgstation.Server.Host.Database
{
ConnectionString = connectionString
};
case DatabaseType.Sqlite:
return new SqliteConnection
{
ConnectionString = connectionString
};
default:
throw new ArgumentOutOfRangeException(nameof(databaseType), databaseType, "Invalid DatabaseType!");
}
@@ -10,8 +10,8 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Configuration;
using Tgstation.Server.Host.Database.Migrations;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Models.Migrations;
namespace Tgstation.Server.Host.Database
{
@@ -78,6 +78,11 @@ namespace Tgstation.Server.Host.Database
/// </summary>
protected DatabaseConfiguration DatabaseConfiguration { get; }
/// <summary>
/// Gets a value indicationg whether the MY_ class of migrations should be used instead of the MS_ class
/// </summary>
protected abstract DatabaseType DatabaseType { get; }
/// <summary>
/// The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/>
/// </summary>
@@ -141,7 +146,7 @@ namespace Tgstation.Server.Host.Database
}
/// <inheritdoc />
public async Task Initialize(CancellationToken cancellationToken)
public virtual async Task Initialize(CancellationToken cancellationToken)
{
if (DatabaseConfiguration.DropDatabase)
{
@@ -177,12 +182,6 @@ namespace Tgstation.Server.Host.Database
/// <inheritdoc />
public Task Save(CancellationToken cancellationToken) => SaveChangesAsync(cancellationToken);
/// <summary>
/// If the MY_ class of migrations should be used instead of the MS_ class
/// </summary>
/// <returns><see langword="true"/> if the MY_ class of migrations should be used instead of the MS_ class, <see langword="false"/> otherwise</returns>
protected abstract bool UseMySQLMigrations();
/// <inheritdoc />
public async Task SchemaDowngradeForServerVersion(Version version, CancellationToken cancellationToken)
{
@@ -196,13 +195,52 @@ namespace Tgstation.Server.Host.Database
// Update this with new migrations as they are made
// Always use the MS class
if (version < new Version(4, 0, 2))
{
// Special handling because this is where SQLite was introduced
if (DatabaseType == DatabaseType.Sqlite)
throw new NotSupportedException("Cannot migrate below version 4.0.2.0 while using Sqlite!");
targetMigration = nameof(MSReattachCompileJobRequired);
}
// Uncomment once next migration/version step happens
/*
else if (version < new Version(V_NEXT_MIGRATION))
{
// Special handling because this is where SQLite was introduced
if (DatabaseType == DatabaseType.Sqlite)
targetMigration = nameof(SLAddSqlite);
targetMigration = nameof(V_NEXT_LAST_MIGRATION);
}
*/
if (targetMigration == null)
{
Logger.LogDebug("No down migration required.");
return;
}
if (UseMySQLMigrations())
targetMigration = String.Format(CultureInfo.InvariantCulture, "MY{0}", targetMigration.Substring(2));
string migrationSubstitution;
switch (DatabaseType)
{
case DatabaseType.SqlServer:
// already setup
migrationSubstitution = null;
break;
case DatabaseType.MySql:
case DatabaseType.MariaDB:
migrationSubstitution = "MY{0}";
break;
case DatabaseType.Sqlite:
migrationSubstitution = "SL{0}";
break;
default:
throw new InvalidOperationException($"Invalid DatabaseType: {DatabaseType}");
}
if (migrationSubstitution != null)
targetMigration = String.Format(CultureInfo.InvariantCulture, migrationSubstitution, targetMigration.Substring(2));
// even though it clearly implements it in the DatabaseFacade definition this won't work without casting (╯ಠ益ಠ)╯︵ ┻━┻
var dbServiceProvider = ((IInfrastructure<IServiceProvider>)Database).Instance;
@@ -15,7 +15,13 @@ namespace Tgstation.Server.Host.Database.Design
{
using (var loggerFactory = new LoggerFactory())
{
return new MySqlDatabaseContext(new DbContextOptions<MySqlDatabaseContext>(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher<User>())), loggerFactory.CreateLogger<MySqlDatabaseContext>());
return new MySqlDatabaseContext(
new DbContextOptions<MySqlDatabaseContext>(),
DesignTimeDbContextFactoryHelpers.GetDbContextOptions(),
new DatabaseSeeder(
new CryptographySuite(
new PasswordHasher<User>())),
loggerFactory.CreateLogger<MySqlDatabaseContext>());
}
}
}
@@ -15,7 +15,13 @@ namespace Tgstation.Server.Host.Database.Design
{
using (var loggerFactory = new LoggerFactory())
{
return new SqlServerDatabaseContext(new DbContextOptions<SqlServerDatabaseContext>(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher<User>())), loggerFactory.CreateLogger<SqlServerDatabaseContext>());
return new SqlServerDatabaseContext(
new DbContextOptions<SqlServerDatabaseContext>(),
DesignTimeDbContextFactoryHelpers.GetDbContextOptions(),
new DatabaseSeeder(
new CryptographySuite(
new PasswordHasher<User>())),
loggerFactory.CreateLogger<SqlServerDatabaseContext>());
}
}
}
@@ -0,0 +1,28 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Database.Design
{
/// <inheritdoc />
sealed class SqliteDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SqliteDatabaseContext>
{
/// <inheritdoc />
public SqliteDatabaseContext CreateDbContext(string[] args)
{
using (var loggerFactory = new LoggerFactory())
{
return new SqliteDatabaseContext(
new DbContextOptions<SqliteDatabaseContext>(),
DesignTimeDbContextFactoryHelpers.GetDbContextOptions(),
new DatabaseSeeder(
new CryptographySuite(
new PasswordHasher<User>())),
loggerFactory.CreateLogger<SqliteDatabaseContext>());
}
}
}
}
@@ -5,9 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20180906135553_MSInitialCreate")]
@@ -2,7 +2,7 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// The initial database migration for MSSQL
@@ -3,9 +3,8 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20180906143029_MYInitialCreate")]
@@ -2,7 +2,7 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// The initial database migration for MySQL/MariaDB
@@ -3,9 +3,8 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20180918020726_MYAddMinimumSecurity")]
@@ -2,7 +2,7 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Add the <see cref="Api.Models.Internal.CompileJob.MinimumSecurityLevel"/> and <see cref="DreamMaker.ApiValidationSecurityLevel"/> columns for MySQL/MariaDB
@@ -5,9 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20180918021228_MSAddMinimumSecurity")]
@@ -2,7 +2,7 @@
using System;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Add the <see cref="Api.Models.Internal.CompileJob.MinimumSecurityLevel"/> and <see cref="DreamMaker.ApiValidationSecurityLevel"/> columns for MSSQL
@@ -3,9 +3,8 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20180918204520_MYNullableAndForeignKeyCleanup")]
@@ -1,7 +1,7 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Cleans up of nullable columns and foreign keys MySQL/MariaDB
@@ -5,9 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20180918205224_MSNullableAndForeignKeyCleanup")]
@@ -1,7 +1,7 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Cleans up of nullable columns and foreign keys MSSQL
@@ -5,9 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20180926033145_MSReattachCompileJobRequired")]
@@ -1,10 +1,10 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Marks the <see cref="CompileJob"/>s of <see cref="ReattachInformation"/>s as non-nullable for MSSQL
/// Marks the <see cref="Models.CompileJob"/>s of <see cref="Models.ReattachInformation"/>s as non-nullable for MSSQL
/// </summary>
public partial class MSReattachCompileJobRequired : Migration
{
@@ -3,9 +3,8 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20180926034014_MYReattachCompileJobRequired")]
@@ -1,10 +1,10 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Marks the <see cref="CompileJob"/>s of <see cref="ReattachInformation"/>s as non-nullable for MySQL/MariaDB
/// Marks the <see cref="Models.CompileJob"/>s of <see cref="Models.ReattachInformation"/>s as non-nullable for MySQL/MariaDB
/// </summary>
public partial class MYReattachCompileJobRequired : Migration
{
@@ -5,9 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20181124231534_MSToggleTestmergeComments")]
@@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Add the <see cref="Api.Models.Internal.RepositorySettings.PostTestMergeComment"/> column for MSSQL
@@ -3,9 +3,8 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20181124231549_MYToggleTestmergeComments")]
@@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Add the <see cref="Api.Models.Internal.RepositorySettings.PostTestMergeComment"/> column for MySQL/MariaDB
@@ -5,9 +5,8 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20190219041825_MSFixRevInfoIndex")]
@@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Make commit shas non-unique per Instance for MSSQL
@@ -3,9 +3,8 @@ using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20190219042440_MYFixRevInfoIndex")]
@@ -1,7 +1,7 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Make commit shas non-unique per Instance for MSSQL
@@ -0,0 +1,653 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqliteDatabaseContext))]
[Migration("20200322210825_SLAddSqlite")]
partial class SLAddSqlite
{
/// <summary>
/// Builds the target model
/// </summary>
/// <param name="modelBuilder">The <see cref="ModelBuilder"/> to use</param>
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConnectionString")
.IsRequired();
b.Property<bool?>("Enabled");
b.Property<long>("InstanceId");
b.Property<string>("Name")
.IsRequired();
b.Property<int?>("Provider");
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("ChatSettingsId");
b.Property<ulong?>("DiscordChannelId");
b.Property<string>("IrcChannel");
b.Property<bool?>("IsAdminChannel")
.IsRequired();
b.Property<bool?>("IsUpdatesChannel")
.IsRequired();
b.Property<bool?>("IsWatchdogChannel")
.IsRequired();
b.Property<string>("Tag");
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();
b.Property<string>("ByondVersion")
.IsRequired();
b.Property<Guid?>("DirectoryName")
.IsRequired();
b.Property<string>("DmeName")
.IsRequired();
b.Property<long>("JobId");
b.Property<int>("MinimumSecurityLevel");
b.Property<string>("Output")
.IsRequired();
b.Property<long>("RevisionInformationId");
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();
b.Property<string>("AccessToken");
b.Property<bool?>("AllowWebClient")
.IsRequired();
b.Property<bool?>("AutoStart")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<ushort?>("PrimaryPort")
.IsRequired();
b.Property<int?>("ProcessId");
b.Property<ushort?>("SecondaryPort")
.IsRequired();
b.Property<int>("SecurityLevel");
b.Property<bool?>("SoftRestart")
.IsRequired();
b.Property<bool?>("SoftShutdown")
.IsRequired();
b.Property<uint?>("StartupTimeout")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<ushort?>("ApiValidationPort")
.IsRequired();
b.Property<int>("ApiValidationSecurityLevel");
b.Property<long>("InstanceId");
b.Property<string>("ProjectName");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<uint?>("AutoUpdateInterval")
.IsRequired();
b.Property<int>("ConfigurationType");
b.Property<string>("Name")
.IsRequired();
b.Property<bool?>("Online")
.IsRequired();
b.Property<string>("Path")
.IsRequired();
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ByondRights");
b.Property<ulong>("ChatBotRights");
b.Property<ulong>("ConfigurationRights");
b.Property<ulong>("DreamDaemonRights");
b.Property<ulong>("DreamMakerRights");
b.Property<long>("InstanceId");
b.Property<ulong>("InstanceUserRights");
b.Property<ulong>("RepositoryRights");
b.Property<long?>("UserId")
.IsRequired();
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();
b.Property<ulong?>("CancelRight");
b.Property<ulong?>("CancelRightsType");
b.Property<bool?>("Cancelled")
.IsRequired();
b.Property<long?>("CancelledById");
b.Property<string>("Description")
.IsRequired();
b.Property<string>("ExceptionDetails");
b.Property<long>("InstanceId");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired();
b.Property<long>("StartedById");
b.Property<DateTimeOffset?>("StoppedAt");
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();
b.Property<string>("AccessIdentifier")
.IsRequired();
b.Property<string>("ChatChannelsJson")
.IsRequired();
b.Property<string>("ChatCommandsJson")
.IsRequired();
b.Property<long>("CompileJobId");
b.Property<bool>("IsPrimary");
b.Property<ushort>("Port");
b.Property<int>("ProcessId");
b.Property<int>("RebootState");
b.Property<string>("ServerCommandsJson")
.IsRequired();
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("AccessToken");
b.Property<string>("AccessUser");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired();
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired();
b.Property<string>("CommitterEmail")
.IsRequired();
b.Property<string>("CommitterName")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<bool?>("PostTestMergeComment")
.IsRequired();
b.Property<bool?>("PushTestMergeCommits")
.IsRequired();
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("RevisionInformationId");
b.Property<long>("TestMergeId");
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();
b.Property<string>("CommitSha")
.IsRequired()
.HasMaxLength(40);
b.Property<long>("InstanceId");
b.Property<string>("OriginCommitSha")
.IsRequired()
.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();
b.Property<string>("Author")
.IsRequired();
b.Property<string>("BodyAtMerge")
.IsRequired();
b.Property<string>("Comment");
b.Property<DateTimeOffset>("MergedAt");
b.Property<long>("MergedById");
b.Property<int?>("Number")
.IsRequired();
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired();
b.Property<string>("PullRequestRevision")
.IsRequired();
b.Property<string>("TitleAtMerge")
.IsRequired();
b.Property<string>("Url")
.IsRequired();
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();
b.Property<ulong>("AdministrationRights");
b.Property<string>("CanonicalName")
.IsRequired();
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired();
b.Property<long?>("CreatedById");
b.Property<bool?>("Enabled")
.IsRequired();
b.Property<ulong>("InstanceManagerRights");
b.Property<DateTimeOffset?>("LastPasswordUpdate");
b.Property<string>("Name")
.IsRequired();
b.Property<string>("PasswordHash");
b.Property<string>("SystemIdentifier");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long?>("AlphaId");
b.Property<bool>("AlphaIsActive");
b.Property<long?>("BravoId");
b.Property<long>("InstanceId");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("WatchdogReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade);
});
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.Restrict);
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade);
});
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);
});
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);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.User")
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
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);
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade);
});
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);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", 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")
.WithOne("WatchdogReattachInformation")
.HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,639 @@
// Suppress the too much class coupling warning
#pragma warning disable CA1506
using System;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Database.Migrations
{
/// <summary>
/// Initial migration for SQLite.
/// </summary>
public partial class SLAddSqlite : 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("Sqlite:Autoincrement", true),
Name = table.Column<string>(nullable: false),
Path = table.Column<string>(nullable: false),
Online = table.Column<bool>(nullable: false),
ConfigurationType = table.Column<int>(nullable: false),
AutoUpdateInterval = table.Column<uint>(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("Sqlite:Autoincrement", true),
Enabled = table.Column<bool>(nullable: false),
CreatedAt = table.Column<DateTimeOffset>(nullable: false),
SystemIdentifier = table.Column<string>(nullable: true),
Name = table.Column<string>(nullable: false),
AdministrationRights = table.Column<ulong>(nullable: false),
InstanceManagerRights = table.Column<ulong>(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("Sqlite:Autoincrement", true),
Name = table.Column<string>(nullable: false),
Enabled = table.Column<bool>(nullable: true),
Provider = table.Column<int>(nullable: true),
ConnectionString = table.Column<string>(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("Sqlite:Autoincrement", true),
AllowWebClient = table.Column<bool>(nullable: false),
SecurityLevel = table.Column<int>(nullable: false),
PrimaryPort = table.Column<ushort>(nullable: false),
SecondaryPort = table.Column<ushort>(nullable: false),
StartupTimeout = table.Column<uint>(nullable: false),
AutoStart = table.Column<bool>(nullable: false),
SoftRestart = table.Column<bool>(nullable: false),
SoftShutdown = table.Column<bool>(nullable: false),
ProcessId = table.Column<int>(nullable: true),
AccessToken = table.Column<string>(nullable: true),
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("Sqlite:Autoincrement", true),
ProjectName = table.Column<string>(nullable: true),
ApiValidationPort = table.Column<ushort>(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("Sqlite:Autoincrement", true),
CommitterName = table.Column<string>(nullable: false),
CommitterEmail = table.Column<string>(nullable: false),
AccessUser = table.Column<string>(nullable: true),
AccessToken = table.Column<string>(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("Sqlite:Autoincrement", true),
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("Sqlite:Autoincrement", true),
UserId = table.Column<long>(nullable: false),
InstanceUserRights = table.Column<ulong>(nullable: false),
ByondRights = table.Column<ulong>(nullable: false),
DreamDaemonRights = table.Column<ulong>(nullable: false),
DreamMakerRights = table.Column<ulong>(nullable: false),
RepositoryRights = table.Column<ulong>(nullable: false),
ChatBotRights = table.Column<ulong>(nullable: false),
ConfigurationRights = table.Column<ulong>(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("Sqlite:Autoincrement", true),
Description = table.Column<string>(nullable: false),
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<ulong>(nullable: true),
CancelRight = table.Column<ulong>(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("Sqlite:Autoincrement", true),
IrcChannel = table.Column<string>(nullable: true),
DiscordChannelId = table.Column<ulong>(nullable: true),
IsAdminChannel = table.Column<bool>(nullable: false),
IsWatchdogChannel = table.Column<bool>(nullable: false),
IsUpdatesChannel = table.Column<bool>(nullable: false),
Tag = table.Column<string>(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("Sqlite:Autoincrement", true),
Number = table.Column<int>(nullable: false),
PullRequestRevision = table.Column<string>(nullable: false),
Comment = table.Column<string>(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.Restrict);
});
migrationBuilder.CreateTable(
name: "CompileJobs",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
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)
},
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.Restrict);
table.ForeignKey(
name: "FK_CompileJobs_RevisionInformations_RevisionInformationId",
column: x => x.RevisionInformationId,
principalTable: "RevisionInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RevInfoTestMerges",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
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",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "ReattachInformations",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ChatCommandsJson = table.Column<string>(nullable: false),
ChatChannelsJson = table.Column<string>(nullable: false),
ServerCommandsJson = table.Column<string>(nullable: false),
AccessIdentifier = table.Column<string>(nullable: false),
ProcessId = table.Column<int>(nullable: false),
IsPrimary = table.Column<bool>(nullable: false),
Port = table.Column<ushort>(nullable: false),
RebootState = 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("Sqlite:Autoincrement", true),
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",
table: "ChatBots",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_ChatBots_Name",
table: "ChatBots",
column: "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_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");
}
}
}
@@ -4,7 +4,7 @@ using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
partial class MySqlDatabaseContextModelSnapshot : ModelSnapshot
@@ -6,7 +6,7 @@ using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Models.Migrations
namespace Tgstation.Server.Host.Database.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
partial class SqlServerDatabaseContextModelSnapshot : ModelSnapshot
@@ -0,0 +1,649 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Tgstation.Server.Host.Database;
namespace Tgstation.Server.Host.Migrations
{
[DbContext(typeof(SqliteDatabaseContext))]
partial class SqliteDatabaseContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.2.6-servicing-10079");
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("ConnectionString")
.IsRequired();
b.Property<bool?>("Enabled");
b.Property<long>("InstanceId");
b.Property<string>("Name")
.IsRequired();
b.Property<int?>("Provider");
b.HasKey("Id");
b.HasIndex("InstanceId");
b.HasIndex("Name")
.IsUnique();
b.ToTable("ChatBots");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("ChatSettingsId");
b.Property<ulong?>("DiscordChannelId");
b.Property<string>("IrcChannel");
b.Property<bool?>("IsAdminChannel")
.IsRequired();
b.Property<bool?>("IsUpdatesChannel")
.IsRequired();
b.Property<bool?>("IsWatchdogChannel")
.IsRequired();
b.Property<string>("Tag");
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();
b.Property<string>("ByondVersion")
.IsRequired();
b.Property<Guid?>("DirectoryName")
.IsRequired();
b.Property<string>("DmeName")
.IsRequired();
b.Property<long>("JobId");
b.Property<int>("MinimumSecurityLevel");
b.Property<string>("Output")
.IsRequired();
b.Property<long>("RevisionInformationId");
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();
b.Property<string>("AccessToken");
b.Property<bool?>("AllowWebClient")
.IsRequired();
b.Property<bool?>("AutoStart")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<ushort?>("PrimaryPort")
.IsRequired();
b.Property<int?>("ProcessId");
b.Property<ushort?>("SecondaryPort")
.IsRequired();
b.Property<int>("SecurityLevel");
b.Property<bool?>("SoftRestart")
.IsRequired();
b.Property<bool?>("SoftShutdown")
.IsRequired();
b.Property<uint?>("StartupTimeout")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<ushort?>("ApiValidationPort")
.IsRequired();
b.Property<int>("ApiValidationSecurityLevel");
b.Property<long>("InstanceId");
b.Property<string>("ProjectName");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamMakerSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<uint?>("AutoUpdateInterval")
.IsRequired();
b.Property<int>("ConfigurationType");
b.Property<string>("Name")
.IsRequired();
b.Property<bool?>("Online")
.IsRequired();
b.Property<string>("Path")
.IsRequired();
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<ulong>("ByondRights");
b.Property<ulong>("ChatBotRights");
b.Property<ulong>("ConfigurationRights");
b.Property<ulong>("DreamDaemonRights");
b.Property<ulong>("DreamMakerRights");
b.Property<long>("InstanceId");
b.Property<ulong>("InstanceUserRights");
b.Property<ulong>("RepositoryRights");
b.Property<long?>("UserId")
.IsRequired();
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();
b.Property<ulong?>("CancelRight");
b.Property<ulong?>("CancelRightsType");
b.Property<bool?>("Cancelled")
.IsRequired();
b.Property<long?>("CancelledById");
b.Property<string>("Description")
.IsRequired();
b.Property<string>("ExceptionDetails");
b.Property<long>("InstanceId");
b.Property<DateTimeOffset?>("StartedAt")
.IsRequired();
b.Property<long>("StartedById");
b.Property<DateTimeOffset?>("StoppedAt");
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();
b.Property<string>("AccessIdentifier")
.IsRequired();
b.Property<string>("ChatChannelsJson")
.IsRequired();
b.Property<string>("ChatCommandsJson")
.IsRequired();
b.Property<long>("CompileJobId");
b.Property<bool>("IsPrimary");
b.Property<ushort>("Port");
b.Property<int>("ProcessId");
b.Property<int>("RebootState");
b.Property<string>("ServerCommandsJson")
.IsRequired();
b.HasKey("Id");
b.HasIndex("CompileJobId");
b.ToTable("ReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<string>("AccessToken");
b.Property<string>("AccessUser");
b.Property<bool?>("AutoUpdatesKeepTestMerges")
.IsRequired();
b.Property<bool?>("AutoUpdatesSynchronize")
.IsRequired();
b.Property<string>("CommitterEmail")
.IsRequired();
b.Property<string>("CommitterName")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<bool?>("PostTestMergeComment")
.IsRequired();
b.Property<bool?>("PushTestMergeCommits")
.IsRequired();
b.Property<bool?>("ShowTestMergeCommitters")
.IsRequired();
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("RepositorySettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long>("RevisionInformationId");
b.Property<long>("TestMergeId");
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();
b.Property<string>("CommitSha")
.IsRequired()
.HasMaxLength(40);
b.Property<long>("InstanceId");
b.Property<string>("OriginCommitSha")
.IsRequired()
.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();
b.Property<string>("Author")
.IsRequired();
b.Property<string>("BodyAtMerge")
.IsRequired();
b.Property<string>("Comment");
b.Property<DateTimeOffset>("MergedAt");
b.Property<long>("MergedById");
b.Property<int?>("Number")
.IsRequired();
b.Property<long?>("PrimaryRevisionInformationId")
.IsRequired();
b.Property<string>("PullRequestRevision")
.IsRequired();
b.Property<string>("TitleAtMerge")
.IsRequired();
b.Property<string>("Url")
.IsRequired();
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();
b.Property<ulong>("AdministrationRights");
b.Property<string>("CanonicalName")
.IsRequired();
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired();
b.Property<long?>("CreatedById");
b.Property<bool?>("Enabled")
.IsRequired();
b.Property<ulong>("InstanceManagerRights");
b.Property<DateTimeOffset?>("LastPasswordUpdate");
b.Property<string>("Name")
.IsRequired();
b.Property<string>("PasswordHash");
b.Property<string>("SystemIdentifier");
b.HasKey("Id");
b.HasIndex("CanonicalName")
.IsUnique();
b.HasIndex("CreatedById");
b.ToTable("Users");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd();
b.Property<long?>("AlphaId");
b.Property<bool>("AlphaIsActive");
b.Property<long?>("BravoId");
b.Property<long>("InstanceId");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("WatchdogReattachInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("ChatSettings")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b =>
{
b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings")
.WithMany("Channels")
.HasForeignKey("ChatSettingsId")
.OnDelete(DeleteBehavior.Cascade);
});
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.Restrict);
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("CompileJobs")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade);
});
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);
});
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);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("InstanceUsers")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.User")
.WithMany("InstanceUsers")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade);
});
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);
b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy")
.WithMany()
.HasForeignKey("StartedById")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob")
.WithMany()
.HasForeignKey("CompileJobId")
.OnDelete(DeleteBehavior.Cascade);
});
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);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation")
.WithMany("ActiveTestMerges")
.HasForeignKey("RevisionInformationId")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge")
.WithMany("RevisonInformations")
.HasForeignKey("TestMergeId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b =>
{
b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance")
.WithMany("RevisionInformations")
.HasForeignKey("InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy")
.WithMany("TestMerges")
.HasForeignKey("MergedById")
.OnDelete(DeleteBehavior.Restrict);
b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation")
.WithOne("PrimaryTestMerge")
.HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId")
.OnDelete(DeleteBehavior.Restrict);
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy")
.WithMany("CreatedUsers")
.HasForeignKey("CreatedById");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", 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")
.WithOne("WatchdogReattachInformation")
.HasForeignKey("Tgstation.Server.Host.Models.WatchdogReattachInformation", "InstanceId")
.OnDelete(DeleteBehavior.Cascade);
});
#pragma warning restore 612, 618
}
}
}
@@ -13,6 +13,9 @@ namespace Tgstation.Server.Host.Database
/// </summary>
sealed class MySqlDatabaseContext : DatabaseContext<MySqlDatabaseContext>
{
/// <inheritdoc />
protected override DatabaseType DatabaseType => DatabaseType.MySql;
/// <summary>
/// Construct a <see cref="MySqlDatabaseContext"/>
/// </summary>
@@ -34,12 +37,15 @@ namespace Tgstation.Server.Host.Database
if (stringDeconstructor.Server == "localhost")
Logger.LogWarning("MariaDB/MySQL server address is set to 'localhost'! If there are connection issues, try setting it to '127.0.0.1'!");
if (!String.IsNullOrEmpty(DatabaseConfiguration.MySqlServerVersion))
options.UseMySql(DatabaseConfiguration.ConnectionString, mySqlOptions => mySqlOptions.ServerVersion(Version.Parse(DatabaseConfiguration.MySqlServerVersion), DatabaseConfiguration.DatabaseType == DatabaseType.MariaDB ? ServerType.MariaDb : ServerType.MySql));
options.UseMySql(
DatabaseConfiguration.ConnectionString,
mySqlOptions => mySqlOptions.ServerVersion(
Version.Parse(DatabaseConfiguration.MySqlServerVersion),
DatabaseConfiguration.DatabaseType == DatabaseType.MariaDB
? ServerType.MariaDb
: ServerType.MySql));
else
options.UseMySql(DatabaseConfiguration.ConnectionString);
}
/// <inheritdoc />
protected override bool UseMySQLMigrations() => true;
}
}
@@ -10,6 +10,9 @@ namespace Tgstation.Server.Host.Database
/// </summary>
sealed class SqlServerDatabaseContext : DatabaseContext<SqlServerDatabaseContext>
{
/// <inheritdoc />
protected override DatabaseType DatabaseType => DatabaseType.SqlServer;
/// <summary>
/// Construct a <see cref="SqlServerDatabaseContext"/>
/// </summary>
@@ -26,8 +29,5 @@ namespace Tgstation.Server.Host.Database
base.OnConfiguring(options);
options.UseSqlServer(DatabaseConfiguration.ConnectionString);
}
/// <inheritdoc />
protected override bool UseMySQLMigrations() => false;
}
}
@@ -0,0 +1,39 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Database
{
/// <summary>
/// <see cref="DatabaseContext{TParentContext}"/> for MySQL
/// </summary>
sealed class SqliteDatabaseContext : DatabaseContext<SqliteDatabaseContext>
{
/// <inheritdoc />
protected override DatabaseType DatabaseType => DatabaseType.Sqlite;
/// <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>
public SqliteDatabaseContext(DbContextOptions<SqliteDatabaseContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger<SqliteDatabaseContext> logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger)
{ }
/// <inheritdoc />
public override Task Initialize(CancellationToken cancellationToken) => throw new NotImplementedException("SQLite support currently is incomplete. See tracking issue at https://github.com/tgstation/tgstation-server/issues/885");
/// <inheritdoc />
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
base.OnConfiguring(options);
options.UseSqlite(DatabaseConfiguration.ConnectionString);
}
}
}
@@ -58,6 +58,7 @@
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.2.6" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.2.6" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.2.6">
<PrivateAssets>all</PrivateAssets>
+1 -1
View File
@@ -9,7 +9,7 @@
"UseBasicWatchdogOnWindows": false
},
"FileLogging": {
"Directory": null, //use the default path
"Directory": null,
"Disable": false,
"LogLevel": "Debug",
"MicrosoftLogLevel": "Warning"
@@ -2,7 +2,7 @@ using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading;
@@ -46,6 +46,13 @@ namespace Tgstation.Server.Tests
adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false);
break;
}
catch (HttpRequestException)
{
//migrating, to be expected
if (DateTimeOffset.Now > giveUpAt)
throw;
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
catch (ServiceUnavailableException)
{
//migrating, to be expected
@@ -115,6 +122,13 @@ namespace Tgstation.Server.Tests
adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false);
break;
}
catch (HttpRequestException)
{
//migrating, to be expected
if (DateTimeOffset.Now > giveUpAt)
throw;
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
catch (ServiceUnavailableException)
{
// migrating, to be expected
@@ -142,6 +156,7 @@ namespace Tgstation.Server.Tests
await Assert.ThrowsExceptionAsync<UnauthorizedException>(() => badClient.Version(cancellationToken)).ConfigureAwait(false);
await new AdministrationTest(adminClient.Administration).Run(cancellationToken).ConfigureAwait(false);
await new UsersTest(adminClient.Users).Run(cancellationToken).ConfigureAwait(false);
await new InstanceManagerTest(adminClient.Instances, server.Directory).Run(cancellationToken).ConfigureAwait(false);
}
}
@@ -4,6 +4,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
@@ -90,6 +91,13 @@ namespace Tgstation.Server.Tests
var client = await serverClientFactory.CreateServerClient(Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false);
break;
}
catch (HttpRequestException)
{
//migrating, to be expected
if (DateTimeOffset.Now > giveUpAt)
throw;
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
catch (ServiceUnavailableException)
{
//migrating, to be expected
+60
View File
@@ -0,0 +1,60 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Client;
namespace Tgstation.Server.Tests
{
sealed class UsersTest
{
readonly IUsersClient client;
public UsersTest(IUsersClient client)
{
this.client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task Run(CancellationToken cancellationToken)
{
await TestSpamCreation(cancellationToken).ConfigureAwait(false);
}
public async Task TestSpamCreation(CancellationToken cancellationToken)
{
ICollection<Task<User>> tasks = new List<Task<User>>();
// Careful with this, very easy to overload the thread pool
const int RepeatCount = 100;
ThreadPool.GetMaxThreads(out var defaultMaxWorker, out var defaultMaxCompletion);
ThreadPool.GetMinThreads(out var defaultMinWorker, out var defaultMinCompletion);
try
{
ThreadPool.SetMinThreads(Math.Min(RepeatCount * 4, defaultMaxWorker), Math.Min(RepeatCount * 4, defaultMaxCompletion));
for (int i = 0; i < RepeatCount; ++i)
{
tasks.Add(
client.Create(
new UserUpdate
{
Name = $"SpamTestUser_{i}",
Password = "asdfasdjfhauwiehruiy273894234jhndjkwh"
},
cancellationToken));
}
await Task.WhenAll(tasks).ConfigureAwait(false);
}
finally
{
ThreadPool.SetMinThreads(defaultMinWorker, defaultMinCompletion);
}
Assert.AreEqual(RepeatCount, tasks.Select(task => task.Result.Id).Distinct().Count(), "Did not receive expected number of unique user IDs!");
}
}
}