diff --git a/.dockerignore b/.dockerignore index de4eb887ed..4a54e2617a 100644 --- a/.dockerignore +++ b/.dockerignore @@ -26,6 +26,7 @@ src/DMAPI src/Tgstation.Server.Host/ClientApp src/Tgstation.Server.Host/wwwroot src/Tgstation.Server.Host/appsettings.Development.json +src/Tgstation.Server.Host/appsettings.Development.yml src/Tgstation.Server.Host/tgs.bat src/Tgstation.Server.Host/tgs.sh src/Tgstation.Server.Client diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md index 96eb6e3108..a096c248c4 100644 --- a/.github/CONTRIBUTING.md +++ b/.github/CONTRIBUTING.md @@ -197,7 +197,7 @@ OAuth providers are hardcoded but it is fairly easy to add new ones. The flow do 1. Create an implementation of [IOAuthValidator](../src/Tgstation.Server.Host/Security/OAuth/IOAuthValidator.cs). - Most providers can simply override the [GenericOAuthValidator](../src/Tgstation.Server.Host/Security/OAuth/GenericOAuthValidator.cs). 1. Construct the implementation in the [OAuthProviders](../src/Tgstation.Server.Host/Security/OAuth/OAuthProviders.cs) class. -1. Add a null entry to the default [appsettings.json](../src/Tgstation.Server.Host/appsettings.json). +1. Add a null entry to the default [appsettings.yml](../src/Tgstation.Server.Host/appsettings.yml). 1. Update the main [README.md](../README.md) to indicate the new provider. 1. Update the [API documentation](../docs/API.dox) to indicate the new provider. diff --git a/.gitignore b/.gitignore index c09ef4b2ac..5b5437ab23 100644 --- a/.gitignore +++ b/.gitignore @@ -11,10 +11,11 @@ artifacts/ *DS_Store *.sln.ide /TestResults -/tests/DMAPI/travistester.lk -/tests/DMAPI/travistester.int -/tests/DMAPI/travistester.dmb +*.dmb +*.int +*.lk /src/Tgstation.Server.Host/appsettings.*.json +/src/Tgstation.Server.Host/appsettings.*.yml /src/Tgstation.Server.Host/wwwroot /src/Tgstation.Server.Host/ClientApp /tools/ReleaseNotes/release_notes.md diff --git a/README.md b/README.md index 45d2111cf1..a7e467f3d3 100644 --- a/README.md +++ b/README.md @@ -80,19 +80,19 @@ Note that this container is meant to be long running. Updates are handled intern Note that automatic configuration reloading is currently not supported in the container. See #1143 -If using manual configuration, before starting your container make sure the aforementioned `appsettings.Production.json` is setup properly. See below +If using manual configuration, before starting your container make sure the aforementioned `appsettings.Production.yml` is setup properly. See below ### Configuring -The first time you run TGS4 you should be prompted with a configuration wizard which will guide you through setting up your appsettings.Production.json +The first time you run TGS4 you should be prompted with a configuration wizard which will guide you through setting up your `appsettings.Production.yml` -This wizard will, generally, run whenever the server is launched without detecting the config json. Follow the instructions below to perform this process manually. +This wizard will, generally, run whenever the server is launched without detecting the config yml. Follow the instructions below to perform this process manually. #### Configuration Methods There are 3 primary supported ways to configure TGS: -- Modify the `appsettings.Production.json` file (Recommended). +- Modify the `appsettings.Production.yml` file (Recommended). - Set environment variables in the form `Section__Subsection=value` or `Section__ArraySubsection__0=value` for arrays. - Set command line arguments in the form `--Section:Subsection=value` or `--Section:ArraySubsection:0=value` for arrays. @@ -100,7 +100,7 @@ The latter two are not recommended as they cannot be dynamically changed at runt #### Manual Configuration -Create an `appsettings.Production.json` file next to `appsettings.json`. This will override the default settings in appsettings.json with your production settings. There are a few keys meant to be changed by hosts. Modifying any config files while the server is running will trigger a safe restart (Keeps DreamDaemon instances running). Note these are all case-sensitive: +Create an `appsettings.Production.yml` file next to `appsettings.yml`. This will override the default settings in `appsettings.yml` with your production settings. There are a few keys meant to be changed by hosts. Modifying any config files while the server is running will trigger a safe restart (Keeps DreamDaemon instances running). Note these are all case-sensitive: - `General:ConfigVersion`: Suppresses warnings about out of date config versions. You should change this after updating TGS to one with a new config version. The current version can be found on the releases page for your server version (This field did not exist before v4.4.0). @@ -143,19 +143,14 @@ Create an `appsettings.Production.json` file next to `appsettings.json`. This wi - `Swarm:Identifier` should be set uniquely on all swarmed servers. Used to identify the current server. This is also used to select which instances exist on the current machine and should not be changed post-setup. - `Security:OAuth:`: Sets the OAuth client ID and secret for a given ``. The currently supported providers are `GitHub`, `Discord`, and `TGForums`. Setting these fields to `null` disables logins with the provider, but does not stop users from associating their accounts using the API. Sample Entry: -```json -{ - "Security": { - "OAuth": { - "Keycloak": { - "ClientId": "...", - "ClientSecret": "...", - "RedirectUrl": "...", - "ServerUrl": "..." - } - } - } -} +```yml +Security: + OAuth: + Keycloak: + ClientId: "..." + ClientSecret: "..." + RedirectUrl: "..." + ServerUrl: "..." ``` The following providers use the `RedirectUrl` setting: @@ -252,7 +247,7 @@ var/global/client_count = 0 ## Remote Access -tgstation-server is an [ASP.Net Core](https://docs.microsoft.com/en-us/aspnet/core/) app based on the Kestrel web server. This section is meant to serve as a general use case overview, but the entire Kestrel configuration can be modified to your liking with the configuration JSON. See [the official documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel) for details. +tgstation-server is an [ASP.Net Core](https://docs.microsoft.com/en-us/aspnet/core/) app based on the Kestrel web server. This section is meant to serve as a general use case overview, but the entire Kestrel configuration can be modified to your liking with the configuration YAML. See [the official documentation](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/servers/kestrel) for details. Exposing the builtin Kestrel server to the internet directly over HTTP is highly not reccommended due to the lack of security. The recommended way to expose tgstation-server to the internet is to host it through a reverse proxy with HTTPS support. Here are some step by step examples to achieve this for major web servers. diff --git a/build/Version.props b/build/Version.props index 813242cd73..a9aeadfbdf 100644 --- a/build/Version.props +++ b/build/Version.props @@ -3,12 +3,13 @@ - 4.7.3 - 2.2.0 - 8.2.0 - 9.1.2 - 5.2.10 - 1.1.0 + 4.8.0 + 2.3.0 + 8.3.0 + 9.2.0 + 6.0.0 + 5.3.0 + 1.1.1 1.2.0 diff --git a/build/tgs.docker.sh b/build/tgs.docker.sh index 490f318a42..d07e183573 100755 --- a/build/tgs.docker.sh +++ b/build/tgs.docker.sh @@ -5,8 +5,8 @@ SCRIPT_VERSION="1.2.0" echo "tgstation-server 4 container startup script v$SCRIPT_VERSION" echo "PWD: $PWD" -PROD_CONFIG=/config_data/appsettings.Production.json -HOST_CONFIG=/app/appsettings.Production.json +PROD_CONFIG=/config_data/appsettings.Production.yml +HOST_CONFIG=/app/appsettings.Production.yml if [ ! -f $PROD_CONFIG ]; then echo "$PROD_CONFIG not detected! Creating empty and running setup wizard..." diff --git a/docs/Architecture.dox b/docs/Architecture.dox index b13c7de06e..6c5bdeea1d 100644 --- a/docs/Architecture.dox +++ b/docs/Architecture.dox @@ -23,7 +23,7 @@ This is a second process spawned by the Host Watchdog which facilitates the vast The server's entrypoint is in the @ref Tgstation.Server.Host.Program class. This class mainly determines if the Host watchdog is present and creates and runs the @ref Tgstation.Server.Host.Server class. That class then builds an ASP.NET Core web host using the @ref Tgstation.Server.Host.Core.Application class. -The @ref Tgstation.Server.Host.Core.Application class has two methods called by the framework. First the @ref Tgstation.Server.Host.Core.Application.ConfigureServices method sets up dependency injection of interfaces for Controllers, the @ref Tgstation.Server.Host.Database.DatabaseContext, and the component factories of the server. The framework handles constructing these things once the application starts. Configuration is loaded from the appropriate appSettings.json into the @ref Tgstation.Server.Host.Configuration classes for injection as well. Then @ref Tgstation.Server.Host.Core.Application.Configure method is run which sets up the web request pipeline which currently has the following stack of handlers: +The @ref Tgstation.Server.Host.Core.Application class has two methods called by the framework. First the @ref Tgstation.Server.Host.Core.Application.ConfigureServices method sets up dependency injection of interfaces for Controllers, the @ref Tgstation.Server.Host.Database.DatabaseContext, and the component factories of the server. The framework handles constructing these things once the application starts. Configuration is loaded from the appropriate appsettings.yml into the @ref Tgstation.Server.Host.Configuration classes for injection as well. Then @ref Tgstation.Server.Host.Core.Application.Configure method is run which sets up the web request pipeline which currently has the following stack of handlers: - Catch any exceptions and respond with 500 and detailed HTML error page - Respond with 503 if the application is still starting or shutting down diff --git a/src/DMAPI/tgs.dm b/src/DMAPI/tgs.dm index 3225f14d8c..a8d824a797 100644 --- a/src/DMAPI/tgs.dm +++ b/src/DMAPI/tgs.dm @@ -1,6 +1,6 @@ // tgstation-server DMAPI -#define TGS_DMAPI_VERSION "5.2.10" +#define TGS_DMAPI_VERSION "6.0.0" // All functions and datums outside this document are subject to change with any version and should not be relied on. @@ -95,8 +95,13 @@ #define TGS_EVENT_WATCHDOG_SHUTDOWN 15 /// Before the watchdog detaches for a TGS update/restart. No parameters. #define TGS_EVENT_WATCHDOG_DETACH 16 -// We don't actually implement this value as the DMAPI can never receive it +// We don't actually implement these 3 events as the DMAPI can never receive them. // #define TGS_EVENT_WATCHDOG_LAUNCH 17 +// #define TGS_EVENT_WATCHDOG_CRASH 18 +// #define TGS_EVENT_WORLD_END_PROCESS 19 +// #define TGS_EVENT_WORLD_REBOOT 20 +/// Watchdog event when TgsInitializationComplete() is called. No parameters. + #define TGS_EVENT_WORLD_PRIME 21 // OTHER ENUMS @@ -130,7 +135,6 @@ * * This may use [/world/var/sleep_offline] to make this happen so ensure no changes are made to it while this call is running. * Afterwards, consider explicitly setting it to what you want to avoid this BYOND bug: http://www.byond.com/forum/post/2575184 - * Before this point, note that any static files or directories may be in use by another server. Your code should account for this. * This function should not be called before ..() in [/world/proc/New]. */ /world/proc/TgsInitializationComplete() @@ -140,7 +144,7 @@ #define TGS_TOPIC var/tgs_topic_return = TgsTopic(args[1]); if(tgs_topic_return) return tgs_topic_return /** - * Call this at the beginning of [world/proc/Reboot]. + * Call this as late as possible in [world/proc/Reboot]. */ /world/proc/TgsReboot() return @@ -152,6 +156,8 @@ /datum/tgs_revision_information /// Full SHA of the commit. var/commit + /// ISO 8601 timestamp of when the commit was created + var/timestamp /// Full sha of last known remote commit. This may be null if the TGS repository is not currently tracking a remote branch. var/origin_commit @@ -201,9 +207,7 @@ /// An http URL to the test merge source. var/url /// The SHA of the test merge when that was merged. - var/pull_request_commit - /// ISO 8601 timestamp of when the test merge was created on TGS. - var/time_merged + var/head_commit /// Optional comment left by the TGS user who initiated the merge. var/comment @@ -263,11 +267,11 @@ // API FUNCTIONS /// Returns the maximum supported [/datum/tgs_version] of the DMAPI. -/world/proc/TgsMaximumAPIVersion() +/world/proc/TgsMaximumApiVersion() return /// Returns the minimum supported [/datum/tgs_version] of the DMAPI. -/world/proc/TgsMinimumAPIVersion() +/world/proc/TgsMinimumApiVersion() return /** diff --git a/src/DMAPI/tgs/core/core.dm b/src/DMAPI/tgs/core/core.dm index f75e7837af..41a0473394 100644 --- a/src/DMAPI/tgs/core/core.dm +++ b/src/DMAPI/tgs/core/core.dm @@ -40,7 +40,7 @@ if(5) api_datum = /datum/tgs_api/v5 - var/datum/tgs_version/max_api_version = TgsMaximumAPIVersion(); + var/datum/tgs_version/max_api_version = TgsMaximumApiVersion(); if(version.suite != null && version.minor != null && version.patch != null && version.deprecated_patch != null && version.deprefixed_parameter > max_api_version.deprefixed_parameter) TGS_ERROR_LOG("Detected unknown API version! Defaulting to latest. Update the DMAPI to fix this problem.") api_datum = /datum/tgs_api/latest @@ -64,10 +64,10 @@ TGS_WRITE_GLOBAL(tgs, null) TGS_ERROR_LOG("Failed to activate API!") -/world/TgsMaximumAPIVersion() +/world/TgsMaximumApiVersion() return new /datum/tgs_version("5.x.x") -/world/TgsMinimumAPIVersion() +/world/TgsMinimumApiVersion() return new /datum/tgs_version("3.2.x") /world/TgsInitializationComplete() diff --git a/src/DMAPI/tgs/v4/api.dm b/src/DMAPI/tgs/v4/api.dm index 081543828e..4eb8e67d92 100644 --- a/src/DMAPI/tgs/v4/api.dm +++ b/src/DMAPI/tgs/v4/api.dm @@ -92,7 +92,7 @@ var/list/json = cached_json["testMerges"] for(var/entry in json) var/datum/tgs_revision_information/test_merge/tm = new - tm.time_merged = text2num(entry["timeMerged"]) + tm.timestamp = text2num(entry["timeMerged"]) var/list/revInfo = entry["revision"] if(revInfo) @@ -104,7 +104,7 @@ tm.url = entry["url"] tm.author = entry["author"] tm.number = entry["number"] - tm.pull_request_commit = entry["pullRequestRevision"] + tm.head_commit = entry["pullRequestRevision"] tm.comment = entry["comment"] cached_test_merges += tm diff --git a/src/DMAPI/tgs/v5/_defines.dm b/src/DMAPI/tgs/v5/_defines.dm index 8fd84e19fa..10bc4cbe40 100644 --- a/src/DMAPI/tgs/v5/_defines.dm +++ b/src/DMAPI/tgs/v5/_defines.dm @@ -79,6 +79,7 @@ #define DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES "chatResponses" #define DMAPI5_REVISION_INFORMATION_COMMIT_SHA "commitSha" +#define DMAPI5_REVISION_INFORMATION_TIMESTAMP "timestamp" #define DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA "originCommitSha" #define DMAPI5_CHAT_USER_ID "id" diff --git a/src/DMAPI/tgs/v5/api.dm b/src/DMAPI/tgs/v5/api.dm index 7a2ff694e0..dfa1b6f948 100644 --- a/src/DMAPI/tgs/v5/api.dm +++ b/src/DMAPI/tgs/v5/api.dm @@ -18,7 +18,9 @@ var/initialized = FALSE /datum/tgs_api/v5/ApiVersion() - return new /datum/tgs_version(TGS_DMAPI_VERSION) + return new /datum/tgs_version( + #include "interop_version.dm" + ) /datum/tgs_api/v5/OnWorldNew(minimum_required_security_level) server_port = world.params[DMAPI5_PARAM_SERVER_PORT] @@ -48,6 +50,7 @@ if(istype(revisionData)) revision = new revision.commit = revisionData[DMAPI5_REVISION_INFORMATION_COMMIT_SHA] + revision.timestamp = revisionData[DMAPI5_REVISION_INFORMATION_TIMESTAMP] revision.origin_commit = revisionData[DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA] else TGS_ERROR_LOG("Failed to decode [DMAPI5_RUNTIME_INFORMATION_REVISION] from runtime information!") @@ -66,12 +69,12 @@ else TGS_WARNING_LOG("Failed to decode [DMAPI5_TEST_MERGE_REVISION] from test merge #[tm.number]!") - tm.time_merged = text2num(entry[DMAPI5_TEST_MERGE_TIME_MERGED]) + tm.timestamp = entry[DMAPI5_TEST_MERGE_TIME_MERGED] tm.title = entry[DMAPI5_TEST_MERGE_TITLE_AT_MERGE] tm.body = entry[DMAPI5_TEST_MERGE_BODY_AT_MERGE] tm.url = entry[DMAPI5_TEST_MERGE_URL] tm.author = entry[DMAPI5_TEST_MERGE_AUTHOR] - tm.pull_request_commit = entry[DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION] + tm.head_commit = entry[DMAPI5_TEST_MERGE_PULL_REQUEST_REVISION] tm.comment = entry[DMAPI5_TEST_MERGE_COMMENT] test_merges += tm diff --git a/src/DMAPI/tgs/v5/interop_version.dm b/src/DMAPI/tgs/v5/interop_version.dm new file mode 100644 index 0000000000..c7bf62ecae --- /dev/null +++ b/src/DMAPI/tgs/v5/interop_version.dm @@ -0,0 +1 @@ +"5.3.0" diff --git a/src/DMAPI/tgs/v5/undefs.dm b/src/DMAPI/tgs/v5/undefs.dm index 34fc481d36..5885a60e75 100644 --- a/src/DMAPI/tgs/v5/undefs.dm +++ b/src/DMAPI/tgs/v5/undefs.dm @@ -79,6 +79,7 @@ #undef DMAPI5_TOPIC_RESPONSE_CHAT_RESPONSES #undef DMAPI5_REVISION_INFORMATION_COMMIT_SHA +#undef DMAPI5_REVISION_INFORMATION_TIMESTAMP #undef DMAPI5_REVISION_INFORMATION_ORIGIN_COMMIT_SHA #undef DMAPI5_CHAT_USER_ID diff --git a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs index 6d45f3e42a..18acf71ae9 100644 --- a/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs +++ b/src/Tgstation.Server.Api/Models/Internal/RevisionInformation.cs @@ -1,3 +1,4 @@ +using System; using System.ComponentModel.DataAnnotations; namespace Tgstation.Server.Api.Models.Internal @@ -8,14 +9,19 @@ namespace Tgstation.Server.Api.Models.Internal public class RevisionInformation { /// - /// The revision sha + /// The revision SHA. /// [Required] [StringLength(Limits.MaximumCommitShaLength)] public string? CommitSha { get; set; } /// - /// The sha of the most recent remote commit + /// The timestamp of the revision. + /// + public DateTimeOffset Timestamp { get; set; } + + /// + /// The SHA of the most recent remote commit. /// [Required] [StringLength(Limits.MaximumCommitShaLength)] diff --git a/src/Tgstation.Server.Api/Models/ServerInformation.cs b/src/Tgstation.Server.Api/Models/ServerInformation.cs index 16b7ea0732..ed0509d76b 100644 --- a/src/Tgstation.Server.Api/Models/ServerInformation.cs +++ b/src/Tgstation.Server.Api/Models/ServerInformation.cs @@ -19,7 +19,7 @@ namespace Tgstation.Server.Api.Models public Version? ApiVersion { get; set; } /// - /// The DMAPI version of the host. + /// The DMAPI interop version the server uses. /// public Version? DMApiVersion { get; set; } diff --git a/src/Tgstation.Server.Host.Service/Program.cs b/src/Tgstation.Server.Host.Service/Program.cs index ac37e65e5a..f86b67243c 100644 --- a/src/Tgstation.Server.Host.Service/Program.cs +++ b/src/Tgstation.Server.Host.Service/Program.cs @@ -1,4 +1,4 @@ -using McMaster.Extensions.CommandLineUtils; +using McMaster.Extensions.CommandLineUtils; using Microsoft.Extensions.Logging; using System; using System.Collections.Specialized; @@ -20,12 +20,10 @@ namespace Tgstation.Server.Host.Service /// sealed class Program { -#pragma warning disable SA1401 // Fields should be private /// /// The for the /// internal static IWatchdogFactory WatchdogFactory = new WatchdogFactory(); -#pragma warning restore SA1401 // Fields should be private /// /// The --uninstall or -u option @@ -151,6 +149,10 @@ namespace Tgstation.Server.Host.Service if (Configure) { +#pragma warning disable CS0618 // Type or member is obsolete + loggerFactory.AddConsole(); +#pragma warning restore CS0618 // Type or member is obsolete + // DCT: None available await WatchdogFactory.CreateWatchdog(loggerFactory).RunAsync(true, Array.Empty(), default).ConfigureAwait(false); } diff --git a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj index d2bb63a992..d6dbaf8980 100644 --- a/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj +++ b/src/Tgstation.Server.Host.Service/Tgstation.Server.Host.Service.csproj @@ -30,6 +30,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj index dc6ff60c72..c2d59ccd7a 100644 --- a/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj +++ b/src/Tgstation.Server.Host.Watchdog/Tgstation.Server.Host.Watchdog.csproj @@ -33,7 +33,7 @@ - + PreserveNewest diff --git a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs index 4f2e4ae68d..d9ab43cfb6 100644 --- a/src/Tgstation.Server.Host.Watchdog/Watchdog.cs +++ b/src/Tgstation.Server.Host.Watchdog/Watchdog.cs @@ -96,10 +96,10 @@ namespace Tgstation.Server.Host.Watchdog foreach (string newPath in Directory.GetFiles(sourcePath, "*.*", SearchOption.AllDirectories)) File.Copy(newPath, newPath.Replace(sourcePath, defaultAssemblyPath), true); - const string AppSettingsJson = "appsettings.json"; - var rootJson = Path.Combine(rootLocation, AppSettingsJson); - File.Delete(rootJson); - File.Move(Path.Combine(defaultAssemblyPath, AppSettingsJson), rootJson); + const string AppSettingsYaml = "appsettings.yml"; + var rootYaml = Path.Combine(rootLocation, AppSettingsYaml); + File.Delete(rootYaml); + File.Move(Path.Combine(defaultAssemblyPath, AppSettingsYaml), rootYaml); } else Directory.CreateDirectory(assemblyStoragePath); diff --git a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs index 7b2d5f1b07..04b80d802c 100644 --- a/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs +++ b/src/Tgstation.Server.Host/Components/Deployment/DreamMaker.cs @@ -525,10 +525,8 @@ namespace Tgstation.Server.Host.Components.Deployment Action progressReporter, CancellationToken cancellationToken) { -#pragma warning disable IDE0016 // Use 'throw' expression if (job == null) throw new ArgumentNullException(nameof(job)); -#pragma warning restore IDE0016 // Use 'throw' expression if (databaseContextFactory == null) throw new ArgumentNullException(nameof(databaseContextFactory)); if (progressReporter == null) @@ -625,6 +623,7 @@ namespace Tgstation.Server.Host.Components.Deployment revInfo = new Models.RevisionInformation { CommitSha = repoSha, + Timestamp = await repo.TimestampCommit(repoSha, cancellationToken).ConfigureAwait(false), OriginCommitSha = repoSha, Instance = new Models.Instance { diff --git a/src/Tgstation.Server.Host/Components/Events/EventType.cs b/src/Tgstation.Server.Host/Components/Events/EventType.cs index 9085c6e215..dabb822de7 100644 --- a/src/Tgstation.Server.Host/Components/Events/EventType.cs +++ b/src/Tgstation.Server.Host/Components/Events/EventType.cs @@ -96,7 +96,7 @@ namespace Tgstation.Server.Host.Components.Events DeploymentComplete, /// - /// Before the watchdog shutsdown. Not sent for graceful shutdowns. No parameters. + /// Before the watchdog shuts down. Not sent for graceful shutdowns. No parameters. /// [EventScript("WatchdogShutdown")] WatchdogShutdown, @@ -111,6 +111,30 @@ namespace Tgstation.Server.Host.Components.Events /// Before the watchdog launches. No parameters. /// [EventScript("WatchdogLaunch")] - WatchdogLaunch + WatchdogLaunch, + + /// + /// Watchdog event when DreamDaemon exits unexpectedly. No parameters. + /// + [EventScript("WatchdogCrash")] + WatchdogCrash, + + /// + /// In between watchdog DreamDaemon restarts if the process has been force-ended by the DMAPI (TgsEndProcess()). No parameters. + /// + [EventScript("WorldEndProcess")] + WorldEndProcess, + + /// + /// Watchdog event when TgsReboot() is called. Not synchronous. Called after . No parameters. + /// + [EventScript("WorldReboot")] + WorldReboot, + + /// + /// Watchdog event when TgsInitializationsComplete() is called. No parameters. + /// + [EventScript("WorldPrime")] + WorldPrime, } } diff --git a/src/Tgstation.Server.Host/Components/Instance.cs b/src/Tgstation.Server.Host/Components/Instance.cs index 3c4b3f437f..1bbc2d6975 100644 --- a/src/Tgstation.Server.Host/Components/Instance.cs +++ b/src/Tgstation.Server.Host/Components/Instance.cs @@ -248,6 +248,7 @@ namespace Tgstation.Server.Host.Components currentRevInfo = new RevisionInformation { CommitSha = currentHead, + Timestamp = await repo.TimestampCommit(currentHead, cancellationToken).ConfigureAwait(false), OriginCommitSha = onOrigin ? currentHead : await repo.GetOriginSha(cancellationToken).ConfigureAwait(false), diff --git a/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs b/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs index 77fb040e37..a54398a512 100644 --- a/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs +++ b/src/Tgstation.Server.Host/Components/Interop/Bridge/RuntimeInformation.cs @@ -76,6 +76,7 @@ namespace Tgstation.Server.Host.Components.Interop.Bridge Revision = new Api.Models.Internal.RevisionInformation { CommitSha = dmbProvider.CompileJob.RevisionInformation.CommitSha, + Timestamp = dmbProvider.CompileJob.RevisionInformation.Timestamp, OriginCommitSha = dmbProvider.CompileJob.RevisionInformation.OriginCommitSha }; diff --git a/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs b/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs index c910a8a1e2..b4697d8952 100644 --- a/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs +++ b/src/Tgstation.Server.Host/Components/Interop/DMApiConstants.cs @@ -32,9 +32,9 @@ namespace Tgstation.Server.Host.Components.Interop public const string TopicData = "tgs_data"; /// - /// The DMAPI being used. + /// The DMAPI being used. /// - public static readonly Version Version = Version.Parse(MasterVersionsAttribute.Instance.RawDMApiVersion); + public static readonly Version InteropVersion = Version.Parse(MasterVersionsAttribute.Instance.RawInteropVersion); /// /// for use when communicating with the DMAPI. diff --git a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs index e2caa16611..e1c0bd0af1 100644 --- a/src/Tgstation.Server.Host/Components/Repository/IRepository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/IRepository.cs @@ -133,5 +133,13 @@ namespace Tgstation.Server.Host.Components.Repository /// The for the operation. /// A resulting in the tracked origin reference's SHA. Task GetOriginSha(CancellationToken cancellationToken); + + /// + /// Gets the a given was created on. + /// + /// The SHA to timestamp. + /// The for the operation. + /// A resulting in the that was created on. + Task TimestampCommit(string sha, CancellationToken cancellationToken); } } diff --git a/src/Tgstation.Server.Host/Components/Repository/Repository.cs b/src/Tgstation.Server.Host/Components/Repository/Repository.cs index 3ab698c381..89c958eb70 100644 --- a/src/Tgstation.Server.Host/Components/Repository/Repository.cs +++ b/src/Tgstation.Server.Host/Components/Repository/Repository.cs @@ -777,5 +777,18 @@ namespace Tgstation.Server.Host.Components.Repository parameters, repositorySettings, cancellationToken); + + /// + public Task TimestampCommit(string sha, CancellationToken cancellationToken) => Task.Factory.StartNew(() => + { + if (sha == null) + throw new ArgumentNullException(nameof(sha)); + + var commit = libGitRepo.Lookup(sha); + if (commit == null) + throw new JobException($"Commit {sha} does not exist in the repository!"); + + return commit.Committer.When; + }, cancellationToken, DefaultIOManager.BlockingTaskCreationOptions, TaskScheduler.Current); } } diff --git a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs index 778c39ba38..9c7a43cb3a 100644 --- a/src/Tgstation.Server.Host/Components/Session/ISessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/ISessionController.cs @@ -49,12 +49,12 @@ namespace Tgstation.Server.Host.Components.Session RebootState RebootState { get; } /// - /// A that completes when the server calls /world/Reboot() + /// A that completes when the server calls /world/TgsReboot(). /// Task OnReboot { get; } /// - /// A that completes when the server calls /world/TgsInitializationsComplete() + /// A that completes when the server calls /world/TgsInitializationComplete() /// Task OnPrime { get; } diff --git a/src/Tgstation.Server.Host/Components/Session/SessionController.cs b/src/Tgstation.Server.Host/Components/Session/SessionController.cs index 1462b6cd50..0de14b9df4 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionController.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionController.cs @@ -66,7 +66,7 @@ namespace Tgstation.Server.Host.Components.Session public Task OnPrime => primeTcs.Task; /// - public bool DMApiAvailable => reattachInformation.Dmb.CompileJob.DMApiVersion?.Major == DMApiConstants.Version.Major; + public bool DMApiAvailable => reattachInformation.Dmb.CompileJob.DMApiVersion?.Major == DMApiConstants.InteropVersion.Major; /// /// The up to date @@ -431,7 +431,7 @@ namespace Tgstation.Server.Host.Components.Session return Error("Missing dmApiVersion field!"); DMApiVersion = parameters.Version; - if (DMApiVersion.Major != DMApiConstants.Version.Major) + if (DMApiVersion.Major != DMApiConstants.InteropVersion.Major) { apiValidationStatus = ApiValidationStatus.Incompatible; return Error("Incompatible dmApiVersion!"); diff --git a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs index 698510757b..1cc3960a7a 100644 --- a/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs +++ b/src/Tgstation.Server.Host/Components/Session/SessionControllerFactory.cs @@ -229,7 +229,7 @@ namespace Tgstation.Server.Host.Components.Session // set command line options // more sanitization here cause it uses the same scheme - var parameters = $"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.Version.Semver().ToString())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}"; + var parameters = $"{DMApiConstants.ParamApiVersion}={byondTopicSender.SanitizeString(DMApiConstants.InteropVersion.Semver().ToString())}&{byondTopicSender.SanitizeString(DMApiConstants.ParamServerPort)}={serverPortProvider.HttpApiPort}&{byondTopicSender.SanitizeString(DMApiConstants.ParamAccessIdentifier)}={byondTopicSender.SanitizeString(accessIdentifier)}"; if (!String.IsNullOrEmpty(launchParameters.AdditionalParameters)) parameters = $"{parameters}&{launchParameters.AdditionalParameters}"; @@ -240,7 +240,7 @@ namespace Tgstation.Server.Host.Components.Session Guid? logFileGuid = null; var arguments = String.Format( CultureInfo.InvariantCulture, - "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4}{5} -public -params \"{6}\"", + "{0} -port {1} -ports 1-65535 {2}-close -{3} -{4}{5} -params \"{6}\"", dmbProvider.DmbName, launchParameters.Port.Value, launchParameters.AllowWebClient.Value ? "-webclient " : String.Empty, diff --git a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs index c364a8cf65..3cddd03a02 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/BasicWatchdog.cs @@ -1,6 +1,7 @@ using Microsoft.Extensions.Logging; using System; using System.Globalization; +using System.Linq; using System.Threading; using System.Threading.Tasks; using Tgstation.Server.Api.Models.Internal; @@ -91,7 +92,12 @@ namespace Tgstation.Server.Host.Components.Watchdog switch (reason) { case MonitorActivationReason.ActiveServerCrashed: - string exitWord = Server.TerminationWasRequested ? "exited" : "crashed"; + var eventType = Server.TerminationWasRequested + ? EventType.WorldEndProcess + : EventType.WatchdogCrash; + await EventConsumer.HandleEvent(eventType, Enumerable.Empty(), cancellationToken).ConfigureAwait(false); + + var exitWord = Server.TerminationWasRequested ? "exited" : "crashed"; if (Server.RebootState == Session.RebootState.Shutdown) { // the time for graceful shutdown is now @@ -124,6 +130,8 @@ namespace Tgstation.Server.Host.Components.Watchdog gracefulRebootRequired = false; Server.ResetRebootState(); + await EventConsumer.HandleEvent(EventType.WorldReboot, Enumerable.Empty(), cancellationToken).ConfigureAwait(false); + switch (rebootState) { case Session.RebootState.Normal: @@ -140,7 +148,6 @@ namespace Tgstation.Server.Host.Components.Watchdog default: throw new InvalidOperationException($"Invalid reboot state: {rebootState}"); } - case MonitorActivationReason.ActiveLaunchParametersUpdated: await Server.SetRebootState(Session.RebootState.Restart, cancellationToken).ConfigureAwait(false); gracefulRebootRequired = true; @@ -148,6 +155,9 @@ namespace Tgstation.Server.Host.Components.Watchdog case MonitorActivationReason.NewDmbAvailable: await HandleNewDmbAvailable(cancellationToken).ConfigureAwait(false); break; + case MonitorActivationReason.ActiveServerPrimed: + await EventConsumer.HandleEvent(EventType.WorldPrime, Enumerable.Empty(), cancellationToken).ConfigureAwait(false); + break; case MonitorActivationReason.Heartbeat: default: throw new InvalidOperationException($"Invalid activation reason: {reason}"); diff --git a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs index 4b0d046561..4f1073e742 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/MonitorActivationReason.cs @@ -1,4 +1,4 @@ -namespace Tgstation.Server.Host.Components.Watchdog +namespace Tgstation.Server.Host.Components.Watchdog { /// /// Reasons for the monitor to wake up @@ -29,5 +29,10 @@ /// A heartbeat is required. /// Heartbeat, + + /// + /// Server primed. + /// + ActiveServerPrimed, } } diff --git a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs index 5a24b01076..3e89293cb3 100644 --- a/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs +++ b/src/Tgstation.Server.Host/Components/Watchdog/WatchdogBase.cs @@ -83,6 +83,11 @@ namespace Tgstation.Server.Host.Components.Watchdog /// protected IAsyncDelayer AsyncDelayer { get; } + /// + /// The that is not the + /// + protected IEventConsumer EventConsumer { get; } + /// /// The for the . /// @@ -118,11 +123,6 @@ namespace Tgstation.Server.Host.Components.Watchdog /// readonly IIOManager diagnosticsIOManager; - /// - /// The that is not the - /// - readonly IEventConsumer eventConsumer; - /// /// The for the . /// @@ -179,7 +179,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// The to populate with /// The value of . /// The value of . - /// The value of . + /// The value of . /// The value of . /// The value of /// The initial value of . May be modified @@ -208,7 +208,7 @@ namespace Tgstation.Server.Host.Components.Watchdog this.jobManager = jobManager ?? throw new ArgumentNullException(nameof(jobManager)); AsyncDelayer = asyncDelayer ?? throw new ArgumentNullException(nameof(asyncDelayer)); this.diagnosticsIOManager = diagnosticsIOManager ?? throw new ArgumentNullException(nameof(diagnosticsIOManager)); - this.eventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); + EventConsumer = eventConsumer ?? throw new ArgumentNullException(nameof(eventConsumer)); this.remoteDeploymentManagerFactory = remoteDeploymentManagerFactory ?? throw new ArgumentNullException(nameof(remoteDeploymentManagerFactory)); Logger = logger ?? throw new ArgumentNullException(nameof(logger)); ActiveLaunchParameters = initialLaunchParameters ?? throw new ArgumentNullException(nameof(initialLaunchParameters)); @@ -267,7 +267,7 @@ namespace Tgstation.Server.Host.Components.Watchdog return; if (!graceful) { - var eventTask = eventConsumer.HandleEvent(releaseServers ? EventType.WatchdogDetach : EventType.WatchdogShutdown, null, cancellationToken); + var eventTask = EventConsumer.HandleEvent(releaseServers ? EventType.WatchdogDetach : EventType.WatchdogShutdown, null, cancellationToken); var chatTask = announce ? Chat.QueueWatchdogMessage("Shutting down...", cancellationToken) : Task.CompletedTask; @@ -378,7 +378,7 @@ namespace Tgstation.Server.Host.Components.Watchdog cancellationToken); // simple announce if (reattachInfo == null) announceTask = Task.WhenAll( - eventConsumer.HandleEvent(EventType.WatchdogLaunch, Enumerable.Empty(), cancellationToken), + EventConsumer.HandleEvent(EventType.WatchdogLaunch, Enumerable.Empty(), cancellationToken), announceTask); } else @@ -609,6 +609,7 @@ namespace Tgstation.Server.Host.Components.Watchdog /// /// The for the operation. /// A representing the running operation. + #pragma warning disable CA1502 private async Task MonitorLifetimes(CancellationToken cancellationToken) { Logger.LogTrace("Entered MonitorLifetimes"); @@ -619,6 +620,12 @@ namespace Tgstation.Server.Host.Components.Watchdog try { MonitorAction nextAction = MonitorAction.Continue; + Task activeServerLifetime = null, + activeServerReboot = null, + serverPrimed = null, + activeLaunchParametersChanged = null, + newDmbAvailable = null; + ISessionController lastController = null; for (ulong iteration = 1; nextAction != MonitorAction.Exit; ++iteration) using (LogContext.PushProperty("Monitor", iteration)) try @@ -627,17 +634,44 @@ namespace Tgstation.Server.Host.Components.Watchdog nextAction = MonitorAction.Continue; var controller = GetActiveController(); - Task activeServerLifetime = controller.Lifetime; - var activeServerReboot = controller.OnReboot; - Task activeLaunchParametersChanged = ActiveParametersUpdated.Task; - var newDmbAvailable = DmbFactory.OnNewerDmb; + void UpdateMonitoredTasks() + { + static void TryUpdateTask(ref Task oldTask, Task newTask) + { + if (oldTask?.IsCompleted == true) + return; + + oldTask = newTask; + } + + if (lastController == controller) + { + TryUpdateTask(ref activeServerLifetime, controller.Lifetime); + TryUpdateTask(ref activeServerReboot, controller.OnReboot); + TryUpdateTask(ref serverPrimed, controller.OnPrime); + } + else + { + activeServerLifetime = controller.Lifetime; + activeServerReboot = controller.OnReboot; + serverPrimed = controller.OnPrime; + lastController = controller; + } + + TryUpdateTask(ref activeLaunchParametersChanged, ActiveParametersUpdated.Task); + TryUpdateTask(ref newDmbAvailable, DmbFactory.OnNewerDmb); + } + + UpdateMonitoredTasks(); var heartbeatSeconds = ActiveLaunchParameters.HeartbeatSeconds.Value; var heartbeat = heartbeatSeconds == 0 || !controller.DMApiAvailable ? Extensions.TaskExtensions.InfiniteTask() - : Task.Delay(TimeSpan.FromSeconds(heartbeatSeconds), cancellationToken); + : Task.Delay( + TimeSpan.FromSeconds(heartbeatSeconds), + cancellationToken); // cancel waiting if requested var cancelTcs = new TaskCompletionSource(); @@ -647,7 +681,8 @@ namespace Tgstation.Server.Host.Components.Watchdog heartbeat, newDmbAvailable, cancelTcs.Task, - activeLaunchParametersChanged); + activeLaunchParametersChanged, + serverPrimed); // wait for something to happen using (cancellationToken.Register(() => cancelTcs.SetCanceled())) @@ -688,7 +723,10 @@ namespace Tgstation.Server.Host.Components.Watchdog || CheckActivationReason(ref activeServerReboot, MonitorActivationReason.ActiveServerRebooted) || CheckActivationReason(ref newDmbAvailable, MonitorActivationReason.NewDmbAvailable) || CheckActivationReason(ref activeLaunchParametersChanged, MonitorActivationReason.ActiveLaunchParametersUpdated) - || CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat); + || CheckActivationReason(ref heartbeat, MonitorActivationReason.Heartbeat) + || CheckActivationReason(ref serverPrimed, MonitorActivationReason.ActiveServerPrimed); + + UpdateMonitoredTasks(); if (!anyActivation) moreActivationsToProcess = false; @@ -769,6 +807,7 @@ namespace Tgstation.Server.Host.Components.Watchdog Logger.LogTrace("Monitor exiting..."); } + #pragma warning restore CA1502 /// /// Starts all s. diff --git a/src/Tgstation.Server.Host/Configuration/README.md b/src/Tgstation.Server.Host/Configuration/README.md index 50f05e1290..83fdf539b9 100644 --- a/src/Tgstation.Server.Host/Configuration/README.md +++ b/src/Tgstation.Server.Host/Configuration/README.md @@ -1,5 +1,5 @@ # Configuration Classes -These types map directly to the settings used in the [appsettings.json](../appsettings.json) file and its derivatives. See the [Microsoft Docs on the ASP .NET Core configuration system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1) for details. +These types map directly to the settings used in the [appsettings.yml](../appsettings.yml) file and its derivatives. See the [Microsoft Docs on the ASP .NET Core configuration system](https://docs.microsoft.com/en-us/aspnet/core/fundamentals/configuration/?view=aspnetcore-3.1) for details. -When making changes here, it's important to also update the config version in [build/Version.props](../../../build/Version.props) according to semver semantics. You'll also need to update the constant in [GeneralConfiguration.cs](./GeneralConfigration.cs). \ No newline at end of file +When making changes here, it's important to also update the config version in [build/Version.props](../../../build/Version.props) according to semver semantics. You'll also need to update the constant in [GeneralConfiguration.cs](./GeneralConfigration.cs). diff --git a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs index 275acbe91a..28f68d21d7 100644 --- a/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs +++ b/src/Tgstation.Server.Host/Controllers/ControlPanelController.cs @@ -1,6 +1,8 @@ using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.StaticFiles; +using Microsoft.Extensions.Primitives; +using Microsoft.Net.Http.Headers; using System; using System.Net.Mime; using Tgstation.Server.Host.Core; @@ -42,6 +44,10 @@ namespace Tgstation.Server.Host.Controllers var contentTypeProvider = new FileExtensionContentTypeProvider(); if (!contentTypeProvider.TryGetContentType(fileInfo.Name, out var contentType)) contentType = MediaTypeNames.Application.Octet; + else if (contentType == MediaTypeNames.Application.Json) + Response.Headers.Add( + HeaderNames.CacheControl, + new StringValues(new[] { "public", "max-age=31536000", "immutable" })); return File(appRoute, contentType); } diff --git a/src/Tgstation.Server.Host/Controllers/HomeController.cs b/src/Tgstation.Server.Host/Controllers/HomeController.cs index 4dcad917d2..e8302baa4b 100644 --- a/src/Tgstation.Server.Host/Controllers/HomeController.cs +++ b/src/Tgstation.Server.Host/Controllers/HomeController.cs @@ -198,7 +198,7 @@ namespace Tgstation.Server.Host.Controllers { Version = assemblyInformationProvider.Version, ApiVersion = ApiHeaders.Version, - DMApiVersion = DMApiConstants.Version, + DMApiVersion = DMApiConstants.InteropVersion, MinimumPasswordLength = generalConfiguration.MinimumPasswordLength, InstanceLimit = generalConfiguration.InstanceLimit, UserLimit = generalConfiguration.UserLimit, diff --git a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs index c76b40682b..1114813a45 100644 --- a/src/Tgstation.Server.Host/Controllers/RepositoryController.cs +++ b/src/Tgstation.Server.Host/Controllers/RepositoryController.cs @@ -91,6 +91,7 @@ namespace Tgstation.Server.Host.Controllers { Instance = instance, CommitSha = repoSha, + Timestamp = await repository.TimestampCommit(repoSha, cancellationToken).ConfigureAwait(false), CompileJobs = new List(), ActiveTestMerges = new List() // non null vals for api returns }; diff --git a/src/Tgstation.Server.Host/Core/Application.cs b/src/Tgstation.Server.Host/Core/Application.cs index 8d0a203961..f16e31fe9c 100644 --- a/src/Tgstation.Server.Host/Core/Application.cs +++ b/src/Tgstation.Server.Host/Core/Application.cs @@ -492,7 +492,7 @@ namespace Tgstation.Server.Host.Core // End of request pipeline setup var masterVersionsAttribute = MasterVersionsAttribute.Instance; logger.LogTrace("Configuration version: {0}", masterVersionsAttribute.RawConfigurationVersion); - logger.LogTrace("DMAPI version: {0}", masterVersionsAttribute.RawDMApiVersion); + logger.LogTrace("DMAPI Interop version: {0}", masterVersionsAttribute.RawInteropVersion); logger.LogTrace("Web control panel version: {0}", masterVersionsAttribute.RawControlPanelVersion); logger.LogDebug("Starting hosting on port {0}...", serverPortProvider.HttpApiPort); diff --git a/src/Tgstation.Server.Host/Database/DatabaseContext.cs b/src/Tgstation.Server.Host/Database/DatabaseContext.cs index 9de0ffa3f2..7992a475cc 100644 --- a/src/Tgstation.Server.Host/Database/DatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/DatabaseContext.cs @@ -377,22 +377,22 @@ namespace Tgstation.Server.Host.Database /// /// Used by unit tests to remind us to setup the correct MSSQL migration downgrades. /// - internal static readonly Type MSLatestMigration = typeof(MSAddSwarmIdentifer); + internal static readonly Type MSLatestMigration = typeof(MSAddRevInfoTimestamp); /// /// Used by unit tests to remind us to setup the correct MYSQL migration downgrades. /// - internal static readonly Type MYLatestMigration = typeof(MYAddSwarmIdentifer); + internal static readonly Type MYLatestMigration = typeof(MYAddRevInfoTimestamp); /// /// Used by unit tests to remind us to setup the correct PostgresSQL migration downgrades. /// - internal static readonly Type PGLatestMigration = typeof(PGAddSwarmIdentifer); + internal static readonly Type PGLatestMigration = typeof(PGAddRevInfoTimestamp); /// /// Used by unit tests to remind us to setup the correct SQLite migration downgrades. /// - internal static readonly Type SLLatestMigration = typeof(SLAddSwarmIdentifer); + internal static readonly Type SLLatestMigration = typeof(SLAddRevInfoTimestamp); /// #pragma warning disable CA1502 // Cyclomatic complexity @@ -420,6 +420,15 @@ namespace Tgstation.Server.Host.Database // Update this with new migrations as they are made string targetMigration = null; + if (targetVersion < new Version(4, 8, 0)) + targetMigration = currentDatabaseType switch + { + DatabaseType.MySql => nameof(MYAddSwarmIdentifer), + DatabaseType.PostgresSql => nameof(PGAddSwarmIdentifer), + DatabaseType.SqlServer => nameof(MSAddSwarmIdentifer), + DatabaseType.Sqlite => nameof(SLAddSwarmIdentifer), + _ => throw new ArgumentException($"Invalid DatabaseType: {currentDatabaseType}", nameof(currentDatabaseType)), + }; if (targetVersion < new Version(4, 7, 0)) targetMigration = currentDatabaseType switch { diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.Designer.cs new file mode 100644 index 0000000000..3691118827 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.Designer.cs @@ -0,0 +1,902 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Metadata; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqlServerDatabaseContext))] + [Migration("20210112154040_MSAddRevInfoTimestamp")] + partial class MSAddRevInfoTimestamp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.10") + .HasAnnotation("Relational:MaxIdentifierLength", 128) + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChannelLimit") + .HasColumnType("int"); + + b.Property("ConnectionString") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("Enabled") + .HasColumnType("bit"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("decimal(20,0)"); + + b.Property("IrcChannel") + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Tag") + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + 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() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondVersion") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("uniqueidentifier"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("GitHubDeploymentId") + .HasColumnType("int"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("RepositoryOrigin") + .HasColumnType("nvarchar(max)"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdditionalParameters") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("bit"); + + b.Property("HeartbeatSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartupTimeout") + .HasColumnType("bigint"); + + b.Property("TopicRequestTimeout") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ApiValidationPort") + .HasColumnType("int"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AutoUpdateInterval") + .HasColumnType("bigint"); + + b.Property("ChatBotLimit") + .HasColumnType("int"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("Online") + .IsRequired() + .HasColumnType("bit"); + + b.Property("Path") + .IsRequired() + .HasColumnType("nvarchar(450)"); + + b.Property("SwarmIdentifer") + .HasColumnType("nvarchar(450)"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique() + .HasFilter("[SwarmIdentifer] IS NOT NULL"); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ByondRights") + .HasColumnType("decimal(20,0)"); + + b.Property("ChatBotRights") + .HasColumnType("decimal(20,0)"); + + b.Property("ConfigurationRights") + .HasColumnType("decimal(20,0)"); + + b.Property("DreamDaemonRights") + .HasColumnType("decimal(20,0)"); + + b.Property("DreamMakerRights") + .HasColumnType("decimal(20,0)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("decimal(20,0)"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("decimal(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CancelRight") + .HasColumnType("decimal(20,0)"); + + b.Property("CancelRightsType") + .HasColumnType("decimal(20,0)"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("bit"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("ErrorCode") + .HasColumnType("bigint"); + + b.Property("ExceptionDetails") + .HasColumnType("nvarchar(max)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetimeoffset"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("ExternalUserId") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AdministrationRights") + .HasColumnType("decimal(20,0)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("decimal(20,0)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique() + .HasFilter("[GroupId] IS NOT NULL"); + + b.HasIndex("UserId") + .IsUnique() + .HasFilter("[UserId] IS NOT NULL"); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("int"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("AccessToken") + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("AccessUser") + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("bit"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("bit"); + + b.Property("CommitterEmail") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("CommitterName") + .IsRequired() + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("bit"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("bit"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("bit"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("bit"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CommitSha") + .IsRequired() + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("Timestamp") + .HasColumnType("datetimeoffset"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Author") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Comment") + .HasColumnType("nvarchar(max)") + .HasMaxLength(10000); + + b.Property("MergedAt") + .HasColumnType("datetimeoffset"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasColumnType("nvarchar(40)") + .HasMaxLength(40); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + b.Property("Url") + .IsRequired() + .HasColumnType("nvarchar(max)"); + + 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() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("CanonicalName") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetimeoffset"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("bit"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetimeoffset"); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.Property("PasswordHash") + .HasColumnType("nvarchar(max)"); + + b.Property("SystemIdentifier") + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique() + .HasFilter("[SystemIdentifier] IS NOT NULL"); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("SqlServer:ValueGenerationStrategy", SqlServerValueGenerationStrategy.IdentityColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("nvarchar(100)") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.cs new file mode 100644 index 0000000000..73f94d358b --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154040_MSAddRevInfoTimestamp.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Add the Timestamp column to RevisionInformations for MSSQL. + /// + public partial class MSAddRevInfoTimestamp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.AddColumn( + name: "Timestamp", + table: "RevisionInformations", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropColumn( + name: "Timestamp", + table: "RevisionInformations"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.Designer.cs new file mode 100644 index 0000000000..35e54bb9e7 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.Designer.cs @@ -0,0 +1,886 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(MySqlDatabaseContext))] + [Migration("20210112154123_MYAddRevInfoTimestamp")] + partial class MYAddRevInfoTimestamp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.10") + .HasAnnotation("Relational:MaxIdentifierLength", 64); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConnectionString") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("Enabled") + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("bigint unsigned"); + + b.Property("IrcChannel") + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Tag") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ByondVersion") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("DMApiMajorVersion") + .HasColumnType("int"); + + b.Property("DMApiMinorVersion") + .HasColumnType("int"); + + b.Property("DMApiPatchVersion") + .HasColumnType("int"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("char(36)"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("GitHubDeploymentId") + .HasColumnType("int"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("int"); + + b.Property("Output") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RepositoryOrigin") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AdditionalParameters") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("HeartbeatSeconds") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Port") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("SecurityLevel") + .HasColumnType("int"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("int unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("int"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("int unsigned"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("smallint unsigned"); + + b.Property("ConfigurationType") + .HasColumnType("int"); + + b.Property("Name") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("Online") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("Path") + .IsRequired() + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.Property("SwarmIdentifer") + .HasColumnType("varchar(255) CHARACTER SET utf8mb4"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ByondRights") + .HasColumnType("bigint unsigned"); + + b.Property("ChatBotRights") + .HasColumnType("bigint unsigned"); + + b.Property("ConfigurationRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamDaemonRights") + .HasColumnType("bigint unsigned"); + + b.Property("DreamMakerRights") + .HasColumnType("bigint unsigned"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("bigint unsigned"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("bigint unsigned"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CancelRight") + .HasColumnType("bigint unsigned"); + + b.Property("CancelRightsType") + .HasColumnType("bigint unsigned"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("ErrorCode") + .HasColumnType("int unsigned"); + + b.Property("ExceptionDetails") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("ExternalUserId") + .IsRequired() + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("int"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AdministrationRights") + .HasColumnType("bigint unsigned"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("bigint unsigned"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("int"); + + b.Property("Port") + .HasColumnType("smallint unsigned"); + + b.Property("ProcessId") + .HasColumnType("int"); + + b.Property("RebootState") + .HasColumnType("int"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("AccessToken") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("AccessUser") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("CommitterEmail") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("CommitterName") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("CommitSha") + .IsRequired() + .HasColumnType("varchar(40) CHARACTER SET utf8mb4") + .HasMaxLength(40); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasColumnType("varchar(40) CHARACTER SET utf8mb4") + .HasMaxLength(40); + + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Author") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("Comment") + .HasColumnType("longtext CHARACTER SET utf8mb4") + .HasMaxLength(10000); + + b.Property("MergedAt") + .HasColumnType("datetime(6)"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("int"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasColumnType("varchar(40) CHARACTER SET utf8mb4") + .HasMaxLength(40); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("Url") + .IsRequired() + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + 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() + .HasColumnType("bigint"); + + b.Property("CanonicalName") + .IsRequired() + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("datetime(6)"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("tinyint(1)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("datetime(6)"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.Property("PasswordHash") + .HasColumnType("longtext CHARACTER SET utf8mb4"); + + b.Property("SystemIdentifier") + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("varchar(100) CHARACTER SET utf8mb4") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.cs new file mode 100644 index 0000000000..3c7576ba70 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154123_MYAddRevInfoTimestamp.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Add the Timestamp column to RevisionInformations for MYSQL. + /// + public partial class MYAddRevInfoTimestamp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.AddColumn( + name: "Timestamp", + table: "RevisionInformations", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropColumn( + name: "Timestamp", + table: "RevisionInformations"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.Designer.cs new file mode 100644 index 0000000000..0cb1f21fa1 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.Designer.cs @@ -0,0 +1,896 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Npgsql.EntityFrameworkCore.PostgreSQL.Metadata; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(PostgresSqlDatabaseContext))] + [Migration("20210112154203_PGAddRevInfoTimestamp")] + partial class PGAddRevInfoTimestamp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn) + .HasAnnotation("ProductVersion", "3.1.10") + .HasAnnotation("Relational:MaxIdentifierLength", 63); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ChannelLimit") + .HasColumnType("integer"); + + b.Property("ConnectionString") + .IsRequired() + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("Enabled") + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Name") + .IsRequired() + .HasColumnType("character varying(100)") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("ReconnectionInterval") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ChatSettingsId") + .HasColumnType("bigint"); + + b.Property("DiscordChannelId") + .HasColumnType("numeric(20,0)"); + + b.Property("IrcChannel") + .HasColumnType("character varying(100)") + .HasMaxLength(100); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Tag") + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ByondVersion") + .IsRequired() + .HasColumnType("text"); + + b.Property("DMApiMajorVersion") + .HasColumnType("integer"); + + b.Property("DMApiMinorVersion") + .HasColumnType("integer"); + + b.Property("DMApiPatchVersion") + .HasColumnType("integer"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("uuid"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("text"); + + b.Property("GitHubDeploymentId") + .HasColumnType("integer"); + + b.Property("GitHubRepoId") + .HasColumnType("bigint"); + + b.Property("JobId") + .HasColumnType("bigint"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("integer"); + + b.Property("Output") + .IsRequired() + .HasColumnType("text"); + + b.Property("RepositoryOrigin") + .HasColumnType("text"); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AdditionalParameters") + .IsRequired() + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("HeartbeatSeconds") + .HasColumnType("bigint"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("SecurityLevel") + .HasColumnType("integer"); + + b.Property("StartupTimeout") + .HasColumnType("bigint"); + + b.Property("TopicRequestTimeout") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ApiValidationPort") + .HasColumnType("integer"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("integer"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("ProjectName") + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AutoUpdateInterval") + .HasColumnType("bigint"); + + b.Property("ChatBotLimit") + .HasColumnType("integer"); + + b.Property("ConfigurationType") + .HasColumnType("integer"); + + b.Property("Name") + .IsRequired() + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("Online") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("Path") + .IsRequired() + .HasColumnType("text"); + + b.Property("SwarmIdentifer") + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ByondRights") + .HasColumnType("numeric(20,0)"); + + b.Property("ChatBotRights") + .HasColumnType("numeric(20,0)"); + + b.Property("ConfigurationRights") + .HasColumnType("numeric(20,0)"); + + b.Property("DreamDaemonRights") + .HasColumnType("numeric(20,0)"); + + b.Property("DreamMakerRights") + .HasColumnType("numeric(20,0)"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("numeric(20,0)"); + + b.Property("PermissionSetId") + .HasColumnType("bigint"); + + b.Property("RepositoryRights") + .HasColumnType("numeric(20,0)"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CancelRight") + .HasColumnType("numeric(20,0)"); + + b.Property("CancelRightsType") + .HasColumnType("numeric(20,0)"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("CancelledById") + .HasColumnType("bigint"); + + b.Property("Description") + .IsRequired() + .HasColumnType("text"); + + b.Property("ErrorCode") + .HasColumnType("bigint"); + + b.Property("ExceptionDetails") + .HasColumnType("text"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("StartedById") + .HasColumnType("bigint"); + + b.Property("StoppedAt") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("ExternalUserId") + .IsRequired() + .HasColumnType("character varying(100)") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("integer"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AdministrationRights") + .HasColumnType("numeric(20,0)"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("InstanceManagerRights") + .HasColumnType("numeric(20,0)"); + + b.Property("UserId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("text"); + + b.Property("CompileJobId") + .HasColumnType("bigint"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("integer"); + + b.Property("Port") + .HasColumnType("integer"); + + b.Property("ProcessId") + .HasColumnType("integer"); + + b.Property("RebootState") + .HasColumnType("integer"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("AccessToken") + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("AccessUser") + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("CommitterEmail") + .IsRequired() + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("CommitterName") + .IsRequired() + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("boolean"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("RevisionInformationId") + .HasColumnType("bigint"); + + b.Property("TestMergeId") + .HasColumnType("bigint"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CommitSha") + .IsRequired() + .HasColumnType("character varying(40)") + .HasMaxLength(40); + + b.Property("InstanceId") + .HasColumnType("bigint"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasColumnType("character varying(40)") + .HasMaxLength(40); + + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Author") + .IsRequired() + .HasColumnType("text"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Comment") + .HasColumnType("character varying(10000)") + .HasMaxLength(10000); + + b.Property("MergedAt") + .HasColumnType("timestamp with time zone"); + + b.Property("MergedById") + .HasColumnType("bigint"); + + b.Property("Number") + .HasColumnType("integer"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("bigint"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasColumnType("character varying(40)") + .HasMaxLength(40); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("text"); + + b.Property("Url") + .IsRequired() + .HasColumnType("text"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("CanonicalName") + .IsRequired() + .HasColumnType("character varying(100)") + .HasMaxLength(100); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("timestamp with time zone"); + + b.Property("CreatedById") + .HasColumnType("bigint"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("boolean"); + + b.Property("GroupId") + .HasColumnType("bigint"); + + b.Property("LastPasswordUpdate") + .HasColumnType("timestamp with time zone"); + + b.Property("Name") + .IsRequired() + .HasColumnType("character varying(100)") + .HasMaxLength(100); + + b.Property("PasswordHash") + .HasColumnType("text"); + + b.Property("SystemIdentifier") + .HasColumnType("character varying(100)") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("bigint") + .HasAnnotation("Npgsql:ValueGenerationStrategy", NpgsqlValueGenerationStrategy.IdentityByDefaultColumn); + + b.Property("Name") + .IsRequired() + .HasColumnType("character varying(100)") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.cs new file mode 100644 index 0000000000..0db9ccf53d --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154203_PGAddRevInfoTimestamp.cs @@ -0,0 +1,35 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Add the Timestamp column to RevisionInformations for PostgresSQL. + /// + public partial class PGAddRevInfoTimestamp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.AddColumn( + name: "Timestamp", + table: "RevisionInformations", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.DropColumn( + name: "Timestamp", + table: "RevisionInformations"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.Designer.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.Designer.cs new file mode 100644 index 0000000000..61cbdf5fa9 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.Designer.cs @@ -0,0 +1,885 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + [DbContext(typeof(SqliteDatabaseContext))] + [Migration("20210112154243_SLAddRevInfoTimestamp")] + partial class SLAddRevInfoTimestamp + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder + .HasAnnotation("ProductVersion", "3.1.10"); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChannelLimit") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ConnectionString") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("Enabled") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("ReconnectionInterval") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "Name") + .IsUnique(); + + b.ToTable("ChatBots"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ChatSettingsId") + .HasColumnType("INTEGER"); + + b.Property("DiscordChannelId") + .HasColumnType("INTEGER"); + + b.Property("IrcChannel") + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.Property("IsAdminChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsUpdatesChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("IsWatchdogChannel") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Tag") + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.HasKey("Id"); + + b.HasIndex("ChatSettingsId", "DiscordChannelId") + .IsUnique(); + + b.HasIndex("ChatSettingsId", "IrcChannel") + .IsUnique(); + + b.ToTable("ChatChannels"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ByondVersion") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DMApiMajorVersion") + .HasColumnType("INTEGER"); + + b.Property("DMApiMinorVersion") + .HasColumnType("INTEGER"); + + b.Property("DMApiPatchVersion") + .HasColumnType("INTEGER"); + + b.Property("DirectoryName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("DmeName") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("GitHubDeploymentId") + .HasColumnType("INTEGER"); + + b.Property("GitHubRepoId") + .HasColumnType("INTEGER"); + + b.Property("JobId") + .HasColumnType("INTEGER"); + + b.Property("MinimumSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("Output") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("RepositoryOrigin") + .HasColumnType("TEXT"); + + b.Property("RevisionInformationId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("DirectoryName"); + + b.HasIndex("JobId") + .IsUnique(); + + b.HasIndex("RevisionInformationId"); + + b.ToTable("CompileJobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdditionalParameters") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("AllowWebClient") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("AutoStart") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("HeartbeatSeconds") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("Port") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("SecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("StartupTimeout") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("TopicRequestTimeout") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamDaemonSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ApiValidationPort") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ApiValidationSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("ProjectName") + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("RequireDMApiValidation") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("DreamMakerSettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Instance", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AutoUpdateInterval") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ChatBotLimit") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ConfigurationType") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("Online") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("Path") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("SwarmIdentifer") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("Path", "SwarmIdentifer") + .IsUnique(); + + b.ToTable("Instances"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ByondRights") + .HasColumnType("INTEGER"); + + b.Property("ChatBotRights") + .HasColumnType("INTEGER"); + + b.Property("ConfigurationRights") + .HasColumnType("INTEGER"); + + b.Property("DreamDaemonRights") + .HasColumnType("INTEGER"); + + b.Property("DreamMakerRights") + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("InstancePermissionSetRights") + .HasColumnType("INTEGER"); + + b.Property("PermissionSetId") + .HasColumnType("INTEGER"); + + b.Property("RepositoryRights") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId"); + + b.HasIndex("PermissionSetId", "InstanceId") + .IsUnique(); + + b.ToTable("InstancePermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CancelRight") + .HasColumnType("INTEGER"); + + b.Property("CancelRightsType") + .HasColumnType("INTEGER"); + + b.Property("Cancelled") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("CancelledById") + .HasColumnType("INTEGER"); + + b.Property("Description") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ErrorCode") + .HasColumnType("INTEGER"); + + b.Property("ExceptionDetails") + .HasColumnType("TEXT"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("StartedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("StartedById") + .HasColumnType("INTEGER"); + + b.Property("StoppedAt") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("CancelledById"); + + b.HasIndex("InstanceId"); + + b.HasIndex("StartedById"); + + b.ToTable("Jobs"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("ExternalUserId") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.Property("Provider") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("UserId"); + + b.HasIndex("Provider", "ExternalUserId") + .IsUnique(); + + b.ToTable("OAuthConnections"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AdministrationRights") + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("InstanceManagerRights") + .HasColumnType("INTEGER"); + + b.Property("UserId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("GroupId") + .IsUnique(); + + b.HasIndex("UserId") + .IsUnique(); + + b.ToTable("PermissionSets"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessIdentifier") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CompileJobId") + .HasColumnType("INTEGER"); + + b.Property("LaunchSecurityLevel") + .HasColumnType("INTEGER"); + + b.Property("Port") + .HasColumnType("INTEGER"); + + b.Property("ProcessId") + .HasColumnType("INTEGER"); + + b.Property("RebootState") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("CompileJobId"); + + b.ToTable("ReattachInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("AccessToken") + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("AccessUser") + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("AutoUpdatesKeepTestMerges") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("AutoUpdatesSynchronize") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("CommitterEmail") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("CommitterName") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("CreateGitHubDeployments") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("PostTestMergeComment") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("PushTestMergeCommits") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("ShowTestMergeCommitters") + .IsRequired() + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId") + .IsUnique(); + + b.ToTable("RepositorySettings"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("RevisionInformationId") + .HasColumnType("INTEGER"); + + b.Property("TestMergeId") + .HasColumnType("INTEGER"); + + b.HasKey("Id"); + + b.HasIndex("RevisionInformationId"); + + b.HasIndex("TestMergeId"); + + b.ToTable("RevInfoTestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CommitSha") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(40); + + b.Property("InstanceId") + .HasColumnType("INTEGER"); + + b.Property("OriginCommitSha") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(40); + + b.Property("Timestamp") + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("InstanceId", "CommitSha") + .IsUnique(); + + b.ToTable("RevisionInformations"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Author") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("BodyAtMerge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Comment") + .HasColumnType("TEXT") + .HasMaxLength(10000); + + b.Property("MergedAt") + .HasColumnType("TEXT"); + + b.Property("MergedById") + .HasColumnType("INTEGER"); + + b.Property("Number") + .HasColumnType("INTEGER"); + + b.Property("PrimaryRevisionInformationId") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("TargetCommitSha") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(40); + + b.Property("TitleAtMerge") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Url") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("Id"); + + b.HasIndex("MergedById"); + + b.HasIndex("PrimaryRevisionInformationId") + .IsUnique(); + + b.ToTable("TestMerges"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("CanonicalName") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.Property("CreatedAt") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("CreatedById") + .HasColumnType("INTEGER"); + + b.Property("Enabled") + .IsRequired() + .HasColumnType("INTEGER"); + + b.Property("GroupId") + .HasColumnType("INTEGER"); + + b.Property("LastPasswordUpdate") + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.Property("PasswordHash") + .HasColumnType("TEXT"); + + b.Property("SystemIdentifier") + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.HasIndex("CanonicalName") + .IsUnique(); + + b.HasIndex("CreatedById"); + + b.HasIndex("GroupId"); + + b.HasIndex("SystemIdentifier") + .IsUnique(); + + b.ToTable("Users"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.UserGroup", b => + { + b.Property("Id") + .ValueGeneratedOnAdd() + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT") + .HasMaxLength(100); + + b.HasKey("Id"); + + b.HasIndex("Name") + .IsUnique(); + + b.ToTable("Groups"); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatBot", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("ChatSettings") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ChatChannel", b => + { + b.HasOne("Tgstation.Server.Host.Models.ChatBot", "ChatSettings") + .WithMany("Channels") + .HasForeignKey("ChatSettingsId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.CompileJob", b => + { + b.HasOne("Tgstation.Server.Host.Models.Job", "Job") + .WithOne() + .HasForeignKey("Tgstation.Server.Host.Models.CompileJob", "JobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("CompileJobs") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamDaemonSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamDaemonSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamDaemonSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.DreamMakerSettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("DreamMakerSettings") + .HasForeignKey("Tgstation.Server.Host.Models.DreamMakerSettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.InstancePermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("InstancePermissionSets") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.PermissionSet", "PermissionSet") + .WithMany("InstancePermissionSets") + .HasForeignKey("PermissionSetId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.Job", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CancelledBy") + .WithMany() + .HasForeignKey("CancelledById"); + + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("Jobs") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.User", "StartedBy") + .WithMany() + .HasForeignKey("StartedById") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.OAuthConnection", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithMany("OAuthConnections") + .HasForeignKey("UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.PermissionSet", b => + { + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "GroupId") + .OnDelete(DeleteBehavior.Cascade); + + b.HasOne("Tgstation.Server.Host.Models.User", "User") + .WithOne("PermissionSet") + .HasForeignKey("Tgstation.Server.Host.Models.PermissionSet", "UserId") + .OnDelete(DeleteBehavior.Cascade); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.ReattachInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.CompileJob", "CompileJob") + .WithMany() + .HasForeignKey("CompileJobId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RepositorySettings", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithOne("RepositorySettings") + .HasForeignKey("Tgstation.Server.Host.Models.RepositorySettings", "InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevInfoTestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "RevisionInformation") + .WithMany("ActiveTestMerges") + .HasForeignKey("RevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.TestMerge", "TestMerge") + .WithMany("RevisonInformations") + .HasForeignKey("TestMergeId") + .OnDelete(DeleteBehavior.ClientNoAction) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.RevisionInformation", b => + { + b.HasOne("Tgstation.Server.Host.Models.Instance", "Instance") + .WithMany("RevisionInformations") + .HasForeignKey("InstanceId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.TestMerge", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "MergedBy") + .WithMany("TestMerges") + .HasForeignKey("MergedById") + .OnDelete(DeleteBehavior.Restrict) + .IsRequired(); + + b.HasOne("Tgstation.Server.Host.Models.RevisionInformation", "PrimaryRevisionInformation") + .WithOne("PrimaryTestMerge") + .HasForeignKey("Tgstation.Server.Host.Models.TestMerge", "PrimaryRevisionInformationId") + .OnDelete(DeleteBehavior.Cascade) + .IsRequired(); + }); + + modelBuilder.Entity("Tgstation.Server.Host.Models.User", b => + { + b.HasOne("Tgstation.Server.Host.Models.User", "CreatedBy") + .WithMany("CreatedUsers") + .HasForeignKey("CreatedById"); + + b.HasOne("Tgstation.Server.Host.Models.UserGroup", "Group") + .WithMany("Users") + .HasForeignKey("GroupId"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.cs b/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.cs new file mode 100644 index 0000000000..c351857d88 --- /dev/null +++ b/src/Tgstation.Server.Host/Database/Migrations/20210112154243_SLAddRevInfoTimestamp.cs @@ -0,0 +1,69 @@ +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +namespace Tgstation.Server.Host.Database.Migrations +{ + /// + /// Add the Timestamp column to RevisionInformations for SQLite. + /// + public partial class SLAddRevInfoTimestamp : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.AddColumn( + name: "Timestamp", + table: "RevisionInformations", + nullable: false, + defaultValue: new DateTimeOffset(new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified), new TimeSpan(0, 0, 0, 0, 0))); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + if (migrationBuilder == null) + throw new ArgumentNullException(nameof(migrationBuilder)); + + migrationBuilder.RenameTable( + name: "RevisionInformations", + newName: "RevisionInformations_down"); + + migrationBuilder.CreateTable( + name: "RevisionInformations", + columns: table => new + { + Id = table.Column(nullable: false) + .Annotation("Sqlite:Autoincrement", true), + CommitSha = table.Column(maxLength: 40, nullable: false), + OriginCommitSha = table.Column(maxLength: 40, nullable: false), + 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.Sql("INSERT INTO RevisionInformations (Id, CommitSha, OriginCommitSha, InstanceId) SELECT Id, CommitSha, OriginCommitSha, InstanceId FROM RevisionInformations_down"); + + migrationBuilder.DropTable( + name: "RevisionInformations_down"); + + migrationBuilder.RenameTable( + name: "RevisionInformations", + newName: "RevisionInformations_down"); + + migrationBuilder.RenameTable( + name: "RevisionInformations_down", + newName: "RevisionInformations"); + } + } +} diff --git a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs index 8707171228..f01161f68e 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/MySqlDatabaseContextModelSnapshot.cs @@ -566,6 +566,9 @@ namespace Tgstation.Server.Host.Database.Migrations .HasColumnType("varchar(40) CHARACTER SET utf8mb4") .HasMaxLength(40); + b.Property("Timestamp") + .HasColumnType("datetime(6)"); + b.HasKey("Id"); b.HasIndex("InstanceId", "CommitSha") diff --git a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs index a4d189000e..4668521f4a 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/PostgresSqlDatabaseContextModelSnapshot.cs @@ -573,6 +573,9 @@ namespace Tgstation.Server.Host.Database.Migrations .HasColumnType("character varying(40)") .HasMaxLength(40); + b.Property("Timestamp") + .HasColumnType("timestamp with time zone"); + b.HasKey("Id"); b.HasIndex("InstanceId", "CommitSha") diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs index 5f77127198..608266d424 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqlServerDatabaseContextModelSnapshot.cs @@ -578,6 +578,9 @@ namespace Tgstation.Server.Host.Database.Migrations .HasColumnType("nvarchar(40)") .HasMaxLength(40); + b.Property("Timestamp") + .HasColumnType("datetimeoffset"); + b.HasKey("Id"); b.HasIndex("InstanceId", "CommitSha") diff --git a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs index ca51e0514a..b12e593740 100644 --- a/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs +++ b/src/Tgstation.Server.Host/Database/Migrations/SqliteDatabaseContextModelSnapshot.cs @@ -565,6 +565,9 @@ namespace Tgstation.Server.Host.Database.Migrations .HasColumnType("TEXT") .HasMaxLength(40); + b.Property("Timestamp") + .HasColumnType("TEXT"); + b.HasKey("Id"); b.HasIndex("InstanceId", "CommitSha") diff --git a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs index 46bd93309c..9c4f6dab09 100644 --- a/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs +++ b/src/Tgstation.Server.Host/Database/MySqlDatabaseContext.cs @@ -1,4 +1,4 @@ -using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore; using Pomelo.EntityFrameworkCore.MySql.Infrastructure; using System; using Tgstation.Server.Host.Configuration; diff --git a/src/Tgstation.Server.Host/Extensions/Converters/VersionConverter.cs b/src/Tgstation.Server.Host/Extensions/Converters/VersionConverter.cs index 9d5ebeec77..3c18ce50e8 100644 --- a/src/Tgstation.Server.Host/Extensions/Converters/VersionConverter.cs +++ b/src/Tgstation.Server.Host/Extensions/Converters/VersionConverter.cs @@ -1,14 +1,35 @@ using Newtonsoft.Json; using System; using Tgstation.Server.Api; +using YamlDotNet.Core; +using YamlDotNet.Core.Events; +using YamlDotNet.Serialization; namespace Tgstation.Server.Host.Extensions.Converters { /// - /// for serializing s for BYOND. + /// and for serializing s for BYOND. /// - sealed class VersionConverter : JsonConverter + sealed class VersionConverter : JsonConverter, IYamlTypeConverter { + /// + /// Check if the supports (de)serializing a given . + /// + /// The to check. + /// If the method should if validation fails. + /// if is a , otherwise. + static bool CheckSupportsType(Type type, bool validate) + { + if (type == null) + throw new ArgumentNullException(nameof(type)); + + var supported = type == typeof(global::System.Version); + if (!supported && validate) + throw new NotSupportedException($"{nameof(VersionConverter)} does not convert {type}s!"); + + return supported; + } + /// public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) { @@ -16,7 +37,7 @@ namespace Tgstation.Server.Host.Extensions.Converters { writer.WriteNull(); } - else if (value is Version version) + else if (value is global::System.Version version) { writer.WriteValue(version.Semver().ToString()); } @@ -29,6 +50,11 @@ namespace Tgstation.Server.Host.Extensions.Converters /// public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { + if (reader == null) + throw new ArgumentNullException(nameof(reader)); + + CheckSupportsType(objectType, true); + if (reader.TokenType == JsonToken.Null) return null; @@ -36,7 +62,7 @@ namespace Tgstation.Server.Host.Extensions.Converters { try { - Version v = new Version((string)reader.Value); + var v = global::System.Version.Parse((string)reader.Value); return v.Semver(); } catch (Exception ex) @@ -50,6 +76,40 @@ namespace Tgstation.Server.Host.Extensions.Converters } /// - public override bool CanConvert(Type objectType) => objectType == typeof(Version); + public override bool CanConvert(Type objectType) => CheckSupportsType(objectType, false); + + /// + public bool Accepts(Type type) => CheckSupportsType(type, false); + + /// + public object ReadYaml(IParser parser, Type type) + { + if (parser == null) + throw new ArgumentNullException(nameof(parser)); + + CheckSupportsType(type, true); + + var scalar = parser.Consume(); + if (scalar == null) + return null; + + return global::System.Version.Parse(scalar.Value); + } + + /// + public void WriteYaml(IEmitter emitter, object value, Type type) + { + if (emitter == null) + throw new ArgumentNullException(nameof(emitter)); + + CheckSupportsType(type, true); + + var version = (global::System.Version)value; + emitter.Emit( + new Scalar( + version + ?.Semver() + .ToString())); + } } } diff --git a/src/Tgstation.Server.Host/Models/RevisionInformation.cs b/src/Tgstation.Server.Host/Models/RevisionInformation.cs index 3b99e8c26e..feeced9571 100644 --- a/src/Tgstation.Server.Host/Models/RevisionInformation.cs +++ b/src/Tgstation.Server.Host/Models/RevisionInformation.cs @@ -42,6 +42,7 @@ namespace Tgstation.Server.Host.Models public Api.Models.RevisionInformation ToApi() => new Api.Models.RevisionInformation { CommitSha = CommitSha, + Timestamp = Timestamp, OriginCommitSha = OriginCommitSha, PrimaryTestMerge = PrimaryTestMerge?.ToApi(), ActiveTestMerges = ActiveTestMerges.Select(x => x.TestMerge.ToApi()).ToList(), diff --git a/src/Tgstation.Server.Host/Program.cs b/src/Tgstation.Server.Host/Program.cs index fa83a096a9..ffe02ba262 100644 --- a/src/Tgstation.Server.Host/Program.cs +++ b/src/Tgstation.Server.Host/Program.cs @@ -5,6 +5,7 @@ using System.Linq; using System.Text; using System.Threading.Tasks; using Tgstation.Server.Host.Core; +using Tgstation.Server.Host.Properties; using Tgstation.Server.Host.System; namespace Tgstation.Server.Host @@ -17,7 +18,7 @@ namespace Tgstation.Server.Host /// /// The expected host watchdog . /// - internal static readonly Version HostWatchdogVersion = new Version(1, 1, 0); + internal static Version HostWatchdogVersion => Version.Parse(MasterVersionsAttribute.Instance.RawHostWatchdogVersion); /// /// The to use. diff --git a/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs b/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs index 20cc04786c..06d2665cbc 100644 --- a/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs +++ b/src/Tgstation.Server.Host/Properties/MasterVersionsAttribute.cs @@ -22,29 +22,37 @@ namespace Tgstation.Server.Host.Properties public string RawConfigurationVersion { get; } /// - /// The of the DMAPI version built. + /// The of the DMAPI interop version used. /// - public string RawDMApiVersion { get; } + public string RawInteropVersion { get; } /// /// The of the control panel version built. /// public string RawControlPanelVersion { get; } + /// + /// The of the control panel version built. + /// + public string RawHostWatchdogVersion { get; } + /// /// Initializes a new instance of the . /// /// The value of . - /// The value of . + /// The value of . /// The value of . + /// The value of . public MasterVersionsAttribute( string rawConfigurationVersion, - string rawDMApiVersion, - string rawControlPanelVersion) + string rawInteropVersion, + string rawControlPanelVersion, + string rawHostWatchdogVersion) { RawConfigurationVersion = rawConfigurationVersion ?? throw new ArgumentNullException(nameof(rawConfigurationVersion)); - RawDMApiVersion = rawDMApiVersion ?? throw new ArgumentNullException(nameof(rawDMApiVersion)); + RawInteropVersion = rawInteropVersion ?? throw new ArgumentNullException(nameof(rawInteropVersion)); RawControlPanelVersion = rawControlPanelVersion ?? throw new ArgumentNullException(nameof(rawControlPanelVersion)); + RawHostWatchdogVersion = rawHostWatchdogVersion ?? throw new ArgumentNullException(nameof(rawHostWatchdogVersion)); } } } diff --git a/src/Tgstation.Server.Host/README.md b/src/Tgstation.Server.Host/README.md index 83f08f4798..5f1aeca63c 100644 --- a/src/Tgstation.Server.Host/README.md +++ b/src/Tgstation.Server.Host/README.md @@ -25,7 +25,7 @@ Here's a breakdown of things in this directory - [.config](./.config) contains the dotnet-tools.json. At the time of writing, this is only used to set the version of the [dotnet ef tools](https://docs.microsoft.com/en-us/ef/core/miscellaneous/cli/) used to create database migrations. - [ClientApp](./ClientApp) contains scripts to build and deploy the web control panel with TGS. - [Components](./Components) is where the bulk of the TGS implementation lives. -- [Configuration](./Configuration) contains classes that partly make up the configuration json files (i.e. [appsettings.json](./appsettings.json)). +- [Configuration](./Configuration) contains classes that partly make up the configuration yaml files (i.e. [appsettings.yml](./appsettings.yml)). - [Controllers](./Controllers) is where HTTP API code lives and bridges it with component code. - [Core](./Core) contains [Application.cs](./Core/Application.cs) and other various helpers that don't belong anywhere else. - [Database](./Database) contains all database related code. diff --git a/src/Tgstation.Server.Host/ServerFactory.cs b/src/Tgstation.Server.Host/ServerFactory.cs index 55bbd24e3a..e739d5c75f 100644 --- a/src/Tgstation.Server.Host/ServerFactory.cs +++ b/src/Tgstation.Server.Host/ServerFactory.cs @@ -49,7 +49,25 @@ namespace Tgstation.Server.Host var basePath = IOManager.ResolvePath(); IHostBuilder CreateDefaultBuilder() => Microsoft.Extensions.Hosting.Host.CreateDefaultBuilder(args) - .ConfigureAppConfiguration((context, configuration) => configuration.SetBasePath(basePath)); + .ConfigureAppConfiguration((context, builder) => + { + builder.SetBasePath(basePath); + + builder.AddYamlFile("appsettings.yml", optional: true, reloadOnChange: true) + .AddYamlFile($"appsettings.{context.HostingEnvironment.EnvironmentName}.yml", optional: true, reloadOnChange: true); + // reorganize the builder so our yaml configs don't override the env/cmdline configs + // values obtained via debugger + var environmentJsonConfig = builder.Sources[2]; + var envConfig = builder.Sources[3]; + var cmdLineConfig = builder.Sources[4]; + var baseYmlConfig = builder.Sources[5]; + var environmentYmlConfig = builder.Sources[6]; + builder.Sources[2] = baseYmlConfig; + builder.Sources[3] = environmentJsonConfig; + builder.Sources[4] = environmentYmlConfig; + builder.Sources[5] = envConfig; + builder.Sources[6] = cmdLineConfig; + }); var setupWizardHostBuilder = CreateDefaultBuilder() .UseSetupApplication(); diff --git a/src/Tgstation.Server.Host/Setup/SetupWizard.cs b/src/Tgstation.Server.Host/Setup/SetupWizard.cs index 6a7c99e03b..af806cab3f 100644 --- a/src/Tgstation.Server.Host/Setup/SetupWizard.cs +++ b/src/Tgstation.Server.Host/Setup/SetupWizard.cs @@ -4,7 +4,6 @@ using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Options; using MySql.Data.MySqlClient; -using Newtonsoft.Json; using Npgsql; using System; using System.Collections.Generic; @@ -20,8 +19,10 @@ using System.Threading.Tasks; using Tgstation.Server.Host.Configuration; using Tgstation.Server.Host.Core; using Tgstation.Server.Host.Database; +using Tgstation.Server.Host.Extensions.Converters; using Tgstation.Server.Host.IO; using Tgstation.Server.Host.System; +using YamlDotNet.Serialization; namespace Tgstation.Server.Host.Setup { @@ -841,8 +842,13 @@ namespace Tgstation.Server.Host.Setup { SwarmConfiguration.Section, swarmConfiguration }, }; - var json = JsonConvert.SerializeObject(map, Formatting.Indented); - var configBytes = Encoding.UTF8.GetBytes(json); + var serializer = new SerializerBuilder() + .WithTypeConverter(new VersionConverter()) + .Build(); + + var serializedYaml = serializer.Serialize(map); + + var configBytes = Encoding.UTF8.GetBytes(serializedYaml); reloadTcs = new TaskCompletionSource(); @@ -863,9 +869,9 @@ namespace Tgstation.Server.Host.Setup { await console.WriteAsync(e.Message, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); - await console.WriteAsync("For your convienence, here's the json we tried to write out:", true, cancellationToken).ConfigureAwait(false); + await console.WriteAsync("For your convienence, here's the yaml we tried to write out:", true, cancellationToken).ConfigureAwait(false); await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); - await console.WriteAsync(json, true, cancellationToken).ConfigureAwait(false); + await console.WriteAsync(serializedYaml, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync(null, true, cancellationToken).ConfigureAwait(false); await console.WriteAsync("Press any key to exit...", true, cancellationToken).ConfigureAwait(false); await console.PressAnyKeyAsync(cancellationToken).ConfigureAwait(false); @@ -930,7 +936,7 @@ namespace Tgstation.Server.Host.Setup return; } - var userConfigFileName = String.Format(CultureInfo.InvariantCulture, "appsettings.{0}.json", hostingEnvironment.EnvironmentName); + var userConfigFileName = String.Format(CultureInfo.InvariantCulture, "appsettings.{0}.yml", hostingEnvironment.EnvironmentName); async Task HandleSetupCancel() { diff --git a/src/Tgstation.Server.Host/Setup/SetupWizardMode.cs b/src/Tgstation.Server.Host/Setup/SetupWizardMode.cs index 8d0798b88f..2de9926e82 100644 --- a/src/Tgstation.Server.Host/Setup/SetupWizardMode.cs +++ b/src/Tgstation.Server.Host/Setup/SetupWizardMode.cs @@ -6,7 +6,7 @@ namespace Tgstation.Server.Host.Setup public enum SetupWizardMode { /// - /// Run the wizard if the appsettings.{Environment}.json is not present or empty. + /// Run the wizard if the appsettings.{Environment}.yml is not present or empty. /// Autodetect, diff --git a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj index 3951c10411..9008024d37 100644 --- a/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj +++ b/src/Tgstation.Server.Host/Tgstation.Server.Host.csproj @@ -49,8 +49,9 @@ <_Parameter1>$(TgsConfigVersion) - <_Parameter2>$(TgsDmapiVersion) + <_Parameter2>$(TgsInteropVersion) <_Parameter3>$(TgsControlPanelVersion) + <_Parameter4>$(TgsHostWatchdogVersion) @@ -83,6 +84,7 @@ + @@ -118,7 +120,11 @@ - + + + + + PreserveNewest diff --git a/src/Tgstation.Server.Host/appsettings.json b/src/Tgstation.Server.Host/appsettings.json deleted file mode 100644 index efc04c7461..0000000000 --- a/src/Tgstation.Server.Host/appsettings.json +++ /dev/null @@ -1,66 +0,0 @@ -{ - "General": { - "MinimumPasswordLength": 15, - "GitHubAccessToken": null, - "SetupWizardMode": "AutoDetect", - "ByondTopicTimeout": 5000, - "RestartTimeout": 60000, - "ApiPort": 5000, - "UseBasicWatchdog": false, - "UserLimit": 100, - "UserGroupLimit": 25, - "InstanceLimit": 10, - "ValidInstancePaths": null, - "HostApiDocumentation": false - }, - "FileLogging": { - "Directory": null, - "Disable": false, - "LogLevel": "Debug", - "MicrosoftLogLevel": "Warning" - }, - "Logging": { - "IncludeScopes": false, - "Debug": { - "LogLevel": { - "Default": "Debug", - "Microsoft": "Information" - } - }, - "Console": { - "LogLevel": { - "Default": "Trace", - "Microsoft": "Warning" - } - } - }, - "ControlPanel": { - "Enable": false, - "AllowAnyOrigin": false, - "AllowedOrigins": [] - }, - "Updates": { - "GitHubRepositoryId": 92952846, - "GitTagPrefix": "tgstation-server-v", - "UpdatePackageAssetName": "ServerUpdatePackage.zip" - }, - "Database": { - "DropDatabase": false, - "DatabaseType": "SqlServer", - "ResetAdminPassword": false, - "ServerVersion": null, - "ConnectionString": "Data Source=(local);Initial Catalog=TGS;Integrated Security=True" - }, - "Security": { - "TokenExpiryMinutes": 15, - "TokenClockSkewMinutes": 1, - "TokenSigningKeyByteAmount": 256, - "CustomTokenSigningKeyBase64": null, - "OAuth": { - "GitHub": null, - "Discord": null, - "TGForums": null, - "Keycloak": null - } - } -} diff --git a/src/Tgstation.Server.Host/appsettings.yml b/src/Tgstation.Server.Host/appsettings.yml new file mode 100644 index 0000000000..b53cd2832a --- /dev/null +++ b/src/Tgstation.Server.Host/appsettings.yml @@ -0,0 +1,52 @@ +General: + MinimumPasswordLength: 15 + GitHubAccessToken: + SetupWizardMode: AutoDetect + ByondTopicTimeout: 5000 + RestartTimeout: 60000 + ApiPort: 5000 + UseBasicWatchdog: false + UserLimit: 100 + UserGroupLimit: 25 + InstanceLimit: 10 + ValidInstancePaths: + HostApiDocumentation: false +FileLogging: + Directory: + Disable: false + LogLevel: Debug + MicrosoftLogLevel: Warning +Logging: + IncludeScopes: false + Debug: + LogLevel: + Default: Debug + Microsoft: Information + Console: + LogLevel: + Default: Trace + Microsoft: Warning +ControlPanel: + Enable: false + AllowAnyOrigin: false + AllowedOrigins: [] +Updates: + GitHubRepositoryId: 92952846 + GitTagPrefix: tgstation-server-v + UpdatePackageAssetName: ServerUpdatePackage.zip +Database: + DropDatabase: false + DatabaseType: SqlServer + ResetAdminPassword: false + ServerVersion: + ConnectionString: Data Source=(local);Initial Catalog=TGS;Integrated Security=True +Security: + TokenExpiryMinutes: 15 + TokenClockSkewMinutes: 1 + TokenSigningKeyByteAmount: 256 + CustomTokenSigningKeyBase64: + OAuth: + GitHub: + Discord: + TGForums: + Keycloak: diff --git a/tests/DMAPI/BasicOperation/Test.dm b/tests/DMAPI/BasicOperation/Test.dm index bd8fedaa91..23dbc6295c 100644 --- a/tests/DMAPI/BasicOperation/Test.dm +++ b/tests/DMAPI/BasicOperation/Test.dm @@ -14,13 +14,6 @@ sleep(50) world.TgsTargetedChatBroadcast("Sample admin-only message", TRUE) - world.log << "Validating API sleep" - // Validate TGS_DMAPI_VERSION against DMAPI version used - var/datum/tgs_version/active_version = world.TgsApiVersion() - var/datum/tgs_version/dmapi_version = new /datum/tgs_version(TGS_DMAPI_VERSION) - if(!active_version.Equals(dmapi_version)) - text2file("DMAPI version [TGS_DMAPI_VERSION] does not match active API version [active_version.raw_parameter]", "test_fail_reason.txt") - var/list/world_params = params2list(world.params) if(!("test" in world_params) || world_params["test"] != "bababooey") text2file("Expected parameter test=bababooey but did not receive", "test_fail_reason.txt") diff --git a/tests/Tgstation.Server.Host.Tests/Extensions/Converters/TestVersionConverter.cs b/tests/Tgstation.Server.Host.Tests/Extensions/Converters/TestVersionConverter.cs new file mode 100644 index 0000000000..a650c0e97f --- /dev/null +++ b/tests/Tgstation.Server.Host.Tests/Extensions/Converters/TestVersionConverter.cs @@ -0,0 +1,52 @@ +using Microsoft.VisualStudio.TestTools.UnitTesting; +using System; +using Tgstation.Server.Api; +using YamlDotNet.Serialization; + +namespace Tgstation.Server.Host.Extensions.Converters.Tests +{ + [TestClass] + public sealed class TestVersionConverter + { + class TestObject + { + public Version Version { get; set; } + } + + readonly Version testVersion; + readonly string testYaml; + + public TestVersionConverter() + { + testVersion = new Version(1, 2, 3); + testYaml = $@"{nameof(TestObject.Version)}: {testVersion.Semver()}"; + } + + [TestMethod] + public void TestYamlSerialization() + { + var testObj = new TestObject + { + Version = testVersion + }; + + var serializedString = new SerializerBuilder() + .WithTypeConverter(new VersionConverter()) + .Build() + .Serialize(testObj); + + Assert.AreEqual(testYaml, serializedString.Trim()); + } + + [TestMethod] + public void TestYamlDeserialization() + { + var deserialized = new DeserializerBuilder() + .WithTypeConverter(new VersionConverter()) + .Build() + .Deserialize(testYaml); + + Assert.AreEqual(testVersion, deserialized.Version); + } + } +} diff --git a/tests/Tgstation.Server.Host.Tests/Extensions/TestServiceCollectionExtensions.cs b/tests/Tgstation.Server.Host.Tests/Extensions/TestServiceCollectionExtensions.cs index 67004ad997..09c2bd502d 100644 --- a/tests/Tgstation.Server.Host.Tests/Extensions/TestServiceCollectionExtensions.cs +++ b/tests/Tgstation.Server.Host.Tests/Extensions/TestServiceCollectionExtensions.cs @@ -1,4 +1,4 @@ -using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.VisualStudio.TestTools.UnitTesting; using Moq; @@ -16,7 +16,9 @@ namespace Tgstation.Server.Host.Extensions.Tests class BadConfig1 { +#pragma warning disable IDE0051 // Remove unused private members const string Section = "asdf"; +#pragma warning restore IDE0051 // Remove unused private members } class BadConfig2 diff --git a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs index 36f2da7900..f83c916ad6 100644 --- a/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/RepositoryTest.cs @@ -64,7 +64,7 @@ namespace Tgstation.Server.Tests.Instance clone = await repositoryClient.Clone(initalRepo, cancellationToken).ConfigureAwait(false); - await WaitForJob(clone.ActiveJob, 900, false, null, cancellationToken).ConfigureAwait(false); + await WaitForJob(clone.ActiveJob, 9000, false, null, cancellationToken).ConfigureAwait(false); var readAfterClone = await repositoryClient.Read(cancellationToken); Assert.AreEqual(initalRepo.Origin, readAfterClone.Origin); @@ -79,6 +79,7 @@ namespace Tgstation.Server.Tests.Instance Assert.IsNotNull(readAfterClone.RevisionInformation.OriginCommitSha); Assert.IsNull(readAfterClone.RevisionInformation.PrimaryTestMerge); Assert.AreEqual(readAfterClone.RevisionInformation.CommitSha, readAfterClone.RevisionInformation.OriginCommitSha); + Assert.AreNotEqual(default, readAfterClone.RevisionInformation.Timestamp); readAfterClone.Origin = new Uri("https://github.com/tgstation/tgstation"); await ApiAssert.ThrowsException(() => repositoryClient.Update(readAfterClone, cancellationToken), ErrorCode.RepoCantChangeOrigin); diff --git a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs index 7203d1c646..5cd9871d7b 100644 --- a/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs +++ b/tests/Tgstation.Server.Tests/Instance/WatchdogTest.cs @@ -144,7 +144,7 @@ namespace Tgstation.Server.Tests.Instance Assert.IsNotNull(newerCompileJob); Assert.AreNotEqual(initialCompileJob.Id, newerCompileJob.Id); Assert.AreEqual(DreamDaemonSecurity.Safe, newerCompileJob.MinimumSecurityLevel); - Assert.AreEqual(DMApiConstants.Version, daemonStatus.StagedCompileJob.DMApiVersion); + Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.StagedCompileJob.DMApiVersion); await instanceClient.DreamDaemon.Shutdown(cancellationToken); } @@ -162,7 +162,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); Assert.IsNotNull(daemonStatus.ActiveCompileJob); Assert.IsNull(daemonStatus.StagedCompileJob); - Assert.AreEqual(DMApiConstants.Version, daemonStatus.ActiveCompileJob.DMApiVersion); + Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.ActiveCompileJob.DMApiVersion); Assert.AreEqual(DreamDaemonSecurity.Safe, daemonStatus.ActiveCompileJob.MinimumSecurityLevel); Job startJob; @@ -307,7 +307,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); Assert.IsNotNull(daemonStatus.ActiveCompileJob); Assert.IsNull(daemonStatus.StagedCompileJob); - Assert.AreEqual(DMApiConstants.Version, daemonStatus.ActiveCompileJob.DMApiVersion); + Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.ActiveCompileJob.DMApiVersion); Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, daemonStatus.ActiveCompileJob.MinimumSecurityLevel); var startJob = await StartDD(cancellationToken).ConfigureAwait(false); @@ -347,7 +347,7 @@ namespace Tgstation.Server.Tests.Instance Assert.AreEqual(WatchdogStatus.Offline, daemonStatus.Status.Value); Assert.IsNotNull(daemonStatus.ActiveCompileJob); Assert.IsNull(daemonStatus.StagedCompileJob); - Assert.AreEqual(DMApiConstants.Version, daemonStatus.ActiveCompileJob.DMApiVersion); + Assert.AreEqual(DMApiConstants.InteropVersion, daemonStatus.ActiveCompileJob.DMApiVersion); Assert.AreEqual(DreamDaemonSecurity.Ultrasafe, daemonStatus.ActiveCompileJob.MinimumSecurityLevel); var startJob = await StartDD(cancellationToken).ConfigureAwait(false); diff --git a/tests/Tgstation.Server.Tests/TestingServer.cs b/tests/Tgstation.Server.Tests/TestingServer.cs index f35f5c38ad..de938a16e0 100644 --- a/tests/Tgstation.Server.Tests/TestingServer.cs +++ b/tests/Tgstation.Server.Tests/TestingServer.cs @@ -110,7 +110,7 @@ namespace Tgstation.Server.Tests args.Add($"Security:OAuth=null"); // SPECIFICALLY DELETE THE DEV APPSETTINGS, WE DON'T WANT IT IN THE WAY - File.Delete("appsettings.Development.json"); + File.Delete("appsettings.Development.yml"); if (!String.IsNullOrEmpty(gitHubAccessToken)) args.Add(String.Format(CultureInfo.InvariantCulture, "General:GitHubAccessToken={0}", gitHubAccessToken)); diff --git a/tests/Tgstation.Server.Tests/VersionsTest.cs b/tests/Tgstation.Server.Tests/VersionsTest.cs index 1a1cf908da..a7fde50140 100644 --- a/tests/Tgstation.Server.Tests/VersionsTest.cs +++ b/tests/Tgstation.Server.Tests/VersionsTest.cs @@ -99,7 +99,15 @@ namespace Tgstation.Server.Tests Assert.IsTrue(Version.TryParse(versionLine, out var actual)); Assert.AreEqual(expected, actual); - Assert.AreEqual(expected, DMApiConstants.Version); + } + + [TestMethod] + public void TestInteropVersion() + { + var versionString = versionsPropertyGroup.Element(xmlNamespace + "TgsInteropVersion").Value; + Assert.IsNotNull(versionString); + Assert.IsTrue(Version.TryParse(versionString, out var expected)); + Assert.AreEqual(expected, DMApiConstants.InteropVersion); } [TestMethod] diff --git a/tgstation-server.sln b/tgstation-server.sln index 7e771b4b01..6ff1e7bb44 100644 --- a/tgstation-server.sln +++ b/tgstation-server.sln @@ -65,6 +65,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tgs", "tgs", "{F7765A4B-021 src\DMAPI\tgs\includes.dm = src\DMAPI\tgs\includes.dm src\DMAPI\tgs\LICENSE = src\DMAPI\tgs\LICENSE src\DMAPI\tgs\README.md = src\DMAPI\tgs\README.md + src\DMAPI\tgs.dm = src\DMAPI\tgs.dm EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "core", "core", "{DCCBA9DA-47BA-4C70-823B-E99A3ACA0377}" @@ -134,6 +135,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "v5", "v5", "{FAEAD3B5-2EAB- src\DMAPI\tgs\v5\_defines.dm = src\DMAPI\tgs\v5\_defines.dm src\DMAPI\tgs\v5\api.dm = src\DMAPI\tgs\v5\api.dm src\DMAPI\tgs\v5\commands.dm = src\DMAPI\tgs\v5\commands.dm + src\DMAPI\tgs\v5\interop_version.dm = src\DMAPI\tgs\v5\interop_version.dm src\DMAPI\tgs\v5\README.md = src\DMAPI\tgs\v5\README.md src\DMAPI\tgs\v5\undefs.dm = src\DMAPI\tgs\v5\undefs.dm EndProjectSection diff --git a/tools/ReleaseNotes/Program.cs b/tools/ReleaseNotes/Program.cs index 65760f4b8d..8522b4066b 100644 --- a/tools/ReleaseNotes/Program.cs +++ b/tools/ReleaseNotes/Program.cs @@ -72,7 +72,7 @@ namespace ReleaseNotes Task milestoneTask = null; var milestoneTaskLock = new object(); - var releaseDictionary = new Dictionary>>(StringComparer.OrdinalIgnoreCase); + var releaseDictionary = new Dictionary>>(StringComparer.OrdinalIgnoreCase); var authorizedUsers = new Dictionary>(); bool postControlPanelMessage = false; @@ -136,7 +136,7 @@ namespace ReleaseNotes foreach (var I in notes) Console.WriteLine(component + " #" + fullPR.Number + " - " + I + " (@" + user.Login + ")"); - var tupleSelector = notes.Select(note => Tuple.Create(note, fullPR.Number)); + var tupleSelector = notes.Select(note => Tuple.Create(note, fullPR.Number, user.Login)); if (releaseDictionary.TryGetValue(component, out var currentValues)) currentValues.AddRange(tupleSelector); else @@ -169,6 +169,7 @@ namespace ReleaseNotes } if (trimmedLine.Length == 0) continue; + notes.Add(trimmedLine); } } @@ -255,13 +256,14 @@ namespace ReleaseNotes var apiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsApiVersion").Value); var configVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsConfigVersion").Value); var dmApiVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsDmapiVersion").Value); + var interopVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsInteropVersion").Value); var webControlVersion = Version.Parse(controlPanelVersionsPropertyGroup.Element(controlPanelXmlNamespace + "TgsControlPanelVersion").Value); var hostWatchdogVersion = Version.Parse(versionsPropertyGroup.Element(xmlNamespace + "TgsHostWatchdogVersion").Value); if (webControlVersion.Major == 0) postControlPanelMessage = true; - prefix = $"Please refer to the [README](https://github.com/tgstation/tgstation-server#setup) for setup instructions.{Environment.NewLine}{Environment.NewLine}#### Component Versions\nCore: {coreVersion}\nConfiguration: {configVersion}\nHTTP API: {apiVersion}\nDreamMaker API: {dmApiVersion}\n[Web Control Panel](https://github.com/tgstation/tgstation-server-control-panel): {webControlVersion}\nHost Watchdog: {hostWatchdogVersion}"; + prefix = $"Please refer to the [README](https://github.com/tgstation/tgstation-server#setup) for setup instructions.{Environment.NewLine}{Environment.NewLine}#### Component Versions\nCore: {coreVersion}\nConfiguration: {configVersion}\nHTTP API: {apiVersion}\nDreamMaker API: {dmApiVersion} (Interop: {interopVersion})\n[Web Control Panel](https://github.com/tgstation/tgstation-server-control-panel): {webControlVersion}\nHost Watchdog: {hostWatchdogVersion}"; break; case 3: prefix = "The /tg/station server suite"; @@ -325,6 +327,8 @@ namespace ReleaseNotes newNotes.Append(noteTuple.Item1); newNotes.Append(" (#"); newNotes.Append(noteTuple.Item2); + newNotes.Append(" @"); + newNotes.Append(noteTuple.Item3); newNotes.Append(')'); }