diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 987e8c33d5..221309a906 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -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. diff --git a/.travis.yml b/.travis.yml index 3643b77270..50230a0ab3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/README.md b/README.md index c7445607c2..603fa83bbc 100644 --- a/README.md +++ b/README.md @@ -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 :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 `..` + +- `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 diff --git a/appveyor.yml b/appveyor.yml index 88a4f72c54..9bd18e311d 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -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: diff --git a/build/Dockerfile b/build/Dockerfile index a0da9591bd..e4e1f9eeea 100644 --- a/build/Dockerfile +++ b/build/Dockerfile @@ -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 diff --git a/build/test_core.sh b/build/test_core.sh new file mode 100755 index 0000000000..a9477dfb24 --- /dev/null +++ b/build/test_core.sh @@ -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 diff --git a/docs/API.dox b/docs/API.dox index af9deb1ebe..49c19ce3ee 100644 --- a/docs/API.dox +++ b/docs/API.dox @@ -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 diff --git a/src/Tgstation.Server.Api/Models/Instance.cs b/src/Tgstation.Server.Api/Models/Instance.cs index 474016bacb..0349ce9fce 100644 --- a/src/Tgstation.Server.Api/Models/Instance.cs +++ b/src/Tgstation.Server.Api/Models/Instance.cs @@ -46,6 +46,7 @@ namespace Tgstation.Server.Api.Models /// /// The representing a change of /// + /// Due to how s are children of s but moving one requires the to be offline, interactions with this are performed in a non-standard fashion. The is read by querying the again (either via list or ID lookup) and cancelled by making any sort of update to the . Once the comes back it can be queried like a normal job [NotMapped] public Job MoveJob { get; set; } diff --git a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs index 45b02e528f..c05a40923b 100644 --- a/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs +++ b/src/Tgstation.Server.Api/Rights/ConfigurationRights.cs @@ -23,6 +23,10 @@ namespace Tgstation.Server.Api.Rights /// /// User may list files /// - List = 4 + List = 4, + /// + /// User may delete empty folders + /// + Delete = 8 } } diff --git a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj index fc51067823..20370a3c63 100644 --- a/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj +++ b/src/Tgstation.Server.Api/Tgstation.Server.Api.csproj @@ -17,7 +17,7 @@ 4.0.0.0 json web api tgstation-server tgstation ss13 byond Prototype release - 4.0.0.0-preview5 + 4.0.0.0-preview6001 diff --git a/src/Tgstation.Server.Client/ApiClient.cs b/src/Tgstation.Server.Client/ApiClient.cs index 8405ac5517..e20a143c47 100644 --- a/src/Tgstation.Server.Client/ApiClient.cs +++ b/src/Tgstation.Server.Client/ApiClient.cs @@ -197,6 +197,9 @@ namespace Tgstation.Server.Client /// public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest(route, null, HttpMethod.Delete, instanceId, cancellationToken); + /// + public Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken) => RunRequest(route, body, HttpMethod.Delete, instanceId, cancellationToken); + /// public Task Delete(string route, long instanceId, CancellationToken cancellationToken) => RunRequest(route, null, HttpMethod.Delete, instanceId, cancellationToken); diff --git a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs index 4f35d86321..d3f1a5bccf 100644 --- a/src/Tgstation.Server.Client/Components/ConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/ConfigurationClient.cs @@ -19,6 +19,20 @@ namespace Tgstation.Server.Client.Components /// readonly Instance instance; + /// + /// Sanitize a path for use in a GET + /// + /// The path to sanitize + /// The sanitized path + static string SanitizeGetPath(string path) + { + if (path == null) + path = String.Empty; + if (path.Length == 0 || path[0] != '/') + path = '/' + path; + return path; + } + /// /// Construct a /// @@ -31,22 +45,20 @@ namespace Tgstation.Server.Client.Components } /// - public Task> List(string directory, CancellationToken cancellationToken) - { - if (directory == null) - directory = String.Empty; - return apiClient.Read>(Routes.List(Routes.Configuration) + directory, instance.Id, cancellationToken); - } + public Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken) => apiClient.Delete(Routes.Configuration, directory, instance.Id, cancellationToken); + + /// + public Task> List(string directory, CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.Configuration) + SanitizeGetPath(directory), instance.Id, cancellationToken); /// public Task Read(ConfigurationFile file, CancellationToken cancellationToken) { if (file == null) throw new ArgumentNullException(nameof(file)); - return apiClient.Read(Routes.Configuration + file.Path, instance.Id, cancellationToken); + return apiClient.Read(Routes.Configuration + SanitizeGetPath(file.Path), instance.Id, cancellationToken); } /// public Task Write(ConfigurationFile file, CancellationToken cancellationToken) => apiClient.Update(Routes.Configuration, file ?? throw new ArgumentNullException(nameof(file)), instance.Id, cancellationToken); } -} \ No newline at end of file +} diff --git a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs index 950c346775..2d2337ff50 100644 --- a/src/Tgstation.Server.Client/Components/IConfigurationClient.cs +++ b/src/Tgstation.Server.Client/Components/IConfigurationClient.cs @@ -31,7 +31,15 @@ namespace Tgstation.Server.Client.Components /// /// The file to write /// The for the operation - /// A representing the running operation + /// A resulting in the new Task Write(ConfigurationFile file, CancellationToken cancellationToken); + + /// + /// Delete an empty + /// + /// The representing the directory to delete + /// The for the operation + /// A representing the running operation + Task DeleteEmptyDirectory(ConfigurationFile directory, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/IApiClient.cs b/src/Tgstation.Server.Client/IApiClient.cs index 8359972f02..82b1298122 100644 --- a/src/Tgstation.Server.Client/IApiClient.cs +++ b/src/Tgstation.Server.Client/IApiClient.cs @@ -35,6 +35,7 @@ namespace Tgstation.Server.Client Task Read(string route, long instanceId, CancellationToken cancellationToken); Task Update(string route, TBody body, long instanceId, CancellationToken cancellationToken); Task Delete(string route, long instanceId, CancellationToken cancellationToken); + Task Delete(string route, TBody body, long instanceId, CancellationToken cancellationToken); Task Delete(string route, long instanceId, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Client/IInstanceManagerClient.cs b/src/Tgstation.Server.Client/IInstanceManagerClient.cs index f983ce3d44..73d60a3893 100644 --- a/src/Tgstation.Server.Client/IInstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/IInstanceManagerClient.cs @@ -19,12 +19,12 @@ namespace Tgstation.Server.Client Task> List(CancellationToken cancellationToken); /// - /// Create an + /// Create or attach an /// /// The to create. will be ignored /// The for the operation - /// A resulting in the created - Task Create(Instance instance, CancellationToken cancellationToken); + /// A resulting in the created or attached + Task CreateOrAttach(Instance instance, CancellationToken cancellationToken); /// /// Relocates, renamed, and/or on/offlines an @@ -48,7 +48,7 @@ namespace Tgstation.Server.Client /// The to delete /// The for the operation /// A representing the running operation - Task Delete(Instance instance, CancellationToken cancellationToken); + Task Detach(Instance instance, CancellationToken cancellationToken); /// /// Create an for a given diff --git a/src/Tgstation.Server.Client/InstanceManagerClient.cs b/src/Tgstation.Server.Client/InstanceManagerClient.cs index f8343c5c91..0aad027e0b 100644 --- a/src/Tgstation.Server.Client/InstanceManagerClient.cs +++ b/src/Tgstation.Server.Client/InstanceManagerClient.cs @@ -33,10 +33,10 @@ namespace Tgstation.Server.Client } /// - public Task Create(Instance instance, CancellationToken cancellationToken) => apiClient.Create(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken); + public Task CreateOrAttach(Instance instance, CancellationToken cancellationToken) => apiClient.Create(Routes.InstanceManager, instance ?? throw new ArgumentNullException(nameof(instance)), cancellationToken); /// - 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); /// public Task> List(CancellationToken cancellationToken) => apiClient.Read>(Routes.List(Routes.InstanceManager), cancellationToken); diff --git a/src/Tgstation.Server.Client/ServerErrorException.cs b/src/Tgstation.Server.Client/ServerErrorException.cs index f5a243678c..940f3513a7 100644 --- a/src/Tgstation.Server.Client/ServerErrorException.cs +++ b/src/Tgstation.Server.Client/ServerErrorException.cs @@ -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 with /// /// The raw HTML response of the - 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; } diff --git a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj index 89a7ceabe4..fc21bbffea 100644 --- a/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj +++ b/src/Tgstation.Server.Client/Tgstation.Server.Client.csproj @@ -3,7 +3,7 @@ netstandard2.0 Full - 4.0.0.0-preview11 + 4.0.0.0-preview9101 true Cyberboss /tg/station 13 diff --git a/src/Tgstation.Server.Host/Components/IInstanceManager.cs b/src/Tgstation.Server.Host/Components/IInstanceManager.cs index b6f9143587..186a2ac25a 100644 --- a/src/Tgstation.Server.Host/Components/IInstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/IInstanceManager.cs @@ -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 /// /// Get the associated with given /// - /// The of the desired + /// The of the desired /// The associated with the given IInstance GetInstance(Models.Instance metadata); @@ -28,9 +28,10 @@ namespace Tgstation.Server.Host.Components /// Offline an /// /// The of the desired + /// The performing the operation /// The for the operation /// A representing the running operation - Task OfflineInstance(Models.Instance metadata, CancellationToken cancellationToken); + Task OfflineInstance(Models.Instance metadata, User user, CancellationToken cancellationToken); /// /// Move an diff --git a/src/Tgstation.Server.Host/Components/InstanceManager.cs b/src/Tgstation.Server.Host/Components/InstanceManager.cs index 541d16a977..1165c0c390 100644 --- a/src/Tgstation.Server.Host/Components/InstanceManager.cs +++ b/src/Tgstation.Server.Host/Components/InstanceManager.cs @@ -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); } /// - 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(); + 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 diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs index 90c7a4d218..69846570a3 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/Configuration.cs @@ -103,36 +103,40 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// public async Task 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 /// public async Task SymlinkStaticFilesTo(string destination, CancellationToken cancellationToken) { - await EnsureDirectories(cancellationToken).ConfigureAwait(false); async Task SymlinkBase(bool files) { Task> 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); + } } /// @@ -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; } + + /// + public async Task 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; + } } } diff --git a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs index ebccc9c24e..29b8b66da0 100644 --- a/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs +++ b/src/Tgstation.Server.Host/Components/StaticFiles/IConfiguration.cs @@ -57,6 +57,15 @@ namespace Tgstation.Server.Host.Components.StaticFiles /// A resulting in if the directory already existed, otherwise Task CreateDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// + /// Attempt to delete an empty directory at + /// + /// The path of the empty directory to delete + /// The for the operation. If , the operation will be performed as the user of the + /// The for the operation + /// if the directory was empty and deleted, otherwise + Task DeleteDirectory(string configurationRelativePath, ISystemIdentity systemIdentity, CancellationToken cancellationToken); + /// /// Writes to a given /// diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs index 07f8690601..d5c4a34359 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseConfiguration.cs @@ -26,8 +26,13 @@ public string ConnectionString { get; set; } /// - /// 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! /// - public bool NoMigrations { get; set; } + public bool DropDatabase { get; set; } + + /// + /// The form of the of a target MySQL/MariaDB server + /// + public string MySqlServerVersion { get; set; } } } diff --git a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs index bbc8be7798..2ca7d8fafc 100644 --- a/src/Tgstation.Server.Host/Configuration/DatabaseType.cs +++ b/src/Tgstation.Server.Host/Configuration/DatabaseType.cs @@ -10,12 +10,12 @@ /// SqlServer, /// - /// Use MySQL/MariaDB + /// Use MySQL /// MySql, /// - /// Use SQLite 3 + /// Use MariaDB /// - Sqlite + MariaDB } } diff --git a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs index 34ded3ea45..d60ae85a35 100644 --- a/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs +++ b/src/Tgstation.Server.Host/Controllers/ConfigurationController.cs @@ -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 /// readonly IInstanceManager instanceManager; + /// + /// The for the + /// + readonly IIOManager ioManager; + /// /// Construct a /// /// The for the /// The for the /// The value of + /// The value of /// The for the - public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) + public ConfigurationController(IDatabaseContext databaseContext, IAuthenticationContextFactory authenticationContextFactory, IInstanceManager instanceManager, IIOManager ioManager, ILogger logger) : base(databaseContext, authenticationContextFactory, logger, true) { this.instanceManager = instanceManager ?? throw new ArgumentNullException(nameof(instanceManager)); + this.ioManager = ioManager ?? throw new ArgumentNullException(nameof(ioManager)); } /// - /// If a should be returned from actions due to conflicts with one or both of the or the + /// If a should be returned from actions due to conflicts with one or both of the or the or a given tries to access parent directories /// + /// The path to validate if any /// if a should be returned, otherwise - 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)); /// [TgsAuthorize(ConfigurationRights.Write)] public override async Task 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 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 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 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(); } } + + /// + /// Deletes an empty + /// + /// A representing the path to the directory to delete + /// The for the operation + /// A resulting in the of the operation + [HttpDelete] + [TgsAuthorize(ConfigurationRights.Delete)] + public async Task 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(); + } + } } } diff --git a/src/Tgstation.Server.Host/Controllers/InstanceController.cs b/src/Tgstation.Server.Host/Controllers/InstanceController.cs index eae5142ff3..43eb73c9d7 100644 --- a/src/Tgstation.Server.Host/Controllers/InstanceController.cs +++ b/src/Tgstation.Server.Host/Controllers/InstanceController.cs @@ -34,6 +34,8 @@ namespace Tgstation.Server.Host.Controllers /// const string InstanceAttachFileName = "TGS4_ALLOW_INSTANCE_ATTACH"; + const string MoveInstanceJobPrefix = "Move instance ID "; + /// /// The for the /// @@ -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 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); } /// @@ -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); } } } diff --git a/src/Tgstation.Server.Host/Controllers/JobController.cs b/src/Tgstation.Server.Host/Controllers/JobController.cs index 07c54687cc..d959a9a0eb 100644 --- a/src/Tgstation.Server.Host/Controllers/JobController.cs +++ b/src/Tgstation.Server.Host/Controllers/JobController.cs @@ -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); } /// diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 556c2d1fbe..a556fcc952 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -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(ConfigureDatabase); services.AddScoped(x => x.GetRequiredService()); break; - case DatabaseType.Sqlite: - services.AddDbContext(ConfigureDatabase); - services.AddScoped(x => x.GetRequiredService()); - break; case DatabaseType.SqlServer: services.AddDbContext(ConfigureDatabase); services.AddScoped(x => x.GetRequiredService()); 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(); diff --git a/src/Tgstation.Server.Host/Core/IJobManager.cs b/src/Tgstation.Server.Host/Core/IJobManager.cs index 51d9a2b999..0d5a16842e 100644 --- a/src/Tgstation.Server.Host/Core/IJobManager.cs +++ b/src/Tgstation.Server.Host/Core/IJobManager.cs @@ -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 /// /// The to cancel /// The who cancelled the + /// If the operation should wait until the job exits before completing /// The for the operation - /// A representing a running operation - Task CancelJob(Job job, User user, CancellationToken cancellationToken); + /// A resulting in if the was cancelled, if it couldn't be found + Task CancelJob(Job job, User user, bool blocking, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Core/JobManager.cs b/src/Tgstation.Server.Host/Core/JobManager.cs index fc08c985b8..47ac244289 100644 --- a/src/Tgstation.Server.Host/Core/JobManager.cs +++ b/src/Tgstation.Server.Host/Core/JobManager.cs @@ -187,13 +187,23 @@ namespace Tgstation.Server.Host.Core } /// - public async Task CancelJob(Job job, User user, CancellationToken cancellationToken) + public async Task 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(); @@ -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; } /// diff --git a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs index 15fd161b0d..dee0d3eecf 100644 --- a/src/Tgstation.Server.Host/IO/DefaultIOManager.cs +++ b/src/Tgstation.Server.Host/IO/DefaultIOManager.cs @@ -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); + + /// + public bool PathContainsParentAccess(string path) => path?.Split(new[] { Path.DirectorySeparatorChar, Path.AltDirectorySeparatorChar }).Any(x => x == "..") ?? throw new ArgumentNullException(nameof(path)); } } diff --git a/src/Tgstation.Server.Host/IO/IIOManager.cs b/src/Tgstation.Server.Host/IO/IIOManager.cs index da63c6d117..4ce21a7c97 100644 --- a/src/Tgstation.Server.Host/IO/IIOManager.cs +++ b/src/Tgstation.Server.Host/IO/IIOManager.cs @@ -31,6 +31,13 @@ namespace Tgstation.Server.Host.IO /// The file name portion of string GetFileNameWithoutExtension(string path); + /// + /// Check if a contains the '..' parent directory accessor + /// + /// The path to check + /// if contains a '..' accessor, otherwise + bool PathContainsParentAccess(string path); + /// /// Copies a directory from to /// diff --git a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs index f361004dfe..5b457fd840 100644 --- a/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/ISynchronousIOManager.cs @@ -39,6 +39,13 @@ namespace Tgstation.Server.Host.IO /// A array representing the contents of the file at byte[] ReadFile(string path); + /// + /// Deletes a directory at if it's empty + /// + /// The path of the directory to delete + /// if the directory does not exist or is empty and was deleted. otherwise + bool DeleteDirectory(string path); + /// /// Write to a file at a given . /// diff --git a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs index 9a60da2313..d31e652e5d 100644 --- a/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs +++ b/src/Tgstation.Server.Host/IO/SynchronousIOManager.cs @@ -21,6 +21,22 @@ namespace Tgstation.Server.Host.IO return false; } + /// + 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; + } + /// public IEnumerable 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; } } diff --git a/src/Tgstation.Server.Host/Models/DatabaseContext.cs b/src/Tgstation.Server.Host/Models/DatabaseContext.cs index c8401f054f..c79f697839 100644 --- a/src/Tgstation.Server.Host/Models/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/DatabaseContext.cs @@ -66,15 +66,10 @@ namespace Tgstation.Server.Host.Models /// protected ILogger Logger { get; } - /// - /// The connection string for the - /// - protected string ConnectionString => databaseConfiguration.ConnectionString; - /// /// The for the /// - readonly DatabaseConfiguration databaseConfiguration; + protected DatabaseConfiguration DatabaseConfiguration { get; } /// /// The for the @@ -85,12 +80,12 @@ namespace Tgstation.Server.Host.Models /// Construct a /// /// The for the - /// The containing the value of + /// The containing the value of /// The value of /// The value of public DatabaseContext(DbContextOptions dbContextOptions, IOptions 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 /// 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); } } diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs new file mode 100644 index 0000000000..dae77c6047 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.Designer.cs @@ -0,0 +1,671 @@ +// +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 + { + /// + /// Builds the target model + /// + /// The to use + 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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("Output"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiValidationPort"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AutoUpdateInterval"); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ChatBotRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ConfigurationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamDaemonRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamMakerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("RepositoryRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CancelRight") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CancelRightsType") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdministrationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("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 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs new file mode 100644 index 0000000000..57f363fa2a --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906135553_MSInitialCreate.cs @@ -0,0 +1,652 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// The initial database migration for MSSQL + /// + public partial class MSInitialCreate : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Enabled = table.Column(nullable: false), + CreatedAt = table.Column(nullable: false), + SystemIdentifier = table.Column(nullable: true), + Name = table.Column(nullable: false), + AdministrationRights = table.Column(nullable: false), + InstanceManagerRights = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + CreatedById = table.Column(nullable: true), + CanonicalName = table.Column(nullable: false), + LastPasswordUpdate = table.Column(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(nullable: true), + DiscordChannelId = table.Column(nullable: true), + IsAdminChannel = table.Column(nullable: false), + IsWatchdogChannel = table.Column(nullable: false), + IsUpdatesChannel = table.Column(nullable: false), + Tag = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ChatSettingsId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatChannels", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ReattachInformations", + columns: table => new + { + ChatCommandsJson = table.Column(nullable: false), + ChatChannelsJson = table.Column(nullable: false), + ServerCommandsJson = table.Column(nullable: false), + AccessIdentifier = table.Column(nullable: false), + ProcessId = table.Column(nullable: false), + IsPrimary = table.Column(nullable: false), + Port = table.Column(nullable: false), + RebootState = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + CompileJobId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ReattachInformations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "WatchdogReattachInformations", + columns: table => new + { + AlphaIsActive = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + AlphaId = table.Column(nullable: true), + BravoId = table.Column(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(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Path = table.Column(nullable: false), + Online = table.Column(nullable: false), + ConfigurationType = table.Column(nullable: false), + AutoUpdateInterval = table.Column(nullable: false), + WatchdogReattachInformationId = table.Column(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(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Enabled = table.Column(nullable: true), + Provider = table.Column(nullable: true), + ConnectionString = table.Column(nullable: false), + InstanceId = table.Column(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(nullable: false), + SecurityLevel = table.Column(nullable: false), + PrimaryPort = table.Column(nullable: false), + SecondaryPort = table.Column(nullable: false), + StartupTimeout = table.Column(nullable: false), + AutoStart = table.Column(nullable: false), + SoftRestart = table.Column(nullable: false), + SoftShutdown = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + ProcessId = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + InstanceId = table.Column(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(nullable: true), + ApiValidationPort = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(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(nullable: false), + InstanceUserRights = table.Column(nullable: false), + ByondRights = table.Column(nullable: false), + DreamDaemonRights = table.Column(nullable: false), + DreamMakerRights = table.Column(nullable: false), + RepositoryRights = table.Column(nullable: false), + ChatBotRights = table.Column(nullable: false), + ConfigurationRights = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(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(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + Description = table.Column(nullable: false), + ExceptionDetails = table.Column(nullable: true), + StartedAt = table.Column(nullable: false), + StoppedAt = table.Column(nullable: true), + Cancelled = table.Column(nullable: false), + CancelRightsType = table.Column(nullable: true), + CancelRight = table.Column(nullable: true), + StartedById = table.Column(nullable: false), + CancelledById = table.Column(nullable: true), + InstanceId = table.Column(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(nullable: false), + CommitterEmail = table.Column(nullable: false), + AccessUser = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + PushTestMergeCommits = table.Column(nullable: false), + ShowTestMergeCommitters = table.Column(nullable: false), + AutoUpdatesKeepTestMerges = table.Column(nullable: false), + AutoUpdatesSynchronize = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(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(maxLength: 40, nullable: false), + OriginCommitSha = table.Column(maxLength: 40, nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(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(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + DmeName = table.Column(nullable: true), + Output = table.Column(nullable: true), + DirectoryName = table.Column(nullable: true), + JobId = table.Column(nullable: true), + RevisionInformationId = table.Column(nullable: false), + ByondVersion = table.Column(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(nullable: false), + PullRequestRevision = table.Column(nullable: false), + Comment = table.Column(nullable: true), + TitleAtMerge = table.Column(nullable: false), + BodyAtMerge = table.Column(nullable: false), + Url = table.Column(nullable: false), + Author = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + MergedAt = table.Column(nullable: false), + MergedById = table.Column(nullable: false), + PrimaryRevisionInformationId = table.Column(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(nullable: false) + .Annotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn), + TestMergeId = table.Column(nullable: false), + RevisionInformationId = table.Column(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); + } + + /// + /// Unapplies the migration + /// + /// The to use + 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"); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs new file mode 100644 index 0000000000..73566bba2d --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.Designer.cs @@ -0,0 +1,644 @@ +// +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 + { + /// + /// Builds the target model + /// + /// The to use + 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("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("Output"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort") + .IsRequired(); + + b.Property("ProcessId"); + + b.Property("SecondaryPort") + .IsRequired(); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiValidationPort") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoUpdateInterval") + .IsRequired(); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatBotRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights"); + + b.Property("RepositoryRights"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("CancelRight"); + + b.Property("CancelRightsType"); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights"); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("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 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs new file mode 100644 index 0000000000..02e3d313d1 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/20180906143029_MYInitialCreate.cs @@ -0,0 +1,649 @@ +using System; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Models.Migrations +{ + /// + /// The initial database migration for MySQL/MariaDB + /// + public partial class MYInitialCreate : Migration + { + /// + /// Applies the migration + /// + /// The to use + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "Users", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Enabled = table.Column(nullable: false), + CreatedAt = table.Column(nullable: false), + SystemIdentifier = table.Column(nullable: true), + Name = table.Column(nullable: false), + AdministrationRights = table.Column(nullable: false), + InstanceManagerRights = table.Column(nullable: false), + PasswordHash = table.Column(nullable: true), + CreatedById = table.Column(nullable: true), + CanonicalName = table.Column(nullable: false), + LastPasswordUpdate = table.Column(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(nullable: true), + DiscordChannelId = table.Column(nullable: true), + IsAdminChannel = table.Column(nullable: false), + IsWatchdogChannel = table.Column(nullable: false), + IsUpdatesChannel = table.Column(nullable: false), + Tag = table.Column(nullable: true), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ChatSettingsId = table.Column(nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_ChatChannels", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "ReattachInformations", + columns: table => new + { + ChatCommandsJson = table.Column(nullable: false), + ChatChannelsJson = table.Column(nullable: false), + ServerCommandsJson = table.Column(nullable: false), + AccessIdentifier = table.Column(nullable: false), + ProcessId = table.Column(nullable: false), + IsPrimary = table.Column(nullable: false), + Port = table.Column(nullable: false), + RebootState = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + CompileJobId = table.Column(nullable: true) + }, + constraints: table => + { + table.PrimaryKey("PK_ReattachInformations", x => x.Id); + }); + + migrationBuilder.CreateTable( + name: "WatchdogReattachInformations", + columns: table => new + { + AlphaIsActive = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + AlphaId = table.Column(nullable: true), + BravoId = table.Column(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(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Path = table.Column(nullable: false), + Online = table.Column(nullable: false), + ConfigurationType = table.Column(nullable: false), + AutoUpdateInterval = table.Column(nullable: false), + WatchdogReattachInformationId = table.Column(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(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Name = table.Column(nullable: false), + Enabled = table.Column(nullable: true), + Provider = table.Column(nullable: true), + ConnectionString = table.Column(nullable: false), + InstanceId = table.Column(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(nullable: false), + SecurityLevel = table.Column(nullable: false), + PrimaryPort = table.Column(nullable: false), + SecondaryPort = table.Column(nullable: false), + StartupTimeout = table.Column(nullable: false), + AutoStart = table.Column(nullable: false), + SoftRestart = table.Column(nullable: false), + SoftShutdown = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + ProcessId = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + InstanceId = table.Column(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(nullable: true), + ApiValidationPort = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(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(nullable: false), + InstanceUserRights = table.Column(nullable: false), + ByondRights = table.Column(nullable: false), + DreamDaemonRights = table.Column(nullable: false), + DreamMakerRights = table.Column(nullable: false), + RepositoryRights = table.Column(nullable: false), + ChatBotRights = table.Column(nullable: false), + ConfigurationRights = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(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(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + Description = table.Column(nullable: false), + ExceptionDetails = table.Column(nullable: true), + StartedAt = table.Column(nullable: false), + StoppedAt = table.Column(nullable: true), + Cancelled = table.Column(nullable: false), + CancelRightsType = table.Column(nullable: true), + CancelRight = table.Column(nullable: true), + StartedById = table.Column(nullable: false), + CancelledById = table.Column(nullable: true), + InstanceId = table.Column(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(nullable: false), + CommitterEmail = table.Column(nullable: false), + AccessUser = table.Column(nullable: true), + AccessToken = table.Column(nullable: true), + PushTestMergeCommits = table.Column(nullable: false), + ShowTestMergeCommitters = table.Column(nullable: false), + AutoUpdatesKeepTestMerges = table.Column(nullable: false), + AutoUpdatesSynchronize = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(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(maxLength: 40, nullable: false), + OriginCommitSha = table.Column(maxLength: 40, nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + InstanceId = table.Column(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(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + DmeName = table.Column(nullable: true), + Output = table.Column(nullable: true), + DirectoryName = table.Column(nullable: true), + JobId = table.Column(nullable: true), + RevisionInformationId = table.Column(nullable: false), + ByondVersion = table.Column(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(nullable: false), + PullRequestRevision = table.Column(nullable: false), + Comment = table.Column(nullable: true), + TitleAtMerge = table.Column(nullable: false), + BodyAtMerge = table.Column(nullable: false), + Url = table.Column(nullable: false), + Author = table.Column(nullable: false), + Id = table.Column(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + MergedAt = table.Column(nullable: false), + MergedById = table.Column(nullable: false), + PrimaryRevisionInformationId = table.Column(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(nullable: false) + .Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn), + TestMergeId = table.Column(nullable: false), + RevisionInformationId = table.Column(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); + } + + /// + /// Unapplies the migration + /// + /// The to use + 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"); + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs index c633cc1ec4..b4b4d13ef2 100644 --- a/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs +++ b/src/Tgstation.Server.Host/Models/Migrations/DesignTimeDbContextFactoryHelpers.cs @@ -14,14 +14,19 @@ namespace Tgstation.Server.Host.Models.Migrations /// /// Path to the json file to use for migrations configuration /// - const string MigrationsJson = "appsettings.Development.json"; + const string RootJson = "appsettings.json"; + /// + /// Path to the development json file to use for migrations configuration + /// + const string DevJson = "appsettings.Development.json"; /// public static IOptions 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()); } diff --git a/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs new file mode 100644 index 0000000000..7c82e23050 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -0,0 +1,638 @@ +// +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("Id") + .ValueGeneratedOnAdd(); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId"); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("Output"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort") + .IsRequired(); + + b.Property("ProcessId"); + + b.Property("SecondaryPort") + .IsRequired(); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ApiValidationPort") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AutoUpdateInterval") + .IsRequired(); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("ByondRights"); + + b.Property("ChatBotRights"); + + b.Property("ConfigurationRights"); + + b.Property("DreamDaemonRights"); + + b.Property("DreamMakerRights"); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights"); + + b.Property("RepositoryRights"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("CancelRight"); + + b.Property("CancelRightsType"); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd(); + + b.Property("AdministrationRights"); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights"); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd(); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("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 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs new file mode 100644 index 0000000000..a17096d672 --- /dev/null +++ b/src/Tgstation.Server.Host/Models/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -0,0 +1,665 @@ +// +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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ConnectionString") + .IsRequired(); + + b.Property("Enabled"); + + b.Property("InstanceId"); + + b.Property("Name") + .IsRequired(); + + b.Property("Provider"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChatSettingsId"); + + b.Property("DiscordChannelId") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("IrcChannel"); + + b.Property("IsAdminChannel") + .IsRequired(); + + b.Property("IsUpdatesChannel") + .IsRequired(); + + b.Property("IsWatchdogChannel") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondVersion") + .IsRequired(); + + b.Property("DirectoryName"); + + b.Property("DmeName"); + + b.Property("JobId"); + + b.Property("Output"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AllowWebClient") + .IsRequired(); + + b.Property("AutoStart") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PrimaryPort"); + + b.Property("ProcessId"); + + b.Property("SecondaryPort"); + + b.Property("SecurityLevel"); + + b.Property("SoftRestart") + .IsRequired(); + + b.Property("SoftShutdown") + .IsRequired(); + + b.Property("StartupTimeout"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiValidationPort"); + + b.Property("InstanceId"); + + b.Property("ProjectName"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AutoUpdateInterval"); + + b.Property("ConfigurationType"); + + b.Property("Name") + .IsRequired(); + + b.Property("Online") + .IsRequired(); + + b.Property("Path") + .IsRequired(); + + b.Property("WatchdogReattachInformationId"); + + b.HasKey("Id"); + + b.HasIndex("Path") + .IsUnique(); + + b.HasIndex("WatchdogReattachInformationId"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstanceUser", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ChatBotRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("ConfigurationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamDaemonRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("DreamMakerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("InstanceId"); + + b.Property("InstanceUserRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("RepositoryRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CancelRight") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CancelRightsType") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("Cancelled") + .IsRequired(); + + b.Property("CancelledById"); + + b.Property("Description") + .IsRequired(); + + b.Property("ExceptionDetails"); + + b.Property("InstanceId"); + + b.Property("StartedAt") + .IsRequired(); + + b.Property("StartedById"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessIdentifier") + .IsRequired(); + + b.Property("ChatChannelsJson") + .IsRequired(); + + b.Property("ChatCommandsJson") + .IsRequired(); + + b.Property("CompileJobId"); + + b.Property("IsPrimary"); + + b.Property("Port"); + + b.Property("ProcessId"); + + b.Property("RebootState"); + + b.Property("ServerCommandsJson") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken"); + + b.Property("AccessUser"); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired(); + + b.Property("AutoUpdatesSynchronize") + .IsRequired(); + + b.Property("CommitterEmail") + .IsRequired(); + + b.Property("CommitterName") + .IsRequired(); + + b.Property("InstanceId"); + + b.Property("PushTestMergeCommits") + .IsRequired(); + + b.Property("ShowTestMergeCommitters") + .IsRequired(); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RevisionInformationId"); + + b.Property("TestMergeId"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CommitSha") + .IsRequired() + .HasMaxLength(40); + + b.Property("InstanceId"); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired(); + + b.Property("BodyAtMerge") + .IsRequired(); + + b.Property("Comment"); + + b.Property("MergedAt"); + + b.Property("MergedById"); + + b.Property("Number") + .IsRequired(); + + b.Property("PrimaryRevisionInformationId"); + + b.Property("PullRequestRevision") + .IsRequired(); + + b.Property("TitleAtMerge") + .IsRequired(); + + b.Property("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("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdministrationRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("CanonicalName") + .IsRequired(); + + b.Property("CreatedAt") + .IsRequired(); + + b.Property("CreatedById"); + + b.Property("Enabled") + .IsRequired(); + + b.Property("InstanceManagerRights") + .HasConversion(new ValueConverter(v => default(decimal), v => default(decimal), new ConverterMappingHints(precision: 20, scale: 0))); + + b.Property("LastPasswordUpdate"); + + b.Property("Name") + .IsRequired(); + + b.Property("PasswordHash"); + + b.Property("SystemIdentifier"); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.WatchdogReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AlphaId"); + + b.Property("AlphaIsActive"); + + b.Property("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 + } + } +} diff --git a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs b/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs deleted file mode 100644 index bf8b3c379b..0000000000 --- a/src/Tgstation.Server.Host/Models/Migrations/SqliteDesignTimeDbContextFactory.cs +++ /dev/null @@ -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 -{ - /// - sealed class SqliteDesignTimeDbContextFactory : IDesignTimeDbContextFactory - { - /// - public SqliteDatabaseContext CreateDbContext(string[] args) => new SqliteDatabaseContext(new DbContextOptions(), DesignTimeDbContextFactoryHelpers.GetDbContextOptions(), new DatabaseSeeder(new CryptographySuite(new PasswordHasher())), new LoggerFactory().CreateLogger()); - } -} diff --git a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs index eb6f0c81cd..99657144aa 100644 --- a/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/MySqlDatabaseContext.cs @@ -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); } } } diff --git a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs index b0f7ae1372..d3d5aa8260 100644 --- a/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Models/SqlServerDatabaseContext.cs @@ -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); } } } diff --git a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs b/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs deleted file mode 100644 index 224f08ff0b..0000000000 --- a/src/Tgstation.Server.Host/Models/SqliteDatabaseContext.cs +++ /dev/null @@ -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 -{ - /// - /// for Sqlite - /// - sealed class SqliteDatabaseContext : DatabaseContext - { - /// - /// Construct a - /// - /// The for the - /// The of for the - /// The for the - /// The for the - public SqliteDatabaseContext(DbContextOptions dbContextOptions, IOptions databaseConfiguration, IDatabaseSeeder databaseSeeder, ILogger logger) : base(dbContextOptions, databaseConfiguration, databaseSeeder, logger) - { } - - /// - 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); - } - } -} diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 68e70510d1..fe16fe434e 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -27,26 +27,26 @@ - - - - + + + + all compile; build; native; contentfiles; analyzers - - - - + + + + - + - + @@ -66,4 +66,13 @@ + + + PreserveNewest + + + PreserveNewest + + + diff --git a/src/Tgstation.Server.Host/appsettings.Development.json b/src/Tgstation.Server.Host/appsettings.Development.json index d40e70e835..bf605320bf 100644 --- a/src/Tgstation.Server.Host/appsettings.Development.json +++ b/src/Tgstation.Server.Host/appsettings.Development.json @@ -1,8 +1,5 @@ { "General": { "DisableFileLogging": true - }, - "Database": { - "NoMigrations": true } } diff --git a/src/Tgstation.Server.Host/appsettings.Docker.json b/src/Tgstation.Server.Host/appsettings.Docker.json index 233889ca3f..d90c93f5a5 100644 --- a/src/Tgstation.Server.Host/appsettings.Docker.json +++ b/src/Tgstation.Server.Host/appsettings.Docker.json @@ -3,7 +3,8 @@ "LogFileDirectory": "/tgs_logs" }, "Database": { - "DatabaseType": "SqlServer", - "ConnectionString": "" + "DatabaseType": "SqlServer or MySQL or MariaDB", + "ConnectionString": "", + "MySqlServerVersion": "" } } diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json index 1c91417297..d16d0bb4f5 100644 --- a/src/Tgstation.Server.Host/appsettings.json +++ b/src/Tgstation.Server.Host/appsettings.json @@ -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" } } diff --git a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs b/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs deleted file mode 100644 index 10e043be13..0000000000 --- a/tests/Tgstation.Server.Host.Tests/Core/TestApplication.cs +++ /dev/null @@ -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 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() - .ConfigureServices((serviceCollection) => serviceCollection.AddSingleton(this)) - .Build() - ) - { - await webHost.StartAsync().ConfigureAwait(false); - await webHost.StopAsync().ConfigureAwait(false); - } - } - finally - { - File.Delete(dbName); - } - } - } -} diff --git a/tests/Tgstation.Server.Tests/AdministrationTest.cs b/tests/Tgstation.Server.Tests/AdministrationTest.cs index ef20efe158..9bad0c0168 100644 --- a/tests/Tgstation.Server.Tests/AdministrationTest.cs +++ b/tests/Tgstation.Server.Tests/AdministrationTest.cs @@ -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 diff --git a/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs b/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs new file mode 100644 index 0000000000..df33996edb --- /dev/null +++ b/tests/Tgstation.Server.Tests/Instance/ConfigurationTest.cs @@ -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(() => 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); + } + } +} diff --git a/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs b/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs new file mode 100644 index 0000000000..cd65f015f2 --- /dev/null +++ b/tests/Tgstation.Server.Tests/Instance/InstanceTest.cs @@ -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); + } + } +} diff --git a/tests/Tgstation.Server.Tests/InstanceManagerTest.cs b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs new file mode 100644 index 0000000000..3464217fd6 --- /dev/null +++ b/tests/Tgstation.Server.Tests/InstanceManagerTest.cs @@ -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 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(() => instanceManagerClient.CreateOrAttach(new Api.Models.Instance + { + Path = testNonEmpty, + Name = "NonEmptyTest" + }, cancellationToken)).ConfigureAwait(false); + await Assert.ThrowsExceptionAsync(() => instanceManagerClient.CreateOrAttach(firstTest, cancellationToken)).ConfigureAwait(false); + + //can't move to existent directories + await Assert.ThrowsExceptionAsync(() => 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(() => 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(() => 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(() => instanceManagerClient.CreateOrAttach(firstTest, cancellationToken)).ConfigureAwait(false); + } + } +} diff --git a/tests/Tgstation.Server.Tests/IntegrationTest.cs b/tests/Tgstation.Server.Tests/IntegrationTest.cs index b9d7277265..f90a7e9151 100644 --- a/tests/Tgstation.Server.Tests/IntegrationTest.cs +++ b/tests/Tgstation.Server.Tests/IntegrationTest.cs @@ -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 diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index c943f1d821..3e30fbfa06 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -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); diff --git a/tgstation-server.sln b/tgstation-server.sln index bf94dec0a7..9f414672ca 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -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 diff --git a/v4_prototype_TODO.txt b/v4_prototype_TODO.txt index dca5dbe542..d5b9817fe9 100644 --- a/v4_prototype_TODO.txt +++ b/v4_prototype_TODO.txt @@ -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