Merge pull request #594 from Cyberboss/StaticFilesDelete [NugetDeploy]

PR with more commits than a title can explain
This commit is contained in:
Jordan Brown
2018-09-06 11:43:11 -04:00
committed by GitHub
59 changed files with 4687 additions and 355 deletions
+15 -16
View File
@@ -6,6 +6,18 @@ Hello and welcome to /tg/station servers's contributing page. You are here becau
First things first, we want to make it clear how you can contribute (if you've never contributed before), as well as the kinds of powers the team has over your additions, to avoid any unpleasant surprises if your pull request is closed for a reason you didn't foresee.
## Meet the Team
**Headcoder**
The Headcoder is responsible for controlling, adding, and removing maintainers from the project. In addition to filling the role of a normal maintainer, they have sole authority on who becomes a maintainer, as well as who remains a maintainer and who does not.
**Maintainers**
Maintainers are quality control. If a proposed pull request doesn't meet the following specifications, they can request you to change it, or simply just close the pull request. Maintainers are required to give a reason for closing the pull request.
Maintainers can revert your changes if they feel they are not worth maintaining or if they did not live up to the quality specifications.
## Getting Started
/tg/station doesn't have a list of goals and features to add; we instead allow freedom for contributors to suggest and create their ideas for the server. That doesn't mean we aren't determined to squash bugs, which unfortunately pop up a lot due to the deep complexity of the game. Here are some useful starting guides, if you want to contribute or if you want to know what challenges you can tackle with zero knowledge about the game's code structure.
@@ -22,21 +34,11 @@ You can of course, as always, ask for help at [#coderbus](irc://irc.rizon.net/co
### Development Environment
You need the Dotnet SDK to compile the main server and command line programs. In order to build the service version and control panel you also need a .NET 4.7.1 build chain
You need the Dotnet 2.1 SDK to compile the main server and command line programs. In order to build the service version you also need a .NET 4.7.1 build chain
The recommended IDE is visual studio 2017 which has installation options for both of these.
The recommended IDE is Visual Studio 2017 which has installation options for both of these.
## Meet the Team
**Headcoder**
The Headcoder is responsible for controlling, adding, and removing maintainers from the project. In addition to filling the role of a normal maintainer, they have sole authority on who becomes a maintainer, as well as who remains a maintainer and who does not.
**Maintainers**
Maintainers are quality control. If a proposed pull request doesn't meet the following specifications, they can request you to change it, or simply just close the pull request. Maintainers are required to give a reason for closing the pull request.
Maintainers can revert your changes if they feel they are not worth maintaining or if they did not live up to the quality specifications.
In order to run the integration tests you must have the environment variables `TGS4_TEST_DATABASE_TYPE` set to `MySql`, `MariaDB` or `SqlServer` and `TGS4_TEST_CONNECTION_STRING` set appropriately
## Specifications
@@ -150,6 +152,3 @@ Do not add any of the following in a Pull Request or risk getting the PR closed:
* National Socialist Party of Germany content, National Socialist Party of Germany related content, or National Socialist Party of Germany references
Just becuase something isn't on this list doesn't mean that it's acceptable. Use common sense above all else.
## A word on Git
Yes, we know that the files have a tonne of mixed Windows and Linux line endings. Attempts to fix this have been met with less than stellar success, and as such we have decided to give up caring until there comes a time when it matters.
+35 -16
View File
@@ -1,25 +1,44 @@
language: generic
sudo: false
git:
depth: 1
env:
global:
- BYOND_MAJOR="511"
- BYOND_MINOR="1385"
- DMEName="tests/DMAPI/travistester.dme"
cache:
directories:
- $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
matrix:
include:
- env:
- DMAPI=true
- BYOND_MAJOR="511"
- BYOND_MINOR="1385"
- DMEName="tests/DMAPI/travistester.dme"
name: "DMAPI Unit Tests"
language: generic
cache:
directories:
- $HOME/BYOND-${BYOND_MAJOR}.${BYOND_MINOR}
addons:
apt:
packages:
- libc6-i386
- libstdc++6:i386
addons:
apt:
packages:
- libc6-i386
- libstdc++6:i386
- env:
- DMAPI=false
name: "Linux Build"
language: csharp
mono: none
dotnet: 2.1.300
services:
- mysql
cache:
directories:
- $HOME/.nuget/packages:
os:
- linux
- osx
install:
- build/install_byond.sh
- if [ $DMAPI = true ]; then build/install_byond.sh; fi
- if [ $DMAPI = false ]; then dotnet restore tgstation-server.sln; fi
script:
- tests/DMAPI/build_byond.sh
- if [ $DMAPI = true ]; then tests/DMAPI/build_byond.sh || travis_terminate 1; fi
- if [ $DMAPI = false ]; then build/test_core.sh; fi
+15 -8
View File
@@ -27,7 +27,7 @@ Generally, updates force a live tracking of the configured git repo, resetting l
#### Docker
tgstation-server supports running in a docker container on linux systems and is the preferred deployment method to avoid [native dependency hell with libgit2](https://github.com/libgit2/libgit2sharp/issues/1533). The official image repository is located at https://hub.docker.com/r/tgstation/server it can be build locally, however, by running `docker build . -f build/Dockerfile` in the repository root.
tgstation-server supports running in a docker container on linux systems and is the preferred deployment method to avoid [native dependency hell with libgit2](https://github.com/libgit2/libgit2sharp/issues/1533). The official image repository is located at https://hub.docker.com/r/tgstation/server it can be built locally, however, by running `docker build . -f build/Dockerfile` in the repository root.
To create a container run `docker create --restart=always -p <public port>:80 -v /path/to/your/appsettings.Production.json:/config_data -v path/to/your/log/folder:/tgs_logs tgstation/server` with any additional options you desire (i.e. You'll have to expose more ports in order to actually host servers, add a volume to create instances on, and create a volume for the SQLite database if that is what you're using).
@@ -36,17 +36,24 @@ Note that due to the nature of docker. If the container restarts, you will be se
### Configuring
Create an `appsettings.Production.json` file next to `appsettings.json`. This will override the default settings in appsettings.json with your production settings. There are a few keys meant to be changed by hosts:
- `General:LogFileDirectory`: Override the default directory where server logs are stored. Default is C:/ProgramData/tgstation-server/logs on Windows, /usr/share/tgstation-server/logs otherwise
- `General:MinimumPasswordLength`: Minimum password length requirement for database users
- `Logging:LogLevel:Default`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting.
- `Database:DatabaseType`: Can be one of `SqlServer`, `MySql`, or `Sqlite`. Note that, at the time of this writing, there is a [blocking bug with the MySQL DBAL provider](https://bugs.mysql.com/bug.php?id=89855) which prevents its usage
- `Database:ConnectionString`: Connection string for your database. For SQLite this should be in the form `Data Source=/path/to/database.ext`. Otherwise, click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide.
- `General:LogFileDirectory`: Override the default directory where server logs are stored. Default is C:/ProgramData/tgstation-server/logs on Windows, /usr/share/tgstation-server/logs otherwise
- `General:MinimumPasswordLength`: Minimum password length requirement for database users
- `Logging:LogLevel:Default`: Can be one of `Trace`, `Debug`, `Information`, `Warning`, `Error`, or `Critical`. Restricts what is put into the log files. Currently `Debug` is reccommended for help with error reporting.
- `Database:DatabaseType`: Can be one of `SqlServer`, `MariaDB`, or `MySql`
- `Database:MySqlServerVersion`: The version of MySql/MariaDB the database resides on, can be left as null for attempted auto detection. Used by the MySQL/MariaDB provider for selection of [certain features](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql/blob/2.1.1/src/EFCore.MySql/Storage/Internal/ServerVersion.cs) ignore at your own risk. A string in the form `<major>.<minor>.<patch>`
- `Database:ConnectionString`: Connection string for your database. Click [here](https://www.developerfusion.com/tools/sql-connection-string/) for an SQL Server generator or see [here](https://www.connectionstrings.com/mysql/) for a MySQL guide.
### Database Configuration
If you are using an SQLite database just ensure the specified database file doesn't exist for first time setup and you may skip this section.
If using MySQL, our provider library [recommends you set 'utf8mb4' as your default charset](https://github.com/PomeloFoundation/Pomelo.EntityFrameworkCore.MySql#1-recommended-server-charset) disregard at your own risk.
For dedicated SQL servers, the user created for the application will need the privilege to create databases for the first run. Once the initial set of migrations is run, the create right may be revoked.
The user created for the application will need the privilege to create databases on the first run. Once the initial set of migrations is run, the create right may be revoked. The user should maintain DDL rights though for applying future migrations
### Starting
+10 -6
View File
@@ -2,6 +2,8 @@ version: '{build}'
pull_requests:
do_not_increment_build_number: true
environment:
TGS4_TEST_DATABASE_TYPE: SqlServer
TGS4_TEST_CONNECTION_STRING: Server=(local)\SQL2017;Initial Catalog=TGS_Test;User ID=sa;Password=Password12!
repo_token:
secure: lJNGAXwiB5HlWdthz3K4PetqpTG5IEAyRgKaiKxFMQ8HW8CcOjRtB97B05op7BsK
branches:
@@ -24,6 +26,8 @@ cache:
- ~\.nuget\packages -> **\*.csproj
- C:\ProgramData\chocolatey\bin -> appveyor.yml
- C:\ProgramData\chocolatey\lib -> appveyor.yml
services:
- mssql2017
install:
- choco install doxygen.portable codecov graphviz.portable opencover.portable
- nuget restore tgstation-server.sln
@@ -33,16 +37,16 @@ build:
verbosity: minimal
publish_nuget: true
test_script:
- OpenCover.Console.exe -register:user -target:"C:/Program Files/dotnet/dotnet.exe" -targetargs:"test -c %CONFIGURATION% --logger:trx;LogFileName=results.trx /p:DebugType=full tests/Tgstation.Server.Api.Tests/Tgstation.Server.Api.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Api.Tests*]*" -output:".\api_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.Api.Tests/Tgstation.Server.Api.Tests.csproj" -filter:"+[Tgstation.Server*]* -[Tgstation.Server.Api.Tests*]*" -output:".\api_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.Api.Tests\TestResults\results.trx))
- OpenCover.Console.exe -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
- 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 -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*]*" -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*]*" -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 -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
- 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
- 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.Console.Tests\TestResults\results.trx))
- set path=%ProgramFiles(x86)%\Microsoft Visual Studio\2017\TestAgent\Common7\IDE\CommonExtensions\Microsoft\TestWindow;%path%
@@ -50,10 +54,10 @@ test_script:
- vstest.console /logger:trx;LogFileName=results.trx "tests\Tgstation.Server.Host.Service.Tests\bin\%CONFIGURATION%\Tgstation.Server.Host.Service.Tests.dll" /Enablecodecoverage /inIsolation /Platform:x64
- ps: $wc = New-Object 'System.Net.WebClient'
- ps: $wc.UploadFile("https://ci.appveyor.com/api/testresults/mstest/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\TestResults\results.trx))
- OpenCover.Console.exe -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
- 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 -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*]*" -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*]*" -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))
after_test:
-19
View File
@@ -16,25 +16,6 @@ COPY tests/Tgstation.Server.Host.Console.Tests/Tgstation.Server.Host.Console.Tes
RUN dotnet restore -nowarn:MSB3202,nu1503 -p:RestoreUseSkipNonexistentTargets=false
COPY . .
WORKDIR /src/tests/Tgstation.Server.Api.Tests
RUN dotnet test -c Release
WORKDIR /src/tests/Tgstation.Server.Host.Tests
RUN dotnet test -c Release
WORKDIR /src/tests/Tgstation.Server.Host.Watchdog.Tests
RUN dotnet test -c Release
WORKDIR /src/tests/Tgstation.Server.Host.Console.Tests
RUN dotnet test -c Release
WORKDIR /src/tests/Tgstation.Server.Tests
RUN dotnet test -c Release
WORKDIR /src/src/Tgstation.Server.Host.Console
RUN dotnet publish -c Release -o /app
WORKDIR /src/src/Tgstation.Server.Host
RUN dotnet publish -c Release -o /app/lib/Default && mv /app/lib/Default/appsettings* /app
+28
View File
@@ -0,0 +1,28 @@
#!/bin/bash
set -e
cd tests/Tgstation.Server.Api.Tests
dotnet test
cd ../Tgstation.Server.Client.Tests
dotnet test
cd ../Tgstation.Server.Host.Tests
dotnet test
cd ../Tgstation.Server.Host.Watchdog.Tests
dotnet test
cd ../Tgstation.Server.Host.Console.Tests
dotnet test
cd ../Tgstation.Server.Tests
export TGS4_TEST_DATABASE_TYPE=MySql
export TGS4_TEST_CONNECTION_STRING="server=127.0.0.1;uid=root;pwd=;database=tgs_test"
dotnet test
+12 -2
View File
@@ -210,6 +210,8 @@ DELETE "/Instance/{InstanceId}" => OK
Some requests return @ref Tgstation.Server.Api.Models.Job objects. These are long running tasks the server will perform asyncronously and can be polled for status.
Note that the job representing instance move operations must be queried and cancelled differently than others. See the documentation of @ref Tgstation.Server.Api.Models.Instance.MoveJob for details
To list all jobs in an Instance use the following request
I GET "/Job/List" => Array of @ref Tgstation.Server.Api.Models.Job
@@ -416,11 +418,19 @@ To create, write, and delete files use the following request
I POST "/Config" @ref Tgstation.Server.Api.Models.ConfigurationFile => @ref Tgstation.Server.Api.Models.ConfigurationFile
When creating a file, only @ref Tgstation.Server.Api.Models.ConfigurationFile.Path and @ref Tgstation.Server.Api.Models.ConfigurationFile.Content should be specified. Any necessary preceeding directories will be created if possible.
When creating a file, only @ref Tgstation.Server.Api.Models.ConfigurationFile.Path and @ref Tgstation.Server.Api.Models.ConfigurationFile.Content should be specified
If the file already exists, the @ref Tgstation.Server.Api.Models.ConfigurationFile.LastReadHash field must also be present with the last version recieved from the server for that file. If this does not match at the time of the request, 409 will be returned, indicating the file has changed since it was last viewed by the client.
To delete a file set @ref Tgstation.Server.Api.Models.ConfigurationFile.Content to null in the request. If deleting a file leaves a directory empty, they too will be deleted.
To delete a file set @ref Tgstation.Server.Api.Models.ConfigurationFile.Content to null in the request.
To delete an empty directory use the following request
I DELETE "/Config" @ref Tgstation.Server.Api.Models.ConfigurationFile => OK
Where the request @ref Tgstation.Server.Api.Models.ConfigurationFile.Path is set to the directory to delete
@subsection api_dog Watchdog
@@ -46,6 +46,7 @@ namespace Tgstation.Server.Api.Models
/// <summary>
/// The <see cref="Job"/> representing a change of <see cref="Path"/>
/// </summary>
/// <remarks>Due to how <see cref="Job"/>s are children of <see cref="Instance"/>s but moving one requires the <see cref="Instance"/> to be offline, interactions with this <see cref="Job"/> are performed in a non-standard fashion. The <see cref="Job"/> is read by querying the <see cref="Instance"/> again (either via list or ID lookup) and cancelled by making any sort of update to the <see cref="Instance"/>. Once the <see cref="Instance"/> comes back <see cref="Online"/> it can be queried like a normal job</remarks>
[NotMapped]
public Job MoveJob { get; set; }
@@ -23,6 +23,10 @@ namespace Tgstation.Server.Api.Rights
/// <summary>
/// User may list files
/// </summary>
List = 4
List = 4,
/// <summary>
/// User may delete empty folders
/// </summary>
Delete = 8
}
}
@@ -17,7 +17,7 @@
<FileVersion>4.0.0.0</FileVersion>
<PackageTags>json web api tgstation-server tgstation ss13 byond</PackageTags>
<PackageReleaseNotes>Prototype release</PackageReleaseNotes>
<Version>4.0.0.0-preview5</Version>
<Version>4.0.0.0-preview6001</Version>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+3
View File
@@ -197,6 +197,9 @@ namespace Tgstation.Server.Client
/// <inheritdoc />
public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, null, HttpMethod.Delete, instanceId, cancellationToken);
/// <inheritdoc />
public Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest<object>(route, body, HttpMethod.Delete, instanceId, cancellationToken);
/// <inheritdoc />
public Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken) => RunRequest<TResult>(route, null, HttpMethod.Delete, instanceId, cancellationToken);
@@ -19,6 +19,20 @@ namespace Tgstation.Server.Client.Components
/// </summary>
readonly Instance instance;
/// <summary>
/// Sanitize a <see cref="ConfigurationFile"/> path for use in a GET <see cref="Uri"/>
/// </summary>
/// <param name="path">The path to sanitize</param>
/// <returns>The sanitized path</returns>
static string SanitizeGetPath(string path)
{
if (path == null)
path = String.Empty;
if (path.Length == 0 || path[0] != '/')
path = '/' + path;
return path;
}
/// <summary>
/// Construct a <see cref="ConfigurationClient"/>
/// </summary>
@@ -31,22 +45,20 @@ namespace Tgstation.Server.Client.Components
}
/// <inheritdoc />
public Task<IReadOnlyList<ConfigurationFile>> List(string directory, CancellationToken cancellationToken)
{
if (directory == null)
directory = String.Empty;
return apiClient.Read<IReadOnlyList<ConfigurationFile>>(Routes.List(Routes.Configuration) + directory, instance.Id, cancellationToken);
}
public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken);
/// <inheritdoc />
public Task<IReadOnlyList<ConfigurationFile>> List(string directory, CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<ConfigurationFile>>(Routes.List(Routes.Configuration) + SanitizeGetPath(directory), instance.Id, cancellationToken);
/// <inheritdoc />
public Task<ConfigurationFile> Read(ConfigurationFile file, CancellationToken cancellationToken)
{
if (file == null)
throw new ArgumentNullException(nameof(file));
return apiClient.Read<ConfigurationFile>(Routes.Configuration + file.Path, instance.Id, cancellationToken);
return apiClient.Read<ConfigurationFile>(Routes.Configuration + SanitizeGetPath(file.Path), instance.Id, cancellationToken);
}
/// <inheritdoc />
public Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update<ConfigurationFile, ConfigurationFile>(Routes.Configuration, file ?? throw new ArgumentNullException(nameof(file)), instance.Id, cancellationToken);
}
}
}
@@ -31,7 +31,15 @@ namespace Tgstation.Server.Client.Components
/// </summary>
/// <param name="file">The <see cref="ConfigurationFile"/> file to write</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
/// <returns>A <see cref="Task{TResult}"/> resulting in the new <see cref="ConfigurationFile"/></returns>
Task<ConfigurationFile> Write(ConfigurationFile file, CancellationToken cancellationToken);
/// <summary>
/// Delete an empty <paramref name="directory"/>
/// </summary>
/// <param name="directory">The <see cref="ConfigurationFile"/> representing the directory to delete</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken);
}
}
@@ -35,6 +35,7 @@ namespace Tgstation.Server.Client
Task<TResult> Read<TResult>(string route, long instanceId, CancellationToken cancellationToken);
Task<TResult> Update<TBody, TResult>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
Task Delete(string route, long instanceId, CancellationToken cancellationToken);
Task Delete<TBody>(string route, TBody body, long instanceId, CancellationToken cancellationToken);
Task<TResult> Delete<TResult>(string route, long instanceId, CancellationToken cancellationToken);
}
}
@@ -19,12 +19,12 @@ namespace Tgstation.Server.Client
Task<IReadOnlyList<Instance>> List(CancellationToken cancellationToken);
/// <summary>
/// Create an <paramref name="instance"/>
/// Create or attach an <paramref name="instance"/>
/// </summary>
/// <param name="instance">The <see cref="Instance"/> to create. <see cref="Instance.Id"/> will be ignored</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the created <see cref="Instance"/></returns>
Task<Instance> Create(Instance instance, CancellationToken cancellationToken);
/// <returns>A <see cref="Task{TResult}"/> resulting in the created or attached <see cref="Instance"/></returns>
Task<Instance> CreateOrAttach(Instance instance, CancellationToken cancellationToken);
/// <summary>
/// Relocates, renamed, and/or on/offlines an <paramref name="instance"/>
@@ -48,7 +48,7 @@ namespace Tgstation.Server.Client
/// <param name="instance">The <see cref="Instance"/> to delete</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task Delete(Instance instance, CancellationToken cancellationToken);
Task Detach(Instance instance, CancellationToken cancellationToken);
/// <summary>
/// Create an <see cref="IInstanceClient"/> for a given <see cref="Instance"/>
@@ -33,10 +33,10 @@ namespace Tgstation.Server.Client
}
/// <inheritdoc />
public Task<Instance> Create(Instance instance, CancellationToken cancellationToken) => apiClient.Create<Instance, Instance>(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken);
public Task<Instance> CreateOrAttach(Instance instance, CancellationToken cancellationToken) => apiClient.Create<Instance, Instance>(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken);
/// <inheritdoc />
public Task Delete(Instance instance, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
public Task Detach(Instance instance, CancellationToken cancellationToken) => apiClient.Delete(Routes.SetID(Routes.InstanceManager, instance?.Id ?? throw new ArgumentNullException(nameof(instance))), cancellationToken);
/// <inheritdoc />
public Task<IReadOnlyList<Instance>> List(CancellationToken cancellationToken) => apiClient.Read<IReadOnlyList<Instance>>(Routes.List(Routes.InstanceManager), cancellationToken);
@@ -1,5 +1,6 @@
using System;
using System.Net;
using Tgstation.Server.Api.Models;
namespace Tgstation.Server.Client
{
@@ -22,7 +23,11 @@ namespace Tgstation.Server.Client
/// Construct an <see cref="ServerErrorException"/> with <paramref name="html"/>
/// </summary>
/// <param name="html">The raw HTML response of the <see cref="ServerErrorException"/></param>
public ServerErrorException(string html) : base(null, HttpStatusCode.InternalServerError)
public ServerErrorException(string html) : base(new ErrorMessage
{
Message = "An internal server error occurred!",
SeverApiVersion = null
}, HttpStatusCode.InternalServerError)
{
Html = html;
}
@@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<DebugType>Full</DebugType>
<Version>4.0.0.0-preview11</Version>
<Version>4.0.0.0-preview9101</Version>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Authors>Cyberboss</Authors>
<Company>/tg/station 13</Company>
@@ -1,6 +1,6 @@
using Microsoft.AspNetCore.Http;
using System.Threading;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.Models;
namespace Tgstation.Server.Host.Components
{
@@ -12,7 +12,7 @@ namespace Tgstation.Server.Host.Components
/// <summary>
/// Get the <see cref="IInstance"/> associated with given <paramref name="metadata"/>
/// </summary>
/// <param name="metadata">The <see cref="Host.Models.Instance"/> of the desired <see cref="IInstance"/></param>
/// <param name="metadata">The <see cref="Models.Instance"/> of the desired <see cref="IInstance"/></param>
/// <returns>The <see cref="IInstance"/> associated with the given <paramref name="metadata"/></returns>
IInstance GetInstance(Models.Instance metadata);
@@ -28,9 +28,10 @@ namespace Tgstation.Server.Host.Components
/// Offline an <see cref="IInstance"/>
/// </summary>
/// <param name="metadata">The <see cref="Models.Instance"/> of the desired <see cref="IInstance"/></param>
/// <param name="user">The <see cref="User"/> performing the operation</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing the running operation</returns>
Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken);
Task OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken);
/// <summary>
/// Move an <see cref="IInstance"/>
@@ -124,12 +124,21 @@ namespace Tgstation.Server.Host.Components
throw new InvalidOperationException("Cannot move an online instance!");
var oldPath = instance.Path;
await ioManager.CopyDirectory(oldPath, newPath, null, cancellationToken).ConfigureAwait(false);
instance.Path = ioManager.ResolvePath(newPath);
await databaseContextFactory.UseContext(db =>
{
var targetInstance = new Models.Instance
{
Id = instance.Id
};
db.Instances.Attach(targetInstance);
targetInstance.Path = newPath;
return db.Save(cancellationToken);
}).ConfigureAwait(false);
await ioManager.DeleteDirectory(oldPath, cancellationToken).ConfigureAwait(false);
}
/// <inheritdoc />
public async Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken)
public async Task OfflineInstance(Models.Instance metadata, Models.User user, CancellationToken cancellationToken)
{
if (metadata == null)
throw new ArgumentNullException(nameof(metadata));
@@ -143,6 +152,23 @@ namespace Tgstation.Server.Host.Components
}
try
{
//we are the one responsible for cancelling his jobs
var tasks = new List<Task>();
await databaseContextFactory.UseContext(async db =>
{
var jobs = db.Jobs.Where(x => x.Instance.Id == metadata.Id).Select(x => new Models.Job
{
Id = x.Id
}).ToAsyncEnumerable();
await jobs.ForEachAsync(job =>
{
lock (tasks)
tasks.Add(jobManager.CancelJob(job, user, true, cancellationToken));
}, cancellationToken).ConfigureAwait(false);
}).ConfigureAwait(false);
await Task.WhenAll(tasks).ConfigureAwait(false);
await instance.StopAsync(cancellationToken).ConfigureAwait(false);
}
finally
@@ -103,36 +103,40 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <inheritdoc />
public async Task<ServerSideModifications> CopyDMFilesTo(string dmeFile, string destination, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
//just assume no other fs race conditions here
var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken);
var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken);
var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken);
await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask).ConfigureAwait(false);
if (!dmeExistsTask.Result && !headFileExistsTask.Result && !tailFileExistsTask.Result)
return null;
var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, cancellationToken);
if (dmeExistsTask.Result)
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
//just assume no other fs race conditions here
var dmeExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, dmeFile), cancellationToken);
var headFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsHeadFile), cancellationToken);
var tailFileExistsTask = ioManager.FileExists(ioManager.ConcatPath(CodeModificationsSubdirectory, CodeModificationsTailFile), cancellationToken);
await Task.WhenAll(dmeExistsTask, headFileExistsTask, tailFileExistsTask).ConfigureAwait(false);
if (!dmeExistsTask.Result && !headFileExistsTask.Result && !tailFileExistsTask.Result)
return null;
var copyTask = ioManager.CopyDirectory(CodeModificationsSubdirectory, destination, null, cancellationToken);
if (dmeExistsTask.Result)
{
await copyTask.ConfigureAwait(false);
return new ServerSideModifications(null, null, true);
}
if (!headFileExistsTask.Result && !tailFileExistsTask.Result)
{
await copyTask.ConfigureAwait(false);
return null;
}
string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath);
await copyTask.ConfigureAwait(false);
return new ServerSideModifications(null, null, true);
return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false);
}
if (!headFileExistsTask.Result && !tailFileExistsTask.Result)
{
await copyTask.ConfigureAwait(false);
return null;
}
string IncludeLine(string filePath) => String.Format(CultureInfo.InvariantCulture, "#include \"{0}\"", filePath);
await copyTask.ConfigureAwait(false);
return new ServerSideModifications(headFileExistsTask.Result ? IncludeLine(CodeModificationsHeadFile) : null, tailFileExistsTask.Result ? IncludeLine(CodeModificationsTailFile) : null, false);
}
string ValidateConfigRelativePath(string configurationRelativePath)
@@ -140,6 +144,8 @@ namespace Tgstation.Server.Host.Components.StaticFiles
var nullOrEmptyCheck = String.IsNullOrEmpty(configurationRelativePath);
if (nullOrEmptyCheck)
configurationRelativePath = ".";
if (configurationRelativePath[0] == Path.DirectorySeparatorChar || configurationRelativePath[0] == Path.AltDirectorySeparatorChar)
configurationRelativePath = '.' + configurationRelativePath;
var resolved = ioManager.ResolvePath(configurationRelativePath);
var local = !nullOrEmptyCheck ? ioManager.ResolvePath(".") : null;
if (!nullOrEmptyCheck && resolved.Length < local.Length) //.. fuccbois
@@ -180,10 +186,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}));
}
if (systemIdentity == null)
ListImpl();
else
await systemIdentity.RunImpersonated(ListImpl, cancellationToken).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (systemIdentity == null)
ListImpl();
else
await systemIdentity.RunImpersonated(ListImpl, cancellationToken).ConfigureAwait(false);
return result;
}
@@ -240,10 +247,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
}
if (systemIdentity == null)
await Task.Factory.StartNew(ReadImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (systemIdentity == null)
await Task.Factory.StartNew(ReadImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(ReadImpl, cancellationToken).ConfigureAwait(false);
return result;
}
@@ -251,7 +259,6 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <inheritdoc />
public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
async Task SymlinkBase(bool files)
{
Task<IReadOnlyList<string>> task;
@@ -275,7 +282,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
})).ConfigureAwait(false);
}
await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
await Task.WhenAll(SymlinkBase(true), SymlinkBase(false)).ConfigureAwait(false);
}
}
/// <inheritdoc />
@@ -328,13 +339,11 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
}
if (systemIdentity == null)
await Task.Factory.StartNew(WriteImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false);
if (result != null && data == null) //make sure these directories always exist
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (systemIdentity == null)
await Task.Factory.StartNew(WriteImpl, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(WriteImpl, cancellationToken).ConfigureAwait(false);
return result;
}
@@ -347,10 +356,12 @@ namespace Tgstation.Server.Host.Components.StaticFiles
bool? result = null;
void DoCreate() => result = synchronousIOManager.CreateDirectory(path, cancellationToken);
if (systemIdentity == null)
await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false);
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
if (systemIdentity == null)
await Task.Factory.StartNew(DoCreate, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current).ConfigureAwait(false);
else
await systemIdentity.RunImpersonated(DoCreate, cancellationToken).ConfigureAwait(false);
return result.Value;
}
@@ -387,5 +398,24 @@ namespace Tgstation.Server.Host.Components.StaticFiles
}
return true;
}
/// <inheritdoc />
public async Task<bool> DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken)
{
await EnsureDirectories(cancellationToken).ConfigureAwait(false);
var path = ValidateConfigRelativePath(configurationRelativePath);
var result = false;
using (await SemaphoreSlimContext.Lock(semaphore, cancellationToken).ConfigureAwait(false))
{
void CheckDeleteImpl() => result = synchronousIOManager.DeleteDirectory(path);
if (systemIdentity != null)
await systemIdentity.RunImpersonated(CheckDeleteImpl, cancellationToken).ConfigureAwait(false);
else
CheckDeleteImpl();
}
return result;
}
}
}
@@ -57,6 +57,15 @@ namespace Tgstation.Server.Host.Components.StaticFiles
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the directory already existed, <see langword="false"/> otherwise</returns>
Task<bool> CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
/// <summary>
/// Attempt to delete an empty directory at <paramref name="configurationRelativePath"/>
/// </summary>
/// <param name="configurationRelativePath">The path of the empty directory to delete</param>
/// <param name="systemIdentity">The <see cref="ISystemIdentity"/> for the operation. If <see langword="null"/>, the operation will be performed as the user of the <see cref="Core.Application"/></param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns><see langword="true"/> if the directory was empty and deleted, <see langword="false"/> otherwise</returns>
Task<bool> DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken);
/// <summary>
/// Writes to a given <paramref name="configurationRelativePath"/>
/// </summary>
@@ -26,8 +26,13 @@
public string ConnectionString { get; set; }
/// <summary>
/// If the database should use direct table creation instead of automatic migrations. Should not be used in production!
/// If the database should be deleted on application startup. Should not be used in production!
/// </summary>
public bool NoMigrations { get; set; }
public bool DropDatabase { get; set; }
/// <summary>
/// The <see cref="string"/> form of the <see cref="System.Version"/> of a target MySQL/MariaDB server
/// </summary>
public string MySqlServerVersion { get; set; }
}
}
@@ -10,12 +10,12 @@
/// </summary>
SqlServer,
/// <summary>
/// Use MySQL/MariaDB
/// Use MySQL
/// </summary>
MySql,
/// <summary>
/// Use SQLite 3
/// Use MariaDB
/// </summary>
Sqlite
MariaDB
}
}
@@ -9,6 +9,7 @@ using Tgstation.Server.Api;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Api.Rights;
using Tgstation.Server.Host.Components;
using Tgstation.Server.Host.IO;
using Tgstation.Server.Host.Models;
using Tgstation.Server.Host.Security;
@@ -25,29 +26,39 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
readonly IInstanceManager instanceManager;
/// <summary>
/// The <see cref="IIOManager"/> for the <see cref="ConfigurationController"/>
/// </summary>
readonly IIOManager ioManager;
/// <summary>
/// Construct a <see cref="UserController"/>
/// </summary>
/// <param name="databaseContext">The <see cref="IDatabaseContext"/> for the <see cref="ApiController"/></param>
/// <param name="authenticationContextFactory">The <see cref="IAuthenticationContextFactory"/> for the <see cref="ApiController"/></param>
/// <param name="instanceManager">The value of <see cref="instanceManager"/></param>
/// <param name="ioManager">The value of <see cref="ioManager"/></param>
/// <param name="logger">The <see cref="ILogger"/> for the <see cref="ApiController"/></param>
public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger<ConfigurationController> logger) : base(databaseContext, authenticationContextFactory, logger, true)
public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IIOManager ioManager, ILogger<ConfigurationController> logger) : base(databaseContext, authenticationContextFactory, logger, true)
{
this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager));
this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager));
}
/// <summary>
/// If a <see cref="ForbidResult"/> should be returned from actions due to conflicts with one or both of the <see cref="Api.Models.Instance.ConfigurationType"/> or the <see cref="IAuthenticationContext.SystemIdentity"/>
/// If a <see cref="ForbidResult"/> should be returned from actions due to conflicts with one or both of the <see cref="Api.Models.Instance.ConfigurationType"/> or the <see cref="IAuthenticationContext.SystemIdentity"/> or a given <paramref name="path"/> tries to access parent directories
/// </summary>
/// <param name="path">The path to validate if any</param>
/// <returns><see langword="true"/> if a <see cref="ForbidResult"/> should be returned, <see langword="false"/> otherwise</returns>
bool ForbidDueToModeConflicts() => Instance.ConfigurationType == ConfigurationType.Disallowed || (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null);
bool ForbidDueToModeConflicts(string path) => Instance.ConfigurationType == ConfigurationType.Disallowed || (Instance.ConfigurationType == ConfigurationType.SystemIdentityWrite && AuthenticationContext.SystemIdentity == null) || (path != null && ioManager.PathContainsParentAccess(path));
/// <inheritdoc />
[TgsAuthorize(ConfigurationRights.Write)]
public override async Task<IActionResult> Update([FromBody] ConfigurationFile model, CancellationToken cancellationToken)
{
if (ForbidDueToModeConflicts())
if (model == null)
throw new ArgumentNullException(nameof(model));
if (ForbidDueToModeConflicts(model.Path))
return Forbid();
var config = instanceManager.GetInstance(Instance).Configuration;
@@ -88,7 +99,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(ConfigurationRights.Read)]
public async Task<IActionResult> File(string filePath, CancellationToken cancellationToken)
{
if (ForbidDueToModeConflicts())
if (ForbidDueToModeConflicts(filePath))
return Forbid();
try
@@ -123,7 +134,7 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(ConfigurationRights.List)]
public async Task<IActionResult> Directory(string directoryPath, CancellationToken cancellationToken)
{
if (ForbidDueToModeConflicts())
if (ForbidDueToModeConflicts(directoryPath))
return Forbid();
try
@@ -152,7 +163,10 @@ namespace Tgstation.Server.Host.Controllers
[TgsAuthorize(ConfigurationRights.Write)]
public override async Task<IActionResult> Create([FromBody] ConfigurationFile model, CancellationToken cancellationToken)
{
if (ForbidDueToModeConflicts())
if (model == null)
throw new ArgumentNullException(nameof(model));
if (ForbidDueToModeConflicts(model.Path))
return Forbid();
try
@@ -168,5 +182,38 @@ namespace Tgstation.Server.Host.Controllers
return Forbid();
}
}
/// <summary>
/// Deletes an empty <paramref name="directory"/>
/// </summary>
/// <param name="directory">A <see cref="ConfigurationFile"/> representing the path to the directory to delete</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task{TResult}"/> resulting in the <see cref="IActionResult"/> of the operation</returns>
[HttpDelete]
[TgsAuthorize(ConfigurationRights.Delete)]
public async Task<IActionResult> Delete([FromBody] ConfigurationFile directory, CancellationToken cancellationToken)
{
if (directory == null)
throw new ArgumentNullException(nameof(directory));
if (ForbidDueToModeConflicts(directory.Path))
return Forbid();
try
{
return await instanceManager.GetInstance(Instance).Configuration.DeleteDirectory(directory.Path, AuthenticationContext.SystemIdentity, cancellationToken).ConfigureAwait(false) ? (IActionResult)Ok() : Conflict(new ErrorMessage
{
Message = "Directory not empty!"
});
}
catch (NotImplementedException)
{
return StatusCode((int)HttpStatusCode.NotImplemented);
}
catch (UnauthorizedAccessException)
{
return Forbid();
}
}
}
}
@@ -34,6 +34,8 @@ namespace Tgstation.Server.Host.Controllers
/// </summary>
const string InstanceAttachFileName = "TGS4_ALLOW_INSTANCE_ATTACH";
const string MoveInstanceJobPrefix = "Move instance ID ";
/// <summary>
/// The <see cref="IJobManager"/> for the <see cref="InstanceController"/>
/// </summary>
@@ -202,6 +204,11 @@ namespace Tgstation.Server.Host.Controllers
.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (originalModel == default)
return StatusCode((int)HttpStatusCode.Gone);
if (originalModel.Online.Value)
return Conflict(new ErrorMessage
{
Message = "Cannot detach an online instance!"
});
if (originalModel.WatchdogReattachInformation != null)
{
@@ -226,6 +233,18 @@ namespace Tgstation.Server.Host.Controllers
{
var instanceQuery = DatabaseContext.Instances.Where(x => x.Id == model.Id);
var moveJob = await instanceQuery
.SelectMany(x => x.Jobs).
Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix, StringComparison.Ordinal))
.Select(x => new Models.Job
{
Id = x.Id
}).FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (moveJob != default)
//cancel it now
await jobManager.CancelJob(moveJob, AuthenticationContext.User, true, cancellationToken).ConfigureAwait(false);
var usersInstanceUserTask = instanceQuery.SelectMany(x => x.InstanceUsers).Where(x => x.UserId == AuthenticationContext.User.Id).FirstOrDefaultAsync(cancellationToken);
var originalModel = await instanceQuery
@@ -296,7 +315,7 @@ namespace Tgstation.Server.Host.Controllers
try
{
if (originalOnline && model.Online == false)
await instanceManager.OfflineInstance(originalModel, cancellationToken).ConfigureAwait(false);
await instanceManager.OfflineInstance(originalModel, AuthenticationContext.User, cancellationToken).ConfigureAwait(false);
else if (!originalOnline && model.Online == true)
{
//force autostart false here because we don't want any long running jobs right now
@@ -321,26 +340,14 @@ namespace Tgstation.Server.Host.Controllers
{
var job = new Models.Job
{
Description = String.Format(CultureInfo.InvariantCulture, "Move instance ID {0} from {1} to {2}", Instance.Id, Instance.Path, rawPath),
Instance = Instance,
Description = String.Format(CultureInfo.InvariantCulture, MoveInstanceJobPrefix + "{0} from {1} to {2}", originalModel.Id, originalModel.Path, rawPath),
Instance = originalModel,
CancelRightsType = RightsType.InstanceManager,
CancelRight = (ulong)InstanceManagerRights.Relocate,
StartedBy = AuthenticationContext.User
};
await jobManager.RegisterOperation(job, async (paramJob, serviceProvider, progressHandler, ct) =>
{
try
{
await instanceManager.MoveInstance(Instance, rawPath, ct).ConfigureAwait(false);
}
catch
{
originalModel.Path = originalModelPath;
await DatabaseContext.Save(default).ConfigureAwait(false);
throw;
}
}, cancellationToken).ConfigureAwait(false);
await jobManager.RegisterOperation(job, (paramJob, serviceProvider, progressHandler, ct) => instanceManager.MoveInstance(originalModel, rawPath, ct), cancellationToken).ConfigureAwait(false);
api.MoveJob = job.ToApi();
}
@@ -354,8 +361,19 @@ namespace Tgstation.Server.Host.Controllers
IQueryable<Models.Instance> query = DatabaseContext.Instances;
if (!AuthenticationContext.User.InstanceManagerRights.Value.HasFlag(InstanceManagerRights.List))
query = query.Where(x => x.InstanceUsers.Any(y => y.UserId == AuthenticationContext.User.Id)).Where(x => x.InstanceUsers.Any(y => y.AnyRights));
var moveJobTasks = query
.SelectMany(x => x.Jobs)
.Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix, StringComparison.Ordinal))
.Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy)
.Include(x => x.Instance)
.ToListAsync(cancellationToken);
var instances = await query.ToListAsync(cancellationToken).ConfigureAwait(false);
return Json(instances.Select(x => x.ToApi()));
var apis = instances.Select(x => x.ToApi());
var moveJobs = await moveJobTasks.ConfigureAwait(false);
foreach(var I in moveJobs)
apis.Where(x => x.Id == I.Instance.Id).First().MoveJob = I.ToApi(); //if this .First() fails i will personally murder kevinz000 because I just know he is somehow responsible
return Json(apis);
}
/// <inheritdoc />
@@ -368,6 +386,11 @@ namespace Tgstation.Server.Host.Controllers
if (cantList)
query = query.Include(x => x.InstanceUsers);
var moveJobTask = query
.SelectMany(x => x.Jobs)
.Where(x => !x.StoppedAt.HasValue && x.Description.StartsWith(MoveInstanceJobPrefix, StringComparison.Ordinal))
.Include(x => x.StartedBy).ThenInclude(x => x.CreatedBy)
.FirstOrDefaultAsync(cancellationToken);
var instance = await query.FirstOrDefaultAsync(cancellationToken).ConfigureAwait(false);
if (instance == null)
@@ -375,8 +398,10 @@ namespace Tgstation.Server.Host.Controllers
if (cantList && !instance.InstanceUsers.Any(x => x.UserId == AuthenticationContext.User.Id && x.AnyRights))
return Forbid();
return Json(instance.ToApi());
var api = instance.ToApi();
api.MoveJob = (await moveJobTask.ConfigureAwait(false))?.ToApi();
return Json(api);
}
}
}
@@ -71,8 +71,8 @@ namespace Tgstation.Server.Host.Controllers
if (job.CancelRight.HasValue && job.CancelRightsType.HasValue && (AuthenticationContext.GetRight(job.CancelRightsType.Value) & job.CancelRight.Value) == 0)
return Forbid();
await jobManager.CancelJob(job, AuthenticationContext.User, cancellationToken).ConfigureAwait(false);
return Ok();
var cancelled = await jobManager.CancelJob(job, AuthenticationContext.User, false, cancellationToken).ConfigureAwait(false);
return cancelled ? (IActionResult)Accepted() : StatusCode((int)HttpStatusCode.Gone);
}
/// <inheritdoc />
@@ -149,22 +149,20 @@ namespace Tgstation.Server.Host.Core
builder.EnableSensitiveDataLogging();
};
switch (databaseConfiguration?.DatabaseType ?? DatabaseType.Sqlite)
var dbType = databaseConfiguration?.DatabaseType;
switch (dbType)
{
case DatabaseType.MySql:
case DatabaseType.MariaDB:
services.AddDbContext<MySqlDatabaseContext>(ConfigureDatabase);
services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<MySqlDatabaseContext>());
break;
case DatabaseType.Sqlite:
services.AddDbContext<SqliteDatabaseContext>(ConfigureDatabase);
services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<SqliteDatabaseContext>());
break;
case DatabaseType.SqlServer:
services.AddDbContext<SqlServerDatabaseContext>(ConfigureDatabase);
services.AddScoped<IDatabaseContext>(x => x.GetRequiredService<SqlServerDatabaseContext>());
break;
default:
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}!", nameof(DatabaseType)));
throw new InvalidOperationException(String.Format(CultureInfo.InvariantCulture, "Invalid {0}: {1}!", nameof(DatabaseType), dbType));
}
services.AddScoped<IAuthenticationContextFactory, AuthenticationContextFactory>();
@@ -1,5 +1,4 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Hosting;
using System;
using System.Threading;
using System.Threading.Tasks;
@@ -31,8 +30,9 @@ namespace Tgstation.Server.Host.Core
/// </summary>
/// <param name="job">The <see cref="Job"/> to cancel</param>
/// <param name="user">The <see cref="User"/> who cancelled the <paramref name="job"/></param>
/// <param name="blocking">If the operation should wait until the job exits before completing</param>
/// <param name="cancellationToken">The <see cref="CancellationToken"/> for the operation</param>
/// <returns>A <see cref="Task"/> representing a running operation</returns>
Task CancelJob(Job job, User user, CancellationToken cancellationToken);
/// <returns>A <see cref="Task{TResult}"/> resulting in <see langword="true"/> if the <paramref name="job"/> was cancelled, <see langword="false"/> if it couldn't be found</returns>
Task<bool> CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken);
}
}
+15 -2
View File
@@ -187,13 +187,23 @@ namespace Tgstation.Server.Host.Core
}
/// <inheritdoc />
public async Task CancelJob(Job job, User user, CancellationToken cancellationToken)
public async Task<bool> CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken)
{
if (job == null)
throw new ArgumentNullException(nameof(job));
if (user == null)
throw new ArgumentNullException(nameof(user));
CheckGetJob(job).Cancel(); //this will ensure the db update is only done once
JobHandler handler;
try
{
handler = CheckGetJob(job);
}
catch (InvalidOperationException)
{
//this is fine
return false;
}
handler.Cancel(); //this will ensure the db update is only done once
using (var scope = serviceProvider.CreateScope())
{
var databaseContext = scope.ServiceProvider.GetRequiredService<IDatabaseContext>();
@@ -205,6 +215,9 @@ namespace Tgstation.Server.Host.Core
//let either startup or cancellation set job.cancelled
await databaseContext.Save(cancellationToken).ConfigureAwait(false);
}
if (blocking)
await handler.Wait(cancellationToken).ConfigureAwait(false);
return true;
}
/// <inheritdoc />
@@ -310,5 +310,8 @@ namespace Tgstation.Server.Host.IO
using (var archive = new ZipArchive(ms, ZipArchiveMode.Read))
archive.ExtractToDirectory(path);
}, cancellationToken, TaskCreationOptions.LongRunning, TaskScheduler.Current);
/// <inheritdoc />
public bool PathContainsParentAccess(string path) => path?.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Any(x => x == "..") ?? throw new ArgumentNullException(nameof(path));
}
}
@@ -31,6 +31,13 @@ namespace Tgstation.Server.Host.IO
/// <returns>The file name portion of <paramref name="path"/></returns>
string GetFileNameWithoutExtension(string path);
/// <summary>
/// Check if a <paramref name="path"/> contains the '..' parent directory accessor
/// </summary>
/// <param name="path">The path to check</param>
/// <returns><see langword="true"/> if <paramref name="path"/> contains a '..' accessor, <see langword="false"/> otherwise</returns>
bool PathContainsParentAccess(string path);
/// <summary>
/// Copies a directory from <paramref name="src"/> to <paramref name="dest"/>
/// </summary>
@@ -39,6 +39,13 @@ namespace Tgstation.Server.Host.IO
/// <returns>A <see cref="byte"/> array representing the contents of the file at <paramref name="path"/></returns>
byte[] ReadFile(string path);
/// <summary>
/// Deletes a directory at <paramref name="path"/> if it's empty
/// </summary>
/// <param name="path">The path of the directory to delete</param>
/// <returns><see langword="true"/> if the directory does not exist or is empty and was deleted. <see langword="false"/> otherwise</returns>
bool DeleteDirectory(string path);
/// <summary>
/// Write <paramref name="data"/> to a file at a given <paramref name="path"/>.
/// </summary>
@@ -21,6 +21,22 @@ namespace Tgstation.Server.Host.IO
return false;
}
/// <inheritdoc />
public bool DeleteDirectory(string path)
{
if (File.Exists(path))
return false;
if (!Directory.Exists(path))
return true;
if (Directory.EnumerateFileSystemEntries(path).Any())
return false;
Directory.Delete(path);
return true;
}
/// <inheritdoc />
public IEnumerable<string> GetDirectories(string path, CancellationToken cancellationToken)
{
@@ -88,7 +104,7 @@ namespace Tgstation.Server.Host.IO
using (var sha1 = new SHA1Managed())
#pragma warning restore CA5350 // Do not use insecure cryptographic algorithm SHA1.
{
string GetSha1(byte[] dataToHash) => dataToHash.Length != 0 ? String.Join("", sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null;
string GetSha1(byte[] dataToHash) => dataToHash != null && dataToHash.Length != 0 ? String.Join("", sha1.ComputeHash(dataToHash).Select(b => b.ToString("x2", CultureInfo.InvariantCulture))) : null;
var originalSha1 = GetSha1(originalBytes);
if (originalSha1 != sha1InOut)
{
@@ -109,16 +125,7 @@ namespace Tgstation.Server.Host.IO
}
}
if (data == null)
{
File.Delete(path);
if (!cancellationToken.IsCancellationRequested)
//delete the entire folder if possible
try
{
Directory.Delete(directory);
}
catch (IOException) { }
}
return true;
}
}
@@ -66,15 +66,10 @@ namespace Tgstation.Server.Host.Models
/// </summary>
protected ILogger Logger { get; }
/// <summary>
/// The connection string for the <see cref="DatabaseContext{TParentContext}"/>
/// </summary>
protected string ConnectionString => databaseConfiguration.ConnectionString;
/// <summary>
/// The <see cref="DatabaseConfiguration"/> for the <see cref="DatabaseContext{TParentContext}"/>
/// </summary>
readonly DatabaseConfiguration databaseConfiguration;
protected DatabaseConfiguration DatabaseConfiguration { get; }
/// <summary>
/// The <see cref="IDatabaseSeeder"/> for the <see cref="DatabaseContext{TParentContext}"/>
@@ -85,12 +80,12 @@ namespace Tgstation.Server.Host.Models
/// Construct a <see cref="DatabaseContext{TParentContext}"/>
/// </summary>
/// <param name="dbContextOptions">The <see cref="DbContextOptions{TParentContext}"/> for the <see cref="DatabaseContext{TParentContext}"/></param>
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="databaseConfiguration"/></param>
/// <param name="databaseConfigurationOptions">The <see cref="IOptions{TOptions}"/> containing the value of <see cref="DatabaseConfiguration"/></param>
/// <param name="databaseSeeder">The value of <see cref="databaseSeeder"/></param>
/// <param name="logger">The value of <see cref="Logger"/></param>
public DatabaseContext(DbContextOptions<TParentContext> dbContextOptions, IOptions<DatabaseConfiguration> databaseConfigurationOptions, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions)
{
databaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
DatabaseConfiguration = databaseConfigurationOptions?.Value ?? throw new ArgumentNullException(nameof(databaseConfigurationOptions));
this.databaseSeeder = databaseSeeder ?? throw new ArgumentNullException(nameof(databaseSeeder));
Logger = logger ?? throw new ArgumentNullException(nameof(logger));
}
@@ -138,20 +133,22 @@ namespace Tgstation.Server.Host.Models
/// <inheritdoc />
public async Task Initialize(CancellationToken cancellationToken)
{
Logger.LogInformation("Migrating database...");
var wasEmpty = false;
if (databaseConfiguration.NoMigrations)
if (DatabaseConfiguration.DropDatabase)
{
Logger.LogWarning("Using all or nothing migration strategy!");
await Database.EnsureCreatedAsync(cancellationToken).ConfigureAwait(false);
Logger.LogCritical("DropDatabase configuration option set! Dropping any existing database...");
await Database.EnsureDeletedAsync(cancellationToken).ConfigureAwait(false);
}
else
var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false);
var wasEmpty = !migrations.Any();
if (wasEmpty || (await Database.GetPendingMigrationsAsync(cancellationToken).ConfigureAwait(false)).Any())
{
var migrations = await Database.GetAppliedMigrationsAsync(cancellationToken).ConfigureAwait(false);
wasEmpty = !migrations.Any();
Logger.LogInformation("Migrating database...");
await Database.MigrateAsync(cancellationToken).ConfigureAwait(false);
}
else
Logger.LogDebug("No migrations to apply.");
wasEmpty |= (await Users.CountAsync(cancellationToken).ConfigureAwait(false)) == 0;
@@ -160,14 +157,10 @@ namespace Tgstation.Server.Host.Models
Logger.LogInformation("Seeding database...");
await databaseSeeder.SeedDatabase(this, cancellationToken).ConfigureAwait(false);
}
else
else if (DatabaseConfiguration.ResetAdminPassword)
{
Logger.LogDebug("No migrations applied!");
if (databaseConfiguration.ResetAdminPassword)
{
Logger.LogWarning("Enabling and resetting admin password due to configuration!");
await databaseSeeder.ResetAdminPassword(this, cancellationToken).ConfigureAwait(false);
}
Logger.LogWarning("Enabling and resetting admin password due to configuration!");
await databaseSeeder.ResetAdminPassword(this, cancellationToken).ConfigureAwait(false);
}
}
@@ -0,0 +1,671 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Tgstation.Server.Host.Models.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
[Migration("20180906135553_MSInitialCreate")]
partial class MSInitialCreate
{
/// <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.1.2-rtm-30932")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("ChatSettingsId");
b.Property<decimal?>("DiscordChannelId")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
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()
.HasFilter("[DiscordChannelId] IS NOT NULL");
b.HasIndex("ChatSettingsId", "IrcChannel")
.IsUnique()
.HasFilter("[IrcChannel] IS NOT NULL");
b.ToTable("ChatChannels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ByondVersion")
.IsRequired();
b.Property<Guid?>("DirectoryName");
b.Property<string>("DmeName");
b.Property<long?>("JobId");
b.Property<string>("Output");
b.Property<long>("RevisionInformationId");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId");
b.HasIndex("RevisionInformationId");
b.ToTable("CompileJobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessToken");
b.Property<bool?>("AllowWebClient")
.IsRequired();
b.Property<bool?>("AutoStart")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<int>("PrimaryPort");
b.Property<int?>("ProcessId");
b.Property<int>("SecondaryPort");
b.Property<int>("SecurityLevel");
b.Property<bool?>("SoftRestart")
.IsRequired();
b.Property<bool?>("SoftShutdown")
.IsRequired();
b.Property<long>("StartupTimeout");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiValidationPort");
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("AutoUpdateInterval");
b.Property<int>("ConfigurationType");
b.Property<string>("Name")
.IsRequired();
b.Property<bool?>("Online")
.IsRequired();
b.Property<string>("Path")
.IsRequired();
b.Property<long?>("WatchdogReattachInformationId");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.HasIndex("WatchdogReattachInformationId");
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("ByondRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("ChatBotRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("ConfigurationRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("DreamDaemonRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("DreamMakerRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<long>("InstanceId");
b.Property<decimal>("InstanceUserRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("RepositoryRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal?>("CancelRight")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal?>("CancelRightsType")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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<int>("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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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?>("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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("CommitSha")
.IsRequired()
.HasMaxLength(40);
b.Property<long>("InstanceId");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("CommitSha")
.IsUnique();
b.HasIndex("InstanceId");
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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");
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()
.HasFilter("[PrimaryRevisionInformationId] IS NOT NULL");
b.ToTable("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("AdministrationRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<string>("CanonicalName")
.IsRequired();
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired();
b.Property<long?>("CreatedById");
b.Property<bool?>("Enabled")
.IsRequired();
b.Property<decimal>("InstanceManagerRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long?>("AlphaId");
b.Property<bool>("AlphaIsActive");
b.Property<long?>("BravoId");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
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")
.WithMany()
.HasForeignKey("JobId");
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.Instance", b =>
{
b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation")
.WithMany()
.HasForeignKey("WatchdogReattachInformationId");
});
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");
});
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.SetNull);
});
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");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,652 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
{
/// <summary>
/// The initial database migration for MSSQL
/// </summary>
public partial class MSInitialCreate : Migration
{
/// <summary>
/// Applies the migration
/// </summary>
/// <param name="migrationBuilder">The <see cref="MigrationBuilder"/> to use</param>
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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<decimal>(nullable: false),
InstanceManagerRights = table.Column<decimal>(nullable: false),
PasswordHash = table.Column<string>(nullable: true),
CreatedById = table.Column<long>(nullable: true),
CanonicalName = table.Column<string>(nullable: false),
LastPasswordUpdate = table.Column<DateTimeOffset>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Users", x => x.Id);
table.ForeignKey(
name: "FK_Users_Users_CreatedById",
column: x => x.CreatedById,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ChatChannels",
columns: table => new
{
IrcChannel = table.Column<string>(nullable: true),
DiscordChannelId = table.Column<decimal>(nullable: true),
IsAdminChannel = table.Column<bool>(nullable: false),
IsWatchdogChannel = table.Column<bool>(nullable: false),
IsUpdatesChannel = table.Column<bool>(nullable: false),
Tag = table.Column<string>(nullable: true),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
ChatSettingsId = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChatChannels", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ReattachInformations",
columns: table => new
{
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<int>(nullable: false),
RebootState = table.Column<int>(nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
CompileJobId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ReattachInformations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "WatchdogReattachInformations",
columns: table => new
{
AlphaIsActive = table.Column<bool>(nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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);
});
migrationBuilder.CreateTable(
name: "Instances",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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<long>(nullable: false),
WatchdogReattachInformationId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Instances", x => x.Id);
table.ForeignKey(
name: "FK_Instances_WatchdogReattachInformations_WatchdogReattachInformationId",
column: x => x.WatchdogReattachInformationId,
principalTable: "WatchdogReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ChatBots",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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
{
AllowWebClient = table.Column<bool>(nullable: false),
SecurityLevel = table.Column<int>(nullable: false),
PrimaryPort = table.Column<int>(nullable: false),
SecondaryPort = table.Column<int>(nullable: false),
StartupTimeout = table.Column<long>(nullable: false),
AutoStart = table.Column<bool>(nullable: false),
SoftRestart = table.Column<bool>(nullable: false),
SoftShutdown = table.Column<bool>(nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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
{
ProjectName = table.Column<string>(nullable: true),
ApiValidationPort = table.Column<int>(nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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: "InstanceUsers",
columns: table => new
{
UserId = table.Column<long>(nullable: false),
InstanceUserRights = table.Column<decimal>(nullable: false),
ByondRights = table.Column<decimal>(nullable: false),
DreamDaemonRights = table.Column<decimal>(nullable: false),
DreamMakerRights = table.Column<decimal>(nullable: false),
RepositoryRights = table.Column<decimal>(nullable: false),
ChatBotRights = table.Column<decimal>(nullable: false),
ConfigurationRights = table.Column<decimal>(nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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<decimal>(nullable: true),
CancelRight = table.Column<decimal>(nullable: true),
StartedById = table.Column<long>(nullable: false),
CancelledById = table.Column<long>(nullable: true),
InstanceId = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_Jobs", x => x.Id);
table.ForeignKey(
name: "FK_Jobs_Users_CancelledById",
column: x => x.CancelledById,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
table.ForeignKey(
name: "FK_Jobs_Instances_InstanceId",
column: x => x.InstanceId,
principalTable: "Instances",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_Jobs_Users_StartedById",
column: x => x.StartedById,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "RepositorySettings",
columns: table => new
{
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),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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
{
CommitSha = table.Column<string>(maxLength: 40, nullable: false),
OriginCommitSha = table.Column<string>(maxLength: 40, nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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: "CompileJobs",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
DmeName = table.Column<string>(nullable: true),
Output = table.Column<string>(nullable: true),
DirectoryName = table.Column<Guid>(nullable: true),
JobId = table.Column<long>(nullable: true),
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: "TestMerges",
columns: table => new
{
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),
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
MergedAt = table.Column<DateTimeOffset>(nullable: false),
MergedById = table.Column<long>(nullable: false),
PrimaryRevisionInformationId = table.Column<long>(nullable: true)
},
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.SetNull);
});
migrationBuilder.CreateTable(
name: "RevInfoTestMerges",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn),
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.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,
filter: "[DiscordChannelId] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_ChatChannels_ChatSettingsId_IrcChannel",
table: "ChatChannels",
columns: new[] { "ChatSettingsId", "IrcChannel" },
unique: true,
filter: "[IrcChannel] IS NOT NULL");
migrationBuilder.CreateIndex(
name: "IX_CompileJobs_DirectoryName",
table: "CompileJobs",
column: "DirectoryName");
migrationBuilder.CreateIndex(
name: "IX_CompileJobs_JobId",
table: "CompileJobs",
column: "JobId");
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_Instances_WatchdogReattachInformationId",
table: "Instances",
column: "WatchdogReattachInformationId");
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_CommitSha",
table: "RevisionInformations",
column: "CommitSha",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_RevisionInformations_InstanceId",
table: "RevisionInformations",
column: "InstanceId");
migrationBuilder.CreateIndex(
name: "IX_TestMerges_MergedById",
table: "TestMerges",
column: "MergedById");
migrationBuilder.CreateIndex(
name: "IX_TestMerges_PrimaryRevisionInformationId",
table: "TestMerges",
column: "PrimaryRevisionInformationId",
unique: true,
filter: "[PrimaryRevisionInformationId] IS NOT NULL");
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.AddForeignKey(
name: "FK_ChatChannels_ChatBots_ChatSettingsId",
table: "ChatChannels",
column: "ChatSettingsId",
principalTable: "ChatBots",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations",
column: "CompileJobId",
principalTable: "CompileJobs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <summary>
/// Unapplies the migration
/// </summary>
/// <param name="migrationBuilder">The <see cref="MigrationBuilder"/> to use</param>
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Jobs_Instances_InstanceId",
table: "Jobs");
migrationBuilder.DropForeignKey(
name: "FK_RevisionInformations_Instances_InstanceId",
table: "RevisionInformations");
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: "ChatBots");
migrationBuilder.DropTable(
name: "TestMerges");
migrationBuilder.DropTable(
name: "Instances");
migrationBuilder.DropTable(
name: "WatchdogReattachInformations");
migrationBuilder.DropTable(
name: "ReattachInformations");
migrationBuilder.DropTable(
name: "CompileJobs");
migrationBuilder.DropTable(
name: "Jobs");
migrationBuilder.DropTable(
name: "RevisionInformations");
migrationBuilder.DropTable(
name: "Users");
}
}
}
@@ -0,0 +1,644 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Tgstation.Server.Host.Models.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
[Migration("20180906143029_MYInitialCreate")]
partial class MYInitialCreate
{
/// <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.1.2-rtm-30932")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
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");
b.Property<string>("DmeName");
b.Property<long?>("JobId");
b.Property<string>("Output");
b.Property<long>("RevisionInformationId");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId");
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<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.Property<long?>("WatchdogReattachInformationId");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.HasIndex("WatchdogReattachInformationId");
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?>("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("CommitSha")
.IsUnique();
b.HasIndex("InstanceId");
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");
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.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
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")
.WithMany()
.HasForeignKey("JobId");
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.Instance", b =>
{
b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation")
.WithMany()
.HasForeignKey("WatchdogReattachInformationId");
});
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");
});
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.SetNull);
});
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");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,649 @@
using System;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
namespace Tgstation.Server.Host.Models.Migrations
{
/// <summary>
/// The initial database migration for MySQL/MariaDB
/// </summary>
public partial class MYInitialCreate : Migration
{
/// <summary>
/// Applies the migration
/// </summary>
/// <param name="migrationBuilder">The <see cref="MigrationBuilder"/> to use</param>
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "Users",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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: "ChatChannels",
columns: table => new
{
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),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
ChatSettingsId = table.Column<long>(nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_ChatChannels", x => x.Id);
});
migrationBuilder.CreateTable(
name: "ReattachInformations",
columns: table => new
{
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),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
CompileJobId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_ReattachInformations", x => x.Id);
});
migrationBuilder.CreateTable(
name: "WatchdogReattachInformations",
columns: table => new
{
AlphaIsActive = table.Column<bool>(nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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);
});
migrationBuilder.CreateTable(
name: "Instances",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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),
WatchdogReattachInformationId = table.Column<long>(nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Instances", x => x.Id);
table.ForeignKey(
name: "FK_Instances_WatchdogReattachInformations_WatchdogReattachInfor~",
column: x => x.WatchdogReattachInformationId,
principalTable: "WatchdogReattachInformations",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
});
migrationBuilder.CreateTable(
name: "ChatBots",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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
{
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),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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
{
ProjectName = table.Column<string>(nullable: true),
ApiValidationPort = table.Column<ushort>(nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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: "InstanceUsers",
columns: table => new
{
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),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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: "RepositorySettings",
columns: table => new
{
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),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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
{
CommitSha = table.Column<string>(maxLength: 40, nullable: false),
OriginCommitSha = table.Column<string>(maxLength: 40, nullable: false),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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: "CompileJobs",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
DmeName = table.Column<string>(nullable: true),
Output = table.Column<string>(nullable: true),
DirectoryName = table.Column<Guid>(nullable: true),
JobId = table.Column<long>(nullable: true),
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: "TestMerges",
columns: table => new
{
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),
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
MergedAt = table.Column<DateTimeOffset>(nullable: false),
MergedById = table.Column<long>(nullable: false),
PrimaryRevisionInformationId = table.Column<long>(nullable: true)
},
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.SetNull);
});
migrationBuilder.CreateTable(
name: "RevInfoTestMerges",
columns: table => new
{
Id = table.Column<long>(nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
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.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");
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_Instances_WatchdogReattachInformationId",
table: "Instances",
column: "WatchdogReattachInformationId");
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_CommitSha",
table: "RevisionInformations",
column: "CommitSha",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_RevisionInformations_InstanceId",
table: "RevisionInformations",
column: "InstanceId");
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.AddForeignKey(
name: "FK_ChatChannels_ChatBots_ChatSettingsId",
table: "ChatChannels",
column: "ChatSettingsId",
principalTable: "ChatBots",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
migrationBuilder.AddForeignKey(
name: "FK_ReattachInformations_CompileJobs_CompileJobId",
table: "ReattachInformations",
column: "CompileJobId",
principalTable: "CompileJobs",
principalColumn: "Id",
onDelete: ReferentialAction.Restrict);
}
/// <summary>
/// Unapplies the migration
/// </summary>
/// <param name="migrationBuilder">The <see cref="MigrationBuilder"/> to use</param>
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Jobs_Instances_InstanceId",
table: "Jobs");
migrationBuilder.DropForeignKey(
name: "FK_RevisionInformations_Instances_InstanceId",
table: "RevisionInformations");
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: "ChatBots");
migrationBuilder.DropTable(
name: "TestMerges");
migrationBuilder.DropTable(
name: "Instances");
migrationBuilder.DropTable(
name: "WatchdogReattachInformations");
migrationBuilder.DropTable(
name: "ReattachInformations");
migrationBuilder.DropTable(
name: "CompileJobs");
migrationBuilder.DropTable(
name: "Jobs");
migrationBuilder.DropTable(
name: "RevisionInformations");
migrationBuilder.DropTable(
name: "Users");
}
}
}
@@ -14,14 +14,19 @@ namespace Tgstation.Server.Host.Models.Migrations
/// <summary>
/// Path to the json file to use for migrations configuration
/// </summary>
const string MigrationsJson = "appsettings.Development.json";
const string RootJson = "appsettings.json";
/// <summary>
/// Path to the development json file to use for migrations configuration
/// </summary>
const string DevJson = "appsettings.Development.json";
/// <inheritdoc />
public static IOptions<DatabaseConfiguration> GetDbContextOptions()
{
var builder = new ConfigurationBuilder();
builder.SetBasePath(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location));
builder.AddJsonFile(MigrationsJson);
builder.AddJsonFile(RootJson);
builder.AddJsonFile(DevJson);
var configuration = builder.Build();
return Options.Create(configuration.GetSection(DatabaseConfiguration.Section).Get<DatabaseConfiguration>());
}
@@ -0,0 +1,638 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Tgstation.Server.Host.Models.Migrations
{
[DbContext(typeof(MySqlDatabaseContext))]
partial class MySqlDatabaseContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.2-rtm-30932")
.HasAnnotation("Relational:MaxIdentifierLength", 64);
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");
b.Property<string>("DmeName");
b.Property<long?>("JobId");
b.Property<string>("Output");
b.Property<long>("RevisionInformationId");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId");
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<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.Property<long?>("WatchdogReattachInformationId");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.HasIndex("WatchdogReattachInformationId");
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?>("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("CommitSha")
.IsUnique();
b.HasIndex("InstanceId");
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");
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.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
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")
.WithMany()
.HasForeignKey("JobId");
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.Instance", b =>
{
b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation")
.WithMany()
.HasForeignKey("WatchdogReattachInformationId");
});
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");
});
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.SetNull);
});
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");
});
#pragma warning restore 612, 618
}
}
}
@@ -0,0 +1,665 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
namespace Tgstation.Server.Host.Models.Migrations
{
[DbContext(typeof(SqlServerDatabaseContext))]
partial class SqlServerDatabaseContextModelSnapshot : ModelSnapshot
{
protected override void BuildModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.1.2-rtm-30932")
.HasAnnotation("Relational:MaxIdentifierLength", 128)
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("ChatSettingsId");
b.Property<decimal?>("DiscordChannelId")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
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()
.HasFilter("[DiscordChannelId] IS NOT NULL");
b.HasIndex("ChatSettingsId", "IrcChannel")
.IsUnique()
.HasFilter("[IrcChannel] IS NOT NULL");
b.ToTable("ChatChannels");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("ByondVersion")
.IsRequired();
b.Property<Guid?>("DirectoryName");
b.Property<string>("DmeName");
b.Property<long?>("JobId");
b.Property<string>("Output");
b.Property<long>("RevisionInformationId");
b.HasKey("Id");
b.HasIndex("DirectoryName");
b.HasIndex("JobId");
b.HasIndex("RevisionInformationId");
b.ToTable("CompileJobs");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("AccessToken");
b.Property<bool?>("AllowWebClient")
.IsRequired();
b.Property<bool?>("AutoStart")
.IsRequired();
b.Property<long>("InstanceId");
b.Property<int>("PrimaryPort");
b.Property<int?>("ProcessId");
b.Property<int>("SecondaryPort");
b.Property<int>("SecurityLevel");
b.Property<bool?>("SoftRestart")
.IsRequired();
b.Property<bool?>("SoftShutdown")
.IsRequired();
b.Property<long>("StartupTimeout");
b.HasKey("Id");
b.HasIndex("InstanceId")
.IsUnique();
b.ToTable("DreamDaemonSettings");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<int>("ApiValidationPort");
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long>("AutoUpdateInterval");
b.Property<int>("ConfigurationType");
b.Property<string>("Name")
.IsRequired();
b.Property<bool?>("Online")
.IsRequired();
b.Property<string>("Path")
.IsRequired();
b.Property<long?>("WatchdogReattachInformationId");
b.HasKey("Id");
b.HasIndex("Path")
.IsUnique();
b.HasIndex("WatchdogReattachInformationId");
b.ToTable("Instances");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("ByondRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("ChatBotRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("ConfigurationRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("DreamDaemonRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("DreamMakerRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<long>("InstanceId");
b.Property<decimal>("InstanceUserRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal>("RepositoryRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal?>("CancelRight")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<decimal?>("CancelRightsType")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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<int>("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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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?>("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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<string>("CommitSha")
.IsRequired()
.HasMaxLength(40);
b.Property<long>("InstanceId");
b.Property<string>("OriginCommitSha")
.IsRequired()
.HasMaxLength(40);
b.HasKey("Id");
b.HasIndex("CommitSha")
.IsUnique();
b.HasIndex("InstanceId");
b.ToTable("RevisionInformations");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
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");
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()
.HasFilter("[PrimaryRevisionInformationId] IS NOT NULL");
b.ToTable("TestMerges");
});
modelBuilder.Entity("Tgstation.Server.Host.Models.User", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<decimal>("AdministrationRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
b.Property<string>("CanonicalName")
.IsRequired();
b.Property<DateTimeOffset?>("CreatedAt")
.IsRequired();
b.Property<long?>("CreatedById");
b.Property<bool?>("Enabled")
.IsRequired();
b.Property<decimal>("InstanceManagerRights")
.HasConversion(new ValueConverter<decimal, decimal>(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0)));
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()
.HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn);
b.Property<long?>("AlphaId");
b.Property<bool>("AlphaIsActive");
b.Property<long?>("BravoId");
b.HasKey("Id");
b.HasIndex("AlphaId");
b.HasIndex("BravoId");
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")
.WithMany()
.HasForeignKey("JobId");
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.Instance", b =>
{
b.HasOne("Tgstation.Server.Host.Models.WatchdogReattachInformation", "WatchdogReattachInformation")
.WithMany()
.HasForeignKey("WatchdogReattachInformationId");
});
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");
});
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.SetNull);
});
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");
});
#pragma warning restore 612, 618
}
}
}
@@ -1,15 +0,0 @@
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Design;
using Microsoft.Extensions.Logging;
using Tgstation.Server.Host.Security;
namespace Tgstation.Server.Host.Models.Migrations
{
/// <inheritdoc />
sealed class SqliteDesignTimeDbContextFactory : IDesignTimeDbContextFactory<SqliteDatabaseContext>
{
/// <inheritdoc />
public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions<SqliteDatabaseContext>(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher<User>())), new LoggerFactory().CreateLogger<SqliteDatabaseContext>());
}
}
@@ -1,6 +1,8 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Pomelo.EntityFrameworkCore.MySql.Infrastructure;
using System;
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Models
@@ -24,7 +26,10 @@ namespace Tgstation.Server.Host.Models
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
base.OnConfiguring(options);
options.UseMySQL(ConnectionString);
if (!String.IsNullOrEmpty(DatabaseConfiguration.MySqlServerVersion))
options.UseMySql(DatabaseConfiguration.ConnectionString, mySqlOptions => mySqlOptions.ServerVersion(Version.Parse(DatabaseConfiguration.MySqlServerVersion), DatabaseConfiguration.DatabaseType == DatabaseType.MariaDB ? ServerType.MariaDb : ServerType.MySql));
else
options.UseMySql(DatabaseConfiguration.ConnectionString);
}
}
}
@@ -24,7 +24,7 @@ namespace Tgstation.Server.Host.Models
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
base.OnConfiguring(options);
options.UseSqlServer(ConnectionString);
options.UseSqlServer(DatabaseConfiguration.ConnectionString);
}
}
}
@@ -1,38 +0,0 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using System;
using Tgstation.Server.Host.Configuration;
namespace Tgstation.Server.Host.Models
{
/// <summary>
/// <see cref="DatabaseContext{TParentContext}"/> for Sqlite
/// </summary>
sealed class SqliteDatabaseContext : DatabaseContext<SqliteDatabaseContext>
{
/// <summary>
/// Construct a <see cref="SqliteDatabaseContext"/>
/// </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 />
protected override void OnConfiguring(DbContextOptionsBuilder options)
{
base.OnConfiguring(options);
//on the off chance that connection string is null here we default to a db file next to the executable since this is the default database
if (ConnectionString == null)
{
Logger.LogWarning("No database configured! Defaulting to SQLite in the working directory!");
options.UseSqlite("Data Source=TgsDatabase.db3");
}
else
options.UseSqlite(ConnectionString);
}
}
}
@@ -27,26 +27,26 @@
<PackageReference Include="Cyberboss.SmartIrc4net.Standard" Version="0.4.6" />
<PackageReference Include="Discord.Net.WebSocket" Version="1.0.2" />
<PackageReference Include="LibGit2Sharp" Version="0.26.0-preview-0027" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.1" />
<PackageReference Include="Microsoft.AspNetCore" Version="2.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="2.1.2" />
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.1.3" />
<PackageReference Include="Microsoft.AspNetCore.Mvc" Version="2.1.2" />
<PackageReference Include="Microsoft.CodeAnalysis.FxCopAnalyzers" Version="2.6.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="2.1.2" PrivateAssets="All" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="2.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="2.1.2" />
<PackageReference Include="Mono.Posix.NETStandard" Version="1.0.0" />
<PackageReference Include="MySql.Data.EntityFrameworkCore" Version="8.0.12" />
<PackageReference Include="Octokit" Version="0.31.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="2.1.1" />
<PackageReference Include="Serilog.Extensions.Logging.File" Version="2.0.0-dev-00024" />
<PackageReference Include="System.Diagnostics.PerformanceCounter" Version="4.5.0" />
<PackageReference Include="System.DirectoryServices.AccountManagement" Version="4.5.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="5.2.4" />
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="1.8.5" />
<PackageReference Include="Z.EntityFramework.Plus.EFCore" Version="1.8.8" />
</ItemGroup>
<ItemGroup>
@@ -66,4 +66,13 @@
<Folder Include="Properties\" />
</ItemGroup>
<ItemGroup>
<Content Update="appsettings.Development.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
<Content Update="appsettings.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
</Project>
@@ -1,8 +1,5 @@
{
"General": {
"DisableFileLogging": true
},
"Database": {
"NoMigrations": true
}
}
@@ -3,7 +3,8 @@
"LogFileDirectory": "/tgs_logs"
},
"Database": {
"DatabaseType": "SqlServer",
"ConnectionString": "<Your connection string>"
"DatabaseType": "SqlServer or MySQL or MariaDB",
"ConnectionString": "<Your connection string>",
"MySqlServerVersion": "<Set if using MySQL/MariaDB i.e. 10.2.7>"
}
}
+2 -1
View File
@@ -29,9 +29,10 @@
"UpdatePackageAssetName": "ServerUpdatePackage.zip"
},
"Database": {
"NoMigrations": false,
"DropDatabase": false,
"DatabaseType": "SqlServer",
"ResetAdminPassword": false,
"MySqlServerVersion": null,
"ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True"
}
}
@@ -1,46 +0,0 @@
using Microsoft.AspNetCore;
using Microsoft.AspNetCore.Hosting;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Host.IO;
namespace Tgstation.Server.Host.Core.Tests
{
[TestClass]
public sealed class TestApplication : IServerControl
{
public Task<bool> ApplyUpdate(byte[] updateZipData, IIOManager ioManager, CancellationToken cancellationToken) => throw new NotImplementedException();
public void RegisterForRestart(Action action)
{
}
public bool Restart() => throw new NotImplementedException();
[TestMethod]
public async Task TestSuccessfulStartup()
{
var dbName = Path.GetTempFileName();
try
{
using (var webHost = WebHost.CreateDefaultBuilder(new string[] { "Database:DatabaseType=Sqlite", "Database:ConnectionString=Data Source=" + dbName }) //force it to use sqlite
.UseStartup<Application>()
.ConfigureServices((serviceCollection) => serviceCollection.AddSingleton<IServerControl>(this))
.Build()
)
{
await webHost.StartAsync().ConfigureAwait(false);
await webHost.StopAsync().ConfigureAwait(false);
}
}
finally
{
File.Delete(dbName);
}
}
}
}
@@ -1,6 +1,7 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Client;
@@ -15,14 +16,14 @@ namespace Tgstation.Server.Tests
this.client = client ?? throw new ArgumentNullException(nameof(client));
}
public async Task Run()
public async Task Run(CancellationToken cancellationToken)
{
await TestRead().ConfigureAwait(false);
await TestRead(cancellationToken).ConfigureAwait(false);
}
async Task TestRead()
async Task TestRead(CancellationToken cancellationToken)
{
var model = await client.Read(default).ConfigureAwait(false);
var model = await client.Read(cancellationToken).ConfigureAwait(false);
Assert.AreEqual(RuntimeInformation.IsOSPlatform(OSPlatform.Windows), model.WindowsHost);
//uhh not much else to do
@@ -0,0 +1,63 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Client;
using Tgstation.Server.Client.Components;
namespace Tgstation.Server.Tests.Instance
{
sealed class ConfigurationTest
{
readonly IConfigurationClient configurationClient;
readonly Api.Models.Instance instance;
public ConfigurationTest(IConfigurationClient configurationClient, Api.Models.Instance instance)
{
this.configurationClient = configurationClient ?? throw new ArgumentNullException(nameof(configurationClient));
this.instance = instance ?? throw new ArgumentNullException(nameof(instance));
}
bool FileExists(ConfigurationFile file)
{
var tmp = (file.Path?.StartsWith('/') ?? false) ? '.' + file.Path : file.Path;
var path = Path.Combine(instance.Path, "Configuration", tmp);
return File.Exists(path);
}
async Task TestDeleteDirectory(CancellationToken cancellationToken)
{
//try to delete non-existent
var TestDir = new ConfigurationFile
{
Path = "/TestDeleteDir"
};
await configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken).ConfigureAwait(false);
//try to delete non-empty
var file = await configurationClient.Write(new ConfigurationFile
{
Content = Encoding.UTF8.GetBytes("Hello world!"),
Path = TestDir.Path + "/test.txt"
}, cancellationToken).ConfigureAwait(false);
Assert.IsTrue(FileExists(file));
await Assert.ThrowsExceptionAsync<ConflictException>(() => configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken)).ConfigureAwait(false);
file.Content = null;
await configurationClient.Write(file, cancellationToken).ConfigureAwait(false);
await configurationClient.DeleteEmptyDirectory(TestDir, cancellationToken).ConfigureAwait(false);
}
public async Task Run(CancellationToken cancellationToken)
{
await TestDeleteDirectory(cancellationToken).ConfigureAwait(false);
}
}
}
@@ -0,0 +1,25 @@
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Client.Components;
namespace Tgstation.Server.Tests.Instance
{
sealed class InstanceTest
{
readonly IInstanceClient instanceClient;
public InstanceTest(IInstanceClient instanceClient)
{
this.instanceClient = instanceClient ?? throw new ArgumentNullException(nameof(instanceClient));
}
public async Task RunTests(CancellationToken cancellationToken)
{
var configTests = new ConfigurationTest(instanceClient.Configuration, instanceClient.Metadata);
await configTests.Run(cancellationToken).ConfigureAwait(false);
}
}
}
@@ -0,0 +1,116 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Tgstation.Server.Api.Models;
using Tgstation.Server.Client;
using Tgstation.Server.Host.Controllers;
using Tgstation.Server.Tests.Instance;
namespace Tgstation.Server.Tests
{
sealed class InstanceManagerTest
{
readonly IInstanceManagerClient instanceManagerClient;
readonly string testRootPath;
long counter;
public InstanceManagerTest(IInstanceManagerClient instanceManagerClient, string testRootPath)
{
this.instanceManagerClient = instanceManagerClient ?? throw new ArgumentNullException(nameof(instanceManagerClient));
this.testRootPath = testRootPath ?? throw new ArgumentNullException(nameof(testRootPath));
counter = 0;
}
Task<Api.Models.Instance> CreateTestInstance(CancellationToken cancellationToken) => instanceManagerClient.CreateOrAttach(new Api.Models.Instance
{
Name = "TestInstance-" + ++counter,
Path = Path.Combine(testRootPath, Guid.NewGuid().ToString()),
Online = true
}, cancellationToken);
public async Task Run(CancellationToken cancellationToken)
{
var firstTest = await CreateTestInstance(cancellationToken).ConfigureAwait(false);
//instances always start offline
Assert.AreEqual(false, firstTest.Online);
//check it exists
Assert.IsTrue(Directory.Exists(firstTest.Path));
//cant create instances in existent directories
var testNonEmpty = Path.Combine(testRootPath, Guid.NewGuid().ToString());
Directory.CreateDirectory(testNonEmpty);
await Assert.ThrowsExceptionAsync<ConflictException>(() => instanceManagerClient.CreateOrAttach(new Api.Models.Instance
{
Path = testNonEmpty,
Name = "NonEmptyTest"
}, cancellationToken)).ConfigureAwait(false);
await Assert.ThrowsExceptionAsync<ConflictException>(() => instanceManagerClient.CreateOrAttach(firstTest, cancellationToken)).ConfigureAwait(false);
//can't move to existent directories
await Assert.ThrowsExceptionAsync<ConflictException>(() => instanceManagerClient.Update(new Api.Models.Instance
{
Id = firstTest.Id,
Path = testNonEmpty
}, cancellationToken)).ConfigureAwait(false);
//test basic move
Directory.Delete(testNonEmpty);
var initialPath = firstTest.Path;
firstTest = await instanceManagerClient.Update(new Api.Models.Instance
{
Id = firstTest.Id,
Path = testNonEmpty
}, cancellationToken).ConfigureAwait(false);
Assert.IsNotNull(firstTest.MoveJob);
do
{
firstTest = await instanceManagerClient.GetId(firstTest, cancellationToken).ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken).ConfigureAwait(false);
} while (firstTest.MoveJob != null);
//online it for real for component tests
firstTest.Online = true;
firstTest.ConfigurationType = ConfigurationType.HostWrite;
firstTest = await instanceManagerClient.Update(firstTest, cancellationToken).ConfigureAwait(false);
Assert.AreEqual(true, firstTest.Online);
Assert.AreEqual(ConfigurationType.HostWrite, firstTest.ConfigurationType);
//can't move online instance
await Assert.ThrowsExceptionAsync<ConflictException>(() => instanceManagerClient.Update(new Api.Models.Instance
{
Id = firstTest.Id,
Path = initialPath
}, cancellationToken)).ConfigureAwait(false);
var testSuite1 = new InstanceTest(instanceManagerClient.CreateClient(firstTest));
await testSuite1.RunTests(cancellationToken).ConfigureAwait(false);
//can't detach online instance
await Assert.ThrowsExceptionAsync<ConflictException>(() => instanceManagerClient.Detach(firstTest, cancellationToken)).ConfigureAwait(false);
firstTest.Online = false;
firstTest = await instanceManagerClient.Update(firstTest, cancellationToken).ConfigureAwait(false);
await instanceManagerClient.Detach(firstTest, cancellationToken).ConfigureAwait(false);
var instanceAttachFileName = (string)typeof(InstanceController).GetField("InstanceAttachFileName", BindingFlags.NonPublic | BindingFlags.Static).GetValue(null);
var attachPath = Path.Combine(firstTest.Path, instanceAttachFileName);
Assert.IsTrue(File.Exists(attachPath));
//can recreate detached instance
firstTest = await instanceManagerClient.CreateOrAttach(firstTest, cancellationToken).ConfigureAwait(false);
//but only if the attach file exists
await instanceManagerClient.Detach(firstTest, cancellationToken).ConfigureAwait(false);
File.Delete(attachPath);
await Assert.ThrowsExceptionAsync<ConflictException>(() => instanceManagerClient.CreateOrAttach(firstTest, cancellationToken)).ConfigureAwait(false);
}
}
}
@@ -1,5 +1,6 @@
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.IO;
using System.Net.Http.Headers;
using System.Reflection;
using System.Threading;
@@ -22,17 +23,38 @@ namespace Tgstation.Server.Tests
using (var server = new TestingServer())
using (var serverCts = new CancellationTokenSource())
{
var serverTask = server.RunAsync(serverCts.Token);
var cancellationToken = serverCts.Token;
var serverTask = server.RunAsync(cancellationToken);
try
{
using (var adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false))
IServerClient adminClient;
var giveUpAt = DateTimeOffset.Now.AddSeconds(60);
do
{
try
{
adminClient = await clientFactory.CreateServerClient(server.Url, User.AdminName, User.DefaultAdminPassword).ConfigureAwait(false);
break;
}
catch (ServiceUnavailableException)
{
//migrating, to be expected
if (DateTimeOffset.Now > giveUpAt)
throw;
await Task.Delay(TimeSpan.FromSeconds(1), cancellationToken);
}
} while (true);
using (adminClient)
{
var serverInfo = await adminClient.Version(default).ConfigureAwait(false);
Assert.AreEqual(ApiHeaders.Version, serverInfo.ApiVersion);
Assert.AreEqual(typeof(IServer).Assembly.GetName().Version, serverInfo.Version);
await new AdministrationTest(adminClient.Administration).Run().ConfigureAwait(false);
await new AdministrationTest(adminClient.Administration).Run(cancellationToken).ConfigureAwait(false);
await new InstanceManagerTest(adminClient.Instances, server.Directory).Run(cancellationToken).ConfigureAwait(false);
}
}
finally
+23 -8
View File
@@ -1,4 +1,5 @@
using System;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using System;
using System.Globalization;
using System.IO;
using System.Threading;
@@ -10,30 +11,44 @@ namespace Tgstation.Server.Tests
sealed class TestingServer : IServer
{
public Uri Url { get; }
public string Directory { get; }
public bool RestartRequested => realServer.RestartRequested;
readonly IServer realServer;
readonly string databasePath;
public TestingServer()
{
databasePath = Path.GetTempFileName();
File.Delete(databasePath);
Directory = Path.GetTempFileName();
File.Delete(Directory);
System.IO.Directory.CreateDirectory(Directory);
Url = new Uri("http://localhost:5001");
//so we need a db
//we have to rely on env vars
var databaseType = Environment.GetEnvironmentVariable("TGS4_TEST_DATABASE_TYPE");
var connectionString = Environment.GetEnvironmentVariable("TGS4_TEST_CONNECTION_STRING");
if (String.IsNullOrEmpty(databaseType))
Assert.Fail("No database type configured in env var TGS4_TEST_DATABASE_TYPE!");
if (String.IsNullOrEmpty(connectionString))
Assert.Fail("No connection string configured in env var TGS4_TEST_CONNECTION_STRING!");
realServer = new ServerFactory().CreateServer(new string[]
{
"--urls",
Url.ToString(),
"Database:DatabaseType=Sqlite",
String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString=Data Source={0}", databasePath)
,"Database:NoMigrations=true" //TODO: remove this when migrations are added
String.Format(CultureInfo.InvariantCulture, "Database:DatabaseType={0}", databaseType),
String.Format(CultureInfo.InvariantCulture, "Database:ConnectionString={0}", connectionString),
"Database:DropDatabase=true"
}, null);
}
public void Dispose()
{
realServer.Dispose();
File.Delete(databasePath);
System.IO.Directory.Delete(Directory, true);
}
public Task RunAsync(CancellationToken cancellationToken) => realServer.RunAsync(cancellationToken);
+2 -1
View File
@@ -25,6 +25,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "build", "build", "{6FF654E6
build\Dockerfile = build\Dockerfile
build\Doxyfile = build\Doxyfile
build\install_byond.sh = build\install_byond.sh
build\test_core.sh = build\test_core.sh
build\tgs.docker.sh = build\tgs.docker.sh
build\tgs.ico = build\tgs.ico
build\UploadCoverage.ps1 = build\UploadCoverage.ps1
@@ -112,7 +113,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".github", ".github", "{E821
.github\ISSUE_TEMPLATE.md = .github\ISSUE_TEMPLATE.md
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tgstation.Server.Tests", "tests\Tgstation.Server.Tests\Tgstation.Server.Tests.csproj", "{09056964-1C74-445A-96EC-33F6DFC07916}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Tgstation.Server.Tests", "tests\Tgstation.Server.Tests\Tgstation.Server.Tests.csproj", "{09056964-1C74-445A-96EC-33F6DFC07916}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
-2
View File
@@ -9,5 +9,3 @@ Test reattachment
Test auto update
Test auto start
Remove auto folder delete from static file stuff before mso stumbles on it and lynches you